recon_gen.common.tree

Tree primitives for App / Dashboard / Analysis / Sheet construction.

Replaces the constant-heavy + manually-cross-referenced builders in apps/{payment_recon,account_recon,investigation}/{analysis,filters, visuals}.py. Authors construct apps as trees of typed nodes; the tree walks itself at emit time to produce the existing models.py dataclasses, which serialize through the same to_aws_json() path the deploy pipeline uses.

Validation rules (catch these at construction or emit time):

Construction-time (raise immediately):

  • sheet.layout.row(height=H).add_<kind>(width=W, ...) — constructs + registers + places a visual atomically; refuses widths that overflow the 36-col grid. The duplicate-placement bug class is structurally impossible (no separate place step).

  • sheet.scope(filter_group, [visuals]) — every scoped visual must be on this sheet (catches the wrong-sheet bug).

  • Analysis.add_sheet / add_parameter / add_filter_group / add_calc_field — rejects duplicate IDs (shadow-bug class).

  • App.add_dataset — rejects duplicate dataset identifiers.

  • App.create_dashboard(...) — requires set_analysis already ran; the analysis-mismatch bug class is structurally impossible (no opening to pass a different Analysis).

  • NumericRangeFilter.__post_init__ — rejects setting both minimum_value and minimum_parameter (or both on the maximum side).

Emit-time (validated by App.resolve_auto_ids + the _validate_* methods, all run from emit_analysis / emit_dashboard):

  • Auto-IDs resolve for any node that didn’t carry an explicit ID.

  • _validate_dataset_references — every typed Dataset ref in the tree must be registered on the App.

  • _validate_calc_field_references — every typed CalcField ref must be registered on the analysis.

  • _validate_parameter_references — every typed ParameterDeclLike ref (in controls + NumericRangeFilter parameter bounds) must be registered on the analysis.

  • _validate_drill_destinations — every Drill action’s target_sheet must be a registered Sheet on the analysis.

  • FilterGroup.emit — refuses an unscoped FilterGroup.

  • cross_sheet_drill (K.2) — Drill DrillParam shape must match the source field’s ColumnShape.

Known follow-up: DrillParam (in common/drill.py) takes a string ParameterName rather than a typed ParameterDeclLike ref. That string isn’t validated against the analysis registry — typos in DrillParam.name flow to deploy. Closing the gap requires threading a typed parameter ref through DrillParamcross_sheet_drill → emission.

Locked decisions (see PLAN.md Phase L):

  • Cross-references are object refs, not string IDs. GridSlot.element takes any LayoutNode (typed visuals + TextBox); FilterGroup.scope_visuals takes (sheet, [visual, ...]); drill destinations take Sheet refs.

  • IDs appear once — at the constructor of the node that owns them. Per-app constants.py modules collapse: every other reference is the local Python variable holding the node ref.

  • emit() per node is the universal interface; trees walk recursively to produce models.py instances.

  • Visual subtypes are typed per kind (KPI, Table, Bar, Sankey). Same names as models.py where they exist; tree types alias models on import inside their own submodules to keep user-facing imports clean (from recon_gen.common.tree import KPI, Sankey, FilterGroup etc.).

Visual kind catalog (L.1.1 finding, used in active codebase): KPIVisual ×29, TableVisual ×22, BarChartVisual ×13, SankeyDiagramVisual ×2. PieChartVisual is modeled but unused.

Module organization:

  • _helpers — AUTO sentinel + label builders + permissions actions

  • fieldsDim / Measure field-well leaf nodes

  • parametersParameterDeclLike Protocol + StringParam / IntegerParam / DateTimeParam

  • visualsVisualLike Protocol + KPI / Table / BarChart / Sankey

  • filtersFilterGroup + typed Filter wrappers (CategoryFilter / NumericRangeFilter / TimeRangeFilter)

  • controls — typed parameter / filter control variants

  • structureGridSlot / Sheet / Analysis / Dashboard / App plus the SheetLayout / Row / AbsoluteSlot layout DSL

class recon_gen.common.tree.AbsoluteSlot(sheet, col_span, row_span, col_index, row_index)[source]

Bases: object

One explicit-position slot. Use for layouts that don’t fit the row pattern — overlapping visuals, asymmetric grids, off-grid positioning. One-shot: construct via sheet.layout.absolute( col_index=, row_index=, col_span=, row_span=) then chain a single add_<kind>(...) to fill it. Re-using an AbsoluteSlot for multiple visuals would emit duplicate slot positions and isn’t supported.

Parameters:
  • sheet (Sheet)

  • col_span (int)

  • row_span (int)

  • col_index (int)

  • row_index (int | None)

add_bar_chart(*, title, category=None, values=None, subtitle, orientation=None, bars_arrangement=None, sort_by=None, actions=None, log_scale=False, visual_id=AUTO)[source]
Return type:

BarChart

Parameters:
  • title (str)

  • category (list[Dim] | None)

  • values (list[Measure] | None)

  • subtitle (str)

  • orientation (Literal['HORIZONTAL', 'VERTICAL'] | None)

  • bars_arrangement (Literal['CLUSTERED', 'STACKED', 'STACKED_PERCENT'] | None)

  • sort_by (tuple[Dim | Measure | str, Literal['ASC', 'DESC']] | None)

  • actions (list[Drill | SameSheetFilter] | None)

  • log_scale (bool)

  • visual_id (VisualId | Literal[_AutoSentinel.AUTO])

add_kpi(*, title, values=None, subtitle, value_zero_indicator=None, value_sign_indicator=None, visual_id=AUTO)[source]
Return type:

KPI

Parameters:
add_line_chart(*, title, category=None, values=None, colors=None, subtitle, chart_type=None, category_label=None, value_label=None, sort_by=None, actions=None, visual_id=AUTO)[source]
Return type:

LineChart

Parameters:
  • title (str)

  • category (list[Dim] | None)

  • values (list[Measure] | None)

  • colors (list[Dim] | None)

  • subtitle (str)

  • chart_type (Literal['LINE', 'AREA', 'STACKED_AREA'] | None)

  • category_label (str | None)

  • value_label (str | None)

  • sort_by (tuple[Dim | Measure | str, Literal['ASC', 'DESC']] | None)

  • actions (list[Drill | SameSheetFilter] | None)

  • visual_id (VisualId | Literal[_AutoSentinel.AUTO])

add_sankey(*, title, source, target, weight, subtitle, items_limit=None, actions=None, visual_id=AUTO)[source]
Return type:

Sankey

Parameters:
  • title (str)

  • source (Dim)

  • target (Dim)

  • weight (Measure)

  • subtitle (str)

  • items_limit (int | None)

  • actions (list[Drill | SameSheetFilter] | None)

  • visual_id (VisualId | Literal[_AutoSentinel.AUTO])

add_table(*, title, group_by=None, values=None, columns=None, subtitle, sort_by=None, actions=None, conditional_formatting=None, visual_id=AUTO)[source]
Return type:

Table

Parameters:
  • title (str)

  • group_by (list[Dim] | None)

  • values (list[Measure] | None)

  • columns (list[Dim] | None)

  • subtitle (str)

  • sort_by (tuple[Dim | Measure | str, Literal['ASC', 'DESC']] | list[tuple[~recon_gen.common.tree.fields.Dim | ~recon_gen.common.tree.fields.Measure | str, ~typing.Literal['ASC', 'DESC']]] | None)

  • actions (list[Drill | SameSheetFilter] | None)

  • conditional_formatting (list[CellAccentText | CellAccentMenu] | None)

  • visual_id (VisualId | Literal[_AutoSentinel.AUTO])

add_text_box(text_box)[source]
Return type:

TextBox

Parameters:

text_box (TextBox)

col_index: int
col_span: int
row_index: int | None
row_span: int
sheet: Sheet
class recon_gen.common.tree.Analysis(analysis_id_suffix, name, sheets=<factory>, parameters=<factory>, filter_groups=<factory>, calc_fields=<factory>)[source]

Bases: object

Tree node for the Analysis-level structure.

analysis_id_suffix is the part the App’s cfg.prefixed() will prepend to (e.g. "investigation-analysis" becomes "qs-gen-investigation-analysis"). Keeping the suffix on the tree node keeps the per-app naming under the tree’s control while leaving the global resource-prefix in the Config.

emit_definition() returns the models.AnalysisDefinition — the App combines this with metadata (AwsAccountId, ThemeArn, Permissions, dataset declarations) to produce the full models.Analysis ready for deploy.

Parameters:
add_calc_field(calc)[source]

Register a calculated field on this analysis.

Construction-time check: calc field names are unique within the analysis. Two calc fields sharing a Name silently let one win at deploy time — same shadow-bug class as parameters / filter groups / datasets.

Return type:

CalcField

Parameters:

calc (CalcField)

add_filter_group(fg)[source]

Register a filter group on this analysis.

Construction-time check: explicit filter group IDs are unique. (Auto-IDs are unique by construction — assigned from the index in the analysis’s filter_groups list — so the check only applies when callers passed an explicit id.)

Return type:

FilterGroup

Parameters:

fg (FilterGroup)

add_parameter(param)[source]

Declare a parameter on this analysis.

Construction-time check: parameter names are unique within the analysis. Catches the silent shadow bug where two declarations share a Name and only one wins at deploy time. Generic over the concrete subtype so the returned ref keeps its type (StringParam / IntegerParam / DateTimeParam) (PEP 695).

Return type:

TypeVar(T, bound= ParameterDeclLike)

Parameters:

param (T)

add_sheet(sheet)[source]
Return type:

Sheet

Parameters:

sheet (Sheet)

analysis_id_suffix: str
calc_fields: list[CalcField]
calc_fields_referenced()[source]

Walk the analysis tree and return every CalcField referenced by any visual or filter group. Distinct from self.calc_fields (the registry): this returns only the calc fields actually used.

Catches “calc field declared but never used” (registered but not in this set) and “calc field used but not declared” (in this set but not in the registry — App._validate_calc_field_ references raises on emit).

Return type:

set[CalcField]

datasets()[source]

Walk the analysis tree and return every Dataset referenced by any visual, filter group, or registered calc field. Used by App.dataset_dependencies to derive the precise refresh set.

Visuals using the spike-shape VisualNode factory wrapper don’t expose their dataset refs (the factory hides them). Typed Visual subtypes (KPI / Table / BarChart / Sankey) all expose datasets() and contribute. The spike-shape gap closes once apps port to typed subtypes (L.2/L.3/L.4).

Registered CalcFields contribute too — their Dataset ref becomes a dep even if no visual directly references the underlying columns.

Return type:

set[Dataset]

emit_definition(*, datasets)[source]
Return type:

AnalysisDefinition

Parameters:

datasets (list[Dataset])

filter_groups: list[FilterGroup]
find_calc_field(*, name)[source]

Look up a single calc field by name.

Return type:

CalcField

Parameters:

name (str)

find_filter_group(*, filter_group_id=None)[source]

Look up a single filter group by id (auto or explicit).

Return type:

FilterGroup

Parameters:

filter_group_id (FilterGroupId | str | None)

find_parameter(*, name)[source]

Look up a single parameter declaration by name.

Return type:

ParameterDeclLike

Parameters:

name (str)

find_sheet(*, name=None, sheet_id=None)[source]

Look up a single sheet on this analysis by name or sheet id.

Raises on no-match or multi-match. Sheet IDs stay explicit (URL-facing per the L.1.8.5 mixed scheme) so passing sheet_id= is the most robust lookup; name= is the next-best for tests that don’t want to hardcode IDs.

Return type:

Sheet

Parameters:
  • name (str | None)

  • sheet_id (SheetId | str | None)

name: str
parameters: list[ParameterDeclLike]
sheets: list[Sheet]
class recon_gen.common.tree.App(name, cfg, analysis=None, dashboard=None, datasets=<factory>, allow_bare_strings=False)[source]

Bases: object

Top-level tree node — coordinates an Analysis + Dashboard plus the deploy-time context (theme, dataset arns, permissions) drawn from the Config.

