# ch08 混排模块 - 代码示例

本目录对应教材第 8 章「混排模块」，用 Python 复刻正文的混排链路（前处理 → 序列生成 → 序列排序 → 价值评估 → 后处理与流量调控），共 **13 个可运行脚本**。目录编号与正文节次一一对应，每个脚本自包含、互不 import，均可直接 `python3 <文件>.py` 运行。除 `ch05_sequence_value/unified_value_model.py` 需要 **PyTorch** 外，其余脚本只依赖 Python 标准库与 NumPy。正文用 C++ 描述的工程组件（如 §8.6.2 的 `PIDLoadController`）在此一律用进程内 Python mock 表达，不起真实服务。

---

## 目录结构

```
code/
├── ch01_mixrank_problem/                § 8.1 多业务混排问题
│   └── mixrank_pipeline.py             混排模块端到端四环节链路（图「混排模块内部处理流程」）
├── ch02_pre_processing/                 § 8.2 混排前处理
│   ├── score_calibration.py            § 8.2.1 分数校准：IR / SIR / Bayesian-SIR + PAVA
│   └── score_normalization.py          § 8.2.2 分数归一化：Min-Max / LogMin-Max / Z-Score / Gauss-CDF
├── ch03_sequence_generation/            § 8.3 序列生成
│   ├── constraint_rule_engine.py       § 8.3.1 约束规则引擎 + 剪枝式 Beam Search
│   └── auction_architecture.py         § 8.3.2 预竞价 / 全竞价 / 折中架构与信息边界
├── ch04_sequence_ranking/               § 8.4 序列排序
│   ├── sequence_fusion_score.py        序列融合分、多目标加权、位置衰减因子与 Top-1 选择
│   └── length_normalization.py         序列长度不可比：长度归一化 vs Mock Item 补齐
├── ch05_sequence_value/                 § 8.5 序列价值评估体系
│   ├── exchange_rate_value.py          业务价值兑换关系（Exchange Rate）与统一价值空间
│   └── unified_value_model.py          统一价值模型：四目标多任务网络 + 加权聚合
└── ch06_post_processing_and_control/    § 8.6 混排后处理与流量调控
    ├── load_control_window.py          § 8.6.1 Load 调控：请求窗口统计与熔断保护
    ├── pid_controller.py               § 8.6.2 PID 控制：复刻正文仿真 + 调参表 + 抗积分饱和
    ├── force_insertion.py              § 8.6.3 强插策略：定向/全局强插与配额代价
    └── quota_and_demotion.py           § 8.6.4 保量与降权：乘性权重 + 硬预留坑位
```

---

## 快速运行

```bash
# § 8.1 多业务混排问题
python3 ch01_mixrank_problem/mixrank_pipeline.py

# § 8.2 混排前处理
python3 ch02_pre_processing/score_calibration.py
python3 ch02_pre_processing/score_normalization.py

# § 8.3 序列生成
python3 ch03_sequence_generation/constraint_rule_engine.py
python3 ch03_sequence_generation/auction_architecture.py

# § 8.4 序列排序
python3 ch04_sequence_ranking/sequence_fusion_score.py
python3 ch04_sequence_ranking/length_normalization.py

# § 8.5 序列价值评估体系
python3 ch05_sequence_value/exchange_rate_value.py
python3 ch05_sequence_value/unified_value_model.py

# § 8.6 混排后处理与流量调控
python3 ch06_post_processing_and_control/load_control_window.py
python3 ch06_post_processing_and_control/pid_controller.py
python3 ch06_post_processing_and_control/force_insertion.py
python3 ch06_post_processing_and_control/quota_and_demotion.py
```

全部脚本均在 CPU 上 10 秒内跑完（最慢的是 `unified_value_model.py`，约 10 秒）。

---

## 各脚本要点

下面的数字均来自 `random.seed(42)` 固定随机种子后的实际运行输出。

### § 8.1 `ch01_mixrank_problem/mixrank_pipeline.py`

