Files
SystemSimulationApp/docs/component-library-spec-v1.md
T

24 KiB
Raw Blame History

组件库分类、发现与读取规范 v1

状态:已在 experimental 临时组件库实施
适用范围:app/simulation/components、组件注册中心、System XML 和 React Flow 组件库
当前试验库:experimental(界面名称:临时测试组件库)

0. 文档定位

本文档只负责“模型如何被系统发现和读取”。模型方程、状态、参数和结果应如何编写, 统一参见组件模型建模规范 v1。

人工或 AI 排查组件读取问题时,按以下顺序读取:

  1. app/simulation/registry.py 中的 ENABLED_COMPONENT_LIBRARIES。
  2. 被启用组件库的 library.py。
  3. library.py/models 明确列出的模型类。
  4. 模型类的 DISPLAY / PORTS / PARAMETERS。
  5. build_component_catalog() 的输出。
  6. 前端 normalizeComponentCatalog()。

当前事实来源优先级:

信息 唯一事实来源 不能作为事实来源
启用哪些库 ENABLED_COMPONENT_LIBRARIES 目录中碰巧存在的文件夹
库分类和模型清单 各库 library.py 前端组件列表
模型类型和版本 模型类 文件名或中文名称
物理端口 模型类 PORTS DISPLAY.ports 或画布方向
参数默认值和边界 模型类 PARAMETERS 前端兜底定义
图标和端口位置 模型类 DISPLAY 物理方程
前端运行目录 /api/components/catalog 手工扫描 Python 包

如果文档、代码和目录响应不一致,应在同一次修改中修复并增加测试,不能通过复制 另一份映射临时绕过。

1. 目标

本规范用于统一以下内容:

  1. 后端组件库如何声明自身信息和分类。
  2. 单个模型如何声明端口、参数、结果变量和界面显示信息。
  3. 后端如何发现、校验、注册并实例化模型。
  4. 前端如何通过统一目录接口自动生成左侧组件库和参数面板。
  5. System XML 中的模型类型如何稳定映射到 Python 实现。

目标工作流如下:

flowchart LR
    A["ENABLED_COMPONENT_LIBRARIES"] --> B["library.py"]
    B --> C["受控导入模型类"]
    C --> D["启动契约校验"]
    D --> E["组件注册表"]
    E --> F["GET /api/components/catalog"]
    F --> G["React Flow 组件库和参数面板"]
    E --> H["System XML 模型实例化"]

新增一个符合本规范的模型后,前端不应再修改 App.tsx 中的组件列表、参数列表 或分类列表。只有新增一种前端尚不支持的图形渲染方式时,才需要补充前端图标组件。

2. 术语和层级

组件目录采用四个相互独立的概念:

层级 示例 含义
组件库 Library experimental 一组具有共同发布、维护和版本边界的模型
界面分类 Category storage 只用于组件面板分组和排序
物理域 Domain pneumatic 决定端口能否连接以及采用哪组连接方程
模型 Model cylinder 可实例化并写入 System XML 的稳定模型类型

分类和物理域禁止混用。例如,storage 是界面分类,pneumatic 是物理域; 一个“储能元件”也可以属于液压域,不能根据分类推断端口连接规则。

标准层级为:

Library
  Category
    Model
      Port
        Port variable
      Parameter
      Result variable

3. 推荐目录结构

每个组件库使用独立 Python 包:

app/simulation/components/
  experimental/
    __init__.py
    library.py
    storage/
      __init__.py
      cylinder.py
      tank.py
    flow/
      __init__.py
      orifice.py
      resistive_pipe.py
    junctions/
      __init__.py
      tee.py

规则如下:

  • library.py 是组件库唯一清单入口。
  • 分类目录名必须与分类 ID 一致。
  • 一个公开模型原则上放在一个独立 .py 文件中。
  • 求解器、介质、通用方程和状态对象不得放入组件库目录。
  • 未准备对外注册的实验类可以保留在库内,但不得加入库清单。
  • 禁止通过扫描任意 .py 文件并执行其中代码来发现模型;必须使用库清单进行受控导入。