Authors construct an App, attach the Analysis (which holds the sheet tree), optionally attach the Dashboard (most apps do — they publish what they author), and call emit_analysis() / emit_dashboard() to get the models.py instances ready for deploy.

Datasets are registered on the App via add_dataset() and referenced from visuals / filters by object ref. At emit time the App walks the tree’s dataset_dependencies() and includes only the datasets actually used in the emitted DataSetIdentifierDeclarations — selective by construction. Validation: if a visual or filter references a Dataset that isn’t registered on the App, emit_analysis raises with the offending identifiers.

Parameters:
add_dataset(dataset)[source]

Register a Dataset on the App.

Construction-time check: dataset identifiers are unique within the app. Catches the silent shadow bug where two registrations share an identifier and only one wins at deploy.

Return type:

Dataset

Parameters:

dataset (Dataset)

allow_bare_strings: bool = False
analysis: Analysis | None = None
cfg: Config
create_dashboard(*, dashboard_id_suffix, name)[source]

Construct + register a Dashboard against the App’s already-set Analysis.

The App owns the Analysis already; this shortcut prevents the analysis-mismatch bug class by construction — there’s no opening to pass a different Analysis.

Return type:

Dashboard

Parameters:
  • dashboard_id_suffix (str)

  • name (str)

dashboard: Dashboard | None = None
dataset_dependencies()[source]

The set of Datasets referenced anywhere in the App’s tree.

Walks the Analysis (sheets → visuals + filter_groups). Each typed Visual subtype + typed Filter wrapper exposes its own datasets() set; the App unions them.

Return type:

set[Dataset]

Deployment side effect. This set drives: - selective deploy (only re-create / refresh the datasets

downstream of an actual change),

  • matview REFRESH ordering (REFRESH only the matviews backing datasets that the changed deploy surface depends on).

Returns an empty set when the App has no Analysis.

datasets: list[Dataset]
emit_analysis()[source]
Return type:

Analysis

emit_dashboard()[source]
Return type:

Dashboard

find_sheet(*, name=None, sheet_id=None)[source]

Convenience pass-through to app.analysis.find_sheet(...).

Return type:

Sheet

Parameters:
  • name (str | None)

  • sheet_id (SheetId | str | None)

name: str
resolve_auto_ids()[source]

Walk the tree and assign auto-IDs to nodes that left their IDs unset. Called from emit_analysis / emit_dashboard before any validation or emission, and exposed publicly so non-QS renderers (HTML, future X.4 editor) can resolve IDs without going through the full QS emit path.

Idempotent — re-runs are no-ops once IDs are filled in.

Mixed scheme (L.1.8.5 + L.1.16): URL-facing IDs (SheetId, ParameterName) and analyst-facing identifiers (Dataset identifier) stay explicit. Internal IDs the analyst never types — visual_id, filter_id, control_id, action_id, field_id, calc-field name — get tree-position-derived defaults when omitted.

Auto-ID formats: - Visual: v-{kind}-s{sheet_idx}-{visual_idx} - FilterGroup: fg-{idx} — analysis-scoped - Filter: f-{kind}-fg{fg_idx}-{filt_idx} - ParameterControl: pc-{kind}-s{sheet_idx}-{ctrl_idx} - FilterControl: fc-{kind}-s{sheet_idx}-{ctrl_idx} - Drill action: act-s{sheet_idx}-v{visual_idx}-{action_idx} - Field-well leaf (Dim/Measure): ``f-{visual_kind}-s{sheet_idx}

-v{visual_idx}-{role}{slot_idx}`` where role tags the field well slot (g group_by, v values, c category, s source, t target, w weight)

  • CalcField: calc-{idx} — analysis-scoped

Same-sheet drills also get their target_sheet back-filled here (Drill.target_sheet=AUTO means “the sheet that owns me”).

Return type:

None

set_analysis(analysis)[source]
Return type:

Analysis

Parameters:

analysis (Analysis)

class recon_gen.common.tree.BarChart(title, subtitle, category=<factory>, values=<factory>, colors=<factory>, orientation=None, bars_arrangement=None, category_label=None, value_label=None, color_label=None, sort_by=None, actions=<factory>, log_scale=False, visual_id=AUTO)[source]

Bases: object

Bar chart visual — one bar per distinct category, height by values.

Field-well shape: Category=[Dim, ...] + Values=[Measure, ...].

orientation ("VERTICAL" or "HORIZONTAL") and bars_arrangement ("CLUSTERED" / "STACKED" / "STACKED_PERCENT") pass through to the underlying BarChartConfiguration. sort_by is a (field_id, direction) tuple — direction "ASC" or "DESC" — and emits a CategorySort entry. All three default to None so the QuickSight defaults apply when not specified.

visual_id is optional (L.1.8.5 auto-ID).

Parameters:
  • title (str)

  • subtitle (str)

  • category (list[Dim])

  • values (list[Measure])

  • colors (list[Dim])

  • orientation (Literal['HORIZONTAL', 'VERTICAL'] | None)

  • bars_arrangement (Literal['CLUSTERED', 'STACKED', 'STACKED_PERCENT'] | None)

  • category_label (str | None)

  • value_label (str | None)

  • color_label (str | None)

  • sort_by (tuple[Dim | Measure | str, Literal['ASC', 'DESC']] | None)

  • actions (list[Drill | SameSheetFilter])

  • log_scale (bool)

  • visual_id (VisualId | Literal[_AutoSentinel.AUTO])

actions: list[Drill | SameSheetFilter]
bars_arrangement: Literal['CLUSTERED', 'STACKED', 'STACKED_PERCENT'] | None = None
calc_fields()[source]
Return type:

set[CalcField]

category: list[Dim]
category_label: str | None = None
color_label: str | None = None
colors: list[Dim]
datasets()[source]
Return type:

set[Dataset]

property element_id: str
property element_type: Literal['VISUAL', 'FILTER_CONTROL', 'PARAMETER_CONTROL', 'TEXT_BOX', 'IMAGE']
emit()[source]
Return type:

Visual

log_scale: bool = False
orientation: Literal['HORIZONTAL', 'VERTICAL'] | None = None
sort_by: tuple[Dim | Measure | str, Literal['ASC', 'DESC']] | None = None
subtitle: str
title: str
value_label: str | None = None
values: list[Measure]
visual_id: VisualId | Literal[_AutoSentinel.AUTO] = 'auto'
class recon_gen.common.tree.CalcField(dataset, expression, name=AUTO, shape=None)[source]

Bases: object

Tree node for one analysis-level calculated field.

name is the column-style identifier visuals/filters reference (e.g. "is_anchor_edge"). Optional — auto-derived as calc-{idx} at emit time when not specified.

dataset is the Dataset object ref the expression evaluates against. expression is the QuickSight calc expression (e.g. "ifelse({source} = ${pAnchor}, 'yes', 'no')").

shape is Optional and only matters for drill sources: when a drill action reads this calc field’s value (via a Dim / Measure object ref in the drill’s writes), the tree needs a ColumnShape to type-check the drill parameter binding. Tag here once rather than re-passing the shape at every drill site.

Identity-keyed (eq=False) so the auto-name resolver can mutate the name field at emit time. CalcFields stay hashable via the default object identity hash, which is what the dependency-graph set membership needs anyway.

Emits a plain dict that drops straight into AnalysisDefinition.CalculatedFields — same shape the existing builders write today.

Parameters:
  • dataset (Dataset)

  • expression (str)

  • name (str | Literal[_AutoSentinel.AUTO])

  • shape (ColumnShape | None)

dataset: Dataset
emit()[source]
Return type:

dict[str, str]

expression: str
name: str | Literal[_AutoSentinel.AUTO] = 'auto'
shape: ColumnShape | None = None
class recon_gen.common.tree.CategoryFilter(dataset, column, binding, match_operator='CONTAINS', null_option='ALL_VALUES', default_control=None, filter_id=AUTO)[source]

Bases: object

Filter on a categorical (string) column or calc field.

dataset is a Dataset object ref (L.1.7 hard switch). column may name a real dataset column or an analysis-level calc field — both resolve to a ColumnIdentifier against the given dataset.

Construct via the factory methods (L.1.22 — the discriminated binding makes the “neither/both set” bug class structurally impossible):

  • CategoryFilter.with_values(dataset, column, values, ...) — static list. Emits FilterListConfiguration with CategoryValues. Use for the calc-field 'yes' sentinel pattern or a hardcoded include-list.

  • CategoryFilter.with_parameter(dataset, column, parameter, ...) — parameter-bound. Emits CustomFilterConfiguration with ParameterName from the param ref. Use when a dropdown writes a single value into a string parameter and the filter narrows to it (e.g. Money Trail’s chain root selector).

null_option only surfaces in the parameter-bound emit (the list-based FilterListConfiguration doesn’t carry it).

Parameters:
  • dataset (Dataset)

  • column (str | CalcField | Column)

  • binding (_ValuesBinding | _ParameterBinding | _LiteralBinding)

  • match_operator (Literal['CONTAINS', 'EQUALS', 'DOES_NOT_EQUAL', 'STARTS_WITH'])

  • null_option (Literal['NON_NULLS_ONLY', 'ALL_VALUES', 'NULLS_ONLY'])

  • default_control (DefaultDropdownControl | None)

  • filter_id (str | Literal[_AutoSentinel.AUTO])

binding: _ValuesBinding | _ParameterBinding | _LiteralBinding
calc_field()[source]

The CalcField this filter references, or None if it points at a real dataset column.

Return type:

CalcField | None

column: str | CalcField | Column
dataset: Dataset
default_control: DefaultDropdownControl | None = None
emit()[source]
Return type:

Filter

filter_id: str | Literal[_AutoSentinel.AUTO] = 'auto'
match_operator: Literal['CONTAINS', 'EQUALS', 'DOES_NOT_EQUAL', 'STARTS_WITH'] = 'CONTAINS'
null_option: Literal['NON_NULLS_ONLY', 'ALL_VALUES', 'NULLS_ONLY'] = 'ALL_VALUES'
classmethod with_literal(*, dataset, column, value, match_operator='EQUALS', null_option='NON_NULLS_ONLY', default_control=None, filter_id=AUTO)[source]

Single-literal exact-match category filter — emits CustomFilterConfiguration with a literal CategoryValue. The list-based FilterListConfiguration rejects EQUALS at the API (only CONTAINS / DOES_NOT_CONTAIN), so this is the only shape that supports an exact-equality test against a single value. Used for the K.2 calc-field PASS pattern: a calc field returns "PASS" and the filter requires equality against that literal.

Return type:

CategoryFilter

Parameters:
  • dataset (Dataset)

  • column (str | CalcField | Column)

  • value (str)

  • match_operator (Literal['CONTAINS', 'EQUALS', 'DOES_NOT_EQUAL', 'STARTS_WITH'])

  • null_option (Literal['NON_NULLS_ONLY', 'ALL_VALUES', 'NULLS_ONLY'])

  • default_control (DefaultDropdownControl | None)

  • filter_id (str | Literal[_AutoSentinel.AUTO])

classmethod with_parameter(*, dataset, column, parameter, match_operator='EQUALS', null_option='ALL_VALUES', default_control=None, filter_id=AUTO)[source]

Parameter-bound category filter — ParameterName is read from the parameter ref at emit time. Default match_operator is EQUALS since dropdown-driven parameters typically write a single value.

Return type:

CategoryFilter

Parameters:
  • dataset (Dataset)

  • column (str | CalcField | Column)

  • parameter (ParameterDeclLike)

  • match_operator (Literal['CONTAINS', 'EQUALS', 'DOES_NOT_EQUAL', 'STARTS_WITH'])

  • null_option (Literal['NON_NULLS_ONLY', 'ALL_VALUES', 'NULLS_ONLY'])

  • default_control (DefaultDropdownControl | None)

  • filter_id (str | Literal[_AutoSentinel.AUTO])

classmethod with_values(*, dataset, column, values, match_operator='CONTAINS', null_option='ALL_VALUES', select_all_options=None, default_control=None, filter_id=AUTO)[source]

