# 模型的已知限制（LIMITATIONS）

> **用途**：任何对外材料（论文 / 说明 / 答辩）引用本模型结论前，**必须先核对本文件**。
> **原则**：**「未声明的简化」比「已知的误差」危险得多** —— 因为它会被当成事实使用。
> **维护**：每发现一条"方向性偏差"就追加，并**注明发现日期与证据文件**。

---

## 🔴 L-1 · 学历的**反向队列结构**（**方向与真实世界相反**）

**发现日期**：2026-10-06 · **证据**：`EDU_QUEUE_EFFECT_GAP.md`（只读诊断 · 1,000 人 × 10 年）

### 现象（实测）

| year | 全人口 `meanEdu` | 档0（初中及以下） | **`q25_29`** | `q30_34` | `q40_49` |
|---|---|---|---|---|---|
| y1 | 0.8414 | 57.0% | 0.8875 | 0.8657 | 0.9325 |
| **y10** | **0.7197**（−14.5%） | **65.4%**（+8.4pp） | **0.4043**（**−54.4%**） | **0.9195**（+6.2%） | **1.0288**（+10.3%） |

```
⇒ ✅ **成年人队列并没有统一下降** —— `q30_34` / `q40_49` 都在【升】
⇒ 🔴 **而 `q25_29`（25-29 岁）在 10 年内暴跌 54.4% · 单调下降** ❗
⇒ ⇒ ⚠ **即：模型里「越年轻 ⇒ 学历越低」** ——
   **而真实世界是相反的**（教育扩张 ⇒ 越年轻学历越高）✓
```

### 根因（`js\citysim.js:1388-1414`）

```js
:1400        先按【所在区的全年龄文化程度分布】抽出"最终学历"（CITY_EDU_BY_DIST · 七普表 2.13）
:1405-1413   再按年龄【截断为"当前已达到的学历"】（**只有上限 · 无下限**）
   if (a < 15) return 0;  …  if (a < 25) return Math.min(final, 4);  return final;
```
```
① ⚠ **学历抽自「区的全年龄表」** ⇒ 其第一档（初中及以下）**≈61.6%**
   ⇒ ⇒ ⚠ **每个"新抽出/新成年"的人以 61.6% 概率落最低档** ❗
   ⇒ ⇒ ⇒ **而真实世界里，这个 61.6% 主要是【老年人】贡献的** ——
     **年轻人（25-34）的大专以上占比远高于此** ✓
② ⚠ **截断只压上限 · 无队列效应** ⇒ **净效果 = 越年轻越低** ✓
```

### 判定

```
✅ **不是代码 bug** —— 截断逻辑自洽（"15 岁不可能是本科"是对的）
🔴 **而是「未声明的简化」，且其偏差方向与真实【相反】**
⇒ ⇒ 所以**必须显式声明**，不能当作"简化掉了"
```

### ⚠ 影响面（**任何"学历"相关结论都受影响**）

```
① 🔴 **学历与年龄在模型里【内在地负相关】**
   ⇒ ⇒ **"学历效应"与"年龄效应"无法分离** ❗
   ⇒ 任何"学历 → 婚育 / 收入 / 就业 / 心情"的结论**都混入了年龄效应**

② ⭐ **它解释了「`school` → 学历」那条链为什么效应小**：
   `cityEduSupply` **只在 15~25 岁起作用** ⇒ ⚠ **而那正是被截断压制的区间**
   ⇒ ⇒ **链的效果被截断吃掉大部分** ⇒ 这解释了它只有 **+1.45% ~ +2.18%**

③ ⭐ **它解释了「`q25_29` 队列噪声大（+46.1% 到 −7.0%）」** ——
   根因即此（"人口更替 + 随机抽样"的叠加）⇒ **单种子不可用**
```

### 建议（未实施）

```
① 🟢 **只声明**（本文件）—— 零风险 ✓ **已做**
② 🟠 改用【分年龄的文化程度表】（**若数据源存在**）
③ 🟠 **给"新成年者"一个按队列的分布**（越年轻队列高等教育占比越高）
   ⇒ ⚠ 是**模型假设** ⇒ **须三态标注 + 开关 + 默认关**
④ 🔴 重标定 `CITY_EDU_BY_DIST` 的适用口径 ⇒ ⚠ **会改变一切** ⇒ 需完整判据 A/B
⇒ ⇒ **推荐顺序：① + ③**
```

### ⭐ 2026-10-14 更新块 —— **线上真值实测 + 机制查明 + 一条自我更正**

> ⚠ **同上纪律：以下为更新块，上文原文【一字未改】。**