把正文图「混排模块内部处理流程」的四个环节串成一次可观察的请求。Router 透传四路业务候选，原始分量纲完全不可比（`ad` 的 `raw_score` 高达 `A1:913.92`，而 `video` 只有 `V4:0.58`，`live` 为 `L2:6.07`）。

- **阶段 1 前处理**：IR 校准把四业务 PCOC 从 `video 1.0594 / ad 1.4272 / live 0.9038 / ecom 1.1714` 拉回 `1.0182 / 0.9398 / 1.0748 / 1.0253`，随后折算为统一价值分（分钟）。
- **阶段 2 序列生成**：约束「刷次内广告间隔 ≥3、上一刷尾部是广告故首位禁广告」把候选压到 **51 条合法序列**。
- **阶段 3 序列排序**：$\lambda=0.2$ 指数位置衰减 + Mock Item（价值 1.896）对齐长度 9，Top-1 得分 10.627，Top-2/Top-3 分别为 10.509 / 10.504。
- **阶段 4 后处理**：滑动窗口广告 Load = 0.1833（目标 0.12），PID 输出 $u_t=-0.1108$；再叠加运营强插（`OP-LIVE` 插到第 3 坑）与电商保量兜底（`QUOTA-1`），序列长度 9 → 10，广告占比 0.200。

### § 8.2.1 `ch02_pre_processing/score_calibration.py`

实现正文的三种校准方法并复现 PAVA。

| 方法 | COPC | ECE | \|COPC−1\| |
| --- | --- | --- | --- |
| 校准前（原始 pxtr，bias=1.45） | 1.4306 | 0.12409 | 0.4306 |
| IR 保序回归 | 0.9935 | 0.01046 | 0.0065 |
| SIR 平滑保序回归 | 0.9920 | 0.01064 | 0.0080 |

- **桶边界跳变**：相邻预估分 0.4150 → 0.4170 跨桶时，IR 的输出从 0.2780 直接跳到 0.3095，边界 ±0.001 跳变幅度 **IR=0.03150 而 SIR=0.00196**（约 1/16），量化了正文所说的「IR 在桶边界不连续」。
- **小样本（2500 条切 50 桶）**：SIR 与 Bayesian-SIR 的平均 \|COPC−1\| 都是 0.0186，但 Bayesian-SIR 的 ECE 从 0.01833 降到 0.01690，**校准曲线方差从 0.000704 降到 0.000570**，即分层先验主要买到的是稳定性而非偏差。
- **正文表 8.1 复刻**：Beta 先验 $\alpha=30,\beta=170$ 下，大样本桶（PV=100000）经验 CTR 0.1200 → 0.1201 几乎不动，而 PV=50 的小样本桶 0.4000 被收缩到 **0.2000**（收缩量 +0.2000），与正文一致。
- **多业务**：四业务 bias 各异（1.45/0.80/1.15/1.02），SIR 后 COPC 全部落在 0.985~1.026。

> 正文引用：保序回归 Fielding, *Statistical inference under order restrictions: the theory and application of isotonic regression*, 1974；SIR 来自 Deng et al., *Calibrating user response predictions in online advertising*, ECML-PKDD 2020；PAVA 实现参考 de Leeuw, Hornik & Mair, *Isotone optimization in R: pool-adjacent-violators algorithm (PAVA) and active set methods*, Journal of Statistical Software 2010（三条均取自书稿 `reference.bib` 中正文实际引用的条目）。

### § 8.2.2 `ch02_pre_processing/score_normalization.py`

对比四种归一化口径。归一化前三业务完全不可比：广告 eCPM 均值 744.78（std 1191.79）、直播打赏 24.07、短视频 pVTR 0.3360。

- Min-Max 对广告这种长尾分布几乎无效：广告 p50 被压到 **0.0156**，而短视频 p50 为 0.3368，跨业务竞争时广告几乎必输；LogMin-Max 把广告 p50 抬回 0.5100、直播 0.6997，三业务终于同量级。
- **归一化不能替代校准**：给广告模型注入「高分段额外抬升 1.0σ」的失准后，`A 先校准再归一化` 的广告胜出率 0.2808、实际兑现价值 **+0.8171**；`B 不校准直接归一化` 胜出率升到 0.2992 但兑现价值跌到 **+0.7996** —— 未被吸收的高估让广告抢走更多流量却降低了真实收益（流量错配）。对 Min-Max / Z-Score / Gauss-CDF 这类纯乘性缩放不变的口径，靠归一化根本看不出模型高估。
- **分布塑形（Score Shaping）**：Top-K 内容统一置 1 后，Top5% 均值从 0.1800 变成 **1.0000**，长尾 p50 从 0.0156 压到 0.0125。

