recon_gen.common.tree.filters

Filter primitives — typed Filter wrappers + FilterGroup.

Filter groups carry their scope as object refs (Sheet + [VisualLike]) and validate at the call site that scoped visuals belong to the referenced sheet. Catches the wrong-sheet bug class — the type checker carries the wiring; raise at construction confirms.

Typed Filter wrappers (CategoryFilter / NumericRangeFilter / TimeRangeFilter) sit alongside the FilterGroup. They share names with the underlying models.py classes — models are aliased on import so user-facing code reads cleanly:

from recon_gen.common.tree import CategoryFilter, FilterGroup

The NumericRangeFilter’s minimum_parameter / maximum_parameter fields take a ParameterDeclLike object ref — the type checker catches “filter bound to undeclared parameter” at the wiring site, where the existing string-keyed Parameter=name pattern lets typos through to deploy.

Classes

CategoryFilter(dataset, column, binding[, ...])

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

DefaultDateTimePickerControl(title[, type])

Inline default widget config for TimeRangeFilter.

DefaultDropdownControl(title[, type])

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

DefaultSliderControl(title, minimum_value, ...)

Inline default widget config for NumericRangeFilter.

FilterGroup(filters[, cross_dataset, ...])

Tree node for one analysis-level filter group.

FilterLike(*args, **kwargs)

Structural type for tree-level filter nodes.

NumericRangeFilter(dataset, column[, ...])

Filter on a numeric column.

ParameterBound(parameter)

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

StaticBound(value)

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

TimeEqualityFilter(dataset, column, parameter)

Single-day equality filter on a date column.

TimeRangeFilter(dataset, column[, minimum, ...])

Filter on a date / datetime column.

class recon_gen.common.tree.filters.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.filters.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.filters.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.filters.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.filters.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.filters.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.filters.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.filters.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.filters.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.filters.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.filters.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