implementace co nejdrive dosazeni SOC na home-01 a umozneni plneho socu n slotu ped koncem sell < 0
This commit is contained in:
@@ -3,6 +3,8 @@
|
||||
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 = {
|
||||
@@ -47,9 +49,16 @@ export function parsePlanSolverSnapshot(
|
||||
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),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,11 +395,41 @@ function tableRowClass(
|
||||
if (buy != null && buy < 0) parts.push('bg-green-950/80')
|
||||
else if (sell != null && sell < 0) parts.push('bg-red-950/80')
|
||||
if ((i.pv_a_curtailed_w ?? 0) > 0) parts.push('border-l-4 border-l-yellow-500')
|
||||
else if (mask?.neg_sell_phase === 'prep' || mask?.neg_sell_phase === 'tail') {
|
||||
parts.push('border-l-4 border-l-violet-600/70')
|
||||
}
|
||||
else if (mask?.allow_charge) parts.push('border-l-4 border-l-emerald-600/70')
|
||||
else if (mask?.allow_discharge_export) parts.push('border-l-4 border-l-orange-500/70')
|
||||
return parts.join(' ')
|
||||
}
|
||||
|
||||
function pvAAllowedW(i: PlanningIntervalDto): number | null {
|
||||
const fc = i.pv_a_forecast_solver_w ?? i.pv_a_forecast_w
|
||||
if (fc == null) return null
|
||||
return Math.max(0, fc - (i.pv_a_curtailed_w ?? 0))
|
||||
}
|
||||
|
||||
function negSellPhaseBadge(mask: PlanMaskSlot | null): {
|
||||
label: string
|
||||
klass: string
|
||||
title: string
|
||||
} | null {
|
||||
const p = mask?.neg_sell_phase
|
||||
if (p == null || p === 'none') return null
|
||||
if (p === 'prep') {
|
||||
return {
|
||||
label: 'sell− prep',
|
||||
klass: 'bg-violet-500/20 text-violet-100 ring-1 ring-violet-500/40',
|
||||
title: 'Fáze přípravy SoC v okně záporného výkupu (cíl z DB, typ. 80 %)',
|
||||
}
|
||||
}
|
||||
return {
|
||||
label: 'sell− tail',
|
||||
klass: 'bg-fuchsia-500/20 text-fuchsia-100 ring-1 ring-fuchsia-500/40',
|
||||
title: 'Závěr okna sell<0: rampa na plné SoC, volitelný ventil pole B',
|
||||
}
|
||||
}
|
||||
|
||||
function exportModeBadge(i: PlanningIntervalDto): {
|
||||
label: string
|
||||
klass: string
|
||||
@@ -543,6 +573,14 @@ function PlanSlotDetail({
|
||||
{mask?.allow_discharge_export ? (
|
||||
<span className="rounded bg-orange-950/60 px-2 py-0.5 text-[10px] text-orange-200">↓ export bat. OK</span>
|
||||
) : null}
|
||||
{(() => {
|
||||
const pb = negSellPhaseBadge(mask)
|
||||
return pb ? (
|
||||
<span className={`inline-flex rounded-md px-2 py-0.5 text-[10px] font-semibold ${pb.klass}`} title={pb.title}>
|
||||
{pb.label}
|
||||
</span>
|
||||
) : null
|
||||
})()}
|
||||
</div>
|
||||
<dl className="mt-3 grid gap-2 font-mono text-xs sm:grid-cols-2 lg:grid-cols-3">
|
||||
<div>
|
||||
@@ -572,8 +610,21 @@ function PlanSlotDetail({
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-slate-500">Škrcení A</dt>
|
||||
<dd>{(i.pv_a_curtailed_w ?? 0) > 0 ? `${i.pv_a_curtailed_w} W` : '—'}</dd>
|
||||
<dt className="text-slate-500">Škrcení A / ≈ reg 340</dt>
|
||||
<dd>
|
||||
{(i.pv_a_curtailed_w ?? 0) > 0 ? (
|
||||
<span className="text-yellow-200">
|
||||
CURTAIL {(i.pv_a_curtailed_w ?? 0).toLocaleString('cs-CZ')} W
|
||||
</span>
|
||||
) : (
|
||||
'—'
|
||||
)}
|
||||
{pvAAllowedW(i) != null ? (
|
||||
<span className="ml-2 text-slate-400">
|
||||
· povoleno {pvAAllowedW(i)!.toLocaleString('cs-CZ')} W
|
||||
</span>
|
||||
) : null}
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-slate-500">Výnos slotu</dt>
|
||||
@@ -1008,7 +1059,7 @@ export default function Planning() {
|
||||
[visibleSlots, selectedStart],
|
||||
)
|
||||
|
||||
const tableColCount = 13 + (solverSnap != null ? 1 : 0) + (showGenCut ? 1 : 0)
|
||||
const tableColCount = 14 + (solverSnap != null ? 1 : 0) + (showGenCut ? 1 : 0)
|
||||
|
||||
async function onReplan() {
|
||||
if (siteId == null) return
|
||||
@@ -1603,6 +1654,12 @@ export default function Planning() {
|
||||
</th>
|
||||
) : null}
|
||||
<th className="whitespace-nowrap py-2 pr-2 font-medium">SoC %</th>
|
||||
<th
|
||||
className="whitespace-nowrap py-2 pr-2 font-medium"
|
||||
title="Povolený výkon pole A (forecast − curtail) ≈ Deye reg 340 max solar"
|
||||
>
|
||||
PV A
|
||||
</th>
|
||||
<th className="whitespace-nowrap py-2 pr-2 font-medium" title="Budoucí sloty: korigovaná předpověď z delta profilu; proběhlé sloty z auditu.">
|
||||
<span className="block">FVE W</span>
|
||||
<span className="block text-[10px] font-normal normal-case text-slate-600">delta · audit</span>
|
||||
@@ -1656,6 +1713,8 @@ export default function Planning() {
|
||||
const sel = selectedStart === i.interval_start
|
||||
const slotMask = maskForInterval(solverSnap, i.interval_start)
|
||||
const exBadge = exportModeBadge(i)
|
||||
const phaseBadge = negSellPhaseBadge(slotMask)
|
||||
const pvAllowed = pvAAllowedW(i)
|
||||
return (
|
||||
<tr
|
||||
key={i.interval_start}
|
||||
@@ -1729,6 +1788,24 @@ export default function Planning() {
|
||||
? `${i.battery_soc_target_pct.toFixed(1)}`
|
||||
: '—'}
|
||||
</td>
|
||||
<td className="pr-2 font-mono tabular-nums text-slate-300">
|
||||
<div className="flex flex-wrap items-center gap-1">
|
||||
{pvAllowed != null ? pvAllowed.toLocaleString('cs-CZ') : '—'}
|
||||
{(i.pv_a_curtailed_w ?? 0) > 0 ? (
|
||||
<span className="rounded bg-yellow-500/20 px-1 py-0.5 text-[9px] font-semibold text-yellow-200">
|
||||
CURTAIL
|
||||
</span>
|
||||
) : null}
|
||||
{phaseBadge ? (
|
||||
<span
|
||||
className={`rounded px-1 py-0.5 text-[9px] font-semibold ${phaseBadge.klass}`}
|
||||
title={phaseBadge.title}
|
||||
>
|
||||
{phaseBadge.label}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</td>
|
||||
<FveWCell i={i} nowMs={nowMs} />
|
||||
<td className="pr-2 font-mono tabular-nums text-slate-300">
|
||||
{formatPlanPowerW(i.load_baseline_w)}
|
||||
|
||||
Reference in New Issue
Block a user