From 3f0d9e8d2c09d9bdd1c49300b2b56ad66de628b2 Mon Sep 17 00:00:00 2001 From: "Joseph T. French" Date: Fri, 22 May 2026 15:21:37 -0500 Subject: [PATCH 1/3] Enhance documentation and attributes for attribution filter, classification lite, initial entity data, and line item metadata predicate models --- .../models/attribution_filter.py | 25 ++++++----- .../models/classification_lite.py | 4 +- .../models/initial_entity_data.py | 45 +++++++++++++++++++ .../models/line_item_metadata_predicate.py | 45 ++++++++++--------- 4 files changed, 86 insertions(+), 33 deletions(-) diff --git a/robosystems_client/models/attribution_filter.py b/robosystems_client/models/attribution_filter.py index 5fa1879..bbcf1a6 100644 --- a/robosystems_client/models/attribution_filter.py +++ b/robosystems_client/models/attribution_filter.py @@ -33,17 +33,20 @@ class AttributionFilter: Attributes: target_qname (str): QName of the flow concept this filter produces facts for — e.g. ``rs- gaap:ProceedsFromIssuanceOfCommonStock``. Resolved to ``target_element_id`` at create time. - predicate (LineItemMetadataPredicate): Filter ledger LineItems whose ``metadata_[field]`` is in ``values``. - - The single predicate kind shipped in Phase 2 MVP. Sufficient for any - source taxonomy that stamps a flow-tag column on each transaction - line — mini's ``TransactionDescriptionCode``, future XBRL GL - ``GenericFlowCategory`` columns, custom tenant tags. - - ``field`` is the JSONB key under ``line_items.metadata`` (e.g. - ``"transaction_description_code"``). ``values`` is the set of values - that route to the filter's target concept; matched LineItems aggregate - signed into the attributed fact for the period. + predicate (LineItemMetadataPredicate): Filter ledger LineItems by flow concept. + + The single predicate kind shipped to date. ``values`` are flow-concept + qnames — mini's ``TransactionDescriptionCode`` values, rs-gaap flow + concepts (what the enrichment classifier emits for QuickBooks data), + future XBRL GL ``GenericFlowCategory`` codes. The engine resolves them + to element_ids and matches the first-class ``LineItem.flow_element_id`` + FK; matched lines aggregate signed into the attributed fact for the + period. + + ``field`` is **legacy and ignored** — the flow tag used to live in + ``line_items.metadata[field]`` but has been promoted to the typed + ``flow_element_id`` FK. Retained for wire-compatibility; the engine no + longer reads it. target_element_id (None | str | Unset): Resolved element id for ``target_qname``. Null at create time; populated by the handler before persistence. Round-tripped in the envelope. """ diff --git a/robosystems_client/models/classification_lite.py b/robosystems_client/models/classification_lite.py index 2360f8f..ee0bd7e 100644 --- a/robosystems_client/models/classification_lite.py +++ b/robosystems_client/models/classification_lite.py @@ -34,8 +34,8 @@ class ClassificationLite: pair. Non-primary rows capture alternates / AI suggestions alongside the chosen primary. Default: True. confidence (float | None | Unset): AI/adapter-supplied confidence (0.0-1.0). Null for deterministic library- seeded rows. - source (None | str | Unset): Provenance — 'arcrole_analysis', 'disclosure_mechanics', 'us-gaap-metamodel', - adapter name, etc. + source (None | str | Unset): Provenance — 'arcrole_analysis', 'disclosure_mechanics', 'fac-traits', adapter + name, etc. """ id: str diff --git a/robosystems_client/models/initial_entity_data.py b/robosystems_client/models/initial_entity_data.py index 97fd2ba..66a9494 100644 --- a/robosystems_client/models/initial_entity_data.py +++ b/robosystems_client/models/initial_entity_data.py @@ -30,6 +30,13 @@ class InitialEntityData: state_of_incorporation (None | str | Unset): State of incorporation fiscal_year_end (None | str | Unset): Fiscal year end (MMDD) ein (None | str | Unset): Employer Identification Number + entity_type (None | str | Unset): Entity legal form (e.g. 'corporation', 'llc' / 'limited_liability_company', + 'partnership', 'sole_proprietorship', 'non_profit'). Drives the graph's default Reporting Style at creation — + partnership and llc get dedicated equity-form Styles; everything else defaults to corporate. Blank falls back to + corporate. + reporting_style_id (None | str | Unset): Optional explicit Reporting Style Structure id to pin on the graph, + overriding the entity_type-derived default. Leave blank to derive from entity_type. Change later via the change- + reporting-style operation. """ name: str @@ -42,6 +49,8 @@ class InitialEntityData: state_of_incorporation: None | str | Unset = UNSET fiscal_year_end: None | str | Unset = UNSET ein: None | str | Unset = UNSET + entity_type: None | str | Unset = UNSET + reporting_style_id: None | str | Unset = UNSET additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) def to_dict(self) -> dict[str, Any]: @@ -97,6 +106,18 @@ def to_dict(self) -> dict[str, Any]: else: ein = self.ein + entity_type: None | str | Unset + if isinstance(self.entity_type, Unset): + entity_type = UNSET + else: + entity_type = self.entity_type + + reporting_style_id: None | str | Unset + if isinstance(self.reporting_style_id, Unset): + reporting_style_id = UNSET + else: + reporting_style_id = self.reporting_style_id + field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update( @@ -121,6 +142,10 @@ def to_dict(self) -> dict[str, Any]: field_dict["fiscal_year_end"] = fiscal_year_end if ein is not UNSET: field_dict["ein"] = ein + if entity_type is not UNSET: + field_dict["entity_type"] = entity_type + if reporting_style_id is not UNSET: + field_dict["reporting_style_id"] = reporting_style_id return field_dict @@ -205,6 +230,24 @@ def _parse_ein(data: object) -> None | str | Unset: ein = _parse_ein(d.pop("ein", UNSET)) + def _parse_entity_type(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + entity_type = _parse_entity_type(d.pop("entity_type", UNSET)) + + def _parse_reporting_style_id(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + reporting_style_id = _parse_reporting_style_id(d.pop("reporting_style_id", UNSET)) + initial_entity_data = cls( name=name, uri=uri, @@ -216,6 +259,8 @@ def _parse_ein(data: object) -> None | str | Unset: state_of_incorporation=state_of_incorporation, fiscal_year_end=fiscal_year_end, ein=ein, + entity_type=entity_type, + reporting_style_id=reporting_style_id, ) initial_entity_data.additional_properties = d diff --git a/robosystems_client/models/line_item_metadata_predicate.py b/robosystems_client/models/line_item_metadata_predicate.py index ef7c1b8..6c02e50 100644 --- a/robosystems_client/models/line_item_metadata_predicate.py +++ b/robosystems_client/models/line_item_metadata_predicate.py @@ -13,57 +13,60 @@ @_attrs_define class LineItemMetadataPredicate: - """Filter ledger LineItems whose ``metadata_[field]`` is in ``values``. + """Filter ledger LineItems by flow concept. - The single predicate kind shipped in Phase 2 MVP. Sufficient for any - source taxonomy that stamps a flow-tag column on each transaction - line — mini's ``TransactionDescriptionCode``, future XBRL GL - ``GenericFlowCategory`` columns, custom tenant tags. + The single predicate kind shipped to date. ``values`` are flow-concept + qnames — mini's ``TransactionDescriptionCode`` values, rs-gaap flow + concepts (what the enrichment classifier emits for QuickBooks data), + future XBRL GL ``GenericFlowCategory`` codes. The engine resolves them + to element_ids and matches the first-class ``LineItem.flow_element_id`` + FK; matched lines aggregate signed into the attributed fact for the + period. - ``field`` is the JSONB key under ``line_items.metadata`` (e.g. - ``"transaction_description_code"``). ``values`` is the set of values - that route to the filter's target concept; matched LineItems aggregate - signed into the attributed fact for the period. + ``field`` is **legacy and ignored** — the flow tag used to live in + ``line_items.metadata[field]`` but has been promoted to the typed + ``flow_element_id`` FK. Retained for wire-compatibility; the engine no + longer reads it. Attributes: - field (str): JSONB key under ``line_items.metadata`` to match against — e.g. ``transaction_description_code``. - The renderer performs an exact-string comparison on the JSONB-extracted text value. - values (list[str]): Metadata values that route to this filter's target concept. A LineItem matches when - ``metadata[field] ∈ values`` AND the line falls within the rollforward's period. + values (list[str]): Flow-concept qnames that route to this filter's target concept. A LineItem matches when its + ``flow_element_id`` is one of the elements named here AND the line falls within the rollforward's period. kind (Literal['line_item_metadata_field'] | Unset): Discriminator value selecting this predicate shape. Default: 'line_item_metadata_field'. + field (str | Unset): Legacy/ignored. The flow tag now lives in the typed ``flow_element_id`` FK, not JSONB + metadata; the engine no longer reads this. Retained for wire-compatibility. Default: + 'transaction_description_code'. """ - field: str values: list[str] kind: Literal["line_item_metadata_field"] | Unset = "line_item_metadata_field" + field: str | Unset = "transaction_description_code" additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) def to_dict(self) -> dict[str, Any]: - field = self.field - values = self.values kind = self.kind + field = self.field + field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update( { - "field": field, "values": values, } ) if kind is not UNSET: field_dict["kind"] = kind + if field is not UNSET: + field_dict["field"] = field return field_dict @classmethod def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: d = dict(src_dict) - field = d.pop("field") - values = cast(list[str], d.pop("values")) kind = cast(Literal["line_item_metadata_field"] | Unset, d.pop("kind", UNSET)) @@ -72,10 +75,12 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: f"kind must match const 'line_item_metadata_field', got '{kind}'" ) + field = d.pop("field", UNSET) + line_item_metadata_predicate = cls( - field=field, values=values, kind=kind, + field=field, ) line_item_metadata_predicate.additional_properties = d From 4c409e21745ba00d04dc5c270dea3bd2da79c5c4 Mon Sep 17 00:00:00 2001 From: "Joseph T. French" Date: Fri, 22 May 2026 19:16:28 -0500 Subject: [PATCH 2/3] Add verification summary and category summary models to enhance reporting capabilities --- robosystems_client/models/__init__.py | 4 + .../models/information_block_envelope.py | 37 +++++ .../models/verification_category_summary.py | 115 +++++++++++++++ .../models/verification_summary.py | 131 ++++++++++++++++++ 4 files changed, 287 insertions(+) create mode 100644 robosystems_client/models/verification_category_summary.py create mode 100644 robosystems_client/models/verification_summary.py diff --git a/robosystems_client/models/__init__.py b/robosystems_client/models/__init__.py index 99e92f1..b45a50a 100644 --- a/robosystems_client/models/__init__.py +++ b/robosystems_client/models/__init__.py @@ -669,7 +669,9 @@ from .validation_error import ValidationError from .validation_error_context import ValidationErrorContext from .validation_lite import ValidationLite +from .verification_category_summary import VerificationCategorySummary from .verification_result_lite import VerificationResultLite +from .verification_summary import VerificationSummary from .view_axis_config import ViewAxisConfig from .view_axis_config_element_labels_type_0 import ViewAxisConfigElementLabelsType0 from .view_axis_config_member_labels_type_0 import ViewAxisConfigMemberLabelsType0 @@ -1166,7 +1168,9 @@ "ValidationError", "ValidationErrorContext", "ValidationLite", + "VerificationCategorySummary", "VerificationResultLite", + "VerificationSummary", "ViewAxisConfig", "ViewAxisConfigElementLabelsType0", "ViewAxisConfigMemberLabelsType0", diff --git a/robosystems_client/models/information_block_envelope.py b/robosystems_client/models/information_block_envelope.py index 9d3ce1a..b00e6d0 100644 --- a/robosystems_client/models/information_block_envelope.py +++ b/robosystems_client/models/information_block_envelope.py @@ -20,6 +20,7 @@ from ..models.information_model_response import InformationModelResponse from ..models.rule_lite import RuleLite from ..models.verification_result_lite import VerificationResultLite + from ..models.verification_summary import VerificationSummary from ..models.view_projections import ViewProjections @@ -62,6 +63,9 @@ class InformationBlockEnvelope: underlying block has no FactSet row yet — typically library-seeded statement Structures with no tenant-generated facts, or Schedule rows written before the create-side FactSet stamping was added. verification_results (list[VerificationResultLite] | Unset): + verification_summary (None | Unset | VerificationSummary): Server-computed aggregate over + ``verification_results`` — overall pass/fail/error/skip counts plus a per-rule_category breakdown for the + grouped Verification Results panel. Null when the block has no verification results. view (ViewProjections | Unset): Charlie's six ``type-of View`` arms, surfaced at the envelope boundary. Each projection is computed server-side at envelope-build time when @@ -95,11 +99,13 @@ class InformationBlockEnvelope: dimensions: list[InformationBlockEnvelopeDimensionsItem] | Unset = UNSET fact_set: FactSetLite | None | Unset = UNSET verification_results: list[VerificationResultLite] | Unset = UNSET + verification_summary: None | Unset | VerificationSummary = UNSET view: ViewProjections | Unset = UNSET additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) def to_dict(self) -> dict[str, Any]: from ..models.fact_set_lite import FactSetLite + from ..models.verification_summary import VerificationSummary id = self.id @@ -183,6 +189,14 @@ def to_dict(self) -> dict[str, Any]: verification_results_item = verification_results_item_data.to_dict() verification_results.append(verification_results_item) + verification_summary: dict[str, Any] | None | Unset + if isinstance(self.verification_summary, Unset): + verification_summary = UNSET + elif isinstance(self.verification_summary, VerificationSummary): + verification_summary = self.verification_summary.to_dict() + else: + verification_summary = self.verification_summary + view: dict[str, Any] | Unset = UNSET if not isinstance(self.view, Unset): view = self.view.to_dict() @@ -220,6 +234,8 @@ def to_dict(self) -> dict[str, Any]: field_dict["fact_set"] = fact_set if verification_results is not UNSET: field_dict["verification_results"] = verification_results + if verification_summary is not UNSET: + field_dict["verification_summary"] = verification_summary if view is not UNSET: field_dict["view"] = view @@ -238,6 +254,7 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: from ..models.information_model_response import InformationModelResponse from ..models.rule_lite import RuleLite from ..models.verification_result_lite import VerificationResultLite + from ..models.verification_summary import VerificationSummary from ..models.view_projections import ViewProjections d = dict(src_dict) @@ -357,6 +374,25 @@ def _parse_fact_set(data: object) -> FactSetLite | None | Unset: verification_results.append(verification_results_item) + def _parse_verification_summary(data: object) -> None | Unset | VerificationSummary: + if data is None: + return data + if isinstance(data, Unset): + return data + try: + if not isinstance(data, dict): + raise TypeError() + verification_summary_type_0 = VerificationSummary.from_dict(data) + + return verification_summary_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + return cast(None | Unset | VerificationSummary, data) + + verification_summary = _parse_verification_summary( + d.pop("verification_summary", UNSET) + ) + _view = d.pop("view", UNSET) view: ViewProjections | Unset if isinstance(_view, Unset): @@ -382,6 +418,7 @@ def _parse_fact_set(data: object) -> FactSetLite | None | Unset: dimensions=dimensions, fact_set=fact_set, verification_results=verification_results, + verification_summary=verification_summary, view=view, ) diff --git a/robosystems_client/models/verification_category_summary.py b/robosystems_client/models/verification_category_summary.py new file mode 100644 index 0000000..9ff6a9f --- /dev/null +++ b/robosystems_client/models/verification_category_summary.py @@ -0,0 +1,115 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="VerificationCategorySummary") + + +@_attrs_define +class VerificationCategorySummary: + """Pass/fail/skip counts for one ``rule_category`` within a block's + verification results. + + Drives the per-category accordions in the Verification Results panel + (financial-viewer §7.12). ``category`` is the rule's ``rule_category`` + (one of the cm:VerificationRule subclasses), resolved by joining each + result to its Rule. + + Attributes: + category (str): + total (int | Unset): Default: 0. + passed (int | Unset): Default: 0. + failed (int | Unset): Default: 0. + errored (int | Unset): Default: 0. + skipped (int | Unset): Default: 0. + """ + + category: str + total: int | Unset = 0 + passed: int | Unset = 0 + failed: int | Unset = 0 + errored: int | Unset = 0 + skipped: int | Unset = 0 + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + category = self.category + + total = self.total + + passed = self.passed + + failed = self.failed + + errored = self.errored + + skipped = self.skipped + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "category": category, + } + ) + if total is not UNSET: + field_dict["total"] = total + if passed is not UNSET: + field_dict["passed"] = passed + if failed is not UNSET: + field_dict["failed"] = failed + if errored is not UNSET: + field_dict["errored"] = errored + if skipped is not UNSET: + field_dict["skipped"] = skipped + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + category = d.pop("category") + + total = d.pop("total", UNSET) + + passed = d.pop("passed", UNSET) + + failed = d.pop("failed", UNSET) + + errored = d.pop("errored", UNSET) + + skipped = d.pop("skipped", UNSET) + + verification_category_summary = cls( + category=category, + total=total, + passed=passed, + failed=failed, + errored=errored, + skipped=skipped, + ) + + verification_category_summary.additional_properties = d + return verification_category_summary + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/robosystems_client/models/verification_summary.py b/robosystems_client/models/verification_summary.py new file mode 100644 index 0000000..b744db0 --- /dev/null +++ b/robosystems_client/models/verification_summary.py @@ -0,0 +1,131 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import TYPE_CHECKING, Any, TypeVar + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +if TYPE_CHECKING: + from ..models.verification_category_summary import VerificationCategorySummary + + +T = TypeVar("T", bound="VerificationSummary") + + +@_attrs_define +class VerificationSummary: + """Server-computed aggregate of a block's ``verification_results``. + + Overall counts plus a per-``rule_category`` breakdown, so the viewer + renders the grouped Verification Results panel (financial-viewer §7.12) + without a client-side results→rules join. Status closure is + ``pass | fail | error | skipped`` (the ``public.verification_results`` + CHECK); ``total`` is their sum. + + Attributes: + total (int | Unset): Default: 0. + passed (int | Unset): Default: 0. + failed (int | Unset): Default: 0. + errored (int | Unset): Default: 0. + skipped (int | Unset): Default: 0. + by_category (list[VerificationCategorySummary] | Unset): + """ + + total: int | Unset = 0 + passed: int | Unset = 0 + failed: int | Unset = 0 + errored: int | Unset = 0 + skipped: int | Unset = 0 + by_category: list[VerificationCategorySummary] | Unset = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + total = self.total + + passed = self.passed + + failed = self.failed + + errored = self.errored + + skipped = self.skipped + + by_category: list[dict[str, Any]] | Unset = UNSET + if not isinstance(self.by_category, Unset): + by_category = [] + for by_category_item_data in self.by_category: + by_category_item = by_category_item_data.to_dict() + by_category.append(by_category_item) + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if total is not UNSET: + field_dict["total"] = total + if passed is not UNSET: + field_dict["passed"] = passed + if failed is not UNSET: + field_dict["failed"] = failed + if errored is not UNSET: + field_dict["errored"] = errored + if skipped is not UNSET: + field_dict["skipped"] = skipped + if by_category is not UNSET: + field_dict["by_category"] = by_category + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + from ..models.verification_category_summary import VerificationCategorySummary + + d = dict(src_dict) + total = d.pop("total", UNSET) + + passed = d.pop("passed", UNSET) + + failed = d.pop("failed", UNSET) + + errored = d.pop("errored", UNSET) + + skipped = d.pop("skipped", UNSET) + + _by_category = d.pop("by_category", UNSET) + by_category: list[VerificationCategorySummary] | Unset = UNSET + if _by_category is not UNSET: + by_category = [] + for by_category_item_data in _by_category: + by_category_item = VerificationCategorySummary.from_dict(by_category_item_data) + + by_category.append(by_category_item) + + verification_summary = cls( + total=total, + passed=passed, + failed=failed, + errored=errored, + skipped=skipped, + by_category=by_category, + ) + + verification_summary.additional_properties = d + return verification_summary + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties From 6a49de06ddd62d6c4fb46077f3c6e638e2277d01 Mon Sep 17 00:00:00 2001 From: "Joseph T. French" Date: Fri, 22 May 2026 19:21:57 -0500 Subject: [PATCH 3/3] Add verification summary to information block queries for enhanced reporting --- robosystems_client/graphql/queries/ledger/__init__.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/robosystems_client/graphql/queries/ledger/__init__.py b/robosystems_client/graphql/queries/ledger/__init__.py index cf91344..1bb449c 100644 --- a/robosystems_client/graphql/queries/ledger/__init__.py +++ b/robosystems_client/graphql/queries/ledger/__init__.py @@ -615,6 +615,10 @@ def parse_mapping_coverage(data: dict[str, Any]) -> dict[str, Any] | None: id elementId value periodStart periodEnd periodType unit factScope factSetId } + verificationSummary { + total passed failed errored skipped + byCategory { category total passed failed errored skipped } + } } } """.strip() @@ -654,6 +658,10 @@ def parse_information_block(data: dict[str, Any]) -> dict[str, Any] | None: id elementId value periodStart periodEnd periodType unit factScope factSetId } + verificationSummary { + total passed failed errored skipped + byCategory { category total passed failed errored skipped } + } } } """.strip()