feat(roboledger): text-block disclosure binding (Document → Nonnumeric fact)#889
Merged
Conversation
Add the 'document' provenance arm (document_id + section_id + content_hash as the drift signal) to the FactProvenance union, the BindTextBlockRequest/Response models, and the non-numeric fields on FactLite (text_value/fact_type/content_type; value becomes optional) and RenderingRowLite (text_value). fact_to_lite maps them through; GraphQL derives the new fields automatically.
…act) Bind a platform Document (or one section, by markdown-parser slug) to a disclosure element as a Nonnumeric text-block fact in a standing factset_type='disclosure' FactSet stamped with document provenance. Cross-database by design: Documents live in the platform DB, so the linkage is the provenance reference, never an FK, and the lookup is graph-scoped so another graph's document id is a plain miss. Re-binding the same element+period replaces the fact and refreshes the content hash; the bind is also the sprint's only text authoring surface, so it stamps element.item_type='text_block' when unset. Hand-written router endpoint + MCP tool (both sessions needed — the registrar runner passes neither the platform session nor graph_id).
Harden the fact-driven disclosure picker: scope to the report's taxonomy closure (downward-recursive on parent_taxonomy_id, threaded from the already-resolved report taxonomy) and require a MEMBER (to-endpoint) fact hit — a total-only fact no longer renders a note with an empty, structurally unfailable breakdown. Report builds now snapshot standing text-block bindings: the latest factset_type='disclosure' set per text-block structure inside the report window is copied into a report FactSet carrying the document provenance verbatim, so a filed report keeps the narrative as bound at generation time even if the document is later edited; regeneration's DELETE-by-report_id clears snapshots while standing sets survive. Cross-graph share carries the non-numeric fact columns through the copy (without them a shared narrative violates the value-shape CHECK).
Text-block CAPs render narrative rows, not a numeric grid: a new builder shares every envelope atom with the statement path and swaps only the rendering projection — one row per bound Nonnumeric fact carrying text_value. disclosure.build_envelope dispatches by CAP: TEXT_BLOCK_CAPS route here, every other disclosure CAP keeps the statement family's grid path. A bound-but-arcless note still renders (the FactSet is proof of content); a bare registry row stays None.
…t arms build_report_bundle now appends a disclosure envelope for every picked disclosure structure (numeric roll_up notes and text-block notes), so notes ride the downloadable flavors instead of only the read API. BundleFact grows the non-numeric arm (text_value/fact_type/ content_type; value and unit_ref optional); units are minted for numeric facts only. The JSON-LD encoder emits rs:value string literals (+ rs:contentType, rs:factType) for Nonnumeric facts with no numericValue/unit/decimals — SHACL shapes already admit the shape — and the canonical context gains the three terms. The XBRL emitter keeps stripping disclosures and adds a belt-and-braces filter so a Nonnumeric fact can never reach the numeric formatter.
…wcase demo The Driftline demo authors a 'Significant Accounting Policies' note (text_block CAP, one concept per policy document) and binds the three uploaded policy documents to it via bind-text-block before materialize, so the narrative facts reach the graph in the same run and the annual report snapshots them. upload_policies now returns title->document_id (the PG id — not the OpenSearch-side document_id) for the bind step, and the verify step prints narrative row heads alongside numeric rows.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Second half of the text-block-facts increment (report-engine M3, on top of #888): narrative disclosures become first-class facts, authored from the existing Document system.
bind-text-blockoperation (REST + MCP): binds a platform Document — or one section by slug — to a disclosure element as aNonnumericfact in a standing'disclosure'FactSet with document provenance (document_id+section_id+ full-sha256content_hashas the drift signal). Re-binding the same element + period replaces the fact and refreshes the hash. Cross-database by provenance reference (Documents live in the platform DB); the lookup is graph-scoped.text_value); numeric notes keep the grid path. GraphQL derives the new fields automatically.rs:valuestring literals withcontentType/factTypeand no unit/decimals for Nonnumeric facts; XBRL keeps stripping disclosures with a belt-and-braces Nonnumeric filter.No schema migration (rides #888's 0021).
Testing
tests/without maxfail: 11,105 passed (two remaining failures are the known pre-existing graph_api ordering flake — passes in isolation).textValue(facts + rendering rows); JSON-LD carries the narratives with no numericValue/unit and both disclosure envelopes; materialized graph hasNonnumericfact nodes with no unit edges; XBRL still Arelle-valid, SHACL conforms; matching-period re-bind returnsreplaced=trueagainst the original standing set, different period creates a separate binding.