### § 8.3.1 `ch03_sequence_generation/constraint_rule_engine.py`

- **复刻正文枚举规模算例**（短视频 6 + 广告 ≤3 + 直播 ≤1）：按广告数 3/2/1/0 分别为 294/336/105/8 条，合计 **743 条**，与正文给出的 743 完全一致。
- **规则引擎过滤**：743 条枚举 → **3 条合法**（通过率 0.40%）。按注册顺序短路统计首次命中拦截数：刷次内 Gap(≥3) 538、多样性约束 133、跨刷次 Gap(首位禁广告) 68、位置约束 1。
- **两段正文伪代码对比**（候选 10，K=8，beam=20）：把 O(L) 全序列重扫换成 O(1) 增量约束后，**序列打分次数降到 67.7%、规则判定的元素访问量降到 21.8%**，最优分完全一致（3.6741，搜索空间等价）。
- **最优性-算力权衡**：10 个候选有序选 8 个共 1814400 条全排列、合法 100368 条，暴力最优分 3.7878；beam=5 只对 **149 条**序列打分（全排列的 0.0082%），结果与最优差 **3.00%**；beam 从 5 增到 50 不再提升。

### § 8.3.2 `ch03_sequence_generation/auction_architecture.py`

> **本轮修复**：原实现用 `itertools` 对「选哪些 + 插哪里」做朴素笛卡尔枚举，全竞价分支（视频 8 + 直播 5 + 广告 5）组合爆炸导致 60s 超时（rc=124）。现改为**带 Gap 约束剪枝的交织枚举**（按业务流内部已有序这一前提，只枚举业务流的「交织模式」而非全排列），并在超大规模下提供 Beam Search 退化路径。现运行 **8 秒**，正文的两个结论都保留。

- **序列生成空间（K=8）**：预竞价 (8,1,1) → 65 条合法序列；折中 (8,2,2) → 396 条（6.1x）；全竞价 (8,5,5) → 1125 条（17.3x）；再加一路电商 → **13769 条（211.8x）**，即正文的组合爆炸。
- **架构对比（120 次请求，混排噪声 $\sigma_{mix}=0.55$ > 业务噪声 $\sigma_{biz}=0.22$）**：理想无噪声全竞价（上界）5.6739；真实全竞价 5.4253（距上界 −4.38%），**预竞价反而是 5.4672（−3.64%）**，折中 5.4420（−4.09%）。逐请求胜负：全竞价赢 23 次、预竞价赢 44 次。这就是正文的「信息边界导致全竞价未必赢」。
- **信息边界扫描**：$\sigma_{mix}$ 从 0.10 升到 0.55 时赢家发生切换 —— 0.10/0.25 时全竞价胜（5.9976 / 5.9442），0.40 起预竞价反超（5.8914 vs 5.8850）。
- **算力换精度**：把预竞价省下的算力投给更重的评估模型后，`预竞价 + 重型评估`（候选 213.9 条、噪声降到 0.30）总价值 **5.6298（距上界 1.91%）**，优于 `全竞价 + 轻量评估` 的 5.5513（3.28%）—— 正文「预竞价本质是搜索剪枝，省下的算力可换更精细评估」的数值证据。
- **Beam 退化方案**：低噪声（σ=.10）时精确枚举最优 5.8192，Beam b=10 为 5.8191（相对 0.99998）；高噪声（σ=.55）时 Beam b=10 的 5.6158 反而略高于枚举的 5.6047 —— 在 1125 条带噪打分里取 max 会放大噪声（winner's curse），从另一个角度印证「扩大搜索空间 ≠ 拿到更优解」。

### § 8.4 `ch04_sequence_ranking/sequence_fusion_score.py`