4. 标识符和版本

4.1 标识符

以下字段使用稳定的英文机器标识:

  • library.id
  • category.id
  • MODEL_TYPE
  • 端口名
  • 参数名
  • 结果变量名

标识符应使用 snake_case,只允许小写英文字母、数字和下划线,并以字母开头。 已有工程约定中的 T、U 等热力学变量可以保留。

界面中文名称单独保存在 label 中。修改 label 不影响工程兼容性;修改机器标识 会影响工程文件、System XML、结果文件和后端注册,因此发布后不得直接改名。

4.2 版本

每个组件库和模型都应具有版本:

LIBRARY_VERSION = "0.1.0"
MODEL_VERSION = "1.0.0"

版本遵循 主版本.次版本.修订版本:

  • 修订版本:只修复实现,不改变输入输出契约。
  • 次版本:向后兼容地新增参数、结果或能力。
  • 主版本:端口、参数语义或方程发生不兼容变化。

当前 System XML v2 尚未保存模型版本。正式发布组件库前,应在 XML 中增加 library 和 modelVersion,并提供旧工程迁移规则。

5. 组件库清单

每个库必须提供 library.py,并使用有类型的不可变声明:

from app.simulation.core.catalog import (
    ComponentCategorySpec,
    ComponentLibrarySpec,
)

LIBRARY = ComponentLibrarySpec(
    id="experimental",
    label="临时测试组件库",
    version="0.1.0",
    source_package="app.simulation.components.experimental",
    temporary=True,
    order=100,
    categories=(
        ComponentCategorySpec(
            id="storage",
            label="储能元件",
            order=10,
        ),
        ComponentCategorySpec(
            id="flow",
            label="流动元件",
            order=20,
        ),
        ComponentCategorySpec(
            id="junctions",
            label="连接元件",
            order=30,
        ),
    ),
    models=(
        "app.simulation.components.experimental.storage.cylinder:Cylinder",
        "app.simulation.components.experimental.storage.tank:Tank",
        "app.simulation.components.experimental.flow.resistive_pipe:ResistivePipe",
        "app.simulation.components.experimental.flow.orifice:Orifice",
        "app.simulation.components.experimental.junctions.tee:Tee",
    ),
)

models 是唯一允许注册到系统的模型清单。它同时解决以下问题:

  • 避免导入测试脚本或内部辅助类。
  • 控制模型加载顺序。
  • 避免同一个 MODEL_TYPE 被多个实现重复注册。
  • 可以只加载部署环境允许使用的组件库。
  • 启动错误能明确定位到具体库和模型。

当前 experimental/library.py 已按此格式声明库、分类和五个公开模型; experimental/__init__.py 只保留旧常量的兼容别名。

6. 模型类契约

一个可注册模型必须显式声明:

class ExampleComponent(Component):
    MODEL_TYPE = "example_component"
    MODEL_VERSION = "1.0.0"
    PORTS = (...)
    PARAMETERS = (...)
    RESULT_VARIABLES = (...)
    DISPLAY = ...

字段含义:

字段 是否必须 用途
MODEL_TYPE 必须 System XML、注册表和结果元数据中的稳定类型
MODEL_VERSION 必须 模型契约和迁移版本
PORTS 必须 物理或信号连接契约
PARAMETERS 必须,可为空 用户输入参数及校验边界
RESULT_VARIABLES 必须,可为空 组件级可展示结果
DISPLAY 必须 前端名称、分类、图标、排序和端口布局

模型实现还必须满足:

  1. 构造函数调用 super().__init__(name)。
  2. 使用 set_parameter_values() 保存所有规范化后的参数。
  3. 使用 register_declared_port() 创建 PORTS 中声明的端口。
  4. 组件级结果键必须与 RESULT_VARIABLES 完全一致。
  5. 所有内部计算均使用 SI 基准值。
  6. 模型不能直接依赖 FastAPI、React Flow 或 XML DOM。
  7. 模型的方程不能依赖图标方向、界面分类或画布位置。