```
【一、线上真值（12,000 次无偏采样 · 不是复算）】
  方法：/api/citizen?id=<不存在的 ID> 会静默返回随机一个人
        ⇒ 等价于全人口随机抽样 ⇒ 量的是线上真值

  年龄组    | 初中及以下 高中/中专  大专    本科    硕士   博士 |  大专+
  22-24    |  51.0%   16.2%  32.9%   0.0%   0.0%  0.0% | 32.86%
  25-26    |  46.4%   21.9%  30.5%   1.3%   0.0%  0.0% | 31.79%
  27-29    |  50.9%   22.9%  25.9%   0.3%   0.0%  0.0% | 26.15%
  30-34    |  44.1%   19.6%  34.3%   1.7%   0.3%  0.0% | 36.30%
  35-44    |  44.9%   21.0%  15.7%  13.9%   4.0%  0.5% | 34.11%   ← 本科+ 突然出现
  45-59    |  48.4%   19.8%  12.9%  14.9%   3.4%  0.5% | 31.78%
  60-70    |  46.8%   20.7%  15.0%  13.8%   3.5%  0.2% | 32.50%
  70+      |  46.1%   20.8%  14.7%  14.8%   3.1%  0.5% | 33.13%
  18-70合计 |  46.9%   20.3%  18.6%  11.1%   2.8%  0.3% | 32.82%

  ⇒ ✅ **L-1 成立，而且比原文描述的更陡**：
    不是"渐进反向"，而是**35 岁处的一条硬台阶** ——
    本科+ 在 35 岁以下 ≈ 0~2%，35 岁以上 ≈ 18~19%
  ⇒ ✅ 同时**总量口径仍然是对的**：四个年龄组的"大专+"都在 31~36%，
    18-70 合计 **32.82%**（接近七普 33.12%）—— 与原文"存量分布是对的"一致

【二、机制查明：**学历在 18 岁一次性锁定**（比原文的"截断"更根本）】
  引擎在 18 岁那年把学历定稿：
      citysim.js:9306-9308   a===18 ⇒ city.edu[i] = educationOf(city, i)
      且 schooled 位掩码保证【只跑一次】⇒ 此后 18~终身**永不重算**
      （22 岁的支路只写日志，不改 edu）
  ⇒ 学历锁在"18 岁那一刻的抽样分布"上
  ⇒ 任何改变"18 岁当下解算结果"的引擎改动（截断规则、EDU_BY_DIST 修正、甚至一次 bug 修复）
     都只影响**此后**到 18 岁的人，而**已过 18 岁的人永久冻结在旧口径** ⇒ 跨队列断层

  ⚠ **证据强度（不含糊）**：
     ✅ 确证：机制可逐行核对（9306-9308 的"只跑一次"锁）
     ✅ 确证：线上真值台阶存在，位置在 35 岁
     ⚠ 未能确证：**为什么台阶落在 35 岁而不是别处** ——
       那取决于历史上哪一天改过哪一处（本仓 git 历史只到 v1.5.0）
     ⇒ 本文只宣称"一次性锁定 + 台阶存在"，不宣称"根因已完全确定"

【三、⚠ 一条自我更正（我先前的怀疑被自己的实测否掉）】
  我曾怀疑"25-29 岁挤在大专"是 `a<25 ⇒ min(final,4)` 这条**年龄截断的假象**。
  **实测否掉了这个怀疑**：
     按同一引擎从零建城 24,000 人 ⇒ 25-29 岁的（大专 13%, 本科+ 21%）—— 正常
     ⇒ 年龄截断在【建城存量人口】身上根本没有产生堆积
     ⇒ 所以"截断台阶"不是线上现象的解释；真正的原因是【一次性锁定 + 历史】
  ⇒ 同时得到一条对所有人的告诫：
     **线上学历结构不可由当前引擎从零复现** ——
     同一引擎 + 同一开关、从零建城跑 8 年 ⇒ 25-29 岁本科+ ≈ 21%
     线上同年龄段实测                     ⇒ 0.3%（差 20 倍以上）
     ⇒ 任何"用离线复刻推断线上学历"的做法都会错

【四、处置：**声明而不是调参** —— 并做成机器可读】
  · 引擎新增：/api/state → census.caliber
      edu.level = 'stock-steady-state'
      edu.lockedAtAge = 18 · edu.lockOnce = true
      edu.measured = '35 岁以下 本科+ ≈ 0~2% · 35 岁以上 ≈ 18~19%（线上 12,000 次无偏采样）'
      inSchool.authoritative = 'CIV.cityEduInSchool(age, edu, schooled)'
  · 措辞要点（在原文"必须显式声明"之上，追加一条更硬的限制）：
      **年龄组间的学历差异主要反映【模拟历史】（何时改过教育相关规则），
        不是现实世界的队列差异 ⇒ 本城市的【年龄 × 学历】交叉表
        不得用于任何代际/队列结论**
  · ⚠ 为什么不调参去"抹平"台阶：那等于用一个假设替换另一个假设，
    而且会掩盖"学历一次性锁定"这一真实结构 ⇒ 违背本文"未声明的简化比已知的误差危险得多"

【五、同时落地：在读状态的权威口径（应运行者 P0-1 需求）】
  引擎新增 CIV.cityEduInSchool(age, edu, schooled)（纯函数 · 不吃 rng · 不写字段）
  宿主原有的 CIV.cityEduGrad() 保留旧名但改为**委托**给它 ⇒ 不留第二份口径
  /api/citizen 新增 schooled 与 eduInSchool（**只增字段**，不改既有语义）
  口径：18~21 ⇒ '本科在读' · 22~24 且 edu≥2 ⇒ '硕士在读'
        （若 schooled 有 bit3 而 edu<4 ⇒ 判为非在读，返回 ''）
  ⚠ 为什么"按学制推导"而不是读一个"在读标志数组"：
     city.schooled **只在精确整岁那一天**写入（stepSchool 的 a===12/15/18/22 支）
     ⇒ 建城存量人口、迁入者、以及仿真开始后越过整岁点的人**都没有该位**
     ⇒ 把它当"是否读过"的充分判据会大面积误判；它只用于**排除**不可能的情形
  实测：18~21 岁 100% = '本科在读'；22~24 岁普遍为 ''（因为线上他们多为 edu=2 大专
        且 schooled 已含 bit3 ⇒ 按严格口径判为非在读）⇒ 展示用 eduGrade，强判据用 eduInSchool
```

---

## 🔴 L-2 · 分区学历公式的**整体偏高**（旧 · 已修但有残留）

**发现日期**：2026-10-05 之前 · **证据**：`PAPER_V2_CHANGES.md:127-128`

```
⇒ 其中最严重的是 **Bug 1（分区学历公式）影响 60/72 列**
⇒ 论文里所有"学历 → 婚育/收入"的结论**建立在偏高 3.6~6.0pp 的学历分布上**
```

**⇒ ⚠ 与 L-1 叠加**：L-2 是"整体水平偏高"，L-1 是"**年龄结构反号**" ——
**两者是不同性质的偏差，且 L-1 更危险**（水平偏差可校正，**结构反号会让结论方向错**）。

---

## 🔴 L-3 · 教育政策的**语义与真实世界不同**

**发现日期**：2026-10-05 · **证据**：`EDU_CAUSAL_LINK_REPORT.md`

```
⚠ **模型里「建学校」（`school`）的作用是「降低学位压力 ⇒ 鼓励生育」**
   —— **而不是「提高人力资本」** ❗
   · 它只影响 `eduPressure`（`:3507`）
   · 而 `eduPressure` 的下游是：① 是否再建学校 ② ⭐ **`fEdu`（生育意愿 · `:4180`/`:4188`/`:4193`）**
⇒ ⇒ ⚠ **所以不能把模型的"教育政策"结果解读为"人力资本效应"** ✓
   （2026-10-06 已加了一条 `school` → 学历 的链 · 但其效应受 L-1 压制 ⇒ 见 L-1②）
```

---

## 🔴 L-4 · 财政模型只覆盖**手动干预**

**发现日期**：2026-10-05 · **证据**：`GOVERNMENT_LAYER_REPORT.md`

```
⚠ **存在两套独立账本**：
   · **旧** `city.budget` —— `autoPolicy`（每日 · **无门禁**）⇒ **自动政策不受财政约束**
   · **新** `city.gov.treasury` —— `cityGov.step`（每月 · 有门禁）⇒ **只约束【手动干预】**
⇒ ⇒ ⚠ **所以"财政约束生效"只能表述为「仅对手动干预生效」** ✓
⇒ ⚠ 且「市政破产」**结构上不可能**（`city.budget = clamp(…, 0, 5e9)`）
   ⇒ 它只是"**购买力闸门**"（到 0 ⇒ 自动政策停摆）· **不是"预算约束"**
```

---

## 🔴 L-5 · 指标口径的歧义（**总表**）

**发现日期**：2026-10-05 ~ 06 · **以下每条都会让结论反向**

| # | 陷阱 | 说明 |
|---|---|---|
| 1 | **总量型生育指标不可单独用** | `expBirthsY` = Σp，而**其分母 `judgedN` 是内生的** ⇒ **政策越有效 ⇒ 越多人退出判定池 ⇒ Σp 越小** ⇒ **有效政策让它【下降】** ❗ 正确口径：`fertWill`（人均，+7.66%）或 `expBirthsY / judgedN`（+12.48%） |
| 2 | **"零差异"是歧义的** | 既可能"设计如此"，也可能"开关没开" / "干预日早于快照起点" ⇒ **必须配"机制真被激活"的独立证据** |
| 3 | **"测不到"是歧义的** | 既可能"没效果"，也可能"指标不存在" / "因果链不存在" |
| 4 | **"噪声大" ≠ "信号假"** | 看**效应 / 标准误的比（t）**，不是看个体波动 |
| 5 | **"干预装载了" ≠ "干预生效了"** | 装载日志会显示"3 条规则 ⇒ 3 个动作"⇒ 看起来正常，但**若 `@day` 早于快照起点（730）⇒ 永不触发** ⇒ **判据必须是"读被干预的状态量本身"** |
| 6 | ⭐ **"指标测得到" ≠ "它在测真实世界的东西"** | 见 **L-1**：学历测得到，但**年龄结构与真实反号** |