Static-list category filter — CategoryValues is the literal list of allowed values. Pass values=[] plus select_all_options="FILTER_ALL_VALUES" for the multi-select-with-all-default pattern: an empty values list means “every distinct column value is selected at runtime”.

Return type:

CategoryFilter

Parameters:
  • dataset (Dataset)

  • column (str | CalcField | Column)

  • values (list[str])

  • match_operator (Literal['CONTAINS', 'EQUALS', 'DOES_NOT_EQUAL', 'STARTS_WITH'])

  • null_option (Literal['NON_NULLS_ONLY', 'ALL_VALUES', 'NULLS_ONLY'])

  • select_all_options (Literal['FILTER_ALL_VALUES'] | None)

  • default_control (DefaultDropdownControl | None)

  • filter_id (str | Literal[_AutoSentinel.AUTO])

class recon_gen.common.tree.CellAccentMenu(on, text_color, background_color)[source]

Bases: object

Render a cell’s text in text_color on a background_color tint to cue a right-click (DATA_POINT_MENU) drill on that field. Distinguishes from CellAccentText’s plain-accent left-click style, so the analyst can tell the two click idioms apart at a glance.

Parameters:
  • on (Dim)

  • text_color (str)

  • background_color (str)

background_color: str
emit()[source]
Return type:

dict[str, Any]

on: Dim
text_color: str
class recon_gen.common.tree.CellAccentText(on, color)[source]

Bases: object

Render a cell’s text in color to cue a left-click drill on that field. The on: Dim ref carries both the field_id and the column name through to the emitted format options.

Parameters:
  • on (Dim)

  • color (str)

color: str
emit()[source]
Return type:

dict[str, Any]

on: Dim
class recon_gen.common.tree.Column(dataset, name)[source]

Bases: object

Typed column reference — dataset object ref + column name.

Authors construct via ds["col_name"] (which validates against the contract). Pass to Dim/Measure constructors directly, or use the chained factories below for the most concise wiring:

ds[“amount”].sum() # Measure.sum ds[“recipient_id”].dim() # categorical Dim ds[“window_end”].date() # date Dim ds[“depth”].numerical() # numerical Dim ds[“recipient_id”].distinct_count()

Frozen + hashable so a Column can be reused across visual slots (the chain ds["col"] returns a value-equal Column each time; ds["col"] == ds["col"] is True, useful for set membership in column-coverage tests).

Imports are lazy inside the factory methods to break the Dataset → Column → Dim/Measure → Dataset circular import.

Parameters:
average(*, field_id=AUTO, currency=False, decimals=None)[source]
Return type:

Measure

Parameters:
  • field_id (str | Literal[_AutoSentinel.AUTO])

  • currency (bool)

  • decimals (int | None)

count(*, field_id=AUTO)[source]
Return type:

Measure

Parameters:

field_id (str | Literal[_AutoSentinel.AUTO])

dataset: Dataset
date(*, date_granularity='DAY', field_id=AUTO)[source]
Return type:

Dim

Parameters:
  • date_granularity (Literal['YEAR', 'QUARTER', 'MONTH', 'WEEK', 'DAY', 'HOUR', 'MINUTE', 'SECOND', 'MILLISECOND'] | None)

  • field_id (str | Literal[_AutoSentinel.AUTO])

dim(*, kind='categorical', field_id=AUTO)[source]
Return type:

Dim

Parameters:
  • kind (Literal['categorical', 'date', 'numerical'])

  • field_id (str | Literal[_AutoSentinel.AUTO])

distinct_count(*, field_id=AUTO)[source]
Return type:

Measure

Parameters:

field_id (str | Literal[_AutoSentinel.AUTO])

property human_name: str

Plain-English header label for this column (v8.5.0).

Looks up the column on the dataset’s registered contract and returns the contract’s human_name (override or auto-derived title-case). Returns the title-cased column name as a fallback if the dataset has no contract — keeps the test fixtures (which construct Datasets directly without going through build_dataset) usable without forcing a registry round-trip.

max(*, field_id=AUTO, currency=False, decimals=None)[source]
Return type:

Measure

Parameters:
  • field_id (str | Literal[_AutoSentinel.AUTO])

  • currency (bool)

  • decimals (int | None)

min(*, field_id=AUTO, currency=False, decimals=None)[source]
Return type:

Measure

Parameters:
  • field_id (str | Literal[_AutoSentinel.AUTO])

  • currency (bool)

  • decimals (int | None)

name: str
numerical(*, field_id=AUTO, currency=False)[source]
Return type:

Dim

Parameters:
  • field_id (str | Literal[_AutoSentinel.AUTO])

  • currency (bool)

sum(*, field_id=AUTO, currency=False, decimals=None)[source]
Return type:

Measure

Parameters:
  • field_id (str | Literal[_AutoSentinel.AUTO])

  • currency (bool)

  • decimals (int | None)

class recon_gen.common.tree.Dashboard(dashboard_id_suffix, name, analysis)[source]

Bases: object

Tree node for a Dashboard.

Carries an object reference to the Analysis whose definition this Dashboard publishes. dashboard_id_suffix follows the same pattern as Analysis.analysis_id_suffix — App’s cfg.prefixed() prepends the project resource prefix.

analysis is the SAME tree node the App owns; the Dashboard re-emits the same definition the Analysis produces, which matches the existing build_dashboard(cfg) pattern in the per-app builders.

Parameters:
  • dashboard_id_suffix (str)

  • name (str)

  • analysis (Analysis)

analysis: Analysis
dashboard_id_suffix: str
name: str
class recon_gen.common.tree.Dataset(identifier, arn)[source]

Bases: object

Tree node for one dataset registration on the App.

identifier is the logical identifier visuals/filters reference (the existing per-app DS_INV_ACCOUNT_NETWORK / DS_AR_TRANSACTIONS strings — values like "inv-account-network-ds"). arn is the AWS QuickSight DataSetArn the deployed analysis points at.

Frozen because Dataset acts as the dependency-graph KEY: it must be hashable so visuals/filters that reference it can be collected into set[Dataset] for the dependency walk.

ds["column_name"] returns a typed Column ref (validated against the dataset’s registered DatasetContract if one exists) — see Column docstring for the chained factory pattern.

Parameters:
  • identifier (str)

  • arn (str)

arn: str
emit_declaration()[source]
Return type:

DataSetIdentifierDeclaration

identifier: str
class recon_gen.common.tree.DateTimeParam(name, default, time_granularity=None, mapped_dataset_params=None)[source]

Bases: object

DateTime parameter declaration.

Pass time_granularity="DAY" | "HOUR" | "MINUTE" | to bound the picker’s resolution. default is required — DateTimeDefaultValues with one of StaticValues / DynamicValue / RollingDate populated. Common rolling default for “today”: DateTimeDefaultValues(RollingDate={"Expression": "truncDate('DD', now())"}).

Why default is required (M.4.4.10d): without one, QS UI’s date picker initializes with no value and crashes on editor open with “epochMilliseconds must be a number, you gave: null”. Making the field type-required prevents the bug class from recurring at the wiring site.

Parameters:
default: DateTimeDefaultValues
emit()[source]
Return type:

ParameterDeclaration

mapped_dataset_params: list[tuple[Dataset, str]] | None = None
name: ParameterName
time_granularity: Literal['YEAR', 'QUARTER', 'MONTH', 'WEEK', 'DAY', 'HOUR', 'MINUTE', 'SECOND', 'MILLISECOND'] | None = None
class recon_gen.common.tree.DateView(frame, empty_behavior=EmptyBehavior.LATEST_ON_EMPTY)[source]

Bases: object

A typed view over a date range / single date, owning its own definition (anchor, span, empty-behavior, required-coverage).

The renderer bindings (analysis-param default, dataset-param default, picker widget, App2 binding) all derive from this one object — AR.2’s emission layer. ONE source of truth; the C1 dual-default split becomes unrepresentable.

The frame carries the anchor + window: frame.as_of is the right-edge anchor, frame.window is the typed DateInterval the view queries against (single-day when frame.window.days == 1; rolling N-day when frame.window.days > 1). BD.1 replaced the v1 frame.window_days: int field with frame.window: DateInterval so the “span > 0 means rolling” convention is now a closed-closed days count, not a separate scalar.

Authoring abstraction — not end-user config. App authors construct one of these per surface; the operator never picks one.

Parameters:
property anchor_day: date

The single date the view points at — frame.as_of. For single-date views this is the only day; for range views this is the right edge.

emit_qs_analysis_default()[source]

Alias for emit_qs_analysis_default_end() — kept for the single-date case where the “default” is unambiguously the anchor (the AR.2 balance-date wiring’s caller name).

Return type:

DateTimeDefaultValues

emit_qs_analysis_default_end()[source]

The QS analysis-param default for the END of a range view (or the single-date case) — a StaticValues literal day, NOT a RollingDate expression. Strict-collapse: the anchor is the owned as_of, baked at deploy.

Return type:

DateTimeDefaultValues

emit_qs_analysis_default_start()[source]

The QS analysis-param default for the START of a range view — window_start as StaticValues. AR.4 wires this onto the L1 universal-range start param + the Exec 30-day start param, replacing the per-app RollingDate(addDateTime(-N, …)) expressions.

Return type:

DateTimeDefaultValues

emit_qs_dataset_default()[source]

The QS dataset-param default — the SAME literal day as the analysis default. App2 reads this directly; QS receives it via MappedDataSetParameters from the analysis side. Both renderers land on the same day.

For single-date views (Daily Statement) this is unambiguously the anchor. Range views use the start / end variants below; this is kept as an alias of the END for symmetry with emit_qs_analysis_default.

Return type:

DateTimeDatasetParameterDefaultValues

emit_qs_dataset_default_end()[source]

Phase BM — dataset-param default for the END of a range view (the anchor day). Mirrors emit_qs_analysis_default_end.

Return type:

DateTimeDatasetParameterDefaultValues

emit_qs_dataset_default_start()[source]

Phase BM — dataset-param default for the START of a range view (window_start). Mirrors emit_qs_analysis_default_start; used by L1 + Exec date-pushdown datasets so the BM-shape DateTimeDatasetParameter for the start picks up the same 7-day / 30-day window the analysis-level picker shows.

Return type:

DateTimeDatasetParameterDefaultValues

empty_behavior: EmptyBehavior = 1
frame: AsOfFrame
is_satisfied_by(available_days)[source]

Does at least one available day fall inside required_coverage? The view’s stated limit, checkable BEFORE render.

Return type:

bool

Parameters:

available_days (list[date])

property required_coverage: tuple[date, date]

The date range this view needs data inside to be meaningful. For range views: [window_start, anchor_day]. For single-date views with LATEST_ON_EMPTY: [date.min, anchor] (any prior day will do). For single-date SHOW_EMPTY: [anchor, anchor] (exact-match-or-blank is the contract).

The seed-coverage assertion in AR.3 calls is_satisfied_by(available_days) to make the plant ⟷ query-window contract a test, not developer-memory.

resolve_day(available_days)[source]

Apply empty_behavior to pick the day this view actually renders.

Returns the anchor_day if it has data, else (for LATEST_ON_EMPTY) the latest day ≤ anchor that does, else None (the view can’t satisfy itself — under SHOW_EMPTY means “render blank for the anchor”; under LATEST_ON_EMPTY means “no data anywhere ≤ anchor”). Range views resolve to their right-edge day the same way.

Return type:

date | None

Parameters:

available_days (list[date])

property window_start: date

The look-back’s lower bound — frame.window_start. Equal to anchor_day when the view is single-date (span=0).

class recon_gen.common.tree.DefaultDateTimePickerControl(title, type='DATE_RANGE')[source]

Bases: object

Inline default widget config for TimeRangeFilter.

Parameters:
  • title (str)

  • type (Literal['SINGLE_VALUED', 'DATE_RANGE'])

title: str
type: Literal['SINGLE_VALUED', 'DATE_RANGE'] = 'DATE_RANGE'
class recon_gen.common.tree.DefaultDropdownControl(title, type='MULTI_SELECT')[source]

Bases: object

