import unitTable from "../../schemas/parameter-units.json" with { type: "json" }; export const DECIMAL_LITERAL = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/; export const sharedUnitTable = unitTable as unknown as Record>; type Parameter = { unit?: string; quantity?: string }; type Definition = { type: string; modelType: string; modelVersion: string; parameters: Record }; type Node = { id: string; data: { label: string; componentType: string; modelType: string; modelVersion?: string; parameters: Record; parameterUnits?: Record } }; type Project = { projectSchemaVersion: 1 | 2; nodes: Node[] }; const indices = new WeakMap>(); export function componentIndex(definitions: readonly D[]): Map { let index = indices.get(definitions); if (!index) { index = new Map(definitions.map(definition => [definition.type, definition])); indices.set(definitions, index); } return index as Map; } export function componentVersionNotices(nodes: readonly Node[], definitions: readonly Definition[]) { const index = componentIndex(definitions); return nodes.flatMap(node => { const definition = index.get(node.data.componentType); if (!definition || definition.modelType !== node.data.modelType || definition.modelVersion === node.data.modelVersion) return []; return [{ componentId: node.id, label: node.data.label || node.id, storedVersion: node.data.modelVersion, currentVersion: definition.modelVersion }]; }); } export function componentVersionWarning(nodes: readonly Node[], definitions: readonly Definition[], running = false) { const notices = componentVersionNotices(nodes, definitions); if (!notices.length) return null; return `${running ? "正在使用旧版或版本未知的工程组件,将按当前模型执行,可能出现仿真失败或仿真结果与实际不符" : "工程包含旧版或版本未知的组件"}:` + notices.map(n => `${n.label}(${n.storedVersion || "版本未知"} → ${n.currentVersion})`).join(";"); } export function parameterUnitConversion(definition: Parameter, unit: string) { const conversion = definition.unit && definition.quantity ? sharedUnitTable[definition.quantity]?.[unit] : undefined; if (conversion) return { scale: conversion[0], offset: conversion[1] }; if (unit === (definition.unit ?? "")) return { scale: 1, offset: 0 }; throw new Error(`不支持的参数单位“${unit}”(${definition.quantity ?? definition.unit})`); } /** Editor numbers stay SI. v2 wire numbers and expressions both use selected units. */ export function decodeProjectParameters

(project: P, definitions: readonly Definition[]): P { if (project.projectSchemaVersion === 1) return project; const index = componentIndex(definitions); return { ...project, projectSchemaVersion: 1, nodes: project.nodes.map(node => { const definition = index.get(node.data.componentType); if (!definition) throw new Error(`无法换算未知组件 ${node.data.label} 的单位`); const parameters = Object.fromEntries(Object.entries(node.data.parameters).map(([name, value]) => { const parameter = definition.parameters[name]; if (!parameter) throw new Error(`组件 ${node.data.label} 包含未知参数 ${name}`); const { scale, offset } = parameterUnitConversion(parameter, node.data.parameterUnits?.[name] ?? parameter.unit ?? ""); const isNumber = typeof value === "number" || DECIMAL_LITERAL.test(value.trim()); const si = isNumber ? Number(value) * scale + offset : value; if (typeof si === "number" && !Number.isFinite(si)) throw new Error(`${node.id}.${name} 必须为有限数值`); return [name, si]; })); return { ...node, data: { ...node.data, parameters } }; }) } as P; } /** Call only for editor SI data, never on an already-encoded external project. */ export function encodeProjectParameters

(project: P, definitions: readonly Definition[]): P { if (project.projectSchemaVersion !== 1) throw new Error("工程已按显示单位编码,不能重复换算"); const index = componentIndex(definitions); return { ...project, projectSchemaVersion: 2, nodes: project.nodes.map(node => { const definition = index.get(node.data.componentType); if (!definition) throw new Error(`无法导出未知组件 ${node.data.label} 的新版单位格式`); const parameterUnits = { ...node.data.parameterUnits }; const parameters = Object.fromEntries(Object.entries(node.data.parameters).map(([name, value]) => { const parameter = definition.parameters[name]; if (!parameter) throw new Error(`组件 ${node.data.label} 包含未知参数 ${name}`); const { scale, offset } = parameterUnitConversion(parameter, node.data.parameterUnits?.[name] ?? parameter.unit ?? ""); const isNumber = typeof value === "number" || DECIMAL_LITERAL.test(value.trim()); let display = isNumber ? (Number(value) - offset) / scale : value; // Some SI doubles have no exact representation after a unit round trip. // Preserve the numerical input by writing that parameter in its SI unit. // Never round the physical value or add a hidden second source of truth. if (typeof display === "number" && display * scale + offset !== Number(value)) { display = Number(value); parameterUnits[name] = parameter.unit ?? ""; } if (typeof display === "number" && !Number.isFinite(display)) throw new Error(`${node.id}.${name} 必须为有限数值`); return [name, display]; })); return { ...node, data: { ...node.data, parameters, parameterUnits } }; }) } as P; }