Files
ems/frontend/src/lib/planSolverSnapshot.ts
Dusan Vojacek 91a9bef3d7
Some checks failed
CI and deploy / migration-check (push) Failing after 13s
CI and deploy / deploy (push) Has been skipped
implementace co nejdrive dosazeni SOC na home-01 a umozneni plneho socu n slotu ped koncem sell < 0
2026-05-26 08:07:00 +02:00

93 lines
2.6 KiB
TypeScript

/** Parsování `planning_run.solver_params` z GET /plan/current (run je celý JSON řádek). */
export type PlanMaskSlot = {
allow_charge: boolean
allow_discharge_export: boolean
neg_sell_phase?: 'none' | 'prep' | 'tail' | null
neg_sell_soc_target_wh?: number | null
}
export type PlanSolverSnapshot = {
chargeAcquisitionKwh: number | null
chargeAcquisitionCutoffAt: string | null
masksByIso: Map<string, PlanMaskSlot>
chargeMaskCount: number
exportMaskCount: number
}
function recordBool(v: unknown): boolean {
return v === true
}
function recordNumber(v: unknown): number | null {
if (v == null) return null
const n = Number(v)
return Number.isFinite(n) ? n : null
}
function recordString(v: unknown): string | null {
return typeof v === 'string' && v.length > 0 ? v : null
}
export function parsePlanSolverSnapshot(
run: Record<string, unknown> | null | undefined,
): PlanSolverSnapshot | null {
if (run == null) return null
const raw = run.solver_params
if (raw == null || typeof raw !== 'object') return null
const sp = raw as Record<string, unknown>
const inputs =
sp.inputs != null && typeof sp.inputs === 'object'
? (sp.inputs as Record<string, unknown>)
: null
const masksByIso = new Map<string, PlanMaskSlot>()
const masks = sp.masks
if (Array.isArray(masks)) {
for (const m of masks) {
if (m == null || typeof m !== 'object') continue
const row = m as Record<string, unknown>
const slot = recordString(row.slot)
if (slot == null) continue
const phaseRaw = row.neg_sell_phase
const phase =
phaseRaw === 'prep' || phaseRaw === 'tail' || phaseRaw === 'none'
? phaseRaw
: null
masksByIso.set(slot, {
allow_charge: recordBool(row.allow_charge),
allow_discharge_export: recordBool(row.allow_discharge_export),
neg_sell_phase: phase,
neg_sell_soc_target_wh: recordNumber(row.neg_sell_soc_target_wh),
})
}
}
let chargeMaskCount = 0
let exportMaskCount = 0
for (const v of masksByIso.values()) {
if (v.allow_charge) chargeMaskCount += 1
if (v.allow_discharge_export) exportMaskCount += 1
}
return {
chargeAcquisitionKwh: inputs
? recordNumber(inputs.charge_acquisition_buy_czk_kwh)
: null,
chargeAcquisitionCutoffAt: inputs
? recordString(inputs.charge_acquisition_cutoff_at)
: null,
masksByIso,
chargeMaskCount,
exportMaskCount,
}
}
export function maskForInterval(
snapshot: PlanSolverSnapshot | null,
intervalStartIso: string,
): PlanMaskSlot | null {
if (snapshot == null) return null
return snapshot.masksByIso.get(intervalStartIso) ?? null
}