recon_gen.common.dataset_contract
Dataset column contracts and shared dataset-building helpers.
A DatasetContract declares the column interface a dataset produces. The SQL is one implementation of that contract (against the demo schema); customers swap in their own SQL. Everything downstream (visuals, filters, drill-downs) binds to contract columns, not SQL specifics.
Functions
|
Build an AWS-shape DataSet. |
|
|
|
Look up the contract registered under |
|
Look up the dataset-parameter list registered under |
|
Look up the SQL registered under |
Snapshot the three dataset registries on enter; restore on exit. |
|
|
Register a visual_identifier -> contract mapping for shape lookup. |
|
Register a visual_identifier → dataset-parameter list mapping. |
|
Register a visual_identifier → dataset SQL mapping. |
Classes
|
Application-level value shape of a drill-eligible column. |
|
Declared column on a dataset's contract. |
|
|
|
BH.24 (2026-05-25) — per-column storage shape. |
- class recon_gen.common.dataset_contract.ColumnShape(*values)[source]
Bases:
EnumApplication-level value shape of a drill-eligible column.
Layered above the AWS coarse type (STRING/DATETIME/…) so that two columns sharing a wire type but representing different semantic values cannot be cross-wired to the same drill parameter. K.2 spike found a silent zero-row bug where
exception_date(DATETIME) was bound to a SINGLE_VALUED string parameter; QuickSight coerced it to the full timestamp text"2026-04-07 00:00:00.000"which never matched the destination’sposted_datecolumn (also STRING butYYYY-MM-DDformatted via TO_CHAR). The shape captures both the encoding and the semantic, so the typed drill helper can refuse the wiring at code-gen time instead of silently producing zero rows.Tag a column with a shape only if it’s an actual drill source or destination — every other column stays
shape=Noneand is rejected byDrillSourceFieldresolution.- ACCOUNT_DISPLAY = 'account_display'
- ACCOUNT_ID = 'account_id'
- DATETIME_DAY = 'datetime_day'
- DATE_YYYY_MM_DD_TEXT = 'date_yyyy_mm_dd_text'
- EXTERNAL_TXN_ID = 'external_txn_id'
- L2_DECLARED_NAME = 'l2_declared_name'
- LEDGER_ACCOUNT_ID = 'ledger_account_id'
- PAYMENT_ID = 'payment_id'
- RAIL_NAME = 'rail_name'
- SETTLEMENT_ID = 'settlement_id'
- SUBLEDGER_ACCOUNT_ID = 'subledger_account_id'
- TRANSFER_ID = 'transfer_id'
- can_assign_to(other)[source]
True iff a value of
selfis acceptable into aotherparam.Identical shapes are always assignable. SUBLEDGER_ACCOUNT_ID and LEDGER_ACCOUNT_ID widen to ACCOUNT_ID (the destination
daily_balances.account_idcolumn holds both ledger and sub-ledger ids). Date encodings do NOT widen — DATETIME and YYYY-MM-DD text are different wire types and cross-wiring them is the K.2 bug class.- Return type:
bool- Parameters:
other (ColumnShape)
- class recon_gen.common.dataset_contract.ColumnSpec(name, type, shape=None, display_name=None, currency=False, storage=Storage.DOLLARS)[source]
Bases:
objectDeclared column on a dataset’s contract.
display_name(v8.5.0): plain-English header label QuickSight table visuals use as the column header. When omitted, defaults to a title-cased rewrite of the snake_casename— e.g.account_id→ “Account ID” (with smart-uppercasing of common initialisms via_smart_title). Override when the auto-derived form is awkward — e.g.amount_moneydefaults to “Amount Money”, but Investigation tables read better with the explicit override “Amount” since the surrounding context already implies money.currency+storage(BH.24, 2026-05-25): explicit currency- column declaration.currency=Truetells renderers to apply USD formatting ($prefix, 2 decimals).storagedeclares how the value comes back from the DB driver —DOLLARS(default, matches today’s “dataset SQL pre-converts via cents_to_dollars_sql” production pattern) orCENTS(raw BIGINT cents, renderer must divide). The two are independent: a column can be currency=True + storage=DOLLARS (the production case), currency=True + storage= CENTS (raw-matview Studio access), currency=False + storage=DOLLARS (a non-money decimal), etc. Renderers consultstorageto decide whether to apply the /100 divide;currencyonly drives format.- Parameters:
name (str)
type (str)
shape (ColumnShape | None)
display_name (str | None)
currency (bool)
storage (Storage)
- currency: bool = False
- display_name: str | None = None
- property human_name: str
Plain-English header label for this column.
display_nameif set, else snake_case → “Title Case” with common initialisms preserved (id → ID, eod → EOD, etc.).
- name: str
- shape: ColumnShape | None = None
- type: str
- class recon_gen.common.dataset_contract.DatasetContract(columns)[source]
Bases:
object- Parameters:
columns (list[ColumnSpec])
- property column_names: list[str]
- columns: list[ColumnSpec]
- to_input_columns()[source]
- Return type:
list[InputColumn]
- class recon_gen.common.dataset_contract.Storage(*values)[source]
Bases:
EnumBH.24 (2026-05-25) — per-column storage shape.
Distinguishes columns whose values come back from the DB driver as raw BIGINT cents (the AO.1.impl Studio slice’s matview storage contract) from columns whose values come back as already-converted float / Decimal dollars (the legacy pre-AO.1 pattern where the dataset SELECT wraps with
cents_to_dollars_sql).Why this matters: App2’s
_measure_sqland_apply_cents_to_dollarsboth divide currency values by 100 at the renderer layer per AO.1’s “matview is cents, renderer converts” convention. If the dataset SQL ALSO divides (the production L1 / Inv / Exec / L2FT datasets all do today viacents_to_dollars_sql), the result is 100× off (BG.7 surfaced this on the Daily Statement KPIs: rendered -$11,993.10 vs matview -$1,199,309.68).Default
DOLLARS— matches today’s production behavior (every dataset pre-converts in SQL). Storage.CENTS is explicitly opt-in for columns that legitimately project raw cents (typically Studio bare-matview reads).currency=Truebecomes a pure FORMAT flag (USD symbol + 2-decimal QS format / “$” prefix on App2); the cents→dollars divide is gated on Storage.CENTS, not on currency=True.- CENTS = 'cents'
- DOLLARS = 'dollars'
- recon_gen.common.dataset_contract.build_dataset(cfg, dataset_id, name, table_key, sql, contract, visual_identifier, dataset_parameters=None)[source]
Build an AWS-shape DataSet.
dataset_parameters: optional list of dataset-level parameters that get substituted intosqlvia the<<$paramName>>syntax at QuickSight query time. Bridge to analysis params viaMappedDataSetParameterson the analysis ParameterDeclaration.Phase BM dissolved the pre-BM
app2_date_column=kwarg + the{date_filter}template slot in favor of unified<<$pXxxDateStart>>/<<$pXxxDateEnd>>pushdown via the samedataset_parametersmechanism (seecommon/sql/app2_filters.py::universal_date_range_clause). One SQL form across QS + App2; the day-edge quirk dissolves.- Return type:
- Parameters:
cfg (Config)
dataset_id (str)
name (str)
table_key (str)
sql (str)
contract (DatasetContract)
visual_identifier (str)
dataset_parameters (list[DatasetParameter] | None)
- recon_gen.common.dataset_contract.dataset_permissions(cfg)[source]
- Return type:
list[ResourcePermission] |None- Parameters:
cfg (Config)
- recon_gen.common.dataset_contract.get_contract(visual_identifier)[source]
Look up the contract registered under
visual_identifier.Raises
KeyErrorif not registered — usually means the dataset hasn’t been built yet in the current process. Tests / generators should callbuild_dataset()before reaching code that resolves drill source fields.- Return type:
- Parameters:
visual_identifier (str)
- recon_gen.common.dataset_contract.get_dataset_params(visual_identifier)[source]
Look up the dataset-parameter list registered under
visual_identifier. Returns[]if nothing was registered — unlikeget_sqlthis is not an error (most datasets have no parameters; the App2 executor handles the empty case gracefully).- Return type:
list[DatasetParameter]- Parameters:
visual_identifier (str)
- recon_gen.common.dataset_contract.get_sql(visual_identifier)[source]
Look up the SQL registered under
visual_identifier.Raises
KeyErrorif not registered — usually means the dataset hasn’t been built yet in the current process (callbuild_all_datasets(cfg)from the relevant app before reaching fetcher-construction code).- Return type:
str- Parameters:
visual_identifier (str)
- recon_gen.common.dataset_contract.isolated_dataset_registries()[source]
Snapshot the three dataset registries on enter; restore on exit.
Production has exactly one
cfgper process — the module-level_SQL_REGISTRY/_DSP_REGISTRY/_CONTRACT_REGISTRYare fine in that mode. Tests that build a second app tree with a non-canonicalcfg.db_table_prefixin the same process (e.g.tests/e2e/test_inv_dashboard_agreement.py::isolated_inv_app, which builds an iagree-prefixed clone for the 4-way agreement test) overwrite the canonical entries on a shared key — downstream tests then read the polluted SQL and 500 against the dropped isolation schema.Wrap the second-cfg build in this manager so its writes are discarded on teardown:
@pytest.fixture(scope="module") def isolated_inv_app(isolated_inv_cfg): with isolated_dataset_registries(): app = build_investigation_app(isolated_inv_cfg, l2_instance=_INSTANCE) app.emit_analysis() yield app
See BL.0 in PLAN.md +
docs/audits/bl_0_shared_state_keying_smell.mdfor the architectural reasoning (rejected: lift state out of globals / extend the key with prefix; accepted: scope the band-aid at the test boundary since production is unaffected).- Return type:
Generator[None,None,None]
- recon_gen.common.dataset_contract.register_contract(visual_identifier, contract)[source]
Register a visual_identifier -> contract mapping for shape lookup.
The key is the visual identifier (e.g.
"ar-ledger-balance-drift-ds"), the same string the visuals use asDataSetIdentifier=and that the analysis maps to a real DataSet ARN via DataSetIdentifierDeclaration.Idempotent for the same (visual_identifier, contract) pair; raises if a different contract is already registered under the same identifier (catches accidental identifier collisions).
- Return type:
None- Parameters:
visual_identifier (str)
contract (DatasetContract)
- recon_gen.common.dataset_contract.register_dataset_params(visual_identifier, params)[source]
Register a visual_identifier → dataset-parameter list mapping.
Same overwrite-on-repeat semantics as
register_sql. Pass[](or omit, viabuild_dataset(dataset_parameters=None)) for a dataset with no parameters —get_dataset_paramsthen returns[]and the App2 executor leaves any stray placeholder for the bind-variable fallback.- Return type:
None- Parameters:
visual_identifier (str)
params (list[DatasetParameter])
- recon_gen.common.dataset_contract.register_sql(visual_identifier, sql)[source]
Register a visual_identifier → dataset SQL mapping.
Idempotent for the same (visual_identifier, sql) pair; the second call with the same identifier overwrites with the new SQL (matches the typical “rebuild for a different dialect” workflow — same identifier, dialect-specific SQL). Tests that need to assert “build was idempotent” should snapshot the registry before / after.
- Return type:
None- Parameters:
visual_identifier (str)
sql (str)