From 104d41d29417f2ff3b4c307a9e2e8ba75dc39a97 Mon Sep 17 00:00:00 2001
From: ljz <425868052@qq.com>
Date: Wed, 15 Jul 2026 15:16:58 +0800
Subject: [PATCH] =?UTF-8?q?=E8=A7=84=E8=8C=83=E5=8C=96xml=E5=86=85?=
=?UTF-8?q?=E5=AE=B9=EF=BC=8C=E5=B9=B6=E5=9C=A8xml=E4=B8=AD=E6=B7=BB?=
=?UTF-8?q?=E5=8A=A0=E4=BB=BF=E7=9C=9F=E8=AE=BE=E7=BD=AE=EF=BC=8C=E8=A7=84?=
=?UTF-8?q?=E8=8C=83=E5=8C=96=E7=89=88=E6=9C=AC=E5=8F=B7=E7=AD=89=E5=86=85?=
=?UTF-8?q?=E5=AE=B9=EF=BC=8C=E8=A1=A5=E5=85=85xml=E8=A7=84=E8=8C=83md?=
=?UTF-8?q?=E6=96=87=E4=BB=B6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 7 +++
app/main.py | 22 ++++++++-
docs/system-xml-v1.md | 70 +++++++++++++++++++++++++++
frontend/src/App.tsx | 10 +++-
schemas/system-simulation-v1.xsd | 80 +++++++++++++++++++++++++++++++
tests/test_system_xml_protocol.py | 67 ++++++++++++++++++++++++++
6 files changed, 254 insertions(+), 2 deletions(-)
create mode 100644 docs/system-xml-v1.md
create mode 100644 schemas/system-simulation-v1.xsd
create mode 100644 tests/test_system_xml_protocol.py
diff --git a/README.md b/README.md
index bcf5f6e..bc1f169 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,9 @@
# SystemSimulationApp
+ReactFlow 系统建模与 PythonModels 仿真应用。
+
+## 文档
+
+- [System XML v1 协议](docs/system-xml-v1.md)
+- [System XML v1 XSD](schemas/system-simulation-v1.xsd)
+
diff --git a/app/main.py b/app/main.py
index 281e3b9..3cd340e 100644
--- a/app/main.py
+++ b/app/main.py
@@ -14,6 +14,8 @@ from pydantic import BaseModel, Field
app = FastAPI(title="System Simulation ReactFlow App")
FRONTEND_DIST_DIR = Path(__file__).resolve().parent.parent / "frontend" / "dist"
PROJECT_STORAGE_DIR = Path(__file__).parent / "data" / "reactflow-projects"
+SYSTEM_XML_SCHEMA_VERSION = "1"
+SYSTEM_XML_UNIT_SYSTEM = "SI"
class ReactFlowPosition(BaseModel):
@@ -190,7 +192,25 @@ def simulate_reactflow_testmodel(payload: ReactFlowProjectPayload) -> dict[str,
def build_reactflow_system_xml(project: ReactFlowProjectPayload) -> bytes:
- system = ET.Element("System", {"name": project.name})
+ system = ET.Element(
+ "System",
+ {
+ "name": project.name,
+ "schemaVersion": SYSTEM_XML_SCHEMA_VERSION,
+ "unitSystem": SYSTEM_XML_UNIT_SYSTEM,
+ },
+ )
+ ET.SubElement(
+ system,
+ "Simulation",
+ {
+ "tStart": str(project.simulation.t_start),
+ "tStop": str(project.simulation.t_stop),
+ "step": str(project.simulation.step),
+ "maxStep": str(project.simulation.max_step),
+ "method": project.simulation.method,
+ },
+ )
components_node = ET.SubElement(system, "Components")
connections_node = ET.SubElement(system, "Connections")
diff --git a/docs/system-xml-v1.md b/docs/system-xml-v1.md
new file mode 100644
index 0000000..c91aa24
--- /dev/null
+++ b/docs/system-xml-v1.md
@@ -0,0 +1,70 @@
+# System XML v1 协议
+
+System XML 是 ReactFlow 前端与 PythonModels 仿真层之间的稳定交换格式。
+
+## 基本约定
+
+- 根元素固定为 `System`,`schemaVersion` 固定为 `1`。
+- `unitSystem` 固定为 `SI`。组件参数必须保存为 SI 基准值,界面显示单位不进入数值换算语义。
+- 子元素顺序固定为 `Simulation`、`Components`、`Connections`。
+- `Component.id` 是稳定的仿真实例 ID;`name` 是用户可编辑的显示名称。
+- `Component.type` 是 PythonModels 模型类型;`componentType` 是前端组件类型。
+- `Connection.source/target` 只标识图形拓扑端点,不代表仿真中的实际流动方向。正流和倒流由求解器决定。
+- v1 文档不携带结果数据;仿真结果通过运行接口返回。
+
+## 完整示例
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+## Simulation
+
+| 属性 | 类型 | 含义 | 约束 |
+|---|---|---|---|
+| `tStart` | double | 仿真起始时间,单位 s | 有限数 |
+| `tStop` | double | 仿真结束时间,单位 s | 必须大于 `tStart` |
+| `step` | double | 结果采样间隔,单位 s | 必须大于 0 |
+| `maxStep` | double | 求解器最大积分步长,单位 s | 必须大于 0 |
+| `method` | string | 求解器名称,例如 `BDF` | 非空 |
+
+## Components
+
+每个 `Component` 必须包含唯一 `id`、显示名称、模型类型、前端类型和画布坐标。组件下可以包含多个 `Port` 和 `Parameter`。
+
+参数的 `value` 必须是可转换为有限浮点数的 SI 值。具体组件支持的端口、参数和取值范围由后续组件注册表协议约束。
+
+## Connections
+
+每个 `Connection` 必须包含唯一 `id` 和两个完整端点:
+
+- `source` + `sourcePort`
+- `target` + `targetPort`
+
+端点中的组件 ID 必须存在,端口名称必须属于对应组件。v1 前端仍使用 source/target 组织连线,但仿真层必须将它们视为连接的两个端点,不得直接解释为固定物理流向。
+
+## 版本兼容
+
+- 解析器遇到缺少 `schemaVersion` 的旧 XML 时,应明确报告“旧协议”,不能静默当作 v1。
+- v1 新增可选字段时必须保持旧解析器可忽略;删除字段或改变字段语义时必须升级主版本。
+- XSD 只验证文档结构和基础数据类型。跨字段约束、组件端口规则和拓扑可求解性由仿真校验层负责。
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 93d4158..afcbcb7 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -1814,10 +1814,18 @@ function buildSystemXml(project: ReactFlowProjectPayload) {
const xmlDocument = document.implementation.createDocument("", "System", null);
const system = xmlDocument.documentElement;
system.setAttribute("name", project.name);
+ system.setAttribute("schemaVersion", "1");
+ system.setAttribute("unitSystem", "SI");
+ const simulation = xmlDocument.createElement("Simulation");
+ simulation.setAttribute("tStart", String(project.simulation.t_start));
+ simulation.setAttribute("tStop", String(project.simulation.t_stop));
+ simulation.setAttribute("step", String(project.simulation.step));
+ simulation.setAttribute("maxStep", String(project.simulation.max_step));
+ simulation.setAttribute("method", project.simulation.method);
const components = xmlDocument.createElement("Components");
const connections = xmlDocument.createElement("Connections");
- system.append(components, connections);
+ system.append(simulation, components, connections);
project.nodes.forEach((node) => {
const component = xmlDocument.createElement("Component");
diff --git a/schemas/system-simulation-v1.xsd b/schemas/system-simulation-v1.xsd
new file mode 100644
index 0000000..399ab38
--- /dev/null
+++ b/schemas/system-simulation-v1.xsd
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/test_system_xml_protocol.py b/tests/test_system_xml_protocol.py
new file mode 100644
index 0000000..f875a41
--- /dev/null
+++ b/tests/test_system_xml_protocol.py
@@ -0,0 +1,67 @@
+from __future__ import annotations
+
+import unittest
+from xml.etree import ElementTree as ET
+
+from app.main import ReactFlowProjectPayload, build_reactflow_system_xml
+
+
+class SystemXmlProtocolTests(unittest.TestCase):
+ def test_v1_xml_contains_simulation_and_stable_section_order(self) -> None:
+ project = ReactFlowProjectPayload(
+ name="protocol-test",
+ nodes=[
+ {
+ "id": "cylinder_1",
+ "type": "simulationComponent",
+ "position": {"x": 12.5, "y": 24.0},
+ "data": {
+ "label": "source-cylinder",
+ "componentType": "cylinder",
+ "modelType": "cylinder",
+ "ports": ["port_b"],
+ "parameters": {"volume": 0.01, "p0": 35000000, "T0": 300},
+ },
+ }
+ ],
+ edges=[],
+ simulation={
+ "t_start": 1.0,
+ "t_stop": 5.0,
+ "step": 0.2,
+ "max_step": 0.01,
+ "method": "BDF",
+ },
+ )
+
+ root = ET.fromstring(build_reactflow_system_xml(project))
+
+ self.assertEqual(root.tag, "System")
+ self.assertEqual(root.attrib["name"], "protocol-test")
+ self.assertEqual(root.attrib["schemaVersion"], "1")
+ self.assertEqual(root.attrib["unitSystem"], "SI")
+ self.assertEqual([child.tag for child in root], ["Simulation", "Components", "Connections"])
+
+ simulation = root.find("Simulation")
+ self.assertIsNotNone(simulation)
+ assert simulation is not None
+ self.assertEqual(
+ simulation.attrib,
+ {
+ "tStart": "1.0",
+ "tStop": "5.0",
+ "step": "0.2",
+ "maxStep": "0.01",
+ "method": "BDF",
+ },
+ )
+
+ component = root.find("./Components/Component")
+ self.assertIsNotNone(component)
+ assert component is not None
+ self.assertEqual(component.attrib["id"], "cylinder_1")
+ self.assertEqual(component.find("./Parameter[@name='p0']").attrib["value"], "35000000")
+
+
+if __name__ == "__main__":
+ unittest.main()