Inline default widget config for CategoryFilter (or any list/parameter-driven filter).

Parameters:
  • title (str)

  • type (Literal['MULTI_SELECT', 'SINGLE_SELECT'])

title: str
type: Literal['MULTI_SELECT', 'SINGLE_SELECT'] = 'MULTI_SELECT'
class recon_gen.common.tree.DefaultSliderControl(title, minimum_value, maximum_value, step_size, type='SINGLE_POINT')[source]

Bases: object

Inline default widget config for NumericRangeFilter.

Parameters:
  • title (str)

  • minimum_value (float)

  • maximum_value (float)

  • step_size (float)

  • type (Literal['SINGLE_POINT', 'RANGE'])

maximum_value: float
minimum_value: float
step_size: float
title: str
type: Literal['SINGLE_POINT', 'RANGE'] = 'SINGLE_POINT'
class recon_gen.common.tree.Dim(dataset, column, kind='categorical', *, date_granularity=None, field_id=AUTO, currency=False)[source]

Bases: object

One dimension field-well entry — typed wrapper that emits a DimensionField of the appropriate kind.

dataset is a Dataset object ref — the locked L.1.7 hard switch. The dataset must be registered on the parent App (via app.add_dataset()) for the analysis to emit.

column accepts either a bare str (a real column on the dataset) or a CalcField object ref (an analysis-level calculated field). The CalcField ref carries the calc-field identity through the type checker — the App’s emit-time validation catches references to unregistered calc fields.

Default kind is categorical (the most common); use the date() / numerical() classmethods for the other variants.

field_id is keyword-only and Optional (L.1.16 auto-ID). When omitted, the App walker assigns one based on the leaf’s tree position. Pass an explicit field_id="..." only when external consumers (browser e2e selectors, etc.) need a stable id — cross-reference plumbing (sort_by, drill writes) accepts the leaf object directly.

Identity-keyed (eq=False) so the auto-id resolver can mutate the field_id at emit time. Dim leaves stay hashable via the default object identity hash, which lets the dependency graph set-membership check work.

Parameters:
  • dataset (Dataset)

  • column (str | CalcField | Column)

  • kind (Literal['categorical', 'date', 'numerical'])

  • date_granularity (Literal['YEAR', 'QUARTER', 'MONTH', 'WEEK', 'DAY', 'HOUR', 'MINUTE', 'SECOND', 'MILLISECOND'] | None)

  • field_id (str | Literal[_AutoSentinel.AUTO])

  • currency (bool)

calc_field()[source]

The CalcField this Dim references, or None if it points at a real dataset column. Used by the dependency-graph walk.

Return type:

CalcField | None

column: str | CalcField | Column
currency: bool = False
dataset: Dataset
classmethod date(dataset, column, *, date_granularity='DAY', field_id=AUTO)[source]

Date dimension. date_granularity defaults to "DAY" — QuickSight’s most common bucket for daily series. Pass None to omit the granularity (the renderer falls back to its default, which can shift bucketing on day-vs-month dashboards).

Return type:

Dim

Parameters:
  • dataset (Dataset)

  • column (str | CalcField | Column)

  • date_granularity (Literal['YEAR', 'QUARTER', 'MONTH', 'WEEK', 'DAY', 'HOUR', 'MINUTE', 'SECOND', 'MILLISECOND'] | None)

  • field_id (str | Literal[_AutoSentinel.AUTO])

date_granularity: Literal['YEAR', 'QUARTER', 'MONTH', 'WEEK', 'DAY', 'HOUR', 'MINUTE', 'SECOND', 'MILLISECOND'] | None = None
emit()[source]
Return type:

DimensionField

emit_unaggregated_field()[source]

Emit the raw UnaggregatedField dict shape used inside TableUnaggregatedFieldWells.Values. The model layer types that field as list[dict[str, Any]] rather than a typed union, so the tree emits it as a dict directly.

Q.1.a.7 — When currency=True is set on a numerical Dim, the same USD FormatConfiguration that emit() wires onto a NumericalDimensionField is also folded into the unaggregated field shape so table cells render with “$” + thousands separator + 2 decimals. Without this, currency=True only took effect when the Dim was used as a chart axis or KPI value, not when it was used as a table column (the by-far common case).

Return type:

dict[str, object]

field_id: str | Literal[_AutoSentinel.AUTO] = 'auto'
kind: Literal['categorical', 'date', 'numerical'] = 'categorical'
classmethod numerical(dataset, column, *, field_id=AUTO, currency=False)[source]
Return type:

Dim

Parameters:
  • dataset (Dataset)

  • column (str | CalcField | Column)

  • field_id (str | Literal[_AutoSentinel.AUTO])

  • currency (bool)

class recon_gen.common.tree.Drill(writes, name, trigger='DATA_POINT_CLICK', action_id=AUTO, target_sheet=AUTO)[source]

Bases: object

One custom action on a Visual.

target_sheet is the destination Sheet object ref. Two binding modes:

  • Cross-sheet drill — pass an explicit target_sheet=sheet. The drill navigates to that sheet (and writes parameter values to it).

  • Same-sheet drill (the walk-the-flow / re-render-around-new-anchor pattern) — leave target_sheet as None. App.resolve_auto_ids back-fills the field with the sheet that owns the visual carrying the drill, so the author never types target_sheet=this_sheet when wiring a drill inside the function that builds the sheet. Resolves the chicken-and-egg cycle (sheet doesn’t exist yet when the drill is constructed inside Sheet.add_visual(...)) without a back-fill loop at the call site.

writes is a list of (DrillParam, DrillSourceField | DrillResetSentinel) tuples — same shape K.2 introduced. The DrillParam carries its own ColumnShape; DrillSourceField.shape must match or cross_sheet_drill raises (call-site shape validation).

trigger picks the click semantic — DATA_POINT_CLICK for left-click, DATA_POINT_MENU for right-click context menu.

action_id is Optional — the App walker assigns one at emit time when not specified.

name is the visible label QuickSight shows in the right-click menu (for DATA_POINT_MENU triggers). For DATA_POINT_CLICK actions the name doesn’t surface in the UI but is still required by the underlying model.

Parameters:
  • writes (list[DrillWrite])

  • name (str)

  • trigger (Literal['DATA_POINT_CLICK', 'DATA_POINT_MENU'])

  • action_id (str | AutoResolved)

  • target_sheet (Sheet | AutoResolved)

action_id: str | Literal[_AutoSentinel.AUTO] = 'auto'
emit()[source]
Return type:

VisualCustomAction

name: str
target_sheet: Sheet | Literal[_AutoSentinel.AUTO] = 'auto'
trigger: Literal['DATA_POINT_CLICK', 'DATA_POINT_MENU'] = 'DATA_POINT_CLICK'
writes: list[tuple[DrillParam, Dim | Measure | DrillSourceField | DrillResetSentinel | DrillStaticDateTime]]
class recon_gen.common.tree.DrillParam(name, shape)[source]

Bases: object

Destination parameter on a drill action — name + expected shape.

The shape captures the parameter’s value semantics; set_drill_parameters refuses to write a source field whose shape differs.

Parameters:
name: ParameterName
shape: ColumnShape
class recon_gen.common.tree.DrillResetSentinel(value='__ALL__')[source]

Bases: object

Marker that a drill should reset a parameter to the sentinel value.

The destination calc-field filter recognizes the sentinel as PASS, so writing this clears the filter without needing an empty-string or null-value path that QuickSight’s drill-action code path won’t deliver to calc fields cleanly.

Parameters:

value (str)

value: str = '__ALL__'
class recon_gen.common.tree.DrillSourceField(field_id, shape)[source]

Bases: object

Source field on a drill action — visual field id + resolved shape.

Build via field_source(field_id, dataset_id, column_name) so the shape is read from the dataset contract, not duplicated by hand.

Parameters:
field_id: str
shape: ColumnShape
class recon_gen.common.tree.DrillStaticDateTime(value)[source]

Bases: object

Marker that a drill should write a fixed ISO-8601 datetime literal to a DateTimeParam destination.

Use case: cross-sheet drills where the destination sheet has a universal date-range filter the source sheet doesn’t share — e.g. L1’s Pending Aging → Transactions. The aging sheet is a current-state view (rows can be arbitrarily old); the Transactions sheet’s universal-filter window defaults to last 7 days. Without a date write the drill-target leg falls outside the destination’s window and the table renders empty. Writing DrillStaticDateTime("1990-01-01T00:00:00.000Z") to the destination’s date-start param widens the window so the drill- target row is always visible.

QuickSight has no “now” or “rolling” expression you can write via SetParametersOperation — the only options are SourceField (column ref) or static CustomValues. Pick the static value carefully so the picker-shown date isn’t misleading; the L1 app uses "1990-01-01T..." for start and "2099-12-31T..." for end, framing the explicit “all time” intent.

Format: ISO-8601 with millisecond precision and the trailing Z, matching what the L2FT app already uses for its static StaticValues defaults.

Parameters:

value (str)

value: str
class recon_gen.common.tree.EmptyBehavior(*values)[source]

Bases: Enum

How a view resolves when its anchor day has no rows.

Captures the implicit precondition every QS view carries today (audit §5 residual tension): a view “knows” what to do when its declared anchor has no data, but that knowledge lives in developer-memory. Making it explicit on the View object turns the precondition into a property of the type.

LATEST_ON_EMPTY = 1

If anchor_day has no data, fall back to the latest day with data ≤ anchor. The default for KPI-style “latest statement” views — renders something useful instead of going blank.

SHOW_EMPTY = 2

Honor anchor_day literally even if it has no rows. The view shows empty. Right for “as-of date is a hard precondition” surfaces where blank IS the correct answer (e.g., a regulator snapshot at an unsettled date).

class recon_gen.common.tree.FilterControlLike(*args, **kwargs)[source]

Bases: Protocol

Tree-level filter control nodes.

datasets() participates in the L.1.7 dependency-graph walk — same shape as ParameterControlLike.datasets().

control_id: str | Literal[_AutoSentinel.AUTO]
datasets()[source]
Return type:

set[Dataset]

emit()[source]
Return type:

FilterControl

class recon_gen.common.tree.FilterCrossSheet(filter, control_id=AUTO)[source]

Bases: object

Cross-sheet filter control — surfaces the filter on multiple sheets via the same bound filter.

No title; the Cross-Sheet control inherits its UI from the underlying filter’s primary control.

Parameters:
  • filter (FilterLike)

  • control_id (str | Literal[_AutoSentinel.AUTO])

control_id: str | Literal[_AutoSentinel.AUTO] = 'auto'
datasets()[source]
Return type:

set[Dataset]

emit()[source]
Return type:

FilterControl

filter: FilterLike
class recon_gen.common.tree.FilterDateTimePicker(filter, title, type='DATE_RANGE', control_id=AUTO)[source]

Bases: object

Date/time picker control bound to a TimeRangeFilter.

Parameters:
  • filter (FilterLike)

  • title (str)

  • type (Literal['SINGLE_VALUED', 'DATE_RANGE'])

  • control_id (str | Literal[_AutoSentinel.AUTO])

control_id: str | Literal[_AutoSentinel.AUTO] = 'auto'
datasets()[source]
Return type:

set[Dataset]

emit()[source]
Return type:

FilterControl

filter: FilterLike
title: str
type: Literal['SINGLE_VALUED', 'DATE_RANGE'] = 'DATE_RANGE'
class recon_gen.common.tree.FilterDropdown(filter, title, type='MULTI_SELECT', selectable_values=None, control_id=AUTO)[source]

Bases: object

Dropdown control bound to an inner filter (CategoryFilter).

filter is the typed inner filter the dropdown drives — at emit time, the control’s SourceFilterId becomes filter.filter_id. The filter must be inside a FilterGroup that’s been registered on the analysis.

Parameters:
  • filter (FilterLike)

  • title (str)

  • type (Literal['SINGLE_SELECT', 'MULTI_SELECT'])

  • selectable_values (StaticValues | LinkedValues | None)

  • control_id (str | Literal[_AutoSentinel.AUTO])