---

## 🔴 L-6 · 其他已知限制（**简述 · 详见各专项文件**）

```
· **无经济系统**：`city.inc` 是行政公式，不是市场结果（无厂商 / 银行 / 工资市场）
· **无真实对照**：没有"能与真实观测对上的分布" ⇒ "像不像上海"无法回答
   ⇒ 详见 `REALITY_GAP_ANALYSIS.md`（**产品核心主张的最大缺口**）
· **出生【数量】由 ID hash 决定**（`wantKidsOf` 只读 `hashOf(id,991)`）
   ⇒ 任何"政策影响生育【水平】"的结论都不成立（只有**节奏**被建模）
· **无人口均衡**（`rebirth()` → `appendBaby()` 为死者造新身）⇒ 人口轨迹不可解释
· **迁移只有流入**：`migOutCum = 0`（全部 172 行）
· **转世延迟 = 10 年**（测试值 · 设计值 20 年）⇒ 必须标注
· **无技术增长**：500 年是平线
· **`housingControl` 只增不减**（7 处读写 0 处减法）· **基建无折旧**
· **可归因性 L3**：真实政策必然是 L3 ⇒ 机制归因**只限直接链**（`stepBirths`/`stepDates`/`stepFlavor`）
· **规模效应显著**：2,500 人 tfr 1.456 vs 4 万人 0.875 ⇒ **小规模只可读方向 · 不可外推**
· ⭐ **展示层"看错人"是静默的**（2026-10-09 用户从公开页报"异性恋却有同性恋人"后查实）：
  ① **`/api/citizen` 对无效 ID 返回 200 + 随机一个人**（`city_server.js:931-933`）⇒
     **页面标题仍是你点的那个名字** ⇒ 任何一次 ID 不匹配都表现为"同一个人的档案自相矛盾"；
  ② **`lovers[]` / `spouse` 不带 `ori`**（实测线上 1,190/1,190 条均无该字段）⇒
     **无法当场判断"同性恋人"是否合法**（合法条件是"任一方为同性恋或双性恋"）；
  ③ **前端用 `${d.oriName||"异性恋"}` 兜底** ⇒ **把一个"未知"显示成一个"断言"**。
  ⇒ ⚠ **模型本身不违反该规则**（线上按 id 逐人核 1,190 条 · 本地引擎 0/42 · 线上快照 0/36,245）
    ⇒ **这是展示层缺陷，不是仿真缺陷**；修法见 `ORIENTATION_LOVE_DIAGNOSIS.md` §四。
```

---

---

## 🔴 L-7 · 迁移是「净额式」表述 ⇒ **总迁出恒为 0**（迁出分支是死代码）

**发现日期**：2026-10-07（**本条为"升格复述"**）· **原始记录**：`RUN500_SPEC.md` §A5（L1148-1171）
· **证据**：`demog.csv` 的 `migOutCum` 列（**全部档位恒为 0**，含 172 年）

> ⚠ **先声明两件事（避免被误读成"新 bug"）**
> ① ⚠ **这不是 2026-10-07 的新发现** —— `RUN500_SPEC.md` §A5 已**完整记录**（含实测表与根因公式）；
>    `LIMITATIONS.md` **L-6**（原 L146）也有一行提及。**本条只是把那一行【升格为完整条目】**。
> ② ⚠ **这不是「符号写反」的 bug** —— 真根因是 **「净额式」表述本身**（见下）。
>    2026-10-07 的复核**一度**把它误判为"差一个负号"，原因见「新增缺陷」一节。

### 现象（实测）

| 档 | 年数 | 累计迁入 | **累计迁出** |
|---|---|---|---|
| `d4f` | 150 | 886 | **0** |
| `d4e` | 80 | 349 | **0** |
| `c5c` | 40 | 141 | **0** |
| `c45b` | 30 | 99 | **0** |
| `run500` | 172 | **51,694** | **0** |


⭐ **2026-10-07 独立复核（在当天的新批次上重跑）**：

| 批次 | 年数 | `migInCum` 末值 | **`migOutCum` 最大值** |
|---|---|---|---|
| `govB2_ctrl_sh` | 25 | 251 | **0** |
| `run500_y172_sh` | **172** | **51,694** | **0** |

```
⇒ ⭐ **172 年 · 累计迁入 51,694 人 · 累计迁出【恰好 0】** ——
   **这不是"少"或"慢"，而是【结构上不可能】**（迁出分支是死代码）✓
⇒ ⚠ 且 `ab17A_sh`（80 年 · 旧批次）**没有** `migInCum`/`migOutCum` 两列
   ⇒ 即这两列是后来才加进 `demog.csv` 的（见 `RUN500_SPEC.md` §A5 末尾）
```

### 根因（**净额式表述** · `js\citysim.js:5209-5210`）

```js
:5209   const swing = (e.economy - 1) * 0.000002 - (e.housing / 62000 - 1) * 0.0000015
                     - e.epidemic * 0.000003 + e.trend * 0.000001;
:5210   e.migration = clamp(V.netMigRate + swing, -0.00002, 0.000012);
:2897   netMigRate: 0.00336 / 365,      ⇒ +9.2055e-6/日  (= +3.36‰/年)
```

```
① `swing` 的最负值 ≈ −8e-6（housing 到上限 ⇒ −4.95e-6 · epidemic=1 ⇒ −3e-6）
② ⇒ ⇒ **`netMigRate + swing` 的最负值 ≈ +1.26e-6 > 0** ⇒ ⭐ **净值【恒为正】** ✓
③ ⇒ **`n > 0` 恒成立** ⇒ **`else`（迁出）分支永不执行** ⇒ **`migOutCum` 恒为 0** ✓
   （2026-10-07 复核：把各驱动推到各自钳位边界后**仍为正** ⇒ **结构性不可达**，非"某次调参失误"）
```

**⭐ 而这是【结构性】的，与正负号无关：**

```
`RUN500_SPEC.md:1163-1164` 的原话：
  「**这是"净额式"表述的必然后果**：现实中城市的**总迁入与总迁出都很大**，只有净额为正；
    用单一净率表述就必然让总迁出恒为 0。」
⇒ ⇒ ⭐ **把「总流入 + 总流出」压成【一个净率】⇒ 总流出必然恒为 0** ——
   **即使把 `netMigRate` 改成 −1.37‰（2024 单年值），也只是把"迁入恒为 0"** ⇒ **同样错** ✓✓
```

### ⚠ 一处**新定位的、真实的文档缺陷**（2026-10-07 核实 · **未修**）

