Skip to content

feat(tavi): carry an authored report's own fields as rs: properties - #73

Merged
jfrench9 merged 1 commit into
mainfrom
feature/tavi-report-properties
Sep 24, 2026
Merged

jfrench9 merged 1 commit into
mainfrom
feature/tavi-report-properties

Conversation

@jfrench9

Copy link
Copy Markdown
Member

Summary

An authored report's own fields now survive the TAVI. A RoboLedger tenant report carries content TAVI has no object for (its reporting style, the entity's legal name, each network's structure, block type, fact set and order, each fact's structure). The holon kept them and the TAVI dropped them, which is one of the things standing between the TAVI and replacing the holon for tenant reports.

Changes

  • serialize/tavi.py: REPORT_PROPERTIES declares seven rs: model-defined properties under the RoboSystems vocabulary (https://robosystems.ai/vocab/, the holon's), each with a property type (§11.6, definitional: false) naming the objects it may sit on:

    • on the model: rs:reportingStyle;
    • on the entity: rs:legalName;
    • on a network: rs:blockType, rs:structureId, rs:factSetId, rs:structureOrder;
    • on a fact: rs:structureId.

    Each property and its type is written only when something carries a value, and rs is bound only then.

  • deserialize/tavi.py: reads them back onto FilingMeta, EntityIdentity, Network and XbrlFact, matched on the expanded IRI whatever prefix a document binds (as provenance is). A fact's rs:structureId is not counted as an unmapped property.

  • Two deliberate omissions keep a filing's document spec-shaped:

    • the legal name is written only when it differs from the entity's name (the parse sets it to the name);
    • the report IRI is not written: the report namespace already names the report, and a filing's report_uri is its EDGAR document URL.
  • Tests: the fields round-trip through TAVI; a filing's TAVI (with a legal name equal to its name and an EDGAR report_uri) carries no rs binding and no property types.

Output Impact

CHANGED OUTPUT for authored reports only. A tenant report's TAVI gains the rs binding, the property types and the properties above. A filing's output is unchanged: NVDA's and JPM's FY2025 10-K TAVI, parsed from EDGAR, are byte-identical to 0.18.0's (sha256 8059a98c… and fe5d9160…), with no rs binding.

On two RoboLedger demo reports (bundle → TAVI → model, compared field by field), the round trip no longer loses block_type, structure_id, fact_set_id or reporting_style. What it still differs on is outside this change: the report IRI and a legal name equal to the name (by design, above); per-fact entity identifiers; derived period fields; and decimals, which the platform's bundle sends as unknown and TAVI can only write as exact.

Testing

  • just test-all: 552 passed, 2 skipped; ruff, format and basedpyright clean.
  • SEC byte identity: to_tavi_report over a fresh EDGAR parse of NVDA and JPM, sha256 against the 0.18.0 release.
  • Tenant round trip: the RoboSystems demo reports' bundles through bundle_to_xbrl_model and this branch's writer and reader.

An authored report (a RoboLedger tenant's) carries content TAVI has no
object for: the reporting style, the entity's legal name, and for each
network its structure, block type, fact set and order, and each fact's
structure. The holon kept them; the TAVI dropped them. They now ride as
model-defined properties under the RoboSystems vocabulary (sections 5.13 /
11.6), each declared by a property type, on the model, entity, network and
fact objects, and the reader reads them back, matched on the IRI whatever
prefix a document binds.

A filing carries none of them, so its document is unchanged: NVDA's and
JPM's TAVI are byte-identical to 0.18.0's, with no `rs` binding. The legal
name is written only when it differs from the name (the parse sets it to
the name), and the report IRI is left out: the report namespace already
names the report, and a filing's report_uri is its EDGAR document URL.
@jfrench9

Copy link
Copy Markdown
Member Author

@claude please review this PR

@claude

claude Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Claude finished @jfrench9's task in 4m 25s —— View job


Claude's review of this PR

Todo list

  • Gather context (diff, changed files)
  • Understand the request (code review only, no implementation)
  • Review xbrlkit/serialize/tavi.py changes
  • Review xbrlkit/deserialize/tavi.py changes
  • Review tests/test_deserialize.py changes
  • Post final review feedback

Summary

This is a clean, well-scoped change. REPORT_PROPERTIES is written and read symmetrically, matched on expanded IRI so it's prefix-agnostic (consistent with how provenance is already matched), and the two "spec-shaped document" omissions (legal name only when it differs from name, no report-IRI property) are both implemented and tested. I traced the round trip end-to-end (serialize → _property_types/_namespaces → deserialize → _report_property/_optional_str/_optional_int) and didn't find correctness bugs. I also manually verified the byte-identity claim's mechanism: _report_properties_used() in the writer and the network.kind == "definition" skip in _networks_and_groups use the same filter, so a filing with none of these fields truly adds nothing to the document.

A few minor things worth a look, none blocking:

  1. xbrlkit/serialize/tavi.py _namespaces (~line 745-750) — namespaces["rs"] = HOLON_VOCAB is now set under two separate if blocks (once for provenance, once for _report_properties_used(model)). Harmless (same key, same value), but could collapse into one condition for clarity:

    if any(fact.provenance is not None for fact in model.facts) or _report_properties_used(model):
        namespaces["rs"] = HOLON_VOCAB
  2. xbrlkit/deserialize/tavi.py module docstring — the "what TAVI carries" list at the top of the file wasn't updated to mention the new rs: report properties being read back. Given how deliberately this file documents its own contract, adding a line there would keep it accurate.

  3. tests/test_deserialize.py, test_a_filings_tavi_carries_no_report_properties — model.filing.report_uri = "https://www.sec.gov/..." doesn't actually exercise anything: no writer code reads filing.report_uri, so this line doesn't test the "report IRI is never written" claim from the PR description. Harmless, but it doesn't verify what the docstring implies.

  4. New allowedObjects names — xbrl:xbrlModelObject, xbrl:entityObject, xbrl:networkObject (xbrlkit/serialize/tavi.py:1308-1313) are introduced fresh here; nothing else in the repo cross-checks these against a spec fixture (only xbrl:factObject was previously asserted in tests/test_tavi.py:875). They follow the existing <jsonKey>Object naming convention consistently, so this is likely fine — just flagging that a typo here wouldn't be caught by any test.

Design choices I checked and found sound:

  • structure_order uses is not None (not truthiness) in _network_properties, so 0 round-trips correctly.
  • fact.structure_id on a fact is explicitly excluded from unmapped_fact_properties in _fact_provenance (xbrlkit/deserialize/tavi.py:970-972), matching the PR description.
  • The default test fixture (_model()) sets no fact provenance, so test_a_filings_tavi_carries_no_report_properties's "rs:" not in json.dumps(document) assertion is valid (provenance's rs:sourceKind/rs:contentHash would otherwise also trip that check).
  • No new line in the diff exceeds the repo's 88-char ruff limit (checked via regex, not full ruff).

Environment note: I could not run just test-all, uv run pytest, or even python3 -c "..." in this review sandbox — those commands required approval I have no user available to grant. I reviewed statically (full diff read, cross-referencing model.py, tracing call sites) rather than executing the test suite; the PR description's own reported results (552 passed, ruff/format/basedpyright clean, SEC byte-identity hashes) were not independently re-verified.

@jfrench9
jfrench9 merged commit 879efb9 into main Sep 24, 2026
4 checks passed
@jfrench9
jfrench9 deleted the feature/tavi-report-properties branch September 24, 2026 23:40
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