fix(ingestion): drop always-NULL class_people.org_unit_id column - #2124
Conversation
`silver.class_people.org_unit_id Nullable(UUID)` was hardcoded to NULL by all
four HR staging models and had no producer anywhere: no `org_units` table
exists in ClickHouse or MariaDB, so no org-unit UUID is ever minted. The
org-chart domain design (`docs/domain/org-chart/specs/`) that would own such
UUIDs is unimplemented.
The column was reported by a data-quality audit as "high in bronze inputs but
0% in silver — lost in transform". It is neither: bronze carries `department`
(a name), not a UUID, and MariaDB `identity.org_chart` is a
`child_person_id -> parent_person_id` supervisor edge table with no
`org_unit_id` column at all.
Everything downstream that is called `org_unit_id` is already a department
NAME string, sourced from `department`:
bronze department
-> insight.people.org_unit_id (argMax(department))
-> insight.team_member (FROM insight.people)
-> frontend RawTeamMemberRow.org_unit_id (typed `string | null`)
-> frontend sends back `org_unit_id in ('Engineering', ...)`
The sole reader of the silver column was
`gold/metric_entity_cohorts_current.sql`, which coalesced past it to
`department_name`. That coalesce was also a latent trap: because it preferred
`org_unit_id`, populating the column would have flipped cohort ids from
department names to UUIDs while `insight.people` and the frontend kept using
names, silently emptying every peer metric. Dropping the column removes the
trap and stops the audit false positive from recurring.
Verified on the virtuozzo cluster: the rewritten cohort query returns 1402
rows / 63 cohorts / 1402 entities, and the symmetric set difference of
(tenant_id, entity_id, cohort_id) against the current view is 0 in both
directions. All four staging models still project an identical 25-column set,
satisfying cpt-dataflow-constraint-staging-class-column-types-match.
Refs: docs/domain/ingestion-data-flow/specs/DESIGN.md,
docs/domain/org-chart/specs/DESIGN.md
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Roman Mitasov <Roman.Mitasov@constructor.tech>
The committed DDL snapshot landed upstream (via the active-directory bootstrap-db fix) while `class_people.org_unit_id` still existed, so it declares a column this branch removes: - `connectors-ddl/silver.sql`: drop `org_unit_id Nullable(UUID)` from `silver.class_people`. - `connectors-ddl/insight.sql`: `metric_entity_cohorts_current` now reads `nullIf(department_name, '')` instead of coalescing the dropped column ahead of the department name. Every other `org_unit_id` in the snapshot is left untouched and is a different thing: `insight.sql` occurrences are the department NAME string (`String` columns, `argMax(department)`, `coalesce(department_name, 'Unknown')`), and `person.sql` occurrences belong to `person.persons`, the identity-owned golden record created by the init-identity migration. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Roman Mitasov <Roman.Mitasov@constructor.tech>
…' into fix/drop-dead-org-unit-id-column
|
Warning Review limit reached
Next review available in: 22 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (7)
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. Comment |
|
Problem
A data-quality audit flagged
silver.class_people.org_unit_idas "high in bronze inputs but 0% populated in silver — lost in transform", citing MariaDBidentity.org_chart(252 rows) as proof that org data exists.That framing is wrong on all three counts:
CAST(NULL AS Nullable(UUID)) AS org_unit_id, following the type-uniformity convention indocs/domain/ingestion-data-flow/specs/DESIGN.md.SELECT … FROM system.tables WHERE name ILIKE '%org_unit%'returns nothing in ClickHouse, and there is no such table in MariaDB either. The org-chart design (docs/domain/org-chart/specs/) that would mint these UUIDs is unimplemented — its PRD even claims all its tables live in ClickHouse, which is not the case.identity.org_chartis not an org-unit table. Its columns arechild_person_id→parent_person_id— a person-to-supervisor edge graph rebuilt by the identity-resolution service from BambooHRsupervisorEmail. It has noorg_unit_idcolumn, so it could never have been the source.What
org_unit_idactually means downstreamEverything in the serving path called
org_unit_idis already a department name string:Live values on virtuozzo are
VZ - R&D - Engineering,VZ - Support, etc. The frontend round-trips the value and never sees the silver column; the four CRM gold views likewise usecoalesce(department_name, 'Unknown') AS org_unit_id.The latent trap this removes
The only reader of the silver column was
gold/metric_entity_cohorts_current.sql:Because that coalesce preferred
org_unit_id, anyone "fixing" the reported 0% by populating the column would have flippedcohort_idfrom department names to UUIDs, whileinsight.peopleand the frontend kept sending names — silently emptying every peer metric tenant-wide. Dropping the column removes that possibility and stops the audit false positive from recurring. The replacement expression carries a comment warning against reintroducing a UUID branch without migratinginsight.peopleand the frontend in the same change.Changes
active_directory__to_class_people.sqlorg_unit_idprojectionbamboohr__to_class_people.sqlorg_unit_idprojectionms_entra__to_class_people.sqlorg_unit_idprojectionworkday__to_class_people.sqlorg_unit_idprojectiongold/metric_entity_cohorts_current.sqlcoalesce(...)→nullIf(department_name, '')connectors-ddl/silver.sqlorg_unit_id Nullable(UUID)from thesilver.class_peoplesnapshotconnectors-ddl/insight.sqlNo DDL migration is needed:
silver.class_peopleismaterialized='table', so dbt rebuilds it without the column.Every other
org_unit_idin the snapshot is deliberately untouched and is a different thing: theinsight.sqloccurrences are the department NAME string (Stringcolumns,argMax(department),coalesce(department_name, 'Unknown')), and theperson.sqloccurrences belong toperson.persons, the identity-owned golden record created by the init-identity migration.Verification
(tenant_id, entity_id, cohort_id)is 0 in both directions.cpt-dataflow-constraint-staging-class-column-types-match(removing the column from only some models would raiseCode: 386).org_unit_idno longer appears as a projection or column anywhere insrc/ingestion/connectors/,silver/,gold/, orconnectors-ddl/silver.sql— only in explanatory comments.insight-front, 51 occurrences): typedstring | null, documented as "Department the member belongs to", zero UUID validation or parsing.Connector wiring invariantscheck added in fix: active-directory half-landed connector — unbreak bootstrap-db, wire image ref, add PR-time wiring guard (#2048) #2056 passes on this branch.Out of scope
insight.peopleis bamboohr-only. It readsbronze_bamboohr.employeesdirectly (bronze → gold, skipping silver), so a tenant on ms-entra, workday, or active-directory gets an emptyinsight.peopleand loses all org attribution. Virtuozzo only survives because BambooHR is its sole HR source. Repointing it at silver would need a fix forsupervisor_emailfirst:class_people.manager_person_idholdssupervisorEId(an ID, not an email).org_unit_id/person_id$filtervalues inanalytics/src/api/handlers.rs(a TODO at the injection site), whichdocs/components/backend/specs/analytics-views-api.mdrequires. Tenant isolation is enforced; within-tenant cross-team reads are not.The
manager_person_idtype mismatch originally reported here was fixed upstream in #2056 and is no longer part of this PR.Refs:
docs/domain/ingestion-data-flow/specs/DESIGN.md,docs/domain/org-chart/specs/DESIGN.md🤖 Generated with Claude Code