Skip to content

Commit d89ca84

Browse files
committed
feat: implement discovery-first global ID mapping to eliminate redundant registry I/O; update unit tests accordingly
1 parent 887efa6 commit d89ca84

10 files changed

Lines changed: 462 additions & 262 deletions

File tree

data_pipeline/assembly/assembly_executor.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,10 @@ def orchestrate_event_assembly(run_context: RunContext, report: Dict) -> bool:
147147
log_error(f"Unexpected error processing event assembly: {e}", report)
148148

149149
finally:
150+
if "lf_derived" in locals():
151+
del lf_derived # type: ignore
152+
if "lf_freezed" in locals():
153+
del lf_freezed
150154
force_gc()
151155

152156
return True
@@ -182,9 +186,6 @@ def orchestrate_dimension_refs(run_context: RunContext, report: Dict) -> bool:
182186
report[table] = {"dim_reference": False, "export": False}
183187
tracker = report[table]
184188

185-
lf_raw = None
186-
df_dim = None
187-
188189
try:
189190
# Switch between local and gcp IO
190191
if run_context.bq_project_id == "PROJECT_ID_NOT_DETECTED":
@@ -246,10 +247,10 @@ def orchestrate_dimension_refs(run_context: RunContext, report: Dict) -> bool:
246247
return False
247248

248249
finally:
249-
if lf_raw is not None:
250-
del lf_raw
251-
if df_dim is not None:
252-
del df_dim
250+
if "lf_raw" in locals():
251+
del lf_raw # type: ignore
252+
if "df_dim" in locals():
253+
del df_dim # type: ignore
253254
gc.collect()
254255

255256
return True

data_pipeline/contract/contract_executor.py

Lines changed: 38 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,44 +2,45 @@
22
# Contract Stage Executor
33
# =============================================================================
44

5+
import polars as pl
56
from data_pipeline.shared.run_context import RunContext
67
from data_pipeline.shared.loader_exporter import load_single_delta, export_file
8+
from data_pipeline.assembly.assembly_executor import force_gc
79
from data_pipeline.shared.table_configs import TABLE_CONFIG
810
from data_pipeline.contract.registry import ROLE_STEPS
9-
from data_pipeline.contract.id_registrar import id_mapping, ID_COLUMNS_TO_MAP
10-
from pathlib import Path
11+
from data_pipeline.contract.id_registrar import ID_ENTITY_MAP
1112

1213

1314
def apply_contract(
1415
run_context: RunContext,
1516
table_name: str,
17+
master_mappings: dict[str, pl.LazyFrame],
1618
invalid_order_ids: set | None = None,
1719
valid_order_ids: set | None = None,
1820
) -> tuple[dict, set, set]:
1921
"""
20-
Main entry point for the Raw-to-Contracted Stage.
22+
Orchestrates the Raw-to-Contracted transformation for a specific logical table.
2123
2224
Workflow:
2325
1. Resolve: Identifies table metadata (role, schema, keys) from the central registry.
2426
2. Hydrate: Fetches the raw snapshot from the lake's snapshot zone.
2527
3. Delegate: Iteratively applies atomic logic rules (Deduplication, Chronology, Null-checks).
2628
4. Validate: Executes 'enforce_schema' as the terminal structural gate.
27-
5. Promote: Persists the contract-compliant dataset to the Silver (contracted) zone.
29+
5. Map: Joins against pre-calculated Discovery mappings to enrich UUIDs with UInt32 integer IDs.
30+
6. Promote: Persists the contract-compliant dataset to the Silver (contracted) zone.
2831
2932
Operational Guarantees:
3033
- Subtractive Only: Exclusively filters rows or casts types; never mutates business values.
3134
- Referential Safety: Propagates invalidated keys across table boundaries to ensure consistent pruning.
32-
- Structural Finality: Guarantees output parity with the ASSEMBLE_SCHEMA specification.
35+
- Structural Finality: Guarantees output parity with the Silver layer specification, including required integer IDs.
3336
3437
Side Effects:
3538
- Persists a Parquet artifact to the contracted directory.
36-
- Updates newly invalidated 'order_id' sets for downstream cross-table pruning.
39+
- Updates invalidated 'order_id' sets for downstream cross-table pruning.
3740
3841
Failure Behavior:
3942
- Traps logic-step exceptions; logs errors to the report and halts the current table's processing.
40-
41-
Returns:
42-
tuple: (Stage Report Dict, Newly Invalidated IDs Set, Validated Order IDs Set)
43+
- Crashes if ID mapping joins fail to prevent downstream schema corruption.
4344
"""
4445