按正文实现三层递进的序列打分口径：`alpha * 模型分 + beta * 业务分` → 多业务目标加权和 → 叠加位置衰减 $\gamma_i=e^{-\lambda(i-1)}$。

- **位置衰减因子复刻**：$\lambda=0.2$ 时 $\gamma_{1..4}$ = 1.00 / 0.82 / 0.67 / 0.55，与正文给出的 1.0, 0.82, 0.67, 0.55 一致。
- **三种口径选出不同 Top-1**：口径 A（模型+业务融合）与 B（多目标加权和）都选候选 #4 `A10|V5|V0|V3|L11|L12`（8.6800 / 14.8722），而口径 C（加位置衰减）选候选 #1 `V2|V5|L12|V3|V4|V1`（5.4725）—— 对应正文「核心挑战来自价值函数设计而非排序算法」。
- **alpha/beta 扫描**：$\alpha \le 0.75$ 时 Top-1 恒为 #1（领先幅度 1.72%→2.01%），$\alpha=1.0$（纯模型分）时切换到 #5 且领先幅度跳到 **8.51%**。
- **lambda 扫描**：$\lambda \le 0.10$ 时 Top-1 首位是广告 `A10`，$\lambda \ge 0.20$ 时切换为视频 `V2`；前 2 位的分数占比从 **31.1% 单调升到 82.5%**，即 λ 越大首屏权重越集中。

### § 8.4 `ch04_sequence_ranking/length_normalization.py`

复刻正文「候选序列长度不一致」与图 `fig_list_mock_item` 的 Mock Item 补齐。

| 口径 | 序列1 (L=9) | 序列2 (L=7) | 胜者 | 差值 |
| --- | --- | --- | --- | --- |
| 裸求和 `Score_raw` | 12.4601 | 11.7131 | 序列1 | 0.7470 |
| 长度归一化 `Score_norm` | 2.7059 | 2.8182 | 序列2 | −0.1123 |
| Mock 补齐 `Score_mock`（$v_M=2.0$） | 12.4601 | 12.6101 | 序列2 | −0.1500 |

- 序列2 单位置平均价值更高（2.8182 > 2.7059）却在裸求和下输掉，差值 0.7470 **完全来自多出的 2 个坑位**。
- **长度偏置量化**：内容质量全部固定为 v=2.0，只改长度，裸求和从 L=5 的 6.9744 单调涨到 L=9 的 9.2095（长度白送分）；长度归一化恒为 2.0000、Mock 补齐恒为 9.2095，两种口径都消除偏置。
- **非循环判据的对照实验**（空坑位按真实基线 $v_{base}=2.3$ 填充作为 ground truth，3162 组长度不等的对比）：裸求和选对率仅 **80.83%**；长度归一化 97.60%；Mock 取历史均值 2.0 时 97.60%；Mock 取准真实基线 2.3 时 **100.00%**。
- **Mock 价值敏感性**：$v_M=0$ 等价裸求和（判错），$v_M \ge 2.0$ 起判对，$v_M$ 过高（4.0）会把空坑位当优质内容、反向奖励短序列 —— 正文因此强调 Mock 价值要取业务历史均值/历史统计。

### § 8.5 `ch05_sequence_value/exchange_rate_value.py`

- **兑换关系推导**：以「用户消费时长（分钟）」为统一单位，1 分钟 = 0.05 元平台收益 ⇒ 1 元 = 20 分钟。由此自动推出 $Value_{ad}=20\times Revenue$、$Value_{live}=10\times Gift$、$Value_{ec}=2\times GMV$，与正文表中的 20 / 10 / 2 一致。
- **统一价值映射复刻**：Video-A（看 3 分钟）=3.00、Ad-B（eCPM 0.20 元）=4.00、Live-C（打赏 0.50 元）=5.00、Ecom-D（GMV 1.50 元）=3.00，即正文的 {3, 4, 5, 3}。
- **序列价值对比**：$Score(Seq1)=1.0\times5+0.8\times4+0.6\times3=10.0$ vs $Score(Seq2)=9.8$，差 0.2 > 0 ⇒ 选 Seq1，与正文一致。
- **口径对照实验**：以「真实序列价值均值」为判据，Min-Max 归一化口径 20.081（相对 0.7883）、Z-Score 20.369（0.7996）、**兑换关系 25.473（1.0000）**。首位业务分布暴露原因：归一化口径把各业务内部名次当成跨业务价值（Min-Max 给了 video 43% 的首位），而兑换关系口径优化平台整体价值（首位 67% 给 ec、28% 给 live）。正文结论「归一化只是缩放，没有解决价值口径不一致」在此被量化为 **20% 的平台价值损失**。

