# 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`。
