// Storm surge — inflow triples briefly, pumps should saturate at 100%, // level rises toward overflow then recedes. // // Expectation: during the surge (t=300..600), demand reaches 100% and // level may transiently climb above maxLevel. Overflow safety should // fire if the surge overwhelms pump capacity; dry-run should not fire. module.exports = { name: 'levelbased-storm', description: 'Sewer inflow triples from 0.008 → 0.024 m³/s for 5 minutes then returns to baseline. Overfill safety may engage.', durationSec: 1500, config: { general: { name: 'EvalStorm', id: 'eval-storm', unit: 'm3/h', logging: { enabled: false, logLevel: 'error' } }, functionality: { softwareType: 'pumpingStation', role: 'stationcontroller', positionVsParent: 'atEquipment' }, basin: { volume: 50, height: 5, inflowLevel: 3, outflowLevel: 0.2, overflowLevel: 4.5 }, hydraulics: { refHeight: 'NAP', basinBottomRef: 0, minHeightBasedOn: 'outlet' }, control: { mode: 'levelbased', allowedModes: new Set(['levelbased']), levelbased: { minLevel: 1, startLevel: 2, maxLevel: 4 }, }, safety: { enableDryRunProtection: true, dryRunThresholdPercent: 2, enableOverfillProtection: true, overfillThresholdPercent: 95, timeleftToFullOrEmptyThresholdSeconds: 0, }, }, setup: async (ps) => { const MAX_OUTFLOW = 0.012; // m³/s pumps cannot keep up with 3× surge ps.machineGroups['mgc1'] = { config: { general: { name: 'mgc1' } }, turnOffAllMachines: () => { ps.measurements.type('flow').variant('predicted').position('out').child('mgc1').value(0, Date.now(), 'm3/s'); }, handleInput: async (_src, demand) => { const d = Math.max(0, Math.min(100, Number(demand) || 0)); const outflow = (d / 100) * MAX_OUTFLOW; ps.measurements.type('flow').variant('predicted').position('out').child('mgc1').value(outflow, Date.now(), 'm3/s'); }, }; ps.calibratePredictedLevel(2.5); }, inputs: (t, ps) => { const surge = (t >= 300 && t < 600) ? 0.024 : 0.008; ps.setManualInflow(surge, Date.now(), 'm3/s'); }, expectations: [ { name: 'dry-run never trips', type: 'end_state_eq', field: 'safetyActive', value: false }, // Level may exceed maxLevel transiently but must stay under basinHeight { name: 'level never breaches physical basin', type: 'max_level_bounded', value: 5.0 }, { name: 'demand saturates at 100% during surge', type: 'max_demand_bounded', value: 100 }, ], };