### § 8.5 `ch05_sequence_value/unified_value_model.py`

对应正文末尾「构建统一价值模型，直接为每个 Item 计算同时考虑用户体验、商业收益、内容生态、长期价值的综合价值分」。共享底座 + 四个预估头，`Value = Σ w_k · c_k · u_k`。

- **拟合质量**（3000 条合成样本、120 轮全批，四头 MSE 之和 0.1287）：用户体验头 MAE 0.2434、商业收益 0.0915、内容生态 0.0771、长期价值 0.0739，预估均值与标签均值逐项对齐（如 u_exp 1.821 vs 1.820）。
- **价值构成拆解**（同一组中等特征 x=[.5,.5,.5,.5]）：广告体验贡献最低（0.476 分钟）而电商收益贡献最高（26.504），最终 Value 为 video 11.591 / ad 9.639 / live 16.861 / **ec 33.105**。
- **口径消融**：只看用户时长 44.374（相对统一价值 **0.6578**）；只看商业收入 66.859（0.9912）；时长+收入两目标 67.146（0.9954）；四目标统一价值 **67.455（1.0000）**。单看时长的口径要损失 34% 平台价值；补齐生态与长期价值两个头仍能再拿回 0.9%。

### § 8.6.1 `ch06_post_processing_and_control/load_control_window.py`

正文强调 Load 统计必须用**滑动/计数窗口**而非全局累计，本脚本给出三组数值证据。

- **窗口 vs 全局累计**（第 500 次请求起广告分被异常抬高）：第 750 次请求时真实占比已达 0.7775，窗口统计给出 **0.7785**（几乎无延迟），全局累计却只有 **0.3087**（差 0.4698）；到第 1500 次请求全局累计仍只有 0.5419。全局口径被故障前的历史稀释，始终低估当前真实占比。
- **熔断保护**（第 1200 次请求起广告原始分 +0.35）：关闭调控时广告占比被推到 **0.7761**（区间 [0.765, 0.784]，远超安全上界）；开启调控后稳定在 **0.1260**（[0.112, 0.139]，目标 0.125），$u_{ad}=-0.3156$ —— 积分项吃掉了这个常值偏置。
- **三业务联合调控**（正文配置：广告 10~15% / 直播 5~8% / 普通 ≥80%）：纯按模型分排序时广告只拿 **7.33%**（低于 10% 下界）、直播超配到 **13.54%**；加 Load 调控后分别为 12.44% / 6.47% / 81.09%，三业务同时落入安全区间。
- **窗口容量影响**：容量 100 时广告占比 std **0.1001**、峰谷差 0.3654（估计噪声让调节量乱抖）；500 时 std 0.0073、峰谷差 0.0422；2000 时 0.0074 / 0.0267；5000 时 0.0073 / **0.0222**（最平滑，但对突发故障响应被拖慢）。正文 C++ 示例取 10×500=5000 条即此折中。

### § 8.6.2 `ch06_post_processing_and_control/pid_controller.py`

严格按正文的离散 PID 推导与 `PIDLoadController` C++ 类实现，含**积分限幅**与**输出裁剪**（正文 `compute_adjustment` 里的 `clamp`）。

