Skip to content

feat(extensions): non-numeric fact substrate (migration 0021) + calc-overlay precedence fix#888

Merged
jfrench9 merged 3 commits into
mainfrom
feature/nonnumeric-fact-substrate
Jul 19, 2026
Merged

feat(extensions): non-numeric fact substrate (migration 0021) + calc-overlay precedence fix#888
jfrench9 merged 3 commits into
mainfrom
feature/nonnumeric-fact-substrate

Conversation

@jfrench9

@jfrench9 jfrench9 commented Jul 19, 2026

Copy link
Copy Markdown
Member

Summary

First half of the text-block-facts increment (report-engine M3): the OLTP fact model gains the non-numeric value path the graph Fact node has always had, and materialize passes it through instead of hardcoding.

  • Schema (extensions migration 0021, TenantOps fan-out): facts.value nullable + string_value/fact_type/value_type/content_type/decimals with a numeric-XOR-nonnumeric CHECK; elements.item_type; fact_sets admits 'disclosure'; TEXT_BLOCK_CAPS named next to the CAP vocabulary.
  • Materialize passthrough: non-numeric slots read from the row; Unit nodes + FACT_HAS_UNIT derive from facts.unit (numeric facts only) instead of pinning USD; Element item_type/is_textblock mapped through. Existing graph output is byte-identical (verified on the showcase demo: same fact_type/decimals/value_type, single unit_usd, 1:1 unit edges, legacy element flags).
  • Numeric-reader guards: rules engine binding/summing/balances, statement rendering, and the statement read query filter to numeric facts.
  • Rules-engine precedence fix: structure-local calculation arcs now win over the global calc DAG per parent (calc_dag.merge_calculations), so a disclosure note decomposing a global calc parent foots against its own members. Global remains the fallback.

No writer behavior changes in this PR — the authoring surface (Document→text-block bind, picker, envelope rendering) follows in the second PR.

Testing

  • Full tests/operations/ + tests/taxonomy/ + tests/migrations/ without maxfail: 2887 passed.
  • Migration applied locally to public + both tenant schemas and verified column/constraint shape.
  • Coffee-roaster showcase demo end-to-end on the migrated stack: statements + inventory note render identically, 25 rules pass / 0 fail, Arelle valid, SHACL conforms.

jfrench9 added 3 commits July 19, 2026 00:03
The graph Fact node has modeled non-numeric facts since inception; the
OLTP facts table was numeric-only (value FLOAT NOT NULL). Close the
asymmetry: value becomes nullable; add string_value, fact_type,
value_type, content_type, and decimals with a numeric-XOR-nonnumeric
CHECK. Element gains item_type (value domain, orthogonal to
element_type). fact_sets admits the 'disclosure' type for standing
text-block binding sets, and structure.py names the TEXT_BLOCK_CAPS
subset. TenantOps fan-out to public + every tenant schema; NOT NULL
columns backfill via a temporary DEFAULT then drop it so migrated
schemas match freshly provisioned ones.
…item_type

Stop hardcoding the graph Fact's non-numeric slots: pass fact_type,
value_type, and content_type through from the OLTP row, COALESCE
string_value into the value column, and fall back to the legacy '-2'
decimals only for numeric rows (Nonnumeric carries NULL, matching
XBRL). Unit nodes now derive from the facts actually present (numeric
only — nonNumeric facts carry no unitRef) with the static unit_usd row
retained for fact-less graphs, and FACT_HAS_UNIT reads facts.unit
instead of pinning every fact to USD. Element staging maps item_type
through and derives is_textblock/is_numeric/is_integer/is_shares from
it, with NULL item_type keeping the legacy numeric defaults — existing
graph output is byte-identical.
…readers

Invert the calc-DAG overlay precedence: a structure's own calculation
arcs are its footing spec, so a disclosure note that decomposes a
global calc parent (Revenues, OperatingExpenses, ...) now foots against
its own members instead of the statement-level children absent from its
FactSet (which reported the rollup skipped or falsely failed). The
merge lives in calc_dag.py as merge_calculations — local wins per
parent, global as fallback, pure. With local-wins the all-children-
absent skip guard becomes semantically correct for locally-arced
parents; locked by tests.

Also guard every numeric fact reader for the new non-numeric arm:
rule variable binding, SumEquals aggregation, period-balance loading,
statement rendering, and the get_statement fact query all filter to
numeric facts so a text-block fact neither crashes float paths nor
counts as present/zero in footing.
@jfrench9
jfrench9 merged commit cf718b5 into main Jul 19, 2026
6 checks passed
@jfrench9
jfrench9 deleted the feature/nonnumeric-fact-substrate branch July 19, 2026 05:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant