0 ? ` | cap ${fmtKw(exportLimitW)}` : ''
+ return `SELL | ⬇ ${fmtKw(tpPowerW)}${cap} | reg142=0 reg178=32`
}
if (pm === 'CHARGE') {
return `CHARGE | ⬆ ${fmtKw(Math.max(0, battery_w))} | grid=yes | SOC→${targetSoc}%`
}
// PASSIVE (ZERO): doplň informaci o variantě 108/109 podle pravidel (bez wattových prahů).
- if (grid_w < 0 && battery_w >= 0) return 'PASSIVE | FVE→síť (108=0)'
+ if (grid_w < 0 && battery_w >= 0) {
+ const cap = exportLimitW > 0 ? ` | cap ${fmtKw(exportLimitW)}` : ''
+ return `PASSIVE | FVE→síť${cap} (108=0)`
+ }
if (grid_w > 0 && battery_w <= 0) return 'PASSIVE | držet bat. (109=0)'
return 'PASSIVE | max/max'
}
@@ -369,12 +376,15 @@ function deyeModeBadge(i: PlanningIntervalDto): { label: string; klass: string;
const m = (i.deye_physical_mode ?? 'PASSIVE').toString().trim().toUpperCase()
const battery_w = i.battery_setpoint_w ?? 0
const grid_w = i.grid_setpoint_w ?? 0
+ const exportLimitW = i.export_limit_w ?? 0
+ const exportMode = (i.export_mode ?? 'NONE').toString().trim().toUpperCase()
+ const cap = exportLimitW > 0 ? `; hard cap ${formatPlanPowerW(exportLimitW)}` : ''
if (m === 'SELL') {
return {
label: 'SELL',
klass: 'bg-orange-500/15 text-orange-200 ring-1 ring-orange-500/35',
- title: 'SELL (selling first): reg142=0, reg178=32 (grid peak shaving off)',
+ title: `SELL (selling first): reg142=0, reg178=32 (grid peak shaving off)${cap}`,
}
}
if (m === 'CHARGE') {
@@ -386,12 +396,14 @@ function deyeModeBadge(i: PlanningIntervalDto): { label: string; klass: string;
}
let variant = 'max/max'
- if (grid_w < 0 && battery_w >= 0) variant = 'FVE→síť (108=0)'
+ if (grid_w < 0 && battery_w >= 0) {
+ variant = exportMode === 'PV_SURPLUS' ? 'FVE→síť' : 'export'
+ }
else if (grid_w > 0 && battery_w <= 0) variant = 'držet bat. (109=0)'
return {
label: 'PASSIVE',
klass: 'bg-slate-600/40 text-slate-200 ring-1 ring-slate-500/30',
- title: `PASSIVE (ZERO): ${variant}; reg142=deye_zero_export_mode; reg178=48`,
+ title: `PASSIVE (ZERO): ${variant}${cap}; reg142=deye_zero_export_mode; reg178=48`,
}
}
@@ -539,6 +551,8 @@ function PlanTooltip({
const fveStr = formatPlanPowerW(p.pv_a_w)
const fveDisplay = fveStr === '—' ? '—' : fveStr.includes('kW') ? fveStr : `${fveStr} W`
const soc = p.battery_soc_target_pct
+ const exportLimit = i.export_limit_w
+ const exportMode = i.export_mode ?? 'NONE'
return (
{formatLocal(i.interval_start)}
@@ -556,6 +570,12 @@ function PlanTooltip({
{sell != null ? `${sell.toFixed(3)} Kč/kWh` : '—'}
FVE (korig. předpověď / audit): {fveDisplay}
+ {exportMode !== 'NONE' ? (
+
+ Export: {exportMode}
+ {exportLimit != null ? ` · limit ${formatPlanPowerW(exportLimit)}` : ''}
+
+ ) : null}
SoC cíl: {soc != null && !Number.isNaN(Number(soc)) ? `${Number(soc).toFixed(1)} %` : '—'}
Dům: {i.load_baseline_w ?? '—'} W
Baterie: {i.battery_setpoint_w ?? '—'} W
diff --git a/frontend/src/types/plan.ts b/frontend/src/types/plan.ts
index 157624e..cfb223a 100644
--- a/frontend/src/types/plan.ts
+++ b/frontend/src/types/plan.ts
@@ -15,6 +15,10 @@ export type PlanningIntervalDto = {
battery_setpoint_w: number | null
battery_soc_target_pct: number | null
grid_setpoint_w: number | null
+ /** Tvrdý limit exportu do sítě v daném slotu (W); 0 = bez exportu. */
+ export_limit_w?: number | null
+ /** Záměr exportu: NONE / PV_SURPLUS / BATTERY_SELL. */
+ export_mode?: 'NONE' | 'PV_SURPLUS' | 'BATTERY_SELL' | null
/** Explicitní fyzický režim Deye pro slot (PASSIVE/SELL/CHARGE). */
deye_physical_mode?: 'PASSIVE' | 'SELL' | 'CHARGE' | null
/** True = solver plánuje odpojit GEN port (MI export cutoff) v tomto slotu (BA81). */