Files
ecm_sync_system/docs/enum_refactor/plan.md
T
2026-03-12 11:39:57 +08:00

13 KiB
Raw Blame History

项目侧字段级配置重构计划

1. 目标

本次重构的目标,不是先统一项目侧全部 config / enum / dict 的历史实现,而是先补一套项目侧自己的“字段级配置总表”和配套工具,使下面这类问题有统一解法:

  • 某个字段在某个 project_type 下有哪些可选项。
  • 这些可选项的中文名是什么。
  • 业务代码如何方便地做手动校验。
  • 初始化流程如何从同一套字段级配置中读取候选项。
  • 哪些字段应该继续在代码里用 enum 处理。
  • 哪些字段应该迁到配置文件处理。

这套能力对齐集团侧 enum_config.json 的思路,但项目侧不照搬集团侧的存储形式,而是基于现有 config 表和 CSV 数据维护方式落地。

因此,本次重构除了建设字段级配置总表本身,还需要同步完成一件事:

  • 梳理字段边界,明确哪些字段属于“固定语义、固定值集合、适合保留在代码 enum 中”,哪些字段属于“随 project_type 或初始化流程变化、应迁到字段级配置中”。

也就是说,这次不是单纯补一个配置表,而是要同时建立一套可执行的字段分流规则:

  • enum 字段:继续留在代码中,由 enum 表达固定约束。
  • field_config 字段:迁到配置中,由字段级配置表达候选项。

2. 重构范围

本次重构只覆盖一类字段:

  • 对应集团侧 enum_config.json 相应字段的项目侧字段。
  • project_type 相关。
  • 和项目初始化过程相关。

也就是说,本次优先处理的是“初始化候选项字段”,而不是全部字符串字段。

优先关注的业务范围包括:

  • project 初始化时需要的字段候选项
  • contract 初始化相关字段
  • construction 初始化相关字段
  • material 初始化相关字段
  • power / preparation / production 中与项目类型初始化直接相关的字段

从现状代码看,这些初始化来源当前混杂在:

  • Python enum
  • Python 硬编码 dict
  • dict_data
  • config 表中的自由 JSON
  • 外部业务大表或类型树

本次要做的不是一次性清理所有来源,而是为这些字段补一个统一的字段级配置出口。

3. 不在本次范围内的内容

以下内容明确不在本次重构范围:

  • 接口返回层的翻译统一
  • dict 体系的整体重构
  • 所有历史自由 config 的一次性收敛
  • 所有外部树和业务大表的一次性字段化
  • schema 目录下的类型系统改造
  • 运行时直接读取 JSON 文件作为配置源

因此,本次允许字段级配置中的 value / label 与现有 dict 翻译短期重复。后续如有需要,可再通过脚本生成 dict 或做统一翻译收敛。

4. 核心设计

4.1 新增“字段级配置总表”概念

项目侧需要新增一层固定格式的字段级配置。

这层配置的职责只有一个:

  • 对外统一回答“某字段在某项目类型下有哪些可选项”。

它不是为了取代所有业务 config,而是为了给业务代码、初始化流程、推送系统提供统一的读取接口。

4.2 存储方式

字段级配置继续存放在现有 config 表中,并通过现有 CSV 数据维护。

建议约束如下:

  • category = project_type
  • config_type = json
  • config_key 不强制按规则自动拼接
  • 代码里仍允许手动传入 key 查询

虽然推荐把 key 命名为 model_name.field_name,例如:

  • construction.section_type_code
  • material.material_type
  • contract.code

但这只是推荐命名,不作为框架自动推导前提。

也就是说:

  • 本次不建设“根据 model + field 自动拼 config key”的机制
  • 只建设“拿到 key 后,按统一 schema 读取字段级配置”的机制

4.3 固定 JSON 结构

字段级配置的 default_value / config_value 必须是一个 JSON object。

对于字段级配置,只强约束一件事:

  • 顶层必须有 field_config

其中:

  • field_config 必须是列表
  • 列表中每个元素至少有 valuelabel

推荐最小结构如下:

{
  "field_config": [
    {"value": "GF", "label": "光伏场区", "weight": 1.4},
    {"value": "SY", "label": "升压站", "weight": 1.4}
  ]
}

field_config 外,其它顶层字段不做统一规定,由各业务自己定义。

例如,某些字段配置可能会额外包含:

  • description
  • init_config
  • 其他与该字段强相关的附属配置

这些字段允许存在,但不纳入统一字段级 schema 强约束。