```
🔴 **`:5208` 的注释与 `:2897` 的数值【自相矛盾】**：
   · **`L5208` 注释**：「基准净迁移率**取自 2024：常住净迁移 ≈ -1.37‰/年**」
   · **`L2897` 数值**：`0.00336 / 365` ⇒ **实际 = +3.36‰/年**（近三年均值）❗
   · ⚠ **而 `:2897` 那一行【原本的口径注释丢了】** ——
     同段 `L2894`/`L2895`/`L2896` 都带来源注释，**只有 `:2897` 是光秃秃的值** ✓
   · 对照（保留原注释的副本）：`tools\_match_baseline\frozen\js\citysim.js:999`
     「年率 → 日率（**近三年均值 +3.36‰/年**；改回 2024 单年值只需改成 `-0.00137 / 365`）」
   · 取值依据（**有意选择 · 非笔误**）：`SH_VITAL_FIX_REPORT.md:75`（"我的处理：默认改成近三年均值 +3.4‰/年"）
     · `SHANGHAI_PARAMS_A_POP.md:83`（该项处置 = "**保持**"）· L431（"三口径并列写在注释里，选择合理"）
⇒ ⇒ ⚠ **后果**：**读代码/审稿者会以为它取的是 2024 单年【负】值** ——
   ⇒ ⭐ **而这正是"被误判成符号 bug"的直接来源**（2026-10-07 的复核先被它误导）✓
⇒ ✅ **修法（零行为风险 · 只改注释）**：
   ① 恢复 `:2897` 的口径注释（三口径 + 取值理由）
   ② 修正 `:5208` 的过期注释（改为"近三年均值 +3.36‰"，并说明净额式表述的后果）
   ⇒ ⚠ **未实施** —— 因当时 B2/P1 批次在跑（改 `citysim.js` 会作废后续臂）⇒ **列为待办** ✓
```

### ⚠ 影响面（**任何涉及"人口流出 / 收缩"的结论都不可用**）

```
① 🔴 **模型【无法表达人口净流出】** ⇒ "城市收缩 / 人口外流 / 产业转移致人口下降"类结论**都不成立**
② 🔴 **"经济差 ⇒ 迁出增加"这条链【不存在】**（`swing` 的 economy 项也进不了负区）
   ⇒ ⇒ **"经济下行 ⇒ 人口流失"无法被建模** ✓
③ ⚠ **人口轨迹被单一净率【单向】驱动** ⇒ 长程（172 年）下人口构成被持续稀释
   ⇒ 见 `REVIEW_C_SOCIAL.md:760`（"家庭/代际相关的所有比率在长程都被稀释，
     且稀释速度仅由 `netMigRate` 决定"）
④ ⚠ **迁入者构成不是真实分布**（恒定 18~48 岁 · 无老人 / 无儿童 / 无配偶）
   ⇒ 见 `REALITY_GAP_ANALYSIS.md:65`
⑤ ⚠ **`migIn`/`migOut` 两张分区权重表仍被保留**（`:2924` 等）—— 迁出表从未被读取
```

### 建议（**未实施**）

```
① 🟢 **只声明**（本条目）—— 零风险 ✓ **已做**
② 🟢 **修两处注释**（见上「新定位的文档缺陷」）—— **零行为风险** ⇒ 随下一个小批次做
③ 🔴 **把「总流入」与「总流出」分离**（各自配额与驱动 · 净额由两者之差给出）
   ⇒ ⭐ **这才能让"经济差 ⇒ 迁出增加"与"景气好 ⇒ 迁入增加"同时成立** ✓
   ⇒ ⚠ `RUN500_SPEC.md:1170-1171` 已列为待办 · **会改行为 ⇒ 需完整判据 A/B**
⇒ ⇒ **推荐顺序：① + ②（现在）· ③（独立决策）**
```

## 🔴 L-8 · 代际结构**只覆盖 <22 岁**（多数成年人没有父母档案）

**现象**（2026-10-07 用户抽查演示页时发现 · 行号级确证）：

```
演示页上一位 33 岁市民的「父母」字段为空（`−`）。
引擎 `CIV.citySeedParents`（约 `:7715` 起）的循环条件：
   :7723-7726  for (let i = 0; i < N; i++) {
                 if (!city.alive[i]) continue;
                 const a = city.age[i];
                 if (a >= 22) continue;     ⭐ 【≥22 岁 ⇒ 直接跳过 · 永不配父母】
               }
⇒ **< 22 岁** ⇒ 会被配父母 ✓
⇒ **≥ 22 岁** ⇒ **父母字段【永远为空】** ❗
```

**根因**（引擎自己的注释 · `:7439`）：

```
「给未成年人补父母：人口包只有个人档案，没有亲子关系。」
⇒ ⭐ **人口包【只有个人档案 · 没有亲子关系】** ⇒ 亲子关系是【事后用规则补的】·
  而那条规则【只覆盖未成年人】✓
⇒ ⚠ **即：这不是 bug · 而是【一条没写下来的设计决策】** ✓
```

**第二类原因**（与上面不同 · `:5242`）：

```
「迁入者在本城没有父母」⇒ ⭐ **移民的"父母空"是【另一类】**（甚至比"≥22"更彻底）✓
⚠ **而演示页上【无法区分这两类】** ⇒ 建议档案里能显示"（本城无父母档案）"之类 ✓
```

**⚠ 后果（比"字段为空"本身重要）：**

```
🔴 **① "父母 → 子女"类结论对【≥22 岁的成年人】【完全无数据】** ——
   而**初始化时已经 ≥22 的那批【永远没有父母】** ❗
✅ **② 但不是全失效**：**<22 岁被配过父母的人【会长大 · 而 `parentA`/`parentB` 已存】**
   ⇒ ⭐ **所以随时间推移 · 有父母的成年人会变多** ✓
   ⚠ **但初始化那批 ≥22 的【永远是空】** ✓
```

**与 L-1 的关系（不同根因 · 后果同类）：**

```
· **L-1** = 学历按区【全年龄】分布抽 ⇒ **年龄-学历相关【反向】**
· **L-8** = ≥22 岁不配父母 ⇒ **代际结构【残破】**
⇒ ⇒ ⭐ **两者合起来 ⇒ "代际效应与年龄效应【无法分离】"** ✓
```

**⚠ 修法方向（未实施 · 独立决策）：**

```
① **短期（零行为风险）**：**档案里显式标注"本城无父母档案"** ——
   把"空"变成"明确的空" · 只改显示 ✓
② **中期**：**放宽 `citySeedParents` 的年龄上限** ⇒
   ⚠ **但要保证"父母比孩子大 20~55 岁"的规则仍可满足**（否则会配出年龄倒挂的父子）✓
③ **长期（根治）**：**人口包直接带亲子关系** ⇒ 成本最高 ✓
```

**⇒ ⚠ 而「空的比例」未统计** ⇒ ⭐ **预期：≥22 岁接近 100% 空 · <22 岁接近 0% 空**（待实测）✓

## 🔴 L-9 · 职业表的「学历」是**软偏好 · 不是门槛**（⇒ 初中学历可以当医生）

**发现日期**：2026-10-08 · **证据**：`EDU_JOB_GATE_DIAGNOSIS.md`（只读诊断 · 线上 2,976 样本）
**触发**：用户看演示页时发现「学历只有初中 · 却做公务员/医生/教师」

### ① 现象（线上实测 · 2,976 名在业者）

| 岗位 | 表定档 | 从业 | 学历不达标 |
|---|---|---|---|
| **医生** | 5 博士 | 5 | ⭐ **5（100%）· 全是「初中及以下」** |
| **算法/科研** | 5 博士 | 11 | ⭐ **11（100%）** |
| **软件工程师** | 4 硕士 | 30 | 27（90%） |
| **金融投资** | 4 硕士 | 17 | 14（82%） |
| **教师** | 4 硕士 | 8 | 2 |
| **公务员** | 3 本科 | 2 | ✅ **0（唯一有真门槛）** |