完整方程示例参见 app/simulation/components/example.md。

7. 界面显示声明

DISPLAY 只描述模型在前端的呈现,不参与物理求解:

from app.simulation.core.catalog import (
    ComponentDisplaySpec,
    PortDisplaySpec,
)

DISPLAY = ComponentDisplaySpec(
    label="示例容腔",
    library_id="experimental",
    category_id="storage",
    symbol="cylinder",
    order=90,
    ports=(
        PortDisplaySpec(
            name="port_a",
            side="left",
            order=10,
        ),
    ),
)

字段规则:

  • label:组件库中的显示名称。
  • library_id:必须引用已加载的库。
  • category_id:必须引用该库已声明的分类。
  • symbol:前端图形渲染器的稳定标识。
  • order:同一分类中的排序值。
  • ports:只声明端口在图形中的位置和顺序。

DISPLAY.ports 中的端口名必须与模型的 PORTS 完全一致,不能缺少、增加或改名。 旋转和镜像只改变前端计算后的视觉方位,不改变端口机器名和物理语义。

前端遇到未知 symbol 时必须显示通用占位图标,同时保留模型拖拽、参数编辑、 连线和 XML 生成功能,不能因为缺少专用图形而丢弃整个模型。

8. 端口规范

端口由 PortDefinition 声明:

PORTS = (
    PortDefinition.pneumatic(
        "port_a",
        nominal_role="bidirectional",
    ),
)

每个端口必须包含:

  • 稳定端口名。
  • kind:physical 或 signal。
  • domain:例如 pneumatic。
  • nominal_role:用于界面提示,不决定实际流向。
  • positive_flow_direction:物理流量变量的符号约定。
  • 端口变量及各自连接规则。

当前气动功率端口包含:

变量 角色 连接规则 单位
p effort equal Pa
m_flow flow sumToZero kg/s
h_outflow stream streamMix J/kg

气动端口统一约定 m_flow > 0 表示质量流入当前组件。inlet、outlet 是标称角色, 不应阻止反向流动;实际方向由求解结果中的流量符号决定。

连接校验至少包括:

  1. 两个端口均存在。
  2. 端口不能连接自身。
  3. kind 相同。
  4. domain 相同。
  5. 端口变量集合及连接规则兼容。
  6. 同一物理端口的连接数量符合当前网络编译器能力。

9. 参数规范

参数使用 ParameterDefinition 声明:

PARAMETERS = (
    ParameterDefinition(
        name="volume",
        label="容积",
        quantity="volume",
        unit="m3",
        default=0.1,
        minimum=0.0,
        minimum_exclusive=True,
    ),
)

规则如下:

  • name 是模型构造、XML 和工程文件共同使用的稳定名称。
  • label 是界面文案。
  • quantity 是受控物理量标识,用于前端匹配可换算单位。
  • unit 是后端 SI 单位。
  • default 必须能够直接创建合法模型。
  • 边界必须与方程有效范围一致。
  • 无量纲量使用 quantity="dimensionless" 和 unit=""。
  • 用户输入可以使用其他公制单位,但提交后端前必须换算为 SI。
  • 文本框编辑中的临时字符串不立即判错,失焦、回车或运行仿真时再执行数值校验。

后端不得静默忽略未知参数。缺少参数时可使用声明的默认值;出现未知参数时必须 返回包含组件 ID 和参数名的明确错误。

10. 结果变量规范

组件结果和端口结果分开管理:

  • 组件结果来自 RESULT_VARIABLES。
  • 端口结果根据 PORTS 中 result_visible=True 的端口变量自动生成。
  • 求解器缓存、残差和调试量默认不进入用户结果。

每个结果变量必须提供:

  • name
  • label
  • quantity
  • unit
  • category
  • order

仿真结果必须输出结构化元数据,前端禁止拆解结果键或按字符串关键词猜测:

