旧版前端工程文件导入时版本对比查验、审阅与仿真时部分阻挡功能实现;前端参数输入格式统一规范
This commit is contained in:
1 parent
22579e51c9
commit
44b6ea74ab
32 files changed
+2087
-322
No files matched your search
+42
-16
@@ -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.")
|
||||
Reference in new issue
Block a user