# Project 领域重构指南 ## 1. 范围 本文档只做 Project 领域的项目侧梳理。 必须覆盖两类字段: - 集团侧 `enum_config` 对应到 Project 的字段:`project_type`、`project_subtype`、`syxs`、`status` - depm 本地已经做成 enum / 类 enum,或者已经承担初始化、创建校验、业务分支职责的字段:`progress_type`、`key_constraints`、`build_methods` 本文重点只回答四件事: - 当前有几种实现 - 模型层怎么存 - 是否真的用到了 - 创建、校验、初始化时怎么取值,以及下一步怎么改 ## 2. 字段盘点 ### 2.1 `project_type` 当前实现: - `app/common/enums.py` 有一套 `ProjectType` - `app/modules/project/enums.py` 又有一套 `ProjectType` - `Project` 模型里是 `String` - `ProjectCreate` / `ProjectUpdate` 里是 `str` 当前使用: - `ProjectService.init_project_data)` 用它作为初始化路由键,去取 `preparation_{project_type}`、`production_{project_type}`、`power_{project_type}` 等配置 - construction 模块的部分接口和工具也直接复用 `ProjectType` 当前问题: - 定义重复 - 模型层不是 enum - 创建 / 更新入口没有真正按 enum 校验 项目侧取值范围: - 当前代码里的正式候选项是:`photovoltaic`、`onshore_wind`、`offshore_wind`、`pumped_storage`、`hydropower`、`thermal_power`、`gas_turbine` - `app/common/enums.py`、`app/modules/project/enums.py`、初始化 `dict_data.csv` 当前三处是一致的 集团侧差异: - 当前未看到和项目侧不同的取值项 - 这个字段目前可视为两边一致 - 但同步 schema 里的 `project_type` 字段当前仍写成 `str`,并没有直接收紧成 `ProjectType` 结论: - 保留为代码 enum - 它是核心路由字段,不迁到 `field_config` ### 2.2 `project_subtype` 当前实现: - 模型里是 `String` - `ProjectCreate` / `ProjectUpdate` 里是 `str` - 没看到本地正式 enum 当前使用: - 当前主要是项目主表字段 - 在 Project 模块内没看到稳定初始化使用点 当前问题: - 没有统一候选项来源 - 没有统一校验 - 现在只是“字符串字段” 项目侧取值范围: - 目前列不出权威范围 - 当前项目侧没有正式 enum、没有 `field_config`、没有 schema 白名单 - 从代码看,它现在理论上就是任意字符串 项目侧可举例范围: - 目前只能借集团侧样例来猜测,例如光伏下可能是 `centralized`、`distributed` 集团侧差异: - 集团侧至少给出了光伏下的两个候选项:`centralized`、`distributed` - 项目侧目前一个正式值都没有落下来 - 其它项目类型在集团侧也基本还是空占位,所以当前只能确认“项目侧缺正式范围,集团侧只给了局部样例” - 同步 schema 对应字段名是 `sub_type`,当前类型是 `Optional[str]`,也还没有 enum 化 困难: - 项目侧没有正式定义源,无法从代码中反推出完整候选项集合 - 集团侧除光伏外也没有给出有效值,导致现在只能列出局部范围,不能写成全项目类型完整表 结论: - 改为 `field_config` - 建议 key:`project.project_subtype` ### 2.3 `syxs` 当前实现: - 模型里是 `String` - 当前 `ProjectCreate` / `ProjectUpdate` 里没有这个字段 - 没看到本地正式 enum 当前使用: - 在 Project 模块内暂时没看到明确创建、初始化或校验使用点 - 当前更像是“模型里已有字段,但项目侧链路尚未收敛”的状态 当前问题: - 有字段,但没有统一入口 - 没有统一候选项来源 - 没有统一校验 项目侧取值范围: - 目前列不出权威范围 - 项目模型里有这个字段,但 Project 创建 schema 里没有该字段 - 代码中也没找到项目侧正式 enum 或统一校验入口 项目侧可举例范围: - 当前只能参考集团侧海风场景,例如 `hssy`、`lssy` 集团侧差异: - 集团侧至少给出了海风下的两个候选项:`hssy`、`lssy` - 项目侧目前还没有正式范围、正式入口和正式校验 - 其它项目类型在集团侧也基本还是空占位,所以当前差异主要是“集团侧有局部定义,项目侧还没落下来” - 同步 schema 中 `syxs` 当前也是 `Optional[str]`,还没有 enum 化 困难: - 项目侧没有正式输入入口,也没有权威候选项定义 - 集团侧只有海风场景给了有效值,因此暂时只能列局部范围 结论: - 改为 `field_config` - 建议 key:`project.syxs` ### 2.4 `status` 当前实现: - `app/modules/project/enums.py` 里有 `ProjectStatus` - `app/common/enums.py` 里也有一套 `ProjectStatus` - 模型里是 `String` - 响应 schema 里是 `str` 当前使用: - `ProjectService.change_project_status()` 会根据 `investment_decision_date`、`actual_construction_date`、`actual_production_date`、`actual_full_production_date` 计算并写回 `status` 当前问题: - 定义重复 - 模型层不是 enum - 当前状态值由服务逻辑推导,但接口层没有统一约束 - 当前正式状态集合必须补上 `early` - 当前库里还存在 `NULL`,这说明状态机和历史数据都还没收口干净 项目侧取值范围: - 目标正式值应为:`early`、`preparation`、`started`、`partially_connected`、`completed` - 当前代码定义和服务推导还没有完整覆盖这 5 个值 - 当前实际数据里还存在 `NULL`,这应视为待清理异常值,而不是正式取值 集团侧差异: - 项目侧最终也要纳入 `early` - 因此这个字段的目标差异不是“是否要 early”,而是“项目侧何时补齐 early 并清掉 NULL” - 同步 schema 里的 `status` 当前也是 `Optional[str]`,没有正式 enum 类型 结论: - 保留为代码 enum - 正式状态集合按 `early`、`preparation`、`started`、`partially_connected`、`completed` 收口 - `NULL` 不是正式值,必须通过数据修复和状态回填消除 ### 2.5 `progress_type` 当前实现: - `app/common/enums.py` 和 `app/modules/project/enums.py` 都定义了 `ProgressType` - 模型里是 `SQLEnum(ProgressType)` - `ProjectInfoResponse` 里使用 `ProgressType` - `ProjectCreate` 里仍是 `str` 当前使用: - 是 Project 主表正式字段 - `project_detail_data` 初始化时会写入 `progress_type` - 列表筛选也会直接按它查询 当前问题: - 定义重复 - 模型层已收紧,但创建入口还没收紧 项目侧取值范围: - 当前正式值是:`A`、`B`、`C` - `app/common/enums.py`、`app/modules/project/enums.py`、初始化 `dict_data.csv` 当前是一致的 集团侧差异: - 集团侧同步 schema 中,`progress_type` 也是正式 enum - 同步 schema 的取值项是:`A`、`B`、`C` - 这一点和项目侧代码 enum 一致 - 当前真正的差异不在取值范围,而在项目侧创建入口仍是 `str`,还没有像同步 schema 一样收紧到 enum 类型 困难: - 这个字段属于 depm 本地已枚举化字段,不是集团侧 `Project` 段直出字段 - 因此这里只能写“项目侧权威范围明确,集团侧暂无直连字段” 结论: - 保留为代码 enum - 不迁到 `field_config` ### 2.6 `key_constraints` 当前实现: - `app/common/enums.py` 和 `app/modules/project/enums.py` 都定义了 `KeyConstraints` - 两套定义已分叉:common 里有 `OTHER`,project 模块里没有 - 模型里是 `SQLEnum(KeyConstraints)` - `ProjectInfoResponse` 里使用 `KeyConstraints` - `ProjectSquareUpdate` 里还是 `str` 当前使用: - 是 Project 主表正式字段 - `project_detail_data` 初始化时会和 `progress_type` 一起写入 - `project_detail_data` 审批回写时也会更新 Project 主表 当前问题: - 定义分叉 - 主表是 enum,但部分接口入参仍是字符串 - 项目侧正式口径应保留 `OTHER` 项目侧取值范围: - 项目侧正式值应为:`NONE`、`LAND`、`TRANSMISSION`、`BOTH`、`OTHER` - 当前 `app/common/enums.py` 和初始化 `dict_data.csv` 已包含这 5 个值 - 当前 `app/modules/project/enums.py` 还少 `OTHER` 集团侧差异: - 集团侧同步 schema 中,`key_constraints` 也是正式 enum - 同步 schema 的取值项是:`NONE`、`LAND`、`TRANSMISSION`、`BOTH`、`QT` - 项目侧正式口径保留 `OTHER` - 这里需要备注的差异只有一条:集团侧用 `QT`,项目侧用 `OTHER` 结论: - 保留为代码 enum - 项目侧按 `OTHER` 收口 - 在同步和推送链路上单独备注 `OTHER/QT` 差异 - 同时补齐 project 模块那套缺失的 `OTHER` ### 2.7 `build_methods` 当前实现: - `app/modules/project/enums.py` 里有 `BuildMethod` - 模型里是 `String` - `ProjectCreate` 里是 `str` - 响应 schema 通过 `enum_to_build_method_name` 做名称映射 当前使用: - construction 服务里确实使用了 `project.build_methods` - 它会影响施工分支判断,例如 `power`、`mixed`、`none`、`self` 当前问题: - 模型层不是 enum - 创建入口不是 enum - 已经参与业务分支,但没有统一校验 项目侧取值范围: - 当前代码 enum 给出的值是:`self`、`power`、`mixed`、`none` - construction 分支判断当前也确实在使用这 4 个值 集团侧差异: - 集团侧同步 schema 里没有 `build_methods` 这个名字,对应字段是 `scgc_type` - `scgc_type` 当前在同步 schema 里是 `Optional[str]` - 目前没有看到同步 schema 中为 `scgc_type` 定义正式 enum 也就是说: - 语义上两边大概率是在说同一个字段 - 但集团侧当前还没有把它收紧成正式 enum - 因此这个字段更像“项目侧本地先有 enum,集团侧 schema 还停留在字符串字段” 困难: - 这是项目侧本地业务字段,不是集团侧 `Project` 段直出字段 - 项目侧虽然已有 enum,但模型和 schema 还没完全收紧 结论: - 保留为代码 enum - 不迁到 `field_config` - 需要把模型层和 schema 层一起收紧 ## 3. 重点结论 - `project_type` 是初始化路由键,必须保留为代码 enum。 - `project_subtype`、`syxs` 是典型的按项目类型变化字段,应迁到 `field_config`。 - `progress_type`、`key_constraints`、`build_methods` 都已经在项目侧真实使用,不是可忽略字段;它们应保留为代码 enum,但当前实现没有收紧干净。 - `status` 也应保留为代码 enum,正式集合必须包含 `early`,并且要清掉历史 `NULL`。 - `key_constraints` 项目侧正式口径保留 `OTHER`,只需要在文档和同步链路上备注集团侧使用 `QT`。 ## 4. 待修改项清单 1. 统一 `ProjectType` 定义,只保留一套正式 enum。 2. 统一 `ProjectStatus` 定义,并把 `early` 纳入正式状态集合。 3. 统一 `ProgressType` 定义,只保留一套正式 enum。 4. 统一 `KeyConstraints` 定义,项目侧正式保留 `OTHER`。 5. 将 `project.project_subtype` 改为字段级配置,放入 `field_config`。 6. 将 `project.syxs` 改为字段级配置,放入 `field_config`。 7. 为 `project_type` 在 schema 层增加正式 enum 校验,不再使用裸 `str`。 8. 为 `progress_type` 在创建入口增加正式 enum 校验。 9. 为 `build_methods` 在 schema 层增加正式 enum 校验。 10. 为 `key_constraints` 在仍使用字符串入参的接口上补 enum 校验。 11. 清理 `Project.status` 的历史 `NULL` 数据,并补状态回填逻辑,确保 `NULL` 不再出现。 12. 评估 `Project` 模型中的 `project_type`、`status`、`build_methods` 是否要收紧为数据库 enum;如果暂不收紧,也要先统一 schema 校验。 13. 为 `project_subtype`、`syxs` 增加统一读取入口:`get_field_options()` / `validate_field_value()`。 14. 明确 `status` 的唯一来源:是继续由 `change_project_status()` 推导,还是允许手动写入。 ## 5. 推荐落地顺序 1. 先统一 `ProjectType`、`ProgressType`、`KeyConstraints`、`ProjectStatus` 的定义源。 2. 再补 schema 层校验,把 `project_type`、`progress_type`、`build_methods`、`key_constraints` 的裸字符串入口收紧。 3. 先补 `status=early` 和历史 `NULL` 清理,再把 `project_subtype`、`syxs` 迁到 `field_config`。 ## 6. 按本轮要求的改造落点 project 领域这轮要区分两类字段: - 固定语义枚举:继续保留代码 enum - 按项目类型变化的候选项:迁到 field config ### 6.1 初始化 Project 本身不直接批量初始化子表候选项,但 `project_type` 是整个初始化流程的路由键。 因此本轮在 project 领域的初始化要求是: - `project_type` 继续保留代码 enum,作为 config 路由键 - `project_subtype`、`syxs` 如果进入初始化或默认值链路,应直接从 field config 读取 ### 6.2 Create / Update 校验 这轮 project 领域要分开处理: - `project_type`、`status`、`progress_type`、`key_constraints`、`build_methods`:继续按代码 enum 收紧 - `project_subtype`、`syxs`:通过新的 config 工具校验 也就是说,project 领域不是“全部配置化”,而是“固定 enum 和 field config 分流后分别收紧”。 ### 6.3 下拉列表 当前 project 领域里: - `project_type`、`progress_type`、`key_constraints`、`build_methods` 更适合继续由代码 enum 提供候选项 - `project_subtype`、`syxs` 后续如果提供前端下拉,应直接走 field config 本轮目标是明确边界,避免把本地固定枚举误迁到 config,也避免把按项目类型变化字段继续留成自由字符串。 ### 6.4 清理项 project 领域本轮改造完成后,应清理: - 重复的 enum 定义源 - 仍以裸字符串接收固定 enum 字段的入口 - `status` 历史 `NULL` 值 ## 7. 测试关注点 project 领域 service 层集成测试至少覆盖: 1. `project_type`、`progress_type`、`key_constraints`、`build_methods` 的合法/非法值校验。 2. `project_subtype`、`syxs` 的 field config 读取与校验。 3. `status` 状态推导补齐 `early` 且不再产生 `NULL`。