- **复刻正文主函数仿真** `pid(0.12, 1.8, 0.35, 0.45, 10, 500)`，10000 次请求等间隔采样：稳态（窗口写满后 5000 条请求）区间 **[0.1134, 0.1301]**、均值 0.1203，稳态偏差绝对值最大 0.0101；调节量双向可调（正向 2843 次 / 负向 7157 次，取值范围 [−0.5000, +0.3120]）。*与正文差异*：正文表给出被控量区间 [0.1170, 0.1280]、稳态偏差 ±0.008 以内；本实现用**单线程顺序执行代替正文的 8 线程并发**，随机序列不同所以逐行数值无法对齐，但稳态区间宽度与偏差量级一致。
- **Kp/Ki/Kd 扫参**（广告占比由 30% 阶跃回落到目标 12%，一步 = 一个 500 请求窗口）：

| Kp | Ki | Kd | 类型 | 下冲量 | 稳态误差 | 收敛步数 | 稳态 std |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 0.0 | 0.00 | 0.00 | 无调控 | 0.0% | +0.0828 | 未收敛 | 0.0053 |
| 0.5 | 0.00 | 0.00 | P | 0.0% | +0.0548 | 未收敛 | 0.0045 |
| 1.8 | 0.00 | 0.00 | P | 16.8% | +0.0290 | 未收敛 | 0.0029 |
| 4.0 | 0.00 | 0.00 | P | 17.5% | +0.0164 | 未收敛 | 0.0020 |
| 1.8 | 0.10 | 0.00 | PI | 37.5% | −0.0004 | 31 | 0.0024 |
| 1.8 | 0.35 | 0.00 | PI | 73.8% | −0.0001 | 54 | 0.0025 |
| 1.8 | 1.00 | 0.00 | PI | 100.0% | −0.0022 | 未收敛 | 0.0454 |
| 1.8 | 0.35 | 0.45 | PID（正文参数） | 71.7% | −0.0001 | **39** | 0.0024 |
| 1.8 | 0.35 | 2.00 | PID | 64.3% | −0.0001 | **26** | 0.0024 |
| 1.8 | 1.00 | 2.00 | PID | 100.0% | −0.0001 | 77 | 0.0035 |
| 4.0 | 0.35 | 0.45 | PID | 51.2% | −0.0001 | 59 | 0.0019 |

  纯 P 的稳态误差恒为正且只能靠加大 Kp 变小（+0.0548 → +0.0164，始终不收敛），正是正文说的「缺少积分项无法消除累积静差」；加 I 后稳态误差压到 1e-4 量级，代价是下冲量从 16.8% 涨到 73.8%；Ki 过大（1.00）时积分反复充放电，稳态 std 从 0.0025 恶化到 **0.0454** 且不再收敛；此时把 Kd 加到 2.00 可把 std 拉回 0.0035，对应正文「D 项提供阻尼、抑制超调与震荡」。在正文参数 (1.8, 0.35, 0.45) 上加 D 项把收敛步数从 54 降到 **39**。

- **积分饱和与抗饱和**（前 40 个窗口广告库存中断、执行器恒为 0）：不限幅时积分项积到 **+4.800**，库存恢复瞬间把 Load 推到 **0.6444**（超目标 437.0%），恢复期累计偏差 5.869；按正文 `clamp(integral + error, ±max_integral)` 限幅到 ±1.0 后积分停在 +1.000，峰值仅 **0.2838**（超目标 136.5%），累计偏差 2.369（为前者的 **40%**）。
- **P/I/D 三项贡献拆解**：第 0 个窗口偏差 −0.1800，P 项独自给出 −0.3240；随 Load 回落 P 项衰减到 ~0（第 99 窗口 −0.0025），此时维持 Load 的几乎全是 I 项（−0.0849）；D 项稳态只剩 +0.0001 —— P 管「现在差多少」、I 管「一直差」、D 管「差得多快」。

### § 8.6.3 `ch06_post_processing_and_control/force_insertion.py`

- **单请求效果**（首刷、人群包 `crowd_newuser_30d`）：混排 Top-1 序列价值 4.2135；依次执行「白名单创作者视频→第 5 坑」「活动主播直播间→首刷第 3 坑」「特定人群包活动广告→第 2 坑」后降到 **3.5141**，累计价值损失 **0.6995**，3 个模型元素被挤出队尾。后插入的规则会把先插入的素材再往后推一位 —— 工程上必须约定规则优先级，正文的做法是把强插放在曝光前最后一步统一处理。
- **代价量化**（4000 次请求）：只有三条定向规则时强插素材曝光占比 19.48%、平均序列价值损失 0.2780；再叠加全局强插后曝光占比升到 **32.17%**、平均损失 0.3322，业务占比漂移为 video **−12.36%**、live **+13.44%**、ad −1.08%。
- **配额对比**：