4546
report = {
@@ -69,7 +70,6 @@ def apply_contract(
6970
if table_name not in TABLE_CONFIG:
7071
report["status"] = "failed"
7172
report["errors"].append(f"Unknown table: {table_name}")
72-
7373
return report, invalid_ids, valid_ids
7474

7575
base_path = run_context.raw_snapshot_path
@@ -89,32 +89,26 @@ def apply_contract(
8989
role = config["role"]
9090

9191
for step in ROLE_STEPS[role]:
92-
9392
contract = step["contract"]
94-
args = []
95-
96-
if "non_nullable" in step["args"]:
97-
args.append(non_nullable)
9893

99-
if "invalid_order_ids" in step["args"]:
100-
args.append(invalid_order_ids)
94+
args = [
95+
non_nullable if "non_nullable" in step["args"] else None,
96+
invalid_order_ids if "invalid_order_ids" in step["args"] else None,
97+
valid_order_ids if "valid_order_ids" in step["args"] else None,
98+
]
10199

102-
if "valid_order_ids" in step["args"]:
103-
args.append(valid_order_ids)
100+
# Remove args not needed for the current registry loop
101+
args = [arg for arg in args if arg is not None]
104102

105103
if "required_column" in step["args"]:
106-
args.append(required_column)
107-
args.append(dtypes)
104+
args.extend([required_column, dtypes])
108105

109106
try:
110-
111107
if step["return_invalid_ids"]:
112108
df, removed, new_invalid = contract(df, *args)
113109
invalid_ids |= new_invalid
114-
115110
else:
116111
df, removed = contract(df, *args)
117-
118112
report[step["metric"]] += removed
119113

120114
except Exception as e:
@@ -129,33 +123,31 @@ def apply_contract(
129123
if table_name == "df_orders":
130124
valid_ids = set(df.get_column("order_id"))
131125

132-
temp_path = run_context.contracted_path / "id_mapping"
133-
storage_mapping_str = run_context.storage_mapping_path
134-
135-
# Switch between local and gcp path
136-
storage_mapping = (
137-
storage_mapping_str
138-
if storage_mapping_str.startswith("gs://")
139-
else Path(storage_mapping_str)
140-
)
141-
142-
df = id_mapping(
143-
df=df,
144-
table_name=table_name,
145-
mapping_dict=ID_COLUMNS_TO_MAP,
146-
runtime_dir=temp_path,
147-
destination=storage_mapping,
148-
run_id=run_context.run_id,
149-
)
126+
df_lf = df.lazy()
150127

151-
output_path = run_context.contracted_path / f"{filename}.parquet"
128+
try:
129+
# Attach mapped integer in dataframe
130+
for entity_col, tables in ID_ENTITY_MAP.items():
131+
if table_name in tables and entity_col in master_mappings:
132+
df_lf = df_lf.join(
133+
master_mappings[entity_col], on=entity_col, how="left"
134+
)
135+
136+
# Force to fail before corrupting downstream
137+
except Exception as e:
138+
raise RuntimeError(f"Mapping Uint32 to UUIDs Failed: {e}") from e
152139

153-
if not export_file(df, output_path):
140+
output_path = run_context.contracted_path / f"{filename}.parquet"
141+
if not export_file(df_lf.collect(), output_path):
154142
report["status"] = "failed"
155143
report["errors"].append("Export failed")
156-
157144
return report, invalid_ids, valid_ids
158145

159-
report["status"] = "success"
146+
if "df" in locals():
147+
del df
148+
if "df_lf" in locals():
149+
del df_lf
150+
force_gc()
160151

152+
report["status"] = "success"
161153
return report, invalid_ids, valid_ids

data_pipeline/contract/contract_logic.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
from typing import List
77
from data_pipeline.shared.table_configs import REQUIRED_TIMESTAMPS, TIMESTAMP_FORMATS
88

9-
109
# ------------------------------------------------------------
1110
# CONTRACT LOGICS
1211
# ------------------------------------------------------------
@@ -68,6 +67,7 @@ def remove_unparsable_timestamps(df: pl.DataFrame) -> tuple[pl.DataFrame, int, s
6867
exprs = []
6968
for col in REQUIRED_TIMESTAMPS:
7069
if col in df.columns:
70+
7171
if df.schema[col] == pl.String:
7272
fmt = TIMESTAMP_FORMATS.get(col)
7373
exprs.append(
@@ -255,6 +255,7 @@ def enforce_schema(
255255
target_dtype = dtypes.get(col)
256256

257257
if target_dtype == pl.Datetime:
258+
258259
if df.schema[col] == pl.String:
259260
fmt = TIMESTAMP_FORMATS.get(col)
260261
exprs.append(pl.col(col).str.to_datetime(format=fmt, strict=False))

0 commit comments

Comments
 (0)