control_id: str | Literal[_AutoSentinel.AUTO] = 'auto'
datasets()[source]
Return type:

set[Dataset]

emit()[source]
Return type:

FilterControl

filter: FilterLike
selectable_values: StaticValues | LinkedValues | None = None
title: str
type: Literal['SINGLE_SELECT', 'MULTI_SELECT'] = 'MULTI_SELECT'
class recon_gen.common.tree.FilterGroup(filters, cross_dataset='SINGLE_DATASET', enabled=True, filter_group_id=AUTO)[source]

Bases: object

Tree node for one analysis-level filter group.

Construct with FilterGroup(filter_group_id=..., filters=[...]), then attach scope by chaining .scope_visuals(sheet, [v1, v2]) or .scope_sheet(sheet). Both call methods validate immediately:

  • scope_visuals raises if any visual isn’t on the given sheet (catches the wrong-sheet bug at the call site).

  • scope_sheet is the all-visuals-on-sheet shortcut.

Multiple scope entries are allowed — the same FilterGroup can apply to (visual subset on sheet A) plus (all visuals on sheet B). Each entry emits its own SheetVisualScopingConfiguration.

filters takes a list of typed FilterLike wrappers (CategoryFilter / NumericRangeFilter / TimeRangeFilter above). Each wrapper’s emit() returns a models.Filter at emission time. Parameter-bound filters (NumericRangeFilter with minimum_parameter / maximum_parameter) carry object refs to ParameterDeclLike nodes — the type checker catches “filter bound to undeclared parameter” at the wiring site.

filter_group_id is optional (L.1.8.5 auto-ID). When omitted, the App’s tree walker assigns fg-{n} at emit time based on the FilterGroup’s index in the analysis’s filter group list.

Parameters:
  • filters (list[FilterLike])

  • cross_dataset (Literal['SINGLE_DATASET', 'ALL_DATASETS'])

  • enabled (bool)

  • filter_group_id (FilterGroupId | Literal[_AutoSentinel.AUTO])

calc_fields()[source]

CalcFields this group’s filters reference.

Return type:

set[CalcField]

cross_dataset: Literal['SINGLE_DATASET', 'ALL_DATASETS'] = 'SINGLE_DATASET'
datasets()[source]

Datasets this group’s filters reference (object refs).

Return type:

set[Dataset]

emit()[source]
Return type:

FilterGroup

enabled: bool = True
filter_group_id: FilterGroupId | Literal[_AutoSentinel.AUTO] = 'auto'
filters: list[FilterLike]
scope_sheet(sheet)[source]

Scope this filter to ALL visuals on a sheet.

Equivalent to the existing _selected_sheets_scope([sheet_id]) helper — emits Scope=ALL_VISUALS on the sheet’s SheetVisualScopingConfiguration, no per-visual list.

Return type:

FilterGroup

Parameters:

sheet (Sheet)

scope_visuals(sheet, visuals)[source]

Scope this filter to specific visuals on a sheet.

Construction-time check: every visual must already be registered on the given sheet via sheet.add_visual(). Cross-sheet wiring is the bug class this catches — without the check, a scope mixing visuals from sheet A with sheet B’s identifier emits a SheetVisualScopingConfiguration that silently drops the off-sheet visual at deploy time.

Return type:

FilterGroup

Parameters:
class recon_gen.common.tree.FilterLike(*args, **kwargs)[source]

Bases: Protocol

Structural type for tree-level filter nodes.

Each typed wrapper (CategoryFilter / NumericRangeFilter / TimeRangeFilter) satisfies this Protocol — exposes a filter_id, the underlying dataset (object ref), and emits a models.Filter. The dataset field participates in the L.1.7 dependency-graph walk.

filter_id is str | None because typed wrappers default to None and let App.resolve_auto_ids fill it. calc_field() returns the CalcField the filter references (or None if it points at a real column) — used by the dependency-graph walk and by FilterControl wrappers that need the filter_id post-resolve.

calc_field()[source]
Return type:

CalcField | None

dataset: Dataset
emit()[source]
Return type:

Filter

filter_id: str | Literal[_AutoSentinel.AUTO]
class recon_gen.common.tree.FilterSlider(filter, title, minimum_value, maximum_value, step_size, type='RANGE', control_id=AUTO)[source]

Bases: object

Slider control bound to a NumericRangeFilter.

Parameters:
  • filter (FilterLike)

  • title (str)

  • minimum_value (float)

  • maximum_value (float)

  • step_size (float)

  • type (Literal['SINGLE_POINT', 'RANGE'])

  • control_id (str | Literal[_AutoSentinel.AUTO])

control_id: str | Literal[_AutoSentinel.AUTO] = 'auto'
datasets()[source]
Return type:

set[Dataset]

emit()[source]
Return type:

FilterControl

filter: FilterLike
maximum_value: float
minimum_value: float
step_size: float
title: str
type: Literal['SINGLE_POINT', 'RANGE'] = 'RANGE'
class recon_gen.common.tree.GridSlot(element, col_span, row_span, col_index, row_index=None)[source]

Bases: object

One placement in a sheet’s grid layout.

Holds an OBJECT reference to the placed LayoutNode. The element id and element type are read off the node at emit time — the slot is agnostic about whether it carries a visual or a text box.

Parameters:
  • element (LayoutNode)

  • col_span (int)

  • row_span (int)

  • col_index (int)

  • row_index (int | None)

col_index: int
col_span: int
element: LayoutNode
emit()[source]
Return type:

GridLayoutElement

row_index: int | None = None
row_span: int
class recon_gen.common.tree.IntegerParam(name, default=<factory>, multi_valued=False, mapped_dataset_params=None)[source]

Bases: object

Integer-valued parameter declaration.

Parameters:
  • name (ParameterName)

  • default (list[int])

  • multi_valued (bool)

  • mapped_dataset_params (list[tuple[Dataset, str]] | None)

default: list[int]
emit()[source]
Return type:

ParameterDeclaration

mapped_dataset_params: list[tuple[Dataset, str]] | None = None
multi_valued: bool = False
name: ParameterName
class recon_gen.common.tree.KPI(title, subtitle, values=<factory>, value_zero_indicator=None, value_sign_indicator=None, visual_id=AUTO)[source]

Bases: object

KPI visual — single number per values entry, no grouping.

Field-well shape: Values=[Measure, ...]. Most KPIs use one measure; multiple are allowed and render as side-by-side numbers.

value_zero_indicator (BK.2) — optional binary check/X+color state on the primary value. See KPIValueZeroIndicator. Only fires on single-value KPIs (multi-value KPIs would need per-value indicators; not currently supported).

visual_id is optional (L.1.8.5 auto-ID). When omitted, the App’s tree walker assigns v-kpi-s{sheet_idx}-{visual_idx} at emit time. Pass an explicit VisualId(...) to override.

Parameters:
calc_fields()[source]

CalcFields this visual references via its field-well leaves.

Return type:

set[CalcField]

datasets()[source]
Return type:

set[Dataset]

property element_id: str
property element_type: Literal['VISUAL', 'FILTER_CONTROL', 'PARAMETER_CONTROL', 'TEXT_BOX', 'IMAGE']
emit()[source]
Return type:

Visual

subtitle: str
title: str
value_sign_indicator: KPIValueSignIndicator | None = None
value_zero_indicator: KPIValueZeroIndicator | None = None
values: list[Measure]
visual_id: VisualId | Literal[_AutoSentinel.AUTO] = 'auto'
class recon_gen.common.tree.KPIValueSignIndicator(inflow_is_healthy=True)[source]

Bases: object

BK.9 — sign-aware (▲ inflow / ▼ outflow) state indicator for a KPI’s primary value. Renders ARROW_UP in green when the aggregated value is non-negative, ARROW_DOWN in red when it’s negative.

Accessibility: icon is the load-bearing channel (▲ vs ▼ is distinguishable to all viewers regardless of color perception); color rides along as a parallel signal.

Same QS wire shape as KPIValueZeroIndicator (see for the three layered gotchas around hex case + expression DSL); the semantic differs — sign of the aggregated value, not zero-vs- not-zero. Currently used on Exec “Net Money Moved” (signed flow).

Parameters:

inflow_is_healthy (bool)

inflow_is_healthy: bool = True
class recon_gen.common.tree.KPIValueZeroIndicator(healthy_when_zero=True)[source]

Bases: object

BK.2 — binary healthy-when-zero state indicator for a KPI’s primary value. Renders a CHECKMARK in green when the aggregated value equals zero, an X in red otherwise.

Accessibility (per user 2026-05-29 — colorblind users in the loop): the ICON is the load-bearing channel. Color is a parallel signal for users who can read it, but the icon alone fully communicates healthy/broken state to red/green-colorblind viewers.

Wire shape: - QS: emits KPIVisual.ConditionalFormatting with two

ConditionalFormattingOptions entries, each carrying a PrimaryValue.Icon.CustomCondition block — the first matches zero, the second matches non-zero. QS evaluates the expression at render time against the displayed primary value.

  • App2: the data-fetcher reads the primary value and emits state_icon (Unicode glyph) + state_color (semantic keyword) on each values entry. bootstrap.js::renderKPI prepends the glyph + applies the color class.

The TWO renderers see different payloads but render the semantically-equivalent shape — same icon glyph, same color intent — so the operator gets the same signal on either surface.

Parameters:

healthy_when_zero (bool)

healthy_when_zero: bool = True
class recon_gen.common.tree.LayoutNode(*args, **kwargs)[source]

Bases: Protocol

Anything placeable in a sheet’s grid layout.

Both typed visual subtypes (KPI / Table / BarChart / Sankey) and the typed TextBox wrapper satisfy this Protocol. Each exposes element_id (the layout slot’s ElementId) and element_type ("VISUAL" or "TEXT_BOX") — the slot reads them off the node at emit time.

The Protocol means Sheet.place(node, ...) accepts both visuals and text boxes uniformly; QuickSight’s two-list split (Visuals vs TextBoxes in SheetDefinition) stays an emit-time concern that callers never see.

property element_id: str
property element_type: Literal['VISUAL', 'FILTER_CONTROL', 'PARAMETER_CONTROL', 'TEXT_BOX', 'IMAGE']
class recon_gen.common.tree.LineChart(title, subtitle, category=<factory>, values=<factory>, colors=<factory>, chart_type=None, category_label=None, value_label=None, sort_by=None, actions=<factory>, visual_id=AUTO)[source]

Bases: object

Line chart visual — one line per distinct colors value, plotted across category (x-axis) with height by values (y-axis).

Field-well shape: Category=[Dim, ...] + Values=[Measure, ...] + Colors=[Dim, ...].

chart_type selects LINE (default), AREA, or STACKED_AREA. sort_by is a (field_id, direction) tuple — direction "ASC" or "DESC" — and emits a CategorySort entry. All optional fields default to None so the QuickSight defaults apply when not specified.

visual_id is optional (L.1.8.5 auto-ID).

Parameters:
  • title (str)

  • subtitle (str)

  • category (list[Dim])

  • values (list[Measure])

  • colors (list[Dim])

  • chart_type (Literal['LINE', 'AREA', 'STACKED_AREA'] | None)

  • category_label (str | None)

  • value_label (str | None)

  • sort_by (tuple[Dim | Measure | str, Literal['ASC', 'DESC']] | None)

  • actions (list[Drill | SameSheetFilter])

  • visual_id (VisualId | Literal[_AutoSentinel.AUTO])

actions: list[Drill | SameSheetFilter]
calc_fields()[source]
Return type:

set[CalcField]

category: list[Dim]
category_label: str | None = None
chart_type: Literal['LINE', 'AREA', 'STACKED_AREA'] | None = None
colors: list[Dim]
datasets()[source]
Return type:

set[Dataset]

property element_id: str
property element_type: Literal['VISUAL', 'FILTER_CONTROL', 'PARAMETER_CONTROL', 'TEXT_BOX', 'IMAGE']
emit()[source]
Return type:

Visual