| 策略 | 强插曝光占比 | 广告占比 | 平均价值损失 | 规则达成率 |
| --- | --- | --- | --- | --- |
| 无条件强插（正文语义） | 32.45% | 11.24% | 0.3398 | 100.00% |
| 每刷最多 1 条 | 12.50% | 10.57% | **0.0086** | 38.96% |
| 广告占比硬上界 15% | 30.13% | 9.70% | 0.2658 | 93.77% |

  「每刷最多 1 条」把价值损失压到约 1/40，代价是只有 38.96% 的运营规则被执行；广告硬上界方案在几乎不牺牲非广告规则（达成率 93.77%）的前提下把广告占比压回安全区间。

### § 8.6.4 `ch06_post_processing_and_control/quota_and_demotion.py`

复刻正文 §8.6.4 的生态调控配置：乘性权重（软手段，只改排序分）+ 保量下界（硬手段，预留坑位）。

- **配置表**：`short_drama` 池占比 12%、权重 1.00、保量下界 15%；`ai_manhua` 6%、权重 **1.30**；`knowledge` 8%、权重 1.10、下界 5%；`local_life` 7%、下界 5%；`aigc` 15%、权重 **0.70**；`low_quality` 10%、权重 **0.50**；`normal` 42% 不干预。每刷 K=8 坑，新创作者内容占池 18%、硬名额每刷至少 1 条。
- **保量闭环**（滚动 100 次曝光窗口）：短剧窗口占比均值 **14.70%**（下界 15%）、std 0.0064；本地生活均值 **5.50%**（下界 5%）、std 0.0081。短剧欠量始终被 clamp 在 [0, 3.0] 内，不会无限累积。占比稳定在下界附近而不是一路上冲：欠量归零后预留立即撤销 —— 保量本身就是一个「只在欠量时发力」的闭环，与 §8.6.2 的 PID 同源；**欠量 clamp 正对应 PID 的抗积分饱和**（避免候选长期缺货后供给恢复时刷屏）。

---

## 实现约定

1. **节次对应**：子目录名 `chNN_<english_snake_case>` 中的 `NN` 与正文 §8.NN 一一对应（`ch01_mixrank_problem` ↔ §8.1，`ch06_post_processing_and_control` ↔ §8.6）。
2. **自包含**：脚本之间不互相 import，每个文件可以单独拷出运行。文件顶部 docstring 用中文写明对应的正文节次与概念、本实现做了什么、关键公式。
3. **可复现**：`if __name__ == "__main__":` 块固定 `random.seed(42)`，用到 NumPy / PyTorch 时同时设 `np.random.seed(42)` / `torch.manual_seed(42)`。全部数据为进程内合成，**不联网、不读本地数据集**。
4. **输出形式**：一律打印数值表格（对齐的列 + 分隔线 + 一句中文结论），不打印 loss 日志、不使用 matplotlib。
5. **Mock 组件**：教学用的假存储、假 RPC、假日志流、假竞价对手、假执行器等在 docstring 中以 `[MOCK]` 标注。正文用 C++ 描述的 `PIDLoadController` 在此以等价的 Python 类实现（单线程顺序执行代替多线程并发）。
6. **以正文为准**：公式、符号、参数（如 PID 的 `(0.12, 1.8, 0.35, 0.45, 10, 500)`、位置衰减 $\lambda=0.2$、兑换系数 20/10/2、保量下界 15%/5%）全部照抄正文；与正文存在差异之处已在上面对应条目中标明（目前仅 `pid_controller.py` 的单线程 vs 多线程一处）。
7. **依赖**：Python 3.12 标准库、NumPy 1.26.4、PyTorch 2.7.1（仅 `unified_value_model.py`）。
