Skip to content

feat(person-attributes): make HR person attributes groupable with history - #2263

Merged
aleksdotbar merged 4 commits into
mainfrom
feat/person-attributes-cohorting
Aug 6, 2026
Merged

feat(person-attributes): make HR person attributes groupable with history#2263
aleksdotbar merged 4 commits into
mainfrom
feat/person-attributes-cohorting

Conversation

@aleksdotbar

@aleksdotbar aleksdotbar commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Problem

Analytics cannot group people by HR attributes. Connector data arrives as
current-state records and per-field change logs; neither says which value was
in effect during a measured period. Cohorting needs effective-dated values
joinable to metric facts.

Change

Ingestion half of person attributes: claims in silver, effective-dated values
in gold.

  • silver.class_person_attribute_claims — one row per account, field and
    transition. claim_action separates a value taking effect from the source
    emptying the field.
  • insight.account_attribute_values — half-open [valid_from, valid_to) per
    account and field. A cleared attribute has no current row, not an empty one.
  • attribute_claims macro plus one producer per HR connector (BambooHR,
    Workday, Microsoft Entra, Active Directory), each claiming a curated set of
    job, organisation, location and employment fields.

Both relations key on source account and carry no canonical person id.
Assignment joins at query time, so identity corrections need no rebuild.

Out of scope

Policy and governance, snapshot publishers, the analytics catalog, grouping
and comparison APIs, named groups.