{
  "key": "cylinder_1.port_b.p",
  "componentId": "cylinder_1",
  "componentType": "cylinder",
  "scope": "port",
  "portName": "port_b",
  "name": "p",
  "label": "压力",
  "quantity": "pressure",
  "unit": "Pa",
  "category": "effort",
  "order": 10
}

结果页应按 componentId、scope、portName、quantity 等结构化字段筛选, 而不是从 key 中推断组件、端口和变量。

11. 标准模型创建入口

注册中心通过统一入口创建模型,避免长期维护集中式 _cylinder_factory、 _tank_factory 等适配函数。当前接口为:

@classmethod
def create(
    cls,
    *,
    name: str,
    medium: IdealGasMedium,
    parameters: Mapping[str, float],
) -> Component:
    ...

创建流程:

  1. 注册中心按 PARAMETERS 填充默认值。
  2. 校验数值有限性和上下限。
  3. 拒绝未知参数。
  4. 调用模型类的 create()。
  5. 验证实例的模型类型、端口和参数快照。
  6. 将实例交给网络编译器。

这种方式允许 Python 构造参数保留内部命名,同时对外始终使用规范中的参数名。

12. 自动发现与注册

后端启动时按以下顺序建立注册表:

读取启用的 library.py
    -> 校验库 ID、版本和分类
    -> 按 models 清单导入模型类
    -> 读取模型静态契约
    -> 执行跨字段校验
    -> 建立 library registry
    -> 建立 model registry
    -> 构建前端 catalog

当前使用显式启用列表:

ENABLED_COMPONENT_LIBRARIES = (
    "app.simulation.components.experimental.library:LIBRARY",
)

禁止以下发现方式:

  • 在整个仓库递归导入所有 Python 文件。
  • 依赖文件名自动推断 MODEL_TYPE。
  • 由前端硬编码后端类路径。
  • 导入失败后悄悄跳过模型。
  • 多个实现重复注册同一个模型类型并由加载顺序决定最终结果。

发现或校验失败时,FastAPI 应拒绝启动并给出库 ID、模型类型、字段和原因。

13. 启动校验规则

注册表完成前必须执行以下校验:

13.1 库与分类

  • 库 ID 全局唯一。
  • 库版本格式有效。
  • 分类 ID 在库内唯一。
  • 所有排序值为整数。
  • 所有模型引用已存在的库和分类。

13.2 模型

  • MODEL_TYPE 全局唯一,且与注册键一致。
  • 模型版本格式有效。
  • 模型继承框架要求的基类。
  • 模型提供统一创建入口。
  • 默认参数能够成功创建实例。

13.3 端口

  • 端口名在模型内唯一。
  • 显示端口集合与物理端口集合完全一致。
  • 端口物理域、变量角色和连接规则有效。
  • 实例实际注册的端口与静态声明一致。

13.4 参数

  • 参数名在模型内唯一。
  • 默认值有限且满足边界。
  • minimum <= maximum。
  • quantity 和 unit 的组合已登记。
  • 实例保留所有规范化参数,不得静默修改或丢失。

13.5 结果

  • 结果变量名在对应作用域内唯一。
  • quantity 和单位有效。
  • component_result_values() 的键与声明一致。
  • 端口结果只来自声明为可见的端口变量。

14. 前端组件目录协议

前端只通过以下接口读取组件库:

GET /api/components/catalog

目录顶层必须具有版本:

{
  "schemaVersion": 1,
  "libraries": []
}

单个模型至少包含:

{
  "type": "cylinder",
  "modelType": "cylinder",
  "modelVersion": "1.0.0",
  "label": "气瓶",
  "symbol": "cylinder",
  "order": 10,
  "category": {
    "id": "storage",
    "label": "储能元件",
    "order": 10
  },
  "ports": [],
  "parameters": []
}

前端读取规则:

  1. 按库 order、分类 order、模型 order 排序。
  2. 使用 type 作为拖拽数据和工程文件中的稳定类型。
  3. 使用 label 显示中文名称。
  4. 使用 ports 生成 React Flow Handle。
  5. 使用 parameters 生成参数输入和单位选择控件。
  6. 使用 symbol 选择图标渲染器。
  7. 不在前端重新定义参数默认值、边界或端口语义。

