feat(models): port production UMF model extensions from pulseflow - #18
Open
dmautz1 wants to merge 5 commits into
Open
feat(models): port production UMF model extensions from pulseflow#18dmautz1 wants to merge 5 commits into
dmautz1 wants to merge 5 commits into
Conversation
Ports the model-level deltas that accumulated in the pulseflow fork of
tablespec back upstream, adapted to this repo's structure. These fields
power upsert merge semantics, SQL plan generation, and cross-pipeline
references in production healthcare pipelines.
New capabilities:
- MergeCondition + UMFColumn.merge_strategy/merge_source/merge_condition:
column-level upsert merge semantics (LEAST/GREATEST/COALESCE, gated by
a source-row condition)
- UMFColumn.internal: helper columns that participate in derivation but
are excluded from final output/DDL/schemas
- UMFColumn.nullable now accepts plain booleans alongside Nullable
contexts (is_nullable_for_all_contexts already handled bool)
- TableReference (models/pipeline.py) + parse_table_reference() on
ForeignKey, OutgoingRelationship, IncomingRelationship, and
DerivationCandidate for pipeline-qualified references
- ForeignKey.join_filter and OutgoingRelationship.alternative_joins
(OR-join paths) for SQL plan generation
- DerivationCandidate.union_value: per-UNION-branch literal values
- UMFMetadata: base_table_filter, base_join_column, final_filter,
union_base_tables, union_type, union_exclude_base, union_coalesce_base,
final_dedup — base-view and final-assembly controls for generated tables
- IngestionConfig.update_mode ('upsert' | 'update_only') for _U file
merge behavior; order_by docs extended to cover data columns and
meta_source_offset tie-breaking
- UMF.effective_primary_key / DEFAULT_PRIMARY_KEY (meta_checksum) as the
default merge key
- ValidationRules warns when ingested-stage expectation types are placed
in raw validation_rules (shift-left guidance)
- Domain type registry is now cached at module level instead of being
re-constructed on every column validation
Deliberately not ported (upstream direction wins): PySpark-style
data_type validation, bool→LOB nullable normalization, lowercase
table_name constraint, and advisory no-primary-key warnings (would
break filterwarnings=error test policy).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Workbook tab assignment for multi-sheet report authoring: columns can declare which Excel worksheet tab they belong to in a generated multi-sheet workbook report. Authoring-time provenance consumed by report-configuration tooling; bounded to Excel's 31-character tab-name limit. The Columns-sheet exporter writes a trailing "Report Sheet" header and the importer locates it by header name (consistent with the existing header-mapped reader), so older workbooks without the column import unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ions Source-layer specs (plain ingested tables) have no survivorship or derivation metadata, so their exported workbooks no longer carry empty Survivorship/Derivations tabs — matching the existing conditional emission of Validation Rules, Relationships, and File Format sheets. The importer already tolerates absent sheets, so round-trip behavior is unchanged for both spec shapes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…to end The ported UMFColumn.internal flag and bool-widened nullable field were model-only; their consumers didn't honor them: - Schema generators (SQL DDL, PySpark, JSON): skip internal helper columns — they participate in derivation but are excluded from the output table. - GX baseline: skip internal columns in column-level expectations, column-count/ordered-list structural checks, and cross-column date pairs; a plain `nullable: false` now emits the global not-null expectation (it was silently skipped as falsy). - Excel export: a boolean nullable writes its value into every context cell instead of defaulting all contexts to False. - Compatibility checker: boolean nullable maps to the synthetic "*" context instead of crashing on model_fields_set, so True -> False is still reported as a breaking tightening. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
First PR in a series porting production-hardened capabilities from the pulseflow fork of tablespec back upstream (models first — they unblock the SQL generation, ingestion, and validation ports that follow). All additions are adapted to this repo's structure and conventions rather than copied verbatim, and every ported field is wired through its upstream consumers so nothing lands as a model-only no-op.
New model capabilities
Upsert merge semantics
MergeCondition+UMFColumn.merge_strategy/merge_source/merge_condition— column-level merge behavior during upsert (keep_minimum→LEAST,keep_maximum→GREATEST,keep_existing→COALESCE), optionally gated on a source-row condition (e.g. only for certain load modes)UMF.effective_primary_key+DEFAULT_PRIMARY_KEY(meta_checksum) — default merge key for row-level dedup when no explicit PK is configuredIngestionConfig.update_mode(upsert|update_only) — how update-file rows merge into the ingested tableSQL plan generation controls (consumed by a follow-up SQLPlanGenerator port)
UMFMetadata.base_table_filter,base_join_column,final_filter,union_base_tables,union_type,union_exclude_base,union_coalesce_base,final_dedupForeignKey.join_filter— extra predicate appended to the JOIN ON clauseOutgoingRelationship.alternative_joins— OR-logic alternative join pathsDerivationCandidate.union_value— per-UNION-branch literal for synthetic columnsInternal helper columns — wired end to end
UMFColumn.internal: participates in derivation but excluded from the output table. Enforced ingenerate_sql_ddl/generate_pyspark_schema/generate_json_schema, and in GX baseline column-level expectations, column-count/ordered-list structural checks, and cross-column date pairsCross-pipeline references
TableReferencemodel (models/pipeline.py) withparse(),is_external(),to_qualified_name()parse_table_reference()onForeignKey,OutgoingRelationship,IncomingRelationship,DerivationCandidateReport workbook authoring
UMFColumn.report_sheet: worksheet tab assignment for multi-sheet workbook reports (bounded to Excel's 31-char tab-name limit), with full Excel export/import round-trip (header-located read, so older workbooks import unchanged)Boolean nullable — wired end to end
UMFColumn.nullableaccepts plain booleans alongsideNullablecontexts. Consumers fixed to honor it: GX baseline emits the global not-null fornullable: false(previously silently skipped as falsy), Excel export writes the bool into every context cell (previously defaulted all contexts to False), and the compatibility checker maps bool to a synthetic"*"context (previously crashed onmodel_fields_set) sotrue → falseis still a breaking tighteningQuality-of-life
ValidationRuleswarns when ingested-stage expectation types are placed in rawvalidation_rules(shift-left guidance)Deliberately NOT ported (upstream direction wins)
data_typenames/validation — this repo's SQL-style type system + EMBEDDING standsNullabletable_nameconstraint and requiredcanonical_namefilterwarnings = errortest policy repo-wide; can be revisited as opt-in lint checksUMF.mtimecache timestamp — deferred to the loader port that actually consumes itFields whose consumers arrive in later port batches (
UMFMetadataSQL-gen controls,alternative_joins,join_filter,union_value→ SQL generator;merge_*,update_mode,effective_primary_key→ ingestion/upsert) are intentionally declarative-only in this PR.Testing
filterwarningsmarkers for the new (correct) misclassification warningmain; full local run post-merge: 2,923 unit + 361 integration/conformance pass; ruff clean; pyright 0 errors on touched modules🤖 Generated with Claude Code