26 KiB
组件库分类、发现与读取规范 v1
状态:已在 experimental 临时组件库实施
适用范围:app/simulation/components、组件注册中心、System XML 和 React Flow 组件库
当前试验库:experimental(界面名称:临时测试组件库)
0. 文档定位
本文档只负责“模型如何被系统发现和读取”。模型方程、状态、参数和结果应如何编写, 统一参见组件模型建模规范 v1。
人工或 AI 排查组件读取问题时,按以下顺序读取:
app/simulation/registry.py中的ENABLED_COMPONENT_LIBRARIES。- 被启用组件库的
library.py。 library.py/models明确列出的模型类。- 模型类的
DISPLAY / PORTS / PARAMETERS。 build_component_catalog()的输出。- 前端
normalizeComponentCatalog()。
当前事实来源优先级:
| 信息 | 唯一事实来源 | 不能作为事实来源 |
|---|---|---|
| 启用哪些库 | ENABLED_COMPONENT_LIBRARIES |
目录中碰巧存在的文件夹 |
| 库分类和模型清单 | 各库 library.py |
前端组件列表 |
| 模型类型和版本 | 模型类 | 文件名或中文名称 |
| 物理端口 | 模型类 PORTS |
DISPLAY.ports 或画布方向 |
| 参数默认值和边界 | 模型类 PARAMETERS |
前端兜底定义 |
| 图标和端口位置 | 模型类 DISPLAY |
物理方程 |
| 前端运行目录 | /api/components/catalog |
手工扫描 Python 包 |
如果文档、代码和目录响应不一致,应在同一次修改中修复并增加测试,不能通过复制 另一份映射临时绕过。
1. 目标
本规范用于统一以下内容:
- 后端组件库如何声明自身信息和分类。
- 单个模型如何声明端口、参数、结果变量和界面显示信息。
- 后端如何发现、校验、注册并实例化模型。
- 前端如何通过统一目录接口自动生成左侧组件库和参数面板。
- 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.idcategory.idMODEL_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 |
必须 | 前端名称、分类、图标、排序和端口布局 |
模型实现还必须满足:
- 构造函数调用
super().__init__(name)。 - 使用
set_parameter_values()保存所有规范化后的参数。 - 使用
register_declared_port()创建PORTS中声明的端口。 - 组件级结果键必须与
RESULT_VARIABLES完全一致。 - 所有内部计算均使用 SI 基准值。
- 模型不能直接依赖 FastAPI、React Flow 或 XML DOM。
- 模型的方程不能依赖图标方向、界面分类或画布位置。
完整方程示例参见
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 是标称角色,
不应阻止反向流动;实际方向由求解结果中的流量符号决定。
连接校验至少包括:
- 两个端口均存在。
- 端口不能连接自身。
kind相同。domain相同。- 端口变量集合及连接规则兼容。
- 同一物理端口的连接数量符合当前网络编译器能力。
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的端口变量自动生成。 - 求解器缓存、残差和调试量默认不进入用户结果。
每个结果变量必须提供:
namelabelquantityunitcategoryorder
仿真结果必须输出结构化元数据,前端禁止拆解结果键或按字符串关键词猜测:
{
"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:
...
创建流程:
- 注册中心按
PARAMETERS填充默认值。 - 校验数值有限性和上下限。
- 拒绝未知参数。
- 调用模型类的
create()。 - 验证实例的模型类型、端口和参数快照。
- 将实例交给网络编译器。
这种方式允许 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": []
}
前端读取规则:
- 按库
order、分类order、模型order排序。 - 使用
type作为拖拽数据和工程文件中的稳定类型。 - 使用
label显示中文名称。 - 使用
ports生成 React Flow Handle。 - 使用
parameters生成参数输入和单位选择控件。 - 使用
symbol选择图标渲染器。 - 不在前端重新定义参数默认值、边界或端口语义。
目录协议提供以下可选的 AMESim 介质扩展字段:
- 参数的
editor: "amesimGasReference"表示该数值不是普通连续量,而是 项目介质定义的gi引用。前端应保留索引0,并从当前画布的介质定义 组件生成其余下拉项;索引下拉项只显示数值,不拼接介质名称或中文说明。 - 参数的
editor: "amesimGasPropertyModel"表示该参数选择介质定义内部的 物性计算模型。参数同时提供options: [{"value": 0, "label": "理想气体"}]一类目录数据,前端据此生成下拉栏;后续增加算法时由介质模型注册新的选项, 前端不硬编码算法名称。 - 模型的
role: "amesimGasMediumDefinition"表示该模型是项目级介质定义。 此类模型允许ports: [],在 System XML v2 中仍按普通零端口Component保存。
这些字段在目录对象中均为可选。宽松读取目录的消费者可以把未知编辑器参数
退化为普通数值输入;按本仓库 JSON Schema 严格校验的消费者必须与后端成套
升级,才能识别新增的 editor 值和 options 字段。正式前端必须依据目录字段
生成控件,不能硬编码具体 AMESim 模型名。
property_model 是每种介质组件内部的稳定选项编号,不等同于 AMESim 原始
eosType。例如空气组件的 property_model=0 表示理想气体,并映射到
fluidType=2/eosType=1;氦气组件的 property_model=0 表示
Peng-Robinson,并映射到 fluidType=12/eosType=6。编译层负责保存这种映射,
前端只使用目录选项。
当前前端保留内置兜底目录,用于后端未启动时继续打开工程。兜底只是一种开发期 容错机制,不能成为新增模型的正式注册方式;正式环境应明确提示目录加载失败。
14.1 前端实际读取步骤
React Flow 启动时:
- 使用
no-store请求/api/components/catalog。 - 检查
schemaVersion == 1。 - 检查库、模型、端口和参数结构。
- 检查模型
type是否全局重复。 - 将参数数组转换为参数面板定义。
- 按库、分类和模型的
order排序。 - 成功时显示“后端目录”。
- 请求或格式校验失败时显示“内置兜底”并使用开发期兜底目录。
前端兜底目录不保证包含新模型。新增公开模型后,只要 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。
介质定义组件不通过物理端口连接。编译器先收集目录角色为
amesimGasMediumDefinition 的零端口组件,再解析带
editor="amesimGasReference" 参数的组件引用;介质定义组件本身不进入数值
仿真网络。System XML 语义校验会在编译前检查介质索引的整数范围、定义唯一
性、正索引引用完整性,以及同一气动连通分量的引用一致性。
XML 解析器只负责结构、引用和契约校验;模型注册中心负责选择 Python 类并创建实例; 模型自身负责方程和状态。三层职责不得混合。
16. 测试要求
每个新模型至少需要:
- 元数据测试:模型类型、端口、参数和结果声明合法。
- 目录测试:模型出现在正确库和分类中。
- 默认创建测试:默认参数能构造模型。
- 参数边界测试:非法值被拒绝,错误信息包含组件和参数。
- 端口测试:实例端口与声明完全一致。
- XML 测试:最小系统能解析并映射到正确模型。
- 方程测试:至少验证一个稳态、残差或守恒关系。
- 最小仿真测试:一个短时算例能产生有限结果和结构化结果元数据。
库级测试还应检查:
- 库清单中的所有类均可导入。
- 所有模型类型全局唯一。
- 无遗漏或重复分类。
GET /api/components/catalog满足目录 schema。
17. 新增模型操作清单
开发者新增模型时只执行以下步骤:
- 在目标库的正确分类目录中新建模型文件。
- 实现
MODEL_TYPE、MODEL_VERSION、PORTS、PARAMETERS、RESULT_VARIABLES和DISPLAY。 - 实现统一
create()和模型方程。 - 将模型类路径加入该库
library.py的models。 - 添加模型单元测试和最小 XML/仿真测试。
- 运行注册校验和完整测试。
- 重启 FastAPI,刷新前端确认目录来源为“后端目录”。
正常情况下不需要修改:
- React Flow 左侧组件列表。
- 参数面板字段。
- System XML 模型类型分派代码。
- 结果变量关键词映射。
- 集中式模型工厂表。
17.1 AI 修改约束
AI 在处理组件库读取任务时必须:
- 先确认目标是新增模型、修改模型还是新增库。
- 读取当前启用列表和目标库清单。
- 只把公开模型加入
library.py/models。 - 不通过递归扫描替代显式清单。
- 不在前端重新声明后端契约作为正式实现。
- 不静默跳过加载失败的模型。
- 保留未知
symbol的通用图标回退能力。 - 修改后检查目录响应,并运行注册表和前端构建测试。
- 告知用户需要重启 FastAPI。
AI 不应仅因为某个 .py 文件位于组件目录,就假定它是公开模型。公开性的唯一判断
依据是该类是否出现在已启用库的 models 清单中。
18. 当前实现与目标规范的差异
| 能力 | 当前状态 | 目标 |
|---|---|---|
| 库 ID、名称、版本、分类和模型清单 | 已实现 | 由各库 library.py 维护 |
| 库和模型注册表 | 已实现 | 由已启用库清单自动构建 |
| 前端目录接口 | 已实现 | 已包含库版本和模型版本 |
| 前端动态分类和参数读取 | 已实现 | 新增已支持图标的模型无需改组件列表 |
| 端口与参数契约 | 已实现 | 保持为唯一事实来源 |
| 结构化结果元数据 | 已实现 | 保持为唯一事实来源 |
DISPLAY |
已实现 | 由公开模型类自行声明 |
| 模型工厂 | 已实现 | 模型类统一 create() |
| 模型发现 | 已实现 | 按库清单受控发现 |
| 启动校验 | 已实现首版 | 覆盖版本、分类、端口、参数、单位和默认实例 |
| XML/工程中的模型版本与迁移 | 未实现 | 正式库发布前补齐 |
| 目录 JSON Schema | 已实现 | schemas/component-catalog-v1.schema.json |
19. 推荐实施顺序
- 已完成:声明类型已放入独立的
core/catalog.py。 - 已完成:
experimental/library.py已成为临时库唯一清单入口。 - 已完成:五个公开模型自行声明
DISPLAY和MODEL_VERSION。 - 已完成:公开模型统一实现
create(),集中式工厂函数已删除。 - 已完成:注册表由库清单构建,并在导入时执行契约和默认实例校验。
- 已完成:已增加组件目录 JSON Schema。
- 待完成:在 System XML 和工程文件中保存模型版本,并设计迁移机制。
- 待完成:规范稳定后新建正式组件库,不再向
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