From e6a2177d483748568f06612dd6fa1e0d7082b9f6 Mon Sep 17 00:00:00 2001 From: "Joseph T. French" Date: Sat, 6 Jun 2026 12:30:02 -0500 Subject: [PATCH 1/5] feat(taxonomy): CF-reconciling/remeasurement coverage + recurrence axis - bind indirectCashFlowReconcilingItem to the 13 kept IncreaseDecreaseIn* working-capital concepts (consistency with their excluded siblings; classification-only, not consumed by the renderer) - bind use/remeasurement to the 3 kept impairment-bearing concepts - add a new fac-traits 'recurrence' axis (member nonrecurring) for normalized-earnings / NOPAT classification, bound to the 9 kept operating special-items (restructuring, impairment, disposal G/L, debt extinguishment); orthogonal to use/remeasurement - widen check_trait_category: model + migration 0019 (public + tenant schemas via for_each_tenant_schema) and 0002 so a fresh seed admits recurrence --- .../packages/fac-traits/v1/taxonomy.jsonld | 27 +++- .../rs-gaap-traits/v1/taxonomy.jsonld | 75 +++++++++++ .../versions/0002_taxonomy_library.py | 16 ++- .../0019_recurrence_trait_category.py | 122 ++++++++++++++++++ robosystems/models/extensions/trait.py | 4 +- 5 files changed, 237 insertions(+), 7 deletions(-) create mode 100644 migrations/extensions/versions/0019_recurrence_trait_category.py diff --git a/frameworks/fac/packages/fac-traits/v1/taxonomy.jsonld b/frameworks/fac/packages/fac-traits/v1/taxonomy.jsonld index 3d9aa8eae..9258996ca 100644 --- a/frameworks/fac/packages/fac-traits/v1/taxonomy.jsonld +++ b/frameworks/fac/packages/fac-traits/v1/taxonomy.jsonld @@ -345,7 +345,7 @@ "format": "XBRL metamodel (traits + domains + members)" }, "forked_at": "2026-04-20", - "description": "Universal accounting-element trait vocabulary — 24 orthogonal classification axes (elementsOfFinancialStatements, liquidity, activityType, operatingNonoperating, recordedValue, realizationStatus, hedging, leaseType, …) covering 95 trait members, plus the flowClassification axis (inflow/outflow/accrual/contra) derived from FASB's instant-* arcroles. Seeds the `traits` table — each member is a (category, identifier) pair. Forked from the FASB us-gaap 2026 metamodel; the axes themselves describe how *any* accounting element gets classified, not just us-gaap, so the vocabulary lives in the fac framework as the dependency root and is inherited by every rs-* curation that depends on fac. Per-element trait bindings (seeding the `element_traits` junction) live in each framework's `*-traits/v1/` package — today: `rs-gaap-traits/v1/`.", + "description": "Universal accounting-element trait vocabulary — 24 orthogonal classification axes (elementsOfFinancialStatements, liquidity, activityType, operatingNonoperating, recordedValue, realizationStatus, hedging, leaseType, …) covering 95 trait members, plus the flowClassification axis (inflow/outflow/accrual/contra) derived from FASB's instant-* arcroles, plus the RS analytical-extension axis `recurrence` (nonrecurring) for earnings-persistence / NOPAT normalization — 26 categories, 100 members in total. Seeds the `traits` table — each member is a (category, identifier) pair. Forked from the FASB us-gaap 2026 metamodel; the axes themselves describe how *any* accounting element gets classified, not just us-gaap, so the vocabulary lives in the fac framework as the dependency root and is inherited by every rs-* curation that depends on fac. Per-element trait bindings (seeding the `element_traits` junction) live in each framework's `*-traits/v1/` package — today: `rs-gaap-traits/v1/`.", "@graph": [ { "@id": "trait:statisticalMeasurement/arithmeticAverage", @@ -1747,6 +1747,31 @@ } ] }, + { + "@id": "trait:recurrence/nonrecurring", + "@type": "rs:Trait", + "https://robosystems.ai/vocab/category": [ + { + "@value": "recurrence" + } + ], + "https://robosystems.ai/vocab/identifier": [ + { + "@value": "nonrecurring" + } + ], + "https://robosystems.ai/vocab/source": [ + { + "@value": "fac-traits" + } + ], + "rdfs:label": [ + { + "@value": "nonrecurring", + "@language": "en" + } + ] + }, { "@id": "trait:recordedValue/reportedAtFairValue", "@type": "rs:Trait", diff --git a/frameworks/rs-gaap/packages/rs-gaap-traits/v1/taxonomy.jsonld b/frameworks/rs-gaap/packages/rs-gaap-traits/v1/taxonomy.jsonld index 5aec74c21..874bf3b10 100644 --- a/frameworks/rs-gaap/packages/rs-gaap-traits/v1/taxonomy.jsonld +++ b/frameworks/rs-gaap/packages/rs-gaap-traits/v1/taxonomy.jsonld @@ -1723,6 +1723,12 @@ { "@id": "rs-gaap:AssetImpairmentCharges", "https://robosystems.ai/vocab/hasTrait": [ + { + "@id": "trait:recurrence/nonrecurring" + }, + { + "@id": "trait:use/remeasurement" + }, { "@id": "trait:elementsOfFinancialStatements/loss" }, @@ -5732,6 +5738,9 @@ { "@id": "rs-gaap:GainLossOnDispositionOfAssets", "https://robosystems.ai/vocab/hasTrait": [ + { + "@id": "trait:recurrence/nonrecurring" + }, { "@id": "trait:elementsOfFinancialStatements/gain" }, @@ -5746,6 +5755,9 @@ { "@id": "rs-gaap:GainLossOnDispositionOfAssets1", "https://robosystems.ai/vocab/hasTrait": [ + { + "@id": "trait:recurrence/nonrecurring" + }, { "@id": "trait:elementsOfFinancialStatements/gain" }, @@ -6029,6 +6041,9 @@ { "@id": "rs-gaap:GainLossOnSaleOfPropertyPlantEquipment", "https://robosystems.ai/vocab/hasTrait": [ + { + "@id": "trait:recurrence/nonrecurring" + }, { "@id": "trait:elementsOfFinancialStatements/gain" }, @@ -6200,6 +6215,9 @@ { "@id": "rs-gaap:GainsLossesOnExtinguishmentOfDebt", "https://robosystems.ai/vocab/hasTrait": [ + { + "@id": "trait:recurrence/nonrecurring" + }, { "@id": "trait:elementsOfFinancialStatements/gain" }, @@ -6895,6 +6913,9 @@ { "@id": "rs-gaap:IncreaseDecreaseInAccountsAndNotesReceivable", "https://robosystems.ai/vocab/hasTrait": [ + { + "@id": "trait:indirectCashFlowReconcilingItem/indirectCashFlowMethodReconcilingItem" + }, { "@id": "trait:operatingNonoperating/operating" } @@ -6911,6 +6932,9 @@ { "@id": "rs-gaap:IncreaseDecreaseInAccountsPayableAndAccruedLiabilities", "https://robosystems.ai/vocab/hasTrait": [ + { + "@id": "trait:indirectCashFlowReconcilingItem/indirectCashFlowMethodReconcilingItem" + }, { "@id": "trait:operatingNonoperating/operating" } @@ -6943,6 +6967,9 @@ { "@id": "rs-gaap:IncreaseDecreaseInAccountsReceivable", "https://robosystems.ai/vocab/hasTrait": [ + { + "@id": "trait:indirectCashFlowReconcilingItem/indirectCashFlowMethodReconcilingItem" + }, { "@id": "trait:operatingNonoperating/operating" } @@ -6975,6 +7002,9 @@ { "@id": "rs-gaap:IncreaseDecreaseInAccruedIncomeTaxesPayable", "https://robosystems.ai/vocab/hasTrait": [ + { + "@id": "trait:indirectCashFlowReconcilingItem/indirectCashFlowMethodReconcilingItem" + }, { "@id": "trait:operatingNonoperating/operating" } @@ -6999,6 +7029,9 @@ { "@id": "rs-gaap:IncreaseDecreaseInAccruedLiabilities", "https://robosystems.ai/vocab/hasTrait": [ + { + "@id": "trait:indirectCashFlowReconcilingItem/indirectCashFlowMethodReconcilingItem" + }, { "@id": "trait:operatingNonoperating/operating" } @@ -7015,6 +7048,9 @@ { "@id": "rs-gaap:IncreaseDecreaseInAccruedTaxesPayable", "https://robosystems.ai/vocab/hasTrait": [ + { + "@id": "trait:indirectCashFlowReconcilingItem/indirectCashFlowMethodReconcilingItem" + }, { "@id": "trait:operatingNonoperating/operating" } @@ -7593,6 +7629,9 @@ { "@id": "rs-gaap:IncreaseDecreaseInInventories", "https://robosystems.ai/vocab/hasTrait": [ + { + "@id": "trait:indirectCashFlowReconcilingItem/indirectCashFlowMethodReconcilingItem" + }, { "@id": "trait:operatingNonoperating/operating" } @@ -7745,6 +7784,9 @@ { "@id": "rs-gaap:IncreaseDecreaseInOperatingAssets", "https://robosystems.ai/vocab/hasTrait": [ + { + "@id": "trait:indirectCashFlowReconcilingItem/indirectCashFlowMethodReconcilingItem" + }, { "@id": "trait:operatingNonoperating/operating" } @@ -7753,6 +7795,9 @@ { "@id": "rs-gaap:IncreaseDecreaseInOperatingCapital", "https://robosystems.ai/vocab/hasTrait": [ + { + "@id": "trait:indirectCashFlowReconcilingItem/indirectCashFlowMethodReconcilingItem" + }, { "@id": "trait:operatingNonoperating/operating" } @@ -7761,6 +7806,9 @@ { "@id": "rs-gaap:IncreaseDecreaseInOperatingLiabilities", "https://robosystems.ai/vocab/hasTrait": [ + { + "@id": "trait:indirectCashFlowReconcilingItem/indirectCashFlowMethodReconcilingItem" + }, { "@id": "trait:operatingNonoperating/operating" } @@ -7911,6 +7959,9 @@ { "@id": "rs-gaap:IncreaseDecreaseInOtherOperatingCapitalNet", "https://robosystems.ai/vocab/hasTrait": [ + { + "@id": "trait:indirectCashFlowReconcilingItem/indirectCashFlowMethodReconcilingItem" + }, { "@id": "trait:operatingNonoperating/operating" } @@ -8055,6 +8106,9 @@ { "@id": "rs-gaap:IncreaseDecreaseInPrepaidExpense", "https://robosystems.ai/vocab/hasTrait": [ + { + "@id": "trait:indirectCashFlowReconcilingItem/indirectCashFlowMethodReconcilingItem" + }, { "@id": "trait:operatingNonoperating/operating" } @@ -8151,6 +8205,9 @@ { "@id": "rs-gaap:IncreaseDecreaseInReceivables", "https://robosystems.ai/vocab/hasTrait": [ + { + "@id": "trait:indirectCashFlowReconcilingItem/indirectCashFlowMethodReconcilingItem" + }, { "@id": "trait:operatingNonoperating/operating" } @@ -18466,6 +18523,9 @@ { "@id": "rs-gaap:RestructuringCharges", "https://robosystems.ai/vocab/hasTrait": [ + { + "@id": "trait:recurrence/nonrecurring" + }, { "@id": "trait:elementsOfFinancialStatements/expense" }, @@ -18480,6 +18540,9 @@ { "@id": "rs-gaap:RestructuringCosts", "https://robosystems.ai/vocab/hasTrait": [ + { + "@id": "trait:recurrence/nonrecurring" + }, { "@id": "trait:operatingNonoperating/operating" }, @@ -18491,6 +18554,12 @@ { "@id": "rs-gaap:RestructuringCostsAndAssetImpairmentCharges", "https://robosystems.ai/vocab/hasTrait": [ + { + "@id": "trait:recurrence/nonrecurring" + }, + { + "@id": "trait:use/remeasurement" + }, { "@id": "trait:operatingNonoperating/operating" } @@ -18521,6 +18590,12 @@ { "@id": "rs-gaap:RestructuringSettlementAndImpairmentProvisions", "https://robosystems.ai/vocab/hasTrait": [ + { + "@id": "trait:recurrence/nonrecurring" + }, + { + "@id": "trait:use/remeasurement" + }, { "@id": "trait:elementsOfFinancialStatements/loss" }, diff --git a/migrations/extensions/versions/0002_taxonomy_library.py b/migrations/extensions/versions/0002_taxonomy_library.py index 239465573..bb8e37caf 100644 --- a/migrations/extensions/versions/0002_taxonomy_library.py +++ b/migrations/extensions/versions/0002_taxonomy_library.py @@ -17,8 +17,8 @@ - CREATE TABLE public.element_labels (XBRL label linkbase) - CREATE TABLE public.element_references (XBRL reference linkbase) -- CREATE TABLE public.traits (FASB metamodel vocabulary — 25 element-side - categories: 24 trait axes + flowClassification) +- CREATE TABLE public.traits (FASB metamodel vocabulary — 26 element-side + categories: 24 trait axes + flowClassification + the RS `recurrence` axis) - CREATE TABLE public.element_traits (element ↔ trait many-to-many junction) - CREATE TABLE public.classifications (association structural patterns — 3 categories: concept_arrangement, member_arrangement, named_disclosure) @@ -275,8 +275,13 @@ ")" ) -# 25 FASB metamodel trait axes (24 axes + flowClassification). -# Matches check_trait_category on public.traits.category. +# 25 FASB metamodel trait axes (24 axes + flowClassification) + the RS +# `recurrence` analytical extension. Matches check_trait_category on +# public.traits.category (robosystems/models/extensions/trait.py). The +# content-driven library seed in this same migration inserts the +# `recurrence/nonrecurring` member from fac-traits.jsonld, so the CHECK +# created here must already admit it on a fresh DB; migration 0019 applies +# the same widen to DBs that ran an earlier version of this constraint. _TRAIT_CATEGORY_CHECK = ( "category IN (" "'elementsOfFinancialStatements', 'liquidity', 'activityType', " @@ -287,7 +292,8 @@ "'accrualOrPayable', 'priority', 'estimatedFutureActivity', " "'statisticalMeasurement', 'taxComponents', 'threshold', 'use', " "'indirectCashFlowReconcilingItem', " - "'flowClassification'" + "'flowClassification', " + "'recurrence'" ")" ) diff --git a/migrations/extensions/versions/0019_recurrence_trait_category.py b/migrations/extensions/versions/0019_recurrence_trait_category.py new file mode 100644 index 000000000..7ed35d7ec --- /dev/null +++ b/migrations/extensions/versions/0019_recurrence_trait_category.py @@ -0,0 +1,122 @@ +"""add recurrence trait category + +Widen the ``check_trait_category`` CHECK constraint on ``traits.category`` to +admit the new ``recurrence`` axis (member ``nonrecurring``) — an RS analytical +extension to the FASB-metamodel trait vocabulary for earnings-persistence +classification (normalized earnings / NOPAT excluding special items). The +``fac-traits`` package seeds the trait member; ``rs-gaap-traits`` binds it to +the kept operating special-items (restructuring, impairment, disposal +gains/losses, debt extinguishment). + +The constraint is created unprefixed (``check_trait_category``) in every schema +by ``0002_taxonomy_library.py``, so this widen runs against ``public`` and every +existing tenant schema — mirroring the CHECK-constraint widen pattern in +``0007_frameworks_bridges.py``. New tenants pick the widened constraint up +automatically via ``ExtensionsBase.metadata.create_all`` at provision. + +Revision ID: 0019 +Revises: 0018 +Create Date: 2026-06-06 12:04:37.532894 + +""" + +from __future__ import annotations + +from alembic import op +from sqlalchemy import text + +# revision identifiers, used by Alembic. +revision = "0019" +down_revision = "0018" +branch_labels = None +depends_on = None + + +# The 24 FASB us-gaap metamodel axes + flowClassification + indirectCashFlowReconcilingItem. +# Mirrors check_trait_category in robosystems/models/extensions/trait.py. +_PRIOR_TRAIT_CATEGORY_CHECK = ( + "category IN (" + "'elementsOfFinancialStatements', 'liquidity', 'activityType', " + "'operatingNonoperating', 'operatingIntent', 'realizationStatus', " + "'recordedValue', 'restriction', 'hedging', 'leaseType', " + "'interestRateType', 'convertibility', 'creditStructure', " + "'debtGuarantee', 'derivativeContract', 'derivativeInstrument', " + "'accrualOrPayable', 'priority', 'estimatedFutureActivity', " + "'statisticalMeasurement', 'taxComponents', 'threshold', 'use', " + "'indirectCashFlowReconcilingItem', " + "'flowClassification'" + ")" +) + +# Same list + the new 'recurrence' analytical axis. +_WIDENED_TRAIT_CATEGORY_CHECK = ( + "category IN (" + "'elementsOfFinancialStatements', 'liquidity', 'activityType', " + "'operatingNonoperating', 'operatingIntent', 'realizationStatus', " + "'recordedValue', 'restriction', 'hedging', 'leaseType', " + "'interestRateType', 'convertibility', 'creditStructure', " + "'debtGuarantee', 'derivativeContract', 'derivativeInstrument', " + "'accrualOrPayable', 'priority', 'estimatedFutureActivity', " + "'statisticalMeasurement', 'taxComponents', 'threshold', 'use', " + "'indirectCashFlowReconcilingItem', " + "'flowClassification', " + "'recurrence'" + ")" +) + + +def _trait_table(schema: str) -> str: + return "public.traits" if schema == "public" else f'"{schema}".traits' + + +def _widen_trait_category_check(conn, schema: str) -> None: + """Drop and re-add the traits.category CHECK with the widened list.""" + table = _trait_table(schema) + conn.execute( + text(f"ALTER TABLE {table} DROP CONSTRAINT IF EXISTS check_trait_category") + ) + conn.execute( + text( + f"ALTER TABLE {table} ADD CONSTRAINT check_trait_category " + f"CHECK ({_WIDENED_TRAIT_CATEGORY_CHECK})" + ) + ) + + +def _restore_trait_category_check(conn, schema: str) -> None: + """Inverse of :func:`_widen_trait_category_check` for downgrade.""" + table = _trait_table(schema) + conn.execute( + text(f"ALTER TABLE {table} DROP CONSTRAINT IF EXISTS check_trait_category") + ) + conn.execute( + text( + f"ALTER TABLE {table} ADD CONSTRAINT check_trait_category " + f"CHECK ({_PRIOR_TRAIT_CATEGORY_CHECK})" + ) + ) + + +def upgrade() -> None: + conn = op.get_bind() + from migrations.extensions.helpers import for_each_tenant_schema + + _widen_trait_category_check(conn, "public") + for_each_tenant_schema(conn, _widen_trait_category_check) + + +def downgrade() -> None: + conn = op.get_bind() + from migrations.extensions.helpers import for_each_tenant_schema + + # Downgrade restores the pre-recurrence list. Any 'recurrence' rows must be + # removed first or the restored CHECK will reject them. + conn.execute(text("DELETE FROM public.traits WHERE category = 'recurrence'")) + + def _drop_recurrence_rows(c, schema: str) -> None: + c.execute(text(f"DELETE FROM \"{schema}\".traits WHERE category = 'recurrence'")) + + for_each_tenant_schema(conn, _drop_recurrence_rows) + + _restore_trait_category_check(conn, "public") + for_each_tenant_schema(conn, _restore_trait_category_check) diff --git a/robosystems/models/extensions/trait.py b/robosystems/models/extensions/trait.py index 18cbb6db6..5d260fc3a 100644 --- a/robosystems/models/extensions/trait.py +++ b/robosystems/models/extensions/trait.py @@ -57,7 +57,9 @@ class Trait(ExtensionsBase): "'statisticalMeasurement', 'taxComponents', 'threshold', 'use', " "'indirectCashFlowReconcilingItem', " # Flow classification (FASB instant-* arcroles) - "'flowClassification'" + "'flowClassification', " + # RS analytical extension — earnings persistence (normalized earnings / NOPAT) + "'recurrence'" ")", name="check_trait_category", ), From 79c2c9f46297627fcf08d39157a632e8ad5036fa Mon Sep 17 00:00:00 2001 From: "Joseph T. French" Date: Sat, 6 Jun 2026 12:30:24 -0500 Subject: [PATCH 2/5] feat(taxonomy): anchor the kept rs-gaap working set to fac Add 96 fac->rs-gaap equivalence arcs so every concrete tenant-kept rs-gaap concept reaches a fac anchor (41/137 -> 137/137), making fac a complete cross-framework substrate over the MVP working set. Library-only (the bridge is tenant_copy:false); kept set + tenant-exclude artifact unchanged (verified). --- .../bridges/fac-to-rs-gaap/v1/taxonomy.jsonld | 288 ++++++++++++++++++ 1 file changed, 288 insertions(+) diff --git a/frameworks/rs-gaap/bridges/fac-to-rs-gaap/v1/taxonomy.jsonld b/frameworks/rs-gaap/bridges/fac-to-rs-gaap/v1/taxonomy.jsonld index faa3681a9..f527fdb27 100644 --- a/frameworks/rs-gaap/bridges/fac-to-rs-gaap/v1/taxonomy.jsonld +++ b/frameworks/rs-gaap/bridges/fac-to-rs-gaap/v1/taxonomy.jsonld @@ -474,6 +474,9 @@ }, { "@id": "rs-gaap:TechnologyServicesCosts" + }, + { + "@id": "rs-gaap:OtherCostOfOperatingRevenue" } ] }, @@ -515,6 +518,30 @@ "http://www.w3.org/2002/07/owl#equivalentClass": [ { "@id": "rs-gaap:AssetsCurrent" + }, + { + "@id": "rs-gaap:CashAndCashEquivalentsAtCarryingValue" + }, + { + "@id": "rs-gaap:InventoryNetOfAllowancesCustomerAdvancesAndProgressBillings" + }, + { + "@id": "rs-gaap:OtherAssetsCurrent" + }, + { + "@id": "rs-gaap:PrepaidExpenseCurrent" + }, + { + "@id": "rs-gaap:ReceivablesNetCurrent" + }, + { + "@id": "rs-gaap:RestrictedCashAndInvestmentsCurrent" + }, + { + "@id": "rs-gaap:ShortTermInvestments" + }, + { + "@id": "rs-gaap:CashCashEquivalentsAndShortTermInvestments" } ] }, @@ -523,6 +550,30 @@ "http://www.w3.org/2002/07/owl#equivalentClass": [ { "@id": "rs-gaap:LiabilitiesCurrent" + }, + { + "@id": "rs-gaap:AccountsPayableCurrent" + }, + { + "@id": "rs-gaap:AccruedLiabilitiesCurrent" + }, + { + "@id": "rs-gaap:DebtCurrent" + }, + { + "@id": "rs-gaap:DeferredRevenueCurrent" + }, + { + "@id": "rs-gaap:FinanceLeaseLiabilityCurrent" + }, + { + "@id": "rs-gaap:OtherLiabilitiesCurrent" + }, + { + "@id": "rs-gaap:AccountsPayableAndAccruedLiabilitiesCurrent" + }, + { + "@id": "rs-gaap:LongTermDebtAndCapitalLeaseObligationsCurrent" } ] }, @@ -589,6 +640,24 @@ }, { "@id": "rs-gaap:StockholdersEquity" + }, + { + "@id": "rs-gaap:AccumulatedOtherComprehensiveIncomeLossNetOfTax" + }, + { + "@id": "rs-gaap:AdditionalPaidInCapital" + }, + { + "@id": "rs-gaap:CommonStockValue" + }, + { + "@id": "rs-gaap:PreferredStockValue" + }, + { + "@id": "rs-gaap:RetainedEarningsAccumulatedDeficit" + }, + { + "@id": "rs-gaap:TreasuryStockValue" } ] }, @@ -961,6 +1030,45 @@ "http://www.w3.org/2002/07/owl#equivalentClass": [ { "@id": "rs-gaap:NetCashProvidedByUsedInFinancingActivities" + }, + { + "@id": "rs-gaap:PaymentsForRepurchaseOfCommonStock" + }, + { + "@id": "rs-gaap:ProceedsFromIssuanceOfCommonStock" + }, + { + "@id": "rs-gaap:ProceedsFromIssuanceOfLongTermDebt" + }, + { + "@id": "rs-gaap:RepaymentsOfLongTermDebt" + }, + { + "@id": "rs-gaap:PaymentsForRepurchaseOfEquity" + }, + { + "@id": "rs-gaap:ProceedsFromIssuanceOfLongTermDebtAndCapitalSecuritiesNet" + }, + { + "@id": "rs-gaap:ProceedsFromIssuanceOrSaleOfEquity" + }, + { + "@id": "rs-gaap:ProceedsFromRepaymentsOfDebt" + }, + { + "@id": "rs-gaap:ProceedsFromRepaymentsOfLongTermDebtAndCapitalSecurities" + }, + { + "@id": "rs-gaap:ProceedsFromRepurchaseOfEquity" + }, + { + "@id": "rs-gaap:RepaymentsOfLongTermDebtAndCapitalSecurities" + }, + { + "@id": "rs-gaap:PaymentsOfDividends" + }, + { + "@id": "rs-gaap:ProceedsFromPartnershipContribution" } ] }, @@ -985,6 +1093,36 @@ "http://www.w3.org/2002/07/owl#equivalentClass": [ { "@id": "rs-gaap:NetCashProvidedByUsedInInvestingActivities" + }, + { + "@id": "rs-gaap:PaymentsToAcquireIntangibleAssets" + }, + { + "@id": "rs-gaap:PaymentsToAcquireInvestments" + }, + { + "@id": "rs-gaap:PaymentsToAcquirePropertyPlantAndEquipment" + }, + { + "@id": "rs-gaap:ProceedsFromSaleMaturityAndCollectionsOfInvestments" + }, + { + "@id": "rs-gaap:ProceedsFromSaleOfIntangibleAssets" + }, + { + "@id": "rs-gaap:ProceedsFromSaleOfPropertyPlantAndEquipment" + }, + { + "@id": "rs-gaap:PaymentsForProceedsFromInvestments" + }, + { + "@id": "rs-gaap:PaymentsForProceedsFromProductiveAssets" + }, + { + "@id": "rs-gaap:PaymentsToAcquireProductiveAssets" + }, + { + "@id": "rs-gaap:ProceedsFromSaleOfProductiveAssets" } ] }, @@ -1009,6 +1147,60 @@ "http://www.w3.org/2002/07/owl#equivalentClass": [ { "@id": "rs-gaap:NetCashProvidedByUsedInOperatingActivities" + }, + { + "@id": "rs-gaap:DepreciationDepletionAndAmortization" + }, + { + "@id": "rs-gaap:IncreaseDecreaseInAccountsPayableAndAccruedLiabilities" + }, + { + "@id": "rs-gaap:IncreaseDecreaseInAccountsReceivable" + }, + { + "@id": "rs-gaap:IncreaseDecreaseInAccruedIncomeTaxesPayable" + }, + { + "@id": "rs-gaap:IncreaseDecreaseInAccruedLiabilities" + }, + { + "@id": "rs-gaap:IncreaseDecreaseInInventories" + }, + { + "@id": "rs-gaap:IncreaseDecreaseInOtherOperatingCapitalNet" + }, + { + "@id": "rs-gaap:IncreaseDecreaseInPrepaidExpense" + }, + { + "@id": "rs-gaap:IncreaseDecreaseInAccountsAndNotesReceivable" + }, + { + "@id": "rs-gaap:IncreaseDecreaseInAccruedTaxesPayable" + }, + { + "@id": "rs-gaap:IncreaseDecreaseInOperatingAssets" + }, + { + "@id": "rs-gaap:IncreaseDecreaseInOperatingCapital" + }, + { + "@id": "rs-gaap:IncreaseDecreaseInOperatingLiabilities" + }, + { + "@id": "rs-gaap:IncreaseDecreaseInReceivables" + }, + { + "@id": "rs-gaap:AdjustmentsToReconcileNetIncomeLossToCashProvidedByUsedInOperatingActivities" + }, + { + "@id": "rs-gaap:AdjustmentsNoncashItemsToReconcileNetIncomeLossToCashProvidedByUsedInOperatingActivities" + }, + { + "@id": "rs-gaap:ShareBasedCompensation" + }, + { + "@id": "rs-gaap:EmployeeBenefitsAndShareBasedCompensationNoncash" } ] }, @@ -1116,6 +1308,33 @@ "http://www.w3.org/2002/07/owl#equivalentClass": [ { "@id": "rs-gaap:AssetsNoncurrent" + }, + { + "@id": "rs-gaap:FinanceLeaseRightOfUseAsset" + }, + { + "@id": "rs-gaap:Goodwill" + }, + { + "@id": "rs-gaap:IntangibleAssetsNetExcludingGoodwill" + }, + { + "@id": "rs-gaap:IntangibleAssetsNetIncludingGoodwill" + }, + { + "@id": "rs-gaap:LongTermInvestmentsAndReceivablesNet" + }, + { + "@id": "rs-gaap:OperatingLeaseRightOfUseAsset" + }, + { + "@id": "rs-gaap:OtherAssetsNoncurrent" + }, + { + "@id": "rs-gaap:PropertyPlantAndEquipmentGross" + }, + { + "@id": "rs-gaap:AccumulatedDepreciationDepletionAndAmortizationPropertyPlantAndEquipment" } ] }, @@ -1124,6 +1343,21 @@ "http://www.w3.org/2002/07/owl#equivalentClass": [ { "@id": "rs-gaap:LiabilitiesNoncurrent" + }, + { + "@id": "rs-gaap:DeferredIncomeTaxLiabilitiesNet" + }, + { + "@id": "rs-gaap:DeferredRevenueNoncurrent" + }, + { + "@id": "rs-gaap:OperatingLeaseLiabilityNoncurrent" + }, + { + "@id": "rs-gaap:OtherLiabilitiesNoncurrent" + }, + { + "@id": "rs-gaap:LiabilitiesOtherThanLongtermDebtNoncurrent" } ] }, @@ -1162,6 +1396,36 @@ "http://www.w3.org/2002/07/owl#equivalentClass": [ { "@id": "rs-gaap:NonoperatingIncomeExpense" + }, + { + "@id": "rs-gaap:ForeignCurrencyTransactionGainLossBeforeTax" + }, + { + "@id": "rs-gaap:GainLossOnDispositionOfAssets" + }, + { + "@id": "rs-gaap:GainLossOnDispositionOfAssets1" + }, + { + "@id": "rs-gaap:GainLossOnSaleOfPropertyPlantEquipment" + }, + { + "@id": "rs-gaap:GainsLossesOnExtinguishmentOfDebt" + }, + { + "@id": "rs-gaap:InvestmentIncomeInterest" + }, + { + "@id": "rs-gaap:OtherNonoperatingIncomeExpense" + }, + { + "@id": "rs-gaap:InvestmentIncomeInterestAndDividend" + }, + { + "@id": "rs-gaap:InvestmentIncomeNet" + }, + { + "@id": "rs-gaap:InvestmentIncomeNonoperating" } ] }, @@ -1358,6 +1622,30 @@ }, { "@id": "rs-gaap:UtilitiesOperatingExpense" + }, + { + "@id": "rs-gaap:AssetImpairmentCharges" + }, + { + "@id": "rs-gaap:OtherCostAndExpenseOperating" + }, + { + "@id": "rs-gaap:ResearchAndDevelopmentExpense" + }, + { + "@id": "rs-gaap:RestructuringCharges" + }, + { + "@id": "rs-gaap:SellingAndMarketingExpense" + }, + { + "@id": "rs-gaap:RestructuringCosts" + }, + { + "@id": "rs-gaap:RestructuringCostsAndAssetImpairmentCharges" + }, + { + "@id": "rs-gaap:RestructuringSettlementAndImpairmentProvisions" } ] }, From 0c10052ba1f8cda6c69e51abadc7f1259a4b27b3 Mon Sep 17 00:00:00 2001 From: "Joseph T. French" Date: Sat, 6 Jun 2026 12:30:44 -0500 Subject: [PATCH 3/5] fix(taxonomy): update stale resolve_pin assertion in framework-bridge test The 'fac' package became tenant_copy:false in the tenant-copy curation, so it is omitted from the resolved copy-pin; assert on fac-traits (a copied depends_on package) instead. The file skips when the extensions DB is absent, so the staleness was hidden in CI. --- tests/taxonomy/test_framework_bridge_seed.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/taxonomy/test_framework_bridge_seed.py b/tests/taxonomy/test_framework_bridge_seed.py index 98a48a751..2aea7be69 100644 --- a/tests/taxonomy/test_framework_bridge_seed.py +++ b/tests/taxonomy/test_framework_bridge_seed.py @@ -157,7 +157,10 @@ class _G: pin = resolve_pin(_G()) # type: ignore[arg-type] assert pin["rs-gaap"] == "v999" - assert pin["fac"] == "v1" # untouched by override + # The depends_on fac chain still expands (fac-traits is copied) and is + # untouched by the override. The `fac` package itself is tenant_copy:false, + # so it is intentionally omitted from the copy-pin (see discovery.py). + assert pin["fac-traits"] == "v1" def test_legacy_flat_dict_returned_asis(self) -> None: from robosystems.taxonomy.pins import resolve_pin From 31a8f2cc757ecdf2dce5051e3b9ff9ca68ea4c00 Mon Sep 17 00:00:00 2001 From: "Joseph T. French" Date: Sat, 6 Jun 2026 12:36:05 -0500 Subject: [PATCH 4/5] chore(taxonomy): consolidate recurrence CHECK into 0002 (drop migration 0019) Pre-launch, no DB has run the 0002 seed yet, so the separate 0019 widen is redundant: 0002 already creates check_trait_category with 'recurrence' and its content-driven seed inserts the member. Folding it in avoids shipping a no-op migration at launch. Once 0002 runs in prod it is frozen, so any later trait-axis addition will need its own migration. --- .../0019_recurrence_trait_category.py | 122 ------------------ 1 file changed, 122 deletions(-) delete mode 100644 migrations/extensions/versions/0019_recurrence_trait_category.py diff --git a/migrations/extensions/versions/0019_recurrence_trait_category.py b/migrations/extensions/versions/0019_recurrence_trait_category.py deleted file mode 100644 index 7ed35d7ec..000000000 --- a/migrations/extensions/versions/0019_recurrence_trait_category.py +++ /dev/null @@ -1,122 +0,0 @@ -"""add recurrence trait category - -Widen the ``check_trait_category`` CHECK constraint on ``traits.category`` to -admit the new ``recurrence`` axis (member ``nonrecurring``) — an RS analytical -extension to the FASB-metamodel trait vocabulary for earnings-persistence -classification (normalized earnings / NOPAT excluding special items). The -``fac-traits`` package seeds the trait member; ``rs-gaap-traits`` binds it to -the kept operating special-items (restructuring, impairment, disposal -gains/losses, debt extinguishment). - -The constraint is created unprefixed (``check_trait_category``) in every schema -by ``0002_taxonomy_library.py``, so this widen runs against ``public`` and every -existing tenant schema — mirroring the CHECK-constraint widen pattern in -``0007_frameworks_bridges.py``. New tenants pick the widened constraint up -automatically via ``ExtensionsBase.metadata.create_all`` at provision. - -Revision ID: 0019 -Revises: 0018 -Create Date: 2026-06-06 12:04:37.532894 - -""" - -from __future__ import annotations - -from alembic import op -from sqlalchemy import text - -# revision identifiers, used by Alembic. -revision = "0019" -down_revision = "0018" -branch_labels = None -depends_on = None - - -# The 24 FASB us-gaap metamodel axes + flowClassification + indirectCashFlowReconcilingItem. -# Mirrors check_trait_category in robosystems/models/extensions/trait.py. -_PRIOR_TRAIT_CATEGORY_CHECK = ( - "category IN (" - "'elementsOfFinancialStatements', 'liquidity', 'activityType', " - "'operatingNonoperating', 'operatingIntent', 'realizationStatus', " - "'recordedValue', 'restriction', 'hedging', 'leaseType', " - "'interestRateType', 'convertibility', 'creditStructure', " - "'debtGuarantee', 'derivativeContract', 'derivativeInstrument', " - "'accrualOrPayable', 'priority', 'estimatedFutureActivity', " - "'statisticalMeasurement', 'taxComponents', 'threshold', 'use', " - "'indirectCashFlowReconcilingItem', " - "'flowClassification'" - ")" -) - -# Same list + the new 'recurrence' analytical axis. -_WIDENED_TRAIT_CATEGORY_CHECK = ( - "category IN (" - "'elementsOfFinancialStatements', 'liquidity', 'activityType', " - "'operatingNonoperating', 'operatingIntent', 'realizationStatus', " - "'recordedValue', 'restriction', 'hedging', 'leaseType', " - "'interestRateType', 'convertibility', 'creditStructure', " - "'debtGuarantee', 'derivativeContract', 'derivativeInstrument', " - "'accrualOrPayable', 'priority', 'estimatedFutureActivity', " - "'statisticalMeasurement', 'taxComponents', 'threshold', 'use', " - "'indirectCashFlowReconcilingItem', " - "'flowClassification', " - "'recurrence'" - ")" -) - - -def _trait_table(schema: str) -> str: - return "public.traits" if schema == "public" else f'"{schema}".traits' - - -def _widen_trait_category_check(conn, schema: str) -> None: - """Drop and re-add the traits.category CHECK with the widened list.""" - table = _trait_table(schema) - conn.execute( - text(f"ALTER TABLE {table} DROP CONSTRAINT IF EXISTS check_trait_category") - ) - conn.execute( - text( - f"ALTER TABLE {table} ADD CONSTRAINT check_trait_category " - f"CHECK ({_WIDENED_TRAIT_CATEGORY_CHECK})" - ) - ) - - -def _restore_trait_category_check(conn, schema: str) -> None: - """Inverse of :func:`_widen_trait_category_check` for downgrade.""" - table = _trait_table(schema) - conn.execute( - text(f"ALTER TABLE {table} DROP CONSTRAINT IF EXISTS check_trait_category") - ) - conn.execute( - text( - f"ALTER TABLE {table} ADD CONSTRAINT check_trait_category " - f"CHECK ({_PRIOR_TRAIT_CATEGORY_CHECK})" - ) - ) - - -def upgrade() -> None: - conn = op.get_bind() - from migrations.extensions.helpers import for_each_tenant_schema - - _widen_trait_category_check(conn, "public") - for_each_tenant_schema(conn, _widen_trait_category_check) - - -def downgrade() -> None: - conn = op.get_bind() - from migrations.extensions.helpers import for_each_tenant_schema - - # Downgrade restores the pre-recurrence list. Any 'recurrence' rows must be - # removed first or the restored CHECK will reject them. - conn.execute(text("DELETE FROM public.traits WHERE category = 'recurrence'")) - - def _drop_recurrence_rows(c, schema: str) -> None: - c.execute(text(f"DELETE FROM \"{schema}\".traits WHERE category = 'recurrence'")) - - for_each_tenant_schema(conn, _drop_recurrence_rows) - - _restore_trait_category_check(conn, "public") - for_each_tenant_schema(conn, _restore_trait_category_check) From 45736b6dace6f677a101f1b8ccbc547d13858e31 Mon Sep 17 00:00:00 2001 From: "Joseph T. French" Date: Sat, 6 Jun 2026 13:01:28 -0500 Subject: [PATCH 5/5] docs(taxonomy): fix stale trait counts + dropped-0019 reference Address PR #731 review nits (all doc/comment-only): - bump 25 categories / 99 members -> 26 / 100 in trait.py docstring and 0002 docstring + the two trait-table comments (recurrence axis was added but these counts weren't updated) - rewrite the check_trait_category comment that referenced migration 0019 (dropped when the widen was consolidated into 0002) to explain no separate widen migration was needed --- .../extensions/versions/0002_taxonomy_library.py | 11 ++++++----- robosystems/models/extensions/trait.py | 5 +++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/migrations/extensions/versions/0002_taxonomy_library.py b/migrations/extensions/versions/0002_taxonomy_library.py index bb8e37caf..d71cadb97 100644 --- a/migrations/extensions/versions/0002_taxonomy_library.py +++ b/migrations/extensions/versions/0002_taxonomy_library.py @@ -34,7 +34,7 @@ - DELETE 0001-seeded library rows - INSERT from JSON-LD seeds: fac, rs-gaap (concept taxonomies); - fac-traits (trait vocabulary, 99 traits across 25 categories; + fac-traits (trait vocabulary, 100 traits across 26 categories; inherited from fac framework via depends_on); rs-gaap-traits (per-element trait bindings, ~1.7k arcs); rs-gaap-hierarchy (class-subclass); fac-to-rs-gaap, fac-calculations, @@ -280,8 +280,9 @@ # public.traits.category (robosystems/models/extensions/trait.py). The # content-driven library seed in this same migration inserts the # `recurrence/nonrecurring` member from fac-traits.jsonld, so the CHECK -# created here must already admit it on a fresh DB; migration 0019 applies -# the same widen to DBs that ran an earlier version of this constraint. +# created here must already admit it on a fresh DB. No deployment had run an +# earlier version of this migration when `recurrence` was added, so no +# separate widen migration for existing DBs was needed. _TRAIT_CATEGORY_CHECK = ( "category IN (" "'elementsOfFinancialStatements', 'liquidity', 'activityType', " @@ -669,7 +670,7 @@ def _create_tenant_library_tables(conn, schema: str) -> None: ) ) - # traits: FASB metamodel vocabulary (25 element-side categories). + # traits: FASB metamodel vocabulary (26 element-side categories). conn.execute( text(f""" CREATE TABLE IF NOT EXISTS {schema}.traits ( @@ -1112,7 +1113,7 @@ def upgrade() -> None: # ────────────────────────────────────────────────────────────────────── # 6. Trait vocabulary + element_traits junction + classification + assoc junction. # ────────────────────────────────────────────────────────────────────── - # traits: FASB metamodel vocabulary (25 element-side categories). + # traits: FASB metamodel vocabulary (26 element-side categories). op.create_table( "traits", sa.Column("id", sa.String(), nullable=False), diff --git a/robosystems/models/extensions/trait.py b/robosystems/models/extensions/trait.py index 5d260fc3a..782d5abb5 100644 --- a/robosystems/models/extensions/trait.py +++ b/robosystems/models/extensions/trait.py @@ -5,8 +5,9 @@ ``(category='liquidity', identifier='current')``, ``(category='flowClassification', identifier='inflow')``, etc. -The 25 categories cover the 24 orthogonal FASB metamodel trait axes plus -``flowClassification`` (derived from FASB instant-* arcroles). Element +The 26 categories cover the 24 orthogonal FASB metamodel trait axes plus +``flowClassification`` (derived from FASB instant-* arcroles) and the RS +``recurrence`` analytical axis (earnings persistence / NOPAT). Element assignments live in ``element_traits`` via :class:`ElementTrait`. This is distinct from :class:`Classification` (association-side), which