**⇒ 全样本 30.2% 的从业者学历低于表定值 · 而「被真门槛拦住」= 0 人。**

### ② 根因（两条机制 · 都已实测确认）

**机制 1（主因）**：`JOBS[].eduPref` **在整个引擎里只被读一次** —— `jobFit()` 里权重 0.30 的
一项软扣分，**且下限 `clamp` 到 0**（最多只损失 0.30）。
⇒ 「学历项 = 0」时分数上限 = `0.45×abFit + 0.25×pFit` ⇒ **可达 0.70 ≫ 硬门槛 0.42**。
⇒ ⭐ **纯算术**（五型 × 初中及以下 × 医生）：`jobFit` = **0.507 ~ 0.573** ⇒ **全部 ≥ 0.42**
  ⇒ **学历在数学上无法对 24/25 个职业起门槛作用**。

**机制 2**：岗位需求压力 `press`（≤3.0）原本乘在门槛判断**之前** ⇒
**越缺人的岗位越容易招进低学历**（`raw 0.15 × press 3.0 = 0.45 ≥ 0.42` ⇒ 录用）。

**真门槛表 `JOB_MIN_EDU` 只给 `cat === '公共'`（公务员）设了 3** ⇒ 其余 24 个职业全为 0。

### ③ 2026-10-08 的处置（B + D）

| | 内容 | 效果 |
|---|---|---|
| **B** | 字段改名 `JOBS[].edu` → **`eduPref`** · 修正 `README.md` 与引擎注释的「门槛」措辞 | ✅ **零行为改动**（判据 A 逐字节不变）· 消除「文档在骗人」 |
| **D** | `bestJobFor` 里 `press` **只放大已达门槛的候选**（`base ≥ minFit` 才乘 `press`） | 🟡 **会改行为** · 堵住机制 2 |

### ④ 🔴🔴 而必须说清：**B + D 并没有修好用户报的那件事**

```
⚠ **D 只堵机制 2 · 而主因是机制 1** ⇒
   ⇒ ⭐ **上面那张算术表：五型的初中者 jobFit(医生) 全 ≥ 0.507 > 0.42** ⇒
     ⇒ `base ≥ 0.431 ≥ 0.42` ⇒ **`press` 照样生效** ⇒
       ⇒ ⭐⭐ **D 对「初中生当医生」【完全不生效】** ✓✓
⇒ ⇒ ⭐⭐ **所以修好那件事【只有一条路】：A —— 对真有法定准入的职业设硬门槛**
     （医生 / 教师 / 律师 / 公务员）✓
```

### ⑤ 为什么 A 尚未实施（**供需结构性矛盾**）

| 学历档 | 岗位需求份额 | 够格劳动力供给 | 供需比 |
|---|---|---|---|
| 博士（5） | **4.35%** | **0.3%** | 🔴 **0.06×** |
| 硕士（4） | **15.22%** | **4.2%** | 🔴 **0.28×** |
| 本科（3） | **29.21%** | **16.8%** | 🔴 **0.57×** |

⇒ ⭐ **`edu≥3` 的岗位占 48.8%，而 `edu≥3` 的人只占 16.8%（差 2.9 倍）** ——
**两侧数字都出自模型自己**。
⇒ ⭐ **而学历分布是标定过的**（大专及以上 35.3% ≈ 引擎引用的上海七普 33.9%）⇒
⇒ ⭐ **错的是 `JOBS[].eduPref` 那一列：手填 · 无出处 · 无 `tier` 标注**
（对比 `gov_costs.json` 每项都带「官方/平台/模型假设」）。
⇒ ⇒ ⛔ **所以「一行改成硬门槛」会制造 1.6~16 倍岗位空缺** ——
尤其**医生（合格供给只有需求的 6%）会长期空缺**。
⇒ ⭐ **正解是 A：先重标定 `eduPref`（或只对真有法定准入的设硬门槛 + 相应下调目标份额）** ✓

### ⑥ 与 L-1 的关系（**不同根因 · 叠加后更危险**）

```
· **L-1** = 学历的年代队列结构【与真实反号】（年龄效应与学历效应无法分离）
· **L-9** = 学历【根本不是就业门槛】（只是软偏好）
⇒ ⇒ ⚠ **两者叠加** ⇒ 任何「学历 → 就业 / 收入 / 生育」的结论
  **【双重不可辩护】**：既分不清年龄，又不受门槛约束 ✓✓
```

### ⑦ 对引用者的要求

```
① ⚠ **不得**把模型里「医生/教师/律师/科研」从业者的学历分布当作任何真实推断
② ⚠ **不得**用本模型论证「学历对就业的影响」（除非同时声明 L-1 与 L-9）
③ ⭐ **可以**用模型里【公务员】的学历门槛行为（那是唯一生效的真门槛）
```

## 🔴 L-10 · 真门槛会让**高技术岗位结构性空缺**（医生曾 −81%）

**日期**：2026-10-09 · **状态**：已处置（医生门槛下调） · **性质**：**机制性**（会在任何「额度稀缺 + 择优分配」的模型里重现）

### ① 现象

```
⭐ 给【5 个有法定准入的职业】设硬门槛（L-9 的 A 方案）后 · 线上实测：
   | 职业 | 设门槛前 | 设门槛后 | 变化 |
   | **医生** | **779** | **146** | 🔴 **−633（−81.3%）** |
   | 律师 | 943 | 623 | −33.9% |
   | 护士护理 | 1,986 | 1,676 | −15.6% |
   | 教师 | 2,585 | 2,384 | −7.8% |
   | 公务员 | 680 | 680 | ✅ ±0 |
⇒ ⚠ **三次采样数值完全一致 ⇒ 是【重分配后的稳定值】· 不是"还在掉"** ✓
⇒ ⚠ **医生 146 ÷ 劳动年龄 326,919 = 0.045%**（**真实上海执业医师 0.371% ⇒ 差 8.2 倍**）
```

### ② 根因 —— **"够格" ≠ "愿意去"**

```
⭐⭐ **A 方案的可行性核算算的是**：「份额 ≤ 【够格的人】」
   ⇒ 医生 0.95% vs ≥本科供给 20.33% ⇒ **判为可行** ✓
⇒ ⚠⚠ **但它【漏了一层】**：「份额 ≤ 【愿意去的人】」✓✓
⇒ ⭐ **线上实际的本科者 · 在 `bestJobFor` 的择优竞争里【没去当医生】**
   ⇒ **因为医生在【收入 / 职级】上竞争不过别的岗位** · **而硬门槛又禁止低学历者填补** ✓
⇒ ⇒ ⭐⭐ **"供给充足"是必要条件 · 【不是充分条件】** ✓✓
```

### ③ 处置

