旧版前端工程文件导入时版本对比查验、审阅与仿真时部分阻挡功能实现;前端参数输入格式统一规范

This commit is contained in:
lujingze committed 2026-09-12 14:15:40 +00:00
1 parent 22579e51c9
commit 44b6ea74ab
32 files changed
+2087 -322

No files matched your search

+42 -16
View File
@@ -20,7 +20,9 @@ from xml.etree import ElementTree as ET
from fastapi import FastAPI, HTTPException, Request, Response
from fastapi.responses import FileResponse, HTMLResponse, StreamingResponse
from pydantic import BaseModel, ConfigDict, Field, ValidationError
from pydantic import BaseModel, ConfigDict, Field, StrictFloat, StrictStr, ValidationError
from app.project_parameters import prepare_project, version_warning
from app.simulation.performance import performance_span, profile_phase, profile_run
from app.simulation.native_codegen.transport import NativeSeriesJson, serialize_result_parts
@@ -147,8 +149,8 @@ class ReactFlowNodeData(BaseModel):
componentType: str = "component"
modelType: str = "component"
# Optional at the storage boundary so an incompatible project can still be
# opened and inspected. Every execution path requires an exact registry
# match before defaults, equations, or ports are consumed.
# opened and inspected. Input adapters warn and select the current model;
# the normalized XML/numerical boundary still enforces an exact match.
modelVersion: str | None = None
ports: list[ReactFlowPortDefinition] = Field(default_factory=list)
parameters: dict[str, Any] = Field(default_factory=dict)
@@ -185,17 +187,17 @@ class ReactFlowEdgePayload(BaseModel):
class ReactFlowSimulationConfig(BaseModel):
t_start: float = 0.0
t_stop: float = 2.0
step: float = 0.1
max_step: float = 0.005
t_start: StrictFloat | StrictStr = 0.0
t_stop: StrictFloat | StrictStr = 2.0
step: StrictFloat | StrictStr = 0.1
max_step: StrictFloat | StrictStr = 0.005
method: str = "BDF"
class ReactFlowProjectPayload(BaseModel):
model_config = ConfigDict(extra="forbid")
projectSchemaVersion: Literal[1]
projectSchemaVersion: Literal[1, 2]
name: str = "untitled"
nodes: list[ReactFlowNodePayload] = Field(default_factory=list)
edges: list[ReactFlowEdgePayload] = Field(default_factory=list)
@@ -339,10 +341,19 @@ def get_component_catalog() -> dict[str, object]:
@app.post("/api/reactflow/system-xml")
def export_reactflow_system_xml(payload: ReactFlowProjectPayload) -> Response:
try:
xml = build_reactflow_system_xml(payload)
normalized, notices = prepare_project(payload)
xml = build_reactflow_system_xml(normalized)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
return Response(content=xml, media_type="application/xml")
headers = {}
if notices:
warning = {**version_warning(notices), "totalCount": len(notices), "components": notices[:10]}
encoded = quote(json.dumps(warning, ensure_ascii=False))
while len(encoded) > 3800 and warning["components"]:
warning["components"] = warning["components"][:-1]
encoded = quote(json.dumps(warning, ensure_ascii=False))
headers["X-Component-Version-Warnings"] = encoded
return Response(content=xml, media_type="application/xml", headers=headers)
@app.post("/api/simulation-results/csv")
@@ -510,10 +521,11 @@ def simulate_reactflow_test_mql(payload: ReactFlowProjectPayload) -> dict[str, o
@app.post("/api/reactflow/compile-model")
def compile_reactflow_model(payload: ReactFlowProjectPayload) -> dict[str, object]:
try:
network = compile_reactflow_network(payload)
normalized, notices = prepare_project(payload)
network = compile_reactflow_network(normalized)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
return {"success": True, **network.as_interface_dict()}
return {"success": True, **network.as_interface_dict(), "warnings": [version_warning(notices)] if notices else []}
@app.post("/api/system-xml/validate")
@@ -1025,7 +1037,7 @@ def validate_reactflow_component_contract(
node: ReactFlowNodePayload,
component_spec: "ComponentModelSpec",
) -> dict[str, float]:
"""Validate the persisted model contract before consuming current defaults."""
"""Validate the normalized SI model contract before consuming current defaults."""
if (
node.data.componentType != node.data.modelType
@@ -1094,6 +1106,7 @@ def validate_reactflow_execution_contract(
def build_reactflow_system_xml(project: ReactFlowProjectPayload) -> bytes:
from app.simulation.registry import get_component_model_spec
validate_si_project(project)
system_attributes = {
"schemaVersion": SYSTEM_XML_SCHEMA_VERSION,
"unitSystem": SYSTEM_XML_UNIT_SYSTEM,
@@ -1300,6 +1313,7 @@ def _solver_model_from_reactflow(
) -> SolverModelInput:
from app.simulation.registry import get_component_model_spec
validate_si_project(project)
components: list[SolverComponentInput] = []
for node in project.nodes:
spec = get_component_model_spec(node.data.modelType)
@@ -1517,6 +1531,18 @@ def parameter_float(
return default
value = node.data.parameters.get(name, default)
try:
return float(value)
except (TypeError, ValueError):
raise ValueError(f"Parameter '{name}' on component '{node.id}' must be numeric.")
if type(value) in (int, float) and isfinite(value):
return float(value)
except OverflowError:
pass
raise ValueError(f"Parameter '{name}' on component '{node.id}' must be a finite SI number; normalize expressions before execution.")
def validate_si_project(project: ReactFlowProjectPayload) -> None:
"""Guard the editor-independent numerical boundary, including time settings."""
if project.projectSchemaVersion != 1:
raise ValueError("Execution requires normalized SI data; preprocess project input first.")
for name in ("t_start", "t_stop", "step", "max_step"):
value = getattr(project.simulation, name)
if type(value) not in (int, float) or not isfinite(value):
raise ValueError(f"Simulation {name} must be a finite SI number.")