sort_by: tuple[Dim | Measure | str, Literal['ASC', 'DESC']] | None = None
subtitle: str
title: str
value_label: str | None = None
values: list[Measure]
visual_id: VisualId | Literal[_AutoSentinel.AUTO] = 'auto'
class recon_gen.common.tree.LinkedValues(dataset, column_name)[source]

Bases: object

Auto-populate the dropdown’s options from a dataset column.

Construct via the factory methods (L.1.22 — the canonical fields are dataset + column_name; the factories normalize the two legitimate construction forms into that pair, eliminating the dual-form __post_init__ validation):

  • LinkedValues.from_column(ds["col"]) — typed Column form. The Column carries its own dataset, so the factory derives dataset from the Column. Preferred — the contract validates the column name at the wiring site.

  • LinkedValues.from_string(dataset=ds, column_name="col") — bare-string escape hatch for datasets without a registered DatasetContract. Dataset must be passed explicitly.

The Dataset participates in the L.1.7 dependency-graph walk via the control’s datasets() method.

Parameters:
  • dataset (Dataset)

  • column_name (str)

column_name: str
dataset: Dataset
emit()[source]
Return type:

dict[str, Any]

classmethod from_column(column)[source]

Linked values pulled from a typed Column. The Column’s dataset is the source dataset.

Return type:

LinkedValues

Parameters:

column (Column)

classmethod from_string(*, dataset, column_name)[source]

Linked values pulled from a bare-string column name on the explicitly-passed dataset. Use when the dataset has no registered DatasetContract.

Return type:

LinkedValues

Parameters:
  • dataset (Dataset)

  • column_name (str)

class recon_gen.common.tree.Measure(dataset, column, kind, *, field_id=AUTO, currency=False, decimals=None)[source]

Bases: object

One value field-well entry — typed wrapper that emits a MeasureField with the appropriate aggregation shape.

dataset is a Dataset object ref (L.1.7 hard switch). The dataset must be registered on the parent App for the analysis to emit.

field_id is keyword-only and Optional (L.1.16 auto-ID). When omitted, the App walker assigns one based on the leaf’s tree position.

Use the classmethod factories for ergonomic construction: Measure.sum(...), Measure.distinct_count(...), etc. Aggregation kind determines which underlying model class is emitted (numerical aggregations on numeric columns, categorical on count-style aggregations).

Parameters:
  • dataset (Dataset)

  • column (str | CalcField | Column)

  • kind (Literal['sum', 'max', 'min', 'average', 'count', 'distinct_count'])

  • field_id (str | Literal[_AutoSentinel.AUTO])

  • currency (bool)

  • decimals (int | None)

classmethod average(dataset, column, *, field_id=AUTO, currency=False, decimals=None)[source]
Return type:

Measure

Parameters:
  • dataset (Dataset)

  • column (str | CalcField | Column)

  • field_id (str | Literal[_AutoSentinel.AUTO])

  • currency (bool)

  • decimals (int | None)

calc_field()[source]

The CalcField this Measure references, or None if it points at a real dataset column.

Return type:

CalcField | None

column: str | CalcField | Column
classmethod count(dataset, column, *, field_id=AUTO)[source]
Return type:

Measure

Parameters:
currency: bool = False
dataset: Dataset
decimals: int | None = None
classmethod distinct_count(dataset, column, *, field_id=AUTO)[source]
Return type:

Measure

Parameters:
emit()[source]
Return type:

MeasureField

field_id: str | Literal[_AutoSentinel.AUTO] = 'auto'
kind: Literal['sum', 'max', 'min', 'average', 'count', 'distinct_count']
classmethod max(dataset, column, *, field_id=AUTO, currency=False, decimals=None)[source]
Return type:

Measure

Parameters:
  • dataset (Dataset)

  • column (str | CalcField | Column)

  • field_id (str | Literal[_AutoSentinel.AUTO])

  • currency (bool)

  • decimals (int | None)

classmethod min(dataset, column, *, field_id=AUTO, currency=False, decimals=None)[source]
Return type:

Measure

Parameters:
  • dataset (Dataset)

  • column (str | CalcField | Column)

  • field_id (str | Literal[_AutoSentinel.AUTO])

  • currency (bool)

  • decimals (int | None)

classmethod sum(dataset, column, *, field_id=AUTO, currency=False, decimals=None)[source]
Return type:

Measure

Parameters:
  • dataset (Dataset)

  • column (str | CalcField | Column)

  • field_id (str | Literal[_AutoSentinel.AUTO])

  • currency (bool)

  • decimals (int | None)

class recon_gen.common.tree.NumericRangeFilter(dataset, column, minimum=None, maximum=None, null_option='NON_NULLS_ONLY', include_minimum=None, include_maximum=None, default_control=None, filter_id=AUTO)[source]

Bases: object

Filter on a numeric column. Range bounds are typed Bound variants — StaticBound(value) for a literal, ParameterBound( parameter) for a parameter-driven bound. The parameter-binding object ref catches “bound to a parameter that doesn’t exist” at the wiring site (the type checker resolves param.name).

L.1.22 — the discriminated Bound union makes the “both static_value and parameter set” bug class structurally impossible: a StaticBound carries a value but no parameter, and a ParameterBound carries a parameter but no value. Each side (min / max) is at most one Bound.

Parameters:
calc_field()[source]
Return type:

CalcField | None

column: str | CalcField | Column
dataset: Dataset
default_control: DefaultSliderControl | None = None
emit()[source]
Return type:

Filter

filter_id: str | Literal[_AutoSentinel.AUTO] = 'auto'
include_maximum: bool | None = None
include_minimum: bool | None = None
maximum: StaticBound | ParameterBound | None = None
property maximum_parameter: ParameterDeclLike | None

The parameter ref the maximum bound is bound to, or None.

minimum: StaticBound | ParameterBound | None = None
property minimum_parameter: ParameterDeclLike | None

The parameter ref the minimum bound is bound to, or None. Used by the parameter-references validator walk.

null_option: Literal['NON_NULLS_ONLY', 'ALL_VALUES', 'NULLS_ONLY'] = 'NON_NULLS_ONLY'
class recon_gen.common.tree.ParameterBound(parameter)[source]

Bases: object

A parameter-driven bound — emits Parameter (resolved from parameter.name) in the range filter.

Parameters:

parameter (ParameterDeclLike)

parameter: ParameterDeclLike
class recon_gen.common.tree.ParameterControlLike(*args, **kwargs)[source]

Bases: Protocol

Tree-level parameter control nodes.

datasets() participates in the L.1.7 dependency-graph walk — controls with LinkedValues populate from a Dataset, and that’s a dep. Controls with static values return an empty set.

control_id: str | Literal[_AutoSentinel.AUTO]
datasets()[source]
Return type:

set[Dataset]

emit()[source]
Return type:

ParameterControl

class recon_gen.common.tree.ParameterDateTimePicker(parameter, title, control_id=AUTO)[source]

Bases: object

Date/time picker control bound to a DateTime parameter.

Parameters:
  • parameter (ParameterDeclLike)

  • title (str)

  • control_id (str | Literal[_AutoSentinel.AUTO])

control_id: str | Literal[_AutoSentinel.AUTO] = 'auto'
datasets()[source]
Return type:

set[Dataset]

emit()[source]
Return type:

ParameterControl

parameter: ParameterDeclLike
title: str
class recon_gen.common.tree.ParameterDeclLike(*args, **kwargs)[source]

Bases: Protocol

Structural type for parameter declaration tree nodes.

emit()[source]
Return type:

ParameterDeclaration

name: ParameterName
class recon_gen.common.tree.ParameterDropdown(parameter, title, selectable_values, type='SINGLE_SELECT', hidden_select_all=False, cascade_source=None, cascade_match_column=None, control_id=AUTO)[source]

Bases: object

Dropdown control bound to a ParameterDeclLike parameter.

parameter is the typed parameter declaration the control reads/writes — at emit time, the control’s SourceParameterName becomes parameter.name. The type checker catches “control bound to a parameter that doesn’t exist” at the wiring site.

selectable_values accepts a StaticValues(["a", "b"]) for a fixed option list or LinkedValues(dataset, column) for an auto-populated list. The LinkedValues.dataset ref participates in the App’s dependency graph.

hidden_select_all=True suppresses the “Select all” entry — needed for SINGLE_SELECT dropdowns where empty/All semantics don’t apply (e.g. a Sankey anchor that needs exactly one value).

cascade_source makes this dropdown depend on another dropdown: when cascade_source changes value, QS refreshes THIS dropdown’s options. Required for cascading filters even when the source dataset’s params are bridged via MappedDataSetParameters — QS won’t refresh the dropdown widget without explicit UI-level cascade wiring (M.3.10c finding).

Parameters:
cascade_match_column: Column | None = None
cascade_source: ParameterDropdown | None = None
control_id: str | Literal[_AutoSentinel.AUTO] = 'auto'
datasets()[source]

Datasets this control references (via LinkedValues if any).

Return type:

set[Dataset]

emit()[source]
Return type:

ParameterControl

hidden_select_all: bool = False
parameter: ParameterDeclLike
selectable_values: StaticValues | LinkedValues
title: str
type: Literal['SINGLE_SELECT', 'MULTI_SELECT'] = 'SINGLE_SELECT'
class recon_gen.common.tree.ParameterSlider(parameter, title, minimum_value, maximum_value, step_size, control_id=AUTO)[source]

Bases: object

Slider control bound to a numeric parameter.

Parameters:
  • parameter (ParameterDeclLike)

  • title (str)

  • minimum_value (float)

  • maximum_value (float)

  • step_size (float)

  • control_id (str | Literal[_AutoSentinel.AUTO])

control_id: str | Literal[_AutoSentinel.AUTO] = 'auto'
datasets()[source]
Return type:

set[Dataset]

emit()[source]
Return type:

ParameterControl

maximum_value: float
minimum_value: float
parameter: ParameterDeclLike
step_size: float
title: str
class recon_gen.common.tree.ParameterTextField(parameter, title, control_id=AUTO)[source]

Bases: object

Free-text input control bound to a string parameter.

Right shape when the parameter’s option universe is unbounded / unknown at deploy time, or when the LinkedValues / StaticValues paths are unavailable. The analyst types a value; QS writes it to the bound parameter verbatim. No sample-values fetch — sidesteps the X.1.b Sample values not found failure mode entirely.

Y.1.m: rejects parameter.multi_valued=True at construction. QS text-field controls cannot commit non-empty values into a multi-valued parameter — the commit silently reverts both this parameter AND any sibling parameters back to their analysis-level defaults. Empty-string commits work (special case); any other string trips the state-reset bug. Reproduced live against L2FT Rails on 2026-05-06 — the metadata cascade was 100% broken in production for this reason. Construction-time error here prevents the whole class of regression at the wiring site.

Parameters:
  • parameter (ParameterDeclLike)

  • title (str)

  • control_id (str | Literal[_AutoSentinel.AUTO])

control_id: str | Literal[_AutoSentinel.AUTO] = 'auto'
datasets()[source]
Return type:

set[Dataset]

emit()[source]
Return type:

ParameterControl

parameter: ParameterDeclLike
title: str
class recon_gen.common.tree.Row(sheet, height, row_index)[source]

Bases: object

One horizontal band in a Sheet’s grid layout.

Tracks the column cursor as visuals + text boxes are added; refuses widths that overflow the 36-column grid. height becomes every visual’s row_span; the column cursor advances by each visual’s width (col_span). A new row from sheet.layout.row() lands below the previous row — the SheetLayout tracks the vertical cursor.

Parameters:
  • sheet (Sheet)

  • height (int)

  • row_index (int)

add_bar_chart(*, width, title, category=None, values=None, colors=None, subtitle, orientation=None, bars_arrangement=None, category_label=None, value_label=None, color_label=None, sort_by=None, actions=None, log_scale=False, visual_id=AUTO)[source]

Construct + register + place a BarChart in this row.

Return type:

BarChart