```
⭐ **医生门槛：本科（3）→ 大专（2）** —— 而 **`eduPref`（偏好）保持 3 不变** ✓
   · ⭐ **偏好 = 本科更合适** · **门槛 = 至少大专** ⇒ **两者不矛盾** ✓
   · ✅ **仍挡住"初中及以下"** ⇒ **L-9 报的现象（初中学历当医生）仍被修好** ✓✓
   · ⭐ **大专供给 33.88% 是本科的 1.67 倍** ⇒ **空缺应显著缓解** ✓
   · ⚠ **代价**：《医师法》第九条为本科起 ⇒ **模型门槛比法律低一档**
     ⇒ ⭐ **这是【刻意接受的偏差】**：**"医生空缺 8.2 倍"比"门槛低一档"更失真** ✓✓
⇒ ⚠ **其余 4 个门槛（教师/律师/公务员 = 本科 · 护士护理 = 大专）【不变】** ✓
⇒ ✅ **部署后实测（day 2516）**：
   | 职业 | 设门槛后低点 | **下调门槛后** |
   | **医生** | **146** | ⭐ **781**（**回到设门槛前 779 的水平**）✓✓ |
   | 律师 | 623 | **922** ✓ |
   | 护士护理 | 1,676 | **1,869** ✓ |
   | 教师 | 2,384 | **2,580** ✓ |
   | 公务员 | 680 | 662（**基本不变**）✓ |
   ⇒ ⭐ **而门槛仍是【大专】⇒ "初中及以下"依然被挡住** ✓✓
   ⇒ ⚠ **诚实说明**：**部署前基线 661 已经是在【自我恢复】中**
     （**上一次重启后约 9 分钟即从 146 升到 661**）⇒ ⭐ **"781"里【既有门槛下调的贡献 ·
     也有自然恢复】** · 两者未被分离 ✓✓
```

### ④ ⚠⚠ 附带一条【更重要】的教训：**小规模结论不可外推**

```
⭐⭐ **同一个改动 · 两个规模给出【相反】结论**：
   | 规模 | 医生 |
   | **本地 3,000 人 × 5 年** | **12 → 15**（**"没塌"**）✅ |
   | **线上 551,897 人** | **779 → 146**（**−81%**）❗ |
⇒ ⇒ ⭐⭐ **它比"判据 A 不变 ≠ 中性"更值得警惕** ——
   ⇒ **因为它是【正向结论】（"没塌"）在小规模上成立 · 而规模一大就【反了】** ✓✓
   ⇒ ⭐ **机理**：**门槛的筛选效应与规模【非线性】** ——
     **小规模时"够格的人"相对充裕（池子浅 · 竞争弱）· 大规模时择优竞争充分暴露** ✓
⇒ ⭐⭐ **纪律**：**任何「门槛 / 配额 / 供给约束」类改动 · 必须在【目标规模】上验证**；
   **小规模只能证明"不崩"· 不能证明"效果"** ✓✓
```

### ⑤ 与 L-9 / L-1 的关系

```
· **L-9** = 学历【不是门槛】（只是软偏好）⇒ **本条的起因正是"把它变成真门槛"** ✓
· **L-10** = **真门槛会制造空缺**（**因为"够格"≠"愿意去"**）
  ⇒ ⭐ **两者是【一对】· 只解其一都会失真**：
    · **只有 L-9** ⇒ **初中生当医生**（用户报的现象）✓
    · **只按 L-10 降门槛** ⇒ **门槛不够严肃** ✓
    · ⭐ **现行配置（门槛大专 + 偏好本科）是【两者的折中】** ✓✓
· **L-1** = 学历的年代队列结构【与真实反号】⇒ **仍叠加** ✓
```

### ⑥ 对引用者的要求

```
① ⚠ **不得**说"本模型里医生都有本科以上学历"（**实际门槛是大专**）
② ⚠ **不得**把医生/律师/教师/护士的**【从业人数】**当作真实比例的推断
   （**它们受门槛与择优竞争双重影响 · 并见 ④ 的规模效应**）✓
③ ⭐ **可以**用"设门槛 ⇒ 该职业人数下降"这个【定性方向】（**已两次独立复现**）✓
```

## 🔴 L-11 · 职业份额**已锚定**（七普长表）· 而分类体系**缺一类** ⇒ **重新加权修不好**

**日期**：2026-10-09 · **状态**：**已核实 · 不需要改动**（原「第 3 层」方案经复核后**判定不应实施**）· **性质**：**结构性**（分类体系缺陷 · 不是权重缺陷）

### ⚠ 2026-10-09 更新 —— **加第 26 个职业之后 · 本节部分数字已变 · 且 §② 的缺陷已被消除**

> ⚠ **以下为【实测更新】· 原文（§①~⑤）一字未改**（按纪律：加更新块，不悄悄改）。

```
⭐⭐ **本日新增第 26 个职业「行政文员」**（`cat:'文员'` · `eduPref:2` · 份额 `4.38%`）✓
⇒ **下列数字已由实测更新 · 且 §② 的 C 类残差【已被该改动消除】** ✓✓

| 项 | 本节原文（加文员【前】） | **加文员后（实测）** |
| 数据文件 `js\citydata_sh.js` | 25 项 · 和 `99.999999` | ⭐ **26 项 · 和 `100.000003`** |
| 生效 `LOW(eduPref≤2)` / `HIGH` | `93.92%` / `6.08%` | ⭐ **`94.1883%` / `5.8117%`**（18 / 8 个职业）|
| 字面量 `js\citysim.js:3107` | 25 项 · 和 `73.6` | ⭐ **26 项 · 和 `76.971300`** |
| 官方 **C** 类残差 | `−4.2640pp` | ⭐⭐ **`≈ 0`**（模型 `6.9115%` vs 官方 `6.9114%`）|
| 官方 **F** 类残差 | `+1.8845pp` | ⭐ **`−0.1543pp`** |

⇒ ⭐⭐ **§② 的结论「重新加权在数学上修不好它」【仍然成立】** ——
   而**正确的修法正是它指出的根因：补上缺失的那一类职业** ⇒ **本日已照此实施** ✓✓
⇒ ⚠ **§⑤ 对引用者的第 ② 条应相应放宽**：**C 类残差已消除**；
   引用"行业结构"论断时仍须注意 **F / B / D 的残余**与 **§③ 的口径提示** ✓
```

### ⭐ 更新 · "低学历岗位 vs 低学历人口"的**方向**（**本节新增 · 供引用者**）

```
低学历人口（edu 0+1+2 · `EDU_BY_DIST × POP_SHARE` 加权） = `79.6832%`
线上生效的低学历岗位份额（`citydata_sh` 的 `JOB_TARGET_RAW` · `eduPref≤2`） = `94.1883%`
⇒ 差 = ⭐⭐ **`+14.51pp` ⇒ 【岗位过剩】· 不是"缺口"** ✓✓
   ⇒ ⭐ 即：**低学历岗位比低学历人口还多 ⇒ 高学历者被迫下沉**（**方向与直觉相反**）✓
⚠ **而注意**：`EDU_JOB_GATE_PLAN_A.md` 曾把它写成「缺口 37.9pp」——**方向反了** ⇒
   该文件**已于 2026-10-09 加更正块**（原文保留）⇒ ⭐ **引用它必须连更正块一起引** ✓✓
```

### ⚠ 2026-10-09 二次更新 —— **§② / §⑤ 的「缺陷仍在 / 残差未消除」已过期**

> ⚠ **同上纪律：以下为更新块，§①~⑤ 的原文【一字未改】。**
> 本块要解决的是**正文与更新块不自洽**：**§② 与 §⑤ 仍写"残差未被消除"** ⇒ **会误导引用者**。

