From 9bc0fd7283a0897a91ddf2fd63791a78e0112d6e Mon Sep 17 00:00:00 2001 From: "Joseph T. French" Date: Tue, 28 Jul 2026 23:09:12 -0500 Subject: [PATCH] fix(graphql): refresh the schema snapshot as part of SDK generation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The snapshot that tests/test_graphql_queries.py validates against was refreshed only by an explicit `just refresh-schema`, disconnected from generation. Nothing would have kept it current, and #155 described the risk incorrectly: it claimed a stale snapshot could only cause a false failure. It can cause a false pass, in the direction that matters. If the backend removes a field the snapshot still lists, a query using it validates clean here and fails at runtime — precisely the library-query breakage the test exists to catch. Adding a field is the safe direction; removing one is not. `just generate-sdk` now refreshes the snapshot after generating the REST client, so both halves of the SDK come from the same running backend in one command. This mirrors the TypeScript client, whose codegen introspects the live GraphQL endpoint on every generate — which is the structural reason its query documents never went stale-invalid while Python's did. refresh-schema now introspects the GraphQL endpoint rather than importing the backend package, so it needs a running backend instead of a sibling checkout — the same dependency generate-sdk already has. It fails loudly when the backend is unreachable rather than leaving a stale file looking fresh. The snapshot is rewritten in introspection order, which reorders ~1700 lines on this first pass. Verified semantically identical: 116 types before and after, none added or removed, and all 51 documents still validate. --- bin/refresh-schema.py | 77 + justfile | 15 +- robosystems_client/graphql/schema.graphql | 3400 ++++++++++----------- tests/test_graphql_queries.py | 15 +- 4 files changed, 1796 insertions(+), 1711 deletions(-) create mode 100755 bin/refresh-schema.py diff --git a/bin/refresh-schema.py b/bin/refresh-schema.py new file mode 100755 index 0000000..5d6f3ce --- /dev/null +++ b/bin/refresh-schema.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +"""Refresh the checked-in GraphQL SDL snapshot by introspecting the backend. + +`tests/test_graphql_queries.py` validates every hand-written query document +against this snapshot. That check is only as good as the snapshot is current: + +- Backend *adds* a field the snapshot lacks -> a query using it fails + validation. Noisy, but safe. +- Backend *removes* a field the snapshot still lists -> a query using it + validates clean and fails at runtime. Silent, and exactly the failure the + test exists to prevent. + +So the snapshot has to be refreshed whenever the SDK is regenerated, which is +why `just generate-sdk` calls this. Introspecting the same running backend the +REST generation already targets keeps both halves of the SDK generated from one +source, and mirrors how the TypeScript client's codegen introspects live. +""" + +from __future__ import annotations + +import json +import sys +import urllib.error +import urllib.request +from pathlib import Path + +from graphql import build_client_schema, get_introspection_query, print_schema + +DEFAULT_URL = "http://localhost:8000/extensions/kg00000000000000000000/graphql" +OUT_PATH = ( + Path(__file__).resolve().parent.parent + / "robosystems_client" + / "graphql" + / "schema.graphql" +) + + +def introspect(url: str) -> dict: + payload = json.dumps({"query": get_introspection_query()}).encode() + request = urllib.request.Request( + url, data=payload, headers={"Content-Type": "application/json"} + ) + with urllib.request.urlopen(request, timeout=60) as response: + body = json.loads(response.read()) + if "errors" in body and body["errors"]: + raise RuntimeError(f"Introspection returned errors: {body['errors']}") + return body["data"] + + +def main() -> int: + url = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_URL + try: + data = introspect(url) + except (urllib.error.URLError, TimeoutError) as exc: + # Fail loudly rather than leaving a stale snapshot in place looking fresh. + print(f"Could not introspect {url}: {exc}", file=sys.stderr) + print("Is the backend running? Start it, then re-run.", file=sys.stderr) + return 1 + + sdl = print_schema(build_client_schema(data)) + previous = OUT_PATH.read_text() if OUT_PATH.exists() else None + OUT_PATH.write_text(sdl) + + if previous is None: + print(f"Wrote {OUT_PATH.name} ({len(sdl.splitlines())} lines)") + elif previous == sdl: + print(f"{OUT_PATH.name} already current ({len(sdl.splitlines())} lines)") + else: + print( + f"{OUT_PATH.name} updated ({len(previous.splitlines())} -> " + f"{len(sdl.splitlines())} lines) — re-run `just test-all`" + ) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/justfile b/justfile index 916fd01..37a8cc2 100644 --- a/justfile +++ b/justfile @@ -49,16 +49,15 @@ typecheck: uv run basedpyright # Generate SDK from localhost API -generate-sdk url="http://localhost:8000/openapi.json": +generate-sdk url="http://localhost:8000/openapi.json" graphql_url="http://localhost:8000/extensions/kg00000000000000000000/graphql": bin/generate-sdk.sh {{url}} + @just refresh-schema {{graphql_url}} -# Refresh the checked-in GraphQL schema snapshot. tests/test_graphql_queries.py -# validates every hand-written query document against it, so refresh this -# whenever the backend schema changes. A stale snapshot can only cause a false -# failure here, never a false pass. -refresh-schema backend="../robosystems": - cd {{backend}} && uv run python -c "from robosystems.graphql.schema import schema; from pathlib import Path; sdl = schema.as_str() if hasattr(schema, 'as_str') else str(schema); Path('{{justfile_directory()}}/robosystems_client/graphql/schema.graphql').write_text(sdl)" - @echo "schema.graphql refreshed - re-run 'just test-all'" +# Refresh the checked-in GraphQL SDL snapshot by introspecting a running backend. +# tests/test_graphql_queries.py validates the hand-written query documents +# against it. generate-sdk runs this too, so the snapshot can't silently drift. +refresh-schema url="http://localhost:8000/extensions/kg00000000000000000000/graphql": + uv run bin/refresh-schema.py {{url}} # Build python package locally (for testing) build-package: diff --git a/robosystems_client/graphql/schema.graphql b/robosystems_client/graphql/schema.graphql index cbd2374..7afa9a2 100644 --- a/robosystems_client/graphql/schema.graphql +++ b/robosystems_client/graphql/schema.graphql @@ -1,980 +1,462 @@ -""" -One CoA account (Element) — the basic chart-of-accounts row. - -``trait`` carries the FASB classification (asset/liability/equity/ -revenue/expense/etc.); ``balance_type`` is the natural side -('debit' or 'credit'). ``account_type`` is a free-form sub-grouping -(e.g. 'cash', 'inventory') used by some integrations. Hierarchy is -expressed via ``parent_id`` + ``depth``. -""" -type Account { - id: String! - code: String - name: String! - description: String - trait: String - subClassification: String - balanceType: String! - parentId: String - depth: Int! - currency: String! - isActive: Boolean! - isPlaceholder: Boolean! - accountType: String - externalId: String - externalSource: String -} - -""" -Paginated chart-of-accounts listing — flat (use the tree endpoint -for parent/child structure). -""" -type AccountList { - accounts: [Account!]! - pagination: PaginationInfo! -} - -""" -All CoA accounts that roll up into a single reporting concept, -with the group total and per-account contributions. -""" -type AccountRollupGroup { - reportingElementId: String! - reportingName: String! - reportingQname: String! - trait: String! - balanceType: String! - total: Float! - accounts: [AccountRollupRow!]! -} - -"""One CoA account contributing to a reporting concept's rollup.""" -type AccountRollupRow { - elementId: String! - accountName: String! - accountCode: String - totalDebits: Float! - totalCredits: Float! - netBalance: Float! -} - -""" -Mapping rendered as account rollups — every reporting concept the -mapping defines, with the CoA accounts that contribute to it and the -current balance for each. ``total_unmapped`` tracks gaps for UI. -""" -type AccountRollups { - mappingId: String! - mappingName: String! - groups: [AccountRollupGroup!]! - totalMapped: Int! - totalUnmapped: Int! -} - -type AccountTree { - roots: [AccountTreeNode!]! - totalAccounts: Int! -} - -type AccountTreeNode { - id: ID! - code: String - name: String! - trait: String - accountType: String - balanceType: String! - depth: Int! - isActive: Boolean! - children: [AccountTreeNode!]! -} - -type Agent { - id: String! - agentType: String! - name: String! - legalName: String - taxId: String - registrationNumber: String - duns: String - lei: String - email: String - phone: String - address: JSON - source: String! - externalId: String - isActive: Boolean! - is1099Recipient: Boolean! - createdAt: DateTime - updatedAt: DateTime - createdBy: String - openReceivable: OpenBalanceByAgent - openPayable: OpenBalanceByAgent -} - -type Artifact { - topic: String - rendererNote: String - template: JSON - mechanics: JSON! -} - -""" -One edge between two elements within a structure (parent/child -presentation, calculation rollup, mapping, equivalence). - -``association_type`` discriminates the edge semantics. Mapping edges -are the user-facing path (CoA → reporting concept); presentation / -calculation edges express structure layout and roll-ups. -``confidence`` is set on AI-suggested mappings (≥0.90 auto-approved, -0.70-0.89 flagged for review). -""" -type Association { - id: String! - structureId: String! - fromElementId: String! - fromElementName: String - fromElementQname: String - toElementId: String! - toElementName: String - toElementQname: String - associationType: String! - orderValue: Float - weight: Float - confidence: Float - suggestedBy: String - approvedBy: String -} - -""" -A grouping of closing-book items shown as a sidebar section -(e.g. Statements, Account Rollups, Schedules, Period Close). -""" -type ClosingBookCategory { - label: String! - items: [ClosingBookItem!]! -} - -""" -One row in the closing book — a navigable artifact for the -period (statement, schedule, rollup, etc.). - -``item_type`` discriminates: 'statement', 'schedule', -'account_rollups', 'period_close', 'trial_balance'. Statement items -carry ``report_id`` to fetch the rendered facts; schedule items -carry ``status`` ('complete' | 'draft' | 'pending'). -""" -type ClosingBookItem { - id: String! - name: String! - itemType: String! - blockType: String - reportId: String - status: String -} - -""" -The closing book navigation tree — categories + items the UI -uses to render the period-close workspace. ``has_data=False`` when -the graph has no posted entries yet. -""" -type ClosingBookStructures { - categories: [ClosingBookCategory!]! - hasData: Boolean! -} - -"""Date (isoformat)""" -scalar Date - -"""Date with time (isoformat)""" -scalar DateTime - -"""A single draft entry with full line item detail for review.""" -type DraftEntry { - entryId: String! - postingDate: Date! - - """Entry type (e.g., 'closing', 'adjusting')""" - type: String! - memo: String - - """ - Where the entry came from (ENTRY_PROVENANCE_VALUES): source_sync, ai_generated, manual_entry, schedule_derived, system_computed, event_handler - """ - provenance: String - - """Schedule structure that generated this entry (if any)""" - sourceStructureId: String - - """Human-readable name of the source schedule""" - sourceStructureName: String - lineItems: [DraftLineItem!]! - - """Sum of debit amounts in cents""" - totalDebit: Int! - - """Sum of credit amounts in cents""" - totalCredit: Int! - - """True if total_debit == total_credit""" - balanced: Boolean! - - """ - True if closing the period will publish this draft to QuickBooks — i.e. the graph has a qb_authoritative/hybrid QB connection AND this is an RL-originated draft (schedule/manual) not already in QB. False means it posts locally only. - """ - willPublishToQb: Boolean! -} - -"""A single line item within a draft entry.""" -type DraftLineItem { - lineItemId: String! - elementId: String! - elementCode: String - elementName: String! - - """Debit amount in cents""" - debitAmount: Int! - - """Credit amount in cents""" - creditAmount: Int! - description: String -} - -"""Element with taxonomy context — extends AccountResponse.""" -type Element { - id: String! - code: String - name: String! - description: String - qname: String - namespace: String - trait: String - subClassification: String - balanceType: String! - periodType: String! - isAbstract: Boolean! - elementType: String! - source: String! - taxonomyId: String - parentId: String - depth: Int! - isActive: Boolean! - externalId: String - externalSource: String -} - -"""Paginated element listing with taxonomy context.""" -type ElementList { - elements: [Element!]! - pagination: PaginationInfo! +type Query { + portfolios(limit: Int = null, offset: Int = null): PortfolioList + securities(entityId: String = null, securityType: String = null, isActive: Boolean = null, limit: Int = null, offset: Int = null): SecurityList + security(securityId: String!): Security + positions(portfolioId: String = null, securityId: String = null, status: String = null, limit: Int = null, offset: Int = null): PositionList + position(positionId: String!): Position + holdings(portfolioId: String!): HoldingsList + portfolioBlock(portfolioId: String!): PortfolioBlock + entity: LedgerEntity + entities(source: String = null): [LedgerEntity!]! + agent(id: String!): Agent + agents(agentType: String = null, source: String = null, isActive: Boolean = true, limit: Int = null, offset: Int = null): [Agent!]! + openReceivables: OpenBalanceAggregate! + openPayables: OpenBalanceAggregate! + openReceivablesByAgent: [OpenBalanceByAgent!]! + openPayablesByAgent: [OpenBalanceByAgent!]! + eventBlock(id: String!): EventBlock + eventBlocks(eventType: String = null, eventCategory: String = null, status: String = null, agentId: String = null, source: String = null, limit: Int = null, offset: Int = null): [EventBlock!]! + summary: LedgerSummary + accounts(classification: String = null, isActive: Boolean = null, limit: Int = null, offset: Int = null): AccountList + accountTree(includeInactive: Boolean = null): AccountTree + accountRollups(mappingId: String = null, startDate: Date = null, endDate: Date = null): AccountRollups + trialBalance(startDate: Date = null, endDate: Date = null): TrialBalance + transactions(type: String = null, startDate: Date = null, endDate: Date = null, limit: Int = null, offset: Int = null): LedgerTransactionList + transaction(transactionId: String!): LedgerTransactionDetail + taxonomies(taxonomyType: String = null): TaxonomyList + reportingTaxonomy: Taxonomy + elements(taxonomyId: String = null, source: String = null, classification: String = null, isAbstract: Boolean = null, limit: Int = null, offset: Int = null): ElementList + mappingCandidates(classification: String!): [Element!]! + unmappedElements(mappingId: String = null): [UnmappedElement!]! + structures(taxonomyId: String = null, blockType: String = null): StructureList + mappings: StructureList + mapping(mappingId: String!): MappingDetail + mappingCoverage(mappingId: String!): MappingCoverage + mappedTrialBalance(mappingId: String!, startDate: Date = null, endDate: Date = null): MappedTrialBalance + periodCloseStatus(periodStart: Date!, periodEnd: Date!): PeriodCloseStatus + fiscalCalendar: FiscalCalendar + periodDrafts(period: String!): PeriodDrafts + closingBookStructures: ClosingBookStructures + reports: ReportList + report(reportId: String!): Report + reportPackage(reportId: String!): ReportPackage + reportDownloadUrl(reportId: String!, format: ReportDownloadFormat = null, expiresIn: Int = null): ReportBundleDownload + statement(reportId: String!, blockType: String!): Statement + publishLists(limit: Int = null, offset: Int = null): PublishListList + publishList(listId: String!): PublishListDetail + informationBlock(id: ID!, scenarioId: String = null, series: Boolean! = false, seriesHistory: Int = null, seriesForecast: Int = null): InformationBlock + informationBlocks(blockType: String = null, category: String = null, limit: Int = null, offset: Int = null, scenarioId: String = null): [InformationBlock!]! + taxonomyBlock(id: ID!): TaxonomyBlock + taxonomyBlocks(taxonomyType: String = null, parentTaxonomyId: ID = null, category: String = null, limit: Int = null, offset: Int = null): [TaxonomyBlock!]! + libraryTaxonomies(standard: String = null, includeElementCount: Boolean = null): [LibraryTaxonomy!]! + libraryTaxonomy(id: ID = null, standard: String = null, version: String = null, includeElementCount: Boolean = null): LibraryTaxonomy + libraryTaxonomyArcs(taxonomyId: ID!, associationType: String = null, structureId: ID = null, limit: Int = null, offset: Int = null): [LibraryAssociation!]! + libraryTaxonomyArcCount(taxonomyId: ID!, associationType: String = null, structureId: ID = null): Int! + libraryElements(taxonomyId: ID = null, source: String = null, classification: String = null, activityType: String = null, elementType: String = null, isAbstract: Boolean = null, limit: Int = null, offset: Int = null, includeLabels: Boolean = null, includeReferences: Boolean = null): [LibraryElement!]! + libraryElement(id: ID = null, qname: String = null): LibraryElement + searchLibraryElements(query: String!, source: String = null, limit: Int = null): [LibraryElement!]! + libraryElementTree(id: ID!, maxDepth: Int = null, structureId: ID = null): LibraryElementTreeNode + libraryElementEquivalents(id: ID!): LibraryEquivalence + libraryElementArcs(id: ID!): [LibraryElementArc!]! + libraryElementClassifications(id: ID!): [LibraryElementClassification!]! + libraryStructures(taxonomyId: ID = null, blockType: String = null): [LibraryStructure!]! + libraryStructure(id: ID!): LibraryStructure + hello: String! } -""" -Lightweight entity projection for embedding in portfolio-block / -position envelopes. Carries identity-only fields; full entity data -lives behind the Master Data entity APIs. -""" -type EntityLite { - """Entity ID (`ent_*` ULID).""" - id: ID! - - """Display name of the entity.""" - name: String! - - """ - Tenant graph this entity is anchored to, when known. `null` for entities not yet linked to a graph. - """ - sourceGraphId: String -} +"""Paginated list of portfolios.""" +type PortfolioList { + """Portfolios on this page.""" + portfolios: [Portfolio!]! -type EventBlock { - id: String! - eventType: String! - eventCategory: String! - eventClass: String! - status: String! - occurredAt: DateTime! - effectiveAt: DateTime - source: String! - externalId: String - externalUrl: String - amount: Int - currency: String! - description: String - metadata: JSON! - dimensionIds: [String!]! - agentId: String - resourceType: String - resourceElementId: String - replacedByEventId: String - replacesEventId: String - obligatedByEventId: String - dischargesEventId: String - createdAt: DateTime! - createdBy: String! + """Pagination cursor and totals.""" + pagination: PaginationInfo! } """ -A single fact row inside a rendered statement. - -One row per concept, with one value per period column. Subtotals and -hierarchy depth come from the structure being projected. -""" -type FactRow { - """Internal element identifier.""" - elementId: String! - - """QName of the reporting concept (e.g. 'us-gaap:Revenues').""" - elementQname: String! - - """Human-readable concept label.""" - elementName: String! - - """ - Concept trait flag from the structure (e.g. 'total', 'subtotal', 'header'). Drives presentation. - """ - trait: String - - """ - One value per period column, in the same order as `periods`. Null when the concept had no facts in that window. - """ - values: [Float]! - - """True when the row should render as a subtotal line.""" - isSubtotal: Boolean! - - """Indentation depth in the structure hierarchy (0 = root).""" - depth: Int! -} - -"""Current fiscal calendar state for a graph.""" -type FiscalCalendar { - graphId: String! - fiscalYearStartMonth: Int! - - """Latest closed period (YYYY-MM), or null if nothing closed""" - closedThrough: String - - """Target period the user wants closed through (YYYY-MM)""" - closeTarget: String - - """ - Number of periods between closed_through and close_target (inclusive of close_target). 0 means caught up. - """ - gapPeriods: Int! - - """Ordered list of periods that a close run would process""" - catchUpSequence: [String!]! - - """ - Whether the next period in the catch-up sequence passes all closeable gates - """ - closeableNow: Boolean! - - """ - Structured blocker codes when closeable_now is False: 'sequence_violation', 'period_incomplete', 'sync_stale', 'calendar_not_initialized', 'period_already_closed', 'pending_obligations' - """ - blockers: [String!]! - - """ - Number of pending schedule_entry_due events blocking close. Non-zero only when `pending_obligations` is in `blockers`. - """ - pendingObligationCount: Int! - - """ - Sample of up to 5 pending obligations (schedule_id, schedule_name, period, event_id) ordered by occurred_at. Use `list-event-blocks` with event_type=schedule_entry_due&status=pending for the full set. - """ - pendingObligationSample: [PendingObligationDetail!]! - - """ - Earliest period (YYYY-MM) with a pending obligation blocking close. Null when no pending_obligations blocker is active. - """ - earliestPendingPeriod: String - - """ - Days the most recent sync is stale relative to the period to close. Populated only when `sync_stale` is in `blockers` and last_sync_at exists (null when there's a connection but no sync has ever run). - """ - syncStaleDays: Int - lastCloseAt: DateTime - initializedAt: DateTime - - """Most recent QB sync timestamp (if connected)""" - lastSyncAt: DateTime - - """Fiscal period rows for this graph""" - periods: [FiscalPeriodSummary!]! -} +Read projection for a single portfolio — core fields only. +Position-level holdings live on the dedicated portfolio-block envelope +(`PortfolioBlockEnvelope`) returned by molecular operations. """ -One fiscal period row — header view used in calendar listings. +type Portfolio { + """Portfolio ID (`port_*` ULID).""" + id: String! -Status lifecycle: ``open`` → ``closing`` → ``closed``. ``closing`` -is the transient state during a close run; ``closed_at`` stamps when -the lock landed. -""" -type FiscalPeriodSummary { - """Period name (YYYY-MM)""" + """Display name.""" name: String! - startDate: Date! - endDate: Date! - - """'open' | 'closing' | 'closed'""" - status: String! - closedAt: DateTime -} - -""" -All securities held in a single entity, rolled up across the -caller's portfolios. -""" -type Holding { - """Issuing entity ID.""" - entityId: String! - - """Display name of the entity.""" - entityName: String! - - """Pre-association tenant graph, when set on the securities.""" - sourceGraphId: String - - """One row per security held in this entity.""" - securities: [HoldingSecuritySummary!]! - - """Sum of cost basis across all securities, in dollars.""" - totalCostBasisDollars: Float! - - """ - Sum of current value across all securities, in dollars. `null` if any security lacks a mark. - """ - totalCurrentValueDollars: Float - - """Number of distinct active positions backing these holdings.""" - positionCount: Int! -} - -"""One security held by an entity, rolled up across portfolios.""" -type HoldingSecuritySummary { - """Security ID.""" - securityId: String! - - """Display name of the security.""" - securityName: String! - - """Instrument family (e.g. `common_stock`, `warrant`).""" - securityType: String! - - """Total quantity held in `quantity_type` units.""" - quantity: Float! - - """Unit basis (`shares`, `units`, `principal`).""" - quantityType: String! - """Aggregate cost basis in dollars, summed across all positions.""" - costBasisDollars: Float! + """Free-text description.""" + description: String """ - Aggregate current value in dollars, or `null` if any underlying position lacks a mark. + Free-text strategy classification (e.g. `value`, `growth`, `income`). Open vocabulary. """ - currentValueDollars: Float -} + strategy: String -"""Aggregated holdings across all of the caller's portfolios.""" -type HoldingsList { - """One row per issuing entity.""" - holdings: [Holding!]! + """Date the portfolio was established (YYYY-MM-DD).""" + inceptionDate: Date - """Count of entities represented.""" - totalEntities: Int! + """ISO 4217 currency code used for portfolio-level aggregates.""" + baseCurrency: String! - """Total active positions backing these holdings.""" - totalPositions: Int! -} + """Row creation timestamp (UTC).""" + createdAt: DateTime! -type InformationBlock { - id: ID! - blockType: String! - name: String! - displayName: String! - category: String! - taxonomyId: String - taxonomyName: String - disclosureId: String - informationModel: InformationModel! - artifact: Artifact! - elements: [InformationBlockElement!]! - connections: [InformationBlockConnection!]! - facts: [InformationBlockFact!]! - rules: [InformationBlockRule!]! - dimensions: [JSON!]! - factSet: InformationBlockFactSet - verificationResults: [InformationBlockVerificationResult!]! - verificationSummary: InformationBlockVerificationSummary - view: InformationBlockViewProjections! + """Last-modified timestamp (UTC).""" + updatedAt: DateTime! } -""" -Server-shaped chart projection — panel/series CONFIG, never values. +"""Date (isoformat)""" +scalar Date -The second real server-computed View arm (after ``rendering``). Values -come from ``rendering.rows`` joined by ``element_id``; the x-axis is -``rendering.periods``. Renderers (report-components) turn one panel -into one chart. -""" -type InformationBlockChart { - panels: [InformationBlockChartPanel!]! -} +"""Date with time (isoformat)""" +scalar DateTime -""" -One chart panel — series sharing a y-axis format family. +"""Pagination information for list responses.""" +type PaginationInfo { + """Total number of items available""" + total: Int! -Mixed-unit catalogs are unplottable on one axis, so the server groups -rows into panels by ``item_type`` family (NULL falls back to -``is_monetary``). The x-axis is always ``rendering.periods``. -""" -type InformationBlockChartPanel { - """Panel heading — e.g. 'Monetary', 'Ratios'.""" - label: String + """Maximum number of items returned in this response""" + limit: Int! - """ - Format family shared by the panel's series (monetary | ratio | percent | multiple | days); None for the untyped fallback panel. - """ - itemType: String + """Number of items skipped""" + offset: Int! - """Per-panel mark — 'line' or 'bar'.""" - kind: String! - series: [InformationBlockChartSeries!]! + """Whether more items are available""" + hasMore: Boolean! } -""" -One plottable series in a chart panel. +type SecurityList { + securities: [Security!]! + pagination: PaginationInfo! +} + +type Security { + id: ID! + entityId: String + entityName: String + sourceGraphId: String + name: String! + securityType: String! + securitySubtype: String + terms: JSON! + isActive: Boolean! + authorizedShares: Int + outstandingShares: Int + createdAt: DateTime! + updatedAt: DateTime! +} -Carries structure and identity only — the values live in the sibling -``rendering.rows`` (join on ``element_id``), so the chart arm never -duplicates the value matrix. ``key`` is the stable series identity for -client state (colors, toggles); today it equals ``element_id``, and -future axes (the forecast scenario) arrive as new fields on this -model, never a new arm shape. """ -type InformationBlockChartSeries { - """Stable series id — element_id today.""" - key: String! - elementId: String! +The `JSON` scalar type represents JSON values as specified by [ECMA-404](https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf). +""" +scalar JSON - """Display name for legends.""" - label: String! +"""Paginated list of positions.""" +type PositionList { + """Positions on this page.""" + positions: [Position!]! + + """Pagination cursor and totals.""" + pagination: PaginationInfo! } """ -Classification projection — one row per `association_classifications` -junction entry. - -Association-side only: concept_arrangement, member_arrangement, -named_disclosure. Element-side FASB metamodel traits (asset, current, -operating, …) live in `TraitLite` via `element_traits`. +Read projection for a single position. -Carries enough for the envelope caller to render / filter by category + -identifier without a follow-up lookup. The full `public.classifications` -vocabulary catalog (name / description / metadata) is available via the -library GraphQL surface when callers need the details. +Pairs cents-precision fields (`cost_basis`, `current_value`) with +pre-computed dollar floats (`*_dollars`) to spare clients the +conversion. The cents fields are authoritative. """ -type InformationBlockClassification { - """Classification vocabulary row id.""" +type Position { + """Position ID (`pos_*` ULID).""" id: String! - """ - One of the 3 association-level categories in the `public.classifications` CHECK constraint: 'concept_arrangement', 'member_arrangement', or 'named_disclosure'. - """ - category: String! + """Owning portfolio ID.""" + portfolioId: String! - """ - Vocabulary identifier within the category — e.g. 'RollUp', 'whole_part', 'AssetsRollUp'. - """ - identifier: String! + """Held security ID.""" + securityId: String! """ - Whether this is the canonical classification for the (association|element, category) pair. Non-primary rows capture alternates / AI suggestions alongside the chosen primary. + Cached display name of the held security, denormalized for list rendering. May lag the security row's current name briefly. """ - isPrimary: Boolean! + securityName: String - """ - AI/adapter-supplied confidence (0.0-1.0). Null for deterministic library-seeded rows. - """ - confidence: Float + """Cached display name of the security's issuing entity.""" + entityName: String - """ - Provenance — 'arcrole_analysis', 'disclosure_mechanics', 'fac-traits', adapter name, etc. - """ - source: String -} + """Quantity held in units defined by `quantity_type`.""" + quantity: Float! -""" -Connection (= Association) projection. + """Unit basis (`shares`, `units`, `principal`).""" + quantityType: String! -Renamed at the API boundary to match Charlie's ontology vocabulary. -The underlying storage table is still ``associations``. -""" -type InformationBlockConnection { - id: String! - fromElementId: String! - toElementId: String! + """Cost basis in **cents** of `currency`. Authoritative.""" + costBasis: Int! """ - presentation | calculation | mapping | equivalence | general-special | essence-alias + Cost basis pre-converted to dollars (`cost_basis / 100`). Convenience for display; `cost_basis` is the source of truth. """ - associationType: String! - arcrole: String - orderValue: Float - weight: Float + costBasisDollars: Float! + + """ISO 4217 currency code for `cost_basis` and `current_value`.""" + currency: String! + + """Latest mark-to-market value in **cents**, or `null` if unmarked.""" + currentValue: Int """ - Association-level classifications — concept_arrangement, member_arrangement, named_disclosure rows from the junction. Empty for library-seeded associations that haven't been classified yet. + Current value in dollars (`current_value / 100`). `null` when `current_value` is null. """ - classifications: [InformationBlockClassification!]! -} + currentValueDollars: Float -""" -Element projection for bundling inside an Information Block envelope. + """Date `current_value` was sourced (YYYY-MM-DD).""" + valuationDate: Date -Narrower than :class:`LibraryElementResponse` — excludes the heavy fields -(labels, references, classifications) that library browsing needs but -block consumers don't. Agents + frontends ask for those on demand via -the full library GraphQL fields when they need them. -""" -type InformationBlockElement { - id: String! - qname: String - name: String! - code: String + """Free-text source attribution for the current valuation.""" + valuationSource: String - """concept | abstract | axis | member | hypercube""" - elementType: String! - isAbstract: Boolean! - isMonetary: Boolean! - balanceType: String - periodType: String + """Date the position was acquired (YYYY-MM-DD).""" + acquisitionDate: Date """ - Value-domain vocabulary (monetary | ratio | percent | multiple | days | string | …). None means untyped; fall back to is_monetary. + Date the position was disposed, if `status='disposed'`. `null` for active positions. """ - itemType: String + dispositionDate: Date """ - The element's documentation-role label — the catalog's authoritative value semantics (e.g. whether a percent driver is a growth rate or a rate-on-base fraction). None when the element carries no documentation label. + Lifecycle state. One of: `active` (currently held), `disposed` (soft-deleted via `update-portfolio-block` dispose), `archived` (historical record only). """ - documentation: String -} + status: String! -"""Fact projection — just the values the envelope caller cares about.""" -type InformationBlockFact { - id: String! - elementId: String! - elementName: String - elementQname: String + """Free-text notes attached to the position.""" + notes: String - """Numeric value; null for Nonnumeric (text-block) facts.""" - value: Float + """Row creation timestamp (UTC).""" + createdAt: DateTime! - """Text payload for Nonnumeric facts; null for numeric.""" - textValue: String + """Last-modified timestamp (UTC).""" + updatedAt: DateTime! +} - """Numeric | Nonnumeric""" - factType: String! +"""Aggregated holdings across all of the caller's portfolios.""" +type HoldingsList { + """One row per issuing entity.""" + holdings: [Holding!]! - """MIME type of text_value (e.g. 'text/markdown').""" - contentType: String - periodStart: Date - periodEnd: Date! - periodType: String! - unit: String! + """Count of entities represented.""" + totalEntities: Int! - """historical | in_scope""" - factScope: String! - factSetId: String + """Total active positions backing these holdings.""" + totalPositions: Int! } """ -FactSet projection — period-specific instantiation of the Structure. - -The envelope carries one ``FactSetLite`` per block when a FactSet row -exists for the requested period; legacy writes that pre-date FactSet -stamping leave ``fact_set`` null until the expand pass starts -populating those rows. +All securities held in a single entity, rolled up across the +caller's portfolios. """ -type InformationBlockFactSet { - id: String! - structureId: String - periodStart: Date - periodEnd: Date! - - """ - 'report' | 'schedule' | 'custom' | 'disclosure' | 'metric'. Enum closure enforced by the ``public.fact_sets`` CHECK constraint. - """ - factsetType: String! +type Holding { + """Issuing entity ID.""" entityId: String! - """ - Back-pointer to the ``reports`` table while ``report_id`` still lives on facts. Drops out once the retirement migration lands. - """ - reportId: String + """Display name of the entity.""" + entityName: String! + + """Pre-association tenant graph, when set on the securities.""" + sourceGraphId: String + + """One row per security held in this entity.""" + securities: [HoldingSecuritySummary!]! - """ - Scenario axis (the forecast engine). NULL = actuals; non-NULL names the owning forecast block whose parallel universe this set belongs to. - """ - scenarioId: String + """Sum of cost basis across all securities, in dollars.""" + totalCostBasisDollars: Float! """ - Typed ``FactProvenance`` descriptor (discriminated on ``origin``: pivot | schedule | derived | asserted) recording how this FactSet's facts were constructed. Surfaced as JSON, mirroring how mechanics is exposed. Null for pre-feature historical FactSets. + Sum of current value across all securities, in dollars. `null` if any security lacks a mark. """ - provenance: JSON + totalCurrentValueDollars: Float + + """Number of distinct active positions backing these holdings.""" + positionCount: Int! } -""" -Pre-computed rendering projection of an Information Block. +"""One security held by an entity, rolled up across portfolios.""" +type HoldingSecuritySummary { + """Security ID.""" + securityId: String! -Computed server-side at envelope-build time for blocks where rendering -is deterministic (the statement family today; future block types add -their own rendering builders). The frontend's ``BlockView`` -``Rendering`` projection consumes this directly — no client-side -rollup, depth computation, or calculation walk needed. -""" -type InformationBlockRendering { - rows: [InformationBlockRenderingRow!]! - periods: [InformationBlockRenderingPeriod!]! - validation: InformationBlockValidation - unmappedCount: Int! -} + """Display name of the security.""" + securityName: String! -"""One period column in a rendered statement.""" -type InformationBlockRenderingPeriod { - start: Date! - end: Date! - label: String + """Instrument family (e.g. `common_stock`, `warrant`).""" + securityType: String! + + """Total quantity held in `quantity_type` units.""" + quantity: Float! + + """Unit basis (`shares`, `units`, `principal`).""" + quantityType: String! + + """Aggregate cost basis in dollars, summed across all positions.""" + costBasisDollars: Float! """ - True when this column comes from a forecast scenario's FactSet — the machine-readable seam marker (labels also carry a '(forecast)' suffix, but consumers should key styling off this flag, not label parsing). None/absent = an actuals column. + Aggregate current value in dollars, or `null` if any underlying position lacks a mark. """ - forecast: Boolean + currentValueDollars: Float } """ -One row of a server-side rendered statement. +Molecular response shape for portfolio-block operations. -Mirrors :class:`FactRow` from the legacy -:mod:`robosystems.operations.roboledger.reports.fact_grid` but lives at -the API boundary so envelope consumers don't depend on the -fact-grid module. ``values`` is one entry per period column in -:class:`RenderingLite.periods`. +Bundles the portfolio core, its embedded positions, and pre-computed +totals into a single payload — the contract for `create-portfolio-block`, +`update-portfolio-block`, and the read-side `get-portfolio-block`. +Cents-precision values aren't surfaced here; use `PositionResponse` +/ `PortfolioResponse` for those. """ -type InformationBlockRenderingRow { - elementId: String! - elementQname: String - elementName: String! +type PortfolioBlock { + """Portfolio ID (`port_*` ULID).""" + id: ID! - """ - FASB elementsOfFinancialStatements trait identifier — 'asset', 'liability', 'equity', 'revenue', 'expense'. Surfaced so the viewer can color-code or group rows without a follow-up trait lookup. - """ - classification: String - balanceType: String + """Display name.""" + name: String! - """ - Value-domain format family from the element (monetary | ratio | percent | multiple | days | …). Drives per-row value formatting; None falls back to is_monetary on the element. - """ - itemType: String - values: [Float]! + """Free-text description.""" + description: String - """ - Narrative payload for text-block disclosure rows (markdown); numeric rows carry values instead. - """ - textValue: String - isSubtotal: Boolean! - depth: Int! -} + """Free-text strategy classification.""" + strategy: String -""" -Rule projection for the Information Block envelope. + """Date the portfolio was established.""" + inceptionDate: Date -One row per ``public.rules`` entry scoped to this block. The rule -engine consumes ``rule_expression`` + ``rule_variables`` to evaluate -against the in-scope fact set; the envelope surfaces the rules so -the UI can render them as a checklist alongside any persisted -verification results. -""" -type InformationBlockRule { - id: String! + """ISO 4217 currency code for portfolio aggregates.""" + baseCurrency: String! - """ - One of 8 cm:VerificationRule subclasses — AutomatedAccountingAndReportingChecks, FundamentalAccountingConceptRelation, PeerConsistencyRule, PriorPeriodConsistencyRule, ReportLevelModelStructureRule, ReportingSystemSpecificRule, ToDoManualTask, XBRLTechnicalSyntaxRule. - """ - ruleCategory: String! + """Embedded owning entity, when set. `null` for unattributed portfolios.""" + owner: EntityLite """ - Arithmetic / logical pattern evaluated over fact values. One of 11 cm:BusinessRulePattern mechanisms — Adjustment, CoExists, EqualTo, Exists, GreaterThan, GreaterThanOrEqualToZero, LessThan, RollForward, RollUp, SumEquals, Variance. Null when the rule is a structural check (see rule_check_kind). + All positions in this portfolio, including disposed ones (filter by `status` for active-only display). """ - rulePattern: String + positions: [PositionBlock!]! - """ - Model-structure check kind evaluated over the association graph. One of 6 kinds — LeafHasClassification, LibraryOriginImmutability, NoCycles, NoOrphanArcs, ParentBeforeChild, UniqueQNameInTaxonomy. Null when the rule is an arithmetic pattern (see rule_pattern). Exactly one of rule_pattern / rule_check_kind is non-null per rule. - """ - ruleCheckKind: String - ruleExpression: String! - ruleTarget: InformationBlockRuleTarget - ruleVariables: [InformationBlockRuleVariable!]! - ruleMessage: String + """Sum of `cost_basis_dollars` across every position.""" + totalCostBasisDollars: Float! """ - Failure severity — 'info' | 'warning' | 'error'. Enum closure enforced by the ``public.rules`` CHECK constraint. + Sum of `current_value_dollars` across every position. `null` when any active position lacks a mark. """ - ruleSeverity: String! + totalCurrentValueDollars: Float - """ - Provenance — 'forked' (from an upstream artifact, e.g. Seattle Method) or 'native' (authored in this seed or by a tenant). Enum closure enforced by the ``public.rules`` CHECK constraint. - """ - ruleOrigin: String! -} + """Count of positions with `status='active'`.""" + activePositionCount: Int! -"""Polymorphic rule target — points at the atom the rule is scoped to.""" -type InformationBlockRuleTarget { - """ - Which atom type the rule targets — 'structure' | 'element' | 'association' | 'taxonomy'. Enum closure enforced by the ``public.rules`` CHECK constraint. - """ - targetKind: String! + """Row creation timestamp (UTC).""" + createdAt: DateTime! - """ - UUID of the target atom — structure_id, element_id, association_id, or taxonomy_id depending on ``target_kind``. - """ - targetRefId: String! + """Last-modified timestamp (UTC).""" + updatedAt: DateTime! } -"""`$Variable` → concept qname binding for a rule expression.""" -type InformationBlockRuleVariable { - """Local name in the rule expression, e.g. 'Assets'.""" - variableName: String! +""" +Lightweight entity projection for embedding in portfolio-block / +position envelopes. Carries identity-only fields; full entity data +lives behind the Master Data entity APIs. +""" +type EntityLite { + """Entity ID (`ent_*` ULID).""" + id: ID! - """ - Concept qname the variable resolves to, e.g. 'fac:Assets'. Null for tenant CoA elements (which key on `code`/`element_id`, not qname) — in that case the binding is carried by `variable_element_id`. - """ - variableQname: String + """Display name of the entity.""" + name: String! """ - Element id the variable binds to directly. Set for schedule SumEquals rules over CoA-debit elements that have no qname; null otherwise. + Tenant graph this entity is anchored to, when known. `null` for entities not yet linked to a graph. """ - variableElementId: String + sourceGraphId: String } """ -Outcome of guard-rail validation on a rendered statement. - -Distinct from :class:`VerificationResultLite` (which surfaces the -rule-engine outcomes from ``public.verification_results``). This lite -type carries the synchronous guard-rail checks computed at -envelope-build time — accounting equation, totals foot, etc. -""" -type InformationBlockValidation { - passed: Boolean! - checks: [String!]! - failures: [String!]! - warnings: [String!]! -} +Position projection embedded inside a `PortfolioBlockEnvelope`. +Pre-converts cents fields to dollars (`cost_basis_dollars`, +`current_value_dollars`) for display; the cents-precision fields +live on the standalone `PositionResponse`. Embeds a `SecurityLite` +so callers can render the security name without a follow-up fetch. """ -Pass/fail/skip counts for one ``rule_category`` within a block's -verification results. +type PositionBlock { + """Position ID (`pos_*` ULID).""" + id: ID! -Drives the per-category accordions in the Verification Results panel. -``category`` is the rule's ``rule_category`` -(one of the cm:VerificationRule subclasses), resolved by joining each -result to its Rule. -""" -type InformationBlockVerificationCategorySummary { - category: String! - total: Int! - passed: Int! - failed: Int! - errored: Int! - skipped: Int! -} + """Quantity held in `quantity_type` units.""" + quantity: Float! -""" -Persisted outcome of one Rule evaluation. + """Unit basis (`shares`, `units`, `principal`).""" + quantityType: String! -One row per ``public.verification_results`` entry the rule engine -writes. The envelope surfaces them so the block viewer's -"Verification Results" tab and MCP ``list-verification-failures`` -tool can render + aggregate without a second round-trip. -""" -type InformationBlockVerificationResult { - id: String! - ruleId: String! - structureId: String - factSetId: String + """Cost basis in dollars (pre-converted from cents).""" + costBasisDollars: Float! """ - 'pass' | 'fail' | 'error' | 'skipped'. Enum closure enforced by the ``public.verification_results`` CHECK constraint. + Latest mark-to-market value in dollars. `null` when the position has not been marked. """ - status: String! - message: String - periodStart: Date - periodEnd: Date - evaluatedAt: DateTime -} + currentValueDollars: Float + + """Date the current value was sourced.""" + valuationDate: Date + + """Free-text source attribution for the valuation.""" + valuationSource: String + + """Date the position was acquired.""" + acquisitionDate: Date + + """ + Lifecycle state (`active`, `disposed`, `archived`). See `PositionResponse.status` for the full vocabulary. + """ + status: String! -""" -Server-computed aggregate of a block's ``verification_results``. + """Free-text notes attached to the position.""" + notes: String -Overall counts plus a per-``rule_category`` breakdown, so the viewer -renders the grouped Verification Results panel -without a client-side results→rules join. Status closure is -``pass | fail | error | skipped`` (the ``public.verification_results`` -CHECK); ``total`` is their sum. -""" -type InformationBlockVerificationSummary { - total: Int! - passed: Int! - failed: Int! - errored: Int! - skipped: Int! - byCategory: [InformationBlockVerificationCategorySummary!]! + """Embedded security details — name, type, issuer.""" + security: SecurityLite! } """ -Charlie's six ``type-of View`` arms, surfaced at the envelope boundary. +Lightweight security projection for embedding in position +envelopes. Skips `terms`, `outstanding_shares`, etc. — fetch the +full `SecurityResponse` when those are needed. +""" +type SecurityLite { + """Security ID (`sec_*` ULID).""" + id: ID! -Each projection is computed server-side at envelope-build time when -its source data is available. The frontend's ``BlockView`` dispatcher -routes to the projection component matching the user's selected view -mode; missing projections (those still in backlog) render as empty -states without breaking the dispatcher. + """Display name of the security.""" + name: String! -Today: ``rendering`` is computed for the statement family, and -``chart`` (the 7th arm — panel/series config over the rendering's -rows and periods) for metric blocks. -Other arms (``fact_table``, ``model_structure``, ``verification_results``, -``report_elements``, ``business_rules``) come online as their backend -support lands; ``fact_table`` is trivially derivable from -``InformationBlockEnvelope.facts`` and may stay as a frontend-only -projection. -""" -type InformationBlockViewProjections { - rendering: InformationBlockRendering - chart: InformationBlockChart -} + """Instrument family (e.g. `common_stock`, `preferred_stock`, `warrant`).""" + securityType: String! -"""The block's intrinsic shape — concept + member arrangement patterns.""" -type InformationModel { - """ - roll_up | roll_forward | variance | adjustment | set | arithmetic | textblock. Null for block types where the concept arrangement is implicit in their mechanics. - """ - conceptArrangement: String + """Optional subtype refinement (e.g. `class_a`).""" + securitySubtype: String + + """`true` when the security is in active status.""" + isActive: Boolean! """ - is_a | whole_part | nested_whole_part | two_dimension_aggregation | complex_aggregating_whole_part, or null if non-hypercube. + Embedded issuer entity, when one is linked. `null` for pre-issuer securities. """ - memberArrangement: String -} + issuer: EntityLite -""" -The `JSON` scalar type represents JSON values as specified by [ECMA-404](https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf). -""" -scalar JSON @specifiedBy(url: "https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf") + """Tenant graph the security is pre-associated to, if any.""" + sourceGraphId: String +} """ Entity details from the extensions OLTP database. @@ -1072,82 +554,235 @@ type LedgerEntity { updatedAt: String } +type Agent { + id: String! + agentType: String! + name: String! + legalName: String + taxId: String + registrationNumber: String + duns: String + lei: String + email: String + phone: String + address: JSON + source: String! + externalId: String + isActive: Boolean! + is1099Recipient: Boolean! + createdAt: DateTime + updatedAt: DateTime + createdBy: String + openReceivable: OpenBalanceByAgent + openPayable: OpenBalanceByAgent +} + """ -A journal entry — accounting interpretation of a transaction. +Per-agent open balance row. -Each transaction has 1+ entries; each entry has 2+ line items that -must balance. ``status`` is the draft/posted/reversed lifecycle; -``type`` is the entry classification ('standard' | 'adjusting' | -'closing' | 'reversing'). +Used for both the aging-by-counterparty list and the per-Agent +GraphQL field. ``open_balance_cents`` reflects only the unsettled +remainder (sum of originating amounts minus sum of discharges) so +partial-payment chains net out correctly. """ -type LedgerEntry { +type OpenBalanceByAgent { + agentId: String! + + """ + Unsettled originating-event amounts minus discharges, in minor currency units. Positive for normal AR/AP; negative when overpaid. + """ + openBalanceCents: Int! + + """Number of originating events with nonzero balance for this agent.""" + openEventCount: Int! + currency: String! +} + +""" +Graph-wide open AR or open AP aggregate. + +``total_open_cents`` is the sum of unsettled originating-event +balances; ``counterparty_count`` is the number of distinct agents +with at least one open invoice or bill. The currency is uniform +per graph today (mixed-currency books would need a per-currency +breakdown — out of scope for v1). +""" +type OpenBalanceAggregate { + """Sum of unsettled balances in minor currency units.""" + totalOpenCents: Int! + + """Distinct agents with at least one open balance.""" + counterpartyCount: Int! + + """Distinct originating events with a nonzero open balance.""" + openEventCount: Int! + + """Currency code (uniform per graph).""" + currency: String! +} + +type EventBlock { id: String! - number: String - type: String! - postingDate: Date! - memo: String + eventType: String! + eventCategory: String! + eventClass: String! status: String! - postedAt: DateTime - lineItems: [LedgerLineItem!]! + occurredAt: DateTime! + effectiveAt: DateTime + source: String! + externalId: String + externalUrl: String + amount: Int + currency: String! + description: String + metadata: JSON! + dimensionIds: [String!]! + agentId: String + resourceType: String + resourceElementId: String + replacedByEventId: String + replacesEventId: String + obligatedByEventId: String + dischargesEventId: String + createdAt: DateTime! + createdBy: String! +} + +""" +High-level rollup of a graph's ledger state — counts plus the +date-range bookends and integration sync timestamp. + +Used by dashboards and the onboarding wizard to answer "is this +graph populated yet?" without walking every transaction. ``connection_count`` +reflects active integrations (QuickBooks / Plaid / etc.); a non-null +``last_sync_at`` means at least one connection has run. +""" +type LedgerSummary { + graphId: String! + accountCount: Int! + transactionCount: Int! + entryCount: Int! + lineItemCount: Int! + earliestTransactionDate: Date + latestTransactionDate: Date + connectionCount: Int! + lastSyncAt: DateTime +} + +""" +Paginated chart-of-accounts listing — flat (use the tree endpoint +for parent/child structure). +""" +type AccountList { + accounts: [Account!]! + pagination: PaginationInfo! +} + +""" +One CoA account (Element) — the basic chart-of-accounts row. + +``trait`` carries the FASB classification (asset/liability/equity/ +revenue/expense/etc.); ``balance_type`` is the natural side +('debit' or 'credit'). ``account_type`` is a free-form sub-grouping +(e.g. 'cash', 'inventory') used by some integrations. Hierarchy is +expressed via ``parent_id`` + ``depth``. +""" +type Account { + id: String! + code: String + name: String! + description: String + trait: String + subClassification: String + balanceType: String! + parentId: String + depth: Int! + currency: String! + isActive: Boolean! + isPlaceholder: Boolean! + accountType: String + externalId: String + externalSource: String +} + +type AccountTree { + roots: [AccountTreeNode!]! + totalAccounts: Int! +} + +type AccountTreeNode { + id: ID! + code: String + name: String! + trait: String + accountType: String + balanceType: String! + depth: Int! + isActive: Boolean! + children: [AccountTreeNode!]! +} + +""" +Mapping rendered as account rollups — every reporting concept the +mapping defines, with the CoA accounts that contribute to it and the +current balance for each. ``total_unmapped`` tracks gaps for UI. +""" +type AccountRollups { + mappingId: String! + mappingName: String! + groups: [AccountRollupGroup!]! + totalMapped: Int! + totalUnmapped: Int! } """ -One debit/credit line within a journal entry. Always exactly one -side has a non-zero amount. +All CoA accounts that roll up into a single reporting concept, +with the group total and per-account contributions. """ -type LedgerLineItem { - id: String! - accountId: String! - accountName: String +type AccountRollupGroup { + reportingElementId: String! + reportingName: String! + reportingQname: String! + trait: String! + balanceType: String! + total: Float! + accounts: [AccountRollupRow!]! +} + +"""One CoA account contributing to a reporting concept's rollup.""" +type AccountRollupRow { + elementId: String! + accountName: String! accountCode: String - debitAmount: Float! - creditAmount: Float! - description: String - lineOrder: Int! + totalDebits: Float! + totalCredits: Float! + netBalance: Float! } """ -High-level rollup of a graph's ledger state — counts plus the -date-range bookends and integration sync timestamp. +Trial balance for posted entries in a date range — every CoA +account that had activity, plus aggregate totals. -Used by dashboards and the onboarding wizard to answer "is this -graph populated yet?" without walking every transaction. ``connection_count`` -reflects active integrations (QuickBooks / Plaid / etc.); a non-null -``last_sync_at`` means at least one connection has run. +Ledger is balanced when ``total_debits == total_credits``. Used as +a sanity check before close-period; failure means an unposted / +malformed entry slipped through. """ -type LedgerSummary { - graphId: String! - accountCount: Int! - transactionCount: Int! - entryCount: Int! - lineItemCount: Int! - earliestTransactionDate: Date - latestTransactionDate: Date - connectionCount: Int! - lastSyncAt: DateTime +type TrialBalance { + rows: [TrialBalanceRow!]! + totalDebits: Float! + totalCredits: Float! } -""" -Full transaction detail — header + every journal entry + every -line item underneath. Used by the transaction detail page. -""" -type LedgerTransactionDetail { - id: String! - number: String - type: String! - category: String - amount: Float! - currency: String! - date: Date! - dueDate: Date - merchantName: String - referenceNumber: String - description: String - source: String! - sourceId: String - status: String! - postedAt: DateTime - entries: [LedgerEntry!]! +"""One CoA account's debit/credit totals over the trial-balance window.""" +type TrialBalanceRow { + accountId: String! + accountCode: String! + accountName: String! + trait: String + accountType: String + totalDebits: Float! + totalCredits: Float! + netBalance: Float! } """Paginated transaction listing — header view.""" @@ -1180,189 +815,236 @@ type LedgerTransactionSummary { status: String! } -"""An arc between two library elements (parent-child, equivalence, etc).""" -type LibraryAssociation { - id: String! - structureId: String! - structureName: String - fromElementId: String! - fromElementQname: String - fromElementName: String - - """Primary elementsOfFinancialStatements trait (for node coloring)""" - fromElementTrait: String - fromElementIsAbstract: Boolean - toElementId: String! - toElementQname: String - toElementName: String - - """Primary elementsOfFinancialStatements trait (for node coloring)""" - toElementTrait: String - toElementIsAbstract: Boolean - - """ - presentation | calculation | mapping | equivalence | general-special | essence-alias - """ - associationType: String! - arcrole: String - orderValue: Float - weight: Float -} - -"""A library element (concept, abstract, axis, member, or hypercube).""" -type LibraryElement { +""" +Full transaction detail — header + every journal entry + every +line item underneath. Used by the transaction detail page. +""" +type LedgerTransactionDetail { id: String! - - """Qualified name, e.g. 'fac:Assets'""" - qname: String! - namespace: String - name: String! - - """ - FASB elementsOfFinancialStatements axis: asset | contraAsset | liability | contraLiability | equity | contraEquity | temporaryEquity | revenue | expense | expenseReversal | gain | loss | comprehensiveIncome | investmentByOwners | distributionToOwners | metric (derived subtotals, not SFAC 6 primary elements). Null for structural rows. - """ - trait: String - - """debit | credit""" - balanceType: String! - - """instant | duration""" - periodType: String! - isAbstract: Boolean! - isMonetary: Boolean! - - """concept | abstract | axis | member | hypercube""" - elementType: String! - - """fac | us-gaap | rs-gaap | …""" + number: String + type: String! + category: String + amount: Float! + currency: String! + date: Date! + dueDate: Date + merchantName: String + referenceNumber: String + description: String source: String! - taxonomyId: String - parentId: String - labels: [LibraryLabel!]! - references: [LibraryReference!]! + sourceId: String + status: String! + postedAt: DateTime + entries: [LedgerEntry!]! } """ -A mapping arc involving a specific element. - -Flat row view: one arc, oriented from the perspective of the element -being inspected. `peer` is the other end; `direction` says whether -this element is the source ('outgoing') or the target ('incoming'). +A journal entry — accounting interpretation of a transaction. -Scoped to arcs whose structure belongs to a `taxonomy_type='mapping'` -taxonomy — the cross-taxonomy bridges (equivalence, general-special, -rs-gaap-type-subtype). Hierarchical arcs inside a single reporting taxonomy -are out of scope. +Each transaction has 1+ entries; each entry has 2+ line items that +must balance. ``status`` is the draft/posted/reversed lifecycle; +``type`` is the entry classification ('standard' | 'adjusting' | +'closing' | 'reversing'). """ -type LibraryElementArc { +type LedgerEntry { id: String! - - """'outgoing' (this element is source) | 'incoming' (target)""" - direction: String! - associationType: String! - arcrole: String - taxonomyId: String - taxonomyStandard: String - taxonomyName: String - structureId: String - structureName: String - peer: LibraryElement! + number: String + type: String! + postingDate: Date! + memo: String + status: String! + postedAt: DateTime + lineItems: [LedgerLineItem!]! } """ -One FASB metamodel trait assigned to a library element. - -A single element can carry multiple traits across multiple categories -(e.g. elementsOfFinancialStatements=expense AND -operatingNonoperating=operating AND liquidity=current). +One debit/credit line within a journal entry. Always exactly one +side has a non-zero amount. """ -type LibraryElementClassification { - """Trait axis (e.g. elementsOfFinancialStatements)""" - category: String! - - """Value within the axis (e.g. expense)""" - identifier: String! - - """Human-readable name""" - name: String +type LedgerLineItem { + id: String! + accountId: String! + accountName: String + accountCode: String + debitAmount: Float! + creditAmount: Float! + description: String + lineOrder: Int! +} - """True for the element's primary EFS trait assignment""" - isPrimary: Boolean! +"""Flat list of taxonomy headers. Used by the catalog/picker UIs.""" +type TaxonomyList { + taxonomies: [Taxonomy!]! } -type LibraryElementTreeNode { - element: LibraryElement! - children: [LibraryElementTreeNode!]! +""" +One taxonomy header — identity + lifecycle flags. Atoms +(elements, structures, associations, rules) are exposed via the +Taxonomy Block envelope. + +``taxonomy_type`` discriminates: ``chart_of_accounts``, +``reporting_standard``, ``reporting_extension``, ``custom_ontology``, +``mapping``, ``schedule``. ``is_locked=True`` means library-origin +(immutable for tenants); ``is_shared=True`` means visible to multiple +graphs from a shared registry. +""" +type Taxonomy { + id: String! + name: String! + description: String + taxonomyType: String! + version: String + standard: String + namespaceUri: String + isShared: Boolean! + isActive: Boolean! + isLocked: Boolean! + sourceTaxonomyId: String + targetTaxonomyId: String } -""" -An element and its equivalence peers. - -Answers "what other concepts mean the same thing as this one" — the -FAC→us-gaap collapse pattern rendered as an API shape. -""" -type LibraryEquivalence { - element: LibraryElement! - equivalents: [LibraryElement!]! +"""Paginated element listing with taxonomy context.""" +type ElementList { + elements: [Element!]! + pagination: PaginationInfo! } -"""A label on a library element.""" -type LibraryLabel { - """Label role: standard/documentation/verbose/…""" - role: String! - - """Language code""" - language: String! - - """Label text""" - text: String! +"""Element with taxonomy context — extends AccountResponse.""" +type Element { + id: String! + code: String + name: String! + description: String + qname: String + namespace: String + trait: String + subClassification: String + balanceType: String! + periodType: String! + isAbstract: Boolean! + elementType: String! + source: String! + taxonomyId: String + parentId: String + depth: Int! + isActive: Boolean! + externalId: String + externalSource: String } -"""A cross-reference on a library element (FASB ASC, SEC, etc).""" -type LibraryReference { - """'ASC' | 'SEC' | 'SFAC' | 'IFRS' | 'Other'""" - refType: String +"""An element not yet mapped to the reporting taxonomy.""" +type UnmappedElement { + id: String! + code: String + name: String! + trait: String + liquidity: String + balanceType: String! + externalSource: String + suggestedTargets: [SuggestedTarget!]! +} - """Full citation text""" - citation: String! +"""A suggested mapping target from the reporting taxonomy.""" +type SuggestedTarget { + elementId: String! + qname: String! + name: String! + confidence: Float +} - """Dereferenceable URL if available""" - uri: String +"""Flat list of structures within a taxonomy.""" +type StructureList { + structures: [Structure!]! } -"""A named structure (extended link role) within a library taxonomy.""" -type LibraryStructure { +""" +One structure header — a renderable section within a taxonomy +(balance sheet, income statement, schedule, etc.). + +``block_type`` drives presentation: 'balance_sheet', +'income_statement', 'cash_flow_statement', 'equity_statement', +'schedule', 'chart_of_accounts', 'coa_mapping', 'rollforward', etc. +""" +type Structure { id: String! name: String! - - """balance_sheet | income_statement | cash_flow_statement | custom | …""" + description: String blockType: String! taxonomyId: String! - - """Original XBRL role URI if any""" - roleUri: String isActive: Boolean! } -"""A library taxonomy (fac, us-gaap, rs-gaap, …).""" -type LibraryTaxonomy { +"""A mapping structure with all its associations.""" +type MappingDetail { id: String! name: String! - description: String + blockType: String! + taxonomyId: String! + associations: [Association!]! + totalAssociations: Int! +} - """fac | us-gaap | rs-gaap | ifrs""" - standard: String - version: String - namespaceUri: String +""" +One edge between two elements within a structure (parent/child +presentation, calculation rollup, mapping, equivalence). - """chart_of_accounts | reporting | mapping | schedule""" - taxonomyType: String! - isShared: Boolean! - isActive: Boolean! - isLocked: Boolean! +``association_type`` discriminates the edge semantics. Mapping edges +are the user-facing path (CoA → reporting concept); presentation / +calculation edges express structure layout and roll-ups. +``confidence`` is set on AI-suggested mappings (≥0.90 auto-approved, +0.70-0.89 flagged for review). +""" +type Association { + id: String! + structureId: String! + fromElementId: String! + fromElementName: String + fromElementQname: String + toElementId: String! + toElementName: String + toElementQname: String + associationType: String! + orderValue: Float + weight: Float + confidence: Float + suggestedBy: String + approvedBy: String +} - """Total elements in this taxonomy (computed on demand)""" - elementCount: Int +"""Coverage stats for a mapping.""" +type MappingCoverage { + mappingId: String! + totalCoaElements: Int! + mappedCount: Int! + unmappedCount: Int! + coveragePercent: Float! + highConfidence: Int! + mediumConfidence: Int! + lowConfidence: Int! + unreachableCount: Int! + unreachable: [UnreachableMappingType!]! +} + +""" +A CoA→rs-gaap mapping whose target doesn't reach a Network root. + +The reporting layer is closed: every mapping target must trace upward +through the rs-gaap calc DAG to one of the canonical roots +(rs-gaap:Assets, rs-gaap:LiabilitiesAndStockholdersEquity, +rs-gaap:NetIncomeLoss, rs-gaap:CashAndCashEquivalentsPeriodIncreaseDecrease). +When it doesn't, the fact will land on a dead branch — visible in the +trial balance but invisible to any rendered statement. Surfacing +these as defects lets operators fix the mapping before the report is +filed. +""" +type UnreachableMappingType { + coaElementId: String! + coaQname: String + coaCode: String + coaName: String + targetElementId: String! + targetQname: String + targetName: String } """ @@ -1385,87 +1067,96 @@ type MappedTrialBalanceRow { netBalance: Float! } -"""Coverage stats for a mapping.""" -type MappingCoverage { - mappingId: String! - totalCoaElements: Int! - mappedCount: Int! - unmappedCount: Int! - coveragePercent: Float! - highConfidence: Int! - mediumConfidence: Int! - lowConfidence: Int! - unreachableCount: Int! - unreachable: [UnreachableMappingType!]! -} +""" +Period-close dashboard view — every schedule in scope for the +period plus drafted/posted entry totals. -"""A mapping structure with all its associations.""" -type MappingDetail { - id: String! - name: String! - blockType: String! - taxonomyId: String! - associations: [Association!]! - totalAssociations: Int! +Use to drive the close-period UI: schedules with ``status='draft'`` +are pending close; ``period_status`` reflects the calendar's lock +state for the period. +""" +type PeriodCloseStatus { + fiscalPeriodStart: Date! + fiscalPeriodEnd: Date! + periodStatus: String! + schedules: [PeriodCloseItem!]! + totalDraft: Int! + totalPosted: Int! } """ -Graph-wide open AR or open AP aggregate. +One schedule's contribution to a period close — drafted closing +entry plus its reversal (when ``auto_reverse=True``). -``total_open_cents`` is the sum of unsettled originating-event -balances; ``counterparty_count`` is the number of distinct agents -with at least one open invoice or bill. The currency is uniform -per graph today (mixed-currency books would need a per-currency -breakdown — out of scope for v1). +``status`` is the closing entry's draft/posted lifecycle. The +reversal mirrors the same shape with ``reversal_*`` fields. """ -type OpenBalanceAggregate { - """Sum of unsettled balances in minor currency units.""" - totalOpenCents: Int! +type PeriodCloseItem { + structureId: String! + structureName: String! + amount: Float! + status: String! + entryId: String + reversalEntryId: String + reversalStatus: String +} - """Distinct agents with at least one open balance.""" - counterpartyCount: Int! +"""Current fiscal calendar state for a graph.""" +type FiscalCalendar { + graphId: String! + fiscalYearStartMonth: Int! - """Distinct originating events with a nonzero open balance.""" - openEventCount: Int! + """Latest closed period (YYYY-MM), or null if nothing closed""" + closedThrough: String - """Currency code (uniform per graph).""" - currency: String! -} + """Target period the user wants closed through (YYYY-MM)""" + closeTarget: String -""" -Per-agent open balance row. + """ + Number of periods between closed_through and close_target (inclusive of close_target). 0 means caught up. + """ + gapPeriods: Int! + + """Ordered list of periods that a close run would process""" + catchUpSequence: [String!]! + + """ + Whether the next period in the catch-up sequence passes all closeable gates + """ + closeableNow: Boolean! -Used for both the aging-by-counterparty list and the per-Agent -GraphQL field. ``open_balance_cents`` reflects only the unsettled -remainder (sum of originating amounts minus sum of discharges) so -partial-payment chains net out correctly. -""" -type OpenBalanceByAgent { - agentId: String! + """ + Structured blocker codes when closeable_now is False: 'sequence_violation', 'period_incomplete', 'sync_stale', 'calendar_not_initialized', 'period_already_closed', 'pending_obligations' + """ + blockers: [String!]! """ - Unsettled originating-event amounts minus discharges, in minor currency units. Positive for normal AR/AP; negative when overpaid. + Number of pending schedule_entry_due events blocking close. Non-zero only when `pending_obligations` is in `blockers`. """ - openBalanceCents: Int! + pendingObligationCount: Int! - """Number of originating events with nonzero balance for this agent.""" - openEventCount: Int! - currency: String! -} + """ + Sample of up to 5 pending obligations (schedule_id, schedule_name, period, event_id) ordered by occurred_at. Use `list-event-blocks` with event_type=schedule_entry_due&status=pending for the full set. + """ + pendingObligationSample: [PendingObligationDetail!]! -"""Pagination information for list responses.""" -type PaginationInfo { - """Total number of items available""" - total: Int! + """ + Earliest period (YYYY-MM) with a pending obligation blocking close. Null when no pending_obligations blocker is active. + """ + earliestPendingPeriod: String - """Maximum number of items returned in this response""" - limit: Int! + """ + Days the most recent sync is stale relative to the period to close. Populated only when `sync_stale` is in `blockers` and last_sync_at exists (null when there's a connection but no sync has ever run). + """ + syncStaleDays: Int + lastCloseAt: DateTime + initializedAt: DateTime - """Number of items skipped""" - offset: Int! + """Most recent QB sync timestamp (if connected)""" + lastSyncAt: DateTime - """Whether more items are available""" - hasMore: Boolean! + """Fiscal period rows for this graph""" + periods: [FiscalPeriodSummary!]! } """ @@ -1484,37 +1175,21 @@ type PendingObligationDetail { } """ -One schedule's contribution to a period close — drafted closing -entry plus its reversal (when ``auto_reverse=True``). - -``status`` is the closing entry's draft/posted lifecycle. The -reversal mirrors the same shape with ``reversal_*`` fields. -""" -type PeriodCloseItem { - structureId: String! - structureName: String! - amount: Float! - status: String! - entryId: String - reversalEntryId: String - reversalStatus: String -} +One fiscal period row — header view used in calendar listings. +Status lifecycle: ``open`` → ``closing`` → ``closed``. ``closing`` +is the transient state during a close run; ``closed_at`` stamps when +the lock landed. """ -Period-close dashboard view — every schedule in scope for the -period plus drafted/posted entry totals. +type FiscalPeriodSummary { + """Period name (YYYY-MM)""" + name: String! + startDate: Date! + endDate: Date! -Use to drive the close-period UI: schedules with ``status='draft'`` -are pending close; ``period_status`` reflects the calendar's lock -state for the period. -""" -type PeriodCloseStatus { - fiscalPeriodStart: Date! - fiscalPeriodEnd: Date! - periodStatus: String! - schedules: [PeriodCloseItem!]! - totalDraft: Int! - totalPosted: Int! + """'open' | 'closing' | 'closed'""" + status: String! + closedAt: DateTime } """All draft entries for a fiscal period, ready for review before close.""" @@ -1552,499 +1227,757 @@ type PeriodDrafts { drafts: [DraftEntry!]! } +"""A single draft entry with full line item detail for review.""" +type DraftEntry { + entryId: String! + postingDate: Date! + + """Entry type (e.g., 'closing', 'adjusting')""" + type: String! + memo: String + + """ + Where the entry came from (ENTRY_PROVENANCE_VALUES): source_sync, ai_generated, manual_entry, schedule_derived, system_computed, event_handler + """ + provenance: String + + """Schedule structure that generated this entry (if any)""" + sourceStructureId: String + + """Human-readable name of the source schedule""" + sourceStructureName: String + lineItems: [DraftLineItem!]! + + """Sum of debit amounts in cents""" + totalDebit: Int! + + """Sum of credit amounts in cents""" + totalCredit: Int! + + """True if total_debit == total_credit""" + balanced: Boolean! + + """ + True if closing the period will publish this draft to QuickBooks — i.e. the graph has a qb_authoritative/hybrid QB connection AND this is an RL-originated draft (schedule/manual) not already in QB. False means it posts locally only. + """ + willPublishToQb: Boolean! +} + +"""A single line item within a draft entry.""" +type DraftLineItem { + lineItemId: String! + elementId: String! + elementCode: String + elementName: String! + + """Debit amount in cents""" + debitAmount: Int! + + """Credit amount in cents""" + creditAmount: Int! + description: String +} + """ -A single reporting period column. +The closing book navigation tree — categories + items the UI +uses to render the period-close workspace. ``has_data=False`` when +the graph has no posted entries yet. +""" +type ClosingBookStructures { + categories: [ClosingBookCategory!]! + hasData: Boolean! +} -Reports render facts in N period columns side-by-side. Each -``PeriodSpec`` is one column — its ``start``/``end`` define the -window the report's facts roll up into; ``label`` is what the renderer -prints in the column header. For year-over-year statements, supply two -PeriodSpecs (current + comparative); for YTD by quarter, supply four. """ -type PeriodSpec { - """Period start date (inclusive). Window the column rolls up.""" - start: Date! +A grouping of closing-book items shown as a sidebar section +(e.g. Statements, Account Rollups, Schedules, Period Close). +""" +type ClosingBookCategory { + label: String! + items: [ClosingBookItem!]! +} - """Period end date (inclusive). Window the column rolls up.""" - end: Date! +""" +One row in the closing book — a navigable artifact for the +period (statement, schedule, rollup, etc.). - """Column header label (e.g. 'FY2025 Q3', '2024', 'YTD').""" - label: String! +``item_type`` discriminates: 'statement', 'schedule', +'account_rollups', 'period_close', 'trial_balance'. Statement items +carry ``report_id`` to fetch the rendered facts; schedule items +carry ``status`` ('complete' | 'draft' | 'pending'). +""" +type ClosingBookItem { + id: String! + name: String! + itemType: String! + blockType: String + reportId: String + status: String +} + +"""List of report header summaries (used by report list reads).""" +type ReportList { + """Report definitions, newest first.""" + reports: [Report!]! } """ -Read projection for a single portfolio — core fields only. +Report definition summary — header metadata, no facts. -Position-level holdings live on the dedicated portfolio-block envelope -(`PortfolioBlockEnvelope`) returned by molecular operations. +Returned by ``create-report``, ``regenerate-report``, +``file-report``, and ``transition-filing-status``. Use the package +read endpoint to retrieve a Report rehydrated with its rendered +``InformationBlockEnvelope`` items. """ -type Portfolio { - """Portfolio ID (`port_*` ULID).""" +type Report { + """Report identifier (ULID).""" id: String! - """Display name.""" + """Human-readable report name.""" name: String! - """Free-text description.""" - description: String + """Taxonomy this report renders against.""" + taxonomyId: String! """ - Free-text strategy classification (e.g. `value`, `growth`, `income`). Open vocabulary. + Computation lifecycle: `generating`, `published`, `failed`. Orthogonal to `filing_status`. """ - strategy: String + generationStatus: String! - """Date the portfolio was established (YYYY-MM-DD).""" - inceptionDate: Date + """Period cadence: `monthly`, `quarterly`, `annual`.""" + periodType: String! - """ISO 4217 currency code used for portfolio-level aggregates.""" - baseCurrency: String! + """Current-period start.""" + periodStart: Date - """Row creation timestamp (UTC).""" - createdAt: DateTime! + """Current-period end.""" + periodEnd: Date - """Last-modified timestamp (UTC).""" - updatedAt: DateTime! -} + """True when an auto-generated prior-period column is included.""" + comparative: Boolean! -""" -Molecular response shape for portfolio-block operations. + """ + Explicit period columns when the report was created with a multi-period layout. + """ + periods: [PeriodSpec!] -Bundles the portfolio core, its embedded positions, and pre-computed -totals into a single payload — the contract for `create-portfolio-block`, -`update-portfolio-block`, and the read-side `get-portfolio-block`. -Cents-precision values aren't surfaced here; use `PositionResponse` -/ `PortfolioResponse` for those. -""" -type PortfolioBlock { - """Portfolio ID (`port_*` ULID).""" - id: ID! + """CoA → taxonomy mapping the facts were rolled up through.""" + mappingId: String - """Display name.""" - name: String! + """True when the report was created by an AI agent rather than a user.""" + aiGenerated: Boolean! - """Free-text description.""" - description: String + """When the report row was created.""" + createdAt: DateTime! + + """When the facts were last (re)generated.""" + lastGenerated: DateTime - """Free-text strategy classification.""" - strategy: String + """ + Structures available for this report's taxonomy — renderable sections (BS / IS / CF / Equity / Schedules). + """ + structures: [StructureSummary!]! - """Date the portfolio was established.""" - inceptionDate: Date + """Display name of the primary entity the report is tagged to.""" + entityName: String - """ISO 4217 currency code for portfolio aggregates.""" - baseCurrency: String! + """ + Filing lifecycle (orthogonal to `generation_status`): `draft`, `under_review`, `filed`, `archived`. + """ + filingStatus: String! - """Embedded owning entity, when set. `null` for unattributed portfolios.""" - owner: EntityLite + """When the report was transitioned to `filed`.""" + filedAt: DateTime + + """User ID that transitioned the report to `filed`.""" + filedBy: String """ - All positions in this portfolio, including disposed ones (filter by `status` for active-only display). + When this report restates an earlier filing, the predecessor's report ID. """ - positions: [PositionBlock!]! + supersedesId: String - """Sum of `cost_basis_dollars` across every position.""" - totalCostBasisDollars: Float! + """When this report has been restated, the successor's report ID.""" + supersededById: String """ - Sum of `current_value_dollars` across every position. `null` when any active position lacks a mark. + Origin graph for received (shared) reports — populated only on the recipient's copy. """ - totalCurrentValueDollars: Float + sourceGraphId: String - """Count of positions with `status='active'`.""" - activePositionCount: Int! + """ + Origin report ID for received (shared) reports — populated only on the recipient's copy. + """ + sourceReportId: String - """Row creation timestamp (UTC).""" - createdAt: DateTime! + """When the report was shared into this graph (recipient side).""" + sharedAt: DateTime - """Last-modified timestamp (UTC).""" - updatedAt: DateTime! + """ + Counts by rule outcome (e.g. `{'passed': 12, 'failed': 1}`) from the most recent evaluation. Null until rules run. + """ + ruleSummary: JSON } -"""Paginated list of portfolios.""" -type PortfolioList { - """Portfolios on this page.""" - portfolios: [Portfolio!]! +""" +A single reporting period column. - """Pagination cursor and totals.""" - pagination: PaginationInfo! +Reports render facts in N period columns side-by-side. Each +``PeriodSpec`` is one column — its ``start``/``end`` define the +window the report's facts roll up into; ``label`` is what the renderer +prints in the column header. For year-over-year statements, supply two +PeriodSpecs (current + comparative); for YTD by quarter, supply four. +""" +type PeriodSpec { + """Period start date (inclusive). Window the column rolls up.""" + start: Date! + + """Period end date (inclusive). Window the column rolls up.""" + end: Date! + + """Column header label (e.g. 'FY2025 Q3', '2024', 'YTD').""" + label: String! } """ -Read projection for a single position. +A structure available within this report's taxonomy. -Pairs cents-precision fields (`cost_basis`, `current_value`) with -pre-computed dollar floats (`*_dollars`) to spare clients the -conversion. The cents fields are authoritative. +Each structure is a renderable section (Balance Sheet, Income +Statement, Cash Flow Statement, Equity, or a Schedule). The Report +row owns the facts; structures are the lenses that project them. """ -type Position { - """Position ID (`pos_*` ULID).""" +type StructureSummary { + """Structure identifier.""" id: String! - """Owning portfolio ID.""" - portfolioId: String! - - """Held security ID.""" - securityId: String! + """Human-readable structure name.""" + name: String! """ - Cached display name of the held security, denormalized for list rendering. May lag the security row's current name briefly. + Structure category: `balance_sheet`, `income_statement`, `cash_flow_statement`, `equity_statement`, `schedule`. """ - securityName: String + blockType: String! +} - """Cached display name of the security's issuing entity.""" +type ReportPackage { + id: ID! + name: String! + description: String + taxonomyId: String! + periodType: String! + periodStart: Date + periodEnd: Date + generationStatus: String! + lastGenerated: DateTime + filingStatus: String! + filedAt: DateTime + filedBy: String + supersedesId: String + supersededById: String + sourceGraphId: String + sourceReportId: String + sharedAt: DateTime entityName: String + aiGenerated: Boolean! + createdAt: DateTime! + createdBy: String! + items: [ReportPackageItem!]! +} - """Quantity held in units defined by `quantity_type`.""" - quantity: Float! - - """Unit basis (`shares`, `units`, `principal`).""" - quantityType: String! +type ReportPackageItem { + factSetId: String! + structureId: String + displayOrder: Int! + block: InformationBlock! +} - """Cost basis in **cents** of `currency`. Authoritative.""" - costBasis: Int! +type InformationBlock { + id: ID! + blockType: String! + name: String! + displayName: String! + category: String! + taxonomyId: String + taxonomyName: String + disclosureId: String + informationModel: InformationModel! + artifact: Artifact! + elements: [InformationBlockElement!]! + connections: [InformationBlockConnection!]! + facts: [InformationBlockFact!]! + rules: [InformationBlockRule!]! + dimensions: [JSON!]! + factSet: InformationBlockFactSet + verificationResults: [InformationBlockVerificationResult!]! + verificationSummary: InformationBlockVerificationSummary + view: InformationBlockViewProjections! +} +"""The block's intrinsic shape — concept + member arrangement patterns.""" +type InformationModel { """ - Cost basis pre-converted to dollars (`cost_basis / 100`). Convenience for display; `cost_basis` is the source of truth. + roll_up | roll_forward | variance | adjustment | set | arithmetic | textblock. Null for block types where the concept arrangement is implicit in their mechanics. """ - costBasisDollars: Float! - - """ISO 4217 currency code for `cost_basis` and `current_value`.""" - currency: String! - - """Latest mark-to-market value in **cents**, or `null` if unmarked.""" - currentValue: Int + conceptArrangement: String """ - Current value in dollars (`current_value / 100`). `null` when `current_value` is null. + is_a | whole_part | nested_whole_part | two_dimension_aggregation | complex_aggregating_whole_part, or null if non-hypercube. """ - currentValueDollars: Float + memberArrangement: String +} - """Date `current_value` was sourced (YYYY-MM-DD).""" - valuationDate: Date +type Artifact { + topic: String + rendererNote: String + template: JSON + mechanics: JSON! +} - """Free-text source attribution for the current valuation.""" - valuationSource: String +""" +Element projection for bundling inside an Information Block envelope. - """Date the position was acquired (YYYY-MM-DD).""" - acquisitionDate: Date +Narrower than :class:`LibraryElementResponse` — excludes the heavy fields +(labels, references, classifications) that library browsing needs but +block consumers don't. Agents + frontends ask for those on demand via +the full library GraphQL fields when they need them. +""" +type InformationBlockElement { + id: String! + qname: String + name: String! + code: String + + """concept | abstract | axis | member | hypercube""" + elementType: String! + isAbstract: Boolean! + isMonetary: Boolean! + balanceType: String + periodType: String """ - Date the position was disposed, if `status='disposed'`. `null` for active positions. + Value-domain vocabulary (monetary | ratio | percent | multiple | days | string | …). None means untyped; fall back to is_monetary. """ - dispositionDate: Date + itemType: String """ - Lifecycle state. One of: `active` (currently held), `disposed` (soft-deleted via `update-portfolio-block` dispose), `archived` (historical record only). + The element's documentation-role label — the catalog's authoritative value semantics (e.g. whether a percent driver is a growth rate or a rate-on-base fraction). None when the element carries no documentation label. """ - status: String! - - """Free-text notes attached to the position.""" - notes: String - - """Row creation timestamp (UTC).""" - createdAt: DateTime! - - """Last-modified timestamp (UTC).""" - updatedAt: DateTime! + documentation: String } """ -Position projection embedded inside a `PortfolioBlockEnvelope`. +Connection (= Association) projection. -Pre-converts cents fields to dollars (`cost_basis_dollars`, -`current_value_dollars`) for display; the cents-precision fields -live on the standalone `PositionResponse`. Embeds a `SecurityLite` -so callers can render the security name without a follow-up fetch. +Renamed at the API boundary to match Charlie's ontology vocabulary. +The underlying storage table is still ``associations``. """ -type PositionBlock { - """Position ID (`pos_*` ULID).""" - id: ID! - - """Quantity held in `quantity_type` units.""" - quantity: Float! - - """Unit basis (`shares`, `units`, `principal`).""" - quantityType: String! +type InformationBlockConnection { + id: String! + fromElementId: String! + toElementId: String! - """Cost basis in dollars (pre-converted from cents).""" - costBasisDollars: Float! + """ + presentation | calculation | mapping | equivalence | general-special | essence-alias + """ + associationType: String! + arcrole: String + orderValue: Float + weight: Float """ - Latest mark-to-market value in dollars. `null` when the position has not been marked. + Association-level classifications — concept_arrangement, member_arrangement, named_disclosure rows from the junction. Empty for library-seeded associations that haven't been classified yet. """ - currentValueDollars: Float + classifications: [InformationBlockClassification!]! +} - """Date the current value was sourced.""" - valuationDate: Date +""" +Classification projection — one row per `association_classifications` +junction entry. - """Free-text source attribution for the valuation.""" - valuationSource: String +Association-side only: concept_arrangement, member_arrangement, +named_disclosure. Element-side FASB metamodel traits (asset, current, +operating, …) live in `TraitLite` via `element_traits`. - """Date the position was acquired.""" - acquisitionDate: Date +Carries enough for the envelope caller to render / filter by category + +identifier without a follow-up lookup. The full `public.classifications` +vocabulary catalog (name / description / metadata) is available via the +library GraphQL surface when callers need the details. +""" +type InformationBlockClassification { + """Classification vocabulary row id.""" + id: String! """ - Lifecycle state (`active`, `disposed`, `archived`). See `PositionResponse.status` for the full vocabulary. + One of the 3 association-level categories in the `public.classifications` CHECK constraint: 'concept_arrangement', 'member_arrangement', or 'named_disclosure'. """ - status: String! + category: String! - """Free-text notes attached to the position.""" - notes: String + """ + Vocabulary identifier within the category — e.g. 'RollUp', 'whole_part', 'AssetsRollUp'. + """ + identifier: String! - """Embedded security details — name, type, issuer.""" - security: SecurityLite! -} + """ + Whether this is the canonical classification for the (association|element, category) pair. Non-primary rows capture alternates / AI suggestions alongside the chosen primary. + """ + isPrimary: Boolean! -"""Paginated list of positions.""" -type PositionList { - """Positions on this page.""" - positions: [Position!]! + """ + AI/adapter-supplied confidence (0.0-1.0). Null for deterministic library-seeded rows. + """ + confidence: Float - """Pagination cursor and totals.""" - pagination: PaginationInfo! + """ + Provenance — 'arcrole_analysis', 'disclosure_mechanics', 'fac-traits', adapter name, etc. + """ + source: String } -"""Publish list summary — header metadata, no members.""" -type PublishList { - """List identifier (ULID).""" +"""Fact projection — just the values the envelope caller cares about.""" +type InformationBlockFact { id: String! + elementId: String! + elementName: String + elementQname: String - """Human-readable list name.""" - name: String! - - """Free-form description.""" - description: String + """Numeric value; null for Nonnumeric (text-block) facts.""" + value: Float - """Number of recipient graphs currently on the list.""" - memberCount: Int! + """Text payload for Nonnumeric facts; null for numeric.""" + textValue: String - """User ID that created the list.""" - createdBy: String! + """Numeric | Nonnumeric""" + factType: String! - """When the list was created.""" - createdAt: DateTime! + """MIME type of text_value (e.g. 'text/markdown').""" + contentType: String + periodStart: Date + periodEnd: Date! + periodType: String! + unit: String! - """Last metadata update (name/description).""" - updatedAt: DateTime! + """historical | in_scope""" + factScope: String! + factSetId: String } -"""Full detail including members.""" -type PublishListDetail { - """List identifier (ULID).""" - id: String! - - """Human-readable list name.""" - name: String! - - """Free-form description.""" - description: String +""" +Rule projection for the Information Block envelope. - """Number of recipient graphs currently on the list.""" - memberCount: Int! +One row per ``public.rules`` entry scoped to this block. The rule +engine consumes ``rule_expression`` + ``rule_variables`` to evaluate +against the in-scope fact set; the envelope surfaces the rules so +the UI can render them as a checklist alongside any persisted +verification results. +""" +type InformationBlockRule { + id: String! - """User ID that created the list.""" - createdBy: String! + """ + One of 8 cm:VerificationRule subclasses — AutomatedAccountingAndReportingChecks, FundamentalAccountingConceptRelation, PeerConsistencyRule, PriorPeriodConsistencyRule, ReportLevelModelStructureRule, ReportingSystemSpecificRule, ToDoManualTask, XBRLTechnicalSyntaxRule. + """ + ruleCategory: String! - """When the list was created.""" - createdAt: DateTime! + """ + Arithmetic / logical pattern evaluated over fact values. One of 11 cm:BusinessRulePattern mechanisms — Adjustment, CoExists, EqualTo, Exists, GreaterThan, GreaterThanOrEqualToZero, LessThan, RollForward, RollUp, SumEquals, Variance. Null when the rule is a structural check (see rule_check_kind). + """ + rulePattern: String - """Last metadata update (name/description).""" - updatedAt: DateTime! + """ + Model-structure check kind evaluated over the association graph. One of 6 kinds — LeafHasClassification, LibraryOriginImmutability, NoCycles, NoOrphanArcs, ParentBeforeChild, UniqueQNameInTaxonomy. Null when the rule is an arithmetic pattern (see rule_pattern). Exactly one of rule_pattern / rule_check_kind is non-null per rule. + """ + ruleCheckKind: String + ruleExpression: String! + ruleTarget: InformationBlockRuleTarget + ruleVariables: [InformationBlockRuleVariable!]! + ruleMessage: String - """All recipient graphs on the list.""" - members: [PublishListMember!]! -} + """ + Failure severity — 'info' | 'warning' | 'error'. Enum closure enforced by the ``public.rules`` CHECK constraint. + """ + ruleSeverity: String! -"""Paginated list of publish lists owned by the current graph.""" -type PublishListList { - """Publish list summaries, newest first.""" - publishLists: [PublishList!]! - pagination: PaginationInfo! + """ + Provenance — 'forked' (from an upstream artifact, e.g. Seattle Method) or 'native' (authored in this seed or by a tenant). Enum closure enforced by the ``public.rules`` CHECK constraint. + """ + ruleOrigin: String! } -"""One recipient graph in a publish list.""" -type PublishListMember { - """Membership row identifier (ULID).""" - id: String! - - """Recipient graph ID.""" - targetGraphId: String! +"""Polymorphic rule target — points at the atom the rule is scoped to.""" +type InformationBlockRuleTarget { + """ + Which atom type the rule targets — 'structure' | 'element' | 'association' | 'taxonomy'. Enum closure enforced by the ``public.rules`` CHECK constraint. + """ + targetKind: String! - """Display name of the recipient graph (if known).""" - targetGraphName: String + """ + UUID of the target atom — structure_id, element_id, association_id, or taxonomy_id depending on ``target_kind``. + """ + targetRefId: String! +} - """Display name of the org that owns the recipient graph.""" - targetOrgName: String +"""`$Variable` → concept qname binding for a rule expression.""" +type InformationBlockRuleVariable { + """Local name in the rule expression, e.g. 'Assets'.""" + variableName: String! - """User ID that added this member.""" - addedBy: String! + """ + Concept qname the variable resolves to, e.g. 'fac:Assets'. Null for tenant CoA elements (which key on `code`/`element_id`, not qname) — in that case the binding is carried by `variable_element_id`. + """ + variableQname: String - """When the member was added.""" - addedAt: DateTime! + """ + Element id the variable binds to directly. Set for schedule SumEquals rules over CoA-debit elements that have no qname; null otherwise. + """ + variableElementId: String } -type Query { - portfolios(limit: Int = null, offset: Int = null): PortfolioList - securities(entityId: String = null, securityType: String = null, isActive: Boolean = null, limit: Int = null, offset: Int = null): SecurityList - security(securityId: String!): Security - positions(portfolioId: String = null, securityId: String = null, status: String = null, limit: Int = null, offset: Int = null): PositionList - position(positionId: String!): Position - holdings(portfolioId: String!): HoldingsList - portfolioBlock(portfolioId: String!): PortfolioBlock - entity: LedgerEntity - entities(source: String = null): [LedgerEntity!]! - agent(id: String!): Agent - agents(agentType: String = null, source: String = null, isActive: Boolean = true, limit: Int = null, offset: Int = null): [Agent!]! - openReceivables: OpenBalanceAggregate! - openPayables: OpenBalanceAggregate! - openReceivablesByAgent: [OpenBalanceByAgent!]! - openPayablesByAgent: [OpenBalanceByAgent!]! - eventBlock(id: String!): EventBlock - eventBlocks(eventType: String = null, eventCategory: String = null, status: String = null, agentId: String = null, source: String = null, limit: Int = null, offset: Int = null): [EventBlock!]! - summary: LedgerSummary - accounts(classification: String = null, isActive: Boolean = null, limit: Int = null, offset: Int = null): AccountList - accountTree(includeInactive: Boolean = null): AccountTree - accountRollups(mappingId: String = null, startDate: Date = null, endDate: Date = null): AccountRollups - trialBalance(startDate: Date = null, endDate: Date = null): TrialBalance - transactions(type: String = null, startDate: Date = null, endDate: Date = null, limit: Int = null, offset: Int = null): LedgerTransactionList - transaction(transactionId: String!): LedgerTransactionDetail - taxonomies(taxonomyType: String = null): TaxonomyList - reportingTaxonomy: Taxonomy - elements(taxonomyId: String = null, source: String = null, classification: String = null, isAbstract: Boolean = null, limit: Int = null, offset: Int = null): ElementList - mappingCandidates(classification: String!): [Element!]! - unmappedElements(mappingId: String = null): [UnmappedElement!]! - structures(taxonomyId: String = null, blockType: String = null): StructureList - mappings: StructureList - mapping(mappingId: String!): MappingDetail - mappingCoverage(mappingId: String!): MappingCoverage - mappedTrialBalance(mappingId: String!, startDate: Date = null, endDate: Date = null): MappedTrialBalance - periodCloseStatus(periodStart: Date!, periodEnd: Date!): PeriodCloseStatus - fiscalCalendar: FiscalCalendar - periodDrafts(period: String!): PeriodDrafts - closingBookStructures: ClosingBookStructures - reports: ReportList - report(reportId: String!): Report - reportPackage(reportId: String!): ReportPackage - reportDownloadUrl(reportId: String!, format: ReportDownloadFormat = null, expiresIn: Int = null): ReportBundleDownload - statement(reportId: String!, blockType: String!): Statement - publishLists(limit: Int = null, offset: Int = null): PublishListList - publishList(listId: String!): PublishListDetail - informationBlock(id: ID!, scenarioId: String = null, series: Boolean! = false, seriesHistory: Int = null, seriesForecast: Int = null): InformationBlock - informationBlocks(blockType: String = null, category: String = null, limit: Int = null, offset: Int = null, scenarioId: String = null): [InformationBlock!]! - taxonomyBlock(id: ID!): TaxonomyBlock - taxonomyBlocks(taxonomyType: String = null, parentTaxonomyId: ID = null, category: String = null, limit: Int = null, offset: Int = null): [TaxonomyBlock!]! - libraryTaxonomies(standard: String = null, includeElementCount: Boolean = null): [LibraryTaxonomy!]! - libraryTaxonomy(id: ID = null, standard: String = null, version: String = null, includeElementCount: Boolean = null): LibraryTaxonomy - libraryTaxonomyArcs(taxonomyId: ID!, associationType: String = null, structureId: ID = null, limit: Int = null, offset: Int = null): [LibraryAssociation!]! - libraryTaxonomyArcCount(taxonomyId: ID!, associationType: String = null, structureId: ID = null): Int! - libraryElements(taxonomyId: ID = null, source: String = null, classification: String = null, activityType: String = null, elementType: String = null, isAbstract: Boolean = null, limit: Int = null, offset: Int = null, includeLabels: Boolean = null, includeReferences: Boolean = null): [LibraryElement!]! - libraryElement(id: ID = null, qname: String = null): LibraryElement - searchLibraryElements(query: String!, source: String = null, limit: Int = null): [LibraryElement!]! - libraryElementTree(id: ID!, maxDepth: Int = null, structureId: ID = null): LibraryElementTreeNode - libraryElementEquivalents(id: ID!): LibraryEquivalence - libraryElementArcs(id: ID!): [LibraryElementArc!]! - libraryElementClassifications(id: ID!): [LibraryElementClassification!]! - libraryStructures(taxonomyId: ID = null, blockType: String = null): [LibraryStructure!]! - libraryStructure(id: ID!): LibraryStructure - hello: String! +""" +FactSet projection — period-specific instantiation of the Structure. + +The envelope carries one ``FactSetLite`` per block when a FactSet row +exists for the requested period; legacy writes that pre-date FactSet +stamping leave ``fact_set`` null until the expand pass starts +populating those rows. +""" +type InformationBlockFactSet { + id: String! + structureId: String + periodStart: Date + periodEnd: Date! + + """ + 'report' | 'schedule' | 'custom' | 'disclosure' | 'metric'. Enum closure enforced by the ``public.fact_sets`` CHECK constraint. + """ + factsetType: String! + entityId: String! + + """ + Back-pointer to the ``reports`` table while ``report_id`` still lives on facts. Drops out once the retirement migration lands. + """ + reportId: String + + """ + Scenario axis (the forecast engine). NULL = actuals; non-NULL names the owning forecast block whose parallel universe this set belongs to. + """ + scenarioId: String + + """ + Typed ``FactProvenance`` descriptor (discriminated on ``origin``: pivot | schedule | derived | asserted) recording how this FactSet's facts were constructed. Surfaced as JSON, mirroring how mechanics is exposed. Null for pre-feature historical FactSets. + """ + provenance: JSON } """ -Report definition summary — header metadata, no facts. +Persisted outcome of one Rule evaluation. -Returned by ``create-report``, ``regenerate-report``, -``file-report``, and ``transition-filing-status``. Use the package -read endpoint to retrieve a Report rehydrated with its rendered -``InformationBlockEnvelope`` items. +One row per ``public.verification_results`` entry the rule engine +writes. The envelope surfaces them so the block viewer's +"Verification Results" tab and MCP ``list-verification-failures`` +tool can render + aggregate without a second round-trip. """ -type Report { - """Report identifier (ULID).""" +type InformationBlockVerificationResult { id: String! - - """Human-readable report name.""" - name: String! - - """Taxonomy this report renders against.""" - taxonomyId: String! + ruleId: String! + structureId: String + factSetId: String """ - Computation lifecycle: `generating`, `published`, `failed`. Orthogonal to `filing_status`. + 'pass' | 'fail' | 'error' | 'skipped'. Enum closure enforced by the ``public.verification_results`` CHECK constraint. """ - generationStatus: String! + status: String! + message: String + periodStart: Date + periodEnd: Date + evaluatedAt: DateTime +} - """Period cadence: `monthly`, `quarterly`, `annual`.""" - periodType: String! +""" +Server-computed aggregate of a block's ``verification_results``. - """Current-period start.""" - periodStart: Date +Overall counts plus a per-``rule_category`` breakdown, so the viewer +renders the grouped Verification Results panel +without a client-side results→rules join. Status closure is +``pass | fail | error | skipped`` (the ``public.verification_results`` +CHECK); ``total`` is their sum. +""" +type InformationBlockVerificationSummary { + total: Int! + passed: Int! + failed: Int! + errored: Int! + skipped: Int! + byCategory: [InformationBlockVerificationCategorySummary!]! +} - """Current-period end.""" - periodEnd: Date +""" +Pass/fail/skip counts for one ``rule_category`` within a block's +verification results. - """True when an auto-generated prior-period column is included.""" - comparative: Boolean! +Drives the per-category accordions in the Verification Results panel. +``category`` is the rule's ``rule_category`` +(one of the cm:VerificationRule subclasses), resolved by joining each +result to its Rule. +""" +type InformationBlockVerificationCategorySummary { + category: String! + total: Int! + passed: Int! + failed: Int! + errored: Int! + skipped: Int! +} - """ - Explicit period columns when the report was created with a multi-period layout. - """ - periods: [PeriodSpec!] +""" +Charlie's six ``type-of View`` arms, surfaced at the envelope boundary. - """CoA → taxonomy mapping the facts were rolled up through.""" - mappingId: String +Each projection is computed server-side at envelope-build time when +its source data is available. The frontend's ``BlockView`` dispatcher +routes to the projection component matching the user's selected view +mode; missing projections (those still in backlog) render as empty +states without breaking the dispatcher. - """True when the report was created by an AI agent rather than a user.""" - aiGenerated: Boolean! +Today: ``rendering`` is computed for the statement family, and +``chart`` (the 7th arm — panel/series config over the rendering's +rows and periods) for metric blocks. +Other arms (``fact_table``, ``model_structure``, ``verification_results``, +``report_elements``, ``business_rules``) come online as their backend +support lands; ``fact_table`` is trivially derivable from +``InformationBlockEnvelope.facts`` and may stay as a frontend-only +projection. +""" +type InformationBlockViewProjections { + rendering: InformationBlockRendering + chart: InformationBlockChart +} - """When the report row was created.""" - createdAt: DateTime! +""" +Pre-computed rendering projection of an Information Block. - """When the facts were last (re)generated.""" - lastGenerated: DateTime +Computed server-side at envelope-build time for blocks where rendering +is deterministic (the statement family today; future block types add +their own rendering builders). The frontend's ``BlockView`` +``Rendering`` projection consumes this directly — no client-side +rollup, depth computation, or calculation walk needed. +""" +type InformationBlockRendering { + rows: [InformationBlockRenderingRow!]! + periods: [InformationBlockRenderingPeriod!]! + validation: InformationBlockValidation + unmappedCount: Int! +} - """ - Structures available for this report's taxonomy — renderable sections (BS / IS / CF / Equity / Schedules). - """ - structures: [StructureSummary!]! +""" +One row of a server-side rendered statement. - """Display name of the primary entity the report is tagged to.""" - entityName: String +Mirrors :class:`FactRow` from the legacy +:mod:`robosystems.operations.roboledger.reports.fact_grid` but lives at +the API boundary so envelope consumers don't depend on the +fact-grid module. ``values`` is one entry per period column in +:class:`RenderingLite.periods`. +""" +type InformationBlockRenderingRow { + elementId: String! + elementQname: String + elementName: String! """ - Filing lifecycle (orthogonal to `generation_status`): `draft`, `under_review`, `filed`, `archived`. + FASB elementsOfFinancialStatements trait identifier — 'asset', 'liability', 'equity', 'revenue', 'expense'. Surfaced so the viewer can color-code or group rows without a follow-up trait lookup. """ - filingStatus: String! - - """When the report was transitioned to `filed`.""" - filedAt: DateTime + classification: String + balanceType: String - """User ID that transitioned the report to `filed`.""" - filedBy: String + """ + Value-domain format family from the element (monetary | ratio | percent | multiple | days | …). Drives per-row value formatting; None falls back to is_monetary on the element. + """ + itemType: String + values: [Float]! """ - When this report restates an earlier filing, the predecessor's report ID. + Narrative payload for text-block disclosure rows (markdown); numeric rows carry values instead. """ - supersedesId: String + textValue: String + isSubtotal: Boolean! + depth: Int! +} - """When this report has been restated, the successor's report ID.""" - supersededById: String +"""One period column in a rendered statement.""" +type InformationBlockRenderingPeriod { + start: Date! + end: Date! + label: String """ - Origin graph for received (shared) reports — populated only on the recipient's copy. + True when this column comes from a forecast scenario's FactSet — the machine-readable seam marker (labels also carry a '(forecast)' suffix, but consumers should key styling off this flag, not label parsing). None/absent = an actuals column. """ - sourceGraphId: String + forecast: Boolean +} + +""" +Outcome of guard-rail validation on a rendered statement. + +Distinct from :class:`VerificationResultLite` (which surfaces the +rule-engine outcomes from ``public.verification_results``). This lite +type carries the synchronous guard-rail checks computed at +envelope-build time — accounting equation, totals foot, etc. +""" +type InformationBlockValidation { + passed: Boolean! + checks: [String!]! + failures: [String!]! + warnings: [String!]! +} + +""" +Server-shaped chart projection — panel/series CONFIG, never values. + +The second real server-computed View arm (after ``rendering``). Values +come from ``rendering.rows`` joined by ``element_id``; the x-axis is +``rendering.periods``. Renderers (report-components) turn one panel +into one chart. +""" +type InformationBlockChart { + panels: [InformationBlockChartPanel!]! +} + +""" +One chart panel — series sharing a y-axis format family. + +Mixed-unit catalogs are unplottable on one axis, so the server groups +rows into panels by ``item_type`` family (NULL falls back to +``is_monetary``). The x-axis is always ``rendering.periods``. +""" +type InformationBlockChartPanel { + """Panel heading — e.g. 'Monetary', 'Ratios'.""" + label: String """ - Origin report ID for received (shared) reports — populated only on the recipient's copy. + Format family shared by the panel's series (monetary | ratio | percent | multiple | days); None for the untyped fallback panel. """ - sourceReportId: String + itemType: String - """When the report was shared into this graph (recipient side).""" - sharedAt: DateTime + """Per-panel mark — 'line' or 'bar'.""" + kind: String! + series: [InformationBlockChartSeries!]! +} + +""" +One plottable series in a chart panel. + +Carries structure and identity only — the values live in the sibling +``rendering.rows`` (join on ``element_id``), so the chart arm never +duplicates the value matrix. ``key`` is the stable series identity for +client state (colors, toggles); today it equals ``element_id``, and +future axes (the forecast scenario) arrive as new fields on this +model, never a new arm shape. +""" +type InformationBlockChartSeries { + """Stable series id — element_id today.""" + key: String! + elementId: String! - """ - Counts by rule outcome (e.g. `{'passed': 12, 'failed': 1}`) from the most recent evaluation. Null until rules run. - """ - ruleSummary: JSON + """Display name for legends.""" + label: String! } """ @@ -2082,95 +2015,6 @@ enum ReportDownloadFormat { XBRL_2_1 } -"""List of report header summaries (used by report list reads).""" -type ReportList { - """Report definitions, newest first.""" - reports: [Report!]! -} - -type ReportPackage { - id: ID! - name: String! - description: String - taxonomyId: String! - periodType: String! - periodStart: Date - periodEnd: Date - generationStatus: String! - lastGenerated: DateTime - filingStatus: String! - filedAt: DateTime - filedBy: String - supersedesId: String - supersededById: String - sourceGraphId: String - sourceReportId: String - sharedAt: DateTime - entityName: String - aiGenerated: Boolean! - createdAt: DateTime! - createdBy: String! - items: [ReportPackageItem!]! -} - -type ReportPackageItem { - factSetId: String! - structureId: String - displayOrder: Int! - block: InformationBlock! -} - -type Security { - id: ID! - entityId: String - entityName: String - sourceGraphId: String - name: String! - securityType: String! - securitySubtype: String - terms: JSON! - isActive: Boolean! - authorizedShares: Int - outstandingShares: Int - createdAt: DateTime! - updatedAt: DateTime! -} - -type SecurityList { - securities: [Security!]! - pagination: PaginationInfo! -} - -""" -Lightweight security projection for embedding in position -envelopes. Skips `terms`, `outstanding_shares`, etc. — fetch the -full `SecurityResponse` when those are needed. -""" -type SecurityLite { - """Security ID (`sec_*` ULID).""" - id: ID! - - """Display name of the security.""" - name: String! - - """Instrument family (e.g. `common_stock`, `preferred_stock`, `warrant`).""" - securityType: String! - - """Optional subtype refinement (e.g. `class_a`).""" - securitySubtype: String - - """`true` when the security is in active status.""" - isActive: Boolean! - - """ - Embedded issuer entity, when one is linked. `null` for pre-issuer securities. - """ - issuer: EntityLite - - """Tenant graph the security is pre-associated to, if any.""" - sourceGraphId: String -} - """ Rendered financial statement — facts viewed through a structure. @@ -2211,79 +2055,130 @@ type Statement { } """ -One structure header — a renderable section within a taxonomy -(balance sheet, income statement, schedule, etc.). +A single fact row inside a rendered statement. -``block_type`` drives presentation: 'balance_sheet', -'income_statement', 'cash_flow_statement', 'equity_statement', -'schedule', 'chart_of_accounts', 'coa_mapping', 'rollforward', etc. +One row per concept, with one value per period column. Subtotals and +hierarchy depth come from the structure being projected. """ -type Structure { - id: String! - name: String! - description: String - blockType: String! - taxonomyId: String! - isActive: Boolean! +type FactRow { + """Internal element identifier.""" + elementId: String! + + """QName of the reporting concept (e.g. 'us-gaap:Revenues').""" + elementQname: String! + + """Human-readable concept label.""" + elementName: String! + + """ + Concept trait flag from the structure (e.g. 'total', 'subtotal', 'header'). Drives presentation. + """ + trait: String + + """ + One value per period column, in the same order as `periods`. Null when the concept had no facts in that window. + """ + values: [Float]! + + """True when the row should render as a subtotal line.""" + isSubtotal: Boolean! + + """Indentation depth in the structure hierarchy (0 = root).""" + depth: Int! } -"""Flat list of structures within a taxonomy.""" -type StructureList { - structures: [Structure!]! +"""Aggregate result of running reporting rules over a structure.""" +type ValidationCheck { + """True iff every rule produced zero failures.""" + passed: Boolean! + + """Names of rules that were evaluated.""" + checks: [String!]! + + """Human-readable descriptions of rule failures.""" + failures: [String!]! + + """Non-blocking advisories from rule evaluation.""" + warnings: [String!]! } -""" -A structure available within this report's taxonomy. +"""Paginated list of publish lists owned by the current graph.""" +type PublishListList { + """Publish list summaries, newest first.""" + publishLists: [PublishList!]! + pagination: PaginationInfo! +} -Each structure is a renderable section (Balance Sheet, Income -Statement, Cash Flow Statement, Equity, or a Schedule). The Report -row owns the facts; structures are the lenses that project them. -""" -type StructureSummary { - """Structure identifier.""" +"""Publish list summary — header metadata, no members.""" +type PublishList { + """List identifier (ULID).""" id: String! - """Human-readable structure name.""" + """Human-readable list name.""" name: String! - """ - Structure category: `balance_sheet`, `income_statement`, `cash_flow_statement`, `equity_statement`, `schedule`. - """ - blockType: String! -} + """Free-form description.""" + description: String -"""A suggested mapping target from the reporting taxonomy.""" -type SuggestedTarget { - elementId: String! - qname: String! - name: String! - confidence: Float -} + """Number of recipient graphs currently on the list.""" + memberCount: Int! -""" -One taxonomy header — identity + lifecycle flags. Atoms -(elements, structures, associations, rules) are exposed via the -Taxonomy Block envelope. + """User ID that created the list.""" + createdBy: String! -``taxonomy_type`` discriminates: ``chart_of_accounts``, -``reporting_standard``, ``reporting_extension``, ``custom_ontology``, -``mapping``, ``schedule``. ``is_locked=True`` means library-origin -(immutable for tenants); ``is_shared=True`` means visible to multiple -graphs from a shared registry. -""" -type Taxonomy { + """When the list was created.""" + createdAt: DateTime! + + """Last metadata update (name/description).""" + updatedAt: DateTime! +} + +"""Full detail including members.""" +type PublishListDetail { + """List identifier (ULID).""" id: String! + + """Human-readable list name.""" name: String! + + """Free-form description.""" description: String - taxonomyType: String! - version: String - standard: String - namespaceUri: String - isShared: Boolean! - isActive: Boolean! - isLocked: Boolean! - sourceTaxonomyId: String - targetTaxonomyId: String + + """Number of recipient graphs currently on the list.""" + memberCount: Int! + + """User ID that created the list.""" + createdBy: String! + + """When the list was created.""" + createdAt: DateTime! + + """Last metadata update (name/description).""" + updatedAt: DateTime! + + """All recipient graphs on the list.""" + members: [PublishListMember!]! +} + +"""One recipient graph in a publish list.""" +type PublishListMember { + """Membership row identifier (ULID).""" + id: String! + + """Recipient graph ID.""" + targetGraphId: String! + + """Display name of the recipient graph (if known).""" + targetGraphName: String + + """Display name of the org that owns the recipient graph.""" + targetOrgName: String + + """User ID that added this member.""" + addedBy: String! + + """When the member was added.""" + addedAt: DateTime! } type TaxonomyBlock { @@ -2308,17 +2203,6 @@ type TaxonomyBlock { associationCount: Int! } -type TaxonomyBlockAssociation { - id: String! - structureId: String! - fromElementQname: String! - toElementQname: String! - associationType: String! - orderValue: Float - arcrole: String - weight: Float -} - type TaxonomyBlockElement { id: String! qname: String @@ -2333,6 +2217,25 @@ type TaxonomyBlockElement { origin: String! } +type TaxonomyBlockStructure { + id: String! + name: String! + blockType: String! + description: String + roleUri: String +} + +type TaxonomyBlockAssociation { + id: String! + structureId: String! + fromElementQname: String! + toElementQname: String! + associationType: String! + orderValue: Float + arcrole: String + weight: Float +} + type TaxonomyBlockRule { id: String! name: String! @@ -2345,90 +2248,187 @@ type TaxonomyBlockRule { targetRef: String } -type TaxonomyBlockStructure { +"""A library taxonomy (fac, us-gaap, rs-gaap, …).""" +type LibraryTaxonomy { id: String! name: String! - blockType: String! description: String - roleUri: String -} -"""Flat list of taxonomy headers. Used by the catalog/picker UIs.""" -type TaxonomyList { - taxonomies: [Taxonomy!]! -} + """fac | us-gaap | rs-gaap | ifrs""" + standard: String + version: String + namespaceUri: String -""" -Trial balance for posted entries in a date range — every CoA -account that had activity, plus aggregate totals. + """chart_of_accounts | reporting | mapping | schedule""" + taxonomyType: String! + isShared: Boolean! + isActive: Boolean! + isLocked: Boolean! -Ledger is balanced when ``total_debits == total_credits``. Used as -a sanity check before close-period; failure means an unposted / -malformed entry slipped through. -""" -type TrialBalance { - rows: [TrialBalanceRow!]! - totalDebits: Float! - totalCredits: Float! + """Total elements in this taxonomy (computed on demand)""" + elementCount: Int } -"""One CoA account's debit/credit totals over the trial-balance window.""" -type TrialBalanceRow { - accountId: String! - accountCode: String! - accountName: String! - trait: String - accountType: String - totalDebits: Float! - totalCredits: Float! - netBalance: Float! +"""An arc between two library elements (parent-child, equivalence, etc).""" +type LibraryAssociation { + id: String! + structureId: String! + structureName: String + fromElementId: String! + fromElementQname: String + fromElementName: String + + """Primary elementsOfFinancialStatements trait (for node coloring)""" + fromElementTrait: String + fromElementIsAbstract: Boolean + toElementId: String! + toElementQname: String + toElementName: String + + """Primary elementsOfFinancialStatements trait (for node coloring)""" + toElementTrait: String + toElementIsAbstract: Boolean + + """ + presentation | calculation | mapping | equivalence | general-special | essence-alias + """ + associationType: String! + arcrole: String + orderValue: Float + weight: Float } -"""An element not yet mapped to the reporting taxonomy.""" -type UnmappedElement { +"""A library element (concept, abstract, axis, member, or hypercube).""" +type LibraryElement { id: String! - code: String + + """Qualified name, e.g. 'fac:Assets'""" + qname: String! + namespace: String name: String! + + """ + FASB elementsOfFinancialStatements axis: asset | contraAsset | liability | contraLiability | equity | contraEquity | temporaryEquity | revenue | expense | expenseReversal | gain | loss | comprehensiveIncome | investmentByOwners | distributionToOwners | metric (derived subtotals, not SFAC 6 primary elements). Null for structural rows. + """ trait: String - liquidity: String + + """debit | credit""" balanceType: String! - externalSource: String - suggestedTargets: [SuggestedTarget!]! + + """instant | duration""" + periodType: String! + isAbstract: Boolean! + isMonetary: Boolean! + + """concept | abstract | axis | member | hypercube""" + elementType: String! + + """fac | us-gaap | rs-gaap | …""" + source: String! + taxonomyId: String + parentId: String + labels: [LibraryLabel!]! + references: [LibraryReference!]! +} + +"""A label on a library element.""" +type LibraryLabel { + """Label role: standard/documentation/verbose/…""" + role: String! + + """Language code""" + language: String! + + """Label text""" + text: String! +} + +"""A cross-reference on a library element (FASB ASC, SEC, etc).""" +type LibraryReference { + """'ASC' | 'SEC' | 'SFAC' | 'IFRS' | 'Other'""" + refType: String + + """Full citation text""" + citation: String! + + """Dereferenceable URL if available""" + uri: String +} + +type LibraryElementTreeNode { + element: LibraryElement! + children: [LibraryElementTreeNode!]! } """ -A CoA→rs-gaap mapping whose target doesn't reach a Network root. +An element and its equivalence peers. -The reporting layer is closed: every mapping target must trace upward -through the rs-gaap calc DAG to one of the canonical roots -(rs-gaap:Assets, rs-gaap:LiabilitiesAndStockholdersEquity, -rs-gaap:NetIncomeLoss, rs-gaap:CashAndCashEquivalentsPeriodIncreaseDecrease). -When it doesn't, the fact will land on a dead branch — visible in the -trial balance but invisible to any rendered statement. Surfacing -these as defects lets operators fix the mapping before the report is -filed. +Answers "what other concepts mean the same thing as this one" — the +FAC→us-gaap collapse pattern rendered as an API shape. """ -type UnreachableMappingType { - coaElementId: String! - coaQname: String - coaCode: String - coaName: String - targetElementId: String! - targetQname: String - targetName: String +type LibraryEquivalence { + element: LibraryElement! + equivalents: [LibraryElement!]! } -"""Aggregate result of running reporting rules over a structure.""" -type ValidationCheck { - """True iff every rule produced zero failures.""" - passed: Boolean! +""" +A mapping arc involving a specific element. - """Names of rules that were evaluated.""" - checks: [String!]! +Flat row view: one arc, oriented from the perspective of the element +being inspected. `peer` is the other end; `direction` says whether +this element is the source ('outgoing') or the target ('incoming'). - """Human-readable descriptions of rule failures.""" - failures: [String!]! +Scoped to arcs whose structure belongs to a `taxonomy_type='mapping'` +taxonomy — the cross-taxonomy bridges (equivalence, general-special, +rs-gaap-type-subtype). Hierarchical arcs inside a single reporting taxonomy +are out of scope. +""" +type LibraryElementArc { + id: String! - """Non-blocking advisories from rule evaluation.""" - warnings: [String!]! + """'outgoing' (this element is source) | 'incoming' (target)""" + direction: String! + associationType: String! + arcrole: String + taxonomyId: String + taxonomyStandard: String + taxonomyName: String + structureId: String + structureName: String + peer: LibraryElement! +} + +""" +One FASB metamodel trait assigned to a library element. + +A single element can carry multiple traits across multiple categories +(e.g. elementsOfFinancialStatements=expense AND +operatingNonoperating=operating AND liquidity=current). +""" +type LibraryElementClassification { + """Trait axis (e.g. elementsOfFinancialStatements)""" + category: String! + + """Value within the axis (e.g. expense)""" + identifier: String! + + """Human-readable name""" + name: String + + """True for the element's primary EFS trait assignment""" + isPrimary: Boolean! +} + +"""A named structure (extended link role) within a library taxonomy.""" +type LibraryStructure { + id: String! + name: String! + + """balance_sheet | income_statement | cash_flow_statement | custom | …""" + blockType: String! + taxonomyId: String! + + """Original XBRL role URI if any""" + roleUri: String + isActive: Boolean! } \ No newline at end of file diff --git a/tests/test_graphql_queries.py b/tests/test_graphql_queries.py index bd3cc55..2e9700a 100644 --- a/tests/test_graphql_queries.py +++ b/tests/test_graphql_queries.py @@ -9,9 +9,18 @@ dead, and no test, type check or lint caught it. Validation runs against a checked-in SDL snapshot rather than a live backend so -it works offline, in the pre-commit hook, and in CI. Refresh the snapshot with -`just refresh-schema` when the backend schema changes; a stale snapshot can only -cause a false failure here, never a false pass on a field that has been removed. +it works offline, in the pre-commit hook, and in CI. + +The snapshot's currency matters in one direction more than the other: + +- Backend *adds* a field the snapshot lacks — a query using it fails here. + Noisy, but safe. +- Backend *removes* a field the snapshot still lists — a query using it passes + here and fails at runtime. Silent, and exactly the failure this test exists + to prevent. + +So the snapshot is refreshed as part of `just generate-sdk`, not left to be +remembered; `just refresh-schema` runs it standalone. """ from __future__ import annotations