diff --git a/README.md b/README.md index cc3a9fcdd..d8ba9e3d1 100644 --- a/README.md +++ b/README.md @@ -203,7 +203,7 @@ Because tenancy is enforced at the graph rather than in application predicates, ### Identity & Access -Programmatic access uses `X-API-Key`; the browser apps use short-lived JWTs. How a person *logs in* is a deployment decision — password, WebAuthn passkey, or an enterprise identity provider — published at `GET /v1/auth/providers`, so one frontend build renders whichever posture the backend is configured for. +Programmatic access uses `X-API-Key`; the browser apps use short-lived JWTs. How a person _logs in_ is a deployment decision — password, WebAuthn passkey, or an enterprise identity provider — published at `GET /v1/auth/providers`, so one frontend build renders whichever posture the backend is configured for. Enterprise SSO (OIDC) and SCIM 2.0 provisioning ship in the repository, off by default — available to any fork without a license gate. Provisioning is link-only: SCIM creates accounts and OIDC only resolves already-provisioned ones, so the identity provider owns the account lifecycle in both directions. @@ -341,7 +341,7 @@ pip install robosystems-client ## Documentation -### Documentation +### Technical Documentation **Getting Started & Platform:** @@ -367,7 +367,7 @@ pip install robosystems-client - [RoboLedger Demo Walkthrough](https://robosystems.ai/docs/technical/roboledger-demo-walkthrough) · [SEC XBRL Pipeline](https://robosystems.ai/docs/technical/sec-xbrl-pipeline) · [Custom Graph Schema](https://robosystems.ai/docs/technical/custom-graph-schema) -### Developer Documentation (Codebase) +### Codebase Documentation Each package documents itself — read the README for a directory before working in it. diff --git a/pyproject.toml b/pyproject.toml index a638a224d..174fa0e1f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -68,7 +68,7 @@ dependencies = [ "pandas>=2.3.0,<3.0", "pyarrow>=24.0.0,<25", # hold: 25 is a fresh major feeding the duckdb/ladybug arrow seams; bump deliberately, paired with icebug 13 "rdflib>=7.0.0,<8.0", - "xbrlkit>=0.18.0,<0.19.0", + "xbrlkit>=0.18.1,<0.19.0", # External API Integrations "intuit-oauth>=1.2.0,<2.0", "python-quickbooks>=0.9.0,<1.0", diff --git a/robosystems/models/api/extensions/reports.py b/robosystems/models/api/extensions/reports.py index c2956e968..646a19898 100644 --- a/robosystems/models/api/extensions/reports.py +++ b/robosystems/models/api/extensions/reports.py @@ -569,12 +569,11 @@ class ReportBundleDownloadResponse(BaseModel): "`disclosure_notes`: the XBRL 2.1 zip ships statements only — " "tenant-authored disclosure notes render on screen and ride the " "Tavi and holon flavors, but are excluded from this file. " - "The Tavi compiled model carries the statements and notes but has no " - "home for `ib_envelopes` (the per-Network Information Block " - "payloads), `definition_links` (equivalence, general-special, " - "essence-alias and mapping arcs), `reporting_style`, " - "`framework_pins`, `fact_sets` (the FactSet partition and each " - "fact's structure pin) or `filing_lifecycle` (filing status, " + "The Tavi compiled model carries the statements, notes, reporting " + "style and FactSet partition but has no home for `ib_envelopes` " + "(the per-Network Information Block payloads), `definition_links` " + "(equivalence, general-special, essence-alias and mapping arcs), " + "`framework_pins` or `filing_lifecycle` (filing status, " "supersession, share provenance); all of it rides the holon flavor." ), ) diff --git a/robosystems/operations/serialization/xbrl/tavi.py b/robosystems/operations/serialization/xbrl/tavi.py index 5cfeed119..0ddcb3e75 100644 --- a/robosystems/operations/serialization/xbrl/tavi.py +++ b/robosystems/operations/serialization/xbrl/tavi.py @@ -20,13 +20,12 @@ TAVI_MEDIA_TYPE = "application/json" -# The holon keeps all of it. +# The holon keeps all of it. The reporting style and the fact-set partition +# ride the Tavi as `rs:` properties since xbrlkit 0.18.1. TAVI_OMITTED_CONTENT: tuple[str, ...] = ( "ib_envelopes", "definition_links", - "reporting_style", "framework_pins", - "fact_sets", "filing_lifecycle", ) diff --git a/tests/operations/serialization/test_tavi_emitter.py b/tests/operations/serialization/test_tavi_emitter.py index 3e8c0d41d..d8c3f15cd 100644 --- a/tests/operations/serialization/test_tavi_emitter.py +++ b/tests/operations/serialization/test_tavi_emitter.py @@ -61,7 +61,13 @@ def test_envelope_is_a_compiled_model_scoped_on_the_report() -> None: @pytest.mark.unit def test_entity_is_named_under_its_scheme_and_labelled() -> None: document = _document() - assert document["xbrlModel"]["entities"] == [{"name": "entity:ent_01"}] + # The legal name differs from the name, so it rides as a property. + assert document["xbrlModel"]["entities"] == [ + { + "name": "entity:ent_01", + "properties": [{"property": "rs:legalName", "value": "Test Co LLC"}], + } + ] assert _labels_of(document, "entity:ent_01") == {"xbrl:label": "Test Co"} assert _fact(document, "rs-gaap:Assets")["factDimensions"]["xbrl:entity"] == ( "entity:ent_01" @@ -160,6 +166,32 @@ def test_serialization_is_compact_deterministic_and_dispatched() -> None: assert json.loads(first)["documentInfo"]["documentType"] == DOCTYPE_COMPILED +@pytest.mark.unit +def test_the_reporting_style_and_fact_set_partition_ride_as_rs_properties() -> None: + """What `omitted_content` no longer names has to be in the file.""" + bundle = _bundle() + document = json.loads(serialize_to_tavi(bundle)) + model = document["xbrlModel"] + assert document["documentInfo"]["namespaces"]["rs"] == "https://robosystems.ai/vocab/" + assert {"property": "rs:reportingStyle", "value": bundle.reporting_style} in model[ + "properties" + ] + network_properties = { + p["property"]: p["value"] + for n in model["networks"] + for p in n.get("properties", []) + } + assert network_properties["rs:structureId"] in {"struct_bs", "struct_note1"} + assert "rs:blockType" in network_properties + pins = [ + p["value"] + for f in model["facts"] + for p in f.get("properties", []) + if p["property"] == "rs:structureId" + ] + assert len(pins) == sum(1 for f in bundle.facts if f.structure_id) + + @pytest.mark.unit def test_description_and_omitted_content_name_what_the_file_lacks() -> None: assert tavi_description( @@ -169,3 +201,6 @@ def test_description_and_omitted_content_name_what_the_file_lacks() -> None: ).startswith("RoboLedger live snapshot (Test Co)") assert "ib_envelopes" in TAVI_OMITTED_CONTENT assert "definition_links" in TAVI_OMITTED_CONTENT + # Carried as `rs:` properties since xbrlkit 0.18.1. + assert "reporting_style" not in TAVI_OMITTED_CONTENT + assert "fact_sets" not in TAVI_OMITTED_CONTENT diff --git a/uv.lock b/uv.lock index 35842431e..7adb92cc1 100644 --- a/uv.lock +++ b/uv.lock @@ -3813,7 +3813,7 @@ requires-dist = [ { name = "uvicorn", specifier = ">=0.48.0,<1.0" }, { name = "uvloop", marker = "sys_platform != 'win32'", specifier = ">=0.21.0,<1.0" }, { name = "webauthn", specifier = ">=2.7.0,<3.0" }, - { name = "xbrlkit", specifier = ">=0.18.0,<0.19.0" }, + { name = "xbrlkit", specifier = ">=0.18.1,<0.19.0" }, { name = "zstandard", specifier = ">=0.23,<1" }, ] provides-extras = ["dev"] @@ -4640,7 +4640,7 @@ wheels = [ [[package]] name = "xbrlkit" -version = "0.18.0" +version = "0.18.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "arelle-release" }, @@ -4652,9 +4652,9 @@ dependencies = [ { name = "regex" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/76/3a/9d6c43a81956f939d0206db4a8ca73ea884afdd4379ae572f08eff57f9ba/xbrlkit-0.18.0.tar.gz", hash = "sha256:540f006e1c1b7a854ae16f6c88f3d402e5b5fd2bb1d8c04e3d04dc3b9b5950b0", size = 2107480, upload-time = "2026-09-24T23:22:31.057Z" } +sdist = { url = "https://files.pythonhosted.org/packages/16/c8/f5972b91e0c5a501ded25eef32d90fb7f0b43f40ce920d16643671763a4f/xbrlkit-0.18.1.tar.gz", hash = "sha256:b99b46dc7d372cceca5c86f43c2d3fb76e042da5da3fd31ed1c062dabc912765", size = 2108968, upload-time = "2026-09-24T23:42:02.372Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/25/8e/7a8c838cff3aa0919ca1360900a2594cdfba4067d450d7aeac51536674ad/xbrlkit-0.18.0-py3-none-any.whl", hash = "sha256:25a46e8be53a2b82bd025a3966ab22e711df1be727eccf0a32594885260b59f3", size = 2063721, upload-time = "2026-09-24T23:22:29.3Z" }, + { url = "https://files.pythonhosted.org/packages/a5/d5/367752ae27befd860f3782c09b4575b835b84f02a702ddfc5fdc280812c9/xbrlkit-0.18.1-py3-none-any.whl", hash = "sha256:a129433bcb6cb6df17b6091f99b788126c7daa4468b7c47366dc558d6d8ca288", size = 2064968, upload-time = "2026-09-24T23:42:00.346Z" }, ] [[package]]