From 3574dce1b16290e9ce8d5961ce97e2dce976cc41 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 26 May 2026 07:12:09 +0000 Subject: [PATCH] Sync card counters with backend schedule (regression fix) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the card displays the backend's slot_schedule (no slider overrides active), the colored bars come from the backend but the counter text ("X charge / Y sell / Z kWh planned") was still being driven by the client-side simulation result. The client sim can legitimately return 0 counts for cases the backend handles (e.g., 'both' mode sell selection with profitability filter + SOC validation that the simplified card sim doesn't replicate exactly). The ?? fallback chain in the render path was supposed to catch this: simR?.chargeCount ?? backend_attribute ?? 0 …but since the client sim explicitly returns chargeCount: 0 (a non-nullish number, not undefined), the backend fallback never triggered. Fix: when useBackendSchedule is true in _drawSlotTimeline, recompute chargeCount, dischargeCount, plannedChargeKwh, plannedDischargeKwh, and the today-view's tomorrow summary from the displayed slot data — which IS the backend's authoritative schedule. Symptom: chart shows orange sell bars at 18-23 but text reads "0 sell / 0.0 kWh planned" alongside. Fixed. --- .../ha_felicity/frontend/ha_felicity_ems.js | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/custom_components/ha_felicity/frontend/ha_felicity_ems.js b/custom_components/ha_felicity/frontend/ha_felicity_ems.js index a1cf96a..237edd7 100644 --- a/custom_components/ha_felicity/frontend/ha_felicity_ems.js +++ b/custom_components/ha_felicity/frontend/ha_felicity_ems.js @@ -587,6 +587,49 @@ class FelicityEMSCard extends LitElement { this._simResult = useBackendSchedule ? { ...simResult, slots: todaySlotData } : simResult; } + // When using backend schedule, recompute counts from the authoritative + // slot data so the textual counters (X charge / Y sell / Z kWh planned) + // match the colored bars. Without this, simResult's stale 0 values + // from the client-side simulation would win over backend reality. + if (useBackendSchedule) { + const displayedSlots = this._simResult.slots; + const sim = this._getAttr("schedule_status", "sim_params") || {}; + const granMin = this._getAttr("schedule_status", "slot_granularity_min") + || Math.round((24 * 60) / displayedSlots.length); + const slotDur = granMin / 60; + const safeMaxPower = this._getNumericState("safe_max_power") || 5000; + const powerKw = Math.max(1, safeMaxPower / 1000); + const eff = sim.efficiency || 0.90; + const effectivePerSlot = powerKw * slotDur * eff; + const energyPerSlot = powerKw * slotDur; + + const chargeCount = displayedSlots.filter(s => s.action === "charge").length; + const dischargeCount = displayedSlots.filter(s => s.action === "discharge").length; + const plannedChargeKwh = chargeCount * effectivePerSlot; + const plannedDischargeKwh = dischargeCount * energyPerSlot; + + // Also recompute tomorrow counts from backend's slot_schedule_tomorrow + // so the today-view's "tomorrow" summary stays consistent. + let tomorrowChargeCount = this._simResult.tomorrowChargeCount; + let tomorrowPlanned = this._simResult.tomorrowPlanned; + if (tomorrowSlotData?.some(s => s.action != null)) { + tomorrowChargeCount = tomorrowSlotData.filter(s => s.action === "charge").length; + const tmrDischargeCount = tomorrowSlotData.filter(s => s.action === "discharge").length; + tomorrowPlanned = tomorrowChargeCount * effectivePerSlot + tmrDischargeCount * energyPerSlot; + } + + this._simResult = { + ...this._simResult, + chargeCount, + dischargeCount, + plannedChargeKwh, + plannedDischargeKwh, + planned: Math.round((plannedChargeKwh + plannedDischargeKwh) * 100) / 100, + tomorrowChargeCount, + tomorrowPlanned, + }; + } + // Manual override or auto-switch to tomorrow when no actions remain const hasTomorrow = tomorrowSlotData?.length > 0; let showTomorrow;