Skip to content

Commit 0e159c4

Browse files
lucas-vivierclaude
andcommitted
docs(planning): add design note, readiness report, and change proposal for 2026-04-01
Co-Authored-By: Claude Opus 4.6 <[email protected]>
1 parent 542cdfa commit 0e159c4

3 files changed

Lines changed: 1184 additions & 0 deletions

File tree

Lines changed: 392 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,392 @@
1+
# Design Note — Time Step vs Investment Decisions and OpenFisca Integration
2+
3+
**Project:** ReformLab
4+
**Author:** Lucas
5+
**Date:** 2026-04-01
6+
**Status:** Draft
7+
**Purpose:** Clarify the architectural distinction between simulation time-step semantics and the independent investment decision model, and define how both connect to OpenFisca without violating existing adapter boundaries.
8+
9+
---
10+
11+
## 1. Decision Summary
12+
13+
ReformLab should treat these as two separate but composable concerns:
14+
15+
1. **Simulation time step / simulation mode**
16+
Controls how the model moves through time.
17+
18+
2. **Investment decision model**
19+
Controls whether and how households switch technologies.
20+
21+
They are not the same feature and must not be implemented as one UI or one backend concept.
22+
23+
OpenFisca remains a **single-period computation backend** behind the existing [`ComputationAdapter`](/Users/lucas/Workspace/reformlab/src/reformlab/computation/adapter.py#L11). ReformLab adds orchestration and behavioral logic above that boundary.
24+
25+
---
26+
27+
## 2. Core Distinction
28+
29+
### 2.1 Simulation Time Step
30+
31+
This defines the temporal execution contract.
32+
33+
Examples:
34+
35+
- `annual_iterative`
36+
Run year by year.
37+
38+
- `horizon_step`
39+
Simulate an `X`-year policy horizon as one modeled step and return endpoint outputs.
40+
41+
This is about **time aggregation and execution semantics**.
42+
43+
### 2.2 Investment Decision Model
44+
45+
This defines behavioral dynamics.
46+
47+
Examples:
48+
49+
- no behavior model: households keep baseline technologies,
50+
- discrete-choice model: households choose between alternatives based on costs, subsidies, taxes, and preferences.
51+
52+
This is about **technology adoption / switching behavior**.
53+
54+
### 2.3 Resulting Matrix
55+
56+
These features can exist independently:
57+
58+
| Simulation mode | Investment decisions | Meaning |
59+
|---|---|---|
60+
| `annual_iterative` | off | Standard yearly fiscal/distributional simulation |
61+
| `annual_iterative` | on | Year-by-year adoption dynamics |
62+
| `horizon_step` | off | Endpoint fiscal/distributional effect after `X` years |
63+
| `horizon_step` | on | Endpoint technology distribution after `X` years of sustained policy exposure |
64+
65+
---
66+
67+
## 3. Existing Architectural Boundary
68+
69+
The current backend already provides the right separation of concerns:
70+
71+
- OpenFisca is isolated behind [`ComputationAdapter.compute(population, policy, period)`](/Users/lucas/Workspace/reformlab/src/reformlab/computation/adapter.py#L26).
72+
- Yearly execution is orchestrated above that boundary in [`Orchestrator.run()`](/Users/lucas/Workspace/reformlab/src/reformlab/orchestrator/runner.py#L81).
73+
- Behavioral choice logic already lives above that boundary in [`DiscreteChoiceStep`](/Users/lucas/Workspace/reformlab/src/reformlab/discrete_choice/step.py#L43).
74+
75+
This means:
76+
77+
- **Do not modify OpenFisca to “understand 5 years at once.”**
78+
- **Do not collapse time-step logic into the discrete-choice model.**
79+
- **Do not collapse discrete choice into UI-only configuration.**
80+
81+
---
82+
83+
## 4. Proposed Execution Model
84+
85+
### 4.1 Annual Iterative Mode
86+
87+
This is the current model.
88+
89+
Execution:
90+
91+
1. Start with population state at year `t`.
92+
2. Run `ComputationStep` for year `t`.
93+
3. Optionally run `DiscreteChoiceStep`.
94+
4. Apply carry-forward / vintage updates.
95+
5. Repeat for year `t+1`.
96+
97+
Output:
98+
99+
- yearly states,
100+
- yearly indicators,
101+
- optional yearly decision records.
102+
103+
### 4.2 Horizon Step Mode
104+
105+
This is the proposed new model.
106+
107+
Execution:
108+
109+
1. Start with population state at year `t`.
110+
2. Define horizon length `X`.
111+
3. Compute or derive `X`-year policy exposure for each household / alternative.
112+
4. If the investment model is enabled, evaluate alternatives using aggregated `X`-year economics.
113+
5. Produce endpoint state at year `t + X`.
114+
115+
Output:
116+
117+
- endpoint household state,
118+
- endpoint technology distribution,
119+
- optional aggregated indicators over the horizon,
120+
- no requirement for yearly intermediate outputs.
121+
122+
This is **not** the same as:
123+
124+
- running annual simulation and filtering to year `t + X`,
125+
- or skipping years in the current yearly loop.
126+
127+
---
128+
129+
## 5. How Horizon Step Connects to OpenFisca
130+
131+
### 5.1 What OpenFisca Should Continue Doing
132+
133+
OpenFisca should continue to provide:
134+
135+
- annual taxes/transfers,
136+
- household-level policy calculations,
137+
- alternative-specific economics when population expansion is used.
138+
139+
### 5.2 What ReformLab Must Add Above It
140+
141+
ReformLab must add a horizon aggregation layer that can transform annual economics into `X`-year decision signals.
142+
143+
Examples of aggregated features:
144+
145+
- cumulative subsidy over `X` years,
146+
- cumulative tax burden over `X` years,
147+
- discounted net cost,
148+
- simple payback period,
149+
- expected operating-cost difference,
150+
- total ownership cost over `X` years.
151+
152+
These are not native OpenFisca outputs. They are **derived orchestration-layer constructs** built from repeated or parameterized use of OpenFisca results.
153+
154+
### 5.3 Clean Integration Pattern
155+
156+
Recommended pattern:
157+
158+
1. Expand population by alternatives, as already done by `DiscreteChoiceStep`.
159+
2. Use OpenFisca via `ComputationAdapter` to compute annual household economics for each alternative.
160+
3. Aggregate those annual results into horizon features for each household-alternative pair.
161+
4. Feed those horizon features into the choice model.
162+
5. Write the chosen endpoint technology/state back into ReformLab state.
163+
164+
This preserves the existing architectural rule:
165+
166+
- OpenFisca computes,
167+
- ReformLab orchestrates and decides.
168+
169+
---
170+
171+
## 6. New Backend Concepts Required
172+
173+
### 6.1 Scenario Execution Mode
174+
175+
Add a scenario-level execution mode concept:
176+
177+
```text
178+
simulation_mode = "annual_iterative" | "horizon_step"
179+
```
180+
181+
### 6.2 Horizon Configuration
182+
183+
Add explicit horizon-step configuration:
184+
185+
```text
186+
horizon_step_years: int
187+
```
188+
189+
Optional future extensions:
190+
191+
- discounting strategy,
192+
- exogenous-price series,
193+
- technology cost trajectories,
194+
- income uprating assumptions,
195+
- domain-specific aggregation rules.
196+
197+
### 6.3 Horizon Aggregator
198+
199+
Introduce a new orchestration/domain component, conceptually:
200+
201+
```text
202+
HorizonAggregator
203+
```
204+
205+
Responsibility:
206+
207+
- derive `X`-year endpoint economics from annual policy computations,
208+
- support multiple policy domains without changing `ComputationAdapter`.
209+
210+
### 6.4 Endpoint Output Contract
211+
212+
For `horizon_step`, outputs should include:
213+
214+
- endpoint year,
215+
- simulation mode,
216+
- horizon length,
217+
- endpoint household states,
218+
- endpoint decision records if behavior is enabled.
219+
220+
---
221+
222+
## 7. Validation and Governance Implications
223+
224+
The preflight layer must validate more than it does today.
225+
226+
New checks needed:
227+
228+
- `horizon_step_years > 0`,
229+
- simulation mode is explicitly set,
230+
- selected policy domain supports `horizon_step`,
231+
- required exogenous assumptions are available for the full horizon,
232+
- investment decisions are either disabled or fully configured,
233+
- output semantics are compatible with the requested comparison/reporting view.
234+
235+
Governance and manifests must also record:
236+
237+
- simulation mode,
238+
- horizon length,
239+
- aggregation assumptions,
240+
- discounting assumptions,
241+
- exogenous series used,
242+
- adapter version,
243+
- whether results are yearly or endpoint-only.
244+
245+
This is required so analysts do not compare annual runs and horizon-step runs as if they were identical artifacts.
246+
247+
---
248+
249+
## 8. Frontend Implications
250+
251+
### 8.1 Workspace Structure
252+
253+
Recommended top-level stages:
254+
255+
1. Policies & Portfolio
256+
2. Population
257+
3. Investment Decisions
258+
4. Scenario
259+
5. Run / Results / Compare
260+
261+
### 8.2 Stage Ownership
262+
263+
**Investment Decisions**
264+
265+
- optional stage,
266+
- enable/disable behavior,
267+
- model choice,
268+
- parameters,
269+
- calibration/review.
270+
271+
**Scenario**
272+
273+
- simulation mode,
274+
- annual horizon or horizon-step size,
275+
- seed,
276+
- inherited primary population,
277+
- optional sensitivity population,
278+
- final validation.
279+
280+
### 8.3 Skip Logic
281+
282+
- If investment decisions are disabled, the user can skip that stage.
283+
- If simulation mode is `annual_iterative`, existing yearly output surfaces remain valid.
284+
- If simulation mode is `horizon_step`, result surfaces must make clear that outputs are endpoint-only unless additional decomposition is available.
285+
286+
---
287+
288+
## 9. Recommended Implementation Strategy
289+
290+
### Phase A: Product and Architecture Alignment
291+
292+
Clarify artifacts first:
293+
294+
- separate `time_step / simulation mode` from `investment decisions`,
295+
- define top-level stage ownership,
296+
- define annual vs horizon-step output semantics.
297+
298+
### Phase B: Backend Contract
299+
300+
Implement:
301+
302+
- scenario execution mode,
303+
- horizon-step validation contract,
304+
- manifest fields,
305+
- endpoint result metadata.
306+
307+
### Phase C: Domain Logic
308+
309+
Implement:
310+
311+
- horizon aggregation layer,
312+
- first supported domain for `horizon_step`,
313+
- integration with discrete choice.
314+
315+
### Phase D: UI
316+
317+
Implement:
318+
319+
- dedicated Investment Decisions stage,
320+
- Scenario-stage simulation-mode controls,
321+
- result-surface differentiation between yearly and endpoint outputs.
322+
323+
---
324+
325+
## 10. Recommended First Slice
326+
327+
The best first implementation slice is:
328+
329+
1. support `horizon_step` **without** investment decisions,
330+
2. produce endpoint fiscal/distributional outputs,
331+
3. then integrate the investment model on top.
332+
333+
Reason:
334+
335+
- it validates the execution contract independently,
336+
- it reduces ambiguity in result semantics,
337+
- it avoids coupling two new sources of complexity in the first slice.
338+
339+
Then the second slice is:
340+
341+
4. enable `horizon_step + investment decisions`,
342+
5. return endpoint technology distribution.
343+
344+
---
345+
346+
## 11. BMAD Implementation Path
347+
348+
Recommended BMAD sequence:
349+
350+
1. `bmad-correct-course`
351+
Approve the sprint change proposal and the stage-boundary correction.
352+
353+
2. `bmad-edit-prd`
354+
Add explicit requirements for:
355+
- simulation mode,
356+
- `horizon_step`,
357+
- separation from investment decisions.
358+
359+
3. `bmad-create-ux-design`
360+
Update the workspace IA to a five-stage shell and define skip logic.
361+
362+
4. `bmad-create-architecture`
363+
Fold this design note into the canonical architecture decision set.
364+
365+
5. `bmad-create-epics-and-stories`
366+
Revise existing stories and add backend work for:
367+
- execution contract,
368+
- horizon aggregation,
369+
- endpoint outputs.
370+
371+
6. `bmad-check-implementation-readiness`
372+
Ensure PRD, UX, architecture, and epics are aligned before development.
373+
374+
7. `bmad-sprint-planning`
375+
Plan the first implementation slice:
376+
- `horizon_step` without behavior first,
377+
- then `horizon_step` with investment decisions.
378+
379+
---
380+
381+
## 12. Final Recommendation
382+
383+
Proceed with this principle:
384+
385+
> **Time-step semantics and investment decisions are independent features that meet at the orchestration layer, not inside OpenFisca.**
386+
387+
That gives ReformLab a clean architecture:
388+
389+
- OpenFisca stays a single-period calculator,
390+
- ReformLab owns temporal orchestration,
391+
- ReformLab owns behavioral adoption logic,
392+
- the frontend reflects those as separate analyst decisions.

0 commit comments

Comments
 (0)