当前前端保留内置兜底目录,用于后端未启动时继续打开工程。兜底只是一种开发期 容错机制,不能成为新增模型的正式注册方式;正式环境应明确提示目录加载失败。

14.1 前端实际读取步骤

React Flow 启动时:

  1. 使用 no-store 请求 /api/components/catalog。
  2. 检查 schemaVersion == 1。
  3. 检查库、模型、端口和参数结构。
  4. 检查模型 type 是否全局重复。
  5. 将参数数组转换为参数面板定义。
  6. 按库、分类和模型的 order 排序。
  7. 成功时显示“后端目录”。
  8. 请求或格式校验失败时显示“内置兜底”并使用开发期兜底目录。

前端兜底目录不保证包含新模型。新增公开模型后,只要 FastAPI 正常提供目录,前端 就能读取;若要求后端离线时也显示新模型,才需要有意识地同步兜底定义。兜底定义 仍不能成为端口、参数或默认值的权威来源。

14.2 修改后如何生效

修改 Python 模型、库清单或注册器后必须重启 FastAPI:

cd F:\Master\SystemSimulationApp
.\.venv-win\Scripts\python.exe -m uvicorn app.main:app --host 127.0.0.1 --port 8000

只刷新浏览器无法让已运行的 Python 进程重新导入模型。前端源代码由 Vite 开发服务 热更新;普通目录内容变化不需要重启 Vite。

15. System XML 映射

System XML 中:

<Component
  id="cylinder_1"
  name="cylinder_1"
  type="cylinder"
  componentType="cylinder">

映射规则:

  • id:工程内唯一的组件实例 ID。
  • name:用户可修改的组件实例名称。
  • type:必须匹配唯一的 MODEL_TYPE。
  • componentType:当前为兼容字段,应与 type 相同。
  • <Port name>:必须存在于模型的 PORTS。
  • <Parameter name>:必须存在于模型的 PARAMETERS。

XML 解析器只负责结构、引用和契约校验;模型注册中心负责选择 Python 类并创建实例; 模型自身负责方程和状态。三层职责不得混合。

16. 测试要求

每个新模型至少需要:

  1. 元数据测试:模型类型、端口、参数和结果声明合法。
  2. 目录测试:模型出现在正确库和分类中。
  3. 默认创建测试:默认参数能构造模型。
  4. 参数边界测试:非法值被拒绝,错误信息包含组件和参数。
  5. 端口测试:实例端口与声明完全一致。
  6. XML 测试:最小系统能解析并映射到正确模型。
  7. 方程测试:至少验证一个稳态、残差或守恒关系。
  8. 最小仿真测试:一个短时算例能产生有限结果和结构化结果元数据。

库级测试还应检查:

  • 库清单中的所有类均可导入。
  • 所有模型类型全局唯一。
  • 无遗漏或重复分类。
  • GET /api/components/catalog 满足目录 schema。

17. 新增模型操作清单

开发者新增模型时只执行以下步骤:

  1. 在目标库的正确分类目录中新建模型文件。
  2. 实现 MODEL_TYPE、MODEL_VERSION、PORTS、PARAMETERS、 RESULT_VARIABLES 和 DISPLAY。
  3. 实现统一 create() 和模型方程。
  4. 将模型类路径加入该库 library.py 的 models。
  5. 添加模型单元测试和最小 XML/仿真测试。
  6. 运行注册校验和完整测试。
  7. 重启 FastAPI,刷新前端确认目录来源为“后端目录”。

正常情况下不需要修改:

  • React Flow 左侧组件列表。
  • 参数面板字段。
  • System XML 模型类型分派代码。
  • 结果变量关键词映射。
  • 集中式模型工厂表。

17.1 AI 修改约束

