refactor export limit semantics
This commit is contained in:
@@ -84,6 +84,12 @@ const LiveRegistersSection = memo(
|
||||
valueText={live?.reg142_limit_control != null ? String(live.reg142_limit_control) : undefined}
|
||||
/>
|
||||
<Metric label="Energy mode" reg={141} valueText={live?.reg141_energy_mode != null ? String(live.reg141_energy_mode) : undefined} />
|
||||
<Metric
|
||||
label="Export cap"
|
||||
reg={143}
|
||||
sub="Hard limit lokality / invertoru; neforecastuje se"
|
||||
valueText={fmtW(live?.reg143_export_limit_w)}
|
||||
/>
|
||||
<Metric
|
||||
label="Peak shaving switch"
|
||||
reg={178}
|
||||
|
||||
@@ -221,6 +221,8 @@ function syntheticForecastOnlyInterval(
|
||||
battery_setpoint_w: null,
|
||||
battery_soc_target_pct: null,
|
||||
grid_setpoint_w: null,
|
||||
export_limit_w: null,
|
||||
export_mode: null,
|
||||
deye_physical_mode: null,
|
||||
ev1_setpoint_w: null,
|
||||
ev2_setpoint_w: null,
|
||||
@@ -341,6 +343,7 @@ function axiosDetail(e: unknown): string {
|
||||
function deyeSetpointLabel(i: PlanningIntervalDto): string {
|
||||
const battery_w = i.battery_setpoint_w ?? 0
|
||||
const grid_w = i.grid_setpoint_w ?? 0
|
||||
const exportLimitW = i.export_limit_w ?? 0
|
||||
const tgt = i.battery_soc_target_pct
|
||||
const targetSoc = tgt != null ? Math.min(95, Math.round(Number(tgt))) : 80
|
||||
|
||||
@@ -353,14 +356,18 @@ function deyeSetpointLabel(i: PlanningIntervalDto): string {
|
||||
const pm = (i.deye_physical_mode ?? '').toString().trim().toUpperCase()
|
||||
if (pm === 'SELL') {
|
||||
const tpPowerW = Math.abs(battery_w)
|
||||
return `SELL | ⬇ ${fmtKw(tpPowerW)} | reg142=0 reg178=32`
|
||||
const cap = exportLimitW > 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 (
|
||||
<div className="rounded-lg border border-slate-600 bg-slate-950 px-3 py-2 text-xs text-slate-200 shadow-xl">
|
||||
<div className="mb-1 font-medium text-slate-100">{formatLocal(i.interval_start)}</div>
|
||||
@@ -556,6 +570,12 @@ function PlanTooltip({
|
||||
{sell != null ? `${sell.toFixed(3)} Kč/kWh` : '—'}
|
||||
</div>
|
||||
<div>FVE (korig. předpověď / audit): {fveDisplay}</div>
|
||||
{exportMode !== 'NONE' ? (
|
||||
<div>
|
||||
Export: {exportMode}
|
||||
{exportLimit != null ? ` · limit ${formatPlanPowerW(exportLimit)}` : ''}
|
||||
</div>
|
||||
) : null}
|
||||
<div>SoC cíl: {soc != null && !Number.isNaN(Number(soc)) ? `${Number(soc).toFixed(1)} %` : '—'}</div>
|
||||
<div>Dům: {i.load_baseline_w ?? '—'} W</div>
|
||||
<div>Baterie: {i.battery_setpoint_w ?? '—'} W</div>
|
||||
|
||||
@@ -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). */
|
||||
|
||||
Reference in New Issue
Block a user