/** * Pure utility to extract stream info — direct port of the Python * get_stream_info() from potential_analysis.py (lines 2289-2418). */ import type { Stream } from '../types/stream'; import type { StreamInfo } from '../types/analysis'; function toFloat(v: unknown): number | null { if (v === null || v === undefined || v === '') return null; const n = Number(v); return Number.isFinite(n) ? n : null; } export function getStreamInfo(stream: Stream): StreamInfo { const sv = stream.stream_values ?? {}; const properties = stream.properties ?? {}; const values = stream.values ?? {}; let tin: number | null = null; let tout: number | null = null; let mdot: number | null = null; let cpVal: number | null = null; let cpDirect: number | null = null; // 1. Try stream_values (new structure) if (sv) { tin = toFloat(sv['Tin']); tout = toFloat(sv['Tout']); mdot = toFloat(sv['ṁ']); cpVal = toFloat(sv['cp']); cpDirect = toFloat(sv['CP']); } // 2. Check properties/values dict structure if (properties && values) { for (const [pk, pname] of Object.entries(properties)) { const vk = pk.replace('prop', 'val'); const v = (values as Record)[vk] ?? ''; if (pname === 'Tin' && v && tin === null) tin = toFloat(v); else if (pname === 'Tout' && v && tout === null) tout = toFloat(v); else if (pname === 'ṁ' && v && mdot === null) mdot = toFloat(v); else if (pname === 'cp' && v && cpVal === null) cpVal = toFloat(v); else if (pname === 'CP' && v && cpDirect === null) cpDirect = toFloat(v); } } // 3. Fallback to legacy fields if (tin === null && stream.temp_in) tin = toFloat(stream.temp_in); if (tout === null && stream.temp_out) tout = toFloat(stream.temp_out); if (mdot === null && stream.mdot) mdot = toFloat(stream.mdot); if (cpVal === null && stream.cp) cpVal = toFloat(stream.cp); // Determine stream type let type: StreamInfo['type'] = null; if (tin !== null && tout !== null) { type = tin > tout ? 'Hot stream (Heat Source)' : 'Cold stream (Heat sink)'; } // CP: use direct if provided, otherwise mdot * cp let CP: number | null = null; if (cpDirect !== null) { CP = cpDirect; } else if (mdot !== null && cpVal !== null) { CP = mdot * cpVal; } // Q = |CP * (Tout - Tin)| let Q: number | null = null; if (CP !== null && tin !== null && tout !== null) { Q = Math.abs(CP * (tout - tin)); } return { tin, tout, mdot, cp: cpVal, CP, Q, type }; }