import { isExpression, Scope, type VmValue } from '@cloudpss/expression'; import type { ArgumentMap, ArgumentValue } from '@cloudpss/expression/definitions'; import { isResourceId } from '../../resource-id.js'; import type { ResourceRevisionVersion } from '../../revision.js'; import type { ResourceContextVersion } from '../../context.js'; import { fillArgumentMap, PowerFlowBranchModelType, PowerFlowDcModelType, PowerFlowNodeModelType, type DiagramComponent, type DiagramComponentConfiguration, type DiagramComponentEdge, type DiagramEdge, type Model, type ModelConfiguration, type ModelId, type PowerFlowModel, } from '../../model/index.js'; import { SECONDARY_ARG_ID, MERGE_DEMERGE_COMPONENTS, isMergeDemergeModel, MERGE_DEMERGE_PIN_PREFIX, } from '../../model/utils/merge-demerge.js'; import type { FuncConfiguration, Parameter } from '../../function/index.js'; import { V5Migrator } from '../v5.js'; const NO_PRESERVE_ASPECT_RATIO_MODELS = ['model/CloudPSS/_newBus_3p'] as unknown[] as readonly ModelId[]; const MERGE_DEMERGE_PIN_PREFIX_OLD = 'id-'; const RE_MERGE_DEMERGE_PIN_OLD = /^id-\d+$/; /** 更改 pin 名字 */ function renameMergeDemergePin(oldPin: string): string { if (isMergeDemergePin(oldPin)) { return `${MERGE_DEMERGE_PIN_PREFIX}${oldPin.slice(MERGE_DEMERGE_PIN_PREFIX_OLD.length)}`; } return oldPin; } /** 更改 pin 名字 */ function isMergeDemergePin( oldPin: string | null | undefined, ): oldPin is `${typeof MERGE_DEMERGE_PIN_PREFIX_OLD}${number}` { return oldPin != null && RE_MERGE_DEMERGE_PIN_OLD.test(oldPin); } const PF_Sbase = 100; const PF_F = 50; const PF_Vbase = 110; const PF_Ibase = PF_Sbase / PF_Vbase; const PF_Zbase = PF_Vbase / PF_Ibase; const PF_Ybase = 1 / PF_Zbase; const PF_DEFAULT_DATA = { valid: true }; const PF_NODE_DATA = { ...PF_DEFAULT_DATA, Sbase: PF_Sbase, F: PF_F, Vbase: PF_Vbase, Ibase: PF_Ibase, Zbase: PF_Zbase, Ybase: PF_Ybase, }; const PF_NODE_DATA_OUTPUT = { ...PF_DEFAULT_DATA, vm: 1.01, Vm: 1.01 * PF_Vbase, va: 30, Va: 30, pg: 1.2, Pg: 1.2 * PF_Sbase, qg: 0.5, Qg: 0.5 * PF_Sbase, pl: 0.8, Pl: 0.8 * PF_Sbase, ql: 0.3, Ql: 0.3 * PF_Sbase, ps: 0.1, Ps: 0.1 * PF_Sbase, qs: 0.05, Qs: 0.05 * PF_Sbase, pbr: 1.1, Pbr: 1.1 * PF_Sbase, qbr: 0.4, Qbr: 0.4 * PF_Sbase, }; const PF_BRANCH_DATA = { ...PF_DEFAULT_DATA, Sbase: PF_Sbase, F: PF_F, bus1: PF_NODE_DATA, bus2: PF_NODE_DATA, }; const PF_BRANCH_DATA_OUTPUT = { ...PF_DEFAULT_DATA, bus1: PF_NODE_DATA_OUTPUT, bus2: PF_NODE_DATA_OUTPUT, pl1: 0.06, Pl1: 0.06 * PF_Sbase, ql1: 0.02, Ql1: 0.02 * PF_Sbase, pl2: 0.05, Pl2: 0.05 * PF_Sbase, ql2: 0.015, Ql2: 0.015 * PF_Sbase, pl12: 0.01, Pl12: 0.01 * PF_Sbase, ql12: 0.005, Ql12: 0.005 * PF_Sbase, pl: 0.2, Pl: 0.2 * PF_Sbase, ql: 0.1, Ql: 0.1 * PF_Sbase, p12: 1.2, P12: 1.2 * PF_Sbase, q12: 0.5, Q12: 0.5 * PF_Sbase, p21: 1.1, P21: 1.1 * PF_Sbase, q21: 0.4, Q21: 0.4 * PF_Sbase, }; /** 获取潮流数据 */ function getPowerFlowData(model: PowerFlowModel, output: boolean): Record { if (PowerFlowDcModelType.includes(model.type as PowerFlowDcModelType)) { return PF_DEFAULT_DATA; } if (PowerFlowNodeModelType.includes(model.type as PowerFlowNodeModelType)) { return output ? PF_NODE_DATA_OUTPUT : PF_NODE_DATA; } if (PowerFlowBranchModelType.includes(model.type as PowerFlowBranchModelType)) { return output ? PF_BRANCH_DATA_OUTPUT : PF_BRANCH_DATA; } return PF_DEFAULT_DATA; } /** 升级算例 */ class Migrator extends V5Migrator { constructor(readonly model: Model) { super(); } readonly revision = this.model.revision; /** 默认配置 */ get defaultConfig(): ArgumentMap { return fillArgumentMap(this.revision.parameters); } readonly variables = this.revision.implements?.diagram?.variables ?? []; /** 迁移属性 */ migrateDiagramRootStyle(styleAttrs: Record, key: string): void { if (!(key in styleAttrs)) return; const sw = styleAttrs[key]; if (typeof sw == 'string' && sw.endsWith('px')) { const num = Number.parseFloat(sw.slice(0, -2)); if (!Number.isNaN(num)) { styleAttrs[key] = num; } } } /** 迁移属性 */ migrateDiagramRootStyles(styleAttrs: Record | undefined): void { if (!styleAttrs) return; this.migrateDiagramRootStyle(styleAttrs, '--stroke-width'); this.migrateDiagramRootStyle(styleAttrs, '--font-size'); } /** 迁移 cell */ migrateEdge(edge: DiagramEdge): void { if (edge.source && 'port' in edge.source && isMergeDemergePin(edge.source.port)) { edge.source.port = renameMergeDemergePin(edge.source.port); } if (edge.target && 'port' in edge.target && isMergeDemergePin(edge.target.port)) { edge.target.port = renameMergeDemergePin(edge.target.port); } } /** 迁移 cell */ migrateComponent(cell: DiagramComponentConfiguration): void { const context = this.context(true, cell); if (isMergeDemergeModel(cell.definition)) { cell.pins = Object.fromEntries( Object.entries(cell.pins ?? {}).map(([key, value]) => { return [renameMergeDemergePin(key), value]; }), ); } this.migrateConfig(cell, context); if (cell.props) { this.migrateExpression('enabled', cell.props, context, true); this.migrateExpression('outlineLevel', cell.props, context, false); } } /** @inheritdoc */ override migrateParameter(p: Parameter, context: Scope): void { super.migrateParameter(p, context); if (p.type === 'table' && MERGE_DEMERGE_COMPONENTS[this.model.rid]) { const conf = MERGE_DEMERGE_COMPONENTS[this.model.rid]!; if (p.key === conf.secondaryArgs) { p.trackTag = SECONDARY_ARG_ID; } } } /** 上下文 */ context(includeVariables: boolean, config?: ModelConfiguration | FuncConfiguration): Scope { const getGlobal = (key: string) => { const { defaultConfig } = this; if (key in defaultConfig) { return defaultConfig[key]; } if (includeVariables) { for (const v of this.variables) { if (v.key === key) return v.value; } } return undefined; }; return new Scope((key: string) => { if (key === '$$') return this.model as Model & VmValue; if (key === '$') return config as ((ModelConfiguration | FuncConfiguration) & VmValue) | undefined; if (key.startsWith('$')) { const prop = key.slice(1); const value = getGlobal(prop); if (value !== undefined) { return value; } } const args = config?.args; if (args && key in args) { return args[key]; } return getGlobal(key); }); } /** * 升级算例 */ migrate(): void { const defaultConfig = () => this.defaultConfig; const context = this.context(false, { get args() { return defaultConfig(); }, }); if (this.revision.version < 5) { this.migrateParameters(this.revision.parameters, context); this.migratePinDefinitions(this.revision.pins, context); } if (this.model.context.version < 5) { for (const config of this.model.configs) { const context = this.context(false, config); this.migrateConfig(config, context); } for (const job of this.model.jobs) { const context = this.context(false, job); this.migrateConfig(job, context); } if (this.model.context.graphic) { const { nodes } = this.model.context.graphic; for (const nodeId in nodes) { const node = nodes[nodeId]; if (!node) continue; this.migrateCondition(node, context); if (node.shape !== 'text') continue; const textAttrs = (node as { shape: 'text'; attrs?: { body?: { textContent?: string } } }).attrs; if (textAttrs?.body?.textContent) { textAttrs.body.textContent = this.migrateTemplate(textAttrs.body.textContent, context); } } } this.model.context.version = 5 as ResourceContextVersion; } if (this.revision.version < 5) { if (this.revision.graphic) { const { markup, preserveAspectRatio } = this.revision.graphic; if (preserveAspectRatio == null && NO_PRESERVE_ASPECT_RATIO_MODELS.includes(this.model.rid)) { this.revision.graphic.preserveAspectRatio = false; } if (Array.isArray(markup)) { for (const m of markup) { this.migrateCondition(m, context); if (m.textContent) { m.textContent = this.migrateTemplate(m.textContent, context); } } } } if (this.revision.implements?.diagram) { const context = this.context(true); const { cells } = this.revision.implements.diagram; this.migrateVariables(this.variables, context); for (const cellId in cells) { const cell = cells[cellId]; if (!cell) continue; if (cell.attrs) { this.migrateDiagramRootStyles( (cell.attrs as Record>>)['root']?.['style'], ); } if ('source' in cell || 'target' in cell) { this.migrateEdge(cell as DiagramEdge); } if ('definition' in cell && isResourceId(cell.definition)) { this.migrateComponent(cell as DiagramComponent | DiagramComponentEdge); if ((cell as DiagramComponent).style) { this.migrateDiagramRootStyles((cell as DiagramComponent).style); } } } } if (this.revision.implements?.powerFlow) { const pf = this.revision.implements.powerFlow; for (const model of pf.models) { const modelData = getPowerFlowData(model, false); const modelContext = new Scope((key: string) => { if (Object.hasOwn(modelData, key)) { return modelData[key]; } return context.get(key); }); this.migrateCondition(model, modelContext); if (model.type === 'transmission-line') { const hasGamma = model.corrected == null && isExpression(model.rb) && isExpression(model.xb) && isExpression(model.bb) && model.rb.source.includes('gamma') && model.xb.source.includes('gamma') && model.bb.source.includes('gamma'); this.migrateExpression('rb', model, modelContext, false); this.migrateExpression('xb', model, modelContext, false); this.migrateExpression('bb', model, modelContext, false); model.corrected ??= true; if (hasGamma) { const hasGammaAfter = isExpression(model.rb) && isExpression(model.xb) && isExpression(model.bb) && model.rb.source.startsWith('gamma') && model.xb.source.startsWith('gamma') && model.bb.source.startsWith('gamma'); // 长导线校正移到后台处理 if (!hasGammaAfter) { model.corrected = false; } } } else { for (const key in model) { if ( key === 'type' || key === 'key' || key === 'name' || key === 'description' || key === 'condition' || key === 'pins' ) { continue; } this.migrateExpression(key, model as never, modelContext, false); } } } const outputContext = new Scope((key: string) => { const model = pf.models.find((m) => m.key === key); if (!model) return context.get(key); return getPowerFlowData(model, true); }); for (const output of pf.outputs) { this.migrateCondition(output, context); this.migrateExpressionSource('value', output, outputContext, false); } } this.model.revision.version = 5 as ResourceRevisionVersion; } } } /** * 升级算例 */ export function migrate(model: Model): void { const m = new Migrator(model); m.migrate(); }