diff --git a/robosystems_client/clients/ledger_client.py b/robosystems_client/clients/ledger_client.py index 99359ef..fd354a4 100644 --- a/robosystems_client/clients/ledger_client.py +++ b/robosystems_client/clients/ledger_client.py @@ -538,6 +538,7 @@ "jsonld": "JSONLD", "holon-jsonld": "HOLON_JSONLD", "xbrl-2.1": "XBRL_2_1", + "tavi": "TAVI", } @@ -2216,22 +2217,24 @@ def download_report_bundle( to: str | Path | None = None, expires_in: int = 300, ) -> ReportBundleDownload: - """Download a published Report's serialization bundle (JSON-LD or XBRL 2.1). + """Download a published Report's serialization bundle. A download is a read, so the presigned URL is resolved through the GraphQL ``reportDownloadUrl`` field (the REST download route was retired). Every flavor resolves to a short-lived presigned S3 URL — - JSON-LD is stamped at publish time; XBRL is materialized + cached on - first request. The client follows the URL and pulls the bytes. + JSON-LD is stamped at publish time; XBRL, the holon and the Tavi + model are materialized + cached on first request. The client follows + the URL and pulls the bytes. Args: graph_id: Graph identifier owning the Report. report_id: Report identifier (``rpt_``-prefixed ULID). format: Serialization flavor — ``"jsonld"`` (default, the flat canonical bundle), ``"holon-jsonld"`` (the dataset-form - scene/boundary/projection holon), or ``"xbrl-2.1"``. The enum - names ``"JSONLD"`` / ``"HOLON_JSONLD"`` / ``"XBRL_2_1"`` are - also accepted. + scene/boundary/projection holon), ``"xbrl-2.1"``, or + ``"tavi"`` (the Project Tavi compiled model, compact JSON). + The enum names ``"JSONLD"`` / ``"HOLON_JSONLD"`` / + ``"XBRL_2_1"`` / ``"TAVI"`` are also accepted. to: Optional file path to write the bytes to. When set, the returned ``ReportBundleDownload.path`` points at the written file. @@ -2275,9 +2278,11 @@ def download_report_bundle( ) generation_count = info.generation_count - default_ext = {"XBRL_2_1": "zip", "HOLON_JSONLD": "holon.jsonld"}.get( - gql_format, "jsonld" - ) + default_ext = { + "XBRL_2_1": "zip", + "HOLON_JSONLD": "holon.jsonld", + "TAVI": "tavi.json", + }.get(gql_format, "jsonld") filename = ( _parse_filename(artifact.headers.get("content-disposition", "")) or f"{report_id}-g{generation_count or 1}.{default_ext}" diff --git a/robosystems_client/graphql/generated/enums.py b/robosystems_client/graphql/generated/enums.py index a554a8b..f32c6d3 100644 --- a/robosystems_client/graphql/generated/enums.py +++ b/robosystems_client/graphql/generated/enums.py @@ -5,3 +5,4 @@ class ReportDownloadFormat(str, Enum): JSONLD = "JSONLD" HOLON_JSONLD = "HOLON_JSONLD" XBRL_2_1 = "XBRL_2_1" + TAVI = "TAVI" diff --git a/robosystems_client/graphql/schema.graphql b/robosystems_client/graphql/schema.graphql index 8124a42..553669d 100644 --- a/robosystems_client/graphql/schema.graphql +++ b/robosystems_client/graphql/schema.graphql @@ -2153,7 +2153,7 @@ type ReportBundleDownload { generationCount: Int! """ - Content the Report carries that this flavor does not. ``disclosure_notes``: the XBRL 2.1 zip ships statements only — tenant-authored disclosure notes render on screen and ride the JSON-LD and holon flavors, but are excluded from this file. + Content the Report carries that this flavor does not. ``disclosure_notes``: the XBRL 2.1 zip ships statements only — tenant-authored disclosure notes render on screen and ride the JSON-LD 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, supersession, share provenance); all of it rides the JSON-LD and holon flavors. """ omittedContent: [String!]! } @@ -2162,6 +2162,7 @@ enum ReportDownloadFormat { JSONLD HOLON_JSONLD XBRL_2_1 + TAVI } """ diff --git a/tests/test_ledger_client.py b/tests/test_ledger_client.py index 9bf0501..75a135e 100644 --- a/tests/test_ledger_client.py +++ b/tests/test_ledger_client.py @@ -2162,6 +2162,29 @@ def test_xbrl_download_follows_presigned_url( assert result.generation_count == 3 assert mock_query.call_args.args[2]["format"] == "XBRL_2_1" + @patch("robosystems_client.clients.ledger_client.httpx.Client") + def test_tavi_download_maps_the_alias_and_names_the_file( + self, mock_client_cls, mock_config, graph_id + ): + """Tavi: ``"tavi"`` maps to ``TAVI``; without a content-disposition the + filename falls back to ``{report}-g{n}.tavi.json``.""" + artifact = self._mock_artifact(b'{"documentInfo":{}}', "unused") + artifact.headers = {} + self._patch_httpx(mock_client_cls, artifact) + with patch.object( + LedgerClient, + "_query", + return_value=self._gql_data("application/json", "tavi", gen=4), + ) as mock_query: + result = LedgerClient(mock_config).download_report_bundle( + graph_id, "rpt_01", format="tavi" + ) + + assert result.filename == "rpt_01-g4.tavi.json" + assert result.format == "tavi" + assert result.content_type == "application/json" + assert mock_query.call_args.args[2]["format"] == "TAVI" + @patch("robosystems_client.clients.ledger_client.httpx.Client") def test_to_arg_writes_bytes_to_disk( self, mock_client_cls, mock_config, graph_id, tmp_path