from __future__ import annotations from dataclasses import dataclass from math import isfinite from typing import Literal ResultVariableScope = Literal["component", "port"] SI_UNIT_BY_QUANTITY: dict[str, str] = { "area": "m2", "dimensionless": "", "density": "kg/m³", "flow_coefficient": "kg/(s*Pa^0.5)", "heat_transfer_coefficient": "W/(m2*K)", "internal_energy": "J", "length": "m", "mass": "kg", "mass_flow": "kg/s", "pressure": "Pa", "specific_enthalpy": "J/kg", "specific_internal_energy": "J/kg", "temperature": "K", "velocity": "m/s", "volume": "m3", } @dataclass(frozen=True) class ParameterDefinition: """User-configurable model input expressed in the backend SI contract.""" name: str default: float label: str = "" quantity: str = "dimensionless" unit: str = "" minimum: float | None = None maximum: float | None = None minimum_exclusive: bool = False def validation_message(self, value: float) -> str | None: if not isfinite(value): return "must be finite" if self.minimum is not None: if self.minimum_exclusive and value <= self.minimum: return f"must be greater than {self.minimum:g}" if not self.minimum_exclusive and value < self.minimum: return f"must be at least {self.minimum:g}" if self.maximum is not None and value > self.maximum: return f"must be at most {self.maximum:g}" return None def as_interface_dict(self, *, value: float | None = None) -> dict[str, object]: payload: dict[str, object] = { "name": self.name, "label": self.label or self.name, "quantity": self.quantity, "unit": self.unit, "default": self.default, "minimumExclusive": self.minimum_exclusive, } if self.minimum is not None: payload["minimum"] = self.minimum if self.maximum is not None: payload["maximum"] = self.maximum if value is not None: payload["value"] = value return payload @dataclass(frozen=True) class ResultVariableDefinition: """Component-relative declaration of a user-visible simulation result.""" name: str label: str quantity: str unit: str = "" category: str = "derived" order: int = 0 visible: bool = True @dataclass(frozen=True) class ResultVariableMetadata: """A result declaration bound to one concrete component instance.""" key: str component_id: str component_type: str scope: ResultVariableScope name: str label: str quantity: str unit: str category: str order: int port_name: str | None = None def as_dict(self) -> dict[str, object]: return { "key": self.key, "componentId": self.component_id, "componentType": self.component_type, "scope": self.scope, "portName": self.port_name, "name": self.name, "label": self.label, "quantity": self.quantity, "unit": self.unit, "category": self.category, "order": self.order, } THERMODYNAMIC_VOLUME_RESULT_VARIABLES = ( ResultVariableDefinition( name="m", label="质量", quantity="mass", unit="kg", category="state", order=10, ), ResultVariableDefinition( name="U", label="内能", quantity="internal_energy", unit="J", category="state", order=20, ), ResultVariableDefinition( name="p", label="压力", quantity="pressure", unit="Pa", category="thermodynamic", order=30, ), ResultVariableDefinition( name="T", label="温度", quantity="temperature", unit="K", category="thermodynamic", order=40, ), ResultVariableDefinition( name="rho", label="密度", quantity="density", unit="kg/m³", category="thermodynamic", order=50, ), ResultVariableDefinition( name="u", label="比内能", quantity="specific_internal_energy", unit="J/kg", category="thermodynamic", order=60, ), ResultVariableDefinition( name="h", label="比焓", quantity="specific_enthalpy", unit="J/kg", category="thermodynamic", order=70, ), )