4.4 非字段级 config 的统一要求

对于仍然保留的自由业务 config,不要求包含 field_config

但建议默认增加一个顶层字段:

  • description

这样至少能保证所有 config 在文档和工具层面具备最低限度的语义说明。

5. field_config Schema 设计

5.1 字段候选项结构

字段级配置中的 field_config 列表项,最少包含:

  • value: str
  • label: str

允许附加字段,例如:

  • weight
  • unit
  • section
  • legacy
  • flag
  • skip_init
  • skip_create

这些附加字段不要求在所有字段中统一,但它们必须和值本身强相关。

5.2 建议的 Pydantic 模型

建议新增专门的 Pydantic schema,用于字段级配置校验。

from pydantic import BaseModel, ConfigDict, Field


class FieldOptionItem(BaseModel):
    model_config = ConfigDict(extra="allow")

    value: str = Field(..., description="候选值")
    label: str = Field(..., description="显示名")

说明:

  • 只需要识别 field_config 这个字段
  • 只需要校验 field_configlist[FieldOptionItem]
  • 其它顶层字段不在统一 schema 里写死

6. ConfigService 统一承载

字段级配置相关的辅助方法统一放到 ConfigService,不再额外拆新的独立 service。

也就是说,这次应增强的是:

  • app/modules/system/services/config.py

而不是在别处再起一套并行 helper。

6.1 建议新增的方法

async def get_field_config(project_type: str, key: str, tenant_id: str | None = None) -> dict[str, Any]:
    ...


async def get_field_options(project_type: str, key: str, tenant_id: str | None = None) -> list[dict[str, Any]]:
    ...


async def get_field_option_map(project_type: str, key: str, tenant_id: str | None = None) -> dict[str, dict[str, Any]]:
    ...


async def get_field_label(project_type: str, key: str, value: str, tenant_id: str | None = None) -> str | None:
    ...


async def validate_field_value(
    project_type: str,
    key: str,
    value: str,
    tenant_id: str | None = None,
    *,
    error_prefix: str | None = None,
) -> None:
    ...

6.2 各接口职责

get_field_config()

  • 读取指定 project_type + key 的 JSON 配置
  • 找出顶层 field_config
  • 校验 field_config 是否满足 list[FieldOptionItem]
  • 返回完整配置对象

get_field_options()

  • 返回 field_config 列表
  • 供初始化、下拉、候选项获取直接使用

get_field_option_map()

  • 返回 {value: option} 的映射
  • 供快速查找、补充属性读取使用

get_field_label()

  • 根据 value 返回 label
  • 本次主要服务于业务内部使用,不承担接口翻译统一职责

validate_field_value()

  • 手动调用
  • 校验给定值是否在当前项目类型该字段的候选项中
  • 校验失败时抛出业务异常

这里特别强调:

  • validate 是手动调用的
  • 本次不做自动注入所有模型或 schema 的统一校验框架
  • 目标是让业务层“方便调用”,不是一次性接管所有校验入口

6.3 ConfigService 需要补齐的基础能力

ConfigService 当前除了读取外,还需要补齐两类基础能力:

1. JSON 序列化 / 反序列化统一承载

不应继续让调用方自己到处 msgspec.json.decode(...)

ConfigService 应统一负责:

  • 读取 JSON 配置时反序列化
  • 更新 JSON 配置时序列化
  • 在字段级配置方法中复用这一套逻辑

也就是说,配置的 JSON 处理应沉到 ConfigService 本身,而不是散落在各业务模块里。

2. 关键读取方法加缓存

ConfigService 中的关键读取方法应增加缓存。

优先包括:

  • get_config_json()
  • get_field_config()
  • get_field_options()
  • get_field_option_map()
  • get_field_label()

要求:

  • 读接口走缓存
  • 更新配置后显式失效相关缓存
  • 不允许缓存让旧配置长期滞留

validate_field_value() 自身不需要单独缓存,但应复用已经缓存过的字段选项读取结果。

6.4 实现要求

工具类内部应统一处理:

  • 读取 config 表 JSON
  • 用 Pydantic 校验 field_config
  • 构建 options / map / label
  • 输出清晰错误,便于发现配置格式问题

不允许的处理方式:

  • 遇到 schema 错误时静默降级
  • 遇到缺字段时自动猜测兼容成其他结构

如果配置不满足 field_config schema,应直接报错。

7. JSON / CSV 工具重整

本次不要求运行时直接读取 JSON 文件。