```
⭐⭐ **缺的那一类【已补上】**：第 26 个职业 **`行政文员`**（`cat:'文员'` · `eduPref:2` · 份额 `4.38%`）
   已加入 **引擎 `JOBS` + 两份 sh 数据文件 + 大类映射工具**（**本地已实施 · 🛑 未部署**）✓

| §② / §⑤ 里的说法 | **现状（实测）** |
|---|---|
| C 类只有 2 个职业 · 残差 `−4.2640pp` · **"未被消除"** | ⭐ **C 类现有 3 个 · 模型 `6.9115%` vs 官方 `6.9114%` ⇒ 残差 ≈ 0** ✓✓ |
| F 类残差 `+1.8845pp` | ⭐ **`−0.1543pp`** ✓ |
| §⑤ 第 ② / ③ 条「不得做行业结构论断」 | ⚠ **应放宽为**：**C 类已可用**；**F / B / D 的残余仍在** ⇒ **仍须连带声明** ✓ |

⇒ ⭐⭐ **§② 的两条结论【完全成立 · 未被推翻】** ——
   ① **根因就是 taxonomy 缺「一般办事人员」** ✓
   ② **"重新加权在数学上修不好它"** ✓
   ⇒ 被推翻的只是**"因此修不了"这个隐含读法**：
     **正解不是重新加权 · 而是补分类** ⇒ **本日已按 §② 自己指出的根因实施** ✓✓
⇒ ⚠ **§① 的"改 `js\citysim.js:3107` 没有效果"【仍然成立】**：
   该行是 fallback；**本次改的是它 + 两份数据文件（三处同步）** ✓
⇒ ⚠ **判据 A 的两个基线【均已改变】** ⇒ 旧值作废（新值见 `HANDOFF_20261009.md` §三）✓
```

### ① ⭐ 职业份额【已锚定】· **不是**"手填 · 无出处"

```
⭐ **来源**：**第七次全国人口普查 · 长表 5-4（行业）与 5-1（职业大类）** ✓
   · **硬约束（精确满足）**：**每个职业的行业分布合计 = 表 5-4 的官方行业占比** ✓
   · **拆分依据（外推 · 逐项可核 · 可替换）**：**行业内部按职业构成常识拆到其职业** ·
     **系数写在该工具的 `SPLIT` 表里** ⇒ **每一项都能被质疑与替换** ✓
   · **交叉校验**：**算出职业大类合计 · 与表 5-1 官方值对比 ⇒ 如实报告残差**（**不掩盖**）✓
⇒ ⭐ **工具**：`civilization\tools\_jobtarget_remap2.cjs`（**头部注释完整 · 可复现**）✓

⭐⭐ **生效位置（两份必须同步）**：
   | 文件 | 谁在用 |
   | ⭐ `js\citydata_sh.js`（`:283`） | **`city_server.js:17`（线上生产）** |
   | ⭐ `tools\citydata_sh.mjs`（`:259`） | **`_soul_test.cjs`（`SOUL_CITY_DATA=sh`）** |

⚠⚠ **而引擎字面量 `js\citysim.js:3107` 只是 fallback**：
   `CIV.CITY_JOB_TARGET_RAW = CD('JOB_TARGET_RAW') || [ … ]`
   ⇒ **`citydata_sh.js` 一旦加载 · 该字面量【永不生效】** ⇒ **改它【没有任何效果】** ✓✓
```

**实测（2026-10-09 本次复核 · 独立探针）**：

```
⭐ **`js\citydata_sh.js` 的 25 项和 = `99.999999`**
   （**归一化分母 `644706` = `658820 − 14100 − 14`** · **即排除"农林牧渔"与"国际组织"**）✓
⭐ **`js\citysim.js:3107` 字面量的 25 项和 = `73.600000`** ⇒ ⭐ **两者是【不同的数组】** ✓
⭐ **生效值的 `LOW(eduPref≤2)` = `93.92%` · `HIGH(eduPref≥3)` = `6.08%`** ✓
⭐⭐ **线上生产证据**（`/api/state` · 就业 316,097 人）：
   · **与【数据文件目标】的绝对偏差合计 = `8.58pp`** ✓
   · **与【引擎默认目标】的绝对偏差合计 = `67.09pp`** ⇒ **生产跑的确实是数据文件** ✓✓
```

### ② ⚠⚠ 【已知结构缺陷】：分类体系缺「一般办事人员」〔**2026-10-09 已消除 · 见上方二次更新**〕

```
⭐ **官方职业大类（表 5-1 · 基数 658,820）**：
   · **C 办事人员和有关人员 = 45,534 ⇒ `6.9114%`** ✓
   · **F 生产运输设备操作人员 = 294,257 ⇒ `44.6642%`** ✓

⭐⭐ **而引擎 25 个职业的归类**：
   · **C 类【只有 2 个】**：**市场营销（T12）+ 设计创意（T14）= `2.647454%`**
     ⇒ ⚠ **残差【`−4.2640pp`】**
     （**要填满 6.91% 就得把它们各配到 ~3.5% ⇒ 明显不合理**）✓
   · **F 类【也只有 2 个】**：**工厂工人（T20）+ 建筑工人（T21）= `46.548781%`**
     ⇒ ⚠ **残差【`+1.8845pp`】** ✓
⇒ ⇒ ⭐⭐ **根因：引擎的职业 taxonomy 缺少「一般办事人员」这个【跨行业的职业类型】** ⇒
  ⇒ ⚠ **这是【模型结构问题 · 不是数据问题】** ✓✓
⇒ ⭐ **所以【重新加权在数学上修不好它】** ——
  **无论怎么分配权重 · 25 个职业里没有那一类 · 就永远填不满 C 的 6.91%** ✓✓
```

### ③ ⚠ 【口径提示】：七普 vs 五经普 · **不可直接互换**

```
⭐ **本模型的锚 = 七普长表**（**制造业 `41.2644%`**）✓
⚠ **而五经普（2023）的制造业 = `15.42%`** ⇒ ⭐ **两者差 2.7 倍** ✓✓
⇒ ⚠ **这是【年代 / 口径】差异 · 不是"模型没锚"** ✓
⇒ ⚠ **并注明**：**五经普锚【未独立复核】**（**沿用原方案的 URL 与数字**）✓✓
⇒ ⇒ ⭐ **若要做「口径迁移」（七普 → 五经普）⇒ 那是一次【独立的】研究线** ·
  **且会牵动所有已发结论** ⇒ **不可与"修 bug"混为一谈** ✓✓
```

### ④ ⭐ 一条方法教训（**比结论更值钱**）

```
⭐⭐ **"改了一个【不参与运行】的副本"是一种新的假通过**：
   ⇒ ⚠ **`js\citysim.js:3107` 的和 = 73.6 · 而它【不参与运行】** ⇒
     ⇒ **若照原方案改它** ⇒ **线上【零效果】·
       而"和 = 100% / LOW / HIGH"等约束检查【照样通过】**
       ⇒ ⭐⭐ **判据自洽 · 指纹变化 · 行为零变化 ⇒ 一次完美的假通过** ✓✓
   ⇒ ⭐ **这是第八种（判据覆盖不到被修的机制）的变体**：
     **不是"判据测不到"· 而是"改的东西压根不在判据的作用域里"** ✓
⇒ ⭐⭐ **纪律**：**改任何【常量表】前 · 先确认【运行时真正读的是哪一份】** ——
  **本项目里同名常量可能有【引擎字面量 fallback + 城市数据文件】两份 ·
  而后者覆盖前者** ✓✓
   ⇒ **验证办法**：**用与生产相同的加载顺序起一个探针 · 打印生效值 ·
     并与【线上 `/api/state` 的观测分布】比对偏差** ✓✓
```