AI 在处理组件库读取任务时必须:

  1. 先确认目标是新增模型、修改模型还是新增库。
  2. 读取当前启用列表和目标库清单。
  3. 只把公开模型加入 library.py/models。
  4. 不通过递归扫描替代显式清单。
  5. 不在前端重新声明后端契约作为正式实现。
  6. 不静默跳过加载失败的模型。
  7. 保留未知 symbol 的通用图标回退能力。
  8. 修改后检查目录响应,并运行注册表和前端构建测试。
  9. 告知用户需要重启 FastAPI。

AI 不应仅因为某个 .py 文件位于组件目录,就假定它是公开模型。公开性的唯一判断 依据是该类是否出现在已启用库的 models 清单中。

18. 当前实现与目标规范的差异

能力 当前状态 目标
库 ID、名称、版本、分类和模型清单 已实现 由各库 library.py 维护
库和模型注册表 已实现 由已启用库清单自动构建
前端目录接口 已实现 已包含库版本和模型版本
前端动态分类和参数读取 已实现 新增已支持图标的模型无需改组件列表
端口与参数契约 已实现 保持为唯一事实来源
结构化结果元数据 已实现 保持为唯一事实来源
DISPLAY 已实现 由公开模型类自行声明
模型工厂 已实现 模型类统一 create()
模型发现 已实现 按库清单受控发现
启动校验 已实现首版 覆盖版本、分类、端口、参数、单位和默认实例
XML/工程中的模型版本与迁移 未实现 正式库发布前补齐
目录 JSON Schema 已实现 schemas/component-catalog-v1.schema.json

19. 推荐实施顺序

  1. 已完成:声明类型已放入独立的 core/catalog.py。
  2. 已完成:experimental/library.py 已成为临时库唯一清单入口。
  3. 已完成:五个公开模型自行声明 DISPLAY 和 MODEL_VERSION。
  4. 已完成:公开模型统一实现 create(),集中式工厂函数已删除。
  5. 已完成:注册表由库清单构建,并在导入时执行契约和默认实例校验。
  6. 已完成:已增加组件目录 JSON Schema。
  7. 待完成:在 System XML 和工程文件中保存模型版本,并设计迁移机制。
  8. 待完成:规范稳定后新建正式组件库,不再向 experimental 增加生产模型。

该顺序可以保证每一步都保持现有前端和 System XML 可用,不需要一次性重写模型、 解析器和界面。

20. 改动影响表

想做的改动 必须修改 通常不需要修改
新增同库同分类模型 模型文件、library.py/models、测试 注册表、前端参数列表
新增分类 库 categories、模型 DISPLAY.category_id、测试 物理端口和求解器
新增组件库 新库包和 library.py、启用列表、测试 已有库清单
修改参数默认值或范围 模型 PARAMETERS、测试、必要的版本 前端参数硬编码
修改端口 模型 PORTS、DISPLAY.ports、XML/网络测试、版本迁移 库分类
新增专用图标 模型 DISPLAY.symbol、前端图标渲染器 参数和物理方程
新增物理域 端口协议、网络、求解器、XML、前端兼容规则和测试 仅修改分类名称
修改目录响应结构 后端序列化、JSON Schema、前端解析、协议版本和测试 单个模型方程

21. 读取故障排查

现象 优先检查
模型完全没有出现在目录响应 模型类路径是否加入已启用库的 models
FastAPI 无法启动 启动错误中的库、模型和字段;通常是契约校验失败
接口有模型但前端没有 schemaVersion、前端控制台、目录规范化错误
前端显示“内置兜底” 8000 端口、/api/components/catalog、后端是否重启
分类错误 DISPLAY.category_id 与库 categories
端口数量或位置错误 PORTS 与 DISPLAY.ports 是否完全一致
参数面板缺字段 模型 PARAMETERS 和目录响应,不先改前端
XML 报不支持类型 XML type 是否精确匹配 MODEL_TYPE
图标是通用图形 symbol 尚无专用前端渲染器,但模型仍应可用

最小诊断命令:

.\.venv-win\Scripts\python.exe -c "from app.simulation.registry import build_component_catalog; print(build_component_catalog())"
.\.venv-win\Scripts\python.exe -m unittest tests.test_component_registry tests.test_component_catalog