A reform applied after holders exist leaves them on the old variable, so a variable's default depends on who else is in the simulation

未关闭
#644 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

维护者通常 2 天内回复

还没有人认领这个 Issue。

评估

难度
4/5
预计耗时
3-5 天
新手友好度
35/100
Issue 类型
缺陷
描述清晰度
描述清楚
活跃度
活跃
技术栈
python
领域
backend

调研方向

Start in simulations/simulation.py at apply_reform (around line 927) and _invalidate_all_caches, then populations/population.py at get_holder (around line 169) to see why existing holders keep their old variable. Compare the holder.default_array() path with the np.where(mask, array, variable.default_value) path in _calculate. Done means one reform gives the same result whether every row or only some rows fail defined_for, with a regression test added.

由索引模型根据 Issue 内容生成。

描述

Problem

Simulation.apply_reform replaces variables on the tax-benefit system but leaves any holder that already exists pointing at the old variable. Holder.default_array() then returns the old default, while Simulation._calculate reads the new one, so one calculation can use both.

We traced this in policyengine-core 3.33.12 and confirmed the lines below at 3.34.1, in simulations/simulation.py unless noted:

  • apply_reform (line 927) runs the reform, then _invalidate_all_caches (line 940). That clears stored values and keeps each Holder.variable.
  • Population.get_holder (populations/population.py, line 169) returns an existing holder unchanged.
  • When every row fails defined_for, _calculate returns holder.default_array() (line 1779), which reads the holder's old variable.
  • When only some rows fail, _calculate reaches np.where(mask, array, variable.default_value) (line 1933), which reads the system's new variable.

policyengine-uk exposes this because Simulation.get_known_variables creates a holder for every variable before its after-data reform runs.

Example

We ran policyengine-uk 2da212abc on Core 3.33.12 with a reform that gives pension_credit_entitlement a default of £2,000, applied after data load. A man aged 40 fails that variable's defined_for.

Simulation The man's pension_credit_entitlement
Alone, reform applied after data load £0
With two men aged 63, reform applied after data load £2,000
Alone, reform applied before data load £2,000
Alone, reform applied after data load, then _bind_to_tax_benefit_system() £2,000

His result depends on who else the simulation holds, and the system's default reads £2,000 throughout.

What to change

  • Rebind holders after a reform: have apply_reform call _bind_to_tax_benefit_system() (line 1307), or have get_holder replace a holder whose variable has left the system.
  • Add a test: apply a reform that changes a defined_for variable's default after a holder exists, and assert one result for a population where every row fails and for a population where some do.

Related: #612, #632.

🤖 Generated with Claude Code

主要语言
Python
星标
22
派生
30
平均合并
4 天 20 小时
30 天内合并 PR
26

环境准备

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

PolicyEngine/policyengine-core 的其他 Issue

查看 PolicyEngine/policyengine-core 的全部 Issue

相似的 Issue

更多 Python Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。