Parameters:
  • width (int)

  • title (str)

  • category (list[Dim] | None)

  • values (list[Measure] | None)

  • colors (list[Dim] | None)

  • subtitle (str)

  • orientation (Literal['HORIZONTAL', 'VERTICAL'] | None)

  • bars_arrangement (Literal['CLUSTERED', 'STACKED', 'STACKED_PERCENT'] | None)

  • category_label (str | None)

  • value_label (str | None)

  • color_label (str | None)

  • sort_by (tuple[Dim | Measure | str, Literal['ASC', 'DESC']] | None)

  • actions (list[Drill | SameSheetFilter] | None)

  • log_scale (bool)

  • visual_id (VisualId | Literal[_AutoSentinel.AUTO])

add_kpi(*, width, title, values=None, subtitle, value_zero_indicator=None, value_sign_indicator=None, visual_id=AUTO)[source]

Construct + register + place a KPI in this row.

Return type:

KPI

Parameters:
add_line_chart(*, width, title, category=None, values=None, colors=None, subtitle, chart_type=None, category_label=None, value_label=None, sort_by=None, actions=None, visual_id=AUTO)[source]

Construct + register + place a LineChart in this row.

Return type:

LineChart

Parameters:
  • width (int)

  • title (str)

  • category (list[Dim] | None)

  • values (list[Measure] | None)

  • colors (list[Dim] | None)

  • subtitle (str)

  • chart_type (Literal['LINE', 'AREA', 'STACKED_AREA'] | None)

  • category_label (str | None)

  • value_label (str | None)

  • sort_by (tuple[Dim | Measure | str, Literal['ASC', 'DESC']] | None)

  • actions (list[Drill | SameSheetFilter] | None)

  • visual_id (VisualId | Literal[_AutoSentinel.AUTO])

add_sankey(*, width, title, source, target, weight, subtitle, items_limit=None, actions=None, visual_id=AUTO)[source]

Construct + register + place a Sankey in this row.

Return type:

Sankey

Parameters:
  • width (int)

  • title (str)

  • source (Dim)

  • target (Dim)

  • weight (Measure)

  • subtitle (str)

  • items_limit (int | None)

  • actions (list[Drill | SameSheetFilter] | None)

  • visual_id (VisualId | Literal[_AutoSentinel.AUTO])

add_table(*, width, title, group_by=None, values=None, columns=None, subtitle, sort_by=None, actions=None, conditional_formatting=None, visual_id=AUTO)[source]

Construct + register + place a Table in this row.

Aggregated mode: pass group_by + values. Unaggregated mode (raw column display): pass columns. The two modes are mutually exclusive (Table.__post_init__ enforces this).

Return type:

Table

Parameters:
  • width (int)

  • title (str)

  • group_by (list[Dim] | None)

  • values (list[Measure] | None)

  • columns (list[Dim] | None)

  • subtitle (str)

  • sort_by (tuple[Dim | Measure | str, Literal['ASC', 'DESC']] | list[tuple[~recon_gen.common.tree.fields.Dim | ~recon_gen.common.tree.fields.Measure | str, ~typing.Literal['ASC', 'DESC']]] | None)

  • actions (list[Drill | SameSheetFilter] | None)

  • conditional_formatting (list[CellAccentText | CellAccentMenu] | None)

  • visual_id (VisualId | Literal[_AutoSentinel.AUTO])

add_text_box(text_box, *, width)[source]

Register + place a pre-constructed TextBox in this row.

TextBox content is verbose XML (built via common/rich_text), so the analyst constructs the TextBox separately and passes it here. Uniqueness is by object identity — placing the same TextBox in two rows is a programmer error caught by the row- cursor advance (you’d be calling _consume twice).

Return type:

TextBox

Parameters:
height: int
row_index: int
sheet: Sheet
class recon_gen.common.tree.SameSheetFilter(target_visuals, name, trigger='DATA_POINT_CLICK', action_id=AUTO)[source]

Bases: object

A click action that filters target visuals on the same sheet via ALL_FIELDS — the click-a-bar-to-narrow-the-table pattern.

target_visuals is a list of typed VisualLike object refs. The action’s emitted TargetVisuals field reads each visual’s (resolved) visual_id at emit time, so the action survives auto- ID resolution and refactors that rename a visual.

Distinct from Drill — emits a FilterOperation rather than a NavigationOperation + SetParametersOperation pair. Doesn’t cross sheets, doesn’t write parameters.

Parameters:
  • target_visuals (list[VisualLike])

  • name (str)

  • trigger (Literal['DATA_POINT_CLICK', 'DATA_POINT_MENU'])

  • action_id (str | Literal[_AutoSentinel.AUTO])

action_id: str | Literal[_AutoSentinel.AUTO] = 'auto'
emit()[source]
Return type:

VisualCustomAction

name: str
target_visuals: list[VisualLike]
trigger: Literal['DATA_POINT_CLICK', 'DATA_POINT_MENU'] = 'DATA_POINT_CLICK'
class recon_gen.common.tree.Sankey(title, subtitle, source=None, target=None, weight=None, items_limit=None, actions=<factory>, visual_id=AUTO)[source]

Bases: object

Sankey diagram visual — flows from source nodes to target nodes, ribbon thickness by weight.

Field-well shape: each of source / target / weight is a single Dim / Measure (the underlying model expects lists, but every usage today has exactly one entry; emit wraps).

items_limit caps the number of source / destination nodes rendered (matches the ItemsLimit shape on the underlying sort configuration). OtherCategories defaults to "INCLUDE" so capped flows roll into a “(others)” bucket rather than being dropped silently.

visual_id is optional (L.1.8.5 auto-ID).

Parameters:
  • title (str)

  • subtitle (str)

  • source (Dim | None)

  • target (Dim | None)

  • weight (Measure | None)

  • items_limit (int | None)

  • actions (list[Drill | SameSheetFilter])

  • visual_id (VisualId | Literal[_AutoSentinel.AUTO])

actions: list[Drill | SameSheetFilter]
calc_fields()[source]
Return type:

set[CalcField]

datasets()[source]
Return type:

set[Dataset]

property element_id: str
property element_type: Literal['VISUAL', 'FILTER_CONTROL', 'PARAMETER_CONTROL', 'TEXT_BOX', 'IMAGE']
emit()[source]
Return type:

Visual

items_limit: int | None = None
source: Dim | None = None
subtitle: str
target: Dim | None = None
title: str
visual_id: VisualId | Literal[_AutoSentinel.AUTO] = 'auto'
weight: Measure | None = None
class recon_gen.common.tree.Sheet(sheet_id, name, title, description, visuals=<factory>, parameter_controls=<factory>, filter_controls=<factory>, text_boxes=<factory>, grid_slots=<factory>)[source]

Bases: object

Tree node for one sheet on an Analysis / Dashboard.

Sheet has four concerns:

  1. Metadatasheet_id, name, title, description set at construction.

  2. Layout — visuals + text boxes placed in a grid. Accessed via sheet.layout: sheet.layout.row(height=...).add_kpi(width=, title=, values=, ...) for sequential rows, or sheet.layout. absolute(col_index=, row_index=, col_span=, row_span=).add_*(...) for explicit positioning. The layout’s row tracks the column cursor so call sites don’t compute col_index arithmetic by hand.

  3. Controls — parameter / filter controls live above/aside the canvas (NOT in the grid). Added directly on Sheet via sheet.add_parameter_dropdown(...) / add_parameter_slider / add_parameter_datetime_picker / add_filter_dropdown / add_filter_slider / add_filter_datetime_picker / add_filter_cross_sheet — one shortcut per control kind.

  4. Scope wiringsheet.scope(filter_group, [v1, v2]) scopes a filter group to specific visuals on this sheet.

emit() returns the SheetDefinition ready to drop into AnalysisDefinition.Sheets.

Parameters:
add_filter_cross_sheet(*, filter, control_id=AUTO)[source]

Construct + register a cross-sheet filter control on this sheet.

Return type:

FilterCrossSheet

Parameters:
  • filter (FilterLike)

  • control_id (str | Literal[_AutoSentinel.AUTO])

add_filter_datetime_picker(*, filter, title, type='DATE_RANGE', control_id=AUTO)[source]

Construct + register a filter datetime picker control.

Return type:

FilterDateTimePicker

Parameters:
  • filter (FilterLike)

  • title (str)

  • type (Literal['SINGLE_VALUED', 'DATE_RANGE'])

  • control_id (str | Literal[_AutoSentinel.AUTO])

add_filter_dropdown(*, filter, title, type='MULTI_SELECT', selectable_values=None, control_id=AUTO)[source]

Construct + register a filter dropdown control on this sheet.

Return type:

FilterDropdown

Parameters:
  • filter (FilterLike)

  • title (str)

  • type (Literal['SINGLE_SELECT', 'MULTI_SELECT'])

  • selectable_values (StaticValues | LinkedValues | None)

  • control_id (str | Literal[_AutoSentinel.AUTO])

add_filter_slider(*, filter, title, minimum_value, maximum_value, step_size, type='RANGE', control_id=AUTO)[source]

Construct + register a filter slider control on this sheet.

Return type:

FilterSlider

Parameters:
  • filter (FilterLike)

  • title (str)

  • minimum_value (float)

  • maximum_value (float)

  • step_size (float)

  • type (Literal['SINGLE_POINT', 'RANGE'])

  • control_id (str | Literal[_AutoSentinel.AUTO])

add_parameter_datetime_picker(*, parameter, title, control_id=AUTO)[source]

Construct + register a parameter datetime picker control.

Return type:

ParameterDateTimePicker

Parameters:
  • parameter (ParameterDeclLike)

  • title (str)

  • control_id (str | Literal[_AutoSentinel.AUTO])

add_parameter_dropdown(*, parameter, title, selectable_values, type='SINGLE_SELECT', hidden_select_all=False, cascade_source=None, cascade_match_column=None, control_id=AUTO)[source]

Construct + register a parameter dropdown control on this sheet.

selectable_values is required: a parameter dropdown without a source list (StaticValues / LinkedValues) shows only the QuickSight empty-state “All” placeholder, so the user can’t actually pick a value — the bound parameter stays unset and any CategoryFilter using it matches nothing. Caught the L1 Daily Statement account-dropdown footgun (v8.3.3 hotfix); the type makes it unrepresentable going forward.

Return type:

ParameterDropdown

Parameters:
add_parameter_slider(*, parameter, title, minimum_value, maximum_value, step_size, control_id=AUTO)[source]

Construct + register a parameter slider control on this sheet.

Return type:

ParameterSlider

Parameters:
  • parameter (ParameterDeclLike)

  • title (str)

  • minimum_value (float)

  • maximum_value (float)

  • step_size (float)

  • control_id (str | Literal[_AutoSentinel.AUTO])

add_parameter_text_field(*, parameter, title, control_id=AUTO)[source]

Construct + register a free-text parameter input control.

Use when the parameter’s option universe is unbounded / unknown at deploy time, or when LinkedValues / StaticValues paths fail (X.1.b L2FT cascade Value dropdown hit Sample values not found from QS’s lazy sample-values fetch on cold per-CI-run dashboards). Text input has no equivalent fetch path.

Return type:

ParameterTextField

Parameters:
  • parameter (ParameterDeclLike)

  • title (str)

  • control_id (str | Literal[_AutoSentinel.AUTO])

description: str
emit()[source]
Return type:

SheetDefinition

filter_controls: list[FilterControlLike]
find_visual(*, title=None, title_contains=None, visual_id=None)[source]

Look up a single visual on this sheet by title / partial title / visual id.

Designed for e2e + introspection: pass any of the three lookup keys and get the matching node back. Raises if no match or multiple matches — the API forces unambiguity at the call site so tests can rely on the result.

Auto-IDs (L.1.8.5) make this the right way to find a visual from outside the tree — IDs are not stable under tree restructuring, but titles + structural position are.

Return type:

VisualLike

Parameters:
  • title (str | None)

  • title_contains (str | None)

  • visual_id (VisualId | str | None)

grid_slots: list[GridSlot]
property layout: SheetLayout

L.1.21 — Layout namespace for grid placement.

