Skip to content

feat(ledger): information block reads, schedules, saved statements and mappings name their entity - #1752

Merged
jfrench9 merged 3 commits into
mainfrom
feature/information-block-entity
Oct 9, 2026
Merged

jfrench9 merged 3 commits into
mainfrom
feature/information-block-entity

Conversation

@jfrench9

@jfrench9 jfrench9 commented Oct 9, 2026 •

Copy link
Copy Markdown
Member

Summary

Closes the entity gap on the information-block surface of a multi-entity graph. Statement structures come from the reporting style and are shared by the group, so the reads that pick "the newest set of this structure" showed whichever entity had closed last, and a statement series could mix entities.

  • Block reads take entity_id (GraphQL informationBlock / informationBlocks, MCP get-information-block / list-information-blocks), defaulting to the group parent as the ledger reads do. A block shared by the group (statements, metrics, disclosures) shows that entity's fact sets. A schedule, reconciliation or forecast is one entity's own and always shows its owner's; the list leaves out other entities' own blocks. A statement read in a scenario with no entity named reads the scenario's entity. The envelope gains entityId, naming the entity it read.
  • Forecasts: the history a plan back-solves, and compute-forecast's choice of actual structure, follow the forecast's entity (each entity can be on a different style). A forecast's entity stays recorded on its lever fact set.
  • Schedules: CreateScheduleRequest.entity_id. The command already took it; schedules made through create-information-block all landed on the parent.
  • Saved statements: statement(reportId) renders in the reporting style of the entity the report is about.
  • Mappings: mappings names the entity whose chart each mapping maps from and takes entityId; mappingCandidates(entityId) narrows by that entity's style.
  • Entity defaults read "the group parent" throughout, not "the earliest-created entity".

No migration: fact_sets.entity_id and structures.entity_id already exist.

Client impact

Additive only (new optional arguments and fields) → client minor. The facades follow in RoboFinSystems/robosystems-typescript-client#259 and RoboFinSystems/robosystems-python-client#244, both to merge after this ships to prod. A combined report across the group stays out of scope (multi-entity M2 step 4).

Test plan

  • just test: 16,876 passed
  • DB-backed tests for a parent and a subsidiary on one shared structure: default read, named entity, series, scenario default, listing, owned blocks by id, per-entity newest structure; mapping ownership + filter
  • Local API on the Driftline demo (parent + Driftline Café LLC): each entity's balance sheet shows only its own set, the parent's budget lists only for the parent and still back-solves 14 months of history, the subsidiary's metrics show no parent columns, mappings names both entities

🤖 Generated with Claude Code

Block reads take entity_id (default the group parent) on GraphQL
informationBlock/informationBlocks and the two MCP read tools. A block
shared by the group (statements, metrics, disclosures) shows that entity's
fact sets; a schedule, reconciliation or forecast always shows its owner's,
and the list leaves out other entities' own blocks. The envelope names the
entity it read. A forecast's history, and compute-forecast's choice of
actual structure, follow the forecast's entity.
- create-information-block's schedule body takes entity_id; every schedule
  made through it used to land on the group parent.
- statement(reportId) renders in the reporting style of the entity the
  report is about.
- mappings lists each mapping with the entity whose chart it maps from, and
  takes entityId to keep one entity's; mappingCandidates narrows by the
  named entity's style.
- Entity defaults read 'the group parent' throughout, not 'the
  earliest-created entity'.
@github-actions github-actions Bot added the change:normal Normal change: automated review documented on the pull request before merge label Oct 9, 2026
@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

Change classification: normal — platform or application code changed.

A normal change carries a documented automated review on this pull request before merge, in place of a second human approver. The change-classification job requests that review and passes once it has been posted. Recorded by the change-classification job.

@jfrench9

jfrench9 commented Oct 9, 2026

Copy link
Copy Markdown
Member Author

@claude please review this PR

@claude

claude Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Claude finished @jfrench9's task in 35s —— View job


PR review: information block reads, schedules, saved statements and mappings name their entity

Verdict: looks good. I found no blocking issues, only a few small nits. This was a read-through of the diff and the surrounding code. I did not run the test suite, so I'm relying on the PR's reported run (16,876 passed).

  • Read the diff against origin/main
  • Checked entity scoping in reads.py, envelope.py, the forecast modules and the mappings path
  • Checked the GraphQL and MCP surfaces and the new tests

What I checked

  • Scope resolution (reads.py). The order is: named entity (validated), then the scenario's owner, then the group parent. A block that is one entity's own overrides all of that with owner_entity_id(...) or scope. This is coherent. The library sentinel gets no entity scope.
  • Owner lookup. coalesce(Structure.entity_id, lever_entity) has a correlated subquery for forecasts. The list query filters owner IS NULL OR owner == scope before limit/offset, so pagination stays correct.
  • No hidden rows from the new filter. fact_sets.entity_id is NOT NULL, so filtering on it can't hide legacy rows with a null entity.
  • Forecasts. compute-forecast already resolves entity_id, so passing it to newest_actual_structure_id is correct. In forecast.build_envelope, history follows the lever set's entity and falls back to an empty LeverHistory() when no entity is known. That is the right degradation.
  • Pinned fact sets. A pinned fact_set_id overrides the entity filter, and _stamp_entity reports the set's own entity on the envelope. That is honest.
  • Mappings. mapping_owner_ids runs in one query. The "newest first, so the earliest link wins" comment is correct, because later dict writes overwrite earlier ones.
  • Errors. EntityNotInGraphError is mapped consistently to a GraphQL error and to the MCP entity_not_found response. Nothing here is an additive-API break.