Customer-defined fields are also out of scope. The snapshot versions on a
configured subset of them, so claiming the rest would stamp a change with the
timestamp of the next tracked change instead of its own. Claiming them
correctly needs full-payload extraction and versioning together, which lands
with the connector rework (#2270).

Validation

  • dbt parse, DAG and tag selection, dbt-convention audit.
  • dbt build on a local ClickHouse over synthetic fixtures across three
    states, all four connectors: value change, clear by NULL, clear by empty
    string, re-set after clear, custom-field change, unchanged record.
  • Rebuild is deterministic; unchanged input adds no rows.
  • Two build-integrity tests: claim grain and interval overlap. Both
    confirmed to fail on their injected defect.

Closes #2262

Summary by CodeRabbit

  • New Features

    • Added standardized person-attribute claim tracking across BambooHR, Workday, Microsoft Entra, and Active Directory.
    • Added effective-dated account attribute values, including department, title, status, location, organization, and employment details.
    • Added support for recording attribute changes, including updates and removals, with validity periods and source metadata.
  • Data Quality

    • Added validation to detect overlapping account-attribute periods and duplicate person-attribute claims.
  • Documentation

    • Updated connector documentation and data schemas to describe the new attribute claims and account values.

Connector-discovered person attributes become effective-dated facts
keyed by source account, ready for query-time cohorting.

- Claims derive from the SCD2 snapshots through a dedicated macro
  rather than fields_history: that macro compares with bare toString,
  so a transition into or out of NULL propagates to false and vanishes.
  A lost clear would leave a stale value open forever.
- A clear comes only from a delivered empty value. Record absence never
  closes an interval: no sync-completeness signal exists, and closing on
  a possibly-partial snapshot would fabricate end dates.
- The silver watermark is per source instance. A single global maximum
  over the union would permanently skip a slower connector's claims.
- Account values carry no canonical person id, so an identity correction
  needs no rebuild; assignment is joined at query time.

Refs #2028

Signed-off-by: Aleksandr Barkhatov <pm@aleks.bar>
@aleksdotbar
aleksdotbar requested a review from a team as a code owner August 6, 2026 06:29
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds connector-specific person-attribute claims models, a shared snapshot-diff macro, Silver claim consolidation, and Gold effective-dated account attribute values. It also adds ClickHouse tables, dbt schemas, integrity tests, and connector documentation.

Changes

Person attribute facts

Layer / File(s) Summary
Claim generation and connector models
src/ingestion/dbt/macros/attribute_claims.sql, src/ingestion/connectors/hr-directory/*/dbt/*person_attribute_claims*, docs/components/connectors/hr-directory/...
The attribute_claims macro converts snapshot changes into typed set and clear claims. Active Directory, BambooHR, Microsoft Entra, and Workday models configure the tracked fields and validate their claim contracts. Connector documentation lists the new models.
Silver claims contract and consolidation
src/ingestion/silver/hr/class_person_attribute_claims.sql, src/ingestion/silver/hr/schema.yml, src/ingestion/scripts/connectors-ddl/silver.sql
Silver unions claims from the four connectors, applies tenant/source version watermarks, and stores typed claim actions with version metadata.
Effective-dated account values
src/ingestion/gold/account_attribute_values.sql, src/ingestion/gold/schema.yml, src/ingestion/scripts/connectors-ddl/insight.sql
The Gold model derives half-open validity intervals from claim observations. The ClickHouse table stores source-account, field, value, validity, and ingestion metadata.
Claims and interval integrity validation
src/ingestion/dbt/tests/hr/assert_person_attribute_claims_unique_grain.sql, src/ingestion/dbt/tests/hr/assert_account_attribute_values_no_overlap.sql
New dbt tests detect duplicate claim grains and overlapping validity intervals.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested reviewers: cyberantonz, ktursunov

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR meets interval, clear-value, and source-account requirements, but excludes customer-defined fields required by issue #2262. Add dynamic support for newly extracted and customer-defined connector fields, or update issue #2262 to define that exclusion.
✅ Passed checks (4 passed)
Check name Status Explanation
Out of Scope Changes check ✅ Passed The changes remain within the linked issue scope for HR and directory attribute claims, effective-dated values, validation, and documentation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the addition of historical HR person attributes that support grouping-related use cases.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/person-attributes-cohorting

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@aleksdotbar
aleksdotbar marked this pull request as draft August 6, 2026 07:10
… set

Review follow-up.

- Claim custom fields from the <conn>_custom_fields var instead of
  discovering raw_data keys: a key the snapshot does not version on gets
  claims stamped with the next tracked change's time, not its own. One
  allow-list now drives versioning and claims; dynamic discovery returns
  with fetch-all extraction and raw-payload snapshot versioning.
- Drop three data tests asserting properties the interval builder
  guarantees by construction; keep the grain and overlap guards as
  untagged build-integrity tests failing the build.
- Add the missing insight.account_attribute_values bootstrap DDL and
  order the silver placeholder block; list the new models in the
  connector docs; trim duplicated prose.

Signed-off-by: Aleksandr Barkhatov <pm@aleks.bar>
One staging relation holds every tenant and connection of a connector, and
their sync workflows run concurrently. Filtering against a table-wide
max(_version) let whichever instance committed first discard another
instance's older-stamped claims, which then never reached silver, so the
per-instance watermark there had nothing to recover.

State that custom-field claims depend on a run supplying the tracked-fields
var, matching the snapshot versioning they follow.

Signed-off-by: Aleksandr Barkhatov <pm@aleks.bar>
Customer-defined fields in the raw payload are no longer claimed. The
snapshot versions on a configured subset of them, so a change to any other
key produced no version of its own and its claim carried the timestamp of
the next tracked change — a wrong effective date is worse than an absent
one. Collecting them correctly needs full-payload extraction and versioning
together, which arrives with the connector rework.

All four producers now have the same shape and read no raw payload.

Signed-off-by: Aleksandr Barkhatov <pm@aleks.bar>
@aleksdotbar
aleksdotbar marked this pull request as ready for review August 6, 2026 08:37
@aleksdotbar aleksdotbar changed the title feat(person-attributes): temporal claims and account values feat(person-attributes): make HR person attributes groupable with history Aug 6, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/ingestion/dbt/macros/attribute_claims.sql`:
- Around line 66-70: Update the history window used for prev_attrs in the
attribute-claims query to partition by the source account identity rather than
unique_key, while retaining the observed_at ordering and one-row lookback.
Ensure consecutive snapshot versions for the same account share a window so
changed attributes produce both set and clear claims correctly.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8cf8a9e1-9f88-49f7-94b3-ea457d5c08b1

📥 Commits

Reviewing files that changed from the base of the PR and between 0f2b4c6 and 3fba0b7.

📒 Files selected for processing (20)
  • docs/components/connectors/hr-directory/ms-entra/README.md
  • docs/components/connectors/hr-directory/workday/specs/DESIGN.md
  • docs/components/connectors/hr-directory/workday/workday.md
  • src/ingestion/connectors/hr-directory/active-directory/dbt/active_directory__person_attribute_claims.sql
  • src/ingestion/connectors/hr-directory/active-directory/dbt/schema.yml
  • src/ingestion/connectors/hr-directory/bamboohr/dbt/bamboohr__person_attribute_claims.sql
  • src/ingestion/connectors/hr-directory/bamboohr/dbt/schema.yml
  • src/ingestion/connectors/hr-directory/ms-entra/dbt/ms_entra__person_attribute_claims.sql
  • src/ingestion/connectors/hr-directory/ms-entra/dbt/schema.yml
  • src/ingestion/connectors/hr-directory/workday/dbt/schema.yml
  • src/ingestion/connectors/hr-directory/workday/dbt/workday__person_attribute_claims.sql
  • src/ingestion/dbt/macros/attribute_claims.sql
  • src/ingestion/dbt/tests/hr/assert_account_attribute_values_no_overlap.sql
  • src/ingestion/dbt/tests/hr/assert_person_attribute_claims_unique_grain.sql
  • src/ingestion/gold/account_attribute_values.sql
  • src/ingestion/gold/schema.yml
  • src/ingestion/scripts/connectors-ddl/insight.sql
  • src/ingestion/scripts/connectors-ddl/silver.sql
  • src/ingestion/silver/hr/class_person_attribute_claims.sql
  • src/ingestion/silver/hr/schema.yml

Comment on lines +66 to +70
lagInFrame(attrs) OVER (
PARTITION BY unique_key
ORDER BY observed_at
ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING
) AS prev_attrs

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Partition the history window by the source account.

unique_key identifies a snapshot version. The Active Directory snapshot documents that it includes _row_hash, so each changed version enters a different window partition. prev_attrs then defaults to an empty map for each transition.

This emits repeated set claims and suppresses clear claims. Gold does not close the prior interval when a field is cleared.

Proposed fix
         lagInFrame(attrs) OVER (
-            PARTITION BY unique_key
+            PARTITION BY
+                insight_tenant_id,
+                insight_source_id,
+                source_account_id
             ORDER BY observed_at
             ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING
         )                                                AS prev_attrs
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
lagInFrame(attrs) OVER (
PARTITION BY unique_key
ORDER BY observed_at
ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING
) AS prev_attrs
lagInFrame(attrs) OVER (
PARTITION BY
insight_tenant_id,
insight_source_id,
source_account_id
ORDER BY observed_at
ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING
) AS prev_attrs
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ingestion/dbt/macros/attribute_claims.sql` around lines 66 - 70, Update
the history window used for prev_attrs in the attribute-claims query to
partition by the source account identity rather than unique_key, while retaining
the observed_at ordering and one-row lookback. Ensure consecutive snapshot
versions for the same account share a window so changed attributes produce both
set and clear claims correctly.

@aleksdotbar
aleksdotbar added this pull request to the merge queue Aug 6, 2026
Merged via the queue into main with commit 1e75348 Aug 6, 2026
52 of 54 checks passed
@aleksdotbar
aleksdotbar deleted the feat/person-attributes-cohorting branch August 6, 2026 12:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Connector person attributes are not available to analytics as effective-dated facts

2 participants