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