Skip to content

Fix the executor simulation runtime's Pyright errors - #871

Draft
anth-volk wants to merge 2 commits into
pyright-executor-modalfrom
pyright-executor-runtime
Draft

anth-volk wants to merge 2 commits into
pyright-executor-modalfrom
pyright-executor-runtime

Conversation

@anth-volk

@anth-volk anth-volk commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #870

Same-Repository Draft PR Check

  • This PR is a draft.
  • This PR is from a branch in PolicyEngine/policyengine-sim-api, not a personal fork.

Summary

Fourth in the Pyright stack, on top of #869. Fixes all 54 Pyright errors in the executor's simulation_runtime.py without changing what a run does. The executor's baseline goes from 126 entries to 72.

Commit Errors fixed Baseline after
Open the simulation runtime's spans with the typed span scope 49 77
Type the simulation runtime's policy, region and dataset reads 5 72

No scopes.operation helper was needed: this module opens only spans.

What changed

  • Spans. The nine runtime.span(...) scopes become scopes.span(runtime, ...), including the credential-setup span chosen by a conditional expression, for consistency (Pyright didn't flag that one). That removes the 47 possibly-unbound errors.
  • _build_simulation's return type. With baseline and reform bound, Pyright reported that a Simulation can't be passed to _report_calculation_start, which takes a StageReportingSimulation (the 2 errors were already baselined, worded Unbound | Simulation). _build_simulation returns one in both branches (ArtifactBaselineSimulation subclasses it), and _report_calculation_start relies on that by calling on_first_ensure, so the return annotation now says StageReportingSimulation. The unused Simulation import goes.
  • Compiled reform. compile_reform returns None only for an empty policy, which _compile_interval_policy has already returned unchanged. A None result now returns the policy too (unreachable), as policyengine's own _reform_dict_to_parameter_values does.
  • UK region type. prefix not in {"constituency", "local_authority"} didn't narrow prefix, because a set display is typed set[str]. A tuple keeps the literal types, so prefix narrows to the two values Region.region_type accepts.
  • Input dataset entity data. The UK weight-matrix check counts households from dataset.data.entity_data, but policyengine types Dataset.data as BaseModel | None. It now reads entity_data with getattr(..., None) and requires a dict, raising TypeError with the dataset's name when none is loaded; before, that case raised AttributeError. A loaded dataset counts the same households. Narrowing on the value, rather than isinstance(..., UKYearData), keeps the existing tests' SimpleNamespace datasets valid. It also needs no new import into a module that keeps policyengine out of its module-level imports.
  • Compiled policy variable. _build_simulation reassigned its policy: dict | None parameter to _compile_interval_policy's result, which may be a Policy. The result now has its own name, runtime_policy, used in the three places that read the reassigned name.

The microdata weight checks (sim.microdata.*) recognise weighted frames by expressions such as <simulation>.output_dataset.data.<entity>. The dataset read changed here is the input dataset (dataset.data), which they don't track, before and after; test_microdata_weight_boundaries.py passes.

Commits (red, then green)

  1. Red: with the 49 entries removed from the baseline, the check on the code before the fix reported 49 errors it doesn't list. Green: passes with 77; executor tests pass.
  2. Red: with the module's last 5 entries removed, the check reported them. Green: passes with 72; executor tests pass, including test_uk_weight_matrix_guard.py.

Suppressions added

None.

Real bugs found

None.

Self-review

review-code on this PR's diff, run by a separate agent, found no bugs and no material code-quality issues. It confirmed:

  • the 54 removed entries are exactly this module's;
  • each commit's Pyright output equals its baseline;
  • each change preserves what a run does.

It noted that the new TypeError for an input dataset with no entity data is untested, and that a dataset loaded from a file can't reach it. #873 switches that check to the shared EntityFrames protocol.

Environment variables

No CI, image or deployment configuration changes, and no new environment-variable reads. simulation_runtime.py runs in the Modal simulation workers; it now imports policyengine_simulation_observability.scopes (#864), part of the observability library the images already install.

Testing

  • python -m scripts.rules pyright projects/policyengine-simulation-executor: passes with 72.
  • Executor unit tests: 1555 passed, 22 skipped.
  • Quality guards and rendered rules views: pass. Ruff 0.15.22: clean.

This PR is above the bottom of the stack, so its CI runs only CI complete, which fails by design until the PRs below merge.

🤖 Generated with Claude Code

anth-volk and others added 2 commits October 22, 2026 00:33
The annual simulation's nine spans in simulation_runtime.py now use
scopes.span, so Pyright no longer reports the 47 variables assigned
inside a span as possibly unbound.

With baseline and reform bound, Pyright then reports that a Simulation
can't be passed to _report_calculation_start, which takes a
StageReportingSimulation (the 2 errors were already baselined as
"Unbound | Simulation"). _build_simulation returns one in both branches
(ArtifactBaselineSimulation subclasses it), and _report_calculation_start
relies on that by calling on_first_ensure, so its return annotation now
says StageReportingSimulation.

The executor's baseline drops from 126 entries to 77.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The last 5 Pyright errors in simulation_runtime.py, without changing
what a run does:

- _compile_interval_policy: compile_reform returns None only for an
  empty policy, which the function has already returned, so a None
  result now returns the policy unchanged instead of failing on
  .items().
- _build_uk_weight_replacement_region checks the region prefix against
  a tuple instead of a set display. A set display is typed set[str],
  so the check didn't narrow prefix to the two UK region types Region
  accepts; the tuple keeps their literal types.
- The UK weight-matrix check counts households from the input dataset's
  entity_data, which Dataset types as an optional BaseModel. It now
  reads entity_data with getattr and requires a dict, raising TypeError
  with the dataset's name when none is loaded; a loaded dataset counts
  the same households.
- _build_simulation reassigned its `policy: dict | None` parameter to
  the compiled policy, which may be a Policy; the result now has its own
  name, runtime_policy, used where the reassigned name was.

The executor's baseline drops from 77 entries to 72.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@anth-volk
anth-volk force-pushed the pyright-executor-runtime branch from 53cea05 to 75b2e5a Compare October 21, 2026 20:33

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fix the executor simulation runtime's 54 Pyright errors

1 participant