Visuals + text boxes are added through the layout (one of: sheet.layout.row(height=...).add_kpi(width=..., ...) or sheet.layout.absolute(col_index=..., row_index=..., col_span=..., row_span=...).add_kpi(...)). The Sheet itself coordinates four concerns; layout is one of them, factored out so the call sites for “place a visual” don’t crowd against “register a control” or “scope a filter group”.

Lazily constructed; the same SheetLayout instance returns on subsequent accesses so the row cursor advances across calls.

name: str
parameter_controls: list[ParameterControlLike]
scope(fg, visuals)[source]

L.1.21 — scope a filter group to specific visuals on this sheet.

Reads more naturally than fg.scope_visuals(sheet, visuals) since the sheet is the contextual subject. Runtime check stays — Python’s type system can’t track which Sheet instance a visual was registered on without dependent types.

Return type:

FilterGroup

Parameters:
sheet_id: SheetId
text_boxes: list[TextBox]
title: str
visuals: list[VisualLike]
class recon_gen.common.tree.SheetLayout(sheet)[source]

Bases: object

Layout namespace on a Sheet — manages rows + absolute placements.

Tracks a vertical row cursor: each row(height=H) opens a new row at the current cursor and advances it by H for the next row. Absolute placements don’t advance the cursor (they’re independent of row flow).

Parameters:

sheet (Sheet)

absolute(*, col_index, row_index=None, col_span, row_span)[source]

Open an explicit-position slot. Doesn’t advance the row cursor — absolute placements are independent of row flow.

Return type:

AbsoluteSlot

Parameters:
  • col_index (int)

  • row_index (int | None)

  • col_span (int)

  • row_span (int)

row(*, height)[source]

Open a new row at the current vertical cursor with the given height. The cursor advances by height so the next row() call lands below this one.

Return type:

Row

Parameters:

height (int)

sheet: Sheet
class recon_gen.common.tree.StaticBound(value)[source]

Bases: object

A literal numeric bound — emits StaticValue in the range filter.

Parameters:

value (float)

value: float
class recon_gen.common.tree.StaticValues(values)[source]

Bases: object

Restrict the dropdown to a fixed list of options.

Parameters:

values (list[str])

emit()[source]
Return type:

dict[str, Any]

values: list[str]
class recon_gen.common.tree.StringParam(name, default=<factory>, multi_valued=False, mapped_dataset_params=None, value_when_unset=None)[source]

Bases: object

String-valued parameter declaration.

Default values are passed as a list — single-valued parameters use [] for “no default” or ["value"] for one default; multi-valued use ["a", "b", "c"].

mapped_dataset_params: optional list of (Dataset, name) pairs binding this analysis parameter to one or more dataset-level parameters substituted via <<$name>> in the dataset’s CustomSql.

value_when_unset: optional string value QS substitutes when this parameter is “unset” at evaluation time. REQUIRED when this parameter feeds a CascadingControlConfiguration — the cascade machinery substitutes this value into its internal match expression; the QS UI default of NULL makes the expression invalid (“calculated field has invalid syntax” tooltip on the target control). Set to the same sentinel used for the dataset’s default so cascade-without-pick is a no-op narrowing instead of a NULL comparison. See docs/reference/quicksight-quirks.md.

Parameters:
  • name (ParameterName)

  • default (list[str])

  • multi_valued (bool)

  • mapped_dataset_params (list[tuple[Dataset, str]] | None)

  • value_when_unset (str | None)

default: list[str]
emit()[source]
Return type:

ParameterDeclaration

mapped_dataset_params: list[tuple[Dataset, str]] | None = None
multi_valued: bool = False
name: ParameterName
value_when_unset: str | None = None
class recon_gen.common.tree.Table(title, subtitle, group_by=<factory>, values=<factory>, columns=<factory>, sort_by=None, actions=<factory>, conditional_formatting=None, visual_id=AUTO)[source]

Bases: object

Table visual — two field-well shapes:

  • Aggregated (default): group_by=[Dim, ...] + values=[Measure, ...]. One row per distinct group_by combination, aggregated by values. Emits TableAggregatedFieldWells.

  • Unaggregated: pass columns=[Dim, ...] (and leave group_by / values empty). Each cell shows the raw column value — no aggregation, one row per source row. Emits TableUnaggregatedFieldWells. Use this for detail/drill-source tables (AR Balances, AR Daily Statement transaction list).

Optional sort_by is a (field_ref, direction) tuple — direction is "ASC" or "DESC".

Optional conditional_formatting passes through to the model’s raw dict (see common/clickability.py for the standard accent-text and tint-background helpers).

visual_id is optional (L.1.8.5 auto-ID).

Parameters:
  • title (str)

  • subtitle (str)

  • group_by (list[Dim])

  • values (list[Measure])

  • columns (list[Dim])

  • sort_by (tuple[Dim | Measure | str, Literal['ASC', 'DESC']] | list[tuple[~recon_gen.common.tree.fields.Dim | ~recon_gen.common.tree.fields.Measure | str, ~typing.Literal['ASC', 'DESC']]] | None)

  • actions (list[Drill | SameSheetFilter])

  • conditional_formatting (list[CellAccentText | CellAccentMenu] | None)

  • visual_id (VisualId | Literal[_AutoSentinel.AUTO])

actions: list[Drill | SameSheetFilter]
calc_fields()[source]
Return type:

set[CalcField]

columns: list[Dim]
conditional_formatting: list[CellAccentText | CellAccentMenu] | None = None
datasets()[source]
Return type:

set[Dataset]

property element_id: str
property element_type: Literal['VISUAL', 'FILTER_CONTROL', 'PARAMETER_CONTROL', 'TEXT_BOX', 'IMAGE']
emit()[source]
Return type:

Visual

group_by: list[Dim]
sort_by: tuple[Dim | Measure | str, Literal['ASC', 'DESC']] | list[tuple[Dim | Measure | str, Literal['ASC', 'DESC']]] | None = None
subtitle: str
title: str
values: list[Measure]
visual_id: VisualId | Literal[_AutoSentinel.AUTO] = 'auto'
class recon_gen.common.tree.TextBox(text_box_id, content)[source]

Bases: object

Tree-side rich-text box.

Mirrors the KPI / Table / BarChart / Sankey typed wrapper pattern: callers construct typed nodes; the tree owns layout / id resolution / emission. Compose content via the common.rich_text helpers (rt.text_box(...)) and pass the string in.

text_box_id is required (no auto-ID for text boxes — they don’t carry a _AUTO_KIND). The ID surfaces in the layout’s ElementId and the sheet’s TextBoxes list.

Parameters:
  • text_box_id (str)

  • content (str)

content: str
property element_id: str
property element_type: Literal['VISUAL', 'FILTER_CONTROL', 'PARAMETER_CONTROL', 'TEXT_BOX', 'IMAGE']
emit()[source]
Return type:

SheetTextBox

text_box_id: str
class recon_gen.common.tree.TimeEqualityFilter(dataset, column, parameter, time_granularity=None, default_control=None, filter_id=AUTO)[source]

Bases: object

Single-day equality filter on a date column.

Used when paired with a SINGLE_VALUED date picker control — TimeRangeFilter renders broken in the QS UI when paired with a single-day picker; TimeEqualityFilter is the right shape for “show rows where the date column equals one specific day”.

parameter (a typed DateTimeParam ref) is the only binding mode currently exposed (the AR Daily Statement use case). Extend with rolling_date / static_value factories when other apps need them.

Parameters:
calc_field()[source]
Return type:

CalcField | None

column: str | CalcField | Column
dataset: Dataset
default_control: DefaultDateTimePickerControl | None = None
emit()[source]
Return type:

Filter

filter_id: str | Literal[_AutoSentinel.AUTO] = 'auto'
parameter: ParameterDeclLike
time_granularity: Literal['YEAR', 'QUARTER', 'MONTH', 'WEEK', 'DAY', 'HOUR', 'MINUTE', 'SECOND', 'MILLISECOND'] | None = None
class recon_gen.common.tree.TimeRangeFilter(dataset, column, minimum=None, maximum=None, null_option='NON_NULLS_ONLY', time_granularity=None, include_minimum=None, include_maximum=None, default_control=None, filter_id=AUTO)[source]

Bases: object

Filter on a date / datetime column.

dataset is a Dataset object ref (L.1.7 hard switch). column is a ColumnRef — a real column or a CalcField.

minimum and maximum are passthrough dicts for now (the existing usage takes a variety of shapes — RollingDate, StaticValue, Parameter — and lifting all of them under typed wrappers can wait for the L.2/L.3/L.4 ports to surface concrete needs).

Parameters:
  • dataset (Dataset)

  • column (str | CalcField | Column)

  • minimum (dict[str, Any] | None)

  • maximum (dict[str, Any] | None)

  • null_option (Literal['NON_NULLS_ONLY', 'ALL_VALUES', 'NULLS_ONLY'])

  • time_granularity (Literal['YEAR', 'QUARTER', 'MONTH', 'WEEK', 'DAY', 'HOUR', 'MINUTE', 'SECOND', 'MILLISECOND'] | None)

  • include_minimum (bool | None)

  • include_maximum (bool | None)

  • default_control (DefaultDateTimePickerControl | None)

  • filter_id (str | Literal[_AutoSentinel.AUTO])

calc_field()[source]
Return type:

CalcField | None

column: str | CalcField | Column
dataset: Dataset
default_control: DefaultDateTimePickerControl | None = None
emit()[source]
Return type:

Filter

filter_id: str | Literal[_AutoSentinel.AUTO] = 'auto'
include_maximum: bool | None = None
include_minimum: bool | None = None
maximum: dict[str, Any] | None = None
minimum: dict[str, Any] | None = None
null_option: Literal['NON_NULLS_ONLY', 'ALL_VALUES', 'NULLS_ONLY'] = 'NON_NULLS_ONLY'
time_granularity: Literal['YEAR', 'QUARTER', 'MONTH', 'WEEK', 'DAY', 'HOUR', 'MINUTE', 'SECOND', 'MILLISECOND'] | None = None
class recon_gen.common.tree.VisualLike(*args, **kwargs)[source]

Bases: Protocol

Structural type for tree-level visual nodes.

Typed subtypes (KPI / Table / BarChart / Sankey) satisfy this Protocol — duck-typed so subtypes don’t have to inherit from a base class. Subtypes contribute to the L.1.7 dependency-graph walk via datasets() / calc_fields().

All visual nodes also satisfy LayoutNode (in structure.py) via element_id + element_type so they can be placed in a sheet’s grid layout (sheet.layout.row(...).add_<kind>(...)).

visual_id is VisualId | AutoResolved — typed subtypes default to AUTO and App.resolve_auto_ids replaces it with the derived id before emit. The walker / emit assert via isinstance narrowing.

calc_fields()[source]
Return type:

set[CalcField]

datasets()[source]
Return type:

set[Dataset]

emit()[source]
Return type:

Visual

visual_id: VisualId | Literal[_AutoSentinel.AUTO]
recon_gen.common.tree.auto_id(slug)[source]

Deterministic UUID v5 from a tree-position slug.

Same input → same UUID across runs (test stability) AND across machines. Output matches QS’s UUID format so the editor accepts it. (M.4.4.10c)

Return type:

str

Parameters:

slug (str)

Modules

actions

Typed drill action wrapper (L.1.10).

calc_fields

Typed analysis-level calculated fields (L.1.8).

controls

Typed Filter + Parameter control wrappers (L.1.9).

datasets

Dataset tree nodes (L.1.7) + typed Column refs (L.1.17).

date_view

The date-view tree primitive (D5).

fields

Field-well leaf nodes — Dim + Measure typed wrappers.

filters

Filter primitives — typed Filter wrappers + FilterGroup.

formatting

Typed conditional-format wrappers for Table cells.

parameters

Typed ParameterDecl subtypes — one per models.py declaration variant.

structure

Structural tree types — GridSlot / Sheet / Analysis / Dashboard / App.

text_boxes

Tree-side wrapper for models.SheetTextBox — the rich-text box nodes used on landing-page sheets (Getting Started).

visuals

Typed Visual subtypes — one per visual kind in active use.