### ⑤ 对引用者的要求

```
① ⚠ **不得**说本模型的职业份额"没有出处 / 是手填的" ——
   **它是七普长表行业边际（精确满足）+ 有文档化的拆分表** ✓
② ⚠⚠ **不得**用本模型的职业份额做「行业结构」论断 ——
   **因为分类体系缺「一般办事人员」· 残差 C `−4.26pp` / F `+1.88pp` 未被消除** ✓✓

   🔴 **2026-10-09 更正**：**上一条已过期** —— 第 26 个职业「行政文员」补齐了 C 类
   （**模型 C `6.9115%` vs 官方 `6.9114%` ⇒ 残差 ≈ 0** · **F → `−0.1543pp`**）⇒
   ⭐ **C 类可用**；⚠ **但 F / B / D 的残余仍在**（`B +1.6381pp` · `D +0.6513pp` · `F −0.1543pp`）⇒
   **做行业结构论断仍须连带声明这三项** ✓✓（**且该改动尚未部署** ⇒ 线上仍是旧份额）
③ ⭐ **要引用 · 请连同【①的出处】与【②的残差】一起引** ✓
④ ⚠ **不得**把七普口径的份额与五经普的数字【直接对比】✓
```

## 🔴 L-13 · 住房自有率有**约 12~14pp 的结构性缺口**（由存量年龄结构决定 · **修不好**）

> ⚠ **同上纪律：以下为追加块，上文 L-1~L-12 的原文【一字未改】。**

```
【现象（2026-10-12/14 线上与离线双路径实测）】
  · 线上「有房率」约 43~47%（成年有房率约 60%），而建城标定目标 houseOwnTarget = 59.6%
    ⇒ 缺口约 12~14pp
  · 该缺口**不随参数变化消失**，这是我们实测出来的（见下）

【已在 v1.5.0/v1.5.1 修掉的两个真缺陷（对比基线）】
  ① 断供判据缺一项：原判据不检查「是否真的欠着贷款」⇒ **无月供的自有房主**也会以
     「因无力偿还按揭」被处置房产（实测触发 518 次，首次发生在第 1 天）
     修后：按户有房率 y12 **28.0% → 37.5%（+9.49pp）** · 断供触发 **518 → 190（−63%）**
  ② 存量房主从未标定：线上一直走快照恢复路径，而建城标定只在第 0 天执行
     ⇒ 有房率只有 **2.54%**（14,506 个房主里 14,448 个带贷款）
     修后：线上 **2.54% → 47%** ✓

【⭐ 为什么剩余缺口「修不好」—— 我们做了拆门槛实验】
  | 改动 | 有房率变化 |
  |---|---|
  | 月供比 60% → 45%（收紧） | **+1.43pp** |
  | 首付 25% → 20% | +0.46pp |
  | 首付 25% → 10% | +2.5pp |
  | 月供比 60% → 70%（放松） | **−1.18pp** |
  | 月供比 60% → 100%（几乎全拆） | **−6.9pp** |
  | 房价 −30% / −47% | −0.6 / −0.7pp |
  | **两道门几乎全拆（月供 100% + 首付 10%）** | 成交 **×2.6**，但**有房率 −2.7pp** |
  ⇒ **放宽信贷【总是有害】**（更多人借得起 ⇒ 更多人断供 ⇒ 净流失）
  ⇒ **拆掉全部可负担性门槛后，有房率依然下降** ⇒ 说明它**由存量年龄结构决定**，
    而非由收入/房价/政策决定

【根因】
  购房窗极窄（仅「已婚 + 25~45 岁 + 无房」可买），而标定存量集中在 **54~72 岁**，
  该存量在 12~20 年内系统性老化退出；按法定顺序继承**只维持、不创造**产权；
  成交绝对量（每年 18~28 套）远小于在世人口增长。

【⚠ 对引用者的要求】
  ① **不得**把本模型的有房率与上海官方「城镇家庭自有率约 60%」**做绝对对标**
  ② **不得**用它做「住房政策实验」（房贷/首付/房价类政策）的结论
  ③ ⭐ **可以**用它做**相对比较**（同一年内不同区 / 不同收入组 / 不同年龄组的差异）
  ④ 引用时须连带说明「存在约 12~14pp 的结构性缺口」
```

---

## 🔴 L-14 · 生育「**意愿 → 实现**」几乎没有落差（**与真实世界相反**）

> ⚠ **同上纪律：以下为追加块，上文原文【一字未改】。**

```
【现象（2026-10-13 · run40 基线 40 年实测）】
  · 模型把「潜在 TFR」几乎**完全实现**：
      tfr / tfrWill  =  y11 130.8% · y21 146.3% · y31 81.4% · y36 66.5%
    （tfrWill = tfrTarget × will.will，引擎定义为**潜在** TFR，不是"声明意愿"）
  · 40 年终值：tfr ≈ 1.246 · tfrTarget ≈ 1.250 ⇒ **实现率贴满**
  · 而真实世界落差极大：**上海实际 TFR ≈ 0.74**，意愿口径常在 1.5~2 之间

【根因】
  引擎按「已声明的生育意愿」实现生育（稳态 TFR ≈ 1.25），
  没有建模真实世界里那些让意愿落空的约束
  （职业-育儿冲突、托育供给、婚配市场摩擦、房价收入比、生育窗口与年龄的硬约束…）。

【⚠ 对引用者的要求】
  ① ⚠ **不得**引用本模型 TFR 的**绝对水平**（1.25 vs 真实 0.74 ⇒ 差约 1.7 倍）
  ② **不得**用它研究「生育意愿为何落空」或「生育支持政策能否奏效」
  ③ ⭐ **可以**用它做**相对差异**（不同收入/学历/住房组的生育差异 · 随时间的趋势）
  ④ 引用时须连带说明「模型几乎完全实现潜在生育」
```

---

## 📌 使用规则

```
**任何对外材料引用本模型的结论前**：
   ① 查本文件**有没有对应的 L-x**
   ② **若有 ⇒ 必须在正文中显式限定**（不能只放在附录）
   ③ ⚠ **若结论方向依赖 L-1（学历年龄结构）· L-5（口径）· L-7（迁移净额式）·
      L-8（代际结构只覆盖 <22 岁）· L-9（学历不是就业门槛）·
      L-10（真门槛会制造空缺 · 且小规模结论不可外推）或
      L-11（职业份额已锚定于七普 · 分类体系缺「一般办事人员」〔**2026-10-09 已补齐 C 类 · 但 F/B/D 残余仍在**〕⇒ 作行业结构论断须声明残余）⇒
      L-13（住房自有率有约 12~14pp 结构性缺口 ⇒ **不得**做住房政策实验或与官方绝对值对标）·
      L-14（生育「意愿→实现」几乎无落差 ⇒ **不得**引用 TFR 绝对水平）⇒
      结论可能反向** ⇒ **必须重算或撤回**
⇒ ⇒ ⭐ **原则**：**「未声明的简化」比「已知的误差」危险得多** ✓
```