Nits (non-blocking)

  1. Over-long docstring line. In envelope.py, load_statement_fact_set_series, the edit produced sets. \`entity_id`` keeps one entity's sets. Sets whose window strictly contains another's (an annual over its, which is over 88 characters. Ruff format won't rewrap docstrings, so just lint` may flag E501. Please rewrap it.
  2. LookupError is no longer handled in mappingCandidates. The old code caught LookupError from load_primary_reporting_style. The new code only catches NoEntityError, and load_entity_reporting_style still raises LookupError when the entity row is missing. resolve_entity_id validates first, so this is nearly unreachable. A one-line comment, or keeping the LookupError catch, would make that explicit.
  3. Same gap in reports.get_statement. load_entity_reporting_style(session, report_def.entity_id) raises LookupError if a report points at an entity that was removed. The fallback to load_primary_reporting_style only applies when entity_id is empty. This is probably fine, but a fallback to the parent would be cheap and safer for old reports.
  4. entity_id is silently ignored on the library graph. _requested_entity_id returns None for the sentinel, so a caller passing entity_id there gets no error. That seems intentional. Mentioning it in the tool and argument description would avoid confusion.
  5. Repeated inline filter. metric.py and reconciliation.py inline *((FactSet.entity_id == entity_id,) if entity_id is not None else ()), while envelope.py has _entity_filter. Reusing one helper would cut the duplication.

Tests

Coverage is good. test_entity_scope_db.py is DB-backed with a parent and a subsidiary on a shared structure. It covers the default read, a named entity, series, the scenario default, listing, owned blocks and per-entity newest structure. It is paired with test_coa_mappings_db.py and test_reports_extra.py. One case I'd add: a pinned fact_set_id belonging to a different entity than the one requested, to lock in the "pin wins and the envelope names the set's entity" behaviour.
· branch feature/information-block-entity

jfrench9 added a commit to RoboFinSystems/robosystems-python-client that referenced this pull request Oct 9, 2026
…e an entity (#244)

## Summary

The Python facade side of RoboFinSystems/robosystems#1752, which scopes
information-block reads to one entity of a reporting group. Parity with
RoboFinSystems/robosystems-typescript-client#259.

- `get_information_block` / `list_information_blocks` take `entity_id`;
the envelope selects `entityId`, the entity it read.
- `list_mappings(graph_id, entity_id=...)`: each mapping carries
`entity_id` (whose chart it maps from), and the argument keeps one
entity's.
- `get_mapping_candidates(..., entity_id=...)` narrows by that entity's
reporting style.
- `create_report(..., entity_id=...)` and `create_schedule(...,
entity_id=...)` pass the entity through.
- `schema.graphql` refreshed from the local stack running #1752. It also
picks up `eventBlocks(eventClass)`, which shipped in v1.16.8 and was
missing from the snapshot. Generated REST models pick up
`CreateScheduleRequest.entity_id`, the envelope's `entity_id`, and the
reworded entity defaults.

Additive only → **minor**. Touches no symbol the integration template
imports.

**Waits on the server:** the documents name `entityId` arguments and
fields that a server serves only once it carries robosystems#1752, so
merge and publish this after the robosystems release that includes #1752
is deployed to prod.

## Test plan

- [x] `just generate-sdk` + `just generate-graphql` against the local
stack
- [x] `just test-all`: 671 passed, ruff + format + basedpyright clean
- [x] New cases: entity forwarded on both block reads, mapping
candidates and mappings (with `entity_id` on the row), `create_report`
wire body, `create_schedule` payload with and without an entity

🤖 Generated with [Claude Code](https://claude.com/claude-code)
jfrench9 added a commit to RoboFinSystems/robosystems-typescript-client that referenced this pull request Oct 9, 2026
…#259)

## Summary

The facade side of RoboFinSystems/robosystems#1752, which scopes
information-block reads to one entity of a reporting group.

- `getInformationBlock` (plain and windowed documents) and
`listInformationBlocks` take `entityId`; the envelope selects
`entityId`, the entity it read.
- `listMappings(graphId, { entityId })`: each mapping carries `entityId`
(whose chart it maps from), and the option keeps one entity's.
- `createReport` and `createSchedule` pass `entityId` through as
`entity_id`.
- `getMappingCandidates(graphId, classification, { entityId })` narrows
by that entity's reporting style.
- Generated SDK types pick up `CreateScheduleRequest.entity_id`, the
envelope's `entity_id`, and the reworded entity defaults.

Additive only → **minor**.

**Waits on the server:** the new documents name `entityId` arguments and
fields that a server only serves once it carries robosystems#1752, so
merge and publish this after the robosystems release that includes #1752
is deployed to prod.

## Test plan

- [x] `npm run generate` against the local stack running #1752
- [x] `npm test`: 426 passed (new cases for entity threading on all four
methods, and the absent entity on `createReport`)
- [x] lint, typecheck, format check

🤖 Generated with [Claude Code](https://claude.com/claude-code)
@jfrench9
jfrench9 merged commit 308a48f into main Oct 9, 2026
8 checks passed
@jfrench9
jfrench9 deleted the feature/information-block-entity branch October 9, 2026 05:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

change:normal Normal change: automated review documented on the pull request before merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant