项目

一般

简介

0001-Sprint2规划会议纪要 » 历史记录 » 版本 1

Huarui Lin, 2026-04-15 21:00

1 1 Huarui Lin
# <div align="center"> Sprint 2 规划会议纪要:数据基建与时序状态机护栏落地 </div> 
2
3
**会议日期**:2026-04-15 
4
**项目名称**:基金量化定投选基系统 (2026-04-11-01) 
5
**会议目标**:锁定 M2 交付标准,对齐 DuckDB/Polars 混合架构边界,解决时序状态机边界逻辑冲突,确立实体机压测基线与 Redmine 强卡控验收标准。 
6
**参会角色**:项目经理(Henry Lin)、算法/研发工程师、基础架构/运维工程师、数据资产管理员。 
7
8
--- 
9
## 一、 会议确认项 
10
11
1. **CONF-01 S2 绝对红线确立**:全局严禁手写 `import pandas`(CI AST 拦截);严禁魔法数字(必须走 `config/config.yaml` 新增节点读取);DuckDB 严守粗筛边界(严禁执行滚动窗口逻辑)。 
12
2. **CONF-02 FW-3 防火墙特例豁免决策**:正式批准 `data_loader.py` 中的 `process_timeseries()` 阶段豁免执行 FW-3(按年份拆分提取)规则,允许一次性加载全量 Arrow 数据进行缺失值填充与截断状态机计算。 
13
3. **CONF-03 DuckDB 显式列投影强制约束**:在 S2-01 的 DuckDB SQL 中,严禁使用 `SELECT *`。对 `fund_basic_info` 必须硬编码 `SELECT fund_id, fund_name, fund_type, create_date` 进行显式投影。仅允许 `fund_type` 作为临时审计字段随 Arrow 流转,在最终落盘为 Parquet 前,必须通过 Polars 显式 `select` 将其彻底丢弃。 
14
4. **CONF-04 data_audit.json Schema 零偏移确认**:S2-04 产出的 `data_audit.json` 必须严格遵循《0002-数据基建》中定义的完整 JSON 结构(含 `pipeline`, `input`, `filtering`, `cleaning`, `output` 五大根节点),拒绝任何字段名自定义或结构增删。 
15
5. **CONF-05 审计关键比率计算逻辑确认**:明确 `filtering.whitelist_retention_rate` 的计算公式必须为:`whitelist_retained_funds / whitelist_total_funds`。其中分母 `whitelist_total_funds` 必须取 `fund_basic_info` 原始 CSV 的物理总行数,严禁使用去重或其他口径。若分母为 0,统一调用 `calc_rate` 工具函数返回 0.0。 
16
6. **CONF-06 config.yaml 扩展节点确认**:确认在 `config/config.yaml` 中新增两个合法节点:① `data.raw_paths`(字典结构,声明路径);② `duckdb.memory_limit`(字符串类型,默认 `"40GB"`)。 
17
7. **CONF-07 建仓期硬删与 `segment_id` 跨段逻辑决策**:采用“选项 A”。建仓期(12周)剔除逻辑严格锚定并仅作用于 `segment_id == 0`,即使 `segment_0` 被完全物理抹除,后续 `segment_1` 及以后的数据段仍予保留,以所有段有效周数总和参与后续存活期过滤,坚决遵守 CR-001 段独立性原则。 
18
8. **CONF-08 异常值置 NULL 的涨跌幅计算基准决策**:采用“选项 B”。异常值(单周涨跌幅绝对值 > 50%)判定必须基于“本周原始有值且上周原始有值”的真实相邻周计算,由 `ffill` 填充产生的值不参与此异常值判定逻辑,防止长缺失恢复后的首周数据被误杀置 NULL。 
19
9. **CONF-09 Parquet 物理存储格式强制约束**:确认 `data/processed/` 下产出的文件命名严格为 `net_value_YYYY.parquet`。PyArrow `write_parquet()` 必须显式指定 `row_group_size=100000`;写入模式必须为“覆盖写”,严禁追加。 
20
10. **CONF-10 最终落盘 Schema 绝对锁定**:产出 Parquet 必须且仅能包含 4 列:`fund_id`(String), `net_value_date`(Date), `cumulative_net_value`(Float64), `segment_id`(UInt32)。任何多余的临时计算列在落盘前必须被 Polars 显式 `select` 丢弃。 
21
11. **CONF-11 DuckDB VIEW 重建机制确认**:`create_duckdb_view()` 函数严禁依赖任何持久化数据库状态。每次被调用时,必须动态扫描 `data/processed/` 目录下的年份文件,拼接生成 `v_net_value_processed` VIEW。 
22
12. **CONF-12 数据血缘 Hash 校验时机确认**:确认 `src/utils/hash.py` 的调用时机必须在 Parquet 文件落盘完成后,按年份文件逐个计算 SHA-256,并将结果列表持久化至 `data/metadata/processed_hash.json`。 
23
13. **CONF-13 M2 内存压测执行环境决策**:采用“选项 A(实体机裸跑测试)”。S2-04 的压测必须在 64GB 实体机宿主机上直接运行 Python 脚本,不施加 Docker cgroup 限制,以获取最真实的物理内存消耗峰值作为算力安全结论的最终背书。 
24
14. **CONF-14 净值字段数据类型强制映射确认**:本系统在全链路(DuckDB 抽取、Polars 计算、Parquet 落盘)必须且只能使用 `Float64`,严禁使用原生 `Decimal` 类型,以防止 Polars 滚动窗口性能劣化及内存溢出风险。 
25
15. **CONF-15 Redmine Story 强卡控验收标准映射锁定**:S2-01至S2-05的Redmine自定义字段必须严格按本次会议确认的配置扩展、SQL约束、状态机单测覆盖、审计Schema及压测内存指标逐条填入,作为PR合并的强阻断门禁。 
26
16. **CONF-16 审计工具函数 `calc_rate` 交付归属决策**:采用“选项 B”。`calc_rate` 工具函数必须作为 S2-04(审计日志产出)的交付物,与其唯一调用者绑定,严禁在 S2-01 阶段提前产出导致“悬空代码”。 
27
17. **CONF-17 R-6 风险(白名单覆盖率异常)阻断级别决策**:采用“选项 B(有条件放行)”。若 S2-04 压测真实触发 `whitelist_retention_rate < 15%` 阈值,M2 里程碑标记为“带风险通过”,不阻断 Step 3 开发。但必须在 Redmine 建立 High 级别 Bug 单拉起排查,若在下个 Sprint 回顾前未闭环,则触发熔断机制。 
28
29
--- 
30
## 二、 会议待办项 
31
32
| 编号 | 责任人 | 任务描述 | 截止时间 | 验收标准 | 
33
| :--- | :--- | :--- | :--- | :--- | 
34
| **TODO-01** | 研发工程师 | S1 占位 Mock 测试代码物理销毁 | S2-01 交付同步 | 必须在 S2-01 首个 PR 中同步物理删除 S1 遗留的占位测试代码,CI 全绿方可进入 Review。 | 
35
| **TODO-02** | 研发工程师 | CI 性能熔断预案触发监控 | Sprint 2 持续 | S2 引入真实 I/O 后,单次 CI 执行总耗时超 180 秒立即触发 Runner capacity 调优或流水线拆分。 | 
36
| **TODO-03** | 研发工程师 | 产出特例豁免 ADR 文档 | 会后首个 PR | 于 Gitea `/docs/adr/` 提交 `002-s2-full-arrow-exception.md`,必须固化特例生效的精确物理边界、内存预估依据及下游禁用声明;同步更新架构基线文档。 | 
37
| **TODO-04** | 研发工程师 | 产出时序状态机核心逻辑单测矩阵 | S2-02 交付同步 | `tests/test_data_loader.py` 必须覆盖 CONF-07 的“`segment_0` 被建仓期完全吞噬后 `segment_1` 仍保留”场景,以及 CONF-08 的“长缺失 ffill 恢复首周免于 >50% 误杀”场景。 | 
38
| **TODO-05** | 研发工程师 | 实体机全量压测报告归档 | S2-04 交付 | 压测日志必须包含 DuckDB 阶段峰值、全量 Arrow 转换峰值、Polars 状态机峰值三个维度的 `peak_memory_gb`,且总峰值必须 ≤ 40GB,日志文件上传至 Redmine S2-04 任务附件。 | 
39
| **TODO-06** | 项目经理 | Redmine 验收标准录入与派发 | 会后 4 小时内 | S2-01 至 S2-05 任务卡片的自定义字段已按 CONF-15 逐条填入,并指派给研发。 | 
40
41
--- 
42
## 三、 会议风险项 
43
44
| 编号 | 风险描述 | 应对策略/演进路线 | 跟踪机制 | 
45
| :--- | :--- | :--- | :--- | 
46
| **RISK-01** | 白名单基金覆盖率(`whitelist_retention_rate`)在压测时可能异常跌破 15% 阈值(R-6 风险具象化)。 | 采用“有条件放行”策略。M2 不阻断,但立即建立 High 级别 Redmine Bug 单,PM 联动数据资产管理员排查上游数据质量或白名单规则。若下个 Sprint 回顾前未闭环,则熔断后续 Pipeline。 | Redmine Bug 单状态跟踪,Sprint 回顾会专项汇报。 | 
47
48
--- 
49
## 四、 会议架构决策记录(ADR)产出要求 
50
51
基于本次会议确认的 CONF-02、CONF-07、CONF-08,要求研发工程师在会后 PR 中,于 Gitea 仓库 `/docs/adr/` 目录下提交以下 ADR: 
52
1. **`002-s2-full-arrow-exception.md`**:固化 FW-3 豁免的技术选型理由(预估内存安全边界、状态机行级处理特性)及严苛的下游禁用声明。 
53
2. **`003-s2-timeseries-state-machine-decisions.md`**:固化为何建仓期剔除仅锚定 `segment_0`(遵守 CR-001 段独立性),以及为何异常值判定必须剥离 ffill 填充值(防止长缺失恢复首周误杀)。 
54
55
--- 
56
## 五、 Sprint 2 交付物全景清单(已对齐) 
57
58
以下为本次会议最终锁定需新增或重写的文件及核心约束简述(供 Redmine 录入与 PR 检查点对齐): 
59
| 分类 | 文件路径 | 核心约束简述 | 
60
| :--- | :--- | :--- | 
61
| **业务参数** | `config/config.yaml` | 新增 `data.raw_paths` 与 `duckdb.memory_limit` 节点,零魔法数字。 | 
62
| **核心源码** | `src/data_loader.py` | DuckDB 显式投影防偏移;Polars 全量 Arrow 状态机处理(含 CONF-07/CONF-08 逻辑);Parquet 4 列强锁落盘;VIEW 动态重建。 | 
63
| **工具扩展** | `src/utils/tools.py` (或同类) | 包含 `calc_rate` 函数(分母为 0 时返回 0.0),仅随 S2-04 交付,禁止提前产出。 | 
64
| **质量保障** | `tests/test_data_loader.py` | 覆盖 segment_0 吞噬后保留、长缺失免误杀等 5 个核心边界单测。 | 
65
| **数据产出** | `data/processed/net_value_YYYY.parquet` | `row_group_size=100000`,覆盖写,绝对纯净的 4 列 Schema。 | 
66
| **数据产出** | `data/metadata/data_audit.json` | 严格符合 5 大根节点 Schema,`retention_rate` 分母取物理总行数。 | 
67
| **数据产出** | `data/metadata/processed_hash.json` | 落盘后即刻生成的各年份文件 SHA-256 校验字典。 | 
68
| **技术真相** | `docs/data_contract.md` | 沉淀完整的数据流转 Schema 与类型映射契约。 | 
69
| **技术真相** | `docs/adr/002-s2-full-arrow-exception.md` | 记录 FW-3 豁免决策。 | 
70
| **技术真相** | `docs/adr/003-s2-timeseries-state-machine-decisions.md` | 记录状态机核心边界决策。 | 
71
72
--- 
73
**会议闭环状态**:✅ 全部议程结束,无遗留阻塞项。研发团队可依据本纪要启动 Sprint 2 实施。