Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
c2e3c62
Add optional mortality and ageing population graph
MaxGhenis Sep 4, 2026
19a80e0
Keep optional graph outside historical birth evidence scope
MaxGhenis Sep 4, 2026
71de7dc
Retain failed mortality gate diagnostics and strengthen graph checks
MaxGhenis Sep 5, 2026
4fcc6cc
Integrate mortality graph diagnostics with current Dynamics base
MaxGhenis Sep 9, 2026
a10edd3
Add annual mortality graph trajectories with isolated evaluation
MaxGhenis Sep 9, 2026
73792cc
Run validation for stacked pull requests
MaxGhenis Sep 9, 2026
f60757d
Add opt-in claiming adapter that excludes benefit entrants
MaxGhenis Sep 9, 2026
7426f72
Recover experimental entrant schedules with explicit support boundaries
MaxGhenis Sep 9, 2026
6511a1d
Add opt-in annual population stock-flow accounting
MaxGhenis Sep 9, 2026
40431fa
Add opt-in annual population stock-flow accounting
MaxGhenis Sep 9, 2026
651fcbf
Account for annual mortality trajectory stock and flows
MaxGhenis Sep 9, 2026
af292a1
Validate supplied annual accounting histories
MaxGhenis Sep 9, 2026
d350dfb
Add lossless typed person identity maps
MaxGhenis Sep 20, 2026
a832557
Update tier inventory for person identity tests
MaxGhenis Sep 20, 2026
69a7d22
Keep opt-in identity transport outside historical evidence seal
MaxGhenis Sep 20, 2026
4e7b578
Add lossless typed person identity maps
MaxGhenis Sep 20, 2026
c1b76f4
Update tier inventory for person identity tests
MaxGhenis Sep 20, 2026
d40dc9c
Keep opt-in identity transport outside historical evidence seal
MaxGhenis Sep 20, 2026
c1c4eb9
Record annual forward earnings with lossless provenance
MaxGhenis Sep 21, 2026
5e856d9
Attach explicit covered-wage observations to earnings histories
MaxGhenis Sep 22, 2026
e4e512e
Retain validated mortality outcomes with source identities
MaxGhenis Sep 22, 2026
bd54d9a
Retain validated mortality outcomes with source identities
MaxGhenis Sep 22, 2026
3ba8f17
Retain annual earnings histories through closed-cohort mortality
MaxGhenis Sep 22, 2026
1c59651
Trigger CI for the master-targeted history proposal
MaxGhenis Sep 22, 2026
149af06
Retain original-cohort histories from assembled projections
MaxGhenis Sep 22, 2026
ad1c30b
Store cohort histories without repeating identity maps
MaxGhenis Sep 22, 2026
e77264e
trial merge #420
MaxGhenis Sep 22, 2026
129fba3
trial merge #436
MaxGhenis Sep 22, 2026
d455d01
trial merge #440
MaxGhenis Sep 22, 2026
07bda10
Merge #437
MaxGhenis Sep 22, 2026
62344e7
Merge #438: Recover experimental entrant schedules with explicit supp…
MaxGhenis Sep 22, 2026
a366707
Merge #439: Add opt-in annual population stock-flow accounting
MaxGhenis Sep 22, 2026
c7a22f8
Merge #441: Validate consecutive annual population accounting histories
MaxGhenis Sep 22, 2026
5e74fde
Merge #443: Preserve source person IDs with append-only private keys
MaxGhenis Sep 22, 2026
59c311e
Merge #444: Preserve annual labor and covered-wage histories
MaxGhenis Sep 22, 2026
f620453
Merge #445: Retain validated mortality outcomes with source identities
MaxGhenis Sep 22, 2026
3ca822d
Merge #446: Retain annual earnings histories through mortality
MaxGhenis Sep 22, 2026
8b5fd09
Merge #447: Retain original-cohort histories from assembled projections
MaxGhenis Sep 22, 2026
f088dbe
Merge #448: Store cohort histories without repeating identity maps
MaxGhenis Sep 22, 2026
2242764
Reconcile the landed stack's shared exclusion registry and tier manifest
MaxGhenis Sep 22, 2026
bed0722
Drop the duplicated reachability test the union merge introduced
MaxGhenis Sep 22, 2026
85c8f5f
Graph: stop exporting undrawn next-period outcomes and refuse coerced…
MaxGhenis Sep 22, 2026
17c4e33
Accounting: make typed reconciliation errors picklable and copyable
MaxGhenis Sep 22, 2026
06c82f2
Entrants: validate the projection allocator and normalize numpy years
MaxGhenis Sep 22, 2026
d09edb2
Identity: make PersonIdentityMap picklable; guard the recorder's key …
MaxGhenis Sep 22, 2026
3afac98
Assembled history: refuse float audit years and normalize numpy integers
MaxGhenis Sep 22, 2026
5cb304e
Recollect the tier manifest after the review fixes (unit 2,298; total…
MaxGhenis Sep 22, 2026
6541baf
Apply Black (line length 79) to the files the review fixes touched
MaxGhenis Sep 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ on:
branches:
- master
pull_request:
branches:
- master

permissions:
contents: read
Expand Down
27 changes: 23 additions & 4 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ on:
branches:
- master
pull_request:
branches:
- master

jobs:
pytest-shard:
Expand Down Expand Up @@ -37,16 +35,37 @@ jobs:
- name: Run tests (shard ${{ matrix.shard }}/4)
run: pytest -q --splits 4 --group ${{ matrix.shard }} --splitting-algorithm duration_based_chunks --durations-path .test_durations

graph-integration:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ['3.13', '3.14']
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
- name: Install the pinned optional graph integration
run: pip install -e ".[graph]" pytest
- name: Require typed graph capabilities
run: python -c "from populace_dynamics.graph._compat import require_graph; require_graph()"
- name: Run graph and existing mortality regressions
run: pytest -q tests/test_graph_mortality.py tests/test_graph_mortality_trajectory.py tests/test_graph_trajectory_accounting.py tests/test_m6_engine_refit.py tests/test_m6_engine_steps.py

# Fan-in jobs keeping the branch-protection context names
# ("pytest (3.11)" / "pytest (3.13)") stable across the shard split.
pytest:
name: pytest (${{ matrix.python-version }})
needs: pytest-shard
needs: [pytest-shard, graph-integration]
if: always()
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.14']
steps:
- name: Verify all shards passed
run: test "${{ needs.pytest-shard.result }}" = "success"
run: |
test "${{ needs.pytest-shard.result }}" = "success"
test "${{ needs.graph-integration.result }}" = "success"
104 changes: 104 additions & 0 deletions docs/accounting-history.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Supplied annual accounting history

`populace_dynamics.engine.accounting_history` is an optional wrapper around
the unchanged [annual accountant](stock-flow-accounting.md). It accepts
already supplied frames and declarations. It does not run a projection,
generate events, or establish scientific acceptance.

```python
from populace_dynamics.engine.accounting_history import (
AnnualTransition,
reconcile_history,
)

history = reconcile_history(
[
AnnualTransition(
opening_year=2020,
closing_year=2021,
opening=people_2020,
closing=people_2021,
additions=declared_2021_additions,
exits=declared_2021_exits,
),
AnnualTransition(
opening_year=2021,
closing_year=2022,
opening=people_2021,
closing=people_2022,
additions=declared_2022_additions,
exits=declared_2022_exits,
),
],
identity_contract="single_presence_episode",
)
```

The keyword `identity_contract` is required. Its only supported value is
`single_presence_episode`: within the supplied history, every person ID
may have one presence episode. Every declared departure retires that ID
under this explicitly selected constraint, including a transient who
arrives and departs between endpoints. Declaring that ID again is refused,
even after empty intervening years or as another transient. New, distinct
IDs remain supported after extinction; the wrapper never allocates IDs.

This contract does not assert that emigration or another departure is
permanent in reality. Same-person return needs a separately reviewed typed
linkage to its earlier departure and explicit return semantics. The current
event kinds do not supply that contract. Changing a returnee's ID to evade
the check would not model their identity correctly. Within a single year,
the annual accountant still supports only an arrival followed by a departure,
not exit followed by return.

Periods must form a nonempty sequence in chronological order, each one year
long, with no gaps or repeated periods. At a shared boundary the previous
closing frame and next opening frame must have the same person-ID set and
the same weight for each ID, regardless of row order. The wrapper reuses the
accountant's signed-int64 ID/year and finite nonnegative binary64 weight
validation before constructing snapshots. Weight equality is exact in that
binary64 domain, with no tolerance. A discrepancy between two representations
of the same boundary is refused; weight changes within an annual transition
retain the accountant's separate revaluation components and residuals.
Zero-weight people remain in the roster.

Each returned `HistoryAccount.periods` item is the unchanged annual account.
The result, its nested mappings, and its ID tuples are immutable; `to_dict()`
creates fresh summary containers and omits identifier rosters. The supplied
`AnnualTransition` descriptors freeze field assignment but do not make
caller-owned DataFrames or declaration lists immutable. Reconciliation does
not mutate those inputs or retain their containers in the result. Callers
must not mutate inputs concurrently while they are being read.

`HistoryAccountingError` identifies the failure kind and zero-based period
index, with validated year coordinates when available. Annual failures keep
their original typed exception in `__cause__`, including reconciliation
discrepancies. Its `to_dict()` returns fresh JSON-safe diagnostic containers.
A failure raises without returning a completed or partial history account.

The first opening roster establishes no prior lifecycle history. This wrapper
checks only the supplied periods and declarations: it cannot establish event
truth, cause, completeness, or behavior outside that interval. A transient
omitted from both declaration lists is still unobservable. No horizon totals,
family behavior, benefit calculations, benchmark comparison, acceptance gate,
or population admission claim is introduced.

Only person IDs, years, and person weights enter this accounting interface.
Other frame columns are ignored and their continuity is not checked; this is
not household or geographic context transport. The separately proposed
[mortality graph](https://github.com/PolicyEngine/microcosm-dynamics/pull/436)
keeps its age/sex source and snapshots at their exact schemas and rejects
extra columns; this wrapper does not extend those schemas.
A future separate context contract must preserve household/member links and
household atomic-location anchors assigned by Microcosm once before support
cloning. Dynamics must inherit those anchors, derive larger geographies with
the same versioned mapping, and allow location changes only through separately
declared mobility or migration events. No context adapter, household-weight
mapping, partial-household lifecycle, or group-quarters lifecycle is supplied
here. Canonical export fields and provenance still require parent contract
verification.

The module is not re-exported by the engine initializer and is not called by
the historical engine. Integration into the separately proposed graph is not
included. Direct imports also execute the existing engine initializer and
its broader source dependencies; this wrapper adds only stdlib, pandas, and
the existing accountant to that path.
76 changes: 76 additions & 0 deletions docs/claiming-successor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Opt-in claiming state successor

`populace_dynamics.engine.claiming.apply_claiming` corrects the existing
behavioral no-draw contract for observed disability conversions and previously
claimed people. It is available through an explicit module import. The
registered historical function in `engine.steps` and its assembly remain
unchanged.

## State contract

The adapter draws a behavioral plan only when all four conditions hold:

- `claim_age` is missing.
- Age is at least 50.
- `di_converted` is false.
- The person has not already claimed.

As in the historical adapter, absent or null conversion and claiming flags
are treated as false. This preserves the existing missing-value disposition;
it does not establish that an unknown upstream event was observed not to occur.
The `age` and `sex` columns are still required for all input frames.

`claim_age` represents a planned behavioral age. A conversion with no existing
plan retains a null age, becomes claimed, and receives the current year as its
claim year if it was not already claimed. Existing plans are preserved. A
previously claimed person's claim year is preserved, including a missing year.
Persisted `claimed=True` prevents a new plan from being drawn after the annual
conversion event flag clears. No placeholder age or new lifetime conversion
field is introduced.

## Explicit injection

The successor reuses `ClaimingSchedule` and the `PeriodModules.claiming`
interface:

```python
from dataclasses import replace
from functools import partial

from populace_dynamics.engine.claiming import apply_claiming
from populace_dynamics.engine.loop import ProjectionEngine

# Supply an existing set of period modules and a caller-owned schedule.
successor_modules = replace(
modules,
claiming=partial(apply_claiming, schedule=schedule),
)
engine = ProjectionEngine(successor_modules)
```

The opt-in module must remain outside the historical registered call graph.
It does not promote or replay a scientific candidate, change a locked gate,
or establish benefit-level acceptance. The integration test uses the actual
annual loop and its registry with explicitly synthetic injections for the
other steps; it does not run all eight fitted models.

## Random streams and scope

The PMFs, sex grouping, nearest-reference-year rule (including earlier-year
tie breaking), age-50 threshold, and `ProjectionModule.CLAIMING` stream are
unchanged. Under `ProjectionRNGRegistry`, each eligible person's draw agrees
with the historical adapter when the same stable ordinal is retained.
Removing an ineligible row or changing row order does not change another
person's keyed draw. Converted and previously claimed rows do not need a
claiming PMF for their sex or a claiming ordinal.

Without a registry, the injected batch generator draws fewer values. This
can shift same-seed realizations for other people and subsequent sex groups.
The successor remains reproducible for the same input and seed; cross-version
bitwise parity is not claimed for this fallback. It does not burn discarded
draws to mimic the historical bug.

The adapter consumes the conversion event supplied by the disability step.
It does not infer statutory conversion timing, insured status, quarters of
coverage, PIA, or benefits. A changed upstream conversion definition or
promotion into registered historical execution requires separate work.
Loading
Loading