From 205dd6f50ba3241584a28f0b9178ceff365f3b40 Mon Sep 17 00:00:00 2001 From: "Joseph T. French" Date: Thu, 24 Sep 2026 19:47:19 -0500 Subject: [PATCH 1/2] feat(ledger): list_reports takes a lifecycle filter and returns filing_status The server's `reports` query now takes `lifecycle` (CURRENT by default, which leaves archived reports out; ARCHIVED; ALL). `list_reports` forwards it as an optional argument, and the list selects `filingStatus`. Additive. The refreshed schema and REST models carry the updated transition-filing-status copy (filed <-> archived). Claude-Session: https://claude.ai/code/session_01Jaz7hXqfysytKc57zpyWY6 --- .../transition_filing_status.py | 56 +++++++++++-------- robosystems_client/clients/ledger_client.py | 25 +++++++-- .../graphql/generated/__init__.py | 3 +- .../graphql/generated/client.py | 8 ++- robosystems_client/graphql/generated/enums.py | 6 ++ .../graphql/generated/list_ledger_reports.py | 1 + .../graphql/generated/operations.py | 5 +- .../ledger/ListLedgerReports.graphql | 6 +- robosystems_client/graphql/schema.graphql | 17 +++++- .../transition_filing_status_request.py | 14 +++-- tests/test_ledger_client.py | 25 +++++++++ 11 files changed, 118 insertions(+), 48 deletions(-) diff --git a/robosystems_client/api/robo_ledger_reports/transition_filing_status.py b/robosystems_client/api/robo_ledger_reports/transition_filing_status.py index fcc0b17..7d24592 100644 --- a/robosystems_client/api/robo_ledger_reports/transition_filing_status.py +++ b/robosystems_client/api/robo_ledger_reports/transition_filing_status.py @@ -111,8 +111,9 @@ def sync_detailed( ) -> Response[ErrorResponse | OperationEnvelopeReportResponse]: """Transition Filing Status - Move a Report along the non-file legs of the filing lifecycle (draft ↔ under_review, filed → - archived). Use 'file-report' to reach 'filed' so audit fields land cleanly. + Move a Report along the non-file legs of the filing lifecycle (draft ↔ under_review, filed ↔ + archived). Archiving takes a filed report off the current list without deleting it; unarchiving + returns it to 'filed'. Use 'file-report' to file a draft so audit fields land cleanly. **Idempotency**: supply an `Idempotency-Key` header to make safe retries; replays within 24 hours return the same envelope. Reusing the key with a different body returns HTTP 409 Conflict. @@ -123,10 +124,11 @@ def sync_detailed( body (TransitionFilingStatusRequest): Generic filing-status transition — escape hatch for non-file moves. - Used for `draft → under_review` (submit for review) and - `filed → archived` (supersede / retire). Filing the package goes - through :class:`FileReportRequest` so `filed_at` / `filed_by` - audit fields land cleanly. + Used for `draft ↔ under_review` (submit for review, or send back), + `filed → archived` (take a filed report off the current list) and + `archived → filed` (bring it back). Filing a draft goes through + :class:`FileReportRequest` so `filed_at` / `filed_by` audit fields + land cleanly. Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -158,8 +160,9 @@ def sync( ) -> ErrorResponse | OperationEnvelopeReportResponse | None: """Transition Filing Status - Move a Report along the non-file legs of the filing lifecycle (draft ↔ under_review, filed → - archived). Use 'file-report' to reach 'filed' so audit fields land cleanly. + Move a Report along the non-file legs of the filing lifecycle (draft ↔ under_review, filed ↔ + archived). Archiving takes a filed report off the current list without deleting it; unarchiving + returns it to 'filed'. Use 'file-report' to file a draft so audit fields land cleanly. **Idempotency**: supply an `Idempotency-Key` header to make safe retries; replays within 24 hours return the same envelope. Reusing the key with a different body returns HTTP 409 Conflict. @@ -170,10 +173,11 @@ def sync( body (TransitionFilingStatusRequest): Generic filing-status transition — escape hatch for non-file moves. - Used for `draft → under_review` (submit for review) and - `filed → archived` (supersede / retire). Filing the package goes - through :class:`FileReportRequest` so `filed_at` / `filed_by` - audit fields land cleanly. + Used for `draft ↔ under_review` (submit for review, or send back), + `filed → archived` (take a filed report off the current list) and + `archived → filed` (bring it back). Filing a draft goes through + :class:`FileReportRequest` so `filed_at` / `filed_by` audit fields + land cleanly. Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -200,8 +204,9 @@ async def asyncio_detailed( ) -> Response[ErrorResponse | OperationEnvelopeReportResponse]: """Transition Filing Status - Move a Report along the non-file legs of the filing lifecycle (draft ↔ under_review, filed → - archived). Use 'file-report' to reach 'filed' so audit fields land cleanly. + Move a Report along the non-file legs of the filing lifecycle (draft ↔ under_review, filed ↔ + archived). Archiving takes a filed report off the current list without deleting it; unarchiving + returns it to 'filed'. Use 'file-report' to file a draft so audit fields land cleanly. **Idempotency**: supply an `Idempotency-Key` header to make safe retries; replays within 24 hours return the same envelope. Reusing the key with a different body returns HTTP 409 Conflict. @@ -212,10 +217,11 @@ async def asyncio_detailed( body (TransitionFilingStatusRequest): Generic filing-status transition — escape hatch for non-file moves. - Used for `draft → under_review` (submit for review) and - `filed → archived` (supersede / retire). Filing the package goes - through :class:`FileReportRequest` so `filed_at` / `filed_by` - audit fields land cleanly. + Used for `draft ↔ under_review` (submit for review, or send back), + `filed → archived` (take a filed report off the current list) and + `archived → filed` (bring it back). Filing a draft goes through + :class:`FileReportRequest` so `filed_at` / `filed_by` audit fields + land cleanly. Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -245,8 +251,9 @@ async def asyncio( ) -> ErrorResponse | OperationEnvelopeReportResponse | None: """Transition Filing Status - Move a Report along the non-file legs of the filing lifecycle (draft ↔ under_review, filed → - archived). Use 'file-report' to reach 'filed' so audit fields land cleanly. + Move a Report along the non-file legs of the filing lifecycle (draft ↔ under_review, filed ↔ + archived). Archiving takes a filed report off the current list without deleting it; unarchiving + returns it to 'filed'. Use 'file-report' to file a draft so audit fields land cleanly. **Idempotency**: supply an `Idempotency-Key` header to make safe retries; replays within 24 hours return the same envelope. Reusing the key with a different body returns HTTP 409 Conflict. @@ -257,10 +264,11 @@ async def asyncio( body (TransitionFilingStatusRequest): Generic filing-status transition — escape hatch for non-file moves. - Used for `draft → under_review` (submit for review) and - `filed → archived` (supersede / retire). Filing the package goes - through :class:`FileReportRequest` so `filed_at` / `filed_by` - audit fields land cleanly. + Used for `draft ↔ under_review` (submit for review, or send back), + `filed → archived` (take a filed report off the current list) and + `archived → filed` (bring it back). Filing a draft goes through + :class:`FileReportRequest` so `filed_at` / `filed_by` audit fields + land cleanly. Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. diff --git a/robosystems_client/clients/ledger_client.py b/robosystems_client/clients/ledger_client.py index 8673255..fab6067 100644 --- a/robosystems_client/clients/ledger_client.py +++ b/robosystems_client/clients/ledger_client.py @@ -2186,9 +2186,20 @@ def create_report( envelope = self._call_op("Create report", response) return self._typed_result("Create report", envelope, ReportResponse) - def list_reports(self, graph_id: str) -> list[ListLedgerReportsReportsReports]: - """List all reports for a graph (includes received shared reports).""" - data = self._query(graph_id, LIST_LEDGER_REPORTS_GQL) + def list_reports( + self, graph_id: str, lifecycle: str | None = None + ) -> list[ListLedgerReportsReportsReports]: + """List reports for a graph (includes received shared reports). + + Args: + graph_id: The graph to list. + lifecycle: ``"current"`` (the server default) leaves archived + reports out, ``"archived"`` returns only those, ``"all"`` every + report. The enum names ``"CURRENT"`` / ``"ARCHIVED"`` / + ``"ALL"`` are also accepted. + """ + variables = {"lifecycle": lifecycle.upper()} if lifecycle else None + data = self._query(graph_id, LIST_LEDGER_REPORTS_GQL, variables) page = ListLedgerReports.model_validate(data).reports return page.reports if page else [] @@ -2372,9 +2383,11 @@ def transition_filing_status( ) -> ReportResponse: """Move a Report along the non-file legs of the filing lifecycle. - Use ``file_report()`` to reach 'filed' so audit fields land cleanly. - Other transitions (draft ↔ under_review, filed → archived) go through - here so the legal-transition graph stays in one place. + Use ``file_report()`` to file a draft so audit fields land cleanly. + Other transitions (draft ↔ under_review, filed ↔ archived) go through + here so the legal-transition graph stays in one place. Archiving takes + a filed report off the current list without deleting it; unarchiving + (``target_status="filed"``) brings it back. """ body = TransitionFilingStatusRequest( report_id=report_id, target_status=target_status diff --git a/robosystems_client/graphql/generated/__init__.py b/robosystems_client/graphql/generated/__init__.py index 629f154..abadf93 100644 --- a/robosystems_client/graphql/generated/__init__.py +++ b/robosystems_client/graphql/generated/__init__.py @@ -1,7 +1,7 @@ from .base_client import BaseClient from .base_model import BaseModel, Upload from .client import Client -from .enums import ReportDownloadFormat +from .enums import ReportDownloadFormat, ReportLifecycle from .exceptions import ( GraphQLClientError, GraphQLClientGraphQLError, @@ -676,6 +676,7 @@ "MappingCandidates", "MappingCandidatesMappingCandidates", "ReportDownloadFormat", + "ReportLifecycle", "SEARCH_LIBRARY_ELEMENTS_GQL", "SearchLibraryElements", "SearchLibraryElementsSearchLibraryElements", diff --git a/robosystems_client/graphql/generated/client.py b/robosystems_client/graphql/generated/client.py index a068d58..19a2d00 100644 --- a/robosystems_client/graphql/generated/client.py +++ b/robosystems_client/graphql/generated/client.py @@ -2,7 +2,7 @@ from .base_client import BaseClient from .base_model import UNSET, UnsetType -from .enums import ReportDownloadFormat +from .enums import ReportDownloadFormat, ReportLifecycle from .get_information_block import GetInformationBlock from .get_investor_holdings import GetInvestorHoldings from .get_investor_portfolio_block import GetInvestorPortfolioBlock @@ -768,8 +768,10 @@ def list_ledger_publish_lists( data = self.get_data(response) return ListLedgerPublishLists.model_validate(data) - def list_ledger_reports(self, **kwargs: Any) -> ListLedgerReports: - variables: dict[str, object] = {} + def list_ledger_reports( + self, lifecycle: ReportLifecycle, **kwargs: Any + ) -> ListLedgerReports: + variables: dict[str, object] = {"lifecycle": lifecycle} response = self.execute( query=LIST_LEDGER_REPORTS_GQL, operation_name="ListLedgerReports", diff --git a/robosystems_client/graphql/generated/enums.py b/robosystems_client/graphql/generated/enums.py index fe80777..2e9800a 100644 --- a/robosystems_client/graphql/generated/enums.py +++ b/robosystems_client/graphql/generated/enums.py @@ -1,6 +1,12 @@ from enum import Enum +class ReportLifecycle(str, Enum): + CURRENT = "CURRENT" + ARCHIVED = "ARCHIVED" + ALL = "ALL" + + class ReportDownloadFormat(str, Enum): HOLON_JSONLD = "HOLON_JSONLD" XBRL_2_1 = "XBRL_2_1" diff --git a/robosystems_client/graphql/generated/list_ledger_reports.py b/robosystems_client/graphql/generated/list_ledger_reports.py index d8f623a..49e41ab 100644 --- a/robosystems_client/graphql/generated/list_ledger_reports.py +++ b/robosystems_client/graphql/generated/list_ledger_reports.py @@ -18,6 +18,7 @@ class ListLedgerReportsReportsReports(BaseModel): name: str taxonomy_id: str = Field(alias="taxonomyId") generation_status: str = Field(alias="generationStatus") + filing_status: str = Field(alias="filingStatus") period_type: str = Field(alias="periodType") period_start: Optional[str] = Field(alias="periodStart") period_end: Optional[str] = Field(alias="periodEnd") diff --git a/robosystems_client/graphql/generated/operations.py b/robosystems_client/graphql/generated/operations.py index 360a979..24f704e 100644 --- a/robosystems_client/graphql/generated/operations.py +++ b/robosystems_client/graphql/generated/operations.py @@ -1592,13 +1592,14 @@ """ LIST_LEDGER_REPORTS_GQL = """ -query ListLedgerReports { - reports { +query ListLedgerReports($lifecycle: ReportLifecycle! = CURRENT) { + reports(lifecycle: $lifecycle) { reports { id name taxonomyId generationStatus + filingStatus periodType periodStart periodEnd diff --git a/robosystems_client/graphql/operations/ledger/ListLedgerReports.graphql b/robosystems_client/graphql/operations/ledger/ListLedgerReports.graphql index 2146244..95cb304 100644 --- a/robosystems_client/graphql/operations/ledger/ListLedgerReports.graphql +++ b/robosystems_client/graphql/operations/ledger/ListLedgerReports.graphql @@ -1,7 +1,7 @@ -query ListLedgerReports { - reports { +query ListLedgerReports($lifecycle: ReportLifecycle! = CURRENT) { + reports(lifecycle: $lifecycle) { reports { - id name taxonomyId generationStatus periodType + id name taxonomyId generationStatus filingStatus periodType periodStart periodEnd comparative mappingId aiGenerated createdAt lastGenerated entityName sourceGraphId sourceReportId sharedAt diff --git a/robosystems_client/graphql/schema.graphql b/robosystems_client/graphql/schema.graphql index 220a44f..d6a95d7 100644 --- a/robosystems_client/graphql/schema.graphql +++ b/robosystems_client/graphql/schema.graphql @@ -463,8 +463,13 @@ type Query { """ closingBookStructures: ClosingBookStructures - """List all report definitions for this graph.""" - reports: ReportList + """List report definitions for this graph, newest first.""" + reports( + """ + `CURRENT` (the default) leaves out archived reports, `ARCHIVED` returns only those, `ALL` returns every report. + """ + lifecycle: ReportLifecycle! = CURRENT + ): ReportList """Single report definition with structures + entity name.""" report( @@ -2453,6 +2458,12 @@ type StructureSummary { blockType: String! } +enum ReportLifecycle { + CURRENT + ARCHIVED + ALL +} + type ReportPackage { id: ID! name: String! @@ -3000,7 +3011,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 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, supersession, share provenance); all of it rides the holon flavor. + 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 Tavi and holon flavors, but are excluded from this file. 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. """ omittedContent: [String!]! } diff --git a/robosystems_client/models/transition_filing_status_request.py b/robosystems_client/models/transition_filing_status_request.py index e687bf5..467704b 100644 --- a/robosystems_client/models/transition_filing_status_request.py +++ b/robosystems_client/models/transition_filing_status_request.py @@ -13,15 +13,17 @@ class TransitionFilingStatusRequest: """Generic filing-status transition — escape hatch for non-file moves. - Used for `draft → under_review` (submit for review) and - `filed → archived` (supersede / retire). Filing the package goes - through :class:`FileReportRequest` so `filed_at` / `filed_by` - audit fields land cleanly. + Used for `draft ↔ under_review` (submit for review, or send back), + `filed → archived` (take a filed report off the current list) and + `archived → filed` (bring it back). Filing a draft goes through + :class:`FileReportRequest` so `filed_at` / `filed_by` audit fields + land cleanly. Attributes: report_id (str): The Report to transition. - target_status (str): Target lifecycle state: `under_review` (submit a draft for review) or `archived` (supersede - / retire a filed report). Reaching `filed` goes through `file-report` so audit fields land cleanly. + target_status (str): Target lifecycle state: `under_review` (submit a draft for review), `draft` (send it back), + `archived` (take a filed report off the current list; it stays a record) or `filed` (unarchive). Filing a draft + goes through `file-report` so audit fields land cleanly. """ report_id: str diff --git a/tests/test_ledger_client.py b/tests/test_ledger_client.py index 59b9ea8..dd807d4 100644 --- a/tests/test_ledger_client.py +++ b/tests/test_ledger_client.py @@ -201,6 +201,31 @@ def test_list_accounts_with_pagination(self, mock_execute, mock_config, graph_id assert variables["classification"] == "asset" assert variables["limit"] == 50 + @patch("robosystems_client.graphql.client.GraphQLClient.execute") + def test_list_reports_default_leaves_lifecycle_to_the_server( + self, mock_execute, mock_config, graph_id + ): + mock_execute.return_value = {"reports": {"reports": []}} + client = LedgerClient(mock_config) + assert client.list_reports(graph_id) == [] + query, variables = mock_execute.call_args[0][1], mock_execute.call_args[0][2] + assert "reports(lifecycle: $lifecycle)" in query + assert "filingStatus" in query + assert not variables + + @pytest.mark.parametrize( + ("lifecycle", "sent"), + [("archived", "ARCHIVED"), ("ALL", "ALL"), ("current", "CURRENT")], + ) + @patch("robosystems_client.graphql.client.GraphQLClient.execute") + def test_list_reports_forwards_lifecycle( + self, mock_execute, lifecycle, sent, mock_config, graph_id + ): + mock_execute.return_value = {"reports": {"reports": []}} + client = LedgerClient(mock_config) + client.list_reports(graph_id, lifecycle=lifecycle) + assert mock_execute.call_args[0][2] == {"lifecycle": sent} + @patch("robosystems_client.graphql.client.GraphQLClient.execute") def test_get_trial_balance(self, mock_execute, mock_config, graph_id): mock_execute.return_value = { From 6743e928b93fcdeb155736a4947e59fafa9eafe5 Mon Sep 17 00:00:00 2001 From: "Joseph T. French" Date: Thu, 24 Sep 2026 19:48:21 -0500 Subject: [PATCH 2/2] fix(ledger): keep the generated list_ledger_reports lifecycle optional Declaring the operation variable `ReportLifecycle!` made ariadne-codegen emit a required `lifecycle` parameter on the generated `Client.list_ledger_reports`. A nullable variable is allowed there because the server argument carries its own default, and the generated method stays callable with no arguments. Claude-Session: https://claude.ai/code/session_01Jaz7hXqfysytKc57zpyWY6 --- robosystems_client/graphql/generated/client.py | 2 +- robosystems_client/graphql/generated/operations.py | 2 +- .../graphql/operations/ledger/ListLedgerReports.graphql | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/robosystems_client/graphql/generated/client.py b/robosystems_client/graphql/generated/client.py index 19a2d00..f74c489 100644 --- a/robosystems_client/graphql/generated/client.py +++ b/robosystems_client/graphql/generated/client.py @@ -769,7 +769,7 @@ def list_ledger_publish_lists( return ListLedgerPublishLists.model_validate(data) def list_ledger_reports( - self, lifecycle: ReportLifecycle, **kwargs: Any + self, lifecycle: Union[Optional[ReportLifecycle], UnsetType] = UNSET, **kwargs: Any ) -> ListLedgerReports: variables: dict[str, object] = {"lifecycle": lifecycle} response = self.execute( diff --git a/robosystems_client/graphql/generated/operations.py b/robosystems_client/graphql/generated/operations.py index 24f704e..5517d09 100644 --- a/robosystems_client/graphql/generated/operations.py +++ b/robosystems_client/graphql/generated/operations.py @@ -1592,7 +1592,7 @@ """ LIST_LEDGER_REPORTS_GQL = """ -query ListLedgerReports($lifecycle: ReportLifecycle! = CURRENT) { +query ListLedgerReports($lifecycle: ReportLifecycle) { reports(lifecycle: $lifecycle) { reports { id diff --git a/robosystems_client/graphql/operations/ledger/ListLedgerReports.graphql b/robosystems_client/graphql/operations/ledger/ListLedgerReports.graphql index 95cb304..bc0be1c 100644 --- a/robosystems_client/graphql/operations/ledger/ListLedgerReports.graphql +++ b/robosystems_client/graphql/operations/ledger/ListLedgerReports.graphql @@ -1,4 +1,4 @@ -query ListLedgerReports($lifecycle: ReportLifecycle! = CURRENT) { +query ListLedgerReports($lifecycle: ReportLifecycle) { reports(lifecycle: $lifecycle) { reports { id name taxonomyId generationStatus filingStatus periodType