但为了方便调试和维护,应把现有 scripts/tool/csv_data_tools/ 按新的字段级配置目标重新整理。

目标是:

  • 一个 key 对应一个 JSON 文件
  • JSON 文件仍然采用 config 行列表格式
  • 工具负责 JSON 和 config.csv、数据库之间的转换

建议目录:

  • scripts/initdata/json_config/

建议能力:

  • validate-json-config: 校验 JSON 文件是否满足 config 行结构,以及 field_config 约束
  • json-to-csv: 从 json_config 目录批量生成 config.csv
  • json-to-db: 从 json_config 目录批量导入数据库
  • csv-to-json: 从 config.csv 按 key 导出 JSON 文件
  • db-to-json: 从数据库导出 JSON 文件用于调试

当前已有的 scripts/tool/csv_data_tools/csv_data_tools.py 可以复用思路,但需要围绕“字段级配置总表 + 一 key 一文件”的目标重新整理。

8. 领域重构指南

主计划只定义系统级方案。具体领域的梳理应拆到独立文件,例如:

后续 project / contract / material 等领域也应按同样方式分别整理。

每个领域文档都应至少覆盖以下内容:

8.1 整理不查表的 enum 类数据

不是只有查表型数据才需要整理。

如果某领域存在不依赖字典表、也不依赖 config 表的纯 enum / 纯硬编码候选项,同样要梳理:

  • 当前定义位置在哪
  • 是否重复定义
  • 是否和集团侧 enum_config 对应字段有关
  • 是否需要继续保留为代码 enum
  • 是否应该迁到新的 field_config

也就是说,纯 enum 数据如果和初始化字段、手动创建字段有关,也在本轮视野内。

8.2 梳理哪些功能需要迁移到新的 field_config

每个领域都需要明确列出:

  • 哪些初始化功能依赖字段候选项
  • 哪些手动创建 / 编辑功能依赖字段候选项
  • 哪些历史白名单校验需要迁移到 validate_field_value()
  • 哪些直接遍历旧 JSON 的逻辑需要迁移到 get_field_options() / get_field_option_map()

并且必须给出示例。

示例类型包括:

  • 初始化时从旧 config 直接遍历候选项,改为从 field_config 读取
  • 手动创建时从硬编码 dict 判断合法值,改为调用 validate_field_value()
  • 展示名称时从字段选项里直接拿 label

8.3 重点关注集团侧 enum_config 对应字段

领域梳理时,应优先关注集团侧 enum_config 中已有对应关系的字段。

因为这类字段本身就属于:

  • 按项目类型变化
  • 会参与初始化
  • 通常也会参与手动创建与校验

因此,每个领域都应围绕这类字段检查两条链路:

  • 项目初始化时,取值从哪里来,如何展开
  • 手动创建 / 更新时,取值如何校验,是否和初始化使用同一来源

如果两条链路来源不一致,应明确记录并给出迁移方案。

9. 实施步骤

第一阶段:定义公共 schema 和 ConfigService 接口

  • 明确字段级配置固定 JSON 结构
  • 新增 FieldOptionItem
  • ConfigService 中新增字段级配置方法
  • 给关键读取方法加缓存
  • 补齐 JSON 序列化 / 反序列化统一处理
  • 明确错误处理规则:配置结构不合法时直接报错

第二阶段:重整 JSON / CSV 工具

  • 建立 scripts/initdata/json_config/ 目录规范
  • 支持一 key 一文件
  • 支持 json_config 批量生成 config.csv
  • 支持 json_config 批量导入数据库
  • 支持从 config.csv / 数据库反向导出 JSON

第三阶段:按领域逐个梳理并迁移

  • 按领域编写独立重构文档
  • 明确该领域哪些字段迁到 field_config
  • 改造初始化流程和手动创建校验流程
  • 下线对应的硬编码白名单和不再适合承担初始化来源的 dict 使用方式

10. 产出要求

本次重构最终至少要产出:

  • 一套明确的字段级配置 schema
  • 一套明确的 ConfigService 字段级配置接口
  • 一套明确的缓存与缓存失效规则
  • 一套 JSON / CSV / DB 三者之间的转换与维护规范
  • 一组领域级重构文档

判断本次重构是否成功的标准不是“历史实现全部统一”,而是:

  • 后续新增一个与项目类型初始化相关的字段时,知道应优先写入字段级配置总表
  • 业务代码可以通过 ConfigService 统一获取 options / map / label / validate
  • 初始化流程和手动创建校验可以逐步收敛到同一来源