diff --git a/CHANGELOG.md b/CHANGELOG.md index eaf5f7d89..6f192fa33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 # [Unreleased] +- ✨ **SEM-dashboard Now shows where power comes from in percent, the next 24 hours of price, and a chip per device** (#1321). +- ✨ **SEM-dashboard System shows each source's value and the entity SEM reads** (#1321), to check a setup. +- 🔧 **The two dashboards are named SEM-dashboard and SEM-classic in the sidebar** (#1321). The names change at the next "generate dashboard". +- ✨ **SEM Next: the plan says why per device, each charger shows its charge plan, the battery its reserve** (#1321). Phones get no sideways scroll. +- 🐛 **SEM Next shows a charger by its own name in every language, not as "ev charger 1"** (#1321). +- 🔧 **SEM Next asks the peak limit, battery levels and charger targets with the same words and logic as the classic dashboard** (#1354). +- ✨ **SEM Next: a heater says what it does and what comes next** (#1321), like "heats to 52 °C at 23:00". - 🐛 **The plan chart shows one car as one row** (#1321). Tomorrow's preview named it differently from tonight's plan. - ✨ **Each planned device says why its hours were chosen**: cheapest hours, sun, battery, before leaving or one rate (#1321). - ✨ **On a dynamic tariff, the cost sensors show the average price paid per bought kWh and the average price of all hours** (#1321). @@ -62,6 +69,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 🔧 **Heaters with a thermometer and Keep at now join the shared plan on every install** (#1335). Cheap hours for the car and battery can shift. - 🐛 **A load SEM started on sun now stops when solar production ends, even when its power sensor reads 0 W** (#1335). - 🐛 **A planned comfort block now runs through without switching the load on and off** (#1334), also with Grid set to Off. +- ✨ **The new dashboard is the default, named Solar Energy Management** (#1321). The old one is now SEM Classic, kept for one release. +- 🐛 **Energy flows add up to your meters** (#1324). A missed reading no longer shortens the split or shows export the meter never counted. +- 🐛 **The night plan's cost now counts what the house buys after the battery runs out** (#1325). It showed empty before. +- 🐛 **A load SEM never saw running says "not measured yet"** instead of a made-up 1 kW (#1326). +- ✨ **SEM Next**: a new dashboard, five screens — one power bar, the plan on the price strip, devices you can reorder, settings as sentences (#1321). +- ✨ **The plan says why each block sits where it does, and what waiting saves against buying now** (#1321). +- ✨ **Loads get Boost: run now for 30, 60 or 120 minutes, then back to Smart** (#1321). A grid peak still stops it. - 📝 **The load card's Comfort hint now says how Keep at is reached**: in the planned hour from any source, sun may go further (#1334). - 📝 **The guides now explain Keep at, Extra from surplus and Run now past, and the order SEM follows** (#1334). - 🐛 **EG4 Web Monitor: solar power is found and grid import reads right from the start** (#810). The charge-start SOC is no longer the target. diff --git a/__init__.py b/__init__.py index 17e092d6b..8e48bfcab 100644 --- a/__init__.py +++ b/__init__.py @@ -3479,6 +3479,9 @@ def _deferred_entity_check(_now, _name=charger_name, # (#1084) the Why? card reads the decision log over this command from .decision_ws import async_register_websocket as _why_ws _why_ws(hass) + # (#1321) the Plan chart's hourly sun forecast + from .solar_forecast_ws import async_register_websocket as _sun_ws + _sun_ws(hass) # (#1174) the plain sentences the logbook names a decision with from .plain_reasons import async_load_plain_reasons await async_load_plain_reasons(hass) @@ -4512,9 +4515,12 @@ async def async_stop_battery_boost(call) -> None: async def async_plan_override_service(call) -> None: """(#1085 Part B) One-off: this device only, for this many minutes — then it's gone, even across a restart.""" - coordinator.set_plan_override( - call.data["device_id"], call.data["action"], - call.data["minutes"], call.data.get("value")) + try: + coordinator.set_plan_override( + call.data["device_id"], call.data["action"], + call.data["minutes"], call.data.get("value")) + except ValueError as err: + raise ServiceValidationError(str(err)) from err await coordinator.async_request_refresh() async def async_plan_override_clear_service(call) -> None: @@ -4666,8 +4672,23 @@ async def async_remove_leftovers_service(call) -> None: # Dashboard generation service async def async_generate_dashboard_service(call) -> None: """Generate and install the SEM dashboard with all assets.""" + from .features.dashboard_generator import ( + CLASSIC_DASHBOARD_TITLE, NEXT_DASHBOARD_ICON, NEXT_DASHBOARD_PATH, NEXT_DASHBOARD_TITLE, + ) + template = call.data.get("template", "next") dashboard_title = call.data.get("dashboard_title", "Solar Energy Management") dashboard_path = call.data.get("dashboard_path", "sem-dashboard") + # (#1321) SEM Next lives beside the classic dashboard: with the + # default title/path it lands at /sem-next, never over /sem-dashboard. + if template == "next": + if dashboard_path == "sem-dashboard": + dashboard_path = NEXT_DASHBOARD_PATH + if dashboard_title == "Solar Energy Management": + dashboard_title = NEXT_DASHBOARD_TITLE + elif dashboard_title == "Solar Energy Management": + # the main name belongs to the default dashboard now + dashboard_title = CLASSIC_DASHBOARD_TITLE + dashboard_icon = NEXT_DASHBOARD_ICON if template == "next" else "mdi:solar-power" try: from .dashboard_generator import DashboardGenerator @@ -4906,6 +4927,7 @@ def _cache_bust_for(base_url: str) -> str: dashboard_config = await generator.generate_dashboard( dashboard_title=dashboard_title, dashboard_path=dashboard_path, + template=template, ) if not dashboard_config: @@ -4963,7 +4985,7 @@ def _cache_bust_for(base_url: str) -> str: if item.get("id") == dashboard_path: item["mode"] = "storage" item["title"] = dashboard_title - item["icon"] = "mdi:solar-power" + item["icon"] = dashboard_icon item["show_in_sidebar"] = True item["require_admin"] = False dashboard_exists = True @@ -4974,7 +4996,7 @@ def _cache_bust_for(base_url: str) -> str: "id": dashboard_path, "mode": "storage", "title": dashboard_title, - "icon": "mdi:solar-power", + "icon": dashboard_icon, "show_in_sidebar": True, "require_admin": False, "url_path": dashboard_path, @@ -5047,6 +5069,7 @@ async def _delayed_restart(_now): schema=vol.Schema({ vol.Optional("dashboard_title", default="Solar Energy Management"): cv.string, vol.Optional("dashboard_path", default="sem-dashboard"): cv.string, + vol.Optional("template", default="next"): vol.In(["classic", "next"]), }), ) _LOGGER.debug("Registered service: %s.generate_dashboard", DOMAIN) @@ -5984,11 +6007,10 @@ async def _async_install_card_assets( dashboard_generated = True # Method 2: Dashboard storage file (set by generate_dashboard service) - dashboard_storage = os.path.join( - hass.config.config_dir, ".storage", "lovelace.sem-dashboard" - ) - if os.path.exists(dashboard_storage): - dashboard_generated = True + for _path in ("sem-dashboard", "sem-next"): + if os.path.exists(os.path.join( + hass.config.config_dir, ".storage", f"lovelace.{_path}")): + dashboard_generated = True if not dashboard_generated: _LOGGER.debug( diff --git a/coordinator/charging_control.py b/coordinator/charging_control.py index b7361d322..6a3bb66e3 100644 --- a/coordinator/charging_control.py +++ b/coordinator/charging_control.py @@ -15,7 +15,7 @@ import logging from typing import Dict, Any, Optional -from dataclasses import dataclass +from dataclasses import dataclass, field from homeassistant.core import HomeAssistant @@ -115,6 +115,10 @@ class ChargingContext: # Mode flags charging_strategy: str = "idle" charging_strategy_reason: str = "" + # (#1321) The primary charger's reason as a code + numbers, so a card + # can say it in the user's language. Recorded only; no decider reads it. + charging_strategy_reason_code: str = "" + charging_strategy_reason_params: Dict[str, Any] = field(default_factory=dict) # Canonical strategy (EVBudgetStrategy.value string) — the input the # canonical EV budget calc consumed in this cycle. Distinct from diff --git a/coordinator/coordinator.py b/coordinator/coordinator.py index 300e86e75..777114365 100644 --- a/coordinator/coordinator.py +++ b/coordinator/coordinator.py @@ -339,7 +339,9 @@ def plan_decision_core(plan) -> tuple: blocks, demands, whys, str(plan.get("takeover")), bool(plan.get("fits")), - round(float(plan.get("total_cost") or 0.0), 2), + # (#1325) the PLACED demands' cost is the decision; the house's + # own grid draw follows the trajectory (TRAJECTORY_KEYS). + round(float(plan.get("device_cost", plan.get("total_cost")) or 0.0), 2), str(plan.get("battery_fleet_partial")), arb_core, fsell_core, @@ -379,7 +381,13 @@ def plan_day_remaining_kwh(owner) -> float: # (#1184) Never a key a plan gate acts on: ``forecast_sell`` sat here, so a # kept stamp sold at the new build's times. ``slots`` stays — a gate reads # only its span, and every build of one period ends it at the night end. -TRAJECTORY_KEYS = ("slots", "self_consumption", "summary") +TRAJECTORY_KEYS = ("slots", "self_consumption", "summary", + # (#1325) the house's grid draw re-derives from the live + # battery walk like ``slots``; the total follows it. + "house_grid", "total_cost", + # (#1321) both follow total_cost; always present (None + # when unknown) so a kept stamp never keeps a stale one + "cost_if_now", "saved") def keep_decision_take_trajectory(prev: dict, fresh: dict, now_iso: str) -> dict: @@ -640,6 +648,25 @@ def _control_device_says_not_now(hass, entity_id: str) -> bool: +def _override_load_ids(coord) -> set: + """(#1321) The load ids a one-off override may name: the surplus walk's + own devices (the ids of the device map), never a charger it hands to + the per-charger loop (``managed_externally``) nor a battery id.""" + sc = getattr(coord, "_surplus_controller", None) + devs = getattr(sc, "_devices", None) or {} + batteries = set(getattr(coord, "_known_battery_ids", set()) or set()) + return {str(did) for did, d in devs.items() + if not getattr(d, "managed_externally", False) + and str(did) not in batteries and str(did) != "primary"} + + +def _override_load(coord, device_id: str): + """(#1321) The load named ``device_id``, or None.""" + if device_id not in _override_load_ids(coord): + return None + return coord._surplus_controller._devices.get(device_id) + + def _band_engaged(dev) -> bool: """(#1102) The load has a working thermometer band.""" return getattr(dev, "comfort_state", None) in ("forced", "willing", "banked") @@ -1071,6 +1098,9 @@ def __init__(self, hass: HomeAssistant, config: Dict[str, Any]) -> None: # (#1085 Part B) one-off per-device overrides; restored from # storage on the first cycle, see ``_async_update_data``. self._plan_overrides: Dict[str, Any] = {} + # (#1321) The battery ids of the last cycle: what plan_override may name. + self._known_battery_ids: set = set() + self._battery_ids_read = False # (#1087) a charger's departure-calendar's next event, cached — # departure_for stays sync/hass-free; this is the only async # reader, throttled, mirrors tariff/calendar_provider.py's hook. @@ -4562,6 +4592,13 @@ async def _async_update_data(self) -> Dict[str, Any]: energy_flows = self._flow_calculator.integrate_energy_flows( book_flows, self.update_interval.total_seconds(), ) + # (#1324) On a meter-backed day the split adds up to the meters. + # The integrator keeps its raw sums; only the published copy fits. + _day_totals = getattr(self._energy_calculator, "calendar_day_totals", None) + _day_totals = _day_totals() if callable(_day_totals) else None + if isinstance(_day_totals, dict): + from .flow_calculator import fit_flows_to_totals + energy_flows = fit_flows_to_totals(energy_flows, _day_totals) # Step 3: Calculate costs and performance. ``performance`` # gets ``energy_flows`` so autarky uses the @@ -5377,6 +5414,8 @@ async def _async_update_data(self) -> Dict[str, Any]: if cid == _fleet_primary_cid: charging_context.charging_strategy = decision.reason charging_context.charging_strategy_reason = decision.reason + charging_context.charging_strategy_reason_code = str(getattr(decision, "reason_code", "") or "") + charging_context.charging_strategy_reason_params = dict(getattr(decision, "reason_params", None) or {}) # Reflect decision into the effective_state sensor # (for dashboards that read sem_charging_state). # @@ -5856,6 +5895,8 @@ async def _async_update_data(self) -> Dict[str, Any]: charging_state=charging_state, charging_strategy=charging_context.charging_strategy, charging_strategy_reason=charging_context.charging_strategy_reason, + charging_strategy_reason_code=getattr(charging_context, "charging_strategy_reason_code", ""), + charging_strategy_reason_params=getattr(charging_context, "charging_strategy_reason_params", None) or {}, # Publish canonical budget (#282 unification, Phase B). # The state machine and the actuator now read from the # same EVBudget instance; publishing from it as well @@ -7853,6 +7894,9 @@ async def _update_analytics_phases( grid_closed=bool(getattr( (getattr(self, "_sink_verdicts", None) or {}).get("grid_export"), "state", "open") == "closed"), + # (#1321) the user's one-offs on loads ("run now"/"not now"), + # purged of expired ones at the top of this cycle. + load_overrides=self._load_override_payloads(), ) surplus_data.surplus_total_w = allocation.total_surplus_w surplus_data.surplus_distributable_w = allocation.distributable_surplus_w @@ -10683,6 +10727,8 @@ async def _run_battery_pipeline(self, power, energy, charging_state) -> None: # #531: live battery count gates the global-mode fall-through in # _per_battery_config (single→multi bleed fix). _bat_count = len(battery_items) + self._known_battery_ids = {bid for bid, _rt in battery_items} + self._battery_ids_read = True # (#1124) …and which cached adapters are batteries THIS cycle: a # synthetic "primary" from the first cycles stays in the cache once # b1/b2 exist, and its frozen write ledger is no battery's answer. @@ -11241,6 +11287,33 @@ def set_plan_override( device. Raises ``ValueError`` on a bad action (the schema already rejects one, this is belt-and-suspenders) or ``minutes`` outside 1 to 1440.""" from . import plan_override as _po + # (#1321) Only a charger or a battery acts on an override. Any other + # id was stored and did nothing, and the caller read it as done. + # Charger ids by the per-charger loop's own rule: the entry's id, else + # ``ev_charger_``; one box set up with flat keys is ``ev_charger``. + raw = self.config.get("ev_chargers") or [] + chargers = [c for c in raw if isinstance(c, dict)] + known = {str(c.get("id") or f"ev_charger_{i}") for i, c in enumerate(raw) if isinstance(c, dict)} + if not chargers: + known.add("ev_charger") + batteries = set(getattr(self, "_known_battery_ids", set()) or set()) + known |= batteries | {"primary"} + # (#1321) A load the surplus walk decides acts on skip and force too + # (``compute_load_intent`` clause 3c) — in Smart mode only. + load = None if device_id in known else _override_load(self, device_id) + if load is not None: + if action not in ("skip", "force"): + raise ValueError( + f"a load acts only on skip or force, not {action!r}") + from ..devices.base import DeviceControlMode as _DCM + if getattr(load, "control_mode", None) != _DCM.SURPLUS: + raise ValueError( + f"{device_id!r} acts on an override only in Smart mode") + # Before the first cycle has read the battery ids, take the id. + elif device_id not in known and getattr(self, "_battery_ids_read", False): + raise ValueError( + f"not a charger, a battery or a load: {device_id!r} " + f"(one of {sorted(known | _override_load_ids(self))})") now = dt_util.utcnow() self._plan_overrides = _po.set_override( self._plan_overrides, device_id, action, @@ -11249,6 +11322,29 @@ def set_plan_override( self._storage.set_plan_overrides(_po.serialize(self._plan_overrides)) _LOGGER.info("Plan override: %s %s for %d min", device_id, action, minutes) + def _load_override_payloads(self) -> Dict[str, Dict[str, Any]]: + """(#1321) ``{device_id: {"action", "until"}}`` for every load with + an active override — what the surplus walk acts on this cycle and + what the device map shows ("Boost until 14:30").""" + from . import plan_override as _po + out: Dict[str, Dict[str, Any]] = {} + try: + now = dt_util.now() + loads = _override_load_ids(self) + overrides = getattr(self, "_plan_overrides", None) + if not isinstance(overrides, dict): + return out + for device_id in overrides: + if device_id not in loads: + continue + payload = _po.status_payload(overrides, device_id, now) + if payload is not None: + out[device_id] = payload + except Exception: # noqa: BLE001 — an unreadable override is no override + _LOGGER.debug("Load overrides unreadable", exc_info=True) + return {} + return out + def clear_plan_override(self, device_id: str) -> None: """End ``device_id``'s override now, if one is set.""" from . import plan_override as _po @@ -12531,11 +12627,28 @@ def _at(hhmm): or 0) if _tgt <= 0.05: continue + # (#1321) the charger's own mode decides: off asks + # nothing, a full car takes nothing, and a sun-only + # mode never charges from the grid by day. + from ..consts.ev_charge_modes import effective_charge_mode_for + try: + _mode = effective_charge_mode_for(self.hass, self.config, _cfg) + except Exception: # noqa: BLE001 — unknown mode: the old grid ask + _mode = None + if _mode == "off": + continue + _cid = str(_cfg.get("id") or "") + try: + if _cid and self._plan_car_full(_cid, power) is True: + continue + except Exception: # noqa: BLE001 — unknown fullness: keep the ask + pass asks.append({ "id": f"ev:{_cfg.get('id')}" if _cfg.get("id") else None, "kind": "ev", "label": charger_own_name(_cfg), # (#1053) "kwh": round(_tgt, 2), + "sun_only": _mode in ("solar_only", "solar_plus_battery"), }) except Exception: # noqa: BLE001 continue @@ -12629,6 +12742,13 @@ def _at(hhmm): plan2 = pack_night( demands2, ledger2, floor_kwh=_floor2, max_discharge_w=_md_w, peak_limit_w=_peak_w) + from .energy_planner import keep_sun_only + _sun_starts = {s.start for s in slots2 + if getattr(s, "cap_override_w", None) is not None} + _sun_only = [a.get("id") or f"{a['kind']}:{i}" + for i, a in enumerate(asks) if a.get("sun_only")] + _allocs2, _no_sun = keep_sun_only( + plan2.allocations, _sun_starts, _sun_only) curve = provisional_soc_curve( ledger2, capacity_kwh=cap_kwh2, max_charge_w=float(self.config.get( @@ -12642,7 +12762,7 @@ def _at(hhmm): # back-to-back windows into runs (the #638 G3c lesson: # group by id first, neighbours in the list never merge). _by_id = {} - for a in plan2.allocations: + for a in _allocs2: _by_id.setdefault(a.demand_id, []).append(a) _blocks2 = [] for _did, _rows in _by_id.items(): @@ -12661,8 +12781,11 @@ def _at(hhmm): preview["provisional"] = { "soc_start": round(soc_seed, 2), "soc_curve": curve, - "fits": plan2.fits, + "fits": plan2.fits and not _no_sun, "blocks": _blocks2, + # (#1321) asks the sun cannot carry in their mode + "not_enough_sun": [{"id": d, "label": labels2.get(d)} + for d in _no_sun], # (#1321) the battery when tomorrow's preview ends "battery_end": battery_at_end(ledger2, _floor2, cap_kwh2), } @@ -13346,10 +13469,30 @@ def _slot_rows(rows): **({"batt": True} if s.home_batt_kwh > 1e-6 else {}), } for s in rows] + def _cost_keys(ledger_slots, device_cost, device_if_now=None, + has_grid_blocks=False): + # (#1325) The night's cost is the placed demands PLUS what the + # house buys once the battery's walk is done. Unknown stays + # None with its reason, never 0. + from .energy_planner import house_grid_cost + house = house_grid_cost(ledger_slots) + total = (None if house["cost"] is None or device_cost is None + else round(float(device_cost) + house["cost"], 4)) + # (#1321) what the plan saves against buying now — the + # comparison is stated in ``energy_planner.plan_savings``. + from .energy_planner import plan_savings + return { + "total_cost": total, + "device_cost": device_cost, + "house_grid": house, + **plan_savings(total, device_if_now, house["cost"], + has_grid_blocks), + } + # (#1358) the floor the ledger used, published with the plan _plan_floor = [None, None] - def _quiet_answer(arb=None, fsell=None, ledger_rows=(), self_cons=None, + def _quiet_answer(arb=None, fsell=None, ledger_rows=(), self_cons=None, ledger_slots=None, battery_end=None): # "Nothing needs the night" IS a valid 22:00 answer — say it, # WITH the why (a silent shadow is indistinguishable from a @@ -13418,6 +13561,7 @@ def _quiet_answer(arb=None, fsell=None, ledger_rows=(), self_cons=None, # (night 3, finding 3) a re-stamped night must be # distinguishable from the first answer. "replan_cause": replan_cause, + **_cost_keys(ledger_slots, 0.0), # (#1321) where the battery ends tonight, when walked "battery_end": battery_end, } @@ -13712,6 +13856,7 @@ def _quiet_answer(arb=None, fsell=None, ledger_rows=(), self_cons=None, self._energy_plan_shadow = _quiet_answer( arb, fsell, _slot_rows(ledger), predict_self_consumption(ledger, []).as_dict(), + ledger_slots=ledger, battery_end=battery_at_end(ledger, floor_kwh, cap_kwh)) return True plan = pack_night(demands, ledger, floor_kwh=floor_kwh, @@ -13789,7 +13934,10 @@ def _quiet_answer(arb=None, fsell=None, ledger_rows=(), self_cons=None, self._energy_plan_shadow = { "computed_at": now.isoformat(), "fits": plan.fits, - "total_cost": plan.total_cost, + **_cost_keys(ledger, plan.total_cost, plan.cost_if_now, + any(d.source != "battery" for d in demands + if any(a.demand_id == d.id + for a in plan.allocations))), "takeover": (plan.takeover.isoformat() if plan.takeover is not None else None), "demands": rows, @@ -13898,6 +14046,8 @@ def _clamp_state(text, limit=255): "charging_strategy": _clamp_state( charging_context.charging_strategy), "charging_strategy_reason": charging_context.charging_strategy_reason, + "charging_strategy_reason_code": getattr(charging_context, "charging_strategy_reason_code", ""), + "charging_strategy_reason_params": getattr(charging_context, "charging_strategy_reason_params", None) or {}, "canonical_strategy": charging_context.canonical_strategy, "discharge_limit": discharge_limit, # (#631) the authoritative per-charger night remaining — what the diff --git a/coordinator/energy_calculator.py b/coordinator/energy_calculator.py index d4a0f06d7..d37d447c2 100644 --- a/coordinator/energy_calculator.py +++ b/coordinator/energy_calculator.py @@ -4762,6 +4762,41 @@ def ev_last_reset(self) -> Optional[datetime]: upgrade would make the recorder count the day's kWh again.""" return getattr(self, "_ev_rolled_at", None) + def calendar_day_totals(self) -> Optional[Dict[str, Optional[float]]]: + """(#1324) Today's totals the flow split must add up to, or None. + + Only while the home row is the meters' balance this cycle: then every + flowing term is counter-backed and sources equal sinks. The EV column + is the midnight mirror — the flows run on calendar days, not EV days. + """ + today = self.booking_day + if today is None: + return None + acc = self._daily_accumulators + if "home" not in self._counter_backed: + # No solar counter (or another unmetered term): fit what the + # meters do back; None keeps the split's own sum, and the house + # becomes the balance of the rest (fit_flows_to_totals). + metered = ("grid_import", "grid_export", "battery_charge", "battery_discharge") + backed = [c for c in metered if c in self._counter_backed] + if not backed: + return None + out: Dict[str, Optional[float]] = { + "solar": None, "home": None, "ev": None, + } + for c in metered: + out[c] = acc.get(f"{c}_{today}", 0.0) if c in backed else None + return out + return { + "solar": acc.get(f"solar_{today}", 0.0), + "grid_import": acc.get(f"grid_import_{today}", 0.0), + "battery_discharge": acc.get(f"battery_discharge_{today}", 0.0), + "home": acc.get(f"home_{today}", 0.0), + "ev": acc.get(f"{MIDNIGHT_EV_CATEGORY}_{today}", 0.0), + "battery_charge": acc.get(f"battery_charge_{today}", 0.0), + "grid_export": acc.get(f"grid_export_{today}", 0.0), + } + def _get_daily(self, category: str, today: date) -> float: """Get daily accumulated energy.""" key = f"{category}_{today}" diff --git a/coordinator/energy_plan_actuation.py b/coordinator/energy_plan_actuation.py index 76248f83e..a67dc95eb 100644 --- a/coordinator/energy_plan_actuation.py +++ b/coordinator/energy_plan_actuation.py @@ -113,6 +113,10 @@ def plan_blocks(allocations) -> list: "price": a.price, # (#1023) only on the top-up: the strip hatches it **({"late": True} if a.late else {}), + # (#1321) why the packer put it here; the card says it in words + **({"reason": a.code} if getattr(a, "code", "") else {}), + **({"reason_params": dict(a.params)} + if getattr(a, "code", "") and a.params else {}), } for a in allocations] diff --git a/coordinator/energy_planner.py b/coordinator/energy_planner.py index 15a378011..bf0b2825e 100644 --- a/coordinator/energy_planner.py +++ b/coordinator/energy_planner.py @@ -28,6 +28,22 @@ FIT_EPS = 0.999 # "fits" = this share of the need; the plan wait accepts the same (#1289) _W_EPS = 1.0 # grants below 1 W are noise +#: (#1321) Why the packer put a block where it did — one code per branch of +#: ``pack_night`` that places energy. The card turns each into words +#: (``next_reason_``). +#: top_up_late — the late part, charged right before the deadline +#: one_block — the cheapest run of back-to-back hours (contiguous) +#: solar — a sun slot: the surplus runs it +#: cheap_hours — a cheap-level hour, the only kind this load may use +#: cheapest_hours — among the cheapest priced hours +#: before_deadline — the cheapest hours that still end by the deadline; +#: a cheaper hour lies after it +#: no_price_yet — an hour with no price published (Keep-at goals) +#: from_battery — drawn from the home battery, not the grid +REASON_CODES = ("top_up_late", "one_block", "solar", "cheap_hours", + "cheapest_hours", "before_deadline", "no_price_yet", + "from_battery") + # --------------------------------------------------------------------------- # Inputs @@ -279,6 +295,10 @@ class Allocation: reason: str #: (#1023) the top-up before departure — the card hatches it late: bool = False + #: (#1321) machine reason (one of ``REASON_CODES``) and its numbers + #: (``price``, ``deadline`` as ISO time) — the card's words for ``reason`` + code: str = "" + params: Any = field(default=None, compare=False) @property def energy_kwh(self) -> float: @@ -306,6 +326,9 @@ class EnergyPlan: total_cost: float fits: bool takeover: Optional[datetime] = None # first slot home hits the meter + #: (#1321) What the same grid energy would cost bought from the first + #: slot on, at full power — see ``_cost_if_now``. None = unknown. + cost_if_now: Optional[float] = None def summary_lines(self) -> list: """The 22:00 answer, one line per demand.""" @@ -362,6 +385,7 @@ def pack_night(demands, ledger, *, floor_kwh=0.0, max_discharge_w=5000.0, allocations = [] results = [] + late_unpriced = False # (#1321) a top-up booked an unpriced hour at 0 last_block_end = {} # demand id -> datetime of its last block def _price(d, i): @@ -463,6 +487,21 @@ def _grid_cell(d, i, want): power = min(grant, max(float(d.min_power_w), want / h * 1000.0)) return power, min(h, want / (power / 1000.0)) + def _why(d, i, code, price): + """(#1321) The reason code for a grid block of ``d`` in slot ``i``: + a sun slot says solar; a price pick says before_deadline when a + cheaper priced hour lies past the deadline, else ``code``.""" + if ledger[i].cap_override_w is not None: + return {"code": "solar", "params": {}} + params = {"price": round(float(price), 4)} + if d.deadline is not None: + params["deadline"] = d.deadline.isoformat() + if code == "cheapest_hours" and any( + _price(d, j) is not None and _price(d, j) < price + and ledger[j].end > d.deadline for j in idx): + code = "before_deadline" + return {"code": code, "params": params} + def _cheapest_run(d, need): """(#1023) The cheapest run of back-to-back priced, eligible slots that covers ``need`` within headroom: ``[(i, power_w, run_h)]``, or @@ -530,6 +569,8 @@ def _cheapest_run(d, need): run_h = min(span_h, max(run_h, d.min_run_s / 3600.0)) begin = hi - timedelta(hours=run_h) _p = _price(d, i) + if _p is None: + late_unpriced = True price = 0.0 if _p is None else float(_p) s.grid_committed_w += power s.headroom_w -= power @@ -538,7 +579,8 @@ def _cheapest_run(d, need): price=price, reason=(f"{d.id}: {power:.0f} W {begin:%H:%M}–{hi:%H:%M} " f"@ {price:.3f} (late charge before departure)"), - late=True)) + late=True, code="top_up_late", + params={"deadline": d.deadline.isoformat()})) last_block_end[d.id] = max(last_block_end.get(d.id, hi), hi) planned += power / 1000.0 * run_h cost += power / 1000.0 * run_h * price @@ -563,7 +605,8 @@ def _cheapest_run(d, need): price=price, reason=(f"{d.id}: {power:.0f} W {begin:%H:%M}–" f"{end:%H:%M} @ {price:.3f} (one block, " - f"headroom left {s.headroom_w:.0f} W)"))) + f"headroom left {s.headroom_w:.0f} W)"), + **_why(d, i, "one_block", price))) last_block_end[d.id] = max( last_block_end.get(d.id, end), end) planned += power / 1000.0 * run_h @@ -643,9 +686,17 @@ def _cheapest_run(d, need): + (f"@ {price:.3f} (slot #{own_rank[i]} cheapest, " if price is not None else "unpriced (no price published, ") + f"headroom left {s.headroom_w:.0f} W)") + if d.source == "battery": + code = {"code": "from_battery", "params": {}} + elif price is None: + code = {"code": "no_price_yet", "params": {}} + elif d.needs_cheap_level and _cheap(d, i): + code = _why(d, i, "cheap_hours", price) + else: + code = _why(d, i, "cheapest_hours", price) allocations.append(Allocation( demand_id=d.id, start=begin, end=end, power_w=power, - price=price, reason=why)) + price=price, reason=why, **code)) last_block_end[d.id] = end planned += e_kwh cost += e_kwh * (price or 0.0) @@ -688,22 +739,146 @@ def _cheapest_run(d, need): est_cost=round(cost, 4) if cost_known else None, note=note, note_codes=tuple(codes))) - takeover = next((s.start for s in ledger if s.home_grid_w > _W_EPS), None) + takeover = takeover_of(ledger) allocations.sort(key=lambda a: (a.start, a.demand_id)) + total = (None if any(a.price is None for a in allocations) + else round(sum(a.energy_kwh * a.price for a in allocations), 4)) + if_now = (None if total is None or late_unpriced + else _cost_if_now(demands, allocations, ledger, _price)) return EnergyPlan( allocations=tuple(allocations), results=tuple(results), # (#1102) one unpriced block makes the night's cost unknown, never 0 - total_cost=(None if any(a.price is None for a in allocations) - else round(sum(a.energy_kwh * a.price for a in allocations), 4)), + total_cost=total, fits=all(r.status == "fits" for r in results), takeover=takeover, + cost_if_now=if_now, ) +def plan_savings(total_cost, device_if_now, house_cost, has_grid_blocks) -> dict: + """(#1321) ``{"cost_if_now", "saved"}`` for the published plan. + + ``cost_if_now`` = the plan's grid energy bought from the first slot on at + full power (``_cost_if_now``) plus the house's own grid draw, which is + the same either way. ``saved`` = cost_if_now − total_cost: what waiting + for the planned hours saves on the devices. Rounded to 2 decimals; a + difference under half a cent is 0. Both None (the sensor leaves them + out) when a price is unknown or the plan buys nothing from the grid. + """ + if (not has_grid_blocks or total_cost is None or device_if_now is None + or house_cost is None): + return {"cost_if_now": None, "saved": None} + if_now = round(float(device_if_now) + float(house_cost), 2) + saved = round(if_now - float(total_cost), 2) + if abs(saved) < 0.005: + saved = 0.0 + return {"cost_if_now": if_now, "saved": saved} + + +def _cost_if_now(demands, allocations, ledger, price_of): + """(#1321) The cost of the plan's grid energy bought NOW instead. + + What it compares: for every grid demand, the kWh the plan placed for it, + run from the first ledger slot onward at the device's full power — what + the device does with no plan, plugged in and started at once — priced at + each slot's import price (the demand's own tariff when it has one). On a + flat tariff this equals the plan's cost. Headroom and anti-cycle limits + are ignored on this side: it is the "no plan" baseline, not a second + plan. Battery-source blocks cost 0 on both sides and are left out. + + None when a slot it would use has no price, or the ledger ends before + the energy is bought. + """ + by_id = {d.id: d for d in demands} + placed = {} + for a in allocations: + d = by_id.get(a.demand_id) + if d is None or d.source == "battery": + continue + placed[a.demand_id] = placed.get(a.demand_id, 0.0) + a.energy_kwh + cost = 0.0 + for did, kwh in placed.items(): + d = by_id[did] + power_kw = float(d.max_power_w) / 1000.0 + if kwh <= 1e-9: + continue + if not power_kw > 0 or power_kw == float("inf"): + return None + left = kwh + for i, s in enumerate(ledger): + if left <= 1e-9: + break + take = min(left, power_kw * s.hours) + if take <= 0: + continue + p = price_of(d, i) + if p is None: + return None + cost += take * float(p) + left -= take + if left > 1e-6: + return None + return round(cost, 4) + + +def takeover_of(ledger): + """The first slot whose house draw lands on the meter (the battery is done).""" + return next((s.start for s in (ledger or []) if s.home_grid_w > _W_EPS), None) + + +def house_grid_cost(ledger) -> dict: + """(#1325) What the house itself buys from the grid over the ledger. + + The packer prices only the demands it places. When the battery's walk + ends before the night does, the house's own meter draw (``home_grid_w``) + is a cost too, and leaving it out made the night look free. Reporting + only: no decider reads this. + + Returns ``{"kwh", "cost", "reason"}``. ``cost`` is None when a slot the + house draws in has no price (``reason`` "unpriced") or there is no + ledger at all (``reason`` "no_ledger"); 0 kWh is a real answer. + """ + rows = list(ledger or []) + if not rows: + return {"kwh": None, "cost": None, "reason": "no_ledger"} + kwh = 0.0 + cost = 0.0 + unpriced = False + for s in rows: + e = max(0.0, float(s.home_grid_w or 0.0)) * s.hours / 1000.0 + if e <= 1e-6: + continue + kwh += e + if s.price is None: + unpriced = True + else: + cost += e * float(s.price) + return { + "kwh": round(kwh, 2), + "cost": None if unpriced else round(cost, 4), + "reason": "unpriced" if unpriced else None, + } + + # (#758) ``PriceSlot`` + ``plan_overnight`` lived here as a "compat" flat-slot # adapter with no production caller — the coordinator builds a real ledger and # calls ``build_night_ledger`` + ``pack_night``. Two corpora used the adapter, # so those tests were proving things about a night that cannot happen (every # slot cheap, no house load, an infinite battery, no peak limit). The adapter # is now ``tests/synthetic_night.py``, where its assumptions are stated. + + +def keep_sun_only(allocations, sun_starts, sun_only_ids): + """(#1321) A charger whose mode never takes grid power by day (solar + only, solar + battery) keeps only its blocks in sun slots. Returns the + kept allocations and the ids left with nothing, so the preview can say + "not enough sun" instead of drawing a grid charge the mode never makes. + """ + kept, placed = [], set() + for a in allocations: + if a.demand_id in sun_only_ids and a.start not in sun_starts: + continue + kept.append(a) + placed.add(a.demand_id) + return kept, [d for d in sun_only_ids if d not in placed] diff --git a/coordinator/flow_calculator.py b/coordinator/flow_calculator.py index e91b9d6b6..01dc1207e 100644 --- a/coordinator/flow_calculator.py +++ b/coordinator/flow_calculator.py @@ -29,6 +29,86 @@ _LOGGER = logging.getLogger(__name__) +# (#1324) The day's flow split, fitted to the meter-backed daily totals. +# Rows are where energy came from, columns where it went. Pairs that cannot +# flow (grid to grid, battery to battery) are not cells at all. +FLOW_ROWS = ("solar", "grid_import", "battery_discharge") +FLOW_COLS = ("home", "ev", "battery_charge", "grid_export") +_FLOW_CELLS = { + ("solar", "home"): "solar_to_home", ("solar", "ev"): "solar_to_ev", + ("solar", "battery_charge"): "solar_to_battery", ("solar", "grid_export"): "solar_to_grid", + ("grid_import", "home"): "grid_to_home", ("grid_import", "ev"): "grid_to_ev", + ("grid_import", "battery_charge"): "grid_to_battery", + ("battery_discharge", "home"): "battery_to_home", ("battery_discharge", "ev"): "battery_to_ev", + ("battery_discharge", "grid_export"): "battery_to_grid", +} +_FIT_ITERATIONS = 50 +_FIT_TOLERANCE_KWH = 0.001 + + +def fit_flows_to_totals(flows: EnergyFlows, totals: Dict[str, float]) -> EnergyFlows: + """The day's flows scaled so each source and each sink adds up to its total. + + The integrator adds power up every cycle, so a reading that drops out + loses energy and two readings taken moments apart can book an export + the meter never saw (#1324). When the daily totals are backed by the + meters, they are the truth and the split is only how they divide: + alternate row and column scaling (iterative proportional fitting) + keeps each flow's share and makes the sums match. A cell that is 0 + stays 0; a total with no flow under it cannot be matched and is left. + Returns a new EnergyFlows; per-charger EV flows follow their fleet + flow's factor, per-string solar is left as measured. + """ + cells = {k: max(0.0, float(getattr(flows, a, 0.0) or 0.0)) for k, a in _FLOW_CELLS.items()} + + def _own(name: str) -> float: + axis = 0 if name in FLOW_ROWS else 1 + return sum(v for k, v in cells.items() if k[axis] == name) + + # A total given as None is not metered: it keeps the split's own sum. + # A house left as None is the balance, so sources and sinks still agree. + target = { + k: _own(k) if totals.get(k) is None else max(0.0, float(totals.get(k) or 0.0)) + for k in FLOW_ROWS + FLOW_COLS + } + if totals.get("home") is None: + target["home"] = max(0.0, sum(target[r] for r in FLOW_ROWS) + - sum(target[c] for c in FLOW_COLS if c != "home")) + for _ in range(_FIT_ITERATIONS): + for axis, names in ((0, FLOW_ROWS), (1, FLOW_COLS)): + for name in names: + keys = [k for k in cells if k[axis] == name] + have = sum(cells[k] for k in keys) + if have > 0: + f = target[name] / have + for k in keys: + cells[k] *= f + worst = 0.0 + for axis, names in ((0, FLOW_ROWS), (1, FLOW_COLS)): + for name in names: + keys = [k for k in cells if k[axis] == name] + have = sum(cells[k] for k in keys) + if have > 0: + worst = max(worst, abs(have - target[name])) + if worst <= _FIT_TOLERANCE_KWH: + break + + out = EnergyFlows() + for k, attr in _FLOW_CELLS.items(): + setattr(out, attr, round(cells[k], 3)) + factor = {} + for attr in ("solar_to_ev", "grid_to_ev", "battery_to_ev"): + raw = float(getattr(flows, attr, 0.0) or 0.0) + factor[attr] = getattr(out, attr) / raw if raw > 0 else 1.0 + for cid, c in (flows.per_charger or {}).items(): + out.per_charger[cid] = ChargerEnergyFlows(**{ + attr: round(float(getattr(c, attr, 0.0) or 0.0) * f, 3) + for attr, f in factor.items() + }) + out.per_string = dict(flows.per_string or {}) + return out + + # ─────────────────────────────────────────────────────────────────────── # Canonical EV-budget calculation — the unified source of truth (#282) # diff --git a/coordinator/forecast_reader.py b/coordinator/forecast_reader.py index 83b544586..8f2e36d01 100644 --- a/coordinator/forecast_reader.py +++ b/coordinator/forecast_reader.py @@ -327,6 +327,12 @@ def forecast_data(self) -> ForecastData: def source(self) -> Optional[str]: return self._source + @property + def platform(self) -> Optional[str]: + """(#1321) The integration domain behind the chosen source, or None + (a custom entity, or nothing found yet).""" + return getattr(self, "_platform", None) if self._source not in (None, "custom") else None + def _clear_no_forecast_repair(self) -> None: """Reset detection-failure state + drop the Repair issue when a forecast integration appears after a previous absence. @@ -468,6 +474,7 @@ def _capture_entity_groups(self, platform: str) -> None: primary guarantees the groups and ``_entities`` describe the same source — they cannot drift. """ + self._platform = platform groups = self._registry_entity_groups(platform) primary = groups.get("forecast_today") or [] if primary and self._entities.get("forecast_today") == primary[0]: diff --git a/coordinator/plan_override.py b/coordinator/plan_override.py index 2816f1aaf..8bdcc1bab 100644 --- a/coordinator/plan_override.py +++ b/coordinator/plan_override.py @@ -9,10 +9,15 @@ One override per device at a time; setting a new one replaces the old. ``action`` is one of ``ACTIONS``. Only ``skip`` and ``force`` are acted on -today, by battery and charger alike, each riding the EXISTING manual-mode -road (``_decide_battery``'s ``mode == "off"/"force_charge"``, the same +today, by battery, charger and load alike, each riding the EXISTING road +(``_decide_battery``'s ``mode == "off"/"force_charge"``, the same ``ChargerIntent.DISABLE``/``CHARGE_MAX`` #580's VPP pause and boost already -use) — no new decision logic, so no new hardware risk. ``cap`` and ``move`` +use; for a load (#1321) one precedence clause, 3e, in +``compute_load_intent``) — no new actuation path. A load acts on one only in +Smart (surplus) mode, and the peak shed, the daily cap, the device's own +stop, its own run (Legionella) and vacation still win. Since #1335 the +three layers are the only load engine, so the load clause acts on every +install (observer mode withholds it). ``cap`` and ``move`` are accepted and stored (so a later part never needs a storage migration) but no decider reads them yet; they stay inert until a part that needs them (``cap`` wants an amps-clamp path; ``move`` wants #1087's calendar data). @@ -203,7 +208,7 @@ def apply_to_charger_decision( rec = active_override(overrides, charger_id, now) if rec is None: return decision - until = f"{rec.expires_at:%H:%M}" + until = _clock(rec, now) charging = decision.intent in (ChargerIntent.CHARGE_AT_AMPS, ChargerIntent.CHARGE_MAX) if rec.action == "skip" and charging: @@ -237,4 +242,12 @@ def status_payload( rec = active_override(overrides, device_id, now) if rec is None: return None - return {"action": rec.action, "until": f"{rec.expires_at:%H:%M}"} + return {"action": rec.action, "until": _clock(rec, now)} + + +def _clock(rec: PlanOverride, now: datetime) -> str: + """(#1321) The end as ``HH:MM`` on the caller's clock. The stored + stamp is UTC (``set_override``), so printing it as is read an hour or + two early in Europe; ``now`` carries the local zone.""" + tz = now.tzinfo or timezone.utc + return f"{rec.expires_at.astimezone(tz):%H:%M}" diff --git a/coordinator/reason_codes.py b/coordinator/reason_codes.py index 6ff804085..0166eeafe 100644 --- a/coordinator/reason_codes.py +++ b/coordinator/reason_codes.py @@ -129,11 +129,16 @@ # (#1349) it would heat from the house battery, but the battery is at its reserve LOAD_BATTERY_RESERVE = "battery_reserve" LOAD_LEGIONELLA = "legionella" +# (#1321) the user's one-off on a load: run now / not now +LOAD_OVERRIDE_RUN = "load_override_run" +LOAD_OVERRIDE_STOP = "load_override_stop" # (#1335) a stop or a no-start says its own cause, never a start's sentence LOAD_LEGIONELLA_END = "legionella_end" LOAD_AT_TARGET = "at_target" LOAD_NO_READING = "no_reading" LOAD_DISABLED = "disabled" +# (#1335) on only: a stop for a short pool is due, the minimum run holds it +LOAD_MIN_RUN_HOLD = "min_run_hold" # (#1323) a heat pump stepped down for a shortfall: grid power bought in an # expensive hour, or the peak limit near LOAD_STEP_DOWN_PRICE = "step_down_price" diff --git a/coordinator/surplus_controller.py b/coordinator/surplus_controller.py index 2ae050d3d..2c111a018 100644 --- a/coordinator/surplus_controller.py +++ b/coordinator/surplus_controller.py @@ -15,7 +15,7 @@ from __future__ import annotations import logging -from dataclasses import dataclass, field +from dataclasses import dataclass, field, replace from datetime import datetime, timedelta from typing import Any, Dict, List, Optional @@ -891,6 +891,26 @@ def _goal_wants_start(device: "ControllableDevice") -> bool: #: (#1102) A device's own run (Legionella): SEM-driven, never re-tuned. OWN_RUN_SOURCE = "legionella" +#: (#1321) The user's one-off "run now" (``plan_override`` force). SEM-driven: +#: SEM started it, so SEM stops it once the override ends. +OVERRIDE_SOURCE = "override" + +#: (#1321) The one-off actions a load acts on; ``cap``/``move`` stay inert. +LOAD_OVERRIDE_ACTIONS = ("skip", "force") + + +def override_may_force(device: "ControllableDevice") -> bool: + """(#1321) May a ``force`` override run this load at all? Not while the + device is on vacation (a heat pump or a tank SEM must not encourage), + not past its daily cap, not once its own stop condition is met.""" + if getattr(device, "vacation", False) is True: + return False + if getattr(device, "daily_max_runtime_reached", False) is True: + return False + if getattr(device, "stop_condition_met", False) is True: + return False + return True + def _heater_claim(device: "ControllableDevice", active: bool, held: float) -> float: """(#1102) What a heater rung expects to draw: the measured draw while it @@ -941,6 +961,7 @@ def compute_load_intent( is_night: bool = True, daylight_remaining_s: "Optional[float]" = None, plan: "PlanVerdict" = NO_OPINION, + override: Optional[str] = None, grid_start_fits: bool = True, shed_code: str = RC.LOAD_PEAK_SHED, price_is_expensive: bool = False, @@ -955,6 +976,8 @@ def compute_load_intent( surplus left for THIS device after higher-priority loads (the caller's priority walk fills it); the battery/grid clauses don't consume it. Everything else is read off ``device`` so it works on a real device or a mock. + ``override`` (#1321) is the load's active one-off action (``skip`` / + ``force``), or None. """ mode = getattr(device, "control_mode", DeviceControlMode.SURPLUS) active = bool(getattr(device, "is_active", False)) @@ -1121,6 +1144,41 @@ def compute_load_intent( return LoadIntent(True, _heater_claim(device, False, held), OWN_RUN_SOURCE, "Legionella run due", RC.LOAD_LEGIONELLA) + # 3e. (#1321) The user's one-off, for the minutes they chose: ``skip`` + # keeps it off, ``force`` runs it with or without sun. Only in + # Smart (surplus) mode — Off and Peak-only returned above, so a + # standing opt-out outranks a one-off, as for chargers and batteries. + # Everything above still wins: the operator's lock (1b), the peak + # shed (2 — a forced load CAN be shed by a peak), the daily cap and + # the device's own stop (3) and its own run (3a). A force does not + # START under a peak warning (``peak_freeze``), nor on vacation. + # A tank at its target or with a dead sensor is not started (3c). + # ``skip`` is an ordinary stop: the minimum run still holds (only + # Mode Off beats it). ``force`` has its own source, so the whole-set + # rules for sun runs — the deficit stop and "no sun" — pass it by, + # and the walk books it only the sun that is left (#1335). + # When the override ends this clause is simply not taken: the walk + # below decides again, and with no sun it stops the run SEM started. + # (#1323) The peak limit near, on a load that can step down (clause 3f). + peak_step_down = (not active and peak_freeze + and getattr(device, "step_down_ready", False) is True) + if override == "skip": + if peak_step_down: + # (#1321/#1323) A pause must not undo a PEAK step-down: a plain + # off intent let layer 3 release it after its hold while the + # peak was still near. The peak's intent wins — the load is off + # either way, and the step-down is the peak's, so it says so. + # A PRICE step-down is not kept: a pause means SEM does nothing. + return LoadIntent(False, 0.0, None, + "peak limit near — stepped down", + RC.LOAD_STEP_DOWN_PEAK, step_down=True) + return LoadIntent(False, 0.0, None, "paused by you for now", + RC.LOAD_OVERRIDE_STOP) + if (override == "force" and override_may_force(device) + and (active or not peak_freeze)): + return LoadIntent(True, _heater_claim(device, active, held), OVERRIDE_SOURCE, + "run now — set by you", RC.LOAD_OVERRIDE_RUN) + # 3b. A ScheduleDevice past its deadline is a hard commitment — force it on # regardless of surplus or peak posture (matches the old deadline pass, # which was deliberately not peak-gated). ruflo B1. (#1048/#1335) A @@ -1130,7 +1188,7 @@ def compute_load_intent( _rated = _num(getattr(device, "rated_power", 0.0)) return LoadIntent(True, _rated, "solar", "deadline force", RC.LOAD_DEADLINE) - # 3e. (#1323) A shortfall: the load steps DOWN. Only a load that can + # 3f. (#1323) A shortfall: the load steps DOWN. Only a load that can # (``step_down_ready`` — the user switched it on, and its comfort # band reads inside its range, so "too cold" below still leads), and # only one that is not running: a run is ended by its own reasons @@ -1142,7 +1200,7 @@ def compute_load_intent( # the cause is the expensive hour without sun, not the meter reading # its own effect. if not active and getattr(device, "step_down_ready", False) is True: - if peak_freeze: + if peak_step_down: return LoadIntent(False, 0.0, None, "peak limit near — stepped down", RC.LOAD_STEP_DOWN_PEAK, step_down=True) @@ -1322,7 +1380,7 @@ def compute_load_intent( # priority ones first once the pool runs more than 100 W short, so a # higher-priority load never loses first. return LoadIntent(True, sun_claim, sun_src, - f"{sun_src}: running on {effective:.0f}W", + f"{sun_src}: running on {max(0.0, effective):.0f}W", command_w=sun_command) # 5. No source → off. When a plan hold is what suppressed the paid @@ -1634,7 +1692,8 @@ async def reconcile_load(device: "ControllableDevice", intent: LoadIntent, # never actuate it — SEM only re-tunes loads it is actually driving. if intent.source is None: return device.get_current_consumption() - if intent.source == OWN_RUN_SOURCE or intent.source in PAID_SOURCES: + if (intent.source in (OWN_RUN_SOURCE, OVERRIDE_SOURCE) + or intent.source in PAID_SOURCES): # (#1335) never re-tuned: an own run, or a paid run holding the # power its start set (offering it the sun's pool wrote its floor # every other cycle). A tank that holds a setpoint for another @@ -1929,6 +1988,9 @@ def __init__( self._tier1_budget_left: float = 0.0 # (arc Phase 4) last raw surplus samples for the median-of-3 pre-filter self._surplus_samples: List[float] = [] + # (#1321) this cycle's one-off overrides on loads, stamped by update(): + # device_id → {"action", "until"}. Empty = nothing set (the common case). + self._load_overrides: Dict[str, Dict[str, Any]] = {} # (#1335) the last three solar production readings (None = no # reading that cycle), for the no-sun stop self._solar_samples: List[Optional[float]] = [] @@ -2173,6 +2235,23 @@ def grid_funded_draw_w(self) -> float: if d.is_active and getattr(d, "_offpeak_forced", False) ) + def _override_action(self, device) -> Optional[str]: + """(#1321) This load's active one-off action (``skip``/``force``), + or None. Only a Smart-mode (surplus) load acts on one; in any other + mode the override is held but read as nothing.""" + rec = self._load_overrides.get(getattr(device, "device_id", None)) + action = rec.get("action") if isinstance(rec, dict) else None + if action not in LOAD_OVERRIDE_ACTIONS: + return None + if getattr(device, "control_mode", None) != DeviceControlMode.SURPLUS: + return None + return action + + def load_override_status(self, device_id: str) -> Optional[Dict[str, Any]]: + """(#1321) ``{"action", "until"}`` for the device map, or None.""" + rec = self._load_overrides.get(device_id) + return dict(rec) if isinstance(rec, dict) else None + def _desired_intents(self, devices, *, remaining_surplus, reclaim_w, battery_priority, price_level, peak_freeze, peak_shed, peak_shed_all): @@ -2234,6 +2313,8 @@ def _desired_intents(self, devices, *, remaining_surplus, reclaim_w, plan=self._plan_windows.get(did) or NO_OPINION, # (#864) a grid start must fit the billing slot grid_start_fits=self._grid_start_fits(device), + # (#1321) the user's one-off on this load, if any + override=self._override_action(device), # (#1323) the step-down's two facts: this load's price level # and what the house buys now (0 when the meter is unread) price_is_expensive=is_expensive_name( @@ -2258,9 +2339,11 @@ def _desired_intents(self, devices, *, remaining_surplus, reclaim_w, did = device.device_id if not self._sun_run(device, intents[did]): continue + charged = spend.get(did, 0.0) if device.is_active and device.can_deactivate() is False: + if charged > 0.0: + self._say_min_run_holds(device, intents) continue - charged = spend.get(did, 0.0) if charged <= 0.0: continue remaining += self._stop_short(device, intents, spend) @@ -2295,10 +2378,23 @@ def _desired_intents(self, devices, *, remaining_surplus, reclaim_w, and self._sun_run(device, intent)): continue if device.can_deactivate() is False: + self._say_min_run_holds(device, intents) continue self._stop_short(device, intents, spend, reason="no sun left") return intents + @staticmethod + def _say_min_run_holds(device, intents) -> None: + """(#1335) A stop for a short pool is due, and the load's minimum + run time holds it on. The intent stays ON with the same power, + source and command — the walk's books do not change — but it says + so: "solar: running on 0W" read as running on sun with no sun.""" + did = device.device_id + intents[did] = replace( + intents[did], + reason="minimum run time holds it — not enough sun", + code=RC.LOAD_MIN_RUN_HOLD) + def _no_sun(self) -> bool: """(#1335) Is solar production itself gone? True when the reading is live this cycle and at least two of the last three cycles read @@ -2506,6 +2602,9 @@ async def update( # TimeManager. None = no sun data; the cheap-hours grid top-up then # keeps its pre-#953 behaviour (unknown is not a claim about the sun). daylight_remaining_s: Optional[float] = None, + # (#1321) device_id → {"action", "until"}: the active one-off + # overrides. None/empty = none set → behaviour unchanged. + load_overrides: Optional[dict] = None, # (#1335) Solar production right now, W. None = no live reading # (the default, and a dark solar sensor): the no-sun stop never # fires on it. @@ -2581,6 +2680,10 @@ async def update( # (#638 G4) stamp this cycle's joint-plan window verdicts for the # intent path. self._plan_windows = dict(plan_windows or {}) + # (#1321) this cycle's one-off overrides, read by layer 1 (clause 3e) + self._load_overrides = { + str(k): dict(v) for k, v in (load_overrides or {}).items() + if isinstance(v, dict)} # (#864/#1335) the billing slot a grid start must fit, for layer 2 self._peak_slot_allowed_w = peak_slot_allowed_w self._grid_import_w = float(grid_import_w or 0.0) diff --git a/coordinator/types.py b/coordinator/types.py index 393befd7d..dc7365dba 100644 --- a/coordinator/types.py +++ b/coordinator/types.py @@ -1229,6 +1229,8 @@ class SEMData: charging_state: str = "idle" charging_strategy: str = "idle" charging_strategy_reason: str = "" + charging_strategy_reason_code: str = "" + charging_strategy_reason_params: Dict[str, Any] = field(default_factory=dict) available_power: float = 0.0 calculated_current: float = 0.0 # (#657) The "why is my car not charging" flags. All three are decided @@ -1522,6 +1524,8 @@ def _shown(name): "charging_state": self.charging_state, "charging_strategy": self.charging_strategy, "charging_strategy_reason": self.charging_strategy_reason, + "charging_strategy_reason_code": self.charging_strategy_reason_code, + "charging_strategy_reason_params": dict(self.charging_strategy_reason_params or {}), "available_power": int(round(self.available_power or 0)), "calculated_current": self.calculated_current, # (#657) EV block reasons + budget internals. diff --git a/dashboard/card/dist/sem-cards.js b/dashboard/card/dist/sem-cards.js index 36d4faf10..6f0c3d4ce 100644 --- a/dashboard/card/dist/sem-cards.js +++ b/dashboard/card/dist/sem-cards.js @@ -1,4 +1,4 @@ -const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,s=Symbol(),i=new WeakMap;let r=class{constructor(e,t,i){if(this._$cssResult$=!0,i!==s)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=e,this.t=t}get styleSheet(){let e=this.o;const s=this.t;if(t&&void 0===e){const t=void 0!==s&&1===s.length;t&&(e=i.get(s)),void 0===e&&((this.o=e=new CSSStyleSheet).replaceSync(this.cssText),t&&i.set(s,e))}return e}toString(){return this.cssText}};const a=(e,...t)=>{const i=1===e.length?e[0]:t.reduce((t,s,i)=>t+(e=>{if(!0===e._$cssResult$)return e.cssText;if("number"==typeof e)return e;throw Error("Value passed to 'css' function must be a 'css' function result: "+e+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(s)+e[i+1],e[0]);return new r(i,e,s)},o=t?e=>e:e=>e instanceof CSSStyleSheet?(e=>{let t="";for(const s of e.cssRules)t+=s.cssText;return(e=>new r("string"==typeof e?e:e+"",void 0,s))(t)})(e):e,{is:n,defineProperty:l,getOwnPropertyDescriptor:c,getOwnPropertyNames:d,getOwnPropertySymbols:p,getPrototypeOf:_}=Object,h=globalThis,u=h.trustedTypes,g=u?u.emptyScript:"",m=h.reactiveElementPolyfillSupport,f=(e,t)=>e,v={toAttribute(e,t){switch(t){case Boolean:e=e?g:null;break;case Object:case Array:e=null==e?e:JSON.stringify(e)}return e},fromAttribute(e,t){let s=e;switch(t){case Boolean:s=null!==e;break;case Number:s=null===e?null:Number(e);break;case Object:case Array:try{s=JSON.parse(e)}catch(e){s=null}}return s}},y=(e,t)=>!n(e,t),b={attribute:!0,type:String,converter:v,reflect:!1,useDefault:!1,hasChanged:y};Symbol.metadata??=Symbol("metadata"),h.litPropertyMetadata??=new WeakMap;let x=class extends HTMLElement{static addInitializer(e){this._$Ei(),(this.l??=[]).push(e)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(e,t=b){if(t.state&&(t.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(e)&&((t=Object.create(t)).wrapped=!0),this.elementProperties.set(e,t),!t.noAccessor){const s=Symbol(),i=this.getPropertyDescriptor(e,s,t);void 0!==i&&l(this.prototype,e,i)}}static getPropertyDescriptor(e,t,s){const{get:i,set:r}=c(this.prototype,e)??{get(){return this[t]},set(e){this[t]=e}};return{get:i,set(t){const a=i?.call(this);r?.call(this,t),this.requestUpdate(e,a,s)},configurable:!0,enumerable:!0}}static getPropertyOptions(e){return this.elementProperties.get(e)??b}static _$Ei(){if(this.hasOwnProperty(f("elementProperties")))return;const e=_(this);e.finalize(),void 0!==e.l&&(this.l=[...e.l]),this.elementProperties=new Map(e.elementProperties)}static finalize(){if(this.hasOwnProperty(f("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(f("properties"))){const e=this.properties,t=[...d(e),...p(e)];for(const s of t)this.createProperty(s,e[s])}const e=this[Symbol.metadata];if(null!==e){const t=litPropertyMetadata.get(e);if(void 0!==t)for(const[e,s]of t)this.elementProperties.set(e,s)}this._$Eh=new Map;for(const[e,t]of this.elementProperties){const s=this._$Eu(e,t);void 0!==s&&this._$Eh.set(s,e)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(e){const t=[];if(Array.isArray(e)){const s=new Set(e.flat(1/0).reverse());for(const e of s)t.unshift(o(e))}else void 0!==e&&t.push(o(e));return t}static _$Eu(e,t){const s=t.attribute;return!1===s?void 0:"string"==typeof s?s:"string"==typeof e?e.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(e=>this.enableUpdating=e),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(e=>e(this))}addController(e){(this._$EO??=new Set).add(e),void 0!==this.renderRoot&&this.isConnected&&e.hostConnected?.()}removeController(e){this._$EO?.delete(e)}_$E_(){const e=new Map,t=this.constructor.elementProperties;for(const s of t.keys())this.hasOwnProperty(s)&&(e.set(s,this[s]),delete this[s]);e.size>0&&(this._$Ep=e)}createRenderRoot(){const s=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return((s,i)=>{if(t)s.adoptedStyleSheets=i.map(e=>e instanceof CSSStyleSheet?e:e.styleSheet);else for(const t of i){const i=document.createElement("style"),r=e.litNonce;void 0!==r&&i.setAttribute("nonce",r),i.textContent=t.cssText,s.appendChild(i)}})(s,this.constructor.elementStyles),s}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(e=>e.hostConnected?.())}enableUpdating(e){}disconnectedCallback(){this._$EO?.forEach(e=>e.hostDisconnected?.())}attributeChangedCallback(e,t,s){this._$AK(e,s)}_$ET(e,t){const s=this.constructor.elementProperties.get(e),i=this.constructor._$Eu(e,s);if(void 0!==i&&!0===s.reflect){const r=(void 0!==s.converter?.toAttribute?s.converter:v).toAttribute(t,s.type);this._$Em=e,null==r?this.removeAttribute(i):this.setAttribute(i,r),this._$Em=null}}_$AK(e,t){const s=this.constructor,i=s._$Eh.get(e);if(void 0!==i&&this._$Em!==i){const e=s.getPropertyOptions(i),r="function"==typeof e.converter?{fromAttribute:e.converter}:void 0!==e.converter?.fromAttribute?e.converter:v;this._$Em=i;const a=r.fromAttribute(t,e.type);this[i]=a??this._$Ej?.get(i)??a,this._$Em=null}}requestUpdate(e,t,s,i=!1,r){if(void 0!==e){const a=this.constructor;if(!1===i&&(r=this[e]),s??=a.getPropertyOptions(e),!((s.hasChanged??y)(r,t)||s.useDefault&&s.reflect&&r===this._$Ej?.get(e)&&!this.hasAttribute(a._$Eu(e,s))))return;this.C(e,t,s)}!1===this.isUpdatePending&&(this._$ES=this._$EP())}C(e,t,{useDefault:s,reflect:i,wrapped:r},a){s&&!(this._$Ej??=new Map).has(e)&&(this._$Ej.set(e,a??t??this[e]),!0!==r||void 0!==a)||(this._$AL.has(e)||(this.hasUpdated||s||(t=void 0),this._$AL.set(e,t)),!0===i&&this._$Em!==e&&(this._$Eq??=new Set).add(e))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(e){Promise.reject(e)}const e=this.scheduleUpdate();return null!=e&&await e,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(const[e,t]of this._$Ep)this[e]=t;this._$Ep=void 0}const e=this.constructor.elementProperties;if(e.size>0)for(const[t,s]of e){const{wrapped:e}=s,i=this[t];!0!==e||this._$AL.has(t)||void 0===i||this.C(t,void 0,s,i)}}let e=!1;const t=this._$AL;try{e=this.shouldUpdate(t),e?(this.willUpdate(t),this._$EO?.forEach(e=>e.hostUpdate?.()),this.update(t)):this._$EM()}catch(t){throw e=!1,this._$EM(),t}e&&this._$AE(t)}willUpdate(e){}_$AE(e){this._$EO?.forEach(e=>e.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(e)),this.updated(e)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(e){return!0}update(e){this._$Eq&&=this._$Eq.forEach(e=>this._$ET(e,this[e])),this._$EM()}updated(e){}firstUpdated(e){}};x.elementStyles=[],x.shadowRootOptions={mode:"open"},x[f("elementProperties")]=new Map,x[f("finalized")]=new Map,m?.({ReactiveElement:x}),(h.reactiveElementVersions??=[]).push("2.1.2");const $=globalThis,w=e=>e,k=$.trustedTypes,S=k?k.createPolicy("lit-html",{createHTML:e=>e}):void 0,C="$lit$",z=`lit$${Math.random().toFixed(9).slice(2)}$`,M="?"+z,D=`<${M}>`,E=document,N=()=>E.createComment(""),F=e=>null===e||"object"!=typeof e&&"function"!=typeof e,T=Array.isArray,A="[ \t\n\f\r]",B=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,R=/-->/g,O=/>/g,I=RegExp(`>|${A}(?:([^\\s"'>=/]+)(${A}*=${A}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),L=/'/g,P=/"/g,U=/^(?:script|style|textarea|title)$/i,q=e=>(t,...s)=>({_$litType$:e,strings:t,values:s}),H=q(1),W=q(2),j=Symbol.for("lit-noChange"),G=Symbol.for("lit-nothing"),K=new WeakMap,V=E.createTreeWalker(E,129);function Y(e,t){if(!T(e)||!e.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==S?S.createHTML(t):t}const Z=(e,t)=>{const s=e.length-1,i=[];let r,a=2===t?"":3===t?"":"",o=B;for(let t=0;t"===l[0]?(o=r??B,c=-1):void 0===l[1]?c=-2:(c=o.lastIndex-l[2].length,n=l[1],o=void 0===l[3]?I:'"'===l[3]?P:L):o===P||o===L?o=I:o===R||o===O?o=B:(o=I,r=void 0);const p=o===I&&e[t+1].startsWith("/>")?" ":"";a+=o===B?s+D:c>=0?(i.push(n),s.slice(0,c)+C+s.slice(c)+z+p):s+z+(-2===c?t:p)}return[Y(e,a+(e[s]||"")+(2===t?"":3===t?"":"")),i]};class X{constructor({strings:e,_$litType$:t},s){let i;this.parts=[];let r=0,a=0;const o=e.length-1,n=this.parts,[l,c]=Z(e,t);if(this.el=X.createElement(l,s),V.currentNode=this.el.content,2===t||3===t){const e=this.el.content.firstChild;e.replaceWith(...e.childNodes)}for(;null!==(i=V.nextNode())&&n.length0){i.textContent=k?k.emptyScript:"";for(let s=0;sT(e)||"function"==typeof e?.[Symbol.iterator])(e)?this.k(e):this._(e)}O(e){return this._$AA.parentNode.insertBefore(e,this._$AB)}T(e){this._$AH!==e&&(this._$AR(),this._$AH=this.O(e))}_(e){this._$AH!==G&&F(this._$AH)?this._$AA.nextSibling.data=e:this.T(E.createTextNode(e)),this._$AH=e}$(e){const{values:t,_$litType$:s}=e,i="number"==typeof s?this._$AC(e):(void 0===s.el&&(s.el=X.createElement(Y(s.h,s.h[0]),this.options)),s);if(this._$AH?._$AD===i)this._$AH.p(t);else{const e=new J(i,this),s=e.u(this.options);e.p(t),this.T(s),this._$AH=e}}_$AC(e){let t=K.get(e.strings);return void 0===t&&K.set(e.strings,t=new X(e)),t}k(e){T(this._$AH)||(this._$AH=[],this._$AR());const t=this._$AH;let s,i=0;for(const r of e)i===t.length?t.push(s=new ee(this.O(N()),this.O(N()),this,this.options)):s=t[i],s._$AI(r),i++;i2||""!==s[0]||""!==s[1]?(this._$AH=Array(s.length-1).fill(new String),this.strings=s):this._$AH=G}_$AI(e,t=this,s,i){const r=this.strings;let a=!1;if(void 0===r)e=Q(this,e,t,0),a=!F(e)||e!==this._$AH&&e!==j,a&&(this._$AH=e);else{const i=e;let o,n;for(e=r[0],o=0;o{const i=s?.renderBefore??t;let r=i._$litPart$;if(void 0===r){const e=s?.renderBefore??null;i._$litPart$=r=new ee(t.insertBefore(N(),e),e,void 0,s??{})}return r._$AI(e),r})(t,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return j}};ce._$litElement$=!0,ce.finalized=!0,le.litElementHydrateSupport?.({LitElement:ce});const de=le.litElementPolyfillSupport;de?.({LitElement:ce}),(le.litElementVersions??=[]).push("4.2.2");const pe={solar:"#ff9800",gridImport:"#488fc2",gridExport:"#8353d1",batteryOut:"#4db6ac",battery:"#4db6ac",home:"#5BC8D8",ev:"#8DC892",inverter:"#96CAEE"},_e=["#FF8A65","#AED581","#CE93D8","#64B5F6","#ff9800","#96CAEE"],he="#86A9B4";function ue(e){if(null==e||isNaN(e))return"— W";return Math.abs(e)>=1e3?`${(e/1e3).toFixed(1)} kW`:`${Math.round(e)} W`}function ge(e,t){if(!e)return"—";try{return new Date(e).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit",timeZone:t||void 0})}catch(e){return"—"}}function me(e){const t=Math.abs(e);if(t<=0)return 4;const s=4-.9*Math.log10(Math.max(t,1));return Math.max(.5,Math.min(4,s))}function fe(e){if(!e)return"EUR";const t=e.states?.["sensor.sem_daily_costs"];return t?.attributes?.unit_of_measurement?t.attributes.unit_of_measurement:e.config?.currency||"EUR"}function ve(e){const t=e||document.documentElement,s=getComputedStyle(t),i=(e,t)=>s.getPropertyValue(e).trim()||t,r=i("--primary-text-color","#e0e0e0"),a=i("--secondary-text-color","#888888"),o=i("--card-background-color","rgba(30,35,45,0.5)"),n=i("--divider-color","rgba(255,255,255,0.12)"),l=i("--secondary-background-color","rgba(255,255,255,0.06)"),c=i("--ha-card-box-shadow","0 2px 8px rgba(0,0,0,0.15)"),d=i("--primary-color","#42a5f5"),p=(()=>{const e=i("--primary-background-color","#111"),t=e.match(/\d+/g);if(t&&t.length>=3){return(.299*+t[0]+.587*+t[1]+.114*+t[2])/255<.5}return!(e.startsWith("#f")||e.startsWith("#e")||e.startsWith("#d")||e.startsWith("#c")||e.startsWith("rgb(2"))})();return{text:r,textSec:a,cardBg:o,divider:n,bgSec:l,shadow:c,accent:d,isDark:p,textTertiary:p?"rgba(255,255,255,0.45)":"rgba(0,0,0,0.50)",textDisabled:p?"rgba(255,255,255,0.26)":"rgba(0,0,0,0.30)",surface:p?"rgba(255,255,255,0.06)":"rgba(0,0,0,0.03)",surfaceHover:p?"rgba(255,255,255,0.10)":"rgba(0,0,0,0.06)",surfaceBorder:p?"rgba(255,255,255,0.12)":"rgba(0,0,0,0.08)",dotColor:p?"rgba(128,128,128,0.05)":"rgba(128,128,128,0.06)",glowAlpha:p?.05:.03,tooltipBg:p?"rgba(20,20,30,0.95)":"rgba(255,255,255,0.95)",tooltipText:p?"#e0e0e0":"#333333",tooltipBorder:p?"rgba(255,255,255,0.15)":"rgba(0,0,0,0.12)"}}function ye(e,t,s){return`radial-gradient(ellipse 70% 60% at 50% 25%, ${t}${Math.round(.06*255).toString(16).padStart(2,"0")} 0%, transparent 100%),\n radial-gradient(circle at 2px 2px, ${e.dotColor} 0.7px, transparent 0.7px)`}function be(e,t){if(!e?.states)return"";const s=t||"sensor.sem_";return["pv1","pv2","pv3","pv4"].map(t=>e.states[`${s}pv_string_${t}_power`]?.state??"").join("|")}function xe(e,t){if(!e||!e.states)return[];const s=t||"sensor.sem_",i=[];for(const t of["pv1","pv2","pv3","pv4"]){const r=`${s}pv_string_${t}_power`,a=e.states[r];if(!a||!0===a.attributes?.restored)continue;const o=parseFloat(a.state),n=`${s}pv_string_${t}_daily_energy`,l=e.states[n],c=l?parseFloat(l.state):NaN;i.push({slot:t,watts:isNaN(o)?null:o,entityId:r,energyKwh:isNaN(c)?null:c,energyEntityId:l?n:null,name:a.attributes?.string_name||l?.attributes?.string_name||"PV"+t.replace(/^pv/,"")})}return i.length>=2?i:[]}function $e(e){return null==e||isNaN(e)?"— kW":`${(Math.abs(e)/1e3).toFixed(2)} kW`}const we="\n .pv-strings-row {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n padding: 6px 12px;\n font-family: 'Segoe UI','Roboto',sans-serif;\n font-size: 12px;\n }\n .pv-chip {\n display: inline-flex;\n align-items: baseline;\n gap: 4px;\n padding: 2px 8px;\n background: rgba(255, 152, 0, 0.10);\n border: 1px solid rgba(255, 152, 0, 0.25);\n border-radius: 999px;\n color: var(--secondary-text-color, #aaa);\n cursor: pointer;\n transition: background 120ms ease;\n }\n .pv-chip:hover { background: rgba(255, 152, 0, 0.18); }\n .pv-chip-label {\n font-size: 11px;\n letter-spacing: 0.5px;\n text-transform: uppercase;\n opacity: 0.7;\n }\n .pv-chip-value {\n color: var(--sem-solar, #ff9800);\n font-weight: 600;\n font-variant-numeric: tabular-nums;\n }\n";function ke(e,t,s){customElements.get(e)||(customElements.define(e,t),s&&(window.customCards=window.customCards||[],window.customCards.push(s)))}const Se={normal:"normal",negated:"sign_negated",learning:"learning"},Ce={tibber:"Tibber",amber:"Amber",octopus:"Octopus Energy",nordpool:"Nord Pool",nordpool_official:"Nord Pool",awattar:"aWATTar",entsoe:"ENTSO-e"},ze={diag_grid_mode:{combined:"grid_combined",split:"grid_split","split-declared":"grid_split","split-declared-unverified":"grid_split_unverified","split-lowconf":"grid_split_unverified",manual:"grid_manual"},diag_grid_sign:Se,diag_battery_sign:Se,heat_pump_mode:{blocked:"blocked",normal:"normal",boost:"boost",force_on:"force_on"},diag_charger_control:{number:"charger_control_number",service:"charger_control_service",none:""},tariff_provider:{custom:"custom",static:"tariff_static",calendar:"tariff_calendar",unknown:"unknown"},load_management_status:{normal:"normal",warning:"warning",shedding:"shedding",emergency:"emergency",disabled:"disabled",error:"error",idle:"idle"}},Me=new Set(["diag_battery_sign"]);function De(e,t,s){if(Object.prototype.hasOwnProperty.call(e,t)){const i=e[t];return i?s(i):""}return null}function Ee(e,t,s){const i=String(t??"").trim();if(!i)return"";const r=ze[e];if(!r)return i;if(Me.has(e))return i.replace(/[a-z_]+/g,e=>{const t=De(r,e,s);return null===t?e:t});const a=De(r,i.toLowerCase(),s);if(null!==a)return a;if("tariff_provider"===e){const e=Ce[i.toLowerCase()];if(e)return e}return i}const Ne="—";function Fe(e){if(!e)return!0;const t=e.state;return null==t||("unavailable"===t||"unknown"===t||""===t||Number.isNaN(parseFloat(t)))}function Te(e){if(Fe(e))return null;const t=parseFloat(e.state);return Number.isFinite(t)?t:null}function Ae(e,t=0){return null!=e&&Number.isFinite(e)?e.toFixed(t):Ne}function Be(e,t=!1){return t||null==e||Number.isNaN(e)?{label:Ne,fraction:0,known:!1}:{label:`${e.toFixed(0)}%`,fraction:Math.max(0,Math.min(1,e/100)),known:!0}}function Re(e,t){let s=!1;for(const i of t||[]){const t=i?e?.[i]:void 0;if(t&&(s=!0,"unavailable"!==t.state&&"unknown"!==t.state))return!1}return s}function Oe(e,t){let s=0;for(const i of t||[])if(i&&(s++,null!=Te(e?.[i])))return!1;return s>0}const Ie=a` +const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,s=Symbol(),i=new WeakMap;let r=class{constructor(e,t,i){if(this._$cssResult$=!0,i!==s)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=e,this.t=t}get styleSheet(){let e=this.o;const s=this.t;if(t&&void 0===e){const t=void 0!==s&&1===s.length;t&&(e=i.get(s)),void 0===e&&((this.o=e=new CSSStyleSheet).replaceSync(this.cssText),t&&i.set(s,e))}return e}toString(){return this.cssText}};const a=(e,...t)=>{const i=1===e.length?e[0]:t.reduce((t,s,i)=>t+(e=>{if(!0===e._$cssResult$)return e.cssText;if("number"==typeof e)return e;throw Error("Value passed to 'css' function must be a 'css' function result: "+e+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(s)+e[i+1],e[0]);return new r(i,e,s)},n=t?e=>e:e=>e instanceof CSSStyleSheet?(e=>{let t="";for(const s of e.cssRules)t+=s.cssText;return(e=>new r("string"==typeof e?e:e+"",void 0,s))(t)})(e):e,{is:o,defineProperty:l,getOwnPropertyDescriptor:c,getOwnPropertyNames:_,getOwnPropertySymbols:d,getPrototypeOf:p}=Object,h=globalThis,u=h.trustedTypes,m=u?u.emptyScript:"",g=h.reactiveElementPolyfillSupport,f=(e,t)=>e,y={toAttribute(e,t){switch(t){case Boolean:e=e?m:null;break;case Object:case Array:e=null==e?e:JSON.stringify(e)}return e},fromAttribute(e,t){let s=e;switch(t){case Boolean:s=null!==e;break;case Number:s=null===e?null:Number(e);break;case Object:case Array:try{s=JSON.parse(e)}catch(e){s=null}}return s}},b=(e,t)=>!o(e,t),v={attribute:!0,type:String,converter:y,reflect:!1,useDefault:!1,hasChanged:b};Symbol.metadata??=Symbol("metadata"),h.litPropertyMetadata??=new WeakMap;let x=class extends HTMLElement{static addInitializer(e){this._$Ei(),(this.l??=[]).push(e)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(e,t=v){if(t.state&&(t.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(e)&&((t=Object.create(t)).wrapped=!0),this.elementProperties.set(e,t),!t.noAccessor){const s=Symbol(),i=this.getPropertyDescriptor(e,s,t);void 0!==i&&l(this.prototype,e,i)}}static getPropertyDescriptor(e,t,s){const{get:i,set:r}=c(this.prototype,e)??{get(){return this[t]},set(e){this[t]=e}};return{get:i,set(t){const a=i?.call(this);r?.call(this,t),this.requestUpdate(e,a,s)},configurable:!0,enumerable:!0}}static getPropertyOptions(e){return this.elementProperties.get(e)??v}static _$Ei(){if(this.hasOwnProperty(f("elementProperties")))return;const e=p(this);e.finalize(),void 0!==e.l&&(this.l=[...e.l]),this.elementProperties=new Map(e.elementProperties)}static finalize(){if(this.hasOwnProperty(f("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(f("properties"))){const e=this.properties,t=[..._(e),...d(e)];for(const s of t)this.createProperty(s,e[s])}const e=this[Symbol.metadata];if(null!==e){const t=litPropertyMetadata.get(e);if(void 0!==t)for(const[e,s]of t)this.elementProperties.set(e,s)}this._$Eh=new Map;for(const[e,t]of this.elementProperties){const s=this._$Eu(e,t);void 0!==s&&this._$Eh.set(s,e)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(e){const t=[];if(Array.isArray(e)){const s=new Set(e.flat(1/0).reverse());for(const e of s)t.unshift(n(e))}else void 0!==e&&t.push(n(e));return t}static _$Eu(e,t){const s=t.attribute;return!1===s?void 0:"string"==typeof s?s:"string"==typeof e?e.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(e=>this.enableUpdating=e),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(e=>e(this))}addController(e){(this._$EO??=new Set).add(e),void 0!==this.renderRoot&&this.isConnected&&e.hostConnected?.()}removeController(e){this._$EO?.delete(e)}_$E_(){const e=new Map,t=this.constructor.elementProperties;for(const s of t.keys())this.hasOwnProperty(s)&&(e.set(s,this[s]),delete this[s]);e.size>0&&(this._$Ep=e)}createRenderRoot(){const s=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return((s,i)=>{if(t)s.adoptedStyleSheets=i.map(e=>e instanceof CSSStyleSheet?e:e.styleSheet);else for(const t of i){const i=document.createElement("style"),r=e.litNonce;void 0!==r&&i.setAttribute("nonce",r),i.textContent=t.cssText,s.appendChild(i)}})(s,this.constructor.elementStyles),s}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(e=>e.hostConnected?.())}enableUpdating(e){}disconnectedCallback(){this._$EO?.forEach(e=>e.hostDisconnected?.())}attributeChangedCallback(e,t,s){this._$AK(e,s)}_$ET(e,t){const s=this.constructor.elementProperties.get(e),i=this.constructor._$Eu(e,s);if(void 0!==i&&!0===s.reflect){const r=(void 0!==s.converter?.toAttribute?s.converter:y).toAttribute(t,s.type);this._$Em=e,null==r?this.removeAttribute(i):this.setAttribute(i,r),this._$Em=null}}_$AK(e,t){const s=this.constructor,i=s._$Eh.get(e);if(void 0!==i&&this._$Em!==i){const e=s.getPropertyOptions(i),r="function"==typeof e.converter?{fromAttribute:e.converter}:void 0!==e.converter?.fromAttribute?e.converter:y;this._$Em=i;const a=r.fromAttribute(t,e.type);this[i]=a??this._$Ej?.get(i)??a,this._$Em=null}}requestUpdate(e,t,s,i=!1,r){if(void 0!==e){const a=this.constructor;if(!1===i&&(r=this[e]),s??=a.getPropertyOptions(e),!((s.hasChanged??b)(r,t)||s.useDefault&&s.reflect&&r===this._$Ej?.get(e)&&!this.hasAttribute(a._$Eu(e,s))))return;this.C(e,t,s)}!1===this.isUpdatePending&&(this._$ES=this._$EP())}C(e,t,{useDefault:s,reflect:i,wrapped:r},a){s&&!(this._$Ej??=new Map).has(e)&&(this._$Ej.set(e,a??t??this[e]),!0!==r||void 0!==a)||(this._$AL.has(e)||(this.hasUpdated||s||(t=void 0),this._$AL.set(e,t)),!0===i&&this._$Em!==e&&(this._$Eq??=new Set).add(e))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(e){Promise.reject(e)}const e=this.scheduleUpdate();return null!=e&&await e,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(const[e,t]of this._$Ep)this[e]=t;this._$Ep=void 0}const e=this.constructor.elementProperties;if(e.size>0)for(const[t,s]of e){const{wrapped:e}=s,i=this[t];!0!==e||this._$AL.has(t)||void 0===i||this.C(t,void 0,s,i)}}let e=!1;const t=this._$AL;try{e=this.shouldUpdate(t),e?(this.willUpdate(t),this._$EO?.forEach(e=>e.hostUpdate?.()),this.update(t)):this._$EM()}catch(t){throw e=!1,this._$EM(),t}e&&this._$AE(t)}willUpdate(e){}_$AE(e){this._$EO?.forEach(e=>e.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(e)),this.updated(e)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(e){return!0}update(e){this._$Eq&&=this._$Eq.forEach(e=>this._$ET(e,this[e])),this._$EM()}updated(e){}firstUpdated(e){}};x.elementStyles=[],x.shadowRootOptions={mode:"open"},x[f("elementProperties")]=new Map,x[f("finalized")]=new Map,g?.({ReactiveElement:x}),(h.reactiveElementVersions??=[]).push("2.1.2");const $=globalThis,w=e=>e,k=$.trustedTypes,S=k?k.createPolicy("lit-html",{createHTML:e=>e}):void 0,C="$lit$",M=`lit$${Math.random().toFixed(9).slice(2)}$`,z="?"+M,D=`<${z}>`,N=document,E=()=>N.createComment(""),F=e=>null===e||"object"!=typeof e&&"function"!=typeof e,A=Array.isArray,T="[ \t\n\f\r]",O=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,B=/-->/g,I=/>/g,L=RegExp(`>|${T}(?:([^\\s"'>=/]+)(${T}*=${T}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),R=/'/g,W=/"/g,P=/^(?:script|style|textarea|title)$/i,U=e=>(t,...s)=>({_$litType$:e,strings:t,values:s}),j=U(1),q=U(2),K=Symbol.for("lit-noChange"),H=Symbol.for("lit-nothing"),G=new WeakMap,V=N.createTreeWalker(N,129);function Y(e,t){if(!A(e)||!e.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==S?S.createHTML(t):t}const Z=(e,t)=>{const s=e.length-1,i=[];let r,a=2===t?"":3===t?"":"",n=O;for(let t=0;t"===l[0]?(n=r??O,c=-1):void 0===l[1]?c=-2:(c=n.lastIndex-l[2].length,o=l[1],n=void 0===l[3]?L:'"'===l[3]?W:R):n===W||n===R?n=L:n===B||n===I?n=O:(n=L,r=void 0);const d=n===L&&e[t+1].startsWith("/>")?" ":"";a+=n===O?s+D:c>=0?(i.push(o),s.slice(0,c)+C+s.slice(c)+M+d):s+M+(-2===c?t:d)}return[Y(e,a+(e[s]||"")+(2===t?"":3===t?"":"")),i]};let X=class e{constructor({strings:t,_$litType$:s},i){let r;this.parts=[];let a=0,n=0;const o=t.length-1,l=this.parts,[c,_]=Z(t,s);if(this.el=e.createElement(c,i),V.currentNode=this.el.content,2===s||3===s){const e=this.el.content.firstChild;e.replaceWith(...e.childNodes)}for(;null!==(r=V.nextNode())&&l.length0){r.textContent=k?k.emptyScript:"";for(let s=0;sA(e)||"function"==typeof e?.[Symbol.iterator])(e)?this.k(e):this._(e)}O(e){return this._$AA.parentNode.insertBefore(e,this._$AB)}T(e){this._$AH!==e&&(this._$AR(),this._$AH=this.O(e))}_(e){this._$AH!==H&&F(this._$AH)?this._$AA.nextSibling.data=e:this.T(N.createTextNode(e)),this._$AH=e}$(e){const{values:t,_$litType$:s}=e,i="number"==typeof s?this._$AC(e):(void 0===s.el&&(s.el=X.createElement(Y(s.h,s.h[0]),this.options)),s);if(this._$AH?._$AD===i)this._$AH.p(t);else{const e=new Q(i,this),s=e.u(this.options);e.p(t),this.T(s),this._$AH=e}}_$AC(e){let t=G.get(e.strings);return void 0===t&&G.set(e.strings,t=new X(e)),t}k(e){A(this._$AH)||(this._$AH=[],this._$AR());const t=this._$AH;let s,i=0;for(const r of e)i===t.length?t.push(s=new ee(this.O(E()),this.O(E()),this,this.options)):s=t[i],s._$AI(r),i++;i2||""!==s[0]||""!==s[1]?(this._$AH=Array(s.length-1).fill(new String),this.strings=s):this._$AH=H}_$AI(e,t=this,s,i){const r=this.strings;let a=!1;if(void 0===r)e=J(this,e,t,0),a=!F(e)||e!==this._$AH&&e!==K,a&&(this._$AH=e);else{const i=e;let n,o;for(e=r[0],n=0;n{const i=s?.renderBefore??t;let r=i._$litPart$;if(void 0===r){const e=s?.renderBefore??null;i._$litPart$=r=new ee(t.insertBefore(E(),e),e,void 0,s??{})}return r._$AI(e),r})(t,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return K}};ce._$litElement$=!0,ce.finalized=!0,le.litElementHydrateSupport?.({LitElement:ce});const _e=le.litElementPolyfillSupport;_e?.({LitElement:ce}),(le.litElementVersions??=[]).push("4.2.2");const de={solar:"#ff9800",gridImport:"#488fc2",gridExport:"#8353d1",batteryOut:"#4db6ac",battery:"#4db6ac",home:"#5BC8D8",ev:"#8DC892",inverter:"#96CAEE"},pe=["#FF8A65","#AED581","#CE93D8","#64B5F6","#ff9800","#96CAEE"],he="#86A9B4";function ue(e){if(null==e||isNaN(e))return"— W";return Math.abs(e)>=1e3?`${(e/1e3).toFixed(1)} kW`:`${Math.round(e)} W`}function me(e,t){if(!e)return"—";try{return new Date(e).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit",timeZone:t||void 0})}catch(e){return"—"}}function ge(e){const t=Math.abs(e);if(t<=0)return 4;const s=4-.9*Math.log10(Math.max(t,1));return Math.max(.5,Math.min(4,s))}function fe(e){if(!e)return"EUR";const t=e.states?.["sensor.sem_daily_costs"];return t?.attributes?.unit_of_measurement?t.attributes.unit_of_measurement:e.config?.currency||"EUR"}function ye(e){const t=e||document.documentElement,s=getComputedStyle(t),i=(e,t)=>s.getPropertyValue(e).trim()||t,r=i("--primary-text-color","#e0e0e0"),a=i("--secondary-text-color","#888888"),n=i("--card-background-color","rgba(30,35,45,0.5)"),o=i("--divider-color","rgba(255,255,255,0.12)"),l=i("--secondary-background-color","rgba(255,255,255,0.06)"),c=i("--ha-card-box-shadow","0 2px 8px rgba(0,0,0,0.15)"),_=i("--primary-color","#42a5f5"),d=(()=>{const e=i("--primary-background-color","#111"),t=e.match(/\d+/g);if(t&&t.length>=3){return(.299*+t[0]+.587*+t[1]+.114*+t[2])/255<.5}return!(e.startsWith("#f")||e.startsWith("#e")||e.startsWith("#d")||e.startsWith("#c")||e.startsWith("rgb(2"))})();return{text:r,textSec:a,cardBg:n,divider:o,bgSec:l,shadow:c,accent:_,isDark:d,textTertiary:d?"rgba(255,255,255,0.45)":"rgba(0,0,0,0.50)",textDisabled:d?"rgba(255,255,255,0.26)":"rgba(0,0,0,0.30)",surface:d?"rgba(255,255,255,0.06)":"rgba(0,0,0,0.03)",surfaceHover:d?"rgba(255,255,255,0.10)":"rgba(0,0,0,0.06)",surfaceBorder:d?"rgba(255,255,255,0.12)":"rgba(0,0,0,0.08)",dotColor:d?"rgba(128,128,128,0.05)":"rgba(128,128,128,0.06)",glowAlpha:d?.05:.03,tooltipBg:d?"rgba(20,20,30,0.95)":"rgba(255,255,255,0.95)",tooltipText:d?"#e0e0e0":"#333333",tooltipBorder:d?"rgba(255,255,255,0.15)":"rgba(0,0,0,0.12)"}}function be(e,t,s){return`radial-gradient(ellipse 70% 60% at 50% 25%, ${t}${Math.round(.06*255).toString(16).padStart(2,"0")} 0%, transparent 100%),\n radial-gradient(circle at 2px 2px, ${e.dotColor} 0.7px, transparent 0.7px)`}function ve(e,t){if(!e?.states)return"";const s=t||"sensor.sem_";return["pv1","pv2","pv3","pv4"].map(t=>e.states[`${s}pv_string_${t}_power`]?.state??"").join("|")}function xe(e,t){if(!e||!e.states)return[];const s=t||"sensor.sem_",i=[];for(const t of["pv1","pv2","pv3","pv4"]){const r=`${s}pv_string_${t}_power`,a=e.states[r];if(!a||!0===a.attributes?.restored)continue;const n=parseFloat(a.state),o=`${s}pv_string_${t}_daily_energy`,l=e.states[o],c=l?parseFloat(l.state):NaN;i.push({slot:t,watts:isNaN(n)?null:n,entityId:r,energyKwh:isNaN(c)?null:c,energyEntityId:l?o:null,name:a.attributes?.string_name||l?.attributes?.string_name||"PV"+t.replace(/^pv/,"")})}return i.length>=2?i:[]}function $e(e){return null==e||isNaN(e)?"— kW":`${(Math.abs(e)/1e3).toFixed(2)} kW`}const we="\n .pv-strings-row {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n padding: 6px 12px;\n font-family: 'Segoe UI','Roboto',sans-serif;\n font-size: 12px;\n }\n .pv-chip {\n display: inline-flex;\n align-items: baseline;\n gap: 4px;\n padding: 2px 8px;\n background: rgba(255, 152, 0, 0.10);\n border: 1px solid rgba(255, 152, 0, 0.25);\n border-radius: 999px;\n color: var(--secondary-text-color, #aaa);\n cursor: pointer;\n transition: background 120ms ease;\n }\n .pv-chip:hover { background: rgba(255, 152, 0, 0.18); }\n .pv-chip-label {\n font-size: 11px;\n letter-spacing: 0.5px;\n text-transform: uppercase;\n opacity: 0.7;\n }\n .pv-chip-value {\n color: var(--sem-solar, #ff9800);\n font-weight: 600;\n font-variant-numeric: tabular-nums;\n }\n";function ke(e,t,s){customElements.get(e)||(customElements.define(e,t),s&&(window.customCards=window.customCards||[],window.customCards.push(s)))}const Se={normal:"normal",negated:"sign_negated",learning:"learning"},Ce={tibber:"Tibber",amber:"Amber",octopus:"Octopus Energy",nordpool:"Nord Pool",nordpool_official:"Nord Pool",awattar:"aWATTar",entsoe:"ENTSO-e"},Me={diag_grid_mode:{combined:"grid_combined",split:"grid_split","split-declared":"grid_split","split-declared-unverified":"grid_split_unverified","split-lowconf":"grid_split_unverified",manual:"grid_manual"},diag_grid_sign:Se,diag_battery_sign:Se,heat_pump_mode:{blocked:"blocked",normal:"normal",boost:"boost",force_on:"force_on"},diag_charger_control:{number:"charger_control_number",service:"charger_control_service",none:""},tariff_provider:{custom:"custom",static:"tariff_static",calendar:"tariff_calendar",unknown:"unknown"},load_management_status:{normal:"normal",warning:"warning",shedding:"shedding",emergency:"emergency",disabled:"disabled",error:"error",idle:"idle"}},ze=new Set(["diag_battery_sign"]);function De(e,t,s){if(Object.prototype.hasOwnProperty.call(e,t)){const i=e[t];return i?s(i):""}return null}function Ne(e,t,s){const i=String(t??"").trim();if(!i)return"";const r=Me[e];if(!r)return i;if(ze.has(e))return i.replace(/[a-z_]+/g,e=>{const t=De(r,e,s);return null===t?e:t});const a=De(r,i.toLowerCase(),s);if(null!==a)return a;if("tariff_provider"===e){const e=Ce[i.toLowerCase()];if(e)return e}return i}const Ee="—";function Fe(e){if(!e)return!0;const t=e.state;return null==t||("unavailable"===t||"unknown"===t||""===t||Number.isNaN(parseFloat(t)))}function Ae(e){if(Fe(e))return null;const t=parseFloat(e.state);return Number.isFinite(t)?t:null}function Te(e,t=0){return null!=e&&Number.isFinite(e)?e.toFixed(t):Ee}function Oe(e,t=!1){return t||null==e||Number.isNaN(e)?{label:Ee,fraction:0,known:!1}:{label:`${e.toFixed(0)}%`,fraction:Math.max(0,Math.min(1,e/100)),known:!0}}function Be(e,t){let s=!1;for(const i of t||[]){const t=i?e?.[i]:void 0;if(t&&(s=!0,"unavailable"!==t.state&&"unknown"!==t.state))return!1}return s}function Ie(e,t){let s=0;for(const i of t||[])if(i&&(s++,null!=Ae(e?.[i])))return!1;return s>0}const Le=a` ha-card { background-color: var(--card-background-color); color: var(--primary-text-color); @@ -15,7 +15,7 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow ha-card:hover { box-shadow: var(--ha-card-box-shadow, 0 4px 16px rgba(0, 0, 0, 0.12)); } -`;class Le extends ce{constructor(){super(),this._hass=null,this._config=null,this._lang=null,this._localizeReady=!1,this._prevVals={},this._frozenEntities={},this._holdTimers={},this._holdIntervals={},this._cachedTheme=null,this._updateTimer=null}set hass(e){this._hass,this._hass=e;const t=e?.language,s="function"==typeof semLocalize;let i=!1;if((t!==this._lang||s&&!this._localizeReady)&&(this._lang=t,this._localizeReady=s,i=!0),this._isFrozen()&&!i)return;const r=this.constructor.watchedEntities||[];if(!i&&Re(e.states,r))return;let a=!1;for(const t of r)if(this._prevVals[t]!==this._watchedValue(e,t)){a=!0;break}if(a||i){for(const t of r)this._prevVals[t]=this._watchedValue(e,t);this._scheduleUpdate()}}_watchedValue(e,t){const s=e.states[t],i=this.constructor.watchedAttributes?.[t];return i?JSON.stringify([s?.state??null,...i.map(e=>s?.attributes?.[e]??null)]):s?.state}get hass(){return this._hass}_fireMoreInfo(e){e&&this.dispatchEvent(new CustomEvent("hass-more-info",{detail:{entityId:e},bubbles:!0,composed:!0}))}_scheduleUpdate(){this._updateTimer||(this._updateTimer=setTimeout(()=>{this._updateTimer=null,this.requestUpdate()},16))}_theme(){const e=getComputedStyle(document.documentElement).getPropertyValue("--primary-background-color").trim();return this._cachedTheme&&this._cachedThemeKey===e||(this._cachedThemeKey=e,this._cachedTheme=ve()),this._cachedTheme}_t(e){return e?"function"==typeof semLocalize?semLocalize(e,this._hass?.language):e:""}_valLabel(e){const t=this._hass?.states?.[`${this._prefix||"sensor.sem_"}${e}`];return Ee(e,t&&"unavailable"!==t.state&&"unknown"!==t.state?t.state:"",e=>this._t(e))}_forecastProviderLabel(e){if(!e)return"";return{solcast:"Solcast",forecast_solar:"Forecast.Solar",open_meteo:"Open-Meteo",helios:"Helios",custom:this._t("custom")||"Custom"}[e]||e}_state(e,t=null){const s=this._frozenEntities[e];return s?s.value:Te(this._hass?.states[e])??t}_stateStr(e){const t=this._frozenEntities[e];if(t)return String(t.value);const s=this._hass?.states[e];return s&&"unavailable"!==s.state&&"unknown"!==s.state?s.state:""}_unitOf(e){const t=this._hass?.states[e];return t&&t.attributes&&t.attributes.unit_of_measurement||""}_kgUnit(e){return this._unitOf(`${this._prefix}${e}`)||"kg"}_pcEntity(e,t,s){const i=this._hass?.states||{},r=new RegExp(`^${e}\\.sem_charger_.+_${t}$`);let a=Object.keys(i).filter(e=>r.test(e));return"night_charging"===t&&(a=a.filter(e=>!e.endsWith("_smart_night_charging"))),a.sort(),a.length?a[0]:s}_stateAttrs(e){return this._hass?.states[e]?.attributes||{}}_freezeEntity(e,t){const s=this._frozenEntities[e];s?.timer&&clearTimeout(s.timer),this._frozenEntities[e]={value:t,timer:setTimeout(()=>{delete this._frozenEntities[e],this.requestUpdate()},1500)}}_isFrozen(){return Object.keys(this._frozenEntities).length>0}async _callService(e,t,s){if(this._hass)try{await this._hass.callService(e,t,s)}catch(e){console.error(`[SEM ${this.tagName}]`,e)}}_setNumber(e,t){const s=this._hass?.states[e];if(!s)return;const i=parseFloat(s.attributes.min)||0,r=parseFloat(s.attributes.max)||100,a=Math.max(i,Math.min(r,t));this._freezeEntity(e,a),this.requestUpdate(),this._callService("number","set_value",{entity_id:e,value:a})}_stepNumber(e,t){const s=this._hass?.states[e];if(!s)return;const i=this._frozenEntities[e],r=i?i.value:parseFloat(s.state)||0,a=parseFloat(s.attributes.step)||1;this._setNumber(e,r+t*a)}_toggleSwitch(e){const t=this._hass?.states[e];if(!t)return;const s="on"===t.state?"off":"on";this._freezeEntity(e,s),this.requestUpdate();const i="on"===t.state?"turn_off":"turn_on";this._callService("switch",i,{entity_id:e})}_selectOption(e,t){this._freezeEntity(e,t),this.requestUpdate(),this._callService("select","select_option",{entity_id:e,option:t})}_pressButton(e){this._callService("button","press",{entity_id:e})}_startHold(e,t){this._stopHold(e),this._holdTimers[e]=setTimeout(()=>{this._holdIntervals[e]=setInterval(()=>{this._stepNumber(e,t)},150)},400)}_stopHold(e){clearTimeout(this._holdTimers[e]),clearInterval(this._holdIntervals[e]),delete this._holdTimers[e],delete this._holdIntervals[e]}updated(){if(window._semDebug){this.style.outline="2px solid red",this.style.outlineOffset="-2px",clearTimeout(this._debugFlashTimer),this._debugFlashTimer=setTimeout(()=>{this.style.outline="",this.style.outlineOffset=""},200);const e=this.tagName.toLowerCase();window._semRenderLog||(window._semRenderLog={}),window._semRenderLog[e]=(window._semRenderLog[e]||0)+1}}connectedCallback(){super.connectedCallback(),window._semDebug&&console.log(`[SEM DEBUG] ${this.tagName} connectedCallback`),this.hasUpdated&&this.requestUpdate(),this._localizeReady||"function"==typeof semLocalize?"function"==typeof semLocalize&&(this._localizeReady=!0):(this._onLocalizeReady=()=>{document.removeEventListener("sem-localize-ready",this._onLocalizeReady),this._onLocalizeReady=null,this._localizeReady=!0,this._hass&&this.requestUpdate()},document.addEventListener("sem-localize-ready",this._onLocalizeReady))}disconnectedCallback(){super.disconnectedCallback(),window._semDebug&&console.log(`[SEM DEBUG] ${this.tagName} disconnectedCallback !!!`),this._onLocalizeReady&&(document.removeEventListener("sem-localize-ready",this._onLocalizeReady),this._onLocalizeReady=null),this._updateTimer&&(clearTimeout(this._updateTimer),this._updateTimer=null);for(const e of Object.keys(this._holdTimers))this._stopHold(e);for(const e of Object.keys(this._frozenEntities))clearTimeout(this._frozenEntities[e]?.timer);this._frozenEntities={}}setConfig(e){this._config=e}getCardSize(){return 4}static getStubConfig(){return{}}}ke("sem-title-card",class extends Le{static get watchedEntities(){return[]}constructor(){super(),this._renderedSubtitle=null,this._templateUnsub=null,this._templateSubbed=!1,this._templateGen=0}setConfig(e){super.setConfig(e),this._unsubTemplate(),this._renderedSubtitle=null}_hasJinja(e){return e&&(e.includes("{%")||e.includes("{{"))}set hass(e){this._hass,this._hass=e;const t=e?.language,s="function"==typeof semLocalize;(t!==this._lang||s&&!this._localizeReady)&&(this._lang=t,this._localizeReady=s,this.requestUpdate()),this.isConnected&&this._hasJinja(this._config?.subtitle)&&!this._templateSubbed&&this._subscribeTemplate()}_subscribeTemplate(){if(!this._hass?.connection||this._templateSubbed)return;this._templateSubbed=!0;const e=this._templateGen,t=this._config.subtitle;this._hass.connection.subscribeMessage(t=>{if(e!==this._templateGen)return;const s=t.result;s!==this._renderedSubtitle&&(this._renderedSubtitle=s,this.requestUpdate())},{type:"render_template",template:t,variables:{}}).then(t=>{e===this._templateGen?this._templateUnsub=t:t()}).catch(()=>{e===this._templateGen&&(this._renderedSubtitle=this._config.subtitle,this.requestUpdate())})}_unsubTemplate(){this._templateGen++,this._templateSubbed=!1,this._templateUnsub&&(this._templateUnsub(),this._templateUnsub=null)}connectedCallback(){super.connectedCallback(),this._hass&&this._hasJinja(this._config?.subtitle)&&this._subscribeTemplate()}disconnectedCallback(){super.disconnectedCallback(),this._unsubTemplate()}render(){if(!this._config)return G;const e=this._t(this._config.title_key||this._config.title||"");let t="";t=this._hasJinja(this._config.subtitle)?this._renderedSubtitle||"":this._t(this._config.subtitle||"");const s=this._theme();return H` +`;class Re extends ce{constructor(){super(),this._hass=null,this._config=null,this._lang=null,this._localizeReady=!1,this._prevVals={},this._frozenEntities={},this._holdTimers={},this._holdIntervals={},this._cachedTheme=null,this._updateTimer=null}set hass(e){this._hass,this._hass=e;const t=e?.language,s="function"==typeof semLocalize;let i=!1;if((t!==this._lang||s&&!this._localizeReady)&&(this._lang=t,this._localizeReady=s,i=!0),this._isFrozen()&&!i)return;const r=this.constructor.watchedEntities||[];if(!i&&Be(e.states,r))return;let a=!1;for(const t of r)if(this._prevVals[t]!==this._watchedValue(e,t)){a=!0;break}if(a||i){for(const t of r)this._prevVals[t]=this._watchedValue(e,t);this._scheduleUpdate()}}_watchedValue(e,t){const s=e.states[t],i=this.constructor.watchedAttributes?.[t];return i?JSON.stringify([s?.state??null,...i.map(e=>s?.attributes?.[e]??null)]):s?.state}get hass(){return this._hass}_fireMoreInfo(e){e&&this.dispatchEvent(new CustomEvent("hass-more-info",{detail:{entityId:e},bubbles:!0,composed:!0}))}_scheduleUpdate(){this._updateTimer||(this._updateTimer=setTimeout(()=>{this._updateTimer=null,this.requestUpdate()},16))}_theme(){const e=getComputedStyle(document.documentElement).getPropertyValue("--primary-background-color").trim();return this._cachedTheme&&this._cachedThemeKey===e||(this._cachedThemeKey=e,this._cachedTheme=ye()),this._cachedTheme}_t(e){return e?"function"==typeof semLocalize?semLocalize(e,this._hass?.language):e:""}_valLabel(e){const t=this._hass?.states?.[`${this._prefix||"sensor.sem_"}${e}`];return Ne(e,t&&"unavailable"!==t.state&&"unknown"!==t.state?t.state:"",e=>this._t(e))}_forecastProviderLabel(e){if(!e)return"";return{solcast:"Solcast",forecast_solar:"Forecast.Solar",open_meteo:"Open-Meteo",helios:"Helios",custom:this._t("custom")||"Custom"}[e]||e}_state(e,t=null){const s=this._frozenEntities[e];return s?s.value:Ae(this._hass?.states[e])??t}_stateStr(e){const t=this._frozenEntities[e];if(t)return String(t.value);const s=this._hass?.states[e];return s&&"unavailable"!==s.state&&"unknown"!==s.state?s.state:""}_unitOf(e){const t=this._hass?.states[e];return t&&t.attributes&&t.attributes.unit_of_measurement||""}_kgUnit(e){return this._unitOf(`${this._prefix}${e}`)||"kg"}_pcEntity(e,t,s){const i=this._hass?.states||{},r=new RegExp(`^${e}\\.sem_charger_.+_${t}$`);let a=Object.keys(i).filter(e=>r.test(e));return"night_charging"===t&&(a=a.filter(e=>!e.endsWith("_smart_night_charging"))),a.sort(),a.length?a[0]:s}_stateAttrs(e){return this._hass?.states[e]?.attributes||{}}_freezeEntity(e,t){const s=this._frozenEntities[e];s?.timer&&clearTimeout(s.timer),this._frozenEntities[e]={value:t,timer:setTimeout(()=>{delete this._frozenEntities[e],this.requestUpdate()},1500)}}_isFrozen(){return Object.keys(this._frozenEntities).length>0}async _callService(e,t,s){if(this._hass)try{await this._hass.callService(e,t,s)}catch(e){console.error(`[SEM ${this.tagName}]`,e)}}_setNumber(e,t){const s=this._hass?.states[e];if(!s)return;const i=parseFloat(s.attributes.min)||0,r=parseFloat(s.attributes.max)||100,a=Math.max(i,Math.min(r,t));this._freezeEntity(e,a),this.requestUpdate(),this._callService("number","set_value",{entity_id:e,value:a})}_stepNumber(e,t){const s=this._hass?.states[e];if(!s)return;const i=this._frozenEntities[e],r=i?i.value:parseFloat(s.state)||0,a=parseFloat(s.attributes.step)||1;this._setNumber(e,r+t*a)}_toggleSwitch(e){const t=this._hass?.states[e];if(!t)return;const s="on"===t.state?"off":"on";this._freezeEntity(e,s),this.requestUpdate();const i="on"===t.state?"turn_off":"turn_on";this._callService("switch",i,{entity_id:e})}_selectOption(e,t){this._freezeEntity(e,t),this.requestUpdate(),this._callService("select","select_option",{entity_id:e,option:t})}_pressButton(e){this._callService("button","press",{entity_id:e})}_startHold(e,t){this._stopHold(e),this._holdTimers[e]=setTimeout(()=>{this._holdIntervals[e]=setInterval(()=>{this._stepNumber(e,t)},150)},400)}_stopHold(e){clearTimeout(this._holdTimers[e]),clearInterval(this._holdIntervals[e]),delete this._holdTimers[e],delete this._holdIntervals[e]}updated(){if(window._semDebug){this.style.outline="2px solid red",this.style.outlineOffset="-2px",clearTimeout(this._debugFlashTimer),this._debugFlashTimer=setTimeout(()=>{this.style.outline="",this.style.outlineOffset=""},200);const e=this.tagName.toLowerCase();window._semRenderLog||(window._semRenderLog={}),window._semRenderLog[e]=(window._semRenderLog[e]||0)+1}}connectedCallback(){super.connectedCallback(),window._semDebug&&console.log(`[SEM DEBUG] ${this.tagName} connectedCallback`),this.hasUpdated&&this.requestUpdate(),this._localizeReady||"function"==typeof semLocalize?"function"==typeof semLocalize&&(this._localizeReady=!0):(this._onLocalizeReady=()=>{document.removeEventListener("sem-localize-ready",this._onLocalizeReady),this._onLocalizeReady=null,this._localizeReady=!0,this._hass&&this.requestUpdate()},document.addEventListener("sem-localize-ready",this._onLocalizeReady))}disconnectedCallback(){super.disconnectedCallback(),window._semDebug&&console.log(`[SEM DEBUG] ${this.tagName} disconnectedCallback !!!`),this._onLocalizeReady&&(document.removeEventListener("sem-localize-ready",this._onLocalizeReady),this._onLocalizeReady=null),this._updateTimer&&(clearTimeout(this._updateTimer),this._updateTimer=null);for(const e of Object.keys(this._holdTimers))this._stopHold(e);for(const e of Object.keys(this._frozenEntities))clearTimeout(this._frozenEntities[e]?.timer);this._frozenEntities={}}setConfig(e){this._config=e}getCardSize(){return 4}static getStubConfig(){return{}}}ke("sem-title-card",class extends Re{static get watchedEntities(){return[]}constructor(){super(),this._renderedSubtitle=null,this._templateUnsub=null,this._templateSubbed=!1,this._templateGen=0}setConfig(e){super.setConfig(e),this._unsubTemplate(),this._renderedSubtitle=null}_hasJinja(e){return e&&(e.includes("{%")||e.includes("{{"))}set hass(e){this._hass,this._hass=e;const t=e?.language,s="function"==typeof semLocalize;(t!==this._lang||s&&!this._localizeReady)&&(this._lang=t,this._localizeReady=s,this.requestUpdate()),this.isConnected&&this._hasJinja(this._config?.subtitle)&&!this._templateSubbed&&this._subscribeTemplate()}_subscribeTemplate(){if(!this._hass?.connection||this._templateSubbed)return;this._templateSubbed=!0;const e=this._templateGen,t=this._config.subtitle;this._hass.connection.subscribeMessage(t=>{if(e!==this._templateGen)return;const s=t.result;s!==this._renderedSubtitle&&(this._renderedSubtitle=s,this.requestUpdate())},{type:"render_template",template:t,variables:{}}).then(t=>{e===this._templateGen?this._templateUnsub=t:t()}).catch(()=>{e===this._templateGen&&(this._renderedSubtitle=this._config.subtitle,this.requestUpdate())})}_unsubTemplate(){this._templateGen++,this._templateSubbed=!1,this._templateUnsub&&(this._templateUnsub(),this._templateUnsub=null)}connectedCallback(){super.connectedCallback(),this._hass&&this._hasJinja(this._config?.subtitle)&&this._subscribeTemplate()}disconnectedCallback(){super.disconnectedCallback(),this._unsubTemplate()}render(){if(!this._config)return H;const e=this._t(this._config.title_key||this._config.title||"");let t="";t=this._hasJinja(this._config.subtitle)?this._renderedSubtitle||"":this._t(this._config.subtitle||"");const s=this._theme();return j`
${e}
- ${t?H`
${t}
`:G} + ${t?j`
${t}
`:H}
- `}getCardSize(){return 1}static getStubConfig(){return{title:"Section Title"}}},{type:"sem-title-card",name:"SEM Title Card",description:"Runtime-translated section header for SEM dashboard",documentationURL:"https://github.com/traktore-org/sem-community/blob/develop/docs/DASHBOARD_GUIDE.md#sem-title-card"});class Pe extends Le{static properties={...Le.properties,_available:{state:!0}};setConfig(e){if(!e?.requires||!e?.card)throw new Error('sem-require: "requires" and "card" are mandatory');this._config=e,this._available=!!customElements.get(e.requires),this._inner=null,this._available||customElements.whenDefined(e.requires).then(()=>{this._available=!0,this._inner=null,this.requestUpdate()})}set hass(e){this._hass=e,this._inner&&(this._inner.hass=e),this.requestUpdate()}async _buildInner(){if(this._inner||!this._available)return;const e=await window.loadCardHelpers();this._inner=e.createCardElement(this._config.card),this._hass&&(this._inner.hass=this._hass),this.requestUpdate()}render(){if(!this._config)return G;if(this._available)return this._inner?H`${this._inner}`:(this._buildInner(),G);const e=this._config.name||this._config.requires;return H` + `}getCardSize(){return 1}static getStubConfig(){return{title:"Section Title"}}},{type:"sem-title-card",name:"SEM Title Card",description:"Runtime-translated section header for SEM dashboard",documentationURL:"https://github.com/traktore-org/sem-community/blob/develop/docs/DASHBOARD_GUIDE.md#sem-title-card"});class We extends Re{static properties={...Re.properties,_available:{state:!0}};setConfig(e){if(!e?.requires||!e?.card)throw new Error('sem-require: "requires" and "card" are mandatory');this._config=e,this._available=!!customElements.get(e.requires),this._inner=null,this._available||customElements.whenDefined(e.requires).then(()=>{this._available=!0,this._inner=null,this.requestUpdate()})}set hass(e){this._hass=e,this._inner&&(this._inner.hass=e),this.requestUpdate()}async _buildInner(){if(this._inner||!this._available)return;const e=await window.loadCardHelpers();this._inner=e.createCardElement(this._config.card),this._hass&&(this._inner.hass=this._hass),this.requestUpdate()}render(){if(!this._config)return H;if(this._available)return this._inner?j`${this._inner}`:(this._buildInner(),H);const e=this._config.name||this._config.requires;return j`
@@ -66,25 +66,25 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow .notice ha-icon { --mdc-icon-size: 30px; color: var(--warning-color, #ffa726); } .title { font-weight: 600; margin-bottom: 4px; } .body { font-size: 13px; opacity: 0.85; line-height: 1.4; } - `;getCardSize(){return this._inner?.getCardSize?.()||3}}ke("sem-require",Pe,{type:"sem-require",name:"SEM Require Wrapper",description:"Renders a card only when its HACS dependency is installed; otherwise a friendly install notice",documentationURL:"https://github.com/traktore-org/sem-community/blob/develop/docs/DASHBOARD_GUIDE.md#sem-require",preview:!1});function Ue(e){return`${String(e.getHours()).padStart(2,"0")}:${String(e.getMinutes()).padStart(2,"0")}`}function qe({targetKw:e,usedKwh:t,allowedW:s,now:i=new Date}){const r=function(e){const t=Number(e);return!Number.isFinite(t)||t<=0?null:.25*t}(e);if(null===r)return null;const a=null==t?NaN:Number(t);if(!Number.isFinite(a))return null;const{start:o,end:n}=function(e=new Date){const t=new Date(e);t.setMinutes(15*Math.floor(t.getMinutes()/15),0,0);const s=new Date(t.getTime()+9e5);return{start:t,end:s,elapsedS:Math.max(0,(e-t)/1e3)}}(i),l=null==s?NaN:Number(s);return{label:`${Ue(o)}–${Ue(n)}`,usedKwh:a,budgetKwh:r,fraction:Math.max(0,Math.min(1,a/r)),overBudget:a>=r,allowedKw:Number.isFinite(l)?l/1e3:null}}function He(e){return!!e?.attributes?.peak_limit_unlimited}function We(e,t){if(He(e))return t;const s=parseFloat(e?.state);return Number.isFinite(s)?`${s.toFixed(1)} kW`:"—"}const je=1,Ge=80,Ke=.1,Ve=.5,Ye=5,Ze=e=>null!=e&&""!==e&&Number.isFinite(Number(e))?Number(e):null;function Xe(e){const t=Ze(e);return null===t?null:Math.round(10*Math.min(Ge,Math.max(je,t)))/10}const Qe="peak_q_label",Je="peak_q_choice_limit",et="peak_q_choice_none",tt="peak_q_sub",st="peak_q_sub_none",it=(e,t)=>"function"==typeof semLocalize?semLocalize(e,t?.language):e,rt=(e,t)=>null==e?Ne:t(e),at=e=>e.toFixed(0)+"%",ot=e=>e.toFixed(1)+" kWh",nt={home:{titleKey:"home",subtitleKey:"home_sub",color:"#5BC8D8",icon:()=>W` + `;getCardSize(){return this._inner?.getCardSize?.()||3}}ke("sem-require",We,{type:"sem-require",name:"SEM Require Wrapper",description:"Renders a card only when its HACS dependency is installed; otherwise a friendly install notice",documentationURL:"https://github.com/traktore-org/sem-community/blob/develop/docs/DASHBOARD_GUIDE.md#sem-require",preview:!1});function Pe(e){return`${String(e.getHours()).padStart(2,"0")}:${String(e.getMinutes()).padStart(2,"0")}`}function Ue({targetKw:e,usedKwh:t,allowedW:s,now:i=new Date}){const r=function(e){const t=Number(e);return!Number.isFinite(t)||t<=0?null:.25*t}(e);if(null===r)return null;const a=null==t?NaN:Number(t);if(!Number.isFinite(a))return null;const{start:n,end:o}=function(e=new Date){const t=new Date(e);t.setMinutes(15*Math.floor(t.getMinutes()/15),0,0);const s=new Date(t.getTime()+9e5);return{start:t,end:s,elapsedS:Math.max(0,(e-t)/1e3)}}(i),l=null==s?NaN:Number(s);return{label:`${Pe(n)}–${Pe(o)}`,usedKwh:a,budgetKwh:r,fraction:Math.max(0,Math.min(1,a/r)),overBudget:a>=r,allowedKw:Number.isFinite(l)?l/1e3:null}}function je(e){return!!e?.attributes?.peak_limit_unlimited}function qe(e,t){if(je(e))return t;const s=parseFloat(e?.state);return Number.isFinite(s)?`${s.toFixed(1)} kW`:"—"}const Ke=1,He=80,Ge=.1,Ve=.5,Ye=5,Ze=e=>null!=e&&""!==e&&Number.isFinite(Number(e))?Number(e):null;function Xe(e){const t=Ze(e);return null===t?null:Math.round(10*Math.min(He,Math.max(Ke,t)))/10}function Je({unlimited:e,limitKw:t}){return{limited:!e,kw:Xe(t)??Ye}}function Qe(e,t){if(!e)return{peak_limit_unlimited:!0};const s=Xe(t);return null===s?{peak_limit_unlimited:!1}:{peak_limit_unlimited:!1,target_peak_limit:s}}const et="peak_q_label",tt="peak_q_choice_limit",st="peak_q_choice_none",it="peak_q_sub",rt="peak_q_sub_none",at=(e,t)=>"function"==typeof semLocalize?semLocalize(e,t?.language):e,nt=(e,t)=>null==e?Ee:t(e),ot=e=>e.toFixed(0)+"%",lt=e=>e.toFixed(1)+" kWh",ct={home:{titleKey:"home",subtitleKey:"home_sub",color:"#5BC8D8",icon:()=>q` - `},energy:{titleKey:"energy",subtitleKey:"energy_sub",color:"#ff9800",icon:()=>W` - `},battery:{titleKey:"battery",subtitleKey:"battery_sub",color:"#4db6ac",icon:()=>W` + `},energy:{titleKey:"energy",subtitleKey:"energy_sub",color:"#ff9800",icon:()=>q` + `},battery:{titleKey:"battery",subtitleKey:"battery_sub",color:"#4db6ac",icon:()=>q` - `},ev:{titleKey:"ev_charging",subtitleKey:"ev_sub",color:"#8DC892",icon:()=>W` + `},ev:{titleKey:"ev_charging",subtitleKey:"ev_sub",color:"#8DC892",icon:()=>q` - `},control:{titleKey:"control",subtitleKey:"control_sub",color:"#96CAEE",icon:()=>W` + `},control:{titleKey:"control",subtitleKey:"control_sub",color:"#96CAEE",icon:()=>q` - `},config:{titleKey:"config_tab_title",subtitleKey:"config_tab_subtitle",color:"#8DC892",icon:()=>W` + `},config:{titleKey:"config_tab_title",subtitleKey:"config_tab_subtitle",color:"#8DC892",icon:()=>q` @@ -95,26 +95,26 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow - `},costs:{titleKey:"costs",subtitleKey:"costs_sub",color:"#f06292",icon:()=>W` + `},costs:{titleKey:"costs",subtitleKey:"costs_sub",color:"#f06292",icon:()=>q` $`},system:{titleKey:"system",subtitleKey:"system_sub",color:"#96CAEE",icon:()=>W` + font-family="'Segoe UI','Roboto',sans-serif">$`},system:{titleKey:"system",subtitleKey:"system_sub",color:"#96CAEE",icon:()=>q` - `},simulation:{titleKey:"simulation",subtitleKey:"simulation_sub",color:"#8353d1",icon:()=>W` + `},simulation:{titleKey:"simulation",subtitleKey:"simulation_sub",color:"#8353d1",icon:()=>q` - `}};ke("sem-tab-header",class extends Le{static get watchedEntities(){return[]}constructor(){super(),this._tab="home",this._prefix="sensor.sem_",this._responsiveApplied=!1,this._lastStatsKey=""}connectedCallback(){super.connectedCallback(),this._applyResponsiveContainer()}_applyResponsiveContainer(){this._responsiveApplied||requestAnimationFrame(()=>{let e=this;for(;e;){const t=e.getRootNode()?.host;if(!t)break;if("HUI-VERTICAL-STACK-CARD"===t.tagName){const e=t.parentElement;if(e&&"HUI-CARD"===e.tagName)return e.style.maxWidth="900px",e.style.margin="0 auto",e.style.display="block",void(this._responsiveApplied=!0)}e=t}})}setConfig(e){super.setConfig(e),this._tab=e.tab||"home",this._prefix=e.entity_prefix||"sensor.sem_"}set hass(e){this._hass,this._hass=e;const t=e?.language,s="function"==typeof semLocalize;let i=!1;if((t!==this._lang||s&&!this._localizeReady)&&(this._lang=t,this._localizeReady=s,i=!0),this._isFrozen()&&!i)return;const r=this._buildStatsKey(e);(r!==this._lastStatsKey||i)&&(this._lastStatsKey=r,this.requestUpdate()),this._responsiveApplied||this._applyResponsiveContainer()}_buildStatsKey(e){return e?"home"===this._tab?this._heroValues().join("|"):this._getStatValues().join("|"):""}_getState(e){const t=this._hass?.states[`${this._prefix}${e}`];return Fe(t)?null:parseFloat(t.state)}_getStatLabels(){const e=e=>it(e,this._hass),t=this._tab;return"energy"===t?[e("solar"),e("home"),e("self_use")]:"battery"===t?[e("soc"),e("power"),e("health")]:"ev"===t?[e("power"),e("today"),e("session")]:"control"===t?[e("peak"),e("devices"),e("active")]:"config"===t?[e("config_stat_chargers"),e("config_stat_heatpump"),e("config_stat_battery")]:"costs"===t?[e("cost"),e("saved"),e("net")]:"system"===t?[e("score"),e("saved"),e("co2")]:"simulation"===t?[e("simulation_days_stored"),e("simulation_selected"),e("simulation_gaps")]:["—","—","—"]}_getStatValues(){const e=this._tab,t=fe(this._hass)||"EUR",s=e=>s=>s.toFixed(e)+" "+t;if("energy"===e)return[rt(this._getState("daily_solar_energy"),ot),rt(this._getState("daily_home_energy"),ot),rt(this._getState("self_consumption_rate"),at)];if("battery"===e)return[Be(this._getState("battery_soc")).label,rt(this._getState("battery_power"),ue),rt(this._getState("battery_health_score"),at)];if("ev"===e)return[rt(this._getState("ev_power"),ue),rt(this._getState("daily_ev_energy"),ot),rt(this._getState("session_energy"),ot)];if("control"===e)return[We(this._hass?.states[`${this._prefix}target_peak_limit`],it(et,this._hass)),rt(this._getState("controllable_devices_count"),e=>e.toFixed(0)),rt(this._getState("surplus_active_devices"),e=>e.toFixed(0))];if("config"===e){const e=e=>it(e,this._hass),t=Object.keys(this._hass?.states||{}).filter(e=>e.startsWith("number.sem_charger_")&&/^number\.sem_charger_.+_minimum_current$/.test(e)).length,s=this._hass?.states[this._prefix.replace(/^sensor\./,"binary_sensor.")+"heat_pump_registered"]?.state;return[String(t),"on"===s?e("on"):void 0===s||"off"===s?e("off"):Ne,Be(this._getState("battery_soc")).label]}return"costs"===e?[rt(this._getState("daily_costs"),s(2)),rt(this._getState("daily_savings"),s(2)),rt(this._getState("daily_net_cost"),e=>(e<=0?"+":"")+Math.abs(e).toFixed(2)+" "+t)]:"system"===e?[rt(this._getState("energy_optimization_score"),e=>e.toFixed(0)),rt(this._getState("lifetime_total_savings"),s(0)),rt(this._getState("lifetime_co2_avoided"),e=>e.toFixed(0)+" "+this._kgUnit("lifetime_co2_avoided"))]:[Ne,Ne,Ne]}_heroValues(){return[this._getState("daily_solar_energy"),this._getState("solar_power")]}_renderHomeHero(){const[e,t]=this._heroValues();return H` + `}};ke("sem-tab-header",class extends Re{static get watchedEntities(){return[]}constructor(){super(),this._tab="home",this._prefix="sensor.sem_",this._responsiveApplied=!1,this._lastStatsKey=""}connectedCallback(){super.connectedCallback(),this._applyResponsiveContainer()}_applyResponsiveContainer(){this._responsiveApplied||requestAnimationFrame(()=>{let e=this;for(;e;){const t=e.getRootNode()?.host;if(!t)break;if("HUI-VERTICAL-STACK-CARD"===t.tagName){const e=t.parentElement;if(e&&"HUI-CARD"===e.tagName)return e.style.maxWidth="900px",e.style.margin="0 auto",e.style.display="block",void(this._responsiveApplied=!0)}e=t}})}setConfig(e){super.setConfig(e),this._tab=e.tab||"home",this._prefix=e.entity_prefix||"sensor.sem_"}set hass(e){this._hass,this._hass=e;const t=e?.language,s="function"==typeof semLocalize;let i=!1;if((t!==this._lang||s&&!this._localizeReady)&&(this._lang=t,this._localizeReady=s,i=!0),this._isFrozen()&&!i)return;const r=this._buildStatsKey(e);(r!==this._lastStatsKey||i)&&(this._lastStatsKey=r,this.requestUpdate()),this._responsiveApplied||this._applyResponsiveContainer()}_buildStatsKey(e){return e?"home"===this._tab?this._heroValues().join("|"):this._getStatValues().join("|"):""}_getState(e){const t=this._hass?.states[`${this._prefix}${e}`];return Fe(t)?null:parseFloat(t.state)}_getStatLabels(){const e=e=>at(e,this._hass),t=this._tab;return"energy"===t?[e("solar"),e("home"),e("self_use")]:"battery"===t?[e("soc"),e("power"),e("health")]:"ev"===t?[e("power"),e("today"),e("session")]:"control"===t?[e("peak"),e("devices"),e("active")]:"config"===t?[e("config_stat_chargers"),e("config_stat_heatpump"),e("config_stat_battery")]:"costs"===t?[e("cost"),e("saved"),e("net")]:"system"===t?[e("score"),e("saved"),e("co2")]:"simulation"===t?[e("simulation_days_stored"),e("simulation_selected"),e("simulation_gaps")]:["—","—","—"]}_getStatValues(){const e=this._tab,t=fe(this._hass)||"EUR",s=e=>s=>s.toFixed(e)+" "+t;if("energy"===e)return[nt(this._getState("daily_solar_energy"),lt),nt(this._getState("daily_home_energy"),lt),nt(this._getState("self_consumption_rate"),ot)];if("battery"===e)return[Oe(this._getState("battery_soc")).label,nt(this._getState("battery_power"),ue),nt(this._getState("battery_health_score"),ot)];if("ev"===e)return[nt(this._getState("ev_power"),ue),nt(this._getState("daily_ev_energy"),lt),nt(this._getState("session_energy"),lt)];if("control"===e)return[qe(this._hass?.states[`${this._prefix}target_peak_limit`],at(st,this._hass)),nt(this._getState("controllable_devices_count"),e=>e.toFixed(0)),nt(this._getState("surplus_active_devices"),e=>e.toFixed(0))];if("config"===e){const e=e=>at(e,this._hass),t=Object.keys(this._hass?.states||{}).filter(e=>e.startsWith("number.sem_charger_")&&/^number\.sem_charger_.+_minimum_current$/.test(e)).length,s=this._hass?.states[this._prefix.replace(/^sensor\./,"binary_sensor.")+"heat_pump_registered"]?.state;return[String(t),"on"===s?e("on"):void 0===s||"off"===s?e("off"):Ee,Oe(this._getState("battery_soc")).label]}return"costs"===e?[nt(this._getState("daily_costs"),s(2)),nt(this._getState("daily_savings"),s(2)),nt(this._getState("daily_net_cost"),e=>(e<=0?"+":"")+Math.abs(e).toFixed(2)+" "+t)]:"system"===e?[nt(this._getState("energy_optimization_score"),e=>e.toFixed(0)),nt(this._getState("lifetime_total_savings"),s(0)),nt(this._getState("lifetime_co2_avoided"),e=>e.toFixed(0)+" "+this._kgUnit("lifetime_co2_avoided"))]:[Ee,Ee,Ee]}_heroValues(){return[this._getState("daily_solar_energy"),this._getState("solar_power")]}_renderHomeHero(){const[e,t]=this._heroValues();return j`
-
${null==e?Ne:H`${e.toFixed(1)}kWh`}
-
${it("todays_solar_production",this._hass)}
+
${null==e?Ee:j`${e.toFixed(1)}kWh`}
+
${at("todays_solar_production",this._hass)}
- ${t>0?H` + ${t>0?j`
${ue(t)} -
`:G} -
`}render(){if(!this._hass||!this._config)return G;const e=nt[this._tab]||nt.home,t=this._config.title||it(e.titleKey,this._hass),s=this._config.subtitle||it(e.subtitleKey,this._hass),i=e.color,r=this._getStatLabels(),a=this._getStatValues(),o=this._theme(),n=o.isDark?i:`color-mix(in srgb, ${i} 65%, black)`,l=o.isDark?`0 0 12px ${i}40`:"none",c=o.isDark?.3:.18;return H` +
`:H} + `}render(){if(!this._hass||!this._config)return H;const e=ct[this._tab]||ct.home,t=this._config.title||at(e.titleKey,this._hass),s=this._config.subtitle||at(e.subtitleKey,this._hass),i=e.color,r=this._getStatLabels(),a=this._getStatValues(),n=this._theme(),o=n.isDark?i:`color-mix(in srgb, ${i} 65%, black)`,l=n.isDark?`0 0 12px ${i}40`:"none",c=n.isDark?.3:.18;return j`
- ${ct.map(e=>H` + ${dt.map(e=>j` `:""}\n
\n \n \n
\n `}function Yr(e){const t=t=>e.querySelector("#"+t);return{type:t("cfg-type")?.value||"switch",entity:(t("cfg-entity")?.value||"").trim(),service:(t("cfg-service")?.value||"").trim(),param:(t("cfg-param")?.value||"").trim()||"current",shed_value:t("cfg-shed")?.value,restore_value:t("cfg-restore")?.value}}function Zr(e){const t=String(e?.timestamp||"");return/^\d{4}-\d{2}/.test(t)?t.slice(0,7):""}function Xr(e,t){const[s,i]=e.split("-").map(Number),r=12*s+(i-1)+t,a=Math.floor(r/12);return`${a}-${String(r-12*a+1).padStart(2,"0")}`}function Jr(e,t){return(e||[]).filter(e=>Zr(e)===t)}const Qr=e=>{const t=Number(e);return Number.isFinite(t)?t:null};function ea(e){let t=0,s=0,i=0,r=0,a="",n=0,o=0;for(const l of e||[]){const e=Qr(l.energy_kwh)||0;t+=e,s+=e*((Qr(l.solar_share_pct)||0)/100);const c=Qr(l.cost);null!==c&&""!==l.cost&&void 0!==l.cost&&null!==l.cost&&(i+=c,r+=1),!a&&l.currency&&(a=String(l.currency)),null!==l.co2_kg&&void 0!==l.co2_kg&&""!==l.co2_kg&&null!==Qr(l.co2_kg)&&(n+=Qr(l.co2_kg),o+=1)}return{count:(e||[]).length,kwh:Math.round(10*t)/10,solarPct:t>0?Math.round(s/t*100):0,cost:r?Math.round(100*i)/100:null,co2:o?Math.round(10*n)/10:null,currency:a}}function ta(e,t="en"){const s=String(e?.timestamp||""),i=String(e?.end||""),r=s.slice(0,10);let a="";if(/^\d{4}-\d{2}-\d{2}$/.test(r)){const e=new Date(`${r}T12:00:00Z`);let s="";try{s=e.toLocaleDateString(t||"en",{weekday:"short",timeZone:"UTC"})}catch(t){s=e.toLocaleDateString("en",{weekday:"short",timeZone:"UTC"})}a=`${s} ${r.slice(8,10)}.${r.slice(5,7)}`}const n=s.length>=16?s.slice(11,16):"",o=i.length>=16?i.slice(11,16):"",l=Qr(e?.solar_share_pct),c=null==e?.cost||""===e?.cost?null:Qr(e.cost),_=null==e?.co2_kg||""===e?.co2_kg?null:Qr(e.co2_kg),d=Qr(e?.duration_min),p=Qr(e?.energy_kwh);return{day:a,span:n&&o?`${n}–${o}`:n,kwh:null===p?"":p.toFixed(1),solar:null===l?"":`${Math.round(l)}%`,solarHigh:null!==l&&Math.round(l)>=80,cost:null===c?"":c.toFixed(2),co2:null===_?"":_.toFixed(1),min:null===d?"":String(Math.round(d))}}const sa="min_plus_solar",ia=[0,15,30,45,60,90,120],ra=[10,20,30,40,50,60,70,80,90],aa=["mon","tue","wed","thu","fri","sat","sun"];function na(e){const t=[];for(let s=0;s<3;s++)t.push(j``);return j`${t}`}function oa(e,t){const s=(e._attrs("sensor.sem_charging_state").per_charger_phases||{})[t]||{};return"stopping"===s.switch_state?e._t("phase_status_stopping"):"settling"===s.switch_state?e._t("phase_status_settling"):s.active_phases?e._tf("phase_status_measured",{n:s.active_phases}):s.believed_phases?e._tf("phase_status_believed",{n:s.believed_phases}):e._t("phase_status_unknown")}function la(e,t){const s=t.cid,i=t=>e._t(t),r=(t,s)=>e._tf(t,s),a=e._obs(),n=e=>"sensor.sem_charger_"+s+"_"+e,o=(t,i)=>e._chargerEntity(t,s,i),l=e._attrs("sensor.sem_charging_state"),c=o("select","charge_mode"),_=c?e._str(c):"",d=c?e._attrs(c):{},p=Array.isArray(d.options)?d.options:[],h=p.filter(e=>"off"!==e&&"always_max"!==e),u="off"===_?"off":"always_max"===_?"always":"smart",m=!1!==d.tariff_available,g=Array.isArray(d.modes_needing_tariff)?d.modes_needing_tariff:[],f=[];p.includes("off")&&f.push({key:"off",label:i(si("off"))}),h.length&&f.push({key:"smart",label:i("next_smart"),icon:"mdi:chevron-down"}),p.includes("always_max")&&f.push({key:"always",label:i(si("always_max"))});const y="smart"===u&&h.length>1?e._seg([...h].sort((e,t)=>ti.indexOf(e)-ti.indexOf(t)).map(e=>({key:e,label:i(si(e))||e,disabled:!m&&g.includes(e)})),_,t=>{e._smartLast={...e._smartLast||{},[s]:t},e._selectOption(c,t)},{small:!0,disabled:a}):H,b=e._num("number.sem_battery_buffer_soc"),v=e._num("number.sem_battery_priority_soc"),x={buffer:null===b?"—":Math.round(b),priority:null===v?"—":Math.round(v)},$=_?r("charge_mode_hint_"+_+"_overnight",x):"",w=o("select","ev_target_type")&&e._str(o("select","ev_target_type"))||"kwh",k=o("number","soc"===w?"target_soc":"daily_ev_target"),S=k?e._num(k):null,C="solar_plus_battery"===_&&null!==S&&S>0?r("next_floor_uses_grid",{value:"soc"===w?Math.round(S)+" %":e._kwh(S)}):"",M=_?[r("charge_mode_hint_"+_+"_surplus",x),C||($&&!$.startsWith("charge_mode_hint_")?i("next_at_night")+": "+$:"")].filter(Boolean).join(" "):"",z="solar_plus_cheap"===_&&l.ev_next_cheap_window?e._hhmm(l.ev_next_cheap_window):"",D="off"===_&&d.paused_until?e._hhmm(d.paused_until):"",N=d.pause_resume_mode?i("charge_mode_"+d.pause_resume_mode)||d.pause_resume_mode:"",E=e._powerW(n("power")),F=e._num(n("commanded_current")),A=function(e,t){const s=(e._attrs("sensor.sem_charging_state").per_charger_phases||{})[t]||{},i=Number(s.active_phases||s.believed_phases);if(Number.isFinite(i)&&i>0)return i;const r=e._num("number.sem_charger_"+t+"_ev_phases");return Number.isFinite(r)&&r>0?r:null}(e,s),T=e._num(n("daily_energy")),O=function(e,t){const s=e=>"sensor.sem_charger_"+t+"_flow_"+e+"_to_ev_energy",i=e._num(s("solar"));if(null===i)return null;const r=i+(e._num(s("grid"))||0)+(e._num(s("battery"))||0);return r>0?i/r*100:null}(e,s),B=e._num(n("session_energy_external")),I=null!==B&&B>0?B:e._num(n("session_energy")),L=e._num(n("session_solar_share")),R=[[i("next_power"),e._kw(E),j`${null===F?"":Math.round(F)+" A"}${na(A)}${null===A?"":" "+r("next_n_ph",{n:A})}`],[i("next_today"),e._kwh(T),null===O?"":r("next_sun_pct",{pct:Math.round(O)})],[i("next_session_cap"),e._kwh(I),null===L?"":r("next_sun_pct",{pct:Math.round(L)})]],W=o("select","ev_target_type"),P=W&&e._str(W)||"kwh",U=W&&e._attrs(W).options||["kwh"],q="soc"===P,K=q?"%":"kWh",G=ii(s,P),V=e._has(G.atLeast)?G.atLeast:null,Y=e._has(G.upTo)?G.upTo:null,Z=V?e._num(V):null,X=Y?e._num(Y):null,{soc:J,isEstimate:Q}=Bi(e._num(n("vehicle_soc")),e._num("sensor.sem_vehicle_soc"),e._num(n("estimated_soc")),e._chargers.length),ee=e._num("number.sem_charger_"+s+"_ev_battery_capacity_kwh"),te=e._num("number.sem_charger_"+s+"_ev_kwh_per_100km"),se=q?J:T,ie=function({have:e,min:t,max:s,isSoc:i}){const r=Number.isFinite(e)?Math.max(0,e):null,a=Number.isFinite(t)?Math.max(0,t):null,n=Number.isFinite(s)&&s>=(a??0)?s:a,o=i?100:Math.max(n??0,r??0,1),l=e=>Math.max(0,Math.min(100,e/o*100));return{fill:null===r?0:l(r),bandLeft:null===a?null:l(a),bandWidth:null===a?0:Math.max(0,l(n??a)-l(a)),flagLeft:null===a?null:l(a)}}({have:se,min:Z,max:X,isSoc:q}),re=o("time","target_time"),ae=re?e._hhmm(e._str(re)):"",ne=t=>null===t?"—":q?Math.round(t):e._fmt(t,1),oe=null===ie.flagLeft?H:j`
${ae?ae+" · ":""}${r("next_at_least_x",{x:ne(Z)+" "+K})}
`,le=j`
+
+ ${null===ie.bandLeft?H:j`
`} + ${oe}
`,ce=null!==J&&ee&&te?Math.round(J/100*ee/te*100):null,_e=null===se||null===Z?null:q?Math.max(0,(Z-J)/100*(ee||0)):Math.max(0,Z-T),de=null!==_e&&te?Math.round(_e/te*100):null,pe=e._attrs("sensor.sem_energy_plan"),he=Array.isArray(pe.blocks)?Ms(pe.blocks,e=>e==="ev:"+s):0,ue=(l.per_charger_departure||{})[s]||{},me=ue.by_weekday||{},ge=Object.keys(me).length>0,fe=ae||ue.default||"",ye=re?e._inline("time:"+s,ae||"—",()=>e._timeEdit("time:"+s,{value:ae,onCommit:t=>e._setTime(re,t)})):j`${ae||"—"}`,be=e._val(i(ge?"ev_dep_most_days":"next_every_day"),()=>e._toggleDep(s),{disabled:a}),ve=`${i(Xs)} ${ne(Z)} · ${i(Js)} ${ne(X)} ${K}`,xe=V&&Y?e._inline("target:"+s,ve,()=>e._pairEdit("target:"+s,{a:{value:Z,min:e._attrs(V).min,max:e._attrs(V).max,step:e._attrs(V).step},b:{value:X,min:e._attrs(Y).min,max:e._attrs(Y).max,step:e._attrs(Y).step},unit:K,onCommit:(t,s)=>{null!==t&&e._setNumber(V,t),null!==s&&e._setNumber(Y,s)}})):j`${ve}`,$e=U.length>1?e._val(q?i("next_soc"):"kWh",()=>e._selectOption(W,q?"kwh":"soc"),{disabled:a}):j`${q?i("next_soc"):"kWh"}`,we=j`
+
${i("next_charge")}
${null===J?"—":(Q?"~":"")+Math.round(J)+" %"}
+
${null===J?i("next_no_soc"):j`${null===ce?"":r("next_km",{km:ce})+" · "}${i(Q?"next_estimate":"next_measured")}`}
+
${i(ei)}
${ye}
+
${be}${he>0?" · "+r("next_kwh_planned",{kwh:e._fmt(he,1)}):""}
+
${i(Qs)}
${xe}
+
${$e} ↔ ${q?"kWh":i("next_soc")}${de?" · "+r("next_plus_km",{km:de}):""}
+
`,ke=[],Se="button.sem_charger_"+s+"_pause_charging";if(e._has(Se)){const t="select.sem_pause_duration",s=e._has(t)?e._str(t):"",r=e._has(t)&&e._attrs(t).options||[];ke.push(e._btn(i("pause_charging"),()=>e._pressButton(Se),{small:!0,icon:"mdi:pause",disabled:a})),s&&ke.push(j`${i("next_for")} ${e._inline("pausedur",i("pause_duration_"+s)||s,()=>e._selEdit("pausedur",{value:s,options:r.map(e=>({key:e,label:i("pause_duration_"+e)||e})),onCommit:s=>e._selectOption(t,s)}))}`)}if(e._has("sensor.sem_battery_soc")){const t=l.battery_boost||null;if(!(!t||!t.active||t.charger_id!==s))ke.push(e._btn(i("next_stop_boost"),()=>e._svc("stop_battery_boost",{}),{small:!0,icon:"mdi:stop",disabled:a})),ke.push(j`${r("next_boost_left",{kwh:e._fmt(t.remaining_kwh,1),floor:Math.round(Number(t.floor_soc)||0)})}`);else if(!1===ue.boost_allowed)ke.push(e._pill(i("ev_boost_not_permitted"),"warn",{icon:"mdi:battery-lock"}));else{const t=l.battery_boost_preview||{},r=(e._boostFloor||{})[s]??Math.round(Number(t.default_floor??50));ke.push(e._btn(i("next_boost_to"),()=>e._svc("start_battery_boost",{charger_id:s,floor_soc:r}),{small:!0,icon:"mdi:home-battery-outline",disabled:a})),ke.push(j`${e._inline("boost:"+s,r+" %",()=>e._selEdit("boost:"+s,{value:String(r),options:(ra.includes(r)?ra:[...ra,r].sort((e,t)=>e-t)).map(e=>({key:String(e),label:e+" %"})),onCommit:t=>{e._boostFloor={...e._boostFloor||{},[s]:Number(t)}}}))}`)}}ke.push(e._btn(i("next_sessions"),()=>e._go("/sem-next/insights#car"),{small:!0,icon:"mdi:history"}));const Ce=Xi(me,fe).map(t=>(t.from===t.to?e._dayName(t.from):e._dayName(t.from)+" – "+e._dayName(t.to))+" "+t.time).join(" · "),Me=ue.next?e._hhmm(ue.next):"",ze=e._depOpen&&e._depOpen[s]?j`
+ ${aa.map((t,i)=>j``)} +
${r("next_dep_help",{time:fe||"—"})}
`:H,De=!!ue.one_block,Ne=Number(ue.late_min)||0,Ee=ia.includes(Ne)?ia:[...ia,Ne].sort((e,t)=>e-t),Fe=[];Fe.push(e._row("mdi:calendar-repeat",i(ei),Ce||fe||"—",Me?r("next_next_departure",{time:Me}):ge?i("ev_dep_most_days"):"",{onClick:()=>e._toggleDep(s)})),ze!==H&&Fe.push(ze),Fe.push(e._row("mdi:view-agenda",i("ev_dep_one_block"),e._val(i(De?"next_on":"next_off"),()=>e._svc("set_charger_departure",{charger_id:s,one_block:!De}),{disabled:a}),j`${i("ev_dep_late_charge")}: ${e._inline("late:"+s,Ne?r("ev_dep_minutes",{n:Ne}):i("ev_dep_late_off"),()=>e._selEdit("late:"+s,{value:String(Ne),options:Ee.map(e=>({key:String(e),label:e?r("ev_dep_minutes",{n:e}):i("ev_dep_late_off")})),onCommit:t=>e._svc("set_charger_departure",{charger_id:s,late_charge_min:Number(t)})}))}`));const Ae=o("select","phase_mode");if(Ae||null!==A){const t=Ae?e._str(Ae):"",r=Ae?e._attrs(Ae).options||["auto","1","3"]:[];Fe.push(e._row("mdi:sine-wave",i("next_phases"),Ae?e._seg(r.map(e=>({key:e,label:i("phase_mode_"+e)||e})),t,t=>e._selectOption(Ae,t),{small:!0,disabled:a}):oa(e,s),Ae?oa(e,s):""))}const Te=[],Oe="number.sem_charger_"+s+"_ev_battery_capacity_kwh",Be="number.sem_charger_"+s+"_minimum_current",Ie="number.sem_charger_"+s+"_maximum_current",Le="number.sem_charger_"+s+"_ev_kwh_per_100km",Re="number.sem_charger_"+s+"_ev_phases";if(e._has(Oe)&&Te.push(e._val(e._kwh(e._num(Oe),0),()=>e._moreInfo(Oe))),e._has(Be)||e._has(Ie)){const t=e._has(Be)?e._fmt(e._num(Be),0):"—",s=e._has(Ie)?e._fmt(e._num(Ie),0):"—";Te.push(e._val(t+" – "+s+" A",()=>e._moreInfo(e._has(Be)?Be:Ie)))}if(e._has(Le)&&Te.push(e._val(e._fmt(e._num(Le),1)+" kWh/100 km",()=>e._moreInfo(Le))),Te.length&&Fe.push(e._row("mdi:tune",i("next_set_once"),j`${Te.map((e,t)=>j`${t?" · ":""}${e}`)}`,j`${i("next_set_once_sub")}${e._has(Re)?j` · ${e._val(r("next_n_phases",{n:e._fmt(e._num(Re),0)}),()=>e._moreInfo(Re))}`:""}`,{more:!0})),e._has(n("estimated_soc"))){const t=e._num(n("estimated_soc")),s=e._attrs(n("estimated_soc")),a=Date.parse(s.vehicle_soc_last_at||""),o=Number.isFinite(a)&&null!=s.vehicle_soc_last?r("next_soc_est_sub",{soc:Math.round(Number(s.vehicle_soc_last)),age:Math.round((Date.now()-a)/6e4)}):i("next_soc_est_learning");Fe.push(e._row("mdi:school-outline",i("next_soc_estimate"),null===t?"—":"~"+Math.round(t)+" %",o,{more:!0,onClick:()=>e._moreInfo(n("estimated_soc"))}))}const We=(e._sess||{})[s];if(We&&Array.isArray(We.rows)){const t=ea(Jr(We.rows,function(){const e=new Date;return e.getFullYear()+"-"+String(e.getMonth()+1).padStart(2,"0")}()));Fe.push(e._row("mdi:chart-bar",i("next_this_month"),r("next_n_sessions",{n:t.count})+" · "+e._kwh(t.kwh),j`${r("next_sun_pct",{pct:t.solarPct})}${null===t.cost?"":" · "+e._money(t.cost)} · ${e._val(i("sessions_xlsx"),()=>e._downloadXlsx(s))}`,{more:!0}))}else Fe.push(e._row("mdi:chart-bar",i("next_this_month"),"…","",{more:!0}));if(1===e._chargers.length&&e._has("sensor.sem_lifetime_ev_energy")){const t=e._num("sensor.sem_lifetime_ev_energy"),s=e._num("sensor.sem_lifetime_ev_cost"),a=e._num("sensor.sem_lifetime_ev_solar_share"),n=e._num("sensor.sem_lifetime_ev_sessions"),o=[];null!==a&&o.push(r("next_sun_pct",{pct:Math.round(a)})),t&&null!==s&&o.push(r("next_per_kwh",{v:e._money(s/t)})),null!==n&&o.push(r("next_n_sessions",{n:Math.round(n)})),Fe.push(e._row("mdi:all-inclusive",i("next_lifetime"),e._kwh(t,0)+(null===s?"":" · "+e._money(s)),o.join(" · "),{more:!0}))}return j` + ${f.length?e._seg(f,u,t=>{c&&("off"===t?e._selectOption(c,"off"):"always"===t?e._selectOption(c,"always_max"):(()=>{const t=(e._smartLast||{})[s],i=h.includes(sa)?sa:h[0];e._selectOption(c,h.includes(t)?t:i)})())},{disabled:a}):H} + ${y!==H?j`
${y}
`:H} + ${M||D?j`
${D?r("next_paused_until",{time:D,mode:N})+" ":""}${"smart"===u?j`${i("next_smart_is")} ${i("charge_mode_"+_)||_} · `:""}${M}${z?" · "+r("next_next_cheap",{time:z}):""}
`:H} + ${e._stats(R)} + ${le} + ${we} +
${ke}
+
${Fe}
`}const ca=Object.fromEntries(Ot.map(e=>[e.id,e.entity])),_a=["auto","self_consumption","allow_arbitrage"],da="sensor.sem_battery_spendable_kwh";function pa(e,t){const s=t=>e._t(t),i=(t,s)=>e._tf(t,s),r=e._obs(),a=e._num.bind(e),n=e._has("select.sem_battery_mode")?"select.sem_battery_mode":null,o=n?e._str(n):"",l=n&&e._attrs(n).options||[],c=l.filter(e=>_a.includes(e)),_=function(e){return"off"===e?"off":"force_charge"===e?"charge":"force_discharge"===e?"discharge":"auto"}(o),d=[];l.includes("off")&&d.push({key:"off",label:s("charge_mode_off")}),c.length&&d.push({key:"auto",label:s("battery_mode_auto"),icon:c.length>1?"mdi:chevron-down":void 0}),l.includes("force_charge")&&d.push({key:"charge",label:s("next_charge")}),l.includes("force_discharge")&&d.push({key:"discharge",label:s("next_discharge")});const p="auto"===_&&c.length>1?e._seg(c.map(e=>({key:e,label:s("battery_mode_"+e)||e})),o,t=>{e._autoLast=t,e._selectOption(n,t)},{small:!0,disabled:r}):H,h=o?s("battery_mode_hint_"+o):"",u=a("sensor.sem_battery_soc"),m=e._powerW("sensor.sem_battery_power"),g=(e._str("sensor.sem_battery_status")||"").toLowerCase(),f=function(e){for(const t of["sensor.sem_battery_measured_capacity_kwh","sensor.sem_diag_battery_capacity","number.sem_battery_capacity"]){const s=e._num(t);if(null!==s&&s>0)return s}const t=Number(e._attrs(da).measured_capacity_kwh);return Number.isFinite(t)&&t>0?t:null}(e),y=null!==u&&f?u/100*f:null,b=a("sensor.sem_daily_battery_charge_energy"),v=a("sensor.sem_daily_battery_discharge_energy"),x="selling"===g?s("selling_to_grid"):null!==m&&m>50||"charging"===g?s("charging"):null!==m&&m<-50||"discharging"===g?s("discharging"):null!==m||g?s("idle"):"",$=[[s("next_charge"),null===u?"—":Math.round(u)+" %",null===y?f?e._kwh(f):"":i("next_stored_of",{stored:e._fmt(y,1),cap:e._fmt(f,1)})],[s("next_power"),null===m?"—":(m<0?"−":"")+e._kw(Math.abs(m)),x],[s("next_today"),null===b&&null===v?"—":e._fmt(b,1)+" / "+e._fmt(v,1)+" kWh",s("next_in_out")]],w={priority:ca.priority,buffer:ca.buffer,auto:ca.autostart,tonight:"sensor.sem_battery_dynamic_floor_pct"},k=It(e._hass?.states),S=function(e,t){const s=t?Number(t.battery_floor_pct):NaN;if(t&&null!==t.battery_floor_pct&&void 0!==t.battery_floor_pct&&Number.isFinite(s))return s;const i=e?e["number.sem_battery_reserve_soc"]:null,r=i?Number(i.state):NaN;return Number.isFinite(r)?r:null}(e._hass?.states,e._attrs("sensor.sem_energy_plan")),C=function({soc:e,priority:t,buffer:s,autoStart:i,floor:r,reserve:a}){const n=e=>Number.isFinite(e)?Math.max(0,Math.min(100,e)):null,o=[],l=(e,t)=>{const s=n(t);null!==s&&o.push({key:e,pct:s,value:Math.round(s)})};l("reserve",a),l("priority",t),l("buffer",s),l("auto",i),l("tonight",r),o.sort((e,t)=>e.pct-t.pct);const c=n(Math.min(t??1/0,s??1/0));return{z1:c??0,z2:n(Math.max(t??-1/0,s??-1/0))??c??0,marks:o,cur:n(e)}}({soc:u,priority:k.priority,buffer:k.buffer,autoStart:k.autostart,floor:a(w.tonight),reserve:S}),M=function(e,t,s){return[e,t,s].every(e=>null!=e&&Number.isFinite(Number(e)))?Number(s)<=0?null:Math.max(0,(Number(e)-Number(t))/100*Number(s)):null}(u,S,f);let z=-99,D=!1,N=!1;const E=C.marks.map(e=>{const t=e.pct-z<14&&!D;return z=e.pct,D=t,N=N||t,{...e,lo:t}}),F=C.marks.length||null!==C.cur?j`
+ ${E.map(e=>j`${s("next_zone_"+e.key)} ${e.value}`)} + ${null===C.cur?H:j``}
`:H,A=(t,s)=>e._val(s,()=>e._moreInfo(t)),T=[],O=Rt(e._hass?.states);T.push(j`${i(O.now.key,O.now.params)}${O.next?j` ${i(O.next.key,O.next.params)}`:H}`),e._has(w.priority)&&T.push(e._sentence(s("next_bat_sent_fill"),{priority:A(w.priority,Math.round(a(w.priority))+" %")}));const B=Bt,I=e._has(B.may_assist.entity)?B.may_assist.entity:null,L=e._has(B.assist_max.entity)?B.assist_max.entity:null,R=e._has(B.solar_gate.entity)?B.solar_gate.entity:null;if(I||L||R){const t=!I||"on"===e._str(I),i=I?e._val(s(t?"next_on":"next_off"),()=>e._toggleSwitch(I),{disabled:r}):j`${s("next_on")}`;T.push(t?e._sentence(s("next_bat_sent_assist"),{buffer:e._has(w.buffer)?A(w.buffer,Math.round(a(w.buffer))+" %"):j`—`,autostart:e._has(w.auto)?A(w.auto,Math.round(a(w.auto))+" %"):j`—`,max:L?A(L,e._kw(a(L))):j`—`,gate:R?A(R,e._fmt(a(R),0)+" W"):j`—`,assist:i}):e._sentence(s("next_bat_sent_no_assist"),{assist:i}))}null!==M&&T.push(i("next_above_reserve",{kwh:e._fmt(M,1),reserve:Math.round(S)}));const W=T.length?j`

${T.map((e,t)=>j`${t?" ":""}${e}`)}

`:H,P=[],U=e._attrs(da);if(e._has(da)&&U.phase){const t=Number(U.overnight_need_kwh),n=Number(U.expected_refill_kwh),o=Number(U.nights_sealed),l=a(da),c=[];Number.isFinite(n)&&c.push(i("next_refill",{kwh:e._fmt(n,1)})),null!==l&&c.push(i("next_spendable_kwh",{kwh:e._fmt(l,1)})),Number.isFinite(o)&&c.push(i("next_nights_learned",{n:o}));const _=e._val(s("rebuild_nights"),()=>e._svc("backfill_battery_nights",{days:365}),{disabled:r});P.push(e._row("mdi:weather-night",s("tonight"),(s("planning_phase_"+U.phase)||U.phase)+(Number.isFinite(t)?" · "+i("next_needs_kwh",{kwh:e._fmt(t,1)}):""),j`${c.join(" · ")}${"learning"===U.phase?j`${c.length?" · ":""}${_}`:""}`))}const q="sensor.sem_battery_charge_pacing",K=e._has(B.pacing.entity)?B.pacing.entity:null;if(e._has(q)||K){const t=e._attrs(q),i=t.reason_code||"none",a={no_limit_entity:"pacing_no_limit",limit_unreadable:"pacing_limit_unreadable",write_refused:"pacing_write_refused",yielded:"pacing_yielded"}[t.action],n=Number(t.cap_w),o=Number.isFinite(n)&&n>0?e._kw(n):"",l=t.full_at?e._hhmm(t.full_at):"";let c=s("next_none");a?c=s(a):"none"!==i&&"night"!==i&&(c=(t.enabled?"":s("pacing_would")+" ")+(s("pacing_"+i)||i)+(l?" · "+s("pacing_full_by")+" "+l:""));const _=K?e._val("on"===e._str(K)?s("next_on"):s("next_off"),()=>e._toggleSwitch(K),{disabled:r}):"";P.push(e._row("mdi:speedometer",s("charge_pacing"),c,j`${o}${o&&_?" · ":""}${_}`))}const G=e._has(B.may_export.entity)?B.may_export.entity:null,V=U.battery_sell_state&&"none"!==U.battery_sell_state?U.battery_sell_state:"";if(G||V){const t=U.battery_sell_until?e._hhmm(U.battery_sell_until):"",n="selling"===V?s("spend_selling")+(t?" · "+s("spend_until")+" "+t:""):"scheduled"===V?s("spend_sell_planned")+(t?" · "+t:""):s("next_none"),o=a("sensor.sem_tariff_current_export_rate"),l=G?j`${s("next_may_export")}: ${e._val("on"===e._str(G)?s("next_on"):s("next_off"),()=>e._toggleSwitch(G),{disabled:r})}`:"";P.push(e._row("mdi:transmission-tower-export",s("next_sell_window"),n,j`${null===o?"":i("next_export_rate",{rate:e._fmt(o,2)})}${null!==o&&G?" · ":""}${l}`))}const Y=e._batteries||[];if(Y.length){const t=Y.map((t,i)=>{const r="select.sem_battery_"+t+"_mode",a=e._str(r);return e._inline("bmode:"+t,s("battery_mode_"+a)||a||"—",()=>e._selEdit("bmode:"+t,{value:a,options:(e._attrs(r).options||[]).map(e=>({key:e,label:s("battery_mode_"+e)||e})),onCommit:t=>e._selectOption(r,t)}))}),r=Y.map(t=>{const s="number.sem_battery_"+t+"_reserve_soc";return e._has(s)?e._val(e._fmt(a(s),0)+" %",()=>e._moreInfo(s)):null}).filter(Boolean);P.push(e._row("mdi:swap-vertical",Y.map((e,t)=>i("next_battery_n",{n:t+1})).join(" · "),j`${t.map((e,t)=>j`${t?" · ":""}${e}`)}`,r.length?j`${s(B.reserve.key)} ${r.map((e,t)=>j`${t?" · ":""}${e}`)}`:""));const n=function(e,t){const s=e||{},i=e=>{const t=s[e],i=t?Number(t.state):NaN;return Number.isFinite(i)?i:null};return(Array.isArray(t)?t:[]).map(e=>{const t="sensor.sem_battery_"+e+"_",r=s[t+"power"],a=r&&r.attributes?String(r.attributes.unit_of_measurement||"").toLowerCase():"",n=i(t+"power");return{bid:e,soc:i(t+"soc"),powerW:null===n?null:"kw"===a?1e3*n:n,status:s[t+"status"]?String(s[t+"status"].state):""}}).filter(e=>null!==e.soc||null!==e.powerW)}(e._hass?.states,Y);n.forEach(t=>{const r=Y.indexOf(t.bid),a=null===t.powerW?"—":(t.powerW<0?"−":"")+e._kw(Math.abs(t.powerW));P.push(e._row("mdi:battery-outline",i("next_battery_n",{n:r+1}),(null===t.soc?"—":Math.round(t.soc)+" %")+" · "+a,(()=>{const e=String(t.status||"").toLowerCase(),i=e?s(e):"";return i&&i!==e?i:t.status||""})()))})}const Z=e._str("sensor.sem_forecast_source"),X=e._has("sensor.sem_forecast_trust_d1")?a("sensor.sem_forecast_trust_d1"):Number.isFinite(Number(U.forecast_trust_d1))?100*Number(U.forecast_trust_d1):null,J=e._has("sensor.sem_forecast_trust_d2")?a("sensor.sem_forecast_trust_d2"):Number.isFinite(Number(U.forecast_trust_d2))?100*Number(U.forecast_trust_d2):null;if(Z||null!==X||null!==J){const t=e=>null===e?s("learning"):Math.round(e)+" %";P.push(e._row("mdi:check-decagram-outline",s("next_evidence"),(Nt(Z)||s("next_src_forecast"))+" · "+t(X)+" / "+t(J),j`${s("next_trusted_sub")}${e._has(da)?j` · ${e._val(s("rebuild_nights"),()=>e._svc("backfill_battery_nights",{days:365}),{disabled:r})}`:""}`,{more:!0}))}const Q=a("sensor.sem_battery_health_score"),ee=a("sensor.sem_battery_cycles_estimated"),te=a("sensor.sem_battery_temperature"),se=a("sensor.sem_battery_measured_capacity_kwh"),ie=a("sensor.sem_diag_battery_capacity");if([Q,ee,te,se].some(e=>null!==e)){const t=[];null!==Q&&t.push(Math.round(Q)+" %"),null!==ee&&t.push(i("next_cycles",{n:Math.round(ee)}));const r=[];null!==te&&r.push(e._fmt(te,0)+" "+(e._unitOf("sensor.sem_battery_temperature")||"°C")),null!==se&&r.push(ie?i("next_measured_of",{cap:e._fmt(se,1),nameplate:e._fmt(ie,1)}):e._kwh(se)),P.push(e._row("mdi:heart-pulse",s("health"),t.join(" · ")||"—",r.join(" · "),{more:!0}))}return j` + ${d.length?e._seg(d,_,t=>{if(!n)return;const s={off:"off",charge:"force_charge",discharge:"force_discharge"};s[t]?e._selectOption(n,s[t]):e._selectOption(n,c.includes(e._autoLast)?e._autoLast:c[0])},{disabled:r}):H} + ${p!==H?j`
${p}
`:H} + ${h?j`
${h}
`:H} + ${e._stats($)} + ${F} + ${W} + ${P.length?j`
${P}
`:H}`}function ha(e,t){const s=(e||{})[t];if(null==s)return"—";const i=Number(s);return Number.isFinite(i)?String(i):"—"}function ua(e){const t=e&&e.anti_cycle_bounds;if(!t||null==t.min||null==t.max)return null;const s=Number(t.min),i=Number(t.max);return Number.isFinite(s)&&Number.isFinite(i)?{min:s,max:i}:null}function ma(e,t){if(""===e||null==e)return null;const s=Number(e);if(!Number.isFinite(s)||s<1)return null;const i=t?Math.min(t.max,Math.max(t.min,s)):s;return i<1?null:i}const ga={heat:"comfort_hvac_heat",cool:"comfort_hvac_cool",by_forecast:"comfort_hvac_by_forecast"};function fa(e,t){const s=!!e&&"all_in"!==e;return{vat:s,markup:s,tax:s,fee:"dynamic"===t}}const ya=e=>{if(null==e||""===e||"boolean"==typeof e)return null;const t=Number(e);return Number.isFinite(t)?t:null};function ba(e){if(null==e)return null;const t=String(e).split(",").map(e=>e.trim()).filter(e=>""!==e);if(t.length<2||t.length>3)return null;const s=[];for(const e of t){const t=e.split(":");if(2!==t.length)return null;const i=ya(t[0].trim()),r=ya(t[1].trim());if(null===i||null===r||r<=0)return null;s.push({c:i,cop:r})}s.sort((e,t)=>e.c-t.c);for(let e=1;eString(Math.round(10*e)/10);function xa(e){const t=(e||[]).filter(e=>null!==ya(e?.c)&&null!==ya(e?.cop));return t.length?[...t].sort((e,t)=>ya(e.c)-ya(t.c)).map(e=>`${va(ya(e.c))}:${va(ya(e.cop))}`).join(", "):""}function $a(e){if(!e)return null;const t=e.attributes||{},s=ya(t.cop_used);if(null===s||s<=0)return null;const i=["unknown","unavailable",""].includes(String(e.state??""));return{cop:s,source:["curve","learned"].includes(t.cop_source)?t.cop_source:"flat",outdoorC:["curve","learned"].includes(t.cop_source)?ya(t.outdoor_c):null,price:i?null:ya(e.state),unit:t.unit_of_measurement||""}}const wa=["flat","curve","learned"];function ka(e){const t=e||{},s=String(t.cop_source||"");return wa.includes(s)?s:ba(t.cop_curve)?"curve":"flat"}function Sa(e){const t=e||{},s=ka(t),i=["flat"];return(ba(t.cop_curve)||"curve"===s)&&i.push("curve"),"learned"!==t.cop_learn?.state&&"learned"!==s||i.push("learned"),i}function Ca(e,t){if(!e||"no_meter"===e.state)return"";if("looks_electric"===e.state)return t("hp_cop_looks_electric");const s=String(Math.round(ya(e.hours)||0));return"learned"===e.state?t("hp_cop_learned_from").replace("{hours}",s).replace("{tmin}",va(ya(e.tmin))).replace("{tmax}",va(ya(e.tmax))):t("hp_cop_learning").replace("{hours}",s)}const Ma=[{key:"off",label:"off",help:"help_mode_off"},{key:"peak_only",label:"peak_only",help:"help_mode_peak_only"},{key:"solar_only",label:"load_mode_sem",help:"load_mode_help_sem"},{key:"solar_battery",label:"load_mode_sem_battery",help:"load_mode_help_sem_battery"}],za=["unknown","L1","L2","L3","3ph"],Da="solar_energy_management",Na=[30,60,120];function Ea(e,t,s,i){const r=t=>e._t(t),a=(t,s)=>e._tf(t,s),n=t.id,o=t.mergedMode,l="solar_only"===o||"solar_battery"===o,c=e._seg(Ma.map(e=>({key:e.key,label:r(e.label)})),o,async s=>{if(s!==o&&(!t.override||await e._callService(Da,"plan_override_clear",{device_id:n})))for(const i of function(e,t){const s="off"===t||"peak_only"===t?t:"surplus",i=[{property:"control_mode",value:s}];return"surplus"===s&&(i.push({property:"battery_assist_enabled",value:String("solar_battery"===t)}),e&&e.goals&&"always"===e.goals.top_up_policy&&i.push({property:"top_up_policy",value:"solar_only"})),i}(t,s))e._dev(n,i.property,i.value)},{disabled:s});let _;if(t.override){const i=e._val(r("next_back"),()=>e._callService(Da,"plan_override_clear",{device_id:n}),{disabled:s}),o="skip"===t.override.action?"next_load_paused_until":"next_boost_until";_=j`
${a(o,{time:t.override.until||"—"})} · ${i}
`}else if(l){const t=e._seg(Na.map(e=>({key:e,label:a("next_n_min",{n:e})})),null,t=>e._callService(Da,"plan_override",{device_id:n,action:"force",minutes:t}),{disabled:s,small:!0});_=j`
${i?j`${r(i)} `:H}${r("next_boost_for")} ${t}
`}else _=i?j`
${r(i)}
`:H;return j`${c}${_}`}function Fa(e){const t=(Number(e)||0)/60;return t%1==0?String(t):t.toFixed(1)}function Aa(e,t){return t&&"unknown"!==t?"3ph"===t?e._t("load_phase_3ph"):t:e._t("load_phase_unknown")}function Ta(e,t){if(null==t||!Number.isFinite(Number(t)))return null;const s=e._hass?.config?.unit_system?.temperature||"°C",i="°F"===s?9*Number(t)/5+32:Number(t);return Math.round(10*i)/10+" "+s}function Oa(e,t){const s=t=>e._t(t),i=(t,s)=>e._tf(t,s),r=e._obs(),a=t.id,n=t.goals||{},o=(t,s)=>e._dev(a,t,s),l=t.kind,c=t.mergedMode,_=!t.hasControlHandle,d=!_&&("solar_only"===c||"solar_battery"===c),p=function(e){return"off"===e?"help_mode_off":"peak_only"===e?"help_mode_peak_only":"solar_battery"===e?"load_mode_help_sem_battery":"solar_only"===e?"load_mode_help_sem":""}(c)||(Ma.find(e=>e.key===c)||{}).help,h=_?H:Ea(e,t,r,p),u=t.powerEntity?e._powerW(t.powerEntity):null,m=t.isOn||null!==u&&u>10||_&&null!==u,g=m&&null!==u?e._kw(u):t.rating>0?t.ratingMeasured?j`~${e._kw(t.rating)}`:j`${e._t("rating_not_measured")}`:null===u?"—":e._kw(u),f=parseFloat(n.daily_min_runtime_min)||0,y=parseFloat(n.daily_max_runtime_min)||0,b=!_&&"band"===bs({goals:n,comfort:t.comfort}).view,v=t.progress?Number(t.progress.runtime_today_min):null,x=null!==v&&Number.isFinite(v)?d&&!b&&f>0?i("next_h_of",{h:Fa(v),min:Fa(f)}):i("next_h_today",{h:Fa(v)}):"—",$=d&&!b&&f>0?i("next_on_solar_goal",{min:Fa(f),max:y>0?Fa(y):s("uncapped")}):"",w="°F"===(e._hass?.config?.unit_system?.temperature||"°C"),k=_?{view:"none"}:bs({goals:n,comfort:t.comfort},{hasBattery:(e._rowsNow()||[]).some(e=>"battery"===e.type),toUnit:e=>w?9*Number(e)/5+32:Number(e)}),S="band"===k.view,C=S||"time"===k.view;let M=["","",""];if(S&&"heat_pump"!==l&&t.comfort&&null!=t.comfort.reading_c)M=[s("next_comfort"),Ta(e,t.comfort.reading_c)||"—",""];else if("hot_water"===l){const t=n.stop_entity&&e._has(n.stop_entity)?n.stop_entity:null,i=t?e._num(t):null,r=e._inline("stop:"+a,String(n.stop_at??"—"),()=>e._numEdit("stop:"+a,{value:n.stop_at,step:1,onCommit:e=>o("stop_at",String(e))}));M=[s("next_water"),null===i?"—":e._fmt(i,0)+" "+(e._unitOf(t)||"°C"),j`${s("next_stop_at")} ${r}`]}else if("heat_pump"===l){const t=e._str("sensor.sem_heat_pump_sg_ready_state"),r="on"===e._str("binary_sensor.sem_heat_pump_solar_boost");M=[s("next_sg_ready"),e._valLabel("heat_pump_mode")||"—",(t?i("next_state_n",{n:t}):"")+(r?" · "+s("boost"):"")]}else t.comfort&&null!=t.comfort.reading_c&&(M=[s("next_comfort"),Ta(e,t.comfort.reading_c)||"—",""]);const z=e._stats([[s("next_power"),g,s(m&&null!==u?"next_live":"next_rated_lc")+" · "+Aa(e,t.phase)],[s("next_today"),x,$],M]),D=d&&!S?function(e,t){const s=parseFloat((e||{}).daily_min_runtime_min),i=parseFloat((t||{}).runtime_today_min);return s>0&&Number.isFinite(i)?Math.max(0,Math.min(100,i/s*100)):null}(t.goals,t.progress):null,N=[],E=(t,s,i)=>e._inline(t+":"+a,s,()=>e._numEdit(t+":"+a,i));if(d){const t=E("min_h",f>0?Fa(f)+" h":s("no_target"),{value:f/60,min:0,max:24,step:.5,unit:"h",onCommit:e=>o("daily_min_runtime_min",String(Math.round(60*e)))}),l=E("max_h",y>0?Fa(y)+" h":s("uncapped"),{value:y>0?y/60:null,min:0,max:24,step:.5,unit:"h",onCommit:e=>o("daily_max_runtime_min",String(e>0?Math.round(60*e):0))}),c=e._hass?.config?.unit_system?.temperature||"°C",_=()=>e._row("mdi:cash-check",s("hq_buy"),e._seg(k.buyChoices.map(e=>({key:e,label:s(vs[e])})),k.buy,e=>{for(const s of(t=e,[{property:"battery_eligible_overnight",value:String("battery"===t)},{property:"top_up_policy",value:"grid"===t?"cheap_hours":"solar_only"}]))o(s.property,s.value);var t},{disabled:r,small:!0}),i(k.buySubKey,{keep:S?k.keep+" "+c:""})+(S&&k.buySubBatteryKey?" "+i(k.buySubBatteryKey,{keep:k.keep+" "+c}):""));if(S){const t=!k.cooling&&/^hot_water/.test(String(a)),r=t&&"°C"===c&&e._has("number.sem_hot_water_solar_target")?e._attrs("number.sem_hot_water_solar_target"):null,n=s=>{if(!t||!Number.isFinite(Number(s)))return;const i="°F"===c?5*(Number(s)-32)/9:Number(s);e._callService(Da,"set_option",{options:{hot_water_solar_target:Math.round(10*i)/10}})},l=(e,t,s)=>E(e,null==t?"—":t+" "+c,{value:t,step:.5,unit:c,onCommit:s});N.push(e._row("mdi:thermometer",s("hq_keep"),k.keepReadOnly?j`${k.keep+" "+c}`:l("comfort_target",k.keep,e=>{o("comfort_target",String(e)),n(Number(e)+k.offset)}),s(k.keys.keepSub)+(k.season?" "+s(k.season)+".":"")+(k.keepNoteKey?" "+s(k.keepNoteKey):""))),N.push(e._row(k.cooling?"mdi:thermometer-alert":"mdi:snowflake-alert",s(k.keys.floor),l("comfort_limit",k.floor,e=>{for(const t of $s(k,e).writes)o(t.property,t.value)}),s(k.keys.floorSub))),N.push(e._row("mdi:white-balance-sunny",s(k.keys.sunMax),l("sun_max",k.sunMax,t=>{const s=xs({id:a,keep:k.keep,cooling:k.cooling},t,r);if(s.error)e._showError(new Error(i(s.error,s.params)));else{for(const e of s.writes)o(e.property,e.value);s.option&&n(s.option.value)}}),s(k.keys.sunMaxSub))),N.push(_())}else if("time"===k.view)N.push(e._row("mdi:flag-outline",s("hq_run_each_day"),e._sentence(s("next_goal_value"),{min:t,max:l}),s("hq_run_each_day_sub"))),N.push(_());else{const i=function(e){const t=e||{};return t.battery_eligible_overnight?"battery":"cheap_hours"===t.top_up_policy?"grid":"off"}(n),r=e._inline("ov:"+a,s("overnight_"+i),()=>e._selEdit("ov:"+a,{value:i,options:["off","battery","grid"].map(e=>({key:e,label:s("overnight_"+e)})),onCommit:e=>{for(const s of(t=e,[{property:"battery_eligible_overnight",value:String("battery"===t)},{property:"top_up_policy",value:"grid"===t?"cheap_hours":"solar_only"}]))o(s.property,s.value);var t}}));N.push(e._row("mdi:flag-outline",s("next_goal"),e._sentence(s("next_goal_value"),{min:t,max:l}),e._sentence(s("next_finish_overnight"),{src:r})))}const d=ua(e._attrs("sensor.sem_controllable_devices_count")),p=(e,t)=>E(e,null!=n[e]&&""!==n[e]?String(n[e]):ha(n,t),{value:n[e],min:d?Math.max(1,d.min):1,max:d?d.max:void 0,step:1,onCommit:t=>{const s=ma(String(t),d);null!=s&&o(e,String(s))}});N.push(e._row("mdi:timer-outline",s("next_min_run_pause"),j`${p("min_on_time_min","min_on_effective_min")} / ${p("min_off_time_min","min_off_effective_min")} ${s("minutes_short")}`,s("next_anti_cycle_sub"),{more:C}))}if(_){const i=e._inline("ph:"+a,Aa(e,t.phase),()=>e._selEdit("ph:"+a,{value:t.phase,options:za.map(t=>({key:t,label:Aa(e,t)})),onCommit:e=>o("phase",e)}));return N.push(e._row("mdi:sine-wave",s("next_phase"),i,s("next_phase_sub"))),j` +
${s("next_measure_only")} ${e._val(s("configure"),()=>e._showConfigureModal(a,t.name),{disabled:r})}
+ ${z} +
${N}
`}const F=(e._rowsNow()||[]).filter(e=>e.id!==a&&"battery"!==e.type),A=t.dependsOn[0]||"",T=F.find(e=>e.id===A),O=e._inline("dep:"+a,T?T.name:A||s("action_none"),()=>e._selEdit("dep:"+a,{value:A,options:[{key:"",label:s("action_none")},...F.map(e=>({key:e.id,label:e.name}))],onCommit:e=>o("depends_on",e)})),B=t.blockedBy?(F.find(e=>e.id===t.blockedBy)||{}).name||t.blockedBy:"";N.push(e._row("mdi:link-variant",s("requires"),O,B?i("next_st_waiting_for",{parent:B}):s("next_requires_sub"),{more:C&&!A&&!B}));const I=e._inline("ph:"+a,Aa(e,t.phase),()=>e._selEdit("ph:"+a,{value:t.phase,options:za.map(t=>({key:t,label:Aa(e,t)})),onCommit:e=>o("phase",e)}));N.push(e._row("mdi:sine-wave",s("next_phase"),I,s("next_phase_sub"),{more:C}));const L=n.stop_entity||"",R=e._inline("stopent:"+a,L?e._name(L):s("action_none"),()=>e._entEdit("stopent:"+a,{value:L,domains:["sensor","input_number","number"],onCommit:e=>o("stop_entity",e)})),W=L?E("stopat",n.stop_at>0?String(n.stop_at)+(e._unitOf(L)?" "+e._unitOf(L):""):"—",{value:n.stop_at||null,step:1,onCommit:e=>o("stop_at",String(e))}):"",P=L&&e._has(L)?e._str(L)+(e._unitOf(L)?" "+e._unitOf(L):""):"",U=function(e){return"solar_only"===e||"solar_battery"===e}(c);U&&N.push(e._row("mdi:flag-checkered",s("next_stop_when"),R,L?j`${s("next_stop_at")} ${W}${P?" · "+i("next_now_value",{v:P}):""}`:s("next_stop_when_sub"),{more:S||!L}));const q=n.comfort_entity||"";if(U&&N.push(e._row("mdi:thermometer-lines",s("next_own_thermometer"),e._inline("thermo:"+a,q?e._name(q):s("next_thermo_device"),()=>e._entEdit("thermo:"+a,{value:q,domains:["sensor"],filter:er,onCommit:e=>o("comfort_entity",e)})),s("next_own_thermometer_sub"),{more:S||!q})),U&&"hot_water"===l&&e._has("number.sem_legionella_target_temp")){const t="number.sem_legionella_target_temp",r="number.sem_legionella_interval_hours";N.push(e._row("mdi:bacteria-outline",s("next_legionella"),e._val(e._fmt(e._num(t),0)+" "+(e._unitOf(t)||"°C"),()=>e._moreInfo(t)),e._has(r)?e._val(i("next_every_h",{h:e._fmt(e._num(r),0)}),()=>e._moreInfo(r)):"",{more:S}))}const K=parseFloat(n.comfort_target)||0,G=parseFloat(n.comfort_offset)||0,V=parseFloat(n.comfort_limit)||0,Y=K>0&&V>0,Z=e._hass?.config?.unit_system?.temperature||"°C",X=(e,t)=>E(e,t?t+" "+Z:"—",{value:t||null,step:.5,unit:Z,onCommit:t=>o(e,String(t))});if(!U||S&&d||N.push(e._row("mdi:thermometer",s("next_comfort"),Ta(e,t.comfort&&t.comfort.reading_c)||"—",j`${e._sentence(s("next_keep_sentence"),{target:X("comfort_target",K),limit:X("comfort_limit",V),offset:X("comfort_offset",G)})}
${s(t.comfort&&!1===t.comfort.may_buy?"next_keep_sub_no_buy":"next_keep_sub")}
`,{more:"time"===k.view||!Y&&!(t.comfort&&null!=t.comfort.reading_c)})),"heat_pump"===l){const r=e._has("sensor.sem_heat_pump_cost_of_heat")?e._num("sensor.sem_heat_pump_cost_of_heat"):null,a=e._plannedAt("load:"+t.id,"comfort:"+t.id);N.push(e._row("mdi:heat-pump-outline",s("next_cost_of_heat"),null===r?"—":i("next_per_kwh_heat",{v:e._money(r)}),a?i("next_hp_next_run",{time:a}):s("next_nothing_planned"))),Object.prototype.hasOwnProperty.call(n,"cop_curve")&&N.push(function(e,t,s,i){const r=t=>e._t(t),a=(t,s)=>e._tf(t,s),n="heat_pump"===t.id?$a(e._hass?.states?.["sensor.sem_heat_pump_cost_of_heat"]):null,o=n?null!==n.outdoorC?a("hp_cop_now_at",{cop:e._fmt(n.cop,1),temp:e._fmt(n.outdoorC,0)+" °C"}):a("hp_cop_now",{cop:e._fmt(n.cop,1)}):"",l=ba(s.cop_curve),c=l?xa(l):r("next_cop_fixed"),_="cop:"+t.id,d=e._inline(_,c,()=>e._textEdit(_,{value:l?xa(l):"",placeholder:"-7:2.5, 2:3.2, 12:4.2",onCommit:e=>{const t=String(e||"").trim();if(""===t)return void i("cop_curve","");const s=ba(t);s&&i("cop_curve",xa(s))}})),p=[o,r("hp_cop_curve_hint")].filter(Boolean).join(" · ");return e._row("mdi:thermometer-lines",r("hp_cop_curve_section"),d,p,{more:!0})}(e,t,n,o)),Object.prototype.hasOwnProperty.call(n,"cop_source")&&N.push(function(e,t,s,i){const r=t=>e._t(t),a=ka(s),n=Sa(s),o=r("hp_cop_source_"+a),l="copsrc:"+t.id,c=n.length>1?e._inline(l,o,()=>e._selEdit(l,{value:a,options:n.map(e=>({key:e,label:r("hp_cop_source_"+e)})),onCommit:e=>i("cop_source",e)})):o,_=Ca(s.cop_learn,r);return e._row("mdi:school-outline",r("hp_cop_source"),c,_,{more:!0})}(e,t,n,o));const l="number.sem_heat_pump_boost_offset";e._has(l)&&N.push(e._row("mdi:tune-vertical",s("next_relay_values"),e._val(i("next_boost_offset",{v:"+"+e._fmt(e._num(l),1)+" "+(e._unitOf(l)||"°C")}),()=>e._moreInfo(l)),"",{more:!0}))}else{const t=parseFloat(n.tariff_rate)||0;N.push(e._row("mdi:cash-multiple",s("next_device_tariff"),E("rate",t>0?e._money(t):s("next_house_tariff"),{value:t||null,min:0,step:.001,onCommit:e=>o("tariff_rate",String(e>0?e:0))}),"",{more:!0}))}return N.push(e._row("mdi:toggle-switch-outline",s("next_may_switch"),e._val(t.isControllable?s("next_on"):s("next_off"),()=>o("controllable",String(!t.isControllable)),{disabled:r}),j`${s("critical")}: ${e._val(t.isCritical?s("next_yes"):s("next_no"),()=>o("critical",String(!t.isCritical)),{disabled:r})}${t.controlEntity?" · "+t.controlEntity:""} · ${e._val(s("configure"),()=>e._showConfigureModal(a,t.name))}`,{more:!0})),j` + ${function(e,t){if(!(t.intent||t.comfort&&null!=t.comfort.keep_c))return H;const s=Yi(t,e._live(t)),i=e._pillText(s),r=t.intent&&t.intent.code,a=r&&!s.next?pi({reason_code:r},t=>e._t(t)):null,n=fs(s,t,{fmtTemp:t=>Ta(e,t)}),o=n?e._tf(n.key,n.params):"",l=t.comfort&&null!=t.comfort.keep_c&&"hq_st_warm"!==s.key&&"hq_st_cool"!==s.key?Ta(e,t.comfort.reading_c):null,c=l?e._tf("hq_now",{reading:l})+" ":"",_=c+i.charAt(0).toUpperCase()+i.slice(1)+".",d=[a,o].filter(Boolean).map(e=>" "+e).join("");return j`

${_}${d}

`}(e,t)} + ${S&&d?function(e,t,s){const i=Math.min(t.floor,t.keep,t.sunMax)-3,r=Math.max(t.floor,t.keep,t.sunMax)+3,a=e=>Math.max(0,Math.min(100,(e-i)/(r-i)*100)),n=e._hass?.config?.unit_system?.temperature||"°C",o=s.comfort?s.comfort.reading_c:null,l=null==o?null:"°F"===n?9*Number(o)/5+32:Number(o),c=e=>j` + ${e} ${n}`;return j`
+ ${t.cooling?j``:j``} + + ${c(t.floor)}${c(t.keep)}${c(t.sunMax)} + ${null===l?H:j``} +
`}(e,k,t):H} + ${h} + ${z} + ${null===D?H:j`
${e._track(D,"var(--c)")}
`} +
${N}
`}const Ba="solar_energy_management",Ia=["sensor.sem_controllable_devices_count","sensor.sem_energy_plan","sensor.sem_charging_state","sensor.sem_surplus_total_w","sensor.sem_surplus_distributable_w","sensor.sem_surplus_allocated_w","sensor.sem_surplus_unallocated_w","sensor.sem_surplus_active_devices","sensor.sem_surplus_total_devices","sensor.sem_consecutive_peak_15min","sensor.sem_target_peak_limit","sensor.sem_peak_margin","sensor.sem_current_vs_peak_percentage","sensor.sem_monthly_consecutive_peak","sensor.sem_available_load_reduction","sensor.sem_load_management_recommendation","sensor.sem_load_management_status","sensor.sem_battery_soc","sensor.sem_battery_power","sensor.sem_battery_status","sensor.sem_battery_spendable_kwh","sensor.sem_battery_dynamic_floor_pct","sensor.sem_battery_charge_pacing","sensor.sem_battery_measured_capacity_kwh","sensor.sem_battery_capacity_drift_pct","sensor.sem_diag_battery_capacity","sensor.sem_battery_health_score","sensor.sem_battery_cycles_estimated","sensor.sem_battery_temperature","sensor.sem_daily_battery_charge_energy","sensor.sem_daily_battery_discharge_energy","sensor.sem_forecast_source","sensor.sem_forecast_trust_d1","sensor.sem_forecast_trust_d2","sensor.sem_tariff_current_export_rate","sensor.sem_vehicle_soc","sensor.sem_heat_pump_sg_ready_state","sensor.sem_heat_pump_mode","binary_sensor.sem_heat_pump_solar_boost","sensor.sem_heat_pump_cost_of_heat","sensor.sem_lifetime_ev_energy","sensor.sem_lifetime_ev_cost","sensor.sem_lifetime_ev_solar_share","sensor.sem_lifetime_ev_sessions","select.sem_battery_mode","select.sem_pause_duration","number.sem_battery_buffer_soc","number.sem_battery_priority_soc","number.sem_battery_auto_start_soc","number.sem_battery_assist_max_power","number.sem_battery_assist_min_surplus","number.sem_battery_capacity","number.sem_legionella_target_temp","number.sem_legionella_interval_hours","number.sem_heat_pump_boost_offset","switch.sem_battery_may_assist_ev","switch.sem_battery_may_export","switch.sem_battery_charge_pacing_enabled"],La=[["sensor","power"],["sensor","commanded_current"],["sensor","daily_energy"],["sensor","session_energy"],["sensor","session_energy_external"],["sensor","session_solar_share"],["sensor","vehicle_soc"],["sensor","estimated_soc"],["sensor","flow_solar_to_ev_energy"],["sensor","flow_grid_to_ev_energy"],["sensor","flow_battery_to_ev_energy"],["select","charge_mode"],["select","ev_target_type"],["select","phase_mode"],["time","target_time"],["number","daily_ev_target"],["number","daily_ev_target_max"],["number","target_soc"],["number","target_soc_max"],["number","ev_battery_capacity_kwh"],["number","minimum_current"],["number","maximum_current"],["number","ev_kwh_per_100km"],["number","ev_phases"],["button","pause_charging"],["binary_sensor","connected"]],Ra="sem-next-devices-open";function Wa(e){try{window.localStorage.setItem(Ra,JSON.stringify(e))}catch(e){}}const Pa=a` + .cpchip { display: inline-flex; align-items: center; gap: 6px; margin: 2px 0 6px 36px; padding: 4px 10px; border-radius: 999px; + border: 1px solid var(--n-line); background: var(--n-bg2, transparent); color: var(--n-fg); font: inherit; font-size: 12.5px; cursor: pointer; max-width: calc(100% - 36px); } + .cpchip span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } + .cpchip ha-icon { --mdc-icon-size: 15px; color: var(--n-fg2); flex: 0 0 auto; } + .cpchip:focus-visible { outline: 2px solid var(--n-accent, currentColor); outline-offset: 2px; } + .addform { display: grid; grid-template-columns: minmax(0, 1fr); gap: 10px; margin-top: 6px; } + .addform label { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--n-fg2); } + .addform input.txt { padding: 8px 10px; border-radius: 10px; border: 1px solid var(--n-line); background: var(--n-card2); color: var(--n-fg); font: inherit; } + .addform .two { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; } + .addform .acts { display: flex; gap: 8px; justify-content: flex-end; flex-wrap: wrap; } + ha-entity-picker { display: block; } + .grid { align-items: start; grid-template-columns: minmax(0, 1fr); max-width: 980px; } + .dev.folded .body { display: none; } + .dev.folded .head { margin-bottom: 0; } + .dev .fold { border: 0; background: none; color: var(--n-fg3); cursor: pointer; padding: 4px; border-radius: 8px; display: inline-flex; } + .dev .fold ha-icon { transition: transform .15s; } + .dev:not(.folded) .fold ha-icon { transform: rotate(180deg); } + .reorder .dev .fold { display: none; } + .dev .name.tap { cursor: pointer; } + .period button.pill { border: 0; cursor: pointer; } + .grid.reorder { grid-template-columns: 1fr; } + .dev.dragging { opacity: .95; z-index: 20; position: relative; box-shadow: 0 10px 26px rgba(0, 0, 0, .35); cursor: grabbing; } + .dev.drop-above { box-shadow: 0 -3px 0 0 var(--n-accent); } + .dev.drop-below { box-shadow: 0 3px 0 0 var(--n-accent); } + .dev .live { font-variant-numeric: tabular-nums; white-space: nowrap; } + .edit { display: inline-flex; gap: 4px; align-items: center; flex-wrap: wrap; } + .edit input[type="number"] { width: 84px; } + .days { display: flex; flex-wrap: wrap; gap: 6px; padding: 8px 0; border-top: 1px solid var(--n-line); } + .dayin { display: flex; flex-direction: column; font-size: 11px; color: var(--n-fg3); gap: 2px; } + .dayin input { max-width: 96px; padding: 2px 6px; } + .days .help { flex: 1 0 100%; margin: 0; } + .banner { display: flex; gap: 10px; align-items: flex-start; background: var(--n-card2); border-radius: 12px; padding: 10px 12px; margin: 0 0 12px; } + .banner ha-icon { color: var(--n-warn); } + .banner.bad ha-icon { color: var(--n-bad); } +`;function Ua(e,t){if(!t)return new Date(e.getFullYear(),e.getMonth(),e.getDate());try{const s=new Intl.DateTimeFormat("en-US",{timeZone:t,year:"numeric",month:"2-digit",day:"2-digit"}),i=Object.fromEntries(s.formatToParts(e).map(e=>[e.type,e.value])),r=new Date(Number(i.year),Number(i.month)-1,Number(i.day),0,0,0,0),a=new Intl.DateTimeFormat("en-US",{timeZone:t,hour:"2-digit",minute:"2-digit",hour12:!1}).formatToParts(r),n=Number(a.find(e=>"hour"===e.type).value),o=Number(a.find(e=>"minute"===e.type).value),l=(60*n+o)%1440,c=0===l?0:l<=720?-l:1440-l;return new Date(r.getTime()+60*c*1e3)}catch(t){return new Date(e.getFullYear(),e.getMonth(),e.getDate())}}ke("sem-next-devices-card",class extends Bs{static get styles(){return[Os,Pa]}static get properties(){return{...super.properties,_edit:{state:!0},_reorder:{state:!0},_open:{state:!0},_adding:{state:!0},_addForm:{state:!0},_addBusy:{state:!0},_tick:{state:!0}}}constructor(){super(),this._edit=null,this._reorder=!1,this._open=function(){try{const e=JSON.parse(window.localStorage.getItem(Ra)||"null");return e&&"object"==typeof e?e:{}}catch(e){return{}}}(),this._adding=!1,this._addForm={},this._addBusy=!1,this._tick=0,this._work=null,this._held=null,this._holdUntil=0,this._sess={},this._sessWatch={},this._depOpen={},this._batteries=[]}_watchIds(){const e=[...super._watchIds(),...Ia],t=this._hass?.states||{};for(const s of this._chargers)for(const[i,r]of La){const a=i+".sem_charger_"+s+"_"+r;t[a]&&e.push(a)}const s=this._attrs("sensor.sem_controllable_devices_count").devices||{};for(const i of Object.values(s))if(i)for(const s of[i.power_entity,i.switch_entity,i.control&&i.control.entity,i.goals&&i.goals.stop_entity,i.goals&&i.goals.comfort_entity])s&&t[s]&&e.push(s);const i=[];for(const e of Object.keys(t)){const t=e.match(/^select\.sem_battery_(b\d+)_mode$/);t&&i.push(t[1])}i.sort(),this._batteries=i;for(const s of i){e.push("select.sem_battery_"+s+"_mode","number.sem_battery_"+s+"_reserve_soc");for(const i of["soc","power","status"])t["sensor.sem_battery_"+s+"_"+i]&&e.push("sensor.sem_battery_"+s+"_"+i)}for(const s of["sensor.sem_energy_plan","number.sem_battery_reserve_soc"])t[s]&&e.push(s);return e}disconnectedCallback(){super.disconnectedCallback(),this._drag&&this._dragEnd({type:"pointercancel"})}updated(e){if(super.updated(e),e.has("_edit")&&this._edit){const e=this.renderRoot.querySelector("[data-edit] input, [data-edit] select");e&&e.focus&&e.focus()}this._watchSessions(),this._afterRender()}_rowsNow(){if(this._reorder&&this._work)return this._work;if(this._held&&Date.now()this._chargerName(e)});for(const e of s)if("charger"!==e.type)if("battery"!==e.type)for(const s of[e.controlEntity,e.powerEntity]){const i=s&&t[s]&&t[s].attributes&&t[s].attributes.icon;if(i){e.icon=i;break}}else e.name=e.name||this._t("next_src_battery");else e.name=this._chargerName(e.cid),e.icon="mdi:ev-station";return s}_plannedAt(...e){const t=us(this._attrs("sensor.sem_energy_plan").blocks,e,Date.now());return t?this._hhmm(t.start):""}_pillText(e){const t=this._tf(e.key,e.params);return e.why?t+" · "+this._t(e.why):t}_live(e){const t=this._attrs("sensor.sem_charging_state");if("charger"===e.type){const t=e.cid,s=this._chargerEntity("select",t,"charge_mode"),i=s?this._attrs(s):{},r=s&&"off"===this._str(s)&&!!i.paused_until,a=this._chargerEntity("binary_sensor",t,"connected");return{powerW:this._powerW("sensor.sem_charger_"+t+"_power"),amps:this._num("sensor.sem_charger_"+t+"_commanded_current"),paused:r,pausedUntil:r?this._hhmm(i.paused_until):"",plannedAt:this._plannedAt("ev:"+t),connected:a?"on"===this._str(a):null}}if("battery"===e.type){const e=t.battery_boost||null;return{powerW:this._powerW("sensor.sem_battery_power"),status:this._str("sensor.sem_battery_status"),boost:!(!e||!e.active),mode:this._str("select.sem_battery_mode")}}const s=e.blockedBy?this._rowsNow().find(t=>t.id===e.blockedBy):null;return{powerW:e.powerEntity?this._powerW(e.powerEntity):null,plannedAt:this._plannedAt("load:"+e.id,"comfort:"+e.id),fmtTemp:e=>this._temp(e),parentName:s?s.name:""}}_svc(e,t){return this._hass?this._hass.callService(Ba,e,t,void 0,!1).catch(e=>this._showError(e)):Promise.resolve()}_dev(e,t,s){return this._svc("update_device_config",{device_id:e,property:t,value:s})}_showError(e){this._failed(e)}_setTime(e,t){t&&this._callService("time","set_value",{entity_id:e,time:5===t.length?t+":00":t})}_inline(e,t,s,i={}){return this._edit===e?j`${s()}`:this._val(t,()=>{this._edit=e},{disabled:this._obs()||!!i.disabled})}_keys(e){return t=>{"Enter"===t.key&&(t.preventDefault(),e(t)),"Escape"===t.key&&(t.preventDefault(),this._edit=null)}}_numEdit(e,{value:t,min:s,max:i,step:r,unit:a,onCommit:n}){const o=e=>{const t=parseFloat(e.target.value);this._edit=null,Number.isFinite(t)&&n(t)};return j`{this._edit=null}}>${a?j`${a}`:H}`}_textEdit(e,{value:t,placeholder:s,onCommit:i}){const r=e=>{const t=e.target.value;this._edit=null,i(t)};return j`{this._edit=null}}>`}_selEdit(e,{value:t,options:s,onCommit:i}){return j``}_timeEdit(e,{value:t,onCommit:s}){const i=e=>{const t=e.target.value;this._edit=null,t&&s(t)};return j`{this._edit=null}}>`}_pairEdit(e,{a:t,b:s,unit:i,onCommit:r}){const a=e=>{const t=e.currentTarget.closest("[data-edit]"),s=t?t.querySelectorAll("input"):[],i=s[0]?parseFloat(s[0].value):NaN,a=s[1]?parseFloat(s[1].value):NaN;this._edit=null,r(Number.isFinite(i)?i:null,Number.isFinite(a)?a:null)},n=e=>j``;return j`${n(t)} – ${n(s)} ${i} + ${this._btn(this._t("next_ok"),a,{small:!0})} + ${this._btn(this._t("next_cancel"),()=>{this._edit=null},{small:!0,ghost:!0})}`}_sentence(e,t){const s=String(e||""),i=/\{(\w+)\}/g,r=[];let a,n=0;for(;a=i.exec(s);)a.index>n&&r.push(s.slice(n,a.index)),r.push(t&&a[1]in t?t[a[1]]:a[0]),n=a.index+a[0].length;return n0&&0===s);i&&!r||this._loadSessions(e)}}async _loadSessions(e){const t=this._sess[e];if(t&&t.loading||!this._hass?.callWS)return;this._sess={...this._sess,[e]:{...t||{},loading:!0}};let s=[];try{const t=await this._hass.callWS({type:Ba+"/session_history",charger_id:e});s=Array.isArray(t?.rows)?t.rows:[]}catch(e){s=[]}this._sess={...this._sess,[e]:{loading:!1,rows:s}},this._tick++}async _downloadXlsx(e){if(this._hass?.callWS)try{const t=await this._hass.callWS({type:Ba+"/session_history/xlsx",charger_id:e});t?.base64&&Wr(t.filename||"sem-sessions.xlsx",t.base64,"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")}catch(e){this._showError(e)}}async _toggleAdd(){this._adding=!this._adding,this._adding&&(this._addForm={},await this._ensureEntityPicker(),this.requestUpdate())}_entityField(e,t,s,i={}){if(customElements.get("ha-entity-picker"))return j`s(String(e.detail?.value||""))}>`;const r="ents-"+t.join("-")+(i.filter?"-f":""),a=function(e,t,s=null){return Object.keys(e||{}).filter(i=>t.includes(i.split(".")[0])&&(!s||s(e[i]))).sort()}(this._hass?.states,t,i.filter);return j`s(e.target.value.trim())}> + ${a.map(e=>j``)}`}_entEdit(e,{value:t,domains:s,filter:i,onCommit:r}){return j`${this._entityField(t,s,e=>{this._edit=null,r(e)},{filter:i})} + ${t?this._btn(this._t("action_none"),()=>{this._edit=null,r("")},{small:!0,ghost:!0}):H} + ${this._btn(this._t("next_cancel"),()=>{this._edit=null},{small:!0,ghost:!0})}`}_setAdd(e,t){this._addForm={...this._addForm,[e]:t}}async _submitAdd(e){const{data:t,error:s}=function(e,t=[]){const s=String(e?.name||"").trim(),i=String(e?.entity||"").trim();if(!s)return{error:"next_add_need_name"};if(!/^[a-z_]+\.[a-z0-9_]+$/.test(i)||!Ji.includes(i.split(".")[0]))return{error:"next_add_need_switch"};const r={device_id:Qi(s,(t||[]).map(e=>e.id)),entity_id:i,name:s,priority:Math.max(1,Math.min(10,Math.round(Number(e.priority)||Math.min(10,(t||[]).length+1))))};i.startsWith("climate.")&&(r.device_type="climate");const a=Number(e.rated);Number.isFinite(a)&&a>0&&(r.rated_power=a);const n=String(e.power||"").trim();return n&&(r.power_entity_id=n),{data:r}}(this._addForm,e);if(s)this._toast(this._t(s),!0);else{this._addBusy=!0;try{await this._hass.callService(Ba,"register_surplus_device",t,void 0,!1),this._toast(this._tf("next_add_done",{name:t.name})),this._adding=!1,this._addForm={}}catch(e){this._failed(e)}finally{this._addBusy=!1}}}_addSheet(e){const t=e=>this._t(e),s=this._obs()||this._addBusy,i=this._addForm||{},r=function(e){return(e||[]).filter(e=>e&&"load"===e.type&&!e.hasControlHandle)}(e),a=r.length?j`
${r.map(e=>{const s=e.powerEntity?this._powerW(e.powerEntity):null;return this._row("mdi:power-plug-outline",e.name,this._btn(t("next_add_control_it"),()=>this._showConfigureModal(e.id,e.name),{small:!0,disabled:this._obs()}),null===s?t("next_add_measured_only"):this._kw(s)+" · "+t("next_add_measured_only"))})}
`:j`

${t("next_add_none_loose")}

`,n=j`
+ + + +
+ + +
+

${t("next_add_starts_off")}

+
+ ${this._btn(t("next_cancel"),()=>this._toggleAdd(),{ghost:!0})} + ${this._btn(t("next_add_btn"),()=>this._submitAdd(e),{primary:!0,icon:"mdi:plus",disabled:s})} +
`;return this._sec(t("next_add_device"),j` +

${t("next_add_energy_help")} ${this._link(t("next_add_open_energy"),()=>this._go("/config/energy"))}

+

${t("next_add_measured_title")}

+ ${a} +

${t("next_add_new_title")}

+ ${n}`)}_isOpen(e){if(this._all)return!0;const t=this._open[e.id];return void 0===t?(s=e.type,!this._narrow()&&"load"!==s):!!t;var s}_narrow(){try{return!(!window.matchMedia||!window.matchMedia("(max-width: 720px)").matches)}catch(e){return!1}}_openChargePlan(e){this._open={...this._open,[Pi+e]:!0},Wa(this._open),this._depOpen={...this._depOpen,[e]:!0},this._scrollTo=Pi+e,this._tick++}_chargePlanChip(e){const t=e.cid,s=this._chargerEntity("select",t,"ev_target_type"),i=s&&this._str(s)||"kwh",r=ii(t,i),a=this._chargerEntity("time",t,"target_time"),n=a?this._hhmm(this._str(a)):"",o=(this._attrs("sensor.sem_charging_state").per_charger_departure||{})[t]||{},l=Xi(o.by_weekday||{},n||o.default||""),c=function({chargeBy:e="",min:t=null,max:s=null,unit:i="kWh",runs:r=[],dayName:a=e=>String(e)}={}){const n=e=>String("%"===i?Math.round(e):Math.round(10*e)/10),o=Number.isFinite(Number(t))&&null!==t,l=Number.isFinite(Number(s))&&null!==s,c=o&&l?`${n(Number(t))}–${n(Number(s))} ${i}`:o?`${n(Number(t))} ${i}`:"",_=Array.isArray(r)?r:[],d=1===_.length?_[0]:null,p=d&&d.time?d.time:e||"",h=e=>e.from===e.to?a(e.from):`${a(e.from)}–${a(e.to)}`,u=_.length?_.map(e=>d?h(e):`${h(e)} ${e.time}`).join(", "):"";return{time:p,range:c,days:u}}({chargeBy:n,unit:"soc"===i?"%":"kWh",min:this._has(r.atLeast)?this._num(r.atLeast):null,max:this._has(r.upTo)?this._num(r.upTo):null,runs:l,dayName:e=>this._dayName(e)}),_=[c.time?this._tf("next_cp_ready_by",{time:c.time}):"",c.range,c.days].filter(Boolean);return _.length?j``:H}_takeOpenRequest(){let e=null;try{e=window.sessionStorage.getItem(Ui),e&&window.sessionStorage.removeItem(Ui)}catch(t){e=null}e&&this._openChargePlan(e)}_afterRender(){if(this._chargers&&this._chargers.length&&!this._openTaken&&(this._openTaken=!0,this._takeOpenRequest()),this._scrollTo){const e=this.renderRoot&&this.renderRoot.querySelector(`section[data-id="${this._scrollTo}"]`);e&&(this._scrollTo=null,e.scrollIntoView({block:"start",behavior:"smooth"}))}}_toggleOpen(e){this._open={...this._open,[e.id]:!this._isOpen(e)},Wa(this._open)}_setAllOpen(e,t){const s={...this._open};for(const i of e)s[i.id]=t;this._open=s,Wa(s)}_toggleReorder(){if(this._reorder){const e=this._work||[];this._reorder=!1,this._work=null,this._held=e,this._holdUntil=Date.now()+15e3,this._svc("update_device_priorities",function(e){return{priorities:(e||[]).filter(e=>e.inMap).map(e=>({device_id:e.id,priority:e.priority}))}}(e))}else this._work=this._rowsNow(),this._reorder=!0}async _move(e,t){if(!this._reorder||!this._work)return;this._work=function(e,t,s){const i=e.findIndex(e=>e.id===t);if(-1===i)return e;if((e[i].dependsOn||[]).length)return e;const r=[];for(const t of e){const e=r[r.length-1],s=t.dependsOn||[];s.length&&e&&e.some(e=>s.includes(e.id))?e.push(t):r.push([t])}const a=r.findIndex(e=>e[0].id===t),n=a+(s<0?-1:1);return-1===a||n<0||n>=r.length?e:([r[a],r[n]]=[r[n],r[a]],Li(Ii(r.flat())))}(this._work,e,t),this._tick++,await this.updateComplete;const s=this.renderRoot.querySelector('.dev[data-id="'+e+'"] [data-mv="'+t+'"]');s&&s.focus&&s.focus()}_dragStart(e,t){if(!this._reorder||!this._work)return;if(null!=e.button&&0!==e.button)return;const s=this._work.find(e=>e.id===t);if(!s||(s.dependsOn||[]).length)return;e.preventDefault();const i=e.currentTarget,r=i.closest(".dev"),a=Array.from(this.renderRoot.querySelectorAll(".grid .dev"));if(!r||!a.length)return;const n=a.map(e=>{const t=e.getBoundingClientRect();return t.top+t.height/2}),o=a.indexOf(r);this._drag={id:t,handle:i,row:r,rows:a,centres:n,y0:e.clientY,fromIndex:o,dropIndex:o,pointerId:e.pointerId},r.classList.add("dragging");try{i.setPointerCapture(e.pointerId)}catch(e){}this._dragMoveBound=this._dragMoveBound||this._dragMove.bind(this),this._dragEndBound=this._dragEndBound||this._dragEnd.bind(this),window.addEventListener("pointermove",this._dragMoveBound),window.addEventListener("pointerup",this._dragEndBound),window.addEventListener("pointercancel",this._dragEndBound)}_dragMove(e){const t=this._drag;if(!t)return;e.preventDefault(),t.row.style.transform="translateY("+(e.clientY-t.y0)+"px)";const s=Wi(t.centres,t.fromIndex,e.clientY);if(s!==t.dropIndex){t.dropIndex=s,t.rows.forEach(e=>e.classList.remove("drop-above","drop-below"));const e=t.rows[se.classList.remove("drop-above","drop-below")),this._drag=null,"pointercancel"!==e.type&&t.dropIndex!==t.fromIndex&&this._work&&(this._work=Ri(this._work,t.id,t.dropIndex),this._tick++)}}async _ensureEntityPicker(){if(!customElements.get("ha-entity-picker"))try{const e=await(window.loadCardHelpers?.());if(e?.createCardElement){const t=await e.createCardElement({type:"entities",entities:[]});await(t.constructor?.getConfigElement?.())}}catch(e){}}async _showConfigureModal(e,t){const s=this.renderRoot.getElementById("sem-config-modal");s&&s.remove();const i=jr(this._rowsNow(),e),r=i?.energySensor||"",a=Kr(i?.control),n="manual_mapping"===i?.control?.discovered_via;await this._ensureEntityPicker();const o=document.createElement("div");o.id="sem-config-modal",o.style.cssText="position:fixed;inset:0;background:rgba(0,0,0,0.6);z-index:999;display:flex;align-items:center;justify-content:center";const l=i&&"load"===i.type;o.innerHTML=Vr({deviceName:t,values:a,t:e=>this._t(e),hasManual:n,behindRelay:l?!!i.behindRelay:null,inUse:n||a.entity?"":i?.controlEntity||""}),this.renderRoot.appendChild(o);const c=o.querySelector("#cfg-type"),_=o.querySelector("#cfg-entity-group"),d=o.querySelector("#cfg-service-group"),p=o.querySelector("#cfg-entity"),h=o.querySelector(".cfg-error"),u=e=>{h.textContent=e,h.style.display="block"};let m=null;customElements.get("ha-entity-picker")&&(m=document.createElement("ha-entity-picker"),m.hass=this._hass,m.allowCustomEntity=!0,m.value=p.value,m.includeDomains=Ur[c.value],m.style.cssText="display:block;margin:6px 0 12px",m.addEventListener("value-changed",e=>{p.value=e.detail.value||""}),p.style.display="none",p.parentNode.insertBefore(m,p));c.addEventListener("change",()=>{const e="service"===c.value;_.style.display=e?"none":"block",d.style.display=e?"block":"none",m&&!e&&(m.includeDomains=Ur[c.value])});const g=o.querySelector("#cfg-relay");g&&i&&g.addEventListener("click",()=>{const e=!g.classList.contains("on");g.classList.toggle("on",e),g.setAttribute("aria-checked",String(e)),this._dev(i.id,"behind_operator_relay",String(e))}),o.addEventListener("click",e=>{e.target===o&&o.remove()}),o.querySelector("#cfg-cancel").addEventListener("click",()=>o.remove());const f=o.querySelector("#cfg-remove");f&&f.addEventListener("click",()=>{this._hass.callService(Ba,"remove_device_control_mapping",{energy_sensor:r}).then(()=>o.remove()).catch(e=>u(e.message))}),o.querySelector("#cfg-save").addEventListener("click",()=>{const e=Yr(o);let t;m&&(e.entity=(m.value||"").trim());try{t=Hr(r,e)}catch(e){return void u(this._t(e.message))}this._hass.callService(Ba,"set_device_control_mapping",t).then(()=>o.remove()).catch(e=>u(e.message))})}_kwSensorW(e){const t=this._num(e);return null===t?null:"w"===(this._unitOf(e)||"").toLowerCase()?t:1e3*t}_surplus(e){const t=e=>this._t(e),s=this._hass.states["sensor.sem_target_peak_limit"],i=this._num("sensor.sem_surplus_total_w"),r=this._num("sensor.sem_surplus_allocated_w"),a=this._num("sensor.sem_surplus_unallocated_w"),n=this._num("sensor.sem_surplus_active_devices"),o=this._num("sensor.sem_surplus_total_devices"),l=this._kwSensorW("sensor.sem_consecutive_peak_15min"),c=this._kwSensorW("sensor.sem_target_peak_limit"),_=null===c?null:c/1e3,d=je(s),p=this._kwSensorW("sensor.sem_peak_margin"),h=[];if(null!==i&&h.push({label:t("next_surplus"),value:this._fmtKw(i),unit:"kW",sub:null===r&&null===a?"":this._tf("next_given_free",{given:this._fmtKw(r??0),free:this._fmtKw(a??0)})}),null===n&&null===o||h.push({label:t("next_active"),value:null===n?"—":Math.round(n),unit:null===o?"":this._tf("next_of_n",{n:Math.round(o)}),sub:t("next_active_sub")}),s&&h.push({label:t("next_peak"),value:this._fmtKw(l),unit:d?t("uncapped"):this._tf("next_of_kw",{kw:this._fmtKw(c)}),sub:d||null===p?t("next_15min"):this._tf("next_15min_margin",{kw:this._fmtKw(p)})}),!h.length)return H;const u=this._num("sensor.sem_current_vs_peak_percentage"),m=d?0:null!==u?u:c&&null!==l?l/c*100:0,g=this._obs(),f={},y=[];if(s){const e=Je({unlimited:d,limitKw:_}),i=(t,s)=>{const i=Qe(t,s);this._svc("update_target_peak",{target_peak_limit:i.target_peak_limit??e.kw,peak_limit_unlimited:i.peak_limit_unlimited})};f.limit=this._inline("peak",null===_?"—":this._kw(1e3*e.kw),()=>this._numEdit("peak",{value:e.kw,min:Ke,max:He,step:Ve,unit:"kW",onCommit:e=>i(!0,e)})),f.answer=this._val(t(e.limited?tt:st),()=>i(!e.limited,e.kw),{disabled:g||null===_}),y.push(this._sentence(e.limited?t(et)+" {limit} · {answer}.":"{answer}.",f)),y.push(t(e.limited?it:rt));const r=d?null:Ue({targetKw:_,usedKwh:s.attributes?.peak_slot_used_kwh,allowedW:s.attributes?.peak_slot_allowed_w});r&&y.push(this._tf("next_sent_slot",{used:r.usedKwh.toFixed(2),budget:r.budgetKwh.toFixed(2)}))}const b=this._kwSensorW("sensor.sem_monthly_consecutive_peak");null!==b&&y.push(this._tf("next_sent_month_peak",{kw:this._kw(b)}));const v=this._kwSensorW("sensor.sem_available_load_reduction");null!==v&&y.push(this._tf("next_sent_can_shed",{kw:this._kw(v),n:e.filter(e=>"load"===e.type&&e.hasControlHandle&&e.isControllable).length}));const x=this._valLabel("load_management_status"),$=this._str("sensor.sem_load_management_recommendation"),w=$?this._t($)||$:"",k=this._attrs("sensor.sem_load_management_status"),S=!0===k.shed_signal_active?this._tf("operator_limit_banner",{kw:"number"==typeof k.shed_signal_cap_kw?k.shed_signal_cap_kw.toFixed(1):"?",time:k.shed_signal_since?this._hhmm(k.shed_signal_since):"?"}):"";return this._sec(t("next_surplus_peak"),j` + ${S?j``:H} + ${this._tiles(h,{three:!0})} + ${s&&!d?j`
${this._track(m,Ts.grid)}
`:H} + ${y.length?j`

${y.map((e,t)=>j`${t?" ":""}${e}`)}

`:H} + ${x||w?j`

${x}${x&&w?" · ":""}${w}

`:H}`,{cls:"span2"})}_shell(e,t){const s=e=>this._t(e),i=this._live(e),r=Yi(e,i),a="charger"===e.type?Ts.ev:"battery"===e.type?Ts.battery_out:Ts.home,n=i.powerW,o="battery"===e.type&&null!==n?(n<0?"−":"")+this._kw(Math.abs(n)):null!==n&&("load"!==e.type||n>10||e.isOn)?this._kw(n):e.rating>0&&e.ratingMeasured?j`~${this._kw(e.rating)}`:e.rating>0?j`${s("rating_not_measured")}`:null===n?"":this._kw(n),l=this._obs(),c=!this._reorder&&this._isOpen(e),_=c?"charger"===e.type?la(this,e):"battery"===e.type?pa(this):Oa(this,e):H,d=(e.dependsOn||[]).length>0,p=()=>{this._reorder||this._toggleOpen(e)};return j`
+
+ this._dragStart(t,e.id)}>${this._icon(d?"mdi:subdirectory-arrow-right":"mdi:drag-vertical")} + ${t+1} +
${this._icon(e.icon)}${e.name}${this._pill(this._pillText(r),r.cls)}
+ ${o} + ${"load"!==e.type||e.lockedByOperator?H:this._pill(s(function(e){return e.hasControlHandle?e.isControllable&&"off"!==e.mergedMode?"control_verdict_may_act":"control_verdict_off":"control_verdict_none"}(e)),"",{icon:"mdi:hand-back-left-outline"})} +
+ + +
+ ${"load"===e.type?j``:H} + +
+ ${"charger"!==e.type||this._reorder?H:this._chargePlanChip(e)} + ${c?j`
${_}
`:H}
`}render(){if(!this._hass)return H;const e=e=>this._t(e),t=this._rowsNow(),s=t.length,i=0===s?e("next_dev_none"):1===s?this._tf("next_dev_lead_one",{first:t[0].name}):this._tf("next_dev_lead2",{n:s,first:t[0].name,last:t[s-1].name}),r=e(s?"next_dev_lead2_sub":"next_no_loads"),a=this._obs(),n=j`
+ ${this._btn(this._reorder?e("next_done_btn"):e("next_reorder"),()=>this._toggleReorder(),{primary:this._reorder,icon:this._reorder?"mdi:check":"mdi:swap-vertical",disabled:a||s<2})} + + ${!this._reorder&&!this._all&&s>1?(()=>{const s=t.every(e=>this._isOpen(e));return j``})():H} + ${this._reorder?j`${e("next_reorder_hint")}`:H} + ${a?j`${e("next_observing_hint")}`:H} +
`;return this._frame({main:i,sub:r},j` + ${n} +
+ ${this._adding&&!this._reorder?this._addSheet(t):H} + ${this._surplus(t)} + ${Rr(t,e=>e.id,(e,t)=>this._shell(e,t))} +
`)}getCardSize(){return 8+4*this._chargers.length}},{type:"sem-next-devices-card",name:"SEM · Devices",description:"Every charger, battery and load in one card shape: status, mode, targets, today, next, one change",documentationURL:"https://github.com/traktore-org/sem-community/blob/develop/docs/DASHBOARD_GUIDE.md#sem-next-devices-card",preview:!0});const ja=36e5,qa=e=>String(e).padStart(2,"0"),Ka=(e,t)=>e.getFullYear()===t.getFullYear()&&e.getMonth()===t.getMonth()&&e.getDate()===t.getDate(),Ha=(e,t)=>{const s=new Date(e.getTime());return s.setDate(s.getDate()+t),s};function Ga(e,t){try{return e.toLocaleDateString(t||"en",{weekday:"short"})}catch(t){return e.toLocaleDateString("en",{weekday:"short"})}}function Va(e,t){try{return e.toLocaleDateString(t||"en",{month:"short"})}catch(t){return e.toLocaleDateString("en",{month:"short"})}}function Ya(e,t=new Date,s={}){const i=Ua(t,s.tz),r=[],a=[];let n,o,l,c=-1;if("day"===e||"yesterday"===e){n="day"===e?i:Ha(i,-1),o=Ha(n,1),l="hour";for(let e=0;e<24;e++)r.push(n.getTime()+e*ja),a.push(e%6==0?qa(e):"");"day"===e&&(c=Math.max(0,Math.min(23,Math.floor((t.getTime()-n.getTime())/ja))))}else if("week"===e){const e=i.getDay()||7;n=Ha(i,-(e-1)),o=Ha(n,7),l="day";for(let e=0;e<7;e++){const t=Ha(n,e);r.push(t.getTime()),a.push(Ga(t,s.lang)),Ka(t,i)&&(c=e)}}else if("month"===e){n=new Date(i.getTime()),n.setDate(1),o=new Date(n.getTime()),o.setMonth(o.getMonth()+1),l="day";const e=Math.round((o.getTime()-n.getTime())/864e5);for(let t=0;tt.getTime()?new Date(t.getTime()):o,spanEnd:o,starts:r,labels:a,highlightIndex:c,n:r.length}}function Za(e,t){return"total"===t&&"month"===e.period?"day":e.period}function Xa(e,t,s="total"){const i=t.starts.length,r=new Array(i).fill(null),a=new Array(i).fill(0),n=(e||[]).map(e=>({t:"number"==typeof e.start?e.start:Date.parse(e.start),p:e})).filter(e=>Number.isFinite(e.t)).sort((e,t)=>e.t-t.t);let o;if("total"===s){const e=n.map(e=>e.p.state??e.p.max??e.p.mean);o="hour"===t.period?function(e){const t=[];let s=null;for(const i of e||[]){const e=Number(i);Number.isFinite(e)?(null===s||eNumber.isFinite(Number(e))?Math.max(0,Number(e)):0)}else o=n.map(e=>e.p.mean??e.p.state??e.p.max);return n.forEach((e,s)=>{const i=Number(o[s]);if(!Number.isFinite(i))return;const n=function(e,t){if(!t.length||!Number.isFinite(e))return-1;let s=0,i=Math.abs(e-t[0]);for(let r=1;r1?t[1]-t[0]:ja)?s:-1}(e.t,t.starts);n<0||(r[n]=(r[n]||0)+i,a[n]+=1)}),"mean"===s?r.map((e,t)=>a[t]?e/a[t]:null):r}const Ja=e=>Number.isFinite(Number(e))?Number(e):0,Qa=e=>e>=10?String(Math.round(e)):e.toFixed(1);function en(e){return e>0?e>5?5*Math.ceil(e/5):Math.ceil(e):1}function tn(e,t,s,i=600,r=150,a=-1,n={}){const o=s.length,l=s.map((t,s)=>e.reduce((e,t)=>e+Math.max(0,Ja(t[s])),0)),c=!e.length||!l.some(e=>e>0),_=en(Math.max(...l,0)),d=(i-12)/Math.max(1,o),p=e=>14+(r-14-22)*(1-e/_),h=n.unit||"",u=[_/2,_].map(e=>({y:p(e),text:Qa(e)+(h?" "+h:"")})),m=[],g=[],f=[],y=[];return s.forEach((s,i)=>{let n=0;const c=6+i*d+.2*d,_=Math.max(2,.6*d),h=a>=0&&i!==a?.6:1;y.push({x:6+i*d,w:d,i:i}),e.forEach((s,r)=>{const a=Math.max(0,Ja(s[i]));if(a<=0)return;const o=p(n+a),l=p(n);m.push({x:c,y:o+(r?1:0),w:_,h:Math.max(0,l-o-(r?1:0)-1),rx:r===e.length-1?3:1,color:t[r]||t[0],opacity:h,i:i,k:r}),n+=a}),s&&g.push({x:c+_/2,y:r-6,text:s,strong:i===a}),l[i]>0&&(o<=12||i===a)&&f.push({x:c+_/2,y:p(l[i])-4,text:l[i]>=10?String(Math.round(l[i])):l[i].toFixed(1)})}),{w:i,h:r,empty:c,max:_,grid:u,bars:m,labels:g,totals:f,slots:y,sums:l}}function sn(e,t=600,s=150,i={}){const r=Math.max(0,...e.map(e=>(e.v||[]).length)),a=e.flatMap(e=>(e.v||[]).filter(e=>null!==e&&Number.isFinite(Number(e))).map(Number)),n=!a.length||!a.some(e=>e>0),o=i.max||en(1.05*Math.max(...a,0)),l=e=>6+(r>1?e/(r-1):.5)*(t-12),c=e=>10+(s-10-18)*(1-Math.max(0,Math.min(o,e))/o),_=i.unit||"",d=[o/2,.95*o].map(e=>({y:c(e),text:Qa(e)+(_?" "+_:"")})),p=e.map(e=>{const t=(e.v||[]).map((e,t)=>null!==e&&Number.isFinite(Number(e))?[l(t),c(Number(e))]:null).filter(Boolean),s=function(e){if(!e.length)return"";if(e.length<3)return e.map((e,t)=>(t?"L":"M")+e[0].toFixed(1)+" "+e[1].toFixed(1)).join(" ");let t="M"+e[0][0].toFixed(1)+" "+e[0][1].toFixed(1);for(let s=0;s{e&&h.push({x:l(t),y:s-4,text:e,anchor:0===t?"start":t===r-1?"end":"middle"})});const u=Array.from({length:r},(e,t)=>l(t)),m=Number.isInteger(i.hover)&&i.hover>=0&&i.hover(e||[]).filter(ln).map(e=>String(e.date)).sort();function pn(e){return e.length?{from:e[0],to:e[e.length-1]}:null}function hn(e,t){const s=dn(e),i=Math.max(0,Math.min(s.length,Math.round(Number(t)||0)));return i?pn(s.slice(s.length-i)):null}function un(e,t){const s=new Date(Date.UTC(e.getFullYear(),e.getMonth(),e.getDate()));return s.setUTCDate(s.getUTCDate()-t),s.toISOString().slice(0,10)}function mn(e,t,s=new Date){const i=_n[t];if(!i)return null;const r=un(s,i),a=un(s,0);return pn(dn(e).filter(e=>e>=r&&ee>=t.from&&e<=t.to).length:0}function fn(e){const t=/^(\d{4})-(\d{2})-(\d{2})/.exec(String(e||""));return t?`${t[3]}.${t[2]}`:String(e||"")}function yn(e,t="en"){const s=/^(\d{4})-(\d{2})-(\d{2})/.exec(String(e||""));if(!s)return String(e||"");const i=new Date(Date.UTC(+s[1],+s[2]-1,+s[3]));let r="";try{r=new Intl.DateTimeFormat(t,{weekday:"short",timeZone:"UTC"}).format(i)}catch(e){r=new Intl.DateTimeFormat("en",{weekday:"short",timeZone:"UTC"}).format(i)}return`${r} ${s[3]}.${s[2]}`}function bn(e){const t=Number(e);if(null==e||!Number.isFinite(t))return"—";const s=Math.round(100*t)/100;return 0===s?"0.00":(s<0?"−":"")+Math.abs(s).toFixed(2)}function vn(e){const t=Number(e);if(null==e||!Number.isFinite(t))return{text:"—",cls:"same"};const s=Math.round(100*t)/100;return 0===s?{text:"0.00",cls:"same"}:s<0?{text:"−"+Math.abs(s).toFixed(2),cls:"cheaper"}:{text:"+"+s.toFixed(2),cls:"dearer"}}function xn(e,t){const s=Number(t),i=Number(e);if(!Number.isFinite(s)||!Number.isFinite(i)||0===s)return"";const r=Math.round(i/Math.abs(s)*1e3)/10;return 0===r?"0.0 %":(r<0?"−":"+")+Math.abs(r).toFixed(1)+" %"}const $n=["simulated","delta"];function wn(e){return $n.includes(e)}const kn=["sunny","mixed","grey","car","cheap"];function Sn(e,t){const s=e||[];return t?s.filter(e=>(e.tags||[]).includes(t)):s}function Cn(e){const t=e||{},s=t.net_delta??null;return{days:Number(t.days)||0,actual:bn(t.actual),simulated:bn(t.simulated),delta:vn(t.delta),net:null===s?null:vn(s),pct:xn(t.delta,t.actual),kwhOverLimit:Number(t.kwh_over_limit)||0,indicativeDays:Number(t.indicative_days)||0}}const Mn=e=>{const t=Number(e);return null!=e&&Number.isFinite(t)?t.toFixed(1):"—"};function zn(e){const t=e||{};return{capacity_kwh:t.capacity_kwh??"",reserve_pct:t.reserve_pct??"",peak_limit_kw:t.peak_limit_kw??"",import_mode:"recorded",import_rate:t.import_rate??"",export_mode:"recorded",export_rate:t.export_rate??"",battery_mode:t.battery_mode??"auto",ev_mode:"recorded",max_charge_w:t.max_charge_w??"",max_discharge_w:t.max_discharge_w??"",pv_mode:t.system_size_kwp?"kwp":"factor",pv_extra_kwp:"",pv_factor:1,battery_to_ev:!1!==t.battery_to_ev}}const Dn=["auto","self_consumption","force_charge","force_discharge","off"],Nn=["recorded","solar_only","solar_plus_battery","solar_plus_cheap","min_plus_solar","always_max","off"],En=e=>{if(""===e||null==e)return null;const t=Number(e);return Number.isFinite(t)?t:null};function Fn(e,t,s){const i=t||{},r=s||{};if("import_rate"===e)return"flat"===i.import_mode;if("export_rate"===e)return"flat"===i.export_mode;if("battery_mode"===e)return(i.battery_mode||"auto")!==(r.battery_mode||"auto");if("ev_mode"===e)return"recorded"!==(i.ev_mode||"recorded");if("battery_to_ev"===e)return!1!==i.battery_to_ev!=(!1!==r.battery_to_ev);if("pv"===e)return"kwp"===i.pv_mode?0!==(En(i.pv_extra_kwp)||0):1!==(En(i.pv_factor)??1);return En(r[e])!==En(i[e])}function An(e){const t=e||{},s={capacity_kwh:En(t.capacity_kwh),reserve_pct:En(t.reserve_pct)},i=En(t.peak_limit_kw);s.peak_limit_kw=i&&i>0?i:null;for(const e of["import","export"]){const i=En(t[`${e}_rate`]);s[`${e}_rate`]="flat"===t[`${e}_mode`]&&null!==i?i:"recorded"}t.battery_mode&&(s.battery_mode=t.battery_mode),s.ev_mode=t.ev_mode||"recorded";for(const e of["max_charge_w","max_discharge_w"]){const i=En(t[e]);null!==i&&(s[e]=i)}if("kwp"===t.pv_mode){const e=En(t.pv_extra_kwp);e&&(s.pv_extra_kwp=e)}else{const e=En(t.pv_factor);null!==e&&1!==e&&(s.pv_factor=e)}return!1===t.battery_to_ev&&(s.battery_to_ev=!1),s}const Tn=["plus_battery","plus_pv","cheap_chargers","no_battery_ev"];function On(e,t,s){if(s?.running)return!1;if(!e?.from||!e?.to)return!1;const i=An(t);return null!==i.capacity_kwh&&i.capacity_kwh>0&&null!==i.reserve_pct}function Bn(e,t){let s=String(e||"");for(const[e,i]of Object.entries(t||{}))s=s.split(`{${e}}`).join(String(i));return s}const In="#8353d1",Ln={sunny:"sim_tag_sunny",mixed:"sim_tag_mixed",grey:"sim_tag_grey",car:"sim_tag_car",cheap:"sim_tag_cheap",negative:"sim_tag_cheap",gap:"sim_tag_gap"},Rn={last30:"sim_pick_last30",last90:"sim_pick_last90",last365:"sim_pick_last365"},Wn=e=>`battery_mode_${e}`,Pn=e=>"recorded"===e?"sim_as_recorded_opt":`charge_mode_${e}`,Un={sunny:"sim_tile_sunny",mixed:"sim_tile_mixed",grey:"sim_tile_grey",car:"sim_tile_car",cheap:"sim_tile_cheap"};ke("sem-simulation-card",class extends Re{static get properties(){return{...super.properties,_days:{state:!0},_defaults:{state:!0},_form:{state:!0},_period:{state:!0},_status:{state:!0},_result:{state:!0},_expanded:{state:!0},_filter:{state:!0},_openDay:{state:!0},_error:{state:!0}}}constructor(){super(),this._days=null,this._defaults=null,this._form=null,this._period=null,this._status=null,this._result=null,this._expanded=!1,this._filter=null,this._openDay=null,this._error=null,this._pollTimer=null,this._loading=!1}set hass(e){const t=!this._hass;super.hass=e,t&&this.isConnected&&this._load()}get hass(){return this._hass}connectedCallback(){super.connectedCallback(),this._hass&&this._load()}disconnectedCallback(){super.disconnectedCallback(),this._stopPolling()}async _ws(e,t={}){return this._hass.callWS({type:`solar_energy_management/simulation/${e}`,...t})}async _load(){if(!this._loading&&this._hass?.callWS){this._loading=!0;try{const[e,t,s]=await Promise.all([this._ws("days").catch(()=>null),this._ws("defaults").catch(()=>null),this._ws("status").catch(()=>null)]);e&&(this._days=e),t&&(this._defaults=t,this._form||(this._form=zn(t))),this._onStatus(s),s?.running||this._result||await this._loadResult()}finally{this._loading=!1}}}_onStatus(e){e&&(this._status=e,e.running?this._schedulePoll():(this._stopPolling(),"failed"===e.status&&(this._error={kind:"failed",text:e.error||""})))}_schedulePoll(){!this._pollTimer&&this.isConnected&&(this._pollTimer=setTimeout(()=>this._poll(),2e3))}_stopPolling(){this._pollTimer&&clearTimeout(this._pollTimer),this._pollTimer=null}async _poll(){if(this._pollTimer=null,!this.isConnected||!this._hass?.callWS)return;let e=null;try{e=await this._ws("status")}catch(t){e=null}if(!this.isConnected)return;if(!e)return void this._schedulePoll();const t=this._status?.running;this._onStatus(e),t&&!e.running&&await this._loadResult(e.run_id)}async _loadResult(e){try{const t=await this._ws("result",e?{run_id:e}:{});this._result=t||null,this._expanded=!1,this._filter=null,this._openDay=null}catch(e){}}async _start(){if(!On(this._period,this._form,this._status))return;const e=(t=this._period,s=this._form,{from:t.from,to:t.to,params:An(s)});var t,s;this._error=null;try{await this._ws("start",e)}catch(e){const t=e?.code||"";return this._error="busy"===t?{kind:"busy"}:{kind:"invalid"===t?"invalid":"failed",text:e?.message||""},void("busy"===t&&this._poll())}this._status={running:!0,status:"running",done:0,total:gn(this._days?.days,this._period),started_at:(new Date).toISOString()},this._schedulePoll()}async _cancel(){try{await this._ws("cancel")}catch(e){}this._stopPolling(),this._poll()}async _download(){try{const e=await this._ws("xlsx",this._result?.run_id?{run_id:this._result.run_id}:{});e?.base64&&(Wr(e.filename||"sem-simulation.xlsx",e.base64,"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"),"download"===this._error?.kind&&(this._error=null))}catch(e){console.warn("SEM: simulation download failed",e),this._error={kind:"download"}}}_setForm(e){this._form={...this._form||zn(this._defaults),...e}}_pickPeriod(e){this._period=mn(this._days?.days||[],e)}_pickMonth(e){e&&(this._period=function(e,t){return pn(dn(e).filter(e=>e.slice(0,7)===t))}(this._days?.days||[],e))}_pickRecent(e){this._period=hn(this._days?.days||[],e)}_preset(e){let t=null;if("plus_pv"===e){const e=!!this._defaults?.system_size_kwp,s=globalThis.prompt?.(this._t(e?"sim_preset_pv_ask_kwp":"sim_preset_pv_ask_factor"));if(null==s||""===String(s).trim())return;if(t=Number(String(s).replace(",",".")),!Number.isFinite(t))return}this._form=function(e,t,s,i=null){const r={...e||zn(t)},a=t||{},n=(e,t)=>{const s=En(e)??En(t);return null===s?e:Math.round(2*s*10)/10};switch(s){case"plus_battery":r.capacity_kwh=n(r.capacity_kwh,a.capacity_kwh),r.max_charge_w=n(r.max_charge_w,a.max_charge_w),r.max_discharge_w=n(r.max_discharge_w,a.max_discharge_w);break;case"plus_pv":{const e=En(i);if(null===e)break;a.system_size_kwp?(r.pv_mode="kwp",r.pv_extra_kwp=e):(r.pv_mode="factor",r.pv_factor=e);break}case"cheap_chargers":r.ev_mode="solar_plus_cheap";break;case"no_battery_ev":r.battery_to_ev=!1}return r}(this._form,this._defaults,e,t)}_setFilter(e){this._filter=this._filter===e?null:e,this._expanded=!1,this._openDay=null}_toggleDay(e){this._openDay=this._openDay===e?null:e}_currency(){return this._result?.currency||this._defaults?.currency||fe(this._hass)||""}_est(){return j`${this._t("sim_est")}`}_tagTitle(e){return(e||[]).map(e=>Ln[e]?this._t(Ln[e]):e).join(" · ")}_renderHeader(){if(!1===this._config?.header)return H;const e=this._days||{};return j` +
+
+ +
-
- ${f("ev_dep_help_days")} +
+
${this._t("sim_title")}
+
${this._t("sim_sub")}
+
+
+
${e.stored??"—"}
${this._t("sim_days_stored")}
+
${gn(e.days,this._period)}
${this._t("sim_in_period")}
+
${e.gaps??"—"}
${this._t("sim_data_gaps")}
+
+
`}_renderPeriod(){const e=this._days?.days||[],t=function(e,t,s=new Date){if(!t)return null;for(const i of cn){const r=mn(e,i,s);if(r&&r.from===t.from&&r.to===t.to)return i}return null}(e,this._period),s=this._period,i=gn(e,s),r=function(e,t){return t?(e||[]).filter(e=>!ln(e)&&String(e.date)>=t.from&&String(e.date)<=t.to).length:0}(e,s);return j` +
+
${this._t("sim_period")}
+ ${e.length?j` +
+ ${cn.map(e=>j`this._pickPeriod(e)}>${this._t(Rn[e])}`)} + +
+ ${this._renderRecentSlider(e)} +
${s?Bn(this._t("sim_period_span"),{from:yn(s.from,this._hass?.language||"en"),to:yn(s.to,this._hass?.language||"en"),n:i}):this._t("sim_pick_period_hint")}
+ ${r>0?j`
${Bn(this._t("sim_period_gaps"),{n:r})}
`:H} +
${this._t("sim_period_note")}
`:j` +
${this._days?this._t("sim_no_days"):this._t("sim_loading")}
`} +
`}_renderRecentSlider(e){const t=function(e){return dn(e).length}(e);if(t<2)return H;const s=function(e,t){if(!t)return 0;const s=dn(e),i=s.filter(e=>e>=t.from&&e<=t.to);if(!i.length||i[i.length-1]!==s[s.length-1])return null;const r=hn(e,i.length);return r&&r.from===t.from&&r.to===t.to?i.length:null}(e,this._period),i=s??0,r=i?hn(e,i):null,a=r?`${fn(r.from)} – ${fn(r.to)}`:"";return j` +
+ + this._pickRecent(e.target.value)}> + ${a} +
`}_renderNumberRow(e,t,s,i,r=null){const a=this._form||{},n=Fn(e,a,this._defaults);return j`
- ${this._t("ev_dep_one_block")} - - {t.stopPropagation(),this._writeDeparture(e,{one_block:!_})}}> - - - -
- ${f("ev_dep_help_one_block")} + ${this._t(t)} + ${i}→ + this._setForm({[e]:t.target.value})}>${s} + `}_renderRateRow(e,t){const s=this._form||{},i=s[`${e}_mode`]||"recorded",r=`${e}_rate`,a=Fn(r,s,this._defaults);return j`
- ${this._t("ev_dep_late_charge")} - - - -
- ${m?H` -
-
${m}
-
`:G} - ${f("ev_dep_help_late")} - `}_renderBoostRow(e,t,s){if(!("sensor.sem_battery_soc"in(this._hass?.states||{})))return G;const i=s.battery_boost||null,r=s.battery_boost_preview||{},a=!(!i||!i.active||i.charger_id!==e),o=!1!==t.boost_allowed,n=Math.round(Number(r.default_floor??50)),l=(this._boostFloor||{})[e],c=null!=l?l:n,d=[10,20,30,40,50,60,70,80,90],p=d.includes(c)?d:[...d,c].sort((e,t)=>e-t),_=Number(s.battery_soc),h=Number.isFinite(_),u=Number(r.capacity_kwh)||0,g=this._boostError&&this._boostError.id===e?this._boostError.message:null,m=this._showHelp?H` -
-
${this._t("ev_boost_help")}
-
`:G;if(a){const e=Math.round(Number(i.floor_soc)),t=Number(i.start_soc),s=Number.isFinite(Number(i.soc))?Number(i.soc):_,r=Number.isFinite(t)&&Number.isFinite(s)&&t>e?Math.max(0,Math.min(100,(t-s)/(t-e)*100)):null;return H` -
- ${this._t("ev_boost_title")} - - ${this._fmtT("ev_boost_down_to",{floor:e})} - - -
-
-
${this._fmtT("ev_boost_left",{kwh:Number(i.remaining_kwh||0).toFixed(1),soc:Number.isFinite(s)?Math.round(s):"—",floor:e})}
- ${null!=r?H`
`:G} -
${this._fmtT("ev_boost_ends",{floor:e})}
+ ${this._t(t)} + ${this._t("sim_as_recorded")}→ + + ${"flat"===i?j`this._setForm({[r]:e.target.value})}>${this._currency()}/kWh`:H} +
`}_renderModeRow(e,t,s,i,r){const a=this._form||{},n=a[e]||s[0],o=Fn(e,a,this._defaults);return j` +
+ ${this._t(t)} + ${this._t(i(r))}→ + +
`}_renderPvRow(){const e=this._form||{},t=this._defaults||{},s="kwp"===e.pv_mode&&t.system_size_kwp,i=Fn("pv",e,t);return j` +
+ ${this._t("sim_pv")} + ${t.system_size_kwp?`${t.system_size_kwp} kWp`:this._t("sim_as_recorded")}→ + ${t.system_size_kwp?j``:H} + this._setForm(s?{pv_extra_kwp:e.target.value}:{pv_factor:e.target.value})}>${s?"kWp":"×"} +
`}_renderEvAssistRow(){const e=this._form||{},t=this._defaults||{},s=!1===e.battery_to_ev?"no":"yes",i=Fn("battery_to_ev",e,t);return j` +
+ ${this._t("sim_battery_to_ev")} + ${this._t(!1===t.battery_to_ev?"sim_no":"sim_yes")}→ + +
`}_renderWhatIf(){const e=this._defaults;if(!e)return H;const t=e.peak_limit_kw?`${e.peak_limit_kw} kW`:this._t("sim_none"),s=e=>e?`${e} W`:"—";return j` +
+
${this._t("sim_what_if")}
+
+ ${this._t("sim_presets")} + ${Tn.map(e=>j`this._preset(e)}>${this._t((e=>`sim_preset_${e}`)(e))}`)}
- ${m} - `}const f=h&&u>0?Math.max(0,(_-c)/100*u):null,v=i&&!i.active&&i.last_end&&i.last_end.charger_id===e?i.last_end:null,y=v?new Date(v.at):null,b=!!(y&&!isNaN(y)&&Date.now()-y.getTime()<432e5);let x=G;if(o)if(g)x=H` -
- - ${g} -
`;else if(b){const e={unplugged:"ev_boost_end_unplugged","mode changed":"ev_boost_end_mode_changed","permission off":"ev_boost_end_permission_off","battery at its floor":"ev_boost_end_floor",stopped:"ev_boost_end_stopped","charger removed":"ev_boost_end_charger_removed"}[v.reason];x=H` -
-
${this._fmtT("ev_boost_last_end",{time:this._hhmm(y),reason:e?this._t(e):v.reason})}
-
`}else x=H` -
-
${this._fmtT("ev_boost_hint",{soc:h?Math.round(_):"—",kwh:null!=f?f.toFixed(1):"—"})}
-
`;else x=H` -
- - ${this._t("ev_boost_not_permitted")} -
`;return H` -
- ${this._t("ev_boost_title")} - - - - -
- ${x} - ${m} - `}async _startBoost(e,t){if(this._hass){this._boostError=null;try{await this._hass.callService("solar_energy_management","start_battery_boost",{charger_id:e,floor_soc:t})}catch(t){this._boostError={id:e,message:t&&t.message||String(t)}}this.requestUpdate()}}_renderPlanStrip(e){const t=this._hass?.states["sensor.sem_charging_state"],s=e?t?.attributes?.per_charger_plans?.[e]:null,i=Array.isArray(s)&&s.length>0?s:t?.attributes?.today_plan||[];if(!Array.isArray(i)||i.length<2)return G;const r=new Set(["ev_charge_start","ev_min_reached","ev_deadline"]);if(!i.some(e=>r.has(e.kind)))return G;const a=Date.now(),o=i.filter(e=>["now","night_open","ev_charge_start","ev_min_reached","ev_deadline"].includes(e.kind));o.sort((e,t)=>new Date(e.when)-new Date(t.when));const{end:n,hours:l}=function(e,t){const s=t+432e5,i=t+864e5;let r=0;for(const t of e||[]){if(!t||!us.includes(t.kind))continue;const e=new Date(t.when).getTime();Number.isFinite(e)&&e>r&&(r=e)}const a=r>t?Math.ceil((r+1)/hs)*hs:0,o=Math.min(i,Math.max(s,a));return{end:o,hours:Math.max(1,Math.round((o-t)/hs))}}(o,a),c=n-a,d=e=>Math.max(0,Math.min(100,(e-a)/c*100)),p=function(e,{now:t,end:s}){const i=(e||[]).filter(e=>e&&_s.includes(e.kind)).map(e=>({...e,t:new Date(e.when).getTime()})).filter(e=>Number.isFinite(e.t)).sort((e,t)=>e.t-t.t),r=i.filter(e=>"ev_charge_start"===e.kind),a=e=>"plan_ev_charge_estimate"===e.detail?"estimate":"plan_ev_charge_late"===e.detail?"late":"charging",o=[];let n=t,l="idle",c=null;const d=()=>{const e=Math.min(c,s);e>n&&o.push({s:n,e:e,state:l}),n=Math.max(n,e),l="wait",c=null};for(const e of i){null!=c&&cn&&o.push({s:n,e:t,state:l}),n=Math.max(n,t),"night_open"===e.kind){const t=r.find(t=>Math.abs(t.t-e.t)<=6e4);l=t?a(t):"wait"}else if("ev_charge_start"===e.kind){l=a(e);const t=e.until?new Date(e.until).getTime():NaN;c=Number.isFinite(t)&&t>e.t?t:null}else"ev_min_reached"!==e.kind&&"ev_deadline"!==e.kind||(l="done",c=null)}return null!=c&&c({idle:"#566072",wait:"#8353d1",estimate:"#b8a6e8",charging:"#8DC892",done:"#4db6ac",late:"#5BC8D8"}[e]||"#566072"),g=p.some(e=>"late"===e.state),m=e=>"cheap"===e?"#43a047":"#f06292",f=e=>(this._t(e)||"").split("{hours}").join(l),v=this._hass?.config?.time_zone||void 0,y=[];for(let e=0;e<=4;e++){const t=a+c*e/4,s=new Date(t).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit",timeZone:v});y.push({x:d(t),label:s})}return H` -
-
- - ${f("plan_strip_title")} +
+ ${this._renderNumberRow("capacity_kwh","sim_battery_size","kWh",`${e.capacity_kwh??"—"} kWh`,150)} + ${this._renderNumberRow("max_charge_w","sim_charge_power","W",s(e.max_charge_w))} + ${this._renderNumberRow("max_discharge_w","sim_discharge_power","W",s(e.max_discharge_w))} + ${this._renderNumberRow("reserve_pct","sim_battery_reserve","%",`${e.reserve_pct??"—"} %`)} + ${this._renderNumberRow("peak_limit_kw","sim_peak_limit","kW",t)} + ${this._renderPvRow()} + ${this._renderRateRow("import","sim_import_price")} + ${this._renderRateRow("export","sim_export_value")} + ${this._renderModeRow("battery_mode","sim_battery_mode",Dn,Wn,e.battery_mode||"auto")} + ${this._renderModeRow("ev_mode","sim_ev_mode",Nn,Pn,"recorded")} + ${this._renderEvAssistRow()}
- - ${g?W` - - - - - - `:G} - ${p.map(e=>W` - - `)} - ${_.map(e=>W` - - `)} - -
- ${y.map(e=>H` - ${e.label} - `)} +
`}_renderRun(){const e=this._status||{},t=!!e.running,s=On(this._period,this._form,e),i=t?function(e,t=Date.now()){const s=Number(e?.done)||0,i=Number(e?.total)||0,r=Date.parse(e?.started_at||"");if(!s||!i||s>=i||!Number.isFinite(r))return null;const a=(t-r)/s;return a>0?Math.max(1,Math.ceil(a*(i-s)/6e4)):null}(e):null,r=gn(this._days?.days,this._period);let a="";t&&(a=Bn(this._t("sim_running"),{x:Math.min((e.done||0)+1,e.total||0),y:e.total||0}),null!==i&&(a+=" · "+Bn(this._t("sim_minutes_left"),{n:i})));const n=this._error;let o="";return"busy"===n?.kind?o=this._t("sim_busy"):"failed"!==n?.kind&&"invalid"!==n?.kind||(o=Bn(this._t("sim_failed"),{error:n.text||""})),j` +
+
+ +
+ ${t?j` +
+ ${a}`:j` + ${r?Bn(this._t("sim_n_days"),{n:r}):this._t("sim_pick_period_hint")}`} +
+ ${t?j``:H}
-
+ ${o?j`
${o}
`:H} +
`}_renderDetail(e){const t=function(e){const t=e||{},s=e=>({actual:Mn(t[`actual_${e}_kwh`]),simulated:Mn(t[`simulated_${e}_kwh`])});return{lines:[{key:"sim_grid_in",...s("grid_import")},{key:"sim_grid_out",...s("grid_export")},{key:"sim_batt_in",...s("battery_charge")},{key:"sim_batt_out",...s("battery_discharge")},{key:"sim_pack_end",actual:Mn(t.actual_end_kwh),simulated:Mn(t.simulated_end_kwh)}],net:void 0===t.net_delta||null===t.net_delta?null:vn(t.net_delta),story:[...t.story||[]]}}(e);return j` +
+ ${t.lines.map(e=>j`
${this._t(e.key)} + ${e.actual} → ${e.simulated} kWh ${this._est()}
`)} + ${t.net?j`
${Bn(this._t("sim_day_net"),{d:""})} + ${t.net.text}
`:H} + ${t.story.length?j`
${t.story.join(" · ")}
`:H} +
`}_renderRow(e,t){const s=vn(e.delta),i=(e.reasons||[]).join(" · "),r=this._openDay===e.date;return j` +
this._toggleDay(e.date)}> + ${yn(e.date,t)}${e.indicative?j` ⚠️`:H} + ${function(e){const t=new Set(e||[]),s=[];for(const e of on)t.has(e)&&!s.includes(nn[e])&&s.push(nn[e]);return s.join("")}(e.tags)} + ${bn(e.actual_cost)}${wn("actual")?this._est():H} + ${bn(e.simulated_cost)} ${wn("simulated")?this._est():H} + ${s.text} ${wn("delta")?this._est():H} +
+ ${r?this._renderDetail(e):H}`}_renderMonths(e){const{rows:t,helps:s}=function(e){const t=(e||[]).map(e=>({month:String(e.month||""),days:Number(e.days)||0,delta:vn(e.net_delta??e.delta),value:Number(e.net_delta??e.delta)||0})),s=Math.max(0,...t.map(e=>Math.abs(e.value)));for(const e of t)e.share=s>0?Math.abs(e.value)/s:0;const i=t.filter(e=>Math.round(100*e.value)<0).map(e=>e.month);return{rows:t,helps:i}}(e.months);return t.length<2?H:j` +
${this._t("sim_months")}
+ ${t.map(e=>j` +
${e.month} + + ${e.delta.text}
`)} +
${s.length?Bn(this._t("sim_months_helps"),{months:s.join(", ")}):this._t("sim_months_none")} ${this._est()}
`}_renderEnergy(e){const t=function(e){const t=e?.actual||{},s=e?.simulated||{};if(!e?.actual&&!e?.simulated)return[];const i=e=>null==e?"—":`${Number(e).toFixed(1)} %`,r=e=>null==e?"—":Number(e).toFixed(1);return[{key:"sim_grid_in",actual:Mn(t.grid_import_kwh),simulated:Mn(s.grid_import_kwh),unit:"kWh"},{key:"sim_grid_out",actual:Mn(t.grid_export_kwh),simulated:Mn(s.grid_export_kwh),unit:"kWh"},{key:"sim_self_use",actual:i(t.self_consumption_pct),simulated:i(s.self_consumption_pct),unit:""},{key:"sim_cycles",actual:r(t.cycles),simulated:r(s.cycles),unit:""}]}(e.energy);return t.length?j` +
${this._t("sim_energy")} + ${this._t("sim_actual_word")} → ${this._t("sim_simulated_word")}
+ ${t.map(e=>j`
${this._t(e.key)} + ${e.actual} → ${e.simulated}${e.unit?" "+e.unit:""} ${this._est()}
`)}`:H}_renderResults(){const e=this._result;if(!e)return H;const t=this._hass?.language||"en",s=this._currency(),i=this._filter,{rows:r,more:a}=function(e,t=!1,s=8){const i=[...e||[]].sort((e,t)=>String(t.date).localeCompare(String(e.date)));return t||i.length<=s?{rows:i,more:0}:{rows:i.slice(0,s),more:i.length-s}}(Sn(e.days,i),this._expanded),n=function(e,t,s=null){const i=Number(t?.days)||0,r=[];for(const t of kn){const a=e?.[t]||{},n=Number(a.days)||0;if("cheap"===t&&0===n)continue;const o="sunny"===t||"mixed"===t||"grey"===t;r.push({tag:t,glyph:nn[t],days:n,of:o?null:i,delta:n?vn(a.delta):null,actual:n?Number(a.actual)||0:null,simulated:n?Number(a.simulated)||0:null,on:s===t})}return r}(e.per_tag,e.total,i),o=Cn(function(e,t){return t?e?.per_tag?.[t]||{days:0}:e?.total||{}}(e,i)),l=Cn(e.total),c=e.skipped||[],_=function(e){const t=e?.calibration,s=e=>null==e?null:Number(e);if(!t||void 0===e?.calibrated)return{ok:!0,caution:!1,pct:null,days:null,leftOut:0,carDays:0,carPct:null,repriced:!1};const i=s(t.error_pct),r=s(t.max_pct);return{ok:!!e.calibrated,caution:!!e.calibrated&&null!==i&&null!==r&&i>r,pct:i,days:s(t.days),leftOut:Number(t.left_out)||0,carDays:Number(t.car_days)||0,carPct:s(t.car_error_pct),repriced:!!e.repriced}}(e);if(!_.ok)return j` +
+
${this._t("sim_results")} · ${Bn(this._t("sim_last_run"),{n:l.days})}
+
${Bn(this._t("sim_cant_simulate"),{pct:null===_.pct?"?":_.pct})}
+ ${this._renderCalibLines(_)} +
${this._t("sim_limits")}
+
`;const d=e.stored_energy||null,p=e.ev||null,h=Number(p?.not_delivered_kwh)||0;return j` +
+
${this._t("sim_results")} · ${Bn(this._t("sim_last_run"),{n:l.days})} + this._download()}> + ${this._t("sessions_xlsx")}
+ ${e.cancelled?j`
${this._t("sim_cancelled")}
`:H} + ${"download"===this._error?.kind?j`
${this._t("sessions_download_failed")}
`:H} + ${l.net?j`
+ ${this._t("sim_net_total")} + ${l.net.text} ${s} ${this._est()} + ${this._t("sim_col_diff")} ${l.delta.text} ${s}
`:H} +
+
this._setFilter(null)}> +
${l.delta.text}
+
${this._t("sim_filter_all")} · ${Bn(this._t("sim_n_days"),{n:l.days})}
+
+ ${n.map(e=>j` +
e.days&&this._setFilter(e.tag)}> +
${e.delta?e.delta.text:"—"}
+
${e.glyph} ${this._t(Un[e.tag])} · ${e.days?null===e.of?Bn(this._t("sim_n_days"),{n:e.days}):Bn(this._t("sim_n_of"),{n:e.days,m:e.of}):this._t("sim_none_in_period")}
+
`)} +
+
${this._t("sim_filter_hint")}
+
${this._t("sim_col_day")}${this._t("sim_col_tags")} + ${this._t("sim_col_actual")}${_.repriced?"*":""} ${s} + ${this._t("sim_col_simulated")} ${s} + ${this._t("sim_col_diff")}
+ ${r.map(e=>this._renderRow(e,t))} + ${a>0?j`
{this._expanded=!0}}>${Bn(this._t("sim_more_days"),{n:a})} ▾
`:H} + ${this._expanded&&Sn(e.days,i).length>8?j`
{this._expanded=!1}}>${this._t("sim_fewer")} ▴
`:H} +
+ ${i?j`${Un[i]?this._t(Un[i]):i} · `:H}${Bn(this._t("sim_n_days"),{n:o.days})} · ${this._t("sim_actual_word")} ${o.actual} → ${this._t("sim_simulated_word")} ${o.simulated} ${this._est()} ${s} + ${o.delta.text} ${s}${o.pct?" · "+o.pct:""} +
+ ${i&&o.net?j`
${Bn(this._t("sim_day_net"),{d:o.net.text+" "+s})} ${this._est()}
`:H} + ${d&&Math.abs(Number(d.kwh)||0)>=.05?j`
${Bn(this._t("sim_stored_sum"),{kwh:(Number(d.kwh)>=0?"+":"")+Number(d.kwh).toFixed(1),value:bn(d.value)+" "+s})} ${this._est()}
`:H} + ${this._renderMonths(e)} + ${this._renderEnergy(e)} + ${p&&((Number(p.moved_kwh)||0)>0||(Number(p.not_delivered_kwh)||0)>0)?j`
${Bn(this._t("sim_ev_moved"),{moved:Number(p.moved_kwh).toFixed(1),short:Number(p.not_delivered_kwh).toFixed(1)})} ${this._est()}
`:H} + ${h>0&&l.net?j`
${Bn(this._t("sim_net_ev_note"),{kwh:h.toFixed(1),value:bn(p.not_delivered_value)+" "+s})} ${this._est()}
`:H} + ${_.repriced?j`
${this._t("sim_repriced_note")}
`:H} + ${this._renderCalibLines(_)} + ${l.kwhOverLimit>0?j`
${Bn(this._t("sim_over_limit"),{n:l.kwhOverLimit.toFixed(1)})} ${this._est()}
`:H} + ${c.length?j`
${Bn(this._t("sim_skipped"),{n:c.length})}
`:H} +
${this._est()} ${this._t("sim_est_legend")}
+
${this._t("sim_limits")}
+
`}_renderCalibLines(e){return j` + ${e.caution?j`
${Bn(this._t("sim_caution"),{pct:e.pct})}
`:H} + ${null===e.days?H:j`
${Bn(this._t("sim_days_checked"),{n:e.days,m:e.leftOut})}
`} + ${e.carDays>0&&null!==e.carPct?j`
${Bn(this._t("sim_car_error"),{pct:e.carPct,n:e.carDays})}
`:H}`}render(){return j` +
+ ${this._renderHeader()} + ${this._renderPeriod()} + ${this._renderWhatIf()} + ${this._renderRun()} + ${this._renderResults()} +
`}getCardSize(){return 12}static get styles(){return[Le,a` + :host { display: block; } + .sim { color: var(--primary-text-color); } + ha-card { display: block; margin: 8px 0; overflow: hidden; } + .wrap { + padding: 14px 20px; position: relative; + background: radial-gradient(ellipse 70% 60% at 50% 25%, #8353d10f 0%, transparent 100%), + radial-gradient(circle at 2px 2px, rgba(128,128,128,0.05) 0.7px, transparent 0.7px); + background-size: 100% 100%, 50px 50px; + } + .header-wrap { + display: flex; align-items: center; gap: 16px; padding: 16px 20px; + background: radial-gradient(ellipse 80% 70% at 20% 50%, #8353d10d 0%, transparent 70%), + radial-gradient(circle at 2px 2px, rgba(128,128,128,0.05) 0.7px, transparent 0.7px); + background-size: 100% 100%, 50px 50px; + } + .icon-ring { position: relative; flex-shrink: 0; width: 64px; height: 64px; } + .icon-ring svg { width: 100%; height: 100%; } + .icon-ring ha-icon { + position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); + color: #8353d1; --mdc-icon-size: 26px; opacity: .85; + } + .title-area { flex: 1 1 auto; min-width: 140px; } + .tab-title { font-size: 20px; font-weight: 700; color: #8353d1; letter-spacing: .5px; text-shadow: 0 0 12px #8353d140; } + .tab-subtitle { font-size: 12px; color: var(--secondary-text-color); margin-top: 2px; font-weight: 500; } + .stats { display: flex; gap: 16px; flex-shrink: 0; } + .stat { text-align: center; min-width: 50px; } + .stat-value { font-size: 15px; font-weight: 700; font-variant-numeric: tabular-nums; } + .stat-label { font-size: 11px; color: var(--secondary-text-color); font-weight: 500; margin-top: 1px; } + .ct-title { + font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: var(--secondary-text-color); + display: flex; align-items: center; gap: 5px; margin-bottom: 6px; + } + .ct-title ha-icon { --mdc-icon-size: 14px; color: #8353d1; } + .ct-title .right { + margin-left: auto; text-transform: none; letter-spacing: 0; font-size: 12px; font-weight: 600; + color: #5BC8D8; display: flex; gap: 4px; align-items: center; cursor: pointer; + } + .ct-title .right ha-icon { color: #5BC8D8; } + .chip { + display: inline-flex; align-items: center; gap: 4px; background: var(--secondary-background-color); + border: 1px solid var(--divider-color); border-radius: 12px; padding: 3px 10px; font-size: 11px; + font-weight: 500; font-variant-numeric: tabular-nums; cursor: pointer; user-select: none; + } + .chip.on { background: #8353d122; border-color: #8353d1; color: #8353d1; font-weight: 700; } + .picks { display: flex; gap: 6px; flex-wrap: wrap; margin: 2px 0 10px; } + .recent { display: grid; grid-template-columns: 1fr auto; gap: 2px 8px; align-items: center; margin: 0 0 10px; font-size: 12px; } + .recent label { font-weight: 600; } + .recent small { color: var(--secondary-text-color); font-variant-numeric: tabular-nums; } + .recent input { grid-column: 1 / -1; width: 100%; min-height: 36px; accent-color: #8353d1; } + .span { font-size: 13px; font-weight: 600; font-variant-numeric: tabular-nums; } + .lbl-small { font-size: 11px; color: var(--secondary-text-color); align-self: center; } + .legend { font-size: 11px; color: var(--secondary-text-color); margin-top: 8px; } + .note { font-size: 12px; color: var(--secondary-text-color); margin: 2px 0 6px; } + .err { font-size: 12px; color: var(--error-color, #db4437); margin-top: 8px; } + .warn { font-size: 12px; color: var(--warning-color, #ff9800); margin-top: 8px; } + .group { + margin-top: 8px; padding: 6px 12px; border: 1px solid var(--divider-color); + border-radius: 10px; background: rgba(255,255,255,0.025); + } + .ct-row { display: flex; align-items: center; min-height: 34px; gap: 8px; border-bottom: 1px solid var(--divider-color); } + .ct-row:last-child { border-bottom: 0; } + .ct-row .lbl { flex: 1 1 auto; font-size: 12px; font-weight: 500; } + .cur { font-size: 11px; color: var(--secondary-text-color); font-variant-numeric: tabular-nums; } + .arrow { color: var(--secondary-text-color); font-size: 12px; } + .inp { + display: inline-flex; align-items: center; gap: 4px; background: var(--secondary-background-color); + border: 1px solid var(--divider-color); border-radius: 8px; padding: 3px 8px; font-size: 12px; + font-weight: 700; font-variant-numeric: tabular-nums; min-width: 56px; justify-content: flex-end; + } + .inp input { + width: 48px; border: 0; background: transparent; color: inherit; font: inherit; text-align: right; + padding: 0; -moz-appearance: textfield; + } + .inp input::-webkit-outer-spin-button, .inp input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } + .inp input:focus { outline: none; } + .inp.changed, .sel-ctl.changed { border-color: #8353d1; color: #8353d1; } + .inp small { font-weight: 500; color: var(--secondary-text-color); } + .sel-ctl { + appearance: none; -webkit-appearance: none; background-color: var(--secondary-background-color); + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23bbbbbb' d='M7 10l5 5 5-5z'/%3E%3C/svg%3E"); + background-repeat: no-repeat; background-position: right 6px center; background-size: 14px; + color: var(--primary-text-color); border: 1px solid var(--divider-color); border-radius: 8px; + padding: 4px 24px 4px 10px; font-size: 12px; font-weight: 700; font-family: inherit; + } + .runrow { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; } + .btn { + display: inline-flex; align-items: center; gap: 6px; border-radius: 10px; padding: 8px 16px; + font-size: 13px; font-weight: 700; border: 1px solid #8353d1; background: #8353d1; color: #fff; + cursor: pointer; font-family: inherit; + } + .btn[disabled] { opacity: .45; cursor: not-allowed; } + .btn.sec { background: transparent; color: var(--primary-text-color); border-color: var(--divider-color); } + .btn ha-icon { --mdc-icon-size: 16px; } + .prog { flex: 1 1 220px; display: flex; flex-direction: column; gap: 4px; } + .bar { height: 6px; border-radius: 3px; background: var(--secondary-background-color); overflow: hidden; } + .bar i { display: block; height: 100%; background: linear-gradient(90deg, #8353d1, #5BC8D8); transition: width .4s; } + .prog small { font-size: 11px; color: var(--secondary-text-color); } + .trow, .thead { + display: grid; grid-template-columns: 1.5fr 1fr .9fr 1.1fr 1.1fr; gap: 6px; align-items: center; + min-height: 28px; border-bottom: 1px solid var(--divider-color); font-size: 12px; + font-variant-numeric: tabular-nums; + } + .thead { font-size: 11px; font-weight: 500; color: var(--secondary-text-color); min-height: 22px; } + .num { text-align: right; } + .warn { font-size: 11px; cursor: help; } + .delta { font-weight: 700; } + .cheaper { color: #8DC892; } + .dearer { color: #ff9800; } + .same, .none { color: var(--secondary-text-color); } + .est { + font-style: normal; font-size: 9px; font-weight: 600; color: #8353d1; border: 1px solid #8353d166; + border-radius: 6px; padding: 0 4px; vertical-align: middle; + } + .tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 8px; margin-top: 10px; } + .tile { border: 1px solid var(--divider-color); border-radius: 10px; padding: 8px 10px; background: rgba(255,255,255,0.025); } + .tile .v { font-size: 16px; font-weight: 700; font-variant-numeric: tabular-nums; } + .tile .l { font-size: 11px; color: var(--secondary-text-color); font-weight: 500; } + .total { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; flex-wrap: wrap; margin-top: 10px; font-size: 12px; } + .total b { font-size: 14px; } + .more { font-size: 11px; color: var(--secondary-text-color); padding: 6px 0 0; cursor: pointer; } + .tap { cursor: pointer; } + .tile.on { border-color: #8353d1; background: #8353d11c; } + .tile.empty { opacity: .55; cursor: default; } + .trow.open { background: #8353d10f; } + .detail { padding: 6px 8px 8px; border-bottom: 1px solid var(--divider-color); background: #8353d108; } + .dl { display: flex; justify-content: space-between; gap: 8px; font-size: 12px; min-height: 22px; align-items: center; font-variant-numeric: tabular-nums; } + .why { font-size: 11px; color: var(--secondary-text-color); margin-top: 4px; } + .headline { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin: 4px 0 8px; font-size: 13px; } + .headline b { font-size: 18px; } + .headline small { color: var(--secondary-text-color); } + .ct-title.sub { margin-top: 14px; } + .ct-title .right.muted { color: var(--secondary-text-color); cursor: default; font-weight: 500; } + .mrow { display: grid; grid-template-columns: 80px 1fr auto; gap: 8px; align-items: center; font-size: 12px; min-height: 22px; font-variant-numeric: tabular-nums; } + .mbar { height: 6px; border-radius: 3px; background: var(--secondary-background-color); overflow: hidden; } + .mbar i { display: block; height: 100%; } + .mbar i.cheaper { background: #8DC892; } + .mbar i.dearer { background: #ff9800; } + .mbar i.same { background: var(--divider-color); } + @media (max-width: 600px) { + .header-wrap { flex-wrap: wrap; } + .trow, .thead { grid-template-columns: 1.3fr .8fr .9fr 1.1fr 1.1fr; font-size: 11px; } + .tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .mrow { grid-template-columns: 64px 1fr auto; } + } + `]}},{type:"sem-simulation-card",name:"SEM Simulation",description:"Replay a recorded period with another battery, solar size, peak limit or price; estimated cost per day type and month",documentationURL:"https://github.com/traktore-org/sem-community/blob/develop/docs/DASHBOARD_GUIDE.md#sem-simulation-card"});const jn={day:{solar:"daily_solar_energy",grid_in:"daily_grid_import_energy",grid_out:"daily_grid_export_energy",battery:"daily_battery_discharge_energy",batt_in:"daily_battery_charge_energy",car:"daily_ev_energy",house:"daily_home_energy",cost:"daily_costs",saved:"daily_savings",batt_saved:"daily_battery_savings",export_rev:"daily_export_revenue",co2:"daily_co2_avoided",forecast:"forecast_today_kwh"},month:{solar:"monthly_solar_yield_energy",grid_in:"monthly_grid_import_energy",grid_out:"monthly_grid_export_energy",battery:"monthly_battery_discharge_energy",batt_in:"monthly_battery_charge_energy",car:"monthly_ev_consumption_energy",house:"monthly_home_consumption_energy",cost:"monthly_costs",saved:"monthly_savings",batt_saved:"monthly_battery_savings",export_rev:"monthly_export_revenue"},year:{solar:"yearly_solar_yield_energy",grid_in:"yearly_grid_import_energy",grid_out:"yearly_grid_export_energy",battery:"yearly_battery_discharge_energy",batt_in:"yearly_battery_charge_energy",car:"yearly_ev_energy",house:"yearly_home_consumption_energy",cost:"yearly_costs",saved:"yearly_savings",batt_saved:"yearly_battery_savings",export_rev:"yearly_export_revenue",co2:"yearly_co2_avoided",lifetime:"lifetime_total_savings"}},qn=[["day","next_period_day"],["yesterday","next_period_yesterday"],["week","next_period_week"],["month","next_period_month"],["year","next_period_year"]],Kn=[["energy","next_tab_energy"],["power","next_tab_power"],["battery","next_tab_battery"],["car","next_tab_car"],["money","next_tab_money"],["forecast","next_tab_forecast"],["replay","next_replay"]],Hn="sensor.sem_",Gn=[["solar",Ts.solar,"next_from_sun"],["battery",Ts.battery_out,"next_from_battery"],["grid",Ts.grid,"next_from_grid"]],Vn=[["home","mdi:home","next_src_house",["solar","battery","grid"]],["ev","mdi:car-electric","next_src_car",["solar","battery","grid"]],["battery","mdi:battery-charging","next_battery_in",["solar","grid"]],["grid","mdi:transmission-tower-export","next_export",["solar","battery"]]],Yn={solar:Ts.solar,battery:Ts.battery_out,grid:Ts.grid},Zn=[Ts.ev,Ts.battery_out,Ts.grid,Ts.export,Ts.battery_in,Ts.home],Xn=["monthly_solar_yield_energy","yearly_solar_yield_energy","pv_daily_specific_yield","pv_estimated_annual_degradation","pv_health","best_surplus_window","self_consumption_rate","autarky_rate","lifetime_co2_avoided","lifetime_trees_equivalent","yearly_trees_equivalent","roi_payback_years","roi_percentage","monthly_consecutive_peak","power_charge_cost","tariff_provider","tariff_price_level","tariff_current_import_rate","tariff_current_export_rate","lifetime_ev_energy","lifetime_ev_cost","lifetime_ev_solar_share","lifetime_ev_battery_share","lifetime_ev_grid_share","lifetime_ev_sessions","forecast_tomorrow_kwh","forecast_source","forecast_trust_d1","forecast_trust_d2","pv_performance_vs_forecast","battery_dynamic_floor_pct","battery_soc"],Jn=(e,t)=>`${Hn}flow_${e}_to_${t}_energy`,Qn=e=>`${e.getFullYear()}-${String(e.getMonth()+1).padStart(2,"0")}-${String(e.getDate()).padStart(2,"0")}`;ke("sem-next-insights-card",class extends Bs{static get properties(){return{_period:{state:!0},_tab:{state:!0},_hist:{state:!0},_hover:{state:!0},_w:{state:!0},_sess:{state:!0},_sessCid:{state:!0},_showSankey:{state:!0}}}static get styles(){return Os}constructor(){super(),this._period="day",this._tab="energy",this._hist=null,this._histKey="",this._hover=-1,this._w=0,this._sess={},this._sessCid="",this._ro=null,this._refresh=null,this._needKey=""}connectedCallback(){super.connectedCallback();const e=("undefined"!=typeof window&&window.location?window.location.hash:"").slice(1);Kn.some(([t])=>t===e)&&(this._tab=e),"undefined"!=typeof ResizeObserver&&(this._ro=new ResizeObserver(e=>{const t=Math.round(e[0]?.contentRect?.width||0);t&&t!==this._w&&(this._w=t)}),this._ro.observe(this)),this._refresh=setInterval(()=>{this._histKey="",this._load()},3e5)}disconnectedCallback(){this._ro&&(this._ro.disconnect(),this._ro=null),clearInterval(this._refresh),this._refresh=null,super.disconnectedCallback()}_watchIds(){const e=[];for(const t of Object.values(jn))for(const s of Object.values(t))e.push(Hn+s);for(const t of Xn)e.push(Hn+t);for(const[,,,t]of Vn)for(const s of t)e.push(Jn(s,"home"));for(const t of this._chargers)e.push(`${Hn}charger_${t}_daily_energy`);return e.push("switch.sem_simulation_tab","number.sem_system_investment_cost","number.sem_demand_charge_rate",Hn+"average_purchase_price"),[...super._watchIds(),...e]}set hass(e){if(super.hass=e,!e)return;const t=this._needs(),s=[this._period,this._tab,Qn(new Date),t.total.join(","),t.mean.join(",")].join("|");s!==this._needKey&&(this._needKey=s,this._load())}get hass(){return this._hass}willUpdate(e){super.willUpdate?.(e);if(!this._has("switch.sem_simulation_tab")||"on"===this._str("switch.sem_simulation_tab")||"replay"!==this._tab||(this._tab="energy"),"car"===this._tab&&this._chargers.length){this._sessCid&&this._chargers.includes(this._sessCid)||(this._sessCid=this._chargers[0]);for(const e of this._chargers)this._sess[e]||this._loadSessions(e)}}_chartW(){return Math.max(280,(this._w||640)-48)}_hourly(){return"day"===this._period||"yesterday"===this._period}_live(){return"day"===this._period||"month"===this._period||"year"===this._period}_needs(){const e=new Set,t=new Set;if(!this._live()){for(const t of["house","solar","cost","saved","batt_saved","export_rev","car","batt_in","battery","co2"])e.add(Hn+jn.day[t]);e.add(Jn("solar","home"))}if("energy"===this._tab){for(const[,,,t]of Vn)for(const s of t)e.add(Jn(s,"home"));if("day"!==this._period)for(const[t,,,s]of Vn)for(const i of s)e.add(Jn(i,t))}else if("power"===this._tab)for(const e of["solar_power","home_consumption_power","grid_power","battery_power"])t.add(Hn+e);else if("battery"===this._tab)t.add(Hn+"battery_power"),t.add(Hn+"battery_soc"),e.add(Hn+jn.day.batt_in),e.add(Hn+jn.day.battery);else if("car"===this._tab)for(const t of this._chargers)e.add(`${Hn}charger_${t}_daily_energy`);else if("money"===this._tab)for(const t of["cost","saved","batt_saved","export_rev"])e.add(Hn+jn.day[t]);else"forecast"===this._tab&&(this._hourly()?(t.add(Hn+"solar_power"),t.add(Hn+"forecast_power_now_w")):(e.add(Hn+"daily_solar_energy"),t.add(Hn+"forecast_today_kwh")));return{total:[...e].filter(e=>this._has(e)),mean:[...t].filter(e=>this._has(e))}}async _load(){if(!this._hass||"replay"===this._tab)return;const e=Ya(this._period,new Date,{tz:this._hass.config?.time_zone,lang:this._hass.language}),t=this._needs(),s=[this._period,e.start.getTime(),t.total.join(","),t.mean.join(",")].join("|");if(s===this._histKey)return;this._histKey=s;const i={};try{for(const s of["total","mean"]){const r=t[s];if(!r.length)continue;const a=await an(this._hass,r,e.start.toISOString(),e.end.toISOString(),Za(e,s));for(const t of r)i[t]=Xa(a[t],e,s)}}catch(e){}s===this._histKey&&(this._hist={key:s,buckets:e,series:i})}_series(e){return this._hist?.series?.[e]||null}_sum(e){const t=this._series(e);return t?t.reduce((e,t)=>e+(t||0),0):null}_buckets(){return this._hist?.buckets||Ya(this._period,new Date,{tz:this._hass?.config?.time_zone,lang:this._hass?.language})}_pt(e){if(this._live()){const t=jn["day"===this._period?"day":this._period][e];return t?this._num(Hn+t):null}const t=jn.day[e];return t?this._sum(Hn+t):null}_periodLabel(){return this._t((qn.find(([e])=>e===this._period)||qn[0])[1])}_lead(){const e=this._pt("house"),t=this._pt("cost"),s=this._pt("saved"),i=this._pt("batt_saved"),r=null===s&&null===i?null:(s||0)+(i||0),a="day"===this._period?this._num(Hn+"autarky_rate"):null,n=this._currency(),o=this._periodLabel();if(null!==a&&null!==t)return this._tf("next_ins_lead_own",{period:o,pct:Math.round(Math.max(0,Math.min(100,a))),cost:this._fmt(t,2),saved:this._fmt(r??0,2),cur:n});if(null!==t)return this._tf("next_ins_lead_money",{period:o,cost:this._fmt(t,2),saved:this._fmt(r??0,2),cur:n});const l=this._pt("solar");return null!==e&&null!==l?this._tf("next_ins_lead_energy",{period:o,house:this._fmt(e,1),solar:this._fmt(l,1)}):this._t("next_ins_lead_none")}_onHover(e,t,s){const i=e.currentTarget.getBoundingClientRect();if(!i.width||t<1)return;const r=Math.max(0,Math.min(1,(e.clientX-i.left)/i.width));this._hover=s?Math.min(t-1,Math.floor(r*t)):Math.round(r*(t-1))}_onKey(e,t){"ArrowLeft"===e.key&&(e.preventDefault(),this._hover=Math.max(0,(this._hover<0?t:this._hover)-1)),"ArrowRight"===e.key&&(e.preventDefault(),this._hover=Math.min(t-1,this._hover+1)),"Escape"===e.key&&(this._hover=-1)}_hint(e){return j`

${this._t(e||"next_no_history")}

`}_stack(e,t){if(e.empty)return this._hint();const s=e.slots.length,i=e.slots[this._hover];return j`this._onHover(e,s,!0)} @click=${e=>this._onHover(e,s,!0)} @pointerleave=${()=>{this._hover=-1}} + @keydown=${e=>this._onKey(e,s)}> + ${q`${e.grid.map(t=>q` + ${t.text}`)} + ${i?q``:H} + ${e.bars.map(e=>q``)} + ${e.labels.map(e=>q`${e.text}`)} + ${e.totals.map(e=>q`${e.text}`)}`} + `}_line(e,t){if(e.empty)return this._hint();const s=e.xs.length;return j`this._onHover(e,s,!1)} @click=${e=>this._onHover(e,s,!1)} @pointerleave=${()=>{this._hover=-1}} + @keydown=${e=>this._onKey(e,s)}> + ${q`${e.grid.map(t=>q` + ${t.text}`)} + ${e.paths.map(e=>q`${e.area?q``:H} + `)} + ${e.hover?q``:H} + ${e.ticks.map(e=>q`${e.text}`)}`} + `}_legend(e){return e.map(([e,t,s])=>j`${e?j``:H}${t}${void 0!==s?j` ${s}`:H}`)}_hoverLabel(e){const t=this._hover;return t<0||t>=e.n?"":"hour"===e.period?`${String(t).padStart(2,"0")}:00`:e.labels[t]||String(new Date(e.starts[t]).getDate())}_energyTab(){const e=this._buckets(),t=this._chartW(),s=Gn.filter(([e])=>this._series(Jn(e,"home"))),i=tn(s.map(([e])=>this._series(Jn(e,"home"))),s.map(([,e])=>e),e.labels,t,150,e.highlightIndex,{unit:"kWh"}),r=i.sums.reduce((e,t)=>e+t,0),a=this._hover,n=a>=0&&a${this._hoverLabel(e)}${this._legend(s.map(([e,t,s])=>[t,this._t(s),this._kwh(this._series(Jn(e,"home"))[a]||0)]))}
`:j`
${this._kwh(r)} ${this._t("next_used")} + ${this._legend(s.map(([e,t,s])=>[t,this._t(s),this._kwh(this._sum(Jn(e,"home")))]))}
`,o=this._sec(`${this._t("next_house_by_source")} · ${this._periodLabel()}`,j`${s.length?this._stack(i,this._t("next_house_by_source")):this._hint()}${i.empty?H:n}`,{cls:"span2"});return j`${o}${this._flowsCard()}${this._solarCard()}${this._sharesCard()}`}_flowValue(e,t){const s=Jn(e,t);return this._has(s)?"day"===this._period?this._num(s):this._sum(s):null}_flowsCard(){const e=Vn.map(([e,t,s,i])=>{const r=i.map(t=>[t,this._flowValue(t,e)]).filter(([,e])=>null!==e),a=r.reduce((e,[,t])=>e+Math.max(0,t),0);return r.length?{sink:e,icon:t,key:s,parts:r,total:a}:null}).filter(Boolean),t=e.length?j`
${e.map(e=>j`
+
${this._icon(e.icon)} ${this._t(e.key)}${this._kwh(e.total)}
+
${e.total>0?e.parts.map(([t,s])=>j``):H}
`)}
+
${this._legend(Gn.map(([,e,t])=>[e,this._t(t)]))}${this._t("next_sankey_note")}
+ ${this._sankey()}`:this._hint("next_no_flows");return this._sec(`${this._t("next_where_went")} · ${this._periodLabel()}`,t)}_sankey(){return"day"===this._period&&"undefined"!=typeof customElements&&customElements.get("sankey-chart")?this._showSankey?j`
`:j`
${this._link(this._t("next_show_sankey"),()=>{this._showSankey=!0})}
`:H}_sankeyConfig(){const e=[["daily_solar_energy",Ts.solar,this._t("next_src_solar"),["home","ev","battery","grid"].map(e=>Jn("solar",e))],["daily_grid_import_energy",Ts.grid,this._t("next_src_grid"),["home","ev","battery"].map(e=>Jn("grid",e))],["daily_battery_discharge_energy",Ts.battery_out,this._t("next_src_battery"),["home","ev","grid"].map(e=>Jn("battery",e))]],t={home:"daily_home_energy",ev:"daily_ev_energy",battery:"daily_battery_charge_energy",grid:"daily_grid_export_energy"},s=[];for(const[e,i]of[["solar",Ts.solar],["grid",Ts.grid],["battery",Ts.battery_out]])for(const[r,a]of Object.entries(t))e!==r&&this._has(Jn(e,r))&&s.push({entity_id:Jn(e,r),color:i,children:[Hn+t[r]]});const i=Object.entries(t).filter(([,e])=>this._has(Hn+e)).map(([,e])=>({entity_id:Hn+e}));return{type:"custom:sankey-chart",show_names:!0,show_icons:!0,show_states:!0,wide:!0,height:220,unit_prefix:"k",round:2,sections:[{entities:e.filter(([e])=>this._has(Hn+e)).map(([e,t,s,i])=>({entity_id:Hn+e,color:t,name:s,children:i.filter(e=>this._has(e))}))},{entities:s},{entities:i}]}}_solarCard(){const e=this._num(Hn+"daily_solar_energy"),t=this._num(Hn+"monthly_solar_yield_energy"),s=this._num(Hn+"yearly_solar_yield_energy"),i=this._num(Hn+"pv_daily_specific_yield"),r=this._num(Hn+"forecast_today_kwh"),a=this._num(Hn+"pv_estimated_annual_degradation"),n=[];if(null!==e){const t=[null!==i&&i>0?`${this._fmt(i,2)} kWh/kWp`:"",r?this._tf("next_pct_forecast",{pct:Math.round(e/r*100)}):""].filter(Boolean).join(" · ");n.push({label:this._t("next_today"),value:this._fmt(e,1),unit:"kWh",sub:t,color:Ts.solar})}null===t&&null===s||n.push({label:this._t("next_month_year"),value:null===t?"—":t>=100?Math.round(t):this._fmt(t,1),unit:null===s?"kWh":`· ${Math.round(s)} kWh`,sub:null!==a&&0!==a?j`${this._icon(a>0?"mdi:trending-down":"mdi:trending-up","s")} ${this._tf("next_per_year",{pct:this._fmt(Math.abs(a),1)})}`:""});const o=[],l=xe(this._hass,Hn);l.length>=2&&o.push(this._row("mdi:view-module",this._tf("next_strings",{n:l.length}),l.map(e=>`${e.name} ${null===e.energyKwh?"—":this._fmt(e.energyKwh,1)}`).join(" · "),"kWh",{onClick:()=>this._moreInfo(l[0].entityId)}));const c=this._str(Hn+"pv_health");if(c){const e=this._attrs(Hn+"pv_health"),t="green"===c?"ok":"red"===c?"bad":"warn",s=[Number.isFinite(Number(e.ratio_7d))&&null!==e.ratio_7d?this._tf("next_ratio_7d",{ratio:Number(e.ratio_7d).toFixed(2)}):"",Number.isFinite(Number(e.downtime_min_today))&&null!==e.downtime_min_today?this._tf("next_dark_minutes",{min:Math.round(e.downtime_min_today)}):"",e.snow?this._t("next_snow"):""].filter(Boolean).join(" · ");o.push(this._row("mdi:solar-panel",this._t("next_pv_health"),this._pill(this._t("next_health_"+c)||c,t),s,{onClick:()=>this._moreInfo(Hn+"pv_health")}))}const _=this._str(Hn+"best_surplus_window");return _&&o.push(this._row("mdi:weather-sunset",this._t("next_best_window"),_,"",{onClick:()=>this._moreInfo(Hn+"best_surplus_window")})),n.length||o.length?this._sec(this._t("next_src_solar"),j`${n.length?this._tiles(n):H}${o.length?j`
${o}
`:H}`):H}_sharesCard(){const e=this._num(Hn+"self_consumption_rate"),t=this._num(Hn+"autarky_rate"),s=this._num(Hn+"daily_co2_avoided"),i=this._num(Hn+"yearly_co2_avoided"),r=this._num(Hn+"lifetime_co2_avoided"),a=this._num(Hn+"lifetime_trees_equivalent")??this._num(Hn+"yearly_trees_equivalent"),n=this._kgUnit("daily_co2_avoided"),o=(e,t,s,i)=>j`
${this._ring(e,t,52,this._t(s))}
${this._t(s)}
${this._t(i)}
`,l=[null!==e?o(e,Ts.solar,"next_self_use","next_self_use_sub"):null,null!==t?o(t,Ts.home,"next_own_supply","next_own_supply_sub"):null].filter(Boolean),c=[];if(null!==s||null!==i||null!==r){const e=[null!==i?`${Math.round(i)} ${n} ${this._t("next_this_year")}`:"",null!==r?`${this._fmtT(r,n)} ${this._t("next_lifetime")}`:"",null!==a?this._tf("next_trees",{n:Math.round(a)}):""].filter(Boolean).join(" · ");c.push(this._row("mdi:leaf",this._t("next_co2_avoided"),null===s?"—":`${this._fmt(s,1)} ${n} ${this._t("next_today").toLowerCase()}`,e,{onClick:()=>this._moreInfo(Hn+"daily_co2_avoided")}))}if(!l.length&&!c.length)return H;const _="day"===this._period?this._t("next_shares"):`${this._t("next_shares")} · ${this._t("next_today")}`;return this._sec(_,j`${l.length?j`
${l}
`:H}${c.length?j`
${c}
`:H}`)}_fmtT(e,t){return"kg"===t&&e>=1e3?`${(e/1e3).toFixed(1)} t`:`${Math.round(e)} ${t}`}_powerTab(){const e=this._buckets(),t=this._chartW(),s=(e,t)=>e&&e.map(e=>null===e?null:Math.max(0,t*e)/1e3),i=[["solar_power",Ts.solar,"next_src_solar",1,!0],["home_consumption_power",Ts.home,"next_src_house",1,!1],["grid_power",Ts.grid,"next_grid_in",-1,!1],["battery_power",Ts.battery_out,"next_battery_out",-1,!1]].map(([e,t,i,r,a])=>({key:i,color:t,area:a,v:s(this._series(Hn+e),r)})).filter(e=>e.v),r=sn(i,t,150,{labels:e.labels,unit:"kW",hover:this._hover}),a=this._hover,n=a>=0&&a[e.color,this._t(e.key),this._fmt(e.v[a],1)+" kW"])]:[...i.map(e=>[e.color,this._t(e.key)]),[null,this._t("next_hover_hint")]];return this._sec(`${this._t("next_power")} · ${this._periodLabel()}`,j`${i.length?this._line(r,this._t("next_power")):this._hint()}${r.empty?H:j`
${this._legend(n)}
`}`,{cls:"span2"})}_batteryTab(){if(!this._has(Hn+"battery_soc")&&!this._has(Hn+"battery_power"))return this._sec(this._t("next_tab_battery"),this._hint("next_no_battery"),{cls:"span2"});const e=this._buckets(),t=this._chartW(),s=this._series(Hn+"battery_power"),i=this._series(Hn+"battery_soc"),r=e=>s&&s.map(t=>null===t?null:Math.max(0,e*t)/1e3),a=sn(s?[{v:r(1),color:Ts.battery_in,area:!0},{v:r(-1),color:Ts.battery_out,area:!0}]:[],t,120,{labels:e.labels,unit:"kW",hover:this._hover}),n=sn(i?[{v:i,color:Ts.battery_in}]:[],t,110,{labels:e.labels,unit:"%",max:100,hover:this._hover}),o=this._pt("batt_in"),l=this._pt("battery"),c=this._num("number.sem_battery_buffer_soc"),_=this._num(Hn+"battery_dynamic_floor_pct"),d=this._hover,p=d>=0&&d${this._legend(h)}
`} + ${i?j`${this._line(n,this._t("next_charge"))}${n.empty?H:j`
${this._legend(u)}
`}`:H}`,{cls:"span2"})}_carTab(){if(!this._chargers.length)return this._sec(this._t("next_tab_car"),this._hint("next_no_charger"),{cls:"span2"});const e=this._buckets(),t=this._chartW(),s=this._chargers.map((e,t)=>({cid:e,color:Zn[t%Zn.length],v:this._series(`${Hn}charger_${e}_daily_energy`)})).filter(e=>e.v),i=tn(s.map(e=>e.v),s.map(e=>e.color),e.labels,t,150,e.highlightIndex,{unit:"kWh"}),r=ea(this._sessionsIn(e)),a=this._pt("car")??i.sums.reduce((e,t)=>e+t,0),n=null!==r.cost&&r.kwh>0?r.cost/r.kwh:null,o=this._num(`number.sem_charger_${this._chargers[0]}_ev_kwh_per_100km`),l=this._hover,c=l>=0&&l[e.color,this._chargerName(e.cid),this._kwh(e.v[l]||0)])]:[[null,this._kwh(a),this._periodLabel().toLowerCase()],r.count?[null,this._t("next_src_sun"),this._pct(r.solarPct)]:null,null!==r.cost?[null,this._t("next_cost"),this._money(r.cost)]:null,null!==n?[null,`${this._fmt(n,2)} ${this._currency()}/kWh`,o?`· ${this._fmt(n*o,2)} ${this._currency()}/100 km`:""]:null,s.length>1?[null,""]:null,...s.length>1?s.map(e=>[e.color,this._chargerName(e.cid)]):[]].filter(Boolean),_=this._sec(`${this._t("next_src_car")} · ${this._periodLabel()}`,j`${s.length?this._stack(i,this._t("next_src_car")):this._hint()}${i.empty?H:j`
${this._legend(c)}
`}`,{cls:"span2"});return j`${_}${this._sessionsCard()}${this._progressCard()}`}_sessionsIn(e){const t=Qn(e.start),s=Qn(new Date(e.spanEnd.getTime()-1)),i=[];for(const e of this._chargers)for(const r of this._sess[e]?.rows||[]){const e=String(r.timestamp||"").slice(0,10);e>=t&&e<=s&&i.push(r)}return i}async _loadSessions(e){if(!this._hass?.callWS)return;const t=new Date;this._sess={...this._sess,[e]:{rows:[],loading:!0,month:this._sess[e]?.month||`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,"0")}`}};let s=[];try{const t=await this._hass.callWS({type:"solar_energy_management/session_history",charger_id:e});s=Array.isArray(t?.rows)?t.rows:[]}catch(e){s=[]}this._sess={...this._sess,[e]:{...this._sess[e],rows:s,loading:!1}}}async _downloadXlsx(e){if(this._hass?.callWS)try{const t=await this._hass.callWS({type:"solar_energy_management/session_history/xlsx",charger_id:e});t?.base64&&Wr(t.filename||"sem-sessions.xlsx",t.base64,"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"),this._sess={...this._sess,[e]:{...this._sess[e],error:!1}}}catch(t){this._sess={...this._sess,[e]:{...this._sess[e],error:!0}}}}_sessionsCard(){const e=this._sessCid,t=this._sess[e]||{rows:[],loading:!0},s=new Date,i=t.month||`${s.getFullYear()}-${String(s.getMonth()+1).padStart(2,"0")}`,r=Jr(t.rows,i),a=ea(r),n=new Set((t.rows||[]).map(Zr).filter(Boolean)),o=(()=>{try{return new Date(`${i}-15T12:00:00`).toLocaleDateString(this._hass?.language||"en",{month:"long",year:"numeric"})}catch(e){return i}})(),l=s=>{this._sess={...this._sess,[e]:{...t,month:Xr(i,s)}}},c=this._chargers.length>1?this._seg(this._chargers.map(e=>({key:e,label:this._chargerName(e)})),e,e=>{this._sessCid=e},{small:!0}):H,_=t.loading?this._hint("next_loading"):j`${r.length?j`
${r.slice(0,12).map(e=>{const t=ta(e,this._hass?.language);return this._row("mdi:ev-station",`${t.day} ${t.span}`,`${t.kwh} kWh`,[t.solar?`${t.solar} ${this._t("next_src_sun")}`:"",t.cost?`${t.cost} ${e.currency||this._currency()}`:"",t.min?`${t.min} min`:""].filter(Boolean).join(" · "))})}
+
${a.count} ${this._t("next_sessions")}${this._kwh(a.kwh)}${this._t("next_src_sun")} ${this._pct(a.solarPct)}${null!==a.cost?j`${this._t("next_cost")} ${this._money(a.cost)}`:H}
`:this._hint("next_no_sessions")} +
+ ${this._btn(this._t("next_prev_month"),()=>l(-1),{small:!0,icon:"mdi:chevron-left",disabled:!n.has(Xr(i,-1))})} + ${this._btn(this._t("next_next_month"),()=>l(1),{small:!0,icon:"mdi:chevron-right",disabled:!n.has(Xr(i,1))})} + ${this._btn(this._t("next_download_xlsx"),()=>this._downloadXlsx(e),{small:!0,icon:"mdi:download",disabled:!(t.rows||[]).length})} + ${t.error?j`${this._t("next_download_failed")}`:H}
`;return this._sec(`${this._t("next_sessions")} · ${o}`,_,{extra:c})}_progressCard(){const e=this._num(Hn+"daily_ev_energy"),t=this._chargers.map(e=>this._num(`number.sem_charger_${e}_daily_ev_target`)).filter(e=>null!==e).reduce((e,t)=>e+t,0)||null,s=["lifetime_ev_energy","lifetime_ev_cost","lifetime_ev_solar_share","lifetime_ev_sessions"].map(e=>this._num(Hn+e));if(null===e&&s.every(e=>null===e))return H;const i=[null!==s[0]?this._kwh(s[0]):"",null!==s[1]?this._money(s[1]):"",null!==s[2]?`${this._pct(s[2])} ${this._t("next_src_sun")}`:"",null!==s[3]?`${Math.round(s[3])} ${this._t("next_sessions")}`:""].filter(Boolean).join(" · ");return this._sec(this._t("next_progress"),j` + ${null!==e&&t?this._track(e/t*100,Ts.ev):H} +
${null!==e?j`${this._t("next_today").toLowerCase()} ${this._fmt(e,1)}${t?` ${this._tf("next_of_target",{kwh:this._fmt(t,1)})}`:" kWh"}`:H} + ${i?j`${this._t("next_lifetime")} ${i}`:H}
`)}_moneyTab(){const e=this._buckets(),t=this._chartW(),s=this._currency(),i=[["cost",Ts.grid,"next_bought"],["saved",Ts.good,"next_saved"],["batt_saved",Ts.battery_out,"next_batt_saved"]].map(([e,t,s])=>({key:s,color:t,v:this._series(Hn+jn.day[e])})).filter(e=>e.v),r=tn(i.map(e=>e.v),i.map(e=>e.color),e.labels,t,150,e.highlightIndex,{unit:s}),a=this._pt("cost"),n=this._pt("saved"),o=this._pt("batt_saved"),l=this._pt("export_rev"),c=this._hover,_=c>=0&&c[e.color,this._t(e.key),this._money(e.v[c]||0)])]:[null!==a?[Ts.grid,this._t("next_bought"),this._money(a)]:null,null!==n?[Ts.good,this._t("next_saved"),this._money(n+(o||0))]:null,null!==l?[Ts.export,this._t("next_earned"),this._money(l)]:null,null!==a&&null!==l?[null,this._t("next_net"),this._money(a-l)]:null].filter(Boolean),d=this._sec(`${this._t("next_cost_savings")} · ${this._periodLabel()}`,j`${i.length?this._stack(r,this._t("next_cost_savings")):this._hint()}${r.empty?H:j`
${this._legend(_)}
`}`,{cls:"span2"});return j`${d}${this._moneyCard()}`}_moneyCard(){const e=this._currency(),t=this._num(Hn+"yearly_savings"),s=this._num(Hn+"yearly_battery_savings"),i=this._num(Hn+"roi_payback_years"),r=this._num(Hn+"roi_percentage"),a=this._num("number.sem_system_investment_cost"),n=[];null!==t&&n.push({label:this._t("next_saved_year"),value:Math.round(t+(s||0)),unit:e,valueColor:Ts.good,sub:`${this._t("next_src_sun")} ${Math.round(t)}${null!==s?` · ${this._t("next_src_battery").toLowerCase()} ${Math.round(s)}`:""}`}),null===i&&null===a||n.push({label:this._t("next_payback"),value:null!==i&&i>0?this._fmt(i,1):"—",unit:this._t("next_years"),sub:j`${null!==r?`${this._t("next_roi")} ${this._fmt(r,1)} % · `:""}${this._t("next_invested")} ${this._has("number.sem_system_investment_cost")?this._val(null===a?"—":Math.round(a),()=>this._moreInfo("number.sem_system_investment_cost")):"—"}`});const o=[],l=this._num(Hn+"lifetime_ev_energy"),c=this._num(Hn+"lifetime_ev_cost");if(null!==l&&l>0&&null!==c){const t=this._num(`number.sem_charger_${this._chargers[0]||""}_ev_kwh_per_100km`),s=[["lifetime_ev_solar_share","next_src_sun"],["lifetime_ev_battery_share","next_src_battery"],["lifetime_ev_grid_share","next_src_grid"]].map(([e,t])=>{const s=this._num(Hn+e);return null===s?"":`${this._t(t).toLowerCase()} ${this._pct(s)}`}).filter(Boolean);o.push(this._row("mdi:ev-station",this._t("next_src_car"),`${this._fmt(c/l,2)} ${e}/kWh`,[t?`${this._fmt(c/l*t,2)} ${e}/100 km`:"",...s].filter(Boolean).join(" · "),{onClick:()=>this._moreInfo(Hn+"lifetime_ev_cost")}))}const _=this._num(Hn+"monthly_consecutive_peak");if(null!==_){const t=this._num(Hn+"power_charge_cost");o.push(this._row("mdi:speedometer",this._t("next_demand_charge"),`${this._fmt(_,1)} kW ${this._t("next_peak").toLowerCase()}`,j`${null!==t?`${this._money(t)} ${this._t("next_this_month").toLowerCase()}`:""}${this._has("number.sem_demand_charge_rate")?j` · ${this._t("next_rate")} ${this._val(`${this._fmt(this._num("number.sem_demand_charge_rate"),2)} ${e}/kW`,()=>this._moreInfo("number.sem_demand_charge_rate"))}`:""}`))}const d=this._str(Hn+"tariff_provider"),p=this._str(Hn+"tariff_price_level"),h=this._num(Hn+"tariff_current_import_rate"),u=this._num(Hn+"tariff_current_export_rate");if(null!==h||d){const e=p&&"flat"!==p?this._t("next_dynamic"):this._t("next_level_flat");o.push(this._row("mdi:tag-multiple",this._t("next_tariff"),[e,d].filter(Boolean).join(" · "),[null!==h?`${this._t("next_import")} ${this._fmt(h,2)}`:"",null!==u?`${this._t("next_export").toLowerCase()} ${this._fmt(u,2)}`:""].filter(Boolean).join(" · "),{onClick:()=>this._moreInfo(Hn+"tariff_current_import_rate")}))}const m=function(e,t){const s=_r[t];if(!s)return null;const i=e?.[s]?.attributes||{},r=e=>null==e||""===e?NaN:Number(e),a=r(i.avg_price_paid),n=r(i.avg_price_hours);return Number.isFinite(a)?{paid:a,hours:Number.isFinite(n)?n:null,entity:s}:null}(this._hass?.states,this._period);m&&o.push(this._row("mdi:scale-balance",this._t("next_avg_purchase"),`${this._fmt(m.paid,3)} ${e}/kWh`,null!==m.hours?this._tf("next_avg_vs_market",{price:`${this._fmt(m.hours,3)} ${e}/kWh`}):"",{onClick:()=>this._moreInfo(m.entity)}));const g=this._num(Hn+"lifetime_total_savings");if(null!==g){const t=this._num(Hn+"lifetime_co2_avoided"),s=this._num(Hn+"lifetime_trees_equivalent");o.push(this._row("mdi:forest",this._t("next_lifetime"),`${Math.round(g)} ${e} ${this._t("next_saved").toLowerCase()}`,[null!==t?`${this._fmtT(t,this._kgUnit("lifetime_co2_avoided"))} CO₂`:"",null!==s?this._tf("next_trees",{n:Math.round(s)}):""].filter(Boolean).join(" · "),{more:!0}))}return n.length||o.length?this._sec(this._t("next_money"),j`${n.length?this._tiles(n):H}${o.length?j`
${o}
`:H}`):H}_forecastTab(){const e=this._buckets(),t=this._chartW(),s=this._hourly();let i=this._series(Hn+(s?"solar_power":"daily_solar_energy")),r=this._series(Hn+(s?"forecast_power_now_w":"forecast_today_kwh"));const a=this._num(Hn+"forecast_today_kwh");s&&(i=i&&i.map(e=>null===e?null:e/1e3),r?r=r.map(e=>null===e?null:e/1e3):"day"===this._period&&a&&(r=function(e,t,s){const i=e=>{const t=/^(\d{1,2}):(\d{2})/.exec(String(e||""));return t?Number(t[1])+Number(t[2])/60:null},r=i(t),a=i(s),n=new Array(24).fill(0),o=Number(e);if(!Number.isFinite(o)||o<=0||null===r||null===a||a<=r)return n;let l=0;for(let e=0;e<24;e++){const t=e+.5;t<=r||t>=a||(n[e]=Math.sin(Math.PI*(t-r)/(a-r)),l+=n[e])}return l>0?n.map(e=>e*o/l):n}(a,this._str(Hn+"night_end_time"),this._str(Hn+"night_start_time"))));const n=[i?{v:i,color:Ts.solar,area:!0}:null,r?{v:r,color:Ts.solar,dash:!0}:null].filter(Boolean),o=sn(n,t,150,{labels:e.labels,unit:s?"kW":"kWh",hover:this._hover}),l=this._num(Hn+"daily_solar_energy"),c=this._num(Hn+"forecast_tomorrow_kwh"),_=Nt(this._str(Hn+"forecast_source")),d=this._num(Hn+"forecast_trust_d1"),p=this._num(Hn+"forecast_trust_d2"),h=this._hover,u=s?" kW":" kWh",m=h>=0&&h${this._legend(m)}
`}`,{cls:"span2"});return j`${g}${this._accuracyCard()}`}_accuracyCard(){const e=[],t=this._num(Hn+"pv_performance_vs_forecast");null!==t&&e.push(this._row("mdi:calendar-today",this._t("next_today_so_far"),`${t>0?"+":""}${this._fmt(t,0)} %`,this._t("next_vs_forecast"),{onClick:()=>this._moreInfo(Hn+"pv_performance_vs_forecast")}));const s=this._attrs(Hn+"pv_health").ratio_7d;if(Number.isFinite(Number(s))&&null!==s){const t=100*(Number(s)-1);e.push(this._row("mdi:calendar-range",this._t("next_last_7_days"),`${t>0?"+":""}${this._fmt(t,0)} %`,this._t("next_vs_forecast"),{onClick:()=>this._moreInfo(Hn+"pv_health")}))}const i=this._attrs(Hn+"battery_spendable_kwh"),r=this._str(Hn+"forecast_source");for(const t of[1,2]){const s=`${Hn}forecast_trust_d${t}`;if(!this._has(s))continue;const a=this._num(s),n=Et({available:i[`forecast_d${t}_available`],trust:a,days:i[`forecast_days_d${t}`],minDays:i.forecast_min_days,t:e=>this._t(e)});e.push(this._row("mdi:weather-partly-cloudy",`${this._t(1===t?"next_tomorrow":"next_two_days")}${r?` · ${Nt(r)||r}`:""}`,null===a?this._t("next_learning"):`${this._t("next_trusted")} ${this._pct(100*a)}`,n,{onClick:()=>this._moreInfo(s)}))}return e.length?this._sec(this._t("next_accuracy"),j`
${e}
`):H}_replayTab(){return this._sec(this._t("next_replay"),j`

${this._t("next_replay_hint")}

+ `,{cls:"span2"})}updated(){super.updated();const e=this.renderRoot?.querySelector("sem-simulation-card");if(e&&!e._semNextConfigured){e._semNextConfigured=!0;try{e.setConfig({})}catch(e){}e.hass=this._hass}const t=this.renderRoot?.querySelector("sankey-chart");if(t){if(!t._semNextConfigured){t._semNextConfigured=!0;try{t.setConfig(this._sankeyConfig())}catch(e){}}t.hass=this._hass}}_pick(e){this._period=e,this._hover=-1,this._load()}_pickTab(e){this._tab=e,this._hover=-1;try{window.history.replaceState(null,"","#"+e)}catch(e){}this._load()}render(){if(!this._hass)return H;const e=!this._has("switch.sem_simulation_tab")||"on"===this._str("switch.sem_simulation_tab"),t=Kn.filter(([t])=>"replay"!==t||e).map(([e,t])=>({key:e,label:this._t(t)})),s={energy:()=>this._energyTab(),power:()=>this._powerTab(),battery:()=>this._batteryTab(),car:()=>this._carTab(),money:()=>this._moneyTab(),forecast:()=>this._forecastTab(),replay:()=>this._replayTab()}[this._tab]||(()=>this._energyTab()),i="replay"===this._tab?{main:this._t("next_replay"),sub:this._t("next_replay_hint")}:{main:this._lead(),sub:this._t("next_ins_sub")};return this._frame(i,j` +
+ ${this._seg(qn.map(([e,t])=>({key:e,label:this._t(t)})),this._period,e=>this._pick(e),{small:!0,disabled:"replay"===this._tab})} + ${this._seg(t,this._tab,e=>this._pickTab(e),{small:!0})} +
+
${s()}
`)}},{type:"sem-next-insights-card",name:"SEM · Insights",description:"One period for every chart: energy by source, power, battery, car, money, forecast, and a replay tab",documentationURL:"https://github.com/traktore-org/sem-community/blob/develop/docs/DASHBOARD_GUIDE.md#sem-next-insights-card",preview:!0});const eo=new Set(["battery_soc_sensor","tariff_schedule_entity","grid_power_sensor","solar_production_sensor","battery_power_sensor","house_power_sensor","heat_pump_relay1_entity","heat_pump_relay2_entity","heat_pump_relay1_on_value","heat_pump_relay1_off_value","heat_pump_relay2_on_value","heat_pump_relay2_off_value","heat_pump_climate_entity","heat_pump_power_sensor","heat_pump_temperature_sensor","heat_pump_invert_sg_ready","heat_pump_tariff_rate","heat_pump_tariff_entity","heat_pump_tariff_meter","heat_pump_energy_sensor","hot_water_energy_sensor","heat_pump_rated_power","hot_water_rated_power","hot_water_entity","hot_water_power_sensor","hot_water_temperature_sensor","battery_force_discharge_control_entity","battery_strategy_control_entity","battery_discharge_control_entity","battery_charge_end_soc_entity","battery_discharge_protection_enabled","battery_setpoint_bidirectional","battery_setpoint_model","battery_power_direction_entity","battery_direction_discharge_value","battery_direction_charge_value","battery_cycles_sensor","ev_chargers"]),to="tariff",so="load_management",io="battery_zones",ro="ev_chargers",ao="heat_pump",no="all",oo="notifications";function lo(e,t){return e?e[t]:void 0}function co(e){return!!e&&"unavailable"!==e.state&&"unknown"!==e.state&&void 0!==e.state}function _o(e){const t=parseFloat(e);return Number.isFinite(t)?t:null}function po(e,t,s={}){const i=lo(e,t);if(!co(i))return null;const r=_o(i.state);if(null===r)return null;const a=i.attributes||{};return{id:t,kind:"number",value:r,unit:s.unit??(a.unit_of_measurement||""),min:_o(a.min),max:_o(a.max),step:_o(a.step)??1,zeroKey:s.zeroKey||null,clock:!!s.clock}}function ho(e,t){const s=lo(e,t);return co(s)?{id:t,kind:"switch",value:"on"===s.state}:null}function uo(e,t,s){if(!e)return null;const i=_o(e[t]??s.default);return null===i?null:{option:t,kind:"number",value:i,unit:s.unit||"",min:s.min??null,max:s.max??null,step:s.step??1,zeroKey:s.zeroKey||null}}function mo(e,t,s){return e?{option:t,kind:"switch",value:null!=e[t]?!!e[t]:!!s}:null}function go(e,t,s,i){if(!e)return null;return{option:t,kind:"select",value:null!=e[t]&&""!==e[t]?String(e[t]):String(i??""),options:s}}function fo(e,t){return e?{option:t,kind:"text",value:null==e[t]?"":String(e[t])}:null}function yo(e){return e?Array.isArray(e)?e:[e]:null}function bo(e,t,s=Yo[t]||{}){return e?{option:t,kind:"entity",value:e[t]?String(e[t]):"",domains:yo(s.domain),deviceClass:s.deviceClass||null,fallback:s.fallback||"",emptyKey:s.emptyKey||null}:null}function vo(e,t,s,i){if(!e)return null;const r=Array.isArray(e[t])?e[t]:[];return{option:t,listIdx:s,kind:"entity",value:r[s]?String(r[s]):"",domains:yo(i.domain),deviceClass:null,fallback:"",emptyKey:null,units:Array.isArray(e.battery_slot_units)?[...e.battery_slot_units]:null}}function xo(e){const t=new Set;for(const s of Object.keys(e||{})){const e=/^sensor\.sem_battery_(b\d+)_power$/.exec(s);e&&t.add(e[1])}return t.size}const $o=[["battery_discharge_control_entities",{domain:"number",labelKey:"config_batt_discharge_entity"}],["battery_charge_end_soc_entities",{domain:["number","sensor","input_number"],labelKey:"config_batt_charge_end_entity"}],["battery_force_discharge_entities",{domain:["number","input_number"],labelKey:"config_force_discharge_entity"}],["battery_strategy_entities",{domain:["select","input_select"],labelKey:"config_strategy_entity"}]];function wo(e,t,s){if(!e||t<2)return[];const i=[];for(const[r,a]of $o)for(let n=0;ne&&e.id===t)||null;if(!a)return null;const n={option:"ev_chargers",charger:t,key:s,kind:i};if("switch"===i)return{...n,value:!!a[s]};if("select"===i)return{...n,value:String(a[s]??r.def??""),options:r.options||[]};if("entity"===i){const e=Zo[s]||{};return{...n,value:a[s]?String(a[s]):"",domains:yo(e.domain),deviceClass:e.deviceClass||null}}return"text"===i?{...n,value:null==a[s]?"":String(a[s]),placeholder:r.placeholder||""}:null}function So(e,t,s){if(!e)return null;const i=e.pv_string_names&&"object"==typeof e.pv_string_names?e.pv_string_names:{};return{option:"pv_string_names",pv:t,kind:"text",value:i[t]?String(i[t]):"",placeholder:s,base:i}}function Co(e){return e?{kind:"text",ro:!0,value:String(e)}:null}function Mo(e,t){return{kind:"action",action:e,labelKey:t}}function zo(e){return e?e.id?e.id:"ev_chargers"===e.option&&e.charger?`opt:ev_chargers:${e.charger}:${e.key}`:"pv_string_names"===e.option&&e.pv?`opt:pv_string_names:${e.pv}`:e.option&&Number.isInteger(e.listIdx)?`opt:${e.option}:${e.listIdx}`:e.option?`opt:${e.option}`:e.action?`act:${e.action}`:"":""}function Do(e,t){const s=e?.[t]?.attributes?.friendly_name;return String(s||t)}function No(e,t,s=e.value,i=null){if(!e)return"";if("entity"===e.kind)return s?Do(i,s):e.fallback?String(t("next_entity_auto")).replace("{name}",Do(i,e.fallback)):t(e.emptyKey||"config_notif_none");if("text"===e.kind&&(""===s||null==s))return e.placeholder||"—";if("number"===e.kind){if(e.zeroKey&&0===Number(s))return t(e.zeroKey);const i=Number(s);if(!Number.isFinite(i))return"—";if(e.clock){const e=Math.floor(i),t=Math.round(60*(i-e));return`${String(e).padStart(2,"0")}:${String(t).padStart(2,"0")}`}const r=i.toFixed(function(e){const t=Number(e);return!Number.isFinite(t)||t<=0||t>=1?0:t>=.1?1:t>=.01?2:3}(e.step));return e.unit?`${r} ${e.unit}`:r}if("switch"===e.kind)return t(s?"next_on":"next_off");if("select"===e.kind){const i=(e.options||[]).find(e=>String(e.value)===String(s));return i?i.labelKey?t(i.labelKey):i.label??String(i.value):String(s??"")}return"action"===e.kind?t(e.labelKey):String(s??"")}function Eo(e,t,s){for(const e of Object.values(s||{}))if(!e)return null;const i=String(e(t)||""),r=[],a=/\{(\w+)\}/g;let n,o=0;for(;n=a.exec(i);){n.index>o&&r.push(i.slice(o,n.index));const e=s[n[1]];if(!e)return null;r.push(e),o=a.lastIndex}return o{s&&r.push(t),r.push(...e)}),s&&r.push(s),Fo(r)}function To(...e){const t=[];for(const s of e)s&&s.length&&(t.length&&t.push(" "),t.push(...s));return t}function Oo(e,t){const s=[];let i=0;for(const r of t){const[t,a]=r;a&&(i&&s.push(" · "),s.push(("object"==typeof t?t.raw:e(t))+" ",a),i+=1)}return{parts:s,count:i}}const Bo=[{value:"static",labelKey:"next_tariff_static"},{value:"dynamic",labelKey:"next_tariff_dynamic"},{value:"calendar",labelKey:"next_tariff_calendar"}],Io=[{value:"percentile",labelKey:"next_class_percentile"},{value:"static",labelKey:"next_class_static"}],Lo=["none","per_kwh","tiered","fixed_monthly"].map(e=>({value:e,labelKey:"config_fee_model_"+e})),Ro=["all_in","spot_incl_vat","spot"].map(e=>({value:e,labelKey:"config_price_basis_"+e})),Wo=["none","nl_2027","custom"].map(e=>({value:e,labelKey:"config_value_preset_"+e})),Po=["none","yield","observe"].map(e=>({value:e,labelKey:"config_ext_ctrl_mode_"+e})),Uo=[{value:"kwh",labelKey:"next_target_kwh"},{value:"soc",labelKey:"next_target_soc"}],jo=[{value:"auto",labelKey:"config_forecast_source_auto"},{value:"solcast",label:"Solcast"},{value:"forecast_solar",label:"Forecast.Solar"},{value:"open_meteo",label:"Open-Meteo"},{value:"helios",label:"Helios"}],qo=e=>({off:"off",weekly:"weekly",all:"all"}[e]||null),Ko=["signed","inverted","direction_select"].map(e=>({value:e,label:e})),Ho=["switch","input_boolean","text","input_text","number","input_number","select","input_select"],Go=new Set(["text","input_text","number","input_number","select","input_select"]),Vo=["binary_sensor","switch","input_boolean","select","input_select"],Yo={grid_power_sensor:{domain:"sensor",deviceClass:"power",labelKey:"config_grid_power_sensor"},solar_production_sensor:{domain:"sensor",deviceClass:"power",labelKey:"config_solar_production_sensor"},battery_power_sensor:{domain:"sensor",deviceClass:"power",labelKey:"config_battery_power_sensor"},house_power_sensor:{domain:"sensor",deviceClass:"power",labelKey:"config_house_power_sensor"},battery_soc_sensor:{domain:"sensor",labelKey:"config_battery_soc_sensor"},battery_cycles_sensor:{domain:"sensor",labelKey:"config_battery_cycles_sensor"},battery_discharge_control_entity:{domain:"number",labelKey:"config_batt_discharge_entity"},battery_external_control_indicator:{domain:Vo,labelKey:"config_ext_ctrl_entity"},battery_charge_end_soc_entity:{domain:["number","sensor","input_number"],labelKey:"config_batt_charge_end_entity"},battery_force_discharge_control_entity:{domain:"number",labelKey:"config_force_discharge_entity"},battery_strategy_control_entity:{domain:"select",labelKey:"config_strategy_entity"},battery_power_direction_entity:{domain:"select",labelKey:"config_direction_entity"},battery_charge_power_limit_entity:{domain:"number",labelKey:"config_charge_power_limit_entity"},deye_system_work_mode_entity:{domain:"select",labelKey:"config_deye_system_work_mode_entity"},dynamic_tariff_entity:{domain:"sensor",labelKey:"config_dynamic_tariff_entity"},dynamic_forecast_entity:{domain:"sensor",labelKey:"config_dynamic_forecast_entity"},dynamic_feedin_entity:{domain:"sensor",labelKey:"config_dynamic_feedin_entity"},tariff_schedule_entity:{domain:"schedule",labelKey:"config_tariff_schedule_entity"},grid_co2_entity:{domain:"sensor",deviceClass:"co2_intensity",labelKey:"config_grid_co2_entity"},outdoor_temperature_entity:{domain:["sensor","weather"],deviceClass:"outdoor_temperature",labelKey:"config_outdoor_temperature_entity"},grid_import_power_entity:{domain:"sensor",deviceClass:"power",labelKey:"config_grid_import_entity"},grid_export_power_entity:{domain:"sensor",deviceClass:"power",labelKey:"config_grid_export_entity"},heat_pump_relay1_entity:{domain:Ho,labelKey:"config_hp_relay1"},heat_pump_relay2_entity:{domain:Ho,labelKey:"config_hp_relay2"},heat_pump_climate_entity:{domain:"climate",labelKey:"config_hp_climate"},heat_pump_power_sensor:{domain:"sensor",deviceClass:"power",labelKey:"config_hp_power_sensor"},heat_pump_energy_sensor:{domain:"sensor",deviceClass:"energy",labelKey:"config_hp_energy_sensor"},heat_pump_temperature_sensor:{domain:"sensor",deviceClass:"temperature",labelKey:"config_hp_temperature_sensor"},vacation_mode_entity:{domain:["binary_sensor","input_boolean","switch","calendar"],labelKey:"config_vacation_entity"},heat_pump_tariff_entity:{domain:["sensor","input_number"],labelKey:"device_tariff_entity"},heat_pump_tariff_meter:{domain:"sensor",deviceClass:"energy",labelKey:"device_tariff_meter"},hot_water_entity:{domain:["switch","input_boolean","water_heater","climate"],labelKey:"config_hw_entity"},hot_water_temperature_sensor:{domain:"sensor",deviceClass:"temperature",labelKey:"config_hw_temp_sensor"},hot_water_power_sensor:{domain:"sensor",deviceClass:"power",labelKey:"config_hw_power_sensor"},hot_water_energy_sensor:{domain:"sensor",deviceClass:"energy",labelKey:"config_hw_energy_sensor"},shed_signal_entity:{domain:["binary_sensor","input_boolean"],labelKey:"config_shed_signal_entity"},vpp_event_active_entity:{domain:["binary_sensor","sensor"],labelKey:"config_vpp_event_entity"},vpp_direction_entity:{domain:["sensor","select","input_select"],labelKey:"config_vpp_direction_entity"},vpp_event_end_entity:{domain:"sensor",deviceClass:"timestamp",labelKey:"config_vpp_event_end_entity"},vpp_pre_event_entity:{domain:["binary_sensor","sensor"],labelKey:"config_vpp_pre_event_entity"}},Zo={ev_connected_sensor:{domain:["binary_sensor","sensor"],labelKey:"config_ev_connected_sensor"},ev_charging_power_sensor:{domain:"sensor",deviceClass:"power",labelKey:"config_ev_charging_power"},ev_current_control_entity:{domain:"number",labelKey:"config_ev_current_control"},ev_start_stop_entity:{domain:["switch","button"],labelKey:"config_ev_start_stop"},ev_phase_switch_entity:{domain:["select","number","switch","input_select","input_number","input_boolean"],labelKey:"config_ev_phase_switch"},ev_charge_mode_entity:{domain:["select","input_select"],labelKey:"config_ev_charge_mode"},vehicle_soc_entity:{domain:"sensor",labelKey:"config_ev_vehicle_soc"},external_control_indicator:{domain:Vo,labelKey:"config_ext_ctrl_entity"}},Xo=new Set(["power","co2_intensity","outdoor_temperature"]);function Jo(e){return!!e&&(!(!lo(e,"sensor.sem_battery_soc")&&!lo(e,"sensor.sem_battery_power"))||Object.keys(e).some(e=>/^sensor\.sem_battery_b\d+_power$/.test(e)))}function Qo(e){return Object.keys(e||{}).filter(e=>/^number\.sem_battery_(b\d+_)?reserve_soc$/.test(e)).sort()}function el(e){const t=[];for(const s of Object.keys(e||{})){const i=s.match(/^sensor\.sem_pv_string_(pv\d+)_power$/);if(!i)continue;const r=e[s];!0!==r?.attributes?.restored&&t.push({slot:i[1],name:r?.attributes?.string_name||i[1].toUpperCase()})}return t.sort((e,t)=>e.slot.localeCompare(t.slot,void 0,{numeric:!0})),t}function tl(e,t){const s=t||{},i=Object.keys(e||{}).some(e=>/^number\.sem_charger_.+_minimum_current$/.test(e)),r="dynamic"===s.tariff_mode?!!s.dynamic_tariff_entity:"calendar"===s.tariff_mode?!!s.tariff_schedule_entity:!!s.electricity_import_rate;return[{key:"energy",labelKey:"config_overview_energy_dashboard",done:!!lo(e,"sensor.sem_charging_state")},{key:"ev",labelKey:"config_overview_chargers",done:i},{key:"hp",labelKey:"heat_pump_title",done:(a="binary_sensor.sem_heat_pump_registered","on"===lo(e,a)?.state)},{key:"hw",labelKey:"config_section_hot_water",done:!!s.hot_water_entity},{key:"tariff",labelKey:"config_section_tariff",done:r},{key:"battery",labelKey:"config_section_battery_zones",done:Jo(e)},{key:"loads",labelKey:"config_section_load_management",done:!!s.load_management_enabled,optional:!0}];var a}function sl(e){const t=lo(e,"sensor.sem_controllable_devices_count")?.attributes?.devices,s=[],i=t&&"object"==typeof t?Object.entries(t):[];for(const[e,t]of i)t&&"object"==typeof t&&"ev_charger"!==t.device_type&&"battery"!==t.device_type&&t.power_entity&&s.push({id:e,name:t.name||e,entity:t.power_entity});return s}function il(e,t,s=0){if(!e)return[];const i=t=>[Yo[t].labelKey,bo(e,t)],r=go(e,"battery_external_control_mode",Po,"none"),a=[i("battery_soc_sensor"),i("battery_cycles_sensor"),i("battery_discharge_control_entity"),i("battery_charge_end_soc_entity"),["config_ext_ctrl_mode",r]];return r&&"none"!==r.value&&a.push(i("battery_external_control_indicator")),s<2&&a.push(i("battery_force_discharge_control_entity"),i("battery_strategy_control_entity")),e.battery_strategy_control_entity&&a.push(["config_strategy_val_active",nl(e,"battery_strategy_active_value","api")],["config_strategy_val_idle",nl(e,"battery_strategy_idle_value","eco")],["config_strategy_val_selfc",nl(e,"battery_strategy_self_consume_value","nom")],["config_strategy_val_off",nl(e,"battery_strategy_off_value","idle")]),a.push(["config_battery_bidirectional",mo(e,"battery_setpoint_bidirectional",!1)],["config_setpoint_model",go(e,"battery_setpoint_model",al(Ko,e.battery_setpoint_model),"signed")]),"direction_select"===e.battery_setpoint_model&&a.push(i("battery_power_direction_entity"),["config_direction_val_discharge",nl(e,"battery_direction_discharge_value","discharge")],["config_direction_val_charge",nl(e,"battery_direction_charge_value","charge")]),a.push(i("battery_charge_power_limit_entity")),"deye"===e.battery_charge_platform&&a.push(["config_deye_system_work_mode",mo(e,"deye_system_work_mode_control",!1)],i("deye_system_work_mode_entity")),a}function rl(e,t){return[["config_bs_capacity",uo(e,"battery_capacity_kwh",{min:1,max:100,step:.5,unit:"kWh",default:10})],["config_bs_max_charge",uo(e,"battery_max_charge_power_w",{min:500,max:25e3,step:100,unit:"W",default:5e3})],["config_bs_efficiency",uo(e,"battery_roundtrip_efficiency",{min:.7,max:.99,step:.01,unit:"",default:.92})],["config_bs_cycle_cost",uo(e,"battery_cycle_cost",{min:0,max:.5,step:.001,unit:`${t}/kWh`,default:.02})],["config_bs_trigger_hour",uo(e,"battery_precharge_trigger_hour",{min:18,max:23,step:1,unit:"h",default:21})],["config_bs_replan_interval",uo(e,"battery_replan_interval_min",{min:5,max:120,step:5,unit:"min",default:30})],["config_bs_block_mode",mo(e,"battery_prefer_consecutive_window",!0)],["config_bs_max_target_soc",uo(e,"battery_max_target_soc",{min:50,max:100,step:5,unit:"%",default:95})],["config_bs_min_deficit",uo(e,"battery_min_deficit_kwh",{min:.5,max:10,step:.5,unit:"kWh",default:2})],["config_bs_pessimism",uo(e,"battery_pessimism_weight",{min:0,max:1,step:.1,unit:"",default:.3})],["config_bs_force_neg",mo(e,"battery_force_charge_negative_price",!0)]]}function al(e,t){return null==t||""===t||e.some(e=>String(e.value)===String(t))?e:[...e,{value:String(t),label:String(t)}]}function nl(e,t,s){const i=fo(e,t);return i?{...i,placeholder:s}:null}function ol(e,t,s={}){const i=s.options||null,r=s.currency||"EUR",a=`${r}/kWh`,n=s.labels||{},o=[];{const s=go(i,"tariff_mode",Bo,"static"),l=s?s.value:"",c=po(e,"number.sem_electricity_import_rate")||uo(i,"electricity_import_rate",{min:0,max:1e4,step:.001,unit:a,default:.3387}),_=uo(i,"electricity_off_peak_rate",{min:0,max:1e4,step:.001,unit:a,default:i?i.electricity_nt_rate??i.electricity_import_rate??.3387:.3387}),d=po(e,"number.sem_electricity_export_rate")||uo(i,"electricity_export_rate",{min:0,max:1e4,step:.001,unit:a,default:.075}),p=uo(i,"import_vat_percent",{min:0,max:100,step:.1,unit:"%",default:0}),h=uo(i,"grid_import_surcharge",{min:0,max:10,step:.001,unit:a,default:0}),u=po(e,"number.sem_cheap_price_threshold"),m=po(e,"number.sem_expensive_price_threshold"),g="dynamic"===l?go(i,"tariff_classification_mode",Io,"percentile"):null,f=Co(n.provider),y=go(i,"feed_in_fee_model",Lo,"none"),b=y?y.value:"none",v=go(i,"import_price_basis",Ro,"all_in"),x=fa(v?v.value:"all_in",l),$=uo(i,"import_markup",{min:0,max:1e4,step:1e-4,unit:a,default:0}),w=uo(i,"import_energy_tax",{min:0,max:1e4,step:1e-4,unit:a,default:0}),k=e=>!!e&&Number(e.value)>0,S="dynamic"===l?Eo(t,"next_pay_dynamic",{mode:s,provider:f})||Eo(t,"next_pay_mode",{mode:s}):"calendar"===l?Eo(t,"next_pay_calendar",{mode:s,rate:c,offpeak:_})||Eo(t,"next_pay_mode",{mode:s}):Eo(t,"next_pay_static",{mode:s,rate:c})||Eo(t,"next_pay_rate",{rate:c})||Eo(t,"next_pay_mode",{mode:s}),C=To(S?Fo(S):[],Ao([x.vat&&k(p)?Eo(t,"next_pay_plus_vat",{vat:p}):null,x.markup&&k($)?Eo(t,"next_pay_plus_markup",{markup:$}):null,x.tax&&k(w)?Eo(t,"next_pay_plus_tax",{tax:w}):null,x.fee&&k(h)?Eo(t,"next_pay_plus_fee",{fee:h}):null]),Ao([Eo(t,"next_pay_export",{export:d})]),Ao([Eo(t,"next_pay_thresholds",{cheap:u,expensive:m,basis:g})||Eo(t,"next_pay_thresholds_plain",{cheap:u,expensive:m})])),M=Oo(t,[["next_more_demand_charge",po(e,"number.sem_demand_charge_rate")||uo(i,"demand_charge_rate",{min:0,max:1e5,step:.01,unit:`${r}/kW`,default:4.32})],["next_more_off_peak","calendar"===l?null:_],["next_more_fee_model",y],["next_more_fee_amount","per_kwh"===b||"fixed_monthly"===b?uo(i,"feed_in_fee",{min:0,max:1e5,step:1e-4,unit:"fixed_monthly"===b?`${r}/${t("next_month_short")}`:a,default:0}):null],["next_more_fee_tiers","tiered"===b?fo(i,"feed_in_fee_tiers"):null],["next_more_price_basis",v],["next_more_markup",x.markup?$:null],["next_more_energy_tax",x.tax?w:null],["next_more_vat",x.vat?p:null],["next_more_grid_fee",x.fee?h:null],["next_more_export_floor",uo(i,"export_value_threshold",{min:-1e4,max:1e4,step:1e-4,unit:a,default:0})],["next_more_preset",go(i,"tariff_value_preset",Wo,"none")],..."dynamic"===l?["dynamic_tariff_entity","dynamic_forecast_entity","dynamic_feedin_entity"].map(e=>[Yo[e].labelKey,bo(i,e)]):[],[Yo.tariff_schedule_entity.labelKey,"calendar"===l?bo(i,"tariff_schedule_entity"):null]]);o.push({key:"pay",titleKey:"next_g_pay",diagnoseSection:to,sentence:C,more:M.parts,count:M.count})}{const s=i?Je({unlimited:!!i.peak_limit_unlimited,limitKw:i.target_peak_limit}):null,r=s?{option:"peak_limit_unlimited",kind:"select",peak:!0,value:s.limited?"limit":"none",options:[{value:"limit",labelKey:tt},{value:"none",labelKey:st}]}:null,a={min:Ke,max:He,step:Ge,unit:"kW"},n=s?{...uo(i,"target_peak_limit",{...a,default:Ye}),value:s.kw}:null,l=uo(i,"warning_peak_level",{...a,default:4.5}),c=uo(i,"emergency_peak_level",{...a,default:6}),_=uo(i,"inverter_ac_limit_w",{min:0,max:1e5,step:100,unit:"W",default:0}),d=_&&_.value>0?_:null,p=i&&i.shed_signal_entity?i.shed_signal_entity:null,h=To(s?s.limited?[t(et)+" ",n," · ",r,". ",t(it)]:[r,". ",t(rt)]:[],Ao([Eo(t,"next_may_ac",{ac:d})])),u=Oo(t,[["next_more_load_mgmt",mo(i,"load_management_enabled",!1)],["config_lm_warning_peak",s&&s.limited?l:null],["config_lm_emergency_peak",s&&s.limited?c:null],["next_more_shed_signal",bo(i,"shed_signal_entity")],["next_more_operator_limit",p?uo(i,"shed_signal_limit",{min:1,max:80,step:.1,unit:"kW",default:4.2}):null],["next_more_ac_limit",d?null:_],["next_more_export_guard",ho(e,"switch.sem_export_guard_enabled")],["next_more_engage",po(e,"number.sem_export_guard_engage_s")],["next_more_release",po(e,"number.sem_export_guard_release_s")],["next_more_override_external",ho(e,"switch.sem_export_guard_override_external")],["config_vpp_enabled",mo(i,"vpp_enabled",!1)],...i&&i.vpp_enabled?[["config_vpp_observer",mo(i,"vpp_observer_mode",!0)],...["vpp_event_active_entity","vpp_direction_entity","vpp_event_end_entity","vpp_pre_event_entity"].map(e=>[Yo[e].labelKey,bo(i,e)]),["config_vpp_reserve_soc",uo(i,"vpp_reserve_soc",{min:5,max:80,step:5,unit:"%",default:20})]]:[]]);o.push({key:"may",titleKey:"next_g_may",diagnoseSection:so,sentence:h,more:u.parts,count:u.count})}if(Jo(e)){const s=It(e),a=e=>null===e?null:{ro:!0,value:Math.round(e)+" %"},n={priority:a(s.priority),buffer:a(s.buffer),autostart:a(s.autostart)},l=To(Ao([Eo(t,"next_batt_zones",n)||Eo(t,"next_batt_zones_two",{priority:n.priority,buffer:n.buffer})],", ",". "),[{kind:"action",action:"open_battery",labelKey:"next_bq_levels_open",nav:!0}],Ao([Eo(t,"next_batt_spend",{v:ho(e,"switch.sem_forecast_spending_enabled")})])),c=Oo(t,[["next_more_max_discharge",po(e,Bt.discharge_max.entity)],["next_more_drain_floor",po(e,"number.sem_battery_morning_drain_floor_soc")],["next_more_house_sink",ho(e,"switch.sem_battery_house_sink_enabled")],["next_more_protection",mo(i,"battery_discharge_protection_enabled",!0)],...il(i,0,xo(e)),...wo(i,xo(e),t),["next_more_scheduler",mo(i,"battery_charge_scheduler_enabled",!1)],...i&&i.battery_charge_scheduler_enabled?rl(i,r):[]]);o.push({key:"battery",titleKey:"next_g_battery",diagnoseSection:io,sentence:l,more:c.parts,count:c.count})}{const r=s.chargers||[],a=Co(n.control),l=[],c=[];for(const s of r){const n=s.cid,o=Co(s.name||n),_=po(e,`number.sem_charger_${n}_ev_phases`),d=po(e,`number.sem_charger_${n}_minimum_current`,{unit:""}),p=po(e,`number.sem_charger_${n}_maximum_current`),h=i?(i.ev_chargers||[]).find(e=>e&&e.id===n):null,u=h&&h.vehicle_soc_entity?Uo:Uo.slice(0,1),m=ko(i,n,"ev_target_type","select",{options:u,def:"kwh"});l.push(Ao([Eo(t,"next_ch_name_control",{name:o,control:a})||Eo(t,"next_ch_name",{name:o}),Eo(t,"next_ch_phases",{phases:_}),Eo(t,"next_ch_amps",{min:d,max:p})])),l.push(Ao([Eo(t,"next_ch_targets",{type:m})]));const g=e=>r.length>1?{raw:`${s.name||n} · ${t(e)}`}:e,f=e=>[g(Zo[e].labelKey),ko(i,n,e,"entity")],y=ko(i,n,"external_control_mode","select",{options:Po,def:"none"});c.push(f("ev_current_control_entity"),f("ev_start_stop_entity"),f("ev_connected_sensor"),f("ev_charging_power_sensor"),f("vehicle_soc_entity"),[g("next_more_ext_control"),y],...y&&"none"!==y.value?[f("external_control_indicator")]:[],[g("next_more_finish_by_departure"),ko(i,n,"finish_by_departure","switch")],[g("next_more_capacity"),po(e,`number.sem_charger_${n}_ev_battery_capacity_kwh`)],[g("next_more_use"),po(e,`number.sem_charger_${n}_ev_kwh_per_100km`)],[g("next_more_phase_switching"),ko(i,n,"ev_phase_switching_enabled","switch")],f("ev_phase_switch_entity"),...h&&h.ev_phase_switch_entity?[[g("config_ev_phase_1p"),ko(i,n,"ev_phase_switch_value_1p","text",{placeholder:"1 / off"})],[g("config_ev_phase_3p"),ko(i,n,"ev_phase_switch_value_3p","text",{placeholder:"3 / on"})]]:[],f("ev_charge_mode_entity"),...h&&h.ev_charge_mode_entity?[[g("config_ev_charge_mode_start"),ko(i,n,"ev_charge_mode_start","text",{placeholder:"On / 2"})],[g("config_ev_charge_mode_stop"),ko(i,n,"ev_charge_mode_stop","text",{placeholder:"Off / 1"})]]:[])}c.push(["next_more_enable_delay",po(e,"number.sem_ev_enable_delay_seconds")],["next_more_disable_delay",po(e,"number.sem_ev_disable_delay_seconds")]);const _=r.length?To(...l):[t("next_ch_none")],d=Oo(t,c);o.push({key:"chargers",titleKey:"next_g_chargers",diagnoseSection:ro,sentence:_,more:d.parts,count:d.count})}{const s="on"===lo(e,"binary_sensor.sem_heat_pump_registered")?.state,r=s||!(!i||!i.heat_pump_relay1_entity&&!i.heat_pump_climate_entity),n=!(!i||!i.hot_water_entity),l=e=>[Yo[e].labelKey,bo(i,e)],c=uo(i,"heat_pump_tariff_rate",{min:0,max:1e4,step:.001,unit:a,default:0}),_=c&&c.value>0?c:null,d=s?po(e,"number.sem_heat_pump_boost_offset"):uo(i,"heat_pump_boost_offset",{min:0,max:10,step:.5,unit:"°C",default:2}),p=r?Ao([Eo(t,"next_hp_rated",{rated:uo(i,"heat_pump_rated_power",{min:100,max:3e4,step:50,unit:"W",default:2e3})}),i&&i.heat_pump_climate_entity?Eo(t,"next_hp_climate",{climate:bo(i,"heat_pump_climate_entity")}):i&&i.heat_pump_relay2_entity?Eo(t,"next_hp_contacts",{relay1:bo(i,"heat_pump_relay1_entity"),relay2:bo(i,"heat_pump_relay2_entity")}):Eo(t,"next_hp_contact",{relay1:bo(i,"heat_pump_relay1_entity")}),Eo(t,"next_hp_boost",{boost:d}),Eo(t,"next_hp_tariff",{rate:_})]):[],h=n?Ao([Eo(t,"next_hw_rated",{rated:uo(i,"hot_water_rated_power",{min:100,max:3e4,step:50,unit:"W",default:2500})}),Eo(t,"next_hw_target",{target:po(e,"number.sem_hot_water_solar_target")}),Eo(t,"next_hw_legionella",{temp:po(e,"number.sem_legionella_target_temp"),hours:po(e,"number.sem_legionella_interval_hours")})]):[],u=r||n?To(p,h):[t("next_heat_none")],m=r&&!(!i||!i.heat_pump_climate_entity),g=r&&!m,f=g&&!(!i||!i.heat_pump_relay2_entity),y=e=>i&&function(e){const t=String(e||"").split(".")[0];return Go.has(t)}(i[`heat_pump_relay${e}_entity`])?[[`config_hp_relay${e}_on`,nl(i,`heat_pump_relay${e}_on_value`,"100000000000")],[`config_hp_relay${e}_off`,nl(i,`heat_pump_relay${e}_off_value`,"000000000000")]]:[],b=Oo(t,[g?null:l("heat_pump_relay1_entity"),...y(1),f?null:l("heat_pump_relay2_entity"),...y(2),m?null:l("heat_pump_climate_entity"),l("heat_pump_power_sensor"),l("heat_pump_energy_sensor"),l("heat_pump_temperature_sensor"),["next_more_max_setpoint",r?uo(i,"heat_pump_max_setpoint",{min:30,max:80,step:1,unit:"°C",default:55}):null],["next_more_invert_contacts",r?mo(i,"heat_pump_invert_sg_ready",!1):null],["next_more_own_tariff",r&&!_?c:null],r?l("heat_pump_tariff_entity"):null,r?l("heat_pump_tariff_meter"):null,["next_more_vacation_signal",bo(i,"vacation_mode_entity")],l("hot_water_entity"),l("hot_water_temperature_sensor"),l("hot_water_power_sensor"),l("hot_water_energy_sensor")].filter(Boolean));o.push({key:"heat",titleKey:"next_g_heat",diagnoseSection:ao,sentence:u,more:b.parts,count:b.count})}{const s=lo(e,"sensor.sem_diag_ed_config")?.attributes?.energy_dashboard||{},r=(e,t,s={})=>bo(i,e,{...Yo[e],fallback:t||"",...s}),a=r("solar_production_sensor",s.solar?.power),l=r("grid_power_sensor",s.grid?.power),c=Jo(e)?r("battery_power_sensor",s.battery?.power):null,_=Co(n.gridSign),d=Mo("flip_grid_sign","next_flip_sign"),p=Mo("flip_battery_sign","next_flip_sign"),h=r("house_power_sensor","",{emptyKey:"next_computed"}),u=Ao([Eo(t,"next_reads_solar",{solar:a}),Eo(t,"next_reads_grid_sign",{grid:l,sign:_,flip:d})||Eo(t,"next_reads_grid",{grid:l,flip:d}),Eo(t,"next_reads_battery",{battery:c,flip:p}),Eo(t,"next_reads_house",{house:h})]),m=el(e),g=e=>[Yo[e].labelKey,bo(i,e)],f=Oo(t,[["next_more_reset_signs",Mo("reset_sign_detection","next_run")],g("grid_import_power_entity"),g("grid_export_power_entity"),g("grid_co2_entity"),g("outdoor_temperature_entity"),...m.map(e=>[{raw:`${t("next_more_strings")} ${e.slot.toUpperCase()}`},So(i,e.slot,e.slot.toUpperCase())]),["next_more_system_size",po(e,"number.sem_system_size_kwp")],["next_more_forecast",go(i,"solar_forecast_source",jo,"auto")]]);o.push({key:"reads",titleKey:"next_g_reads",diagnoseSection:no,sentence:u,more:f.parts,count:f.count})}{const r=function(e,t,s){const i=lo(e,t);if(!co(i))return null;const r=Array.isArray(i.attributes?.options)?i.attributes.options:[];return{id:t,kind:"select",value:String(i.state),options:r.map(e=>({value:e,labelKey:s?s(e):null,label:s?null:e}))}}(e,"select.sem_hints",qo),a=s.notifyServices||[],n=a.length?go(i,"mobile_notification_service",[{value:"",labelKey:"config_notif_none"},...a],""):i&&i.mobile_notification_service?Co(i.mobile_notification_service):null,l=To(Ao([Eo(t,"next_misc_hints_to",{hints:r,service:n})||Eo(t,"next_misc_hints",{hints:r})]),Ao([Eo(t,"next_misc_interval",{interval:po(e,"number.sem_update_interval")})]),Ao([Eo(t,"next_misc_morning",{on:ho(e,"switch.sem_ev_morning_window_enabled"),hours:po(e,"number.sem_ev_morning_window_hours")})]),Ao([Eo(t,"next_misc_night",{offset:po(e,"number.sem_night_start_offset_min"),end:po(e,"number.sem_night_latest_end",{clock:!0})})||Eo(t,"next_misc_night_offset",{offset:po(e,"number.sem_night_start_offset_min")})]),Ao([Eo(t,"next_misc_vacation",{v:ho(e,"switch.sem_vacation_mode")})]),Ao([Eo(t,"next_misc_keep",{days:uo(i,"status_retention_days",{min:0,max:14,step:1,unit:t("config_retention_unit_days"),default:0,zeroKey:"config_retention_off"})})])),c=Oo(t,[["next_more_simulation_tab",ho(e,"switch.sem_simulation_tab")],["next_more_min_solar",po(e,"number.sem_minimum_solar_power")],["next_more_regulation_offset",po(e,"number.sem_regulation_offset")],["next_more_charger_notes",mo(i,"enable_charger_notifications",!0)],["next_more_mobile_notes",mo(i,"enable_mobile_notifications",!1)],["next_more_clean_up",Mo("purge_status_history","next_run")]]);o.push({key:"misc",titleKey:"next_g_misc",diagnoseSection:oo,sentence:l,more:c.parts,count:c.count})}return o}function ll(e,t){const s=[],i={},r={};let a=null,n=!1;const o=[];for(const l of Object.values(e||{})){const e=l&&l.slot;if(!e)continue;const c=l.value;if(e.id)if("number"===e.kind){const i=lo(t,e.id)?.attributes||{};let r=Number(c);const a=_o(i.min),n=_o(i.max);null!==a&&(r=Math.max(a,r)),null!==n&&(r=Math.min(n,r)),s.push({domain:"number",service:"set_value",data:{entity_id:e.id,value:r}})}else"select"===e.kind?s.push({domain:"select",service:"select_option",data:{entity_id:e.id,option:String(c)}}):"switch"===e.kind?s.push({domain:"switch",service:c?"turn_on":"turn_off",data:{entity_id:e.id}}):"text"===e.kind&&s.push({domain:"text",service:"set_value",data:{entity_id:e.id,value:String(c)}});else if("pv_string_names"===e.option&&e.pv){a||(a={...e.base||{}});const t=String(c??"").trim();t?a[e.pv]=t:delete a[e.pv],n=!0}else e.option&&Number.isInteger(e.listIdx)?(o.push({[e.option]:{slot:e.listIdx,value:c?String(c):null,battery_slot_units:e.units||null}}),n=!0):"ev_chargers"===e.option&&e.charger?(r[e.charger]||(r[e.charger]={id:e.charger}),r[e.charger][e.key]=c,n=!0):e.peak?Object.assign(i,Qe("limit"===c,null)):e.option&&(i[e.option]="number"===e.kind?Number(c):c,eo.has(e.option)&&(n=!0))}return Object.keys(r).length&&(i.ev_chargers=Object.values(r)),a&&(i.pv_string_names=a),{entityWrites:s,options:i,listEdits:o,reload:n}}function cl(e){if(!e)return null;const t=e.state;if(null==t||""===t||"unavailable"===t||"unknown"===t)return null;const s=parseFloat(t);if(Number.isNaN(s))return null;return"kw"===String((e.attributes||{}).unit_of_measurement||"").toLowerCase()?1e3*s:s}function _l(e,t){return null===e&&null===t?null:(e||0)-(t||0)}function dl(e){const t=[],s=cl(e("solar_power"));t.push({key:"solar",w:null===s?null:Math.max(0,s),dir:""});const i=_l(cl(e("grid_import_power")),cl(e("grid_export_power")));t.push({key:"grid",w:null===i?null:Math.abs(i),dir:null===i||Math.abs(i)<50?"":i>0?"buying":"sending"});const r=_l(cl(e("battery_charge_power")),cl(e("battery_discharge_power")));null!==r&&t.push({key:"battery",w:Math.abs(r),dir:Math.abs(r)<50?"":r>0?"charging":"giving"});const a=cl(e("home_consumption_power"));return t.push({key:"home",w:a,dir:""}),t}function pl(e){return null==e?"—":String(Math.round(e)).replace(/\B(?=(\d{3})+(?!\d))/g," ")+" W"}const hl=new Set(["w","kw","mw"]);function ul(e){const t=e&&e.attributes||{};if(t.device_class)return"power"===t.device_class;const s=String(t.unit_of_measurement||"").trim().toLowerCase();return hl.has(s)}const ml=new Set(["gco2eq/kwh","gco2e/kwh","gco2/kwh","kgco2eq/kwh","kgco2e/kwh","kgco2/kwh","gco2eq/mwh","kgco2eq/mwh","kgco2/mwh","tco2/mwh","tco2eq/mwh"]);function gl(e){const t=e&&e.attributes||{},s=String(t.unit_of_measurement||"").toLowerCase().replace(/\s+/g,"").replace(/\u2082/g,"2");return ml.has(s)}function fl(e){const t=String(e&&e.entity_id||"");if(t.startsWith("weather."))return!t.startsWith("weather.forecast_");const s=e&&e.attributes||{},i=String(s.unit_of_measurement||"").trim().toLowerCase();return"temperature"===s.device_class||"°c"===i||"°f"===i}function yl(e){const t=e&&e.suggested_charger,s=e&&e.missing||[];return t&&t.id&&0===s.length?"add":function(e){const t=e&&e.suggested_charger;if(t&&t.id)return!1;const s=e&&e.missing||[],i=e&&e.charger_roles||[];return 1===s.length&&"control"===s[0]&&i.length>0}(e)?"watch":"report"}function bl(e){return{add:"config_detect_offer",watch:"config_detect_watch_only",report:"config_detect_near_miss"}[yl(e)]}function vl(e){return(e&&e.choose_vehicle||[]).length?"config_detect_choose_vehicle_help":"config_detect_watch_only_help"}function xl(e,t,s){return e&&e.id?(t||[]).includes(e.id)?{action:"exists",id:e.id}:{action:"add",charger:{...e,ev_min_current:6,ev_surplus_priority:(s||0)+3}}:{action:"none"}}function $l(e,t,s){if(!e||!e.config_key||!t)return null;if(!e.pair)return{[e.config_key]:t};const i=e.pair[t];if(!i||!Object.keys(i).length)return null;const r={[e.config_key]:t};for(const[a,n]of Object.entries(i)){if(a===e.config_key)return null;const i=(n||[]).filter(e=>e&&e!==t),o=s&&s[a],l=i.includes(o)?o:i[0];if(!l)return null;r[a]=l}return r}function wl(e,t){return!!e&&Object.entries(e).every(([e,s])=>!!t&&t[e]===s)}const kl="solar_energy_management",Sl="sensor.sem_diag_charger_control";function Cl(e){switch(e&&e.action){case"observe_only":return{key:"config_proposed_observe_only"};case"not_loaded":return{key:"config_proposed_not_loaded"};case"no_unit":return{key:"config_proposed_no_unit",vars:{wanted:e.unit_wanted||""}};case"unit_mismatch":return{key:"config_proposed_unit_mismatch",vars:{seen:e.unit_seen||"?",wanted:e.unit_wanted||""}};case"options_unmapped":return{key:"config_proposed_options_unmapped",vars:{missing:(e.values_missing||[]).join(", "),options:(e.options||[]).join(", ")}};case"pair_incomplete":return{key:"config_proposed_pair_incomplete",vars:{missing:(e.missing_role||[]).join(", ")}};case"pair_blocked":return{key:"config_proposed_pair_blocked",vars:{missing:(e.missing_role||[]).join(", ")}};case"per_charger":return{key:"config_proposed_per_charger"};case"set_option":return e.reason?{text:e.reason}:null;default:return e&&e.reason?{text:e.reason}:null}}function Ml(e,t,s){const i="set_option"===t.action?$l(t,t.entity,s):null,r=(t.alternatives||[]).filter(e=>e&&e.entity).map(e=>{const i=!e.action||"set_option"===e.action,r="set_option"===t.action&&i?$l(t,e.entity,s):null;return{entity:e.entity,matchedKey:e.matched_key||"",write:r,saved:wl(r,s),why:"set_option"!==t.action||i?null:Cl(e)}}),a=wl(i,s);return{role:e,what:t.entity||t.service||(t.candidates||[]).join(" / ")||"",matchedKey:t.matched_key||"",write:i&&!a?i:null,saved:a,why:Cl(t),alts:r}}function zl(e){if("report"!==yl(e))return"";const t=String.fromCharCode(96),s=e=>t+e+t,i=(e.entities||[]).map(e=>"- "+s(e.entity)+" ("+e.domain+(e.device_class?"/"+e.device_class:"")+")").join("\n"),r=["**Detected hardware — no role matched**","","Integration: "+(e.roster?.name?e.roster.name+" ("+s(e.platform)+")":s(e.platform)),e.roster?.installs?"Installs (HA analytics): "+e.roster.installs:"","","Entities on this device:",i,"","What the device is and which entity does what:","(please fill in)"].filter(Boolean).join("\n");return"https://github.com/traktore-org/sem-community/issues/new?labels=enhancement&title="+encodeURIComponent("Detected hardware: "+e.platform+" — no role matched")+"&body="+encodeURIComponent(r)}function Dl(e,t){const s=new Set(((t||{}).ev_chargers||[]).map(e=>e&&e.id).filter(Boolean));for(const t of Object.keys(e||{})){const e=t.match(/^number\.sem_charger_(.+)_minimum_current$/);e&&s.add(e[1])}return[...s]}const Nl=a` + :host { display: block; } + .sheet { padding: 4px 0; } + .top { display: flex; align-items: center; gap: 8px; margin: 0 0 10px; } + .top h2 { flex: 1; margin: 0; font-size: 18px; font-weight: 600; } + .mono { font-family: var(--code-font-family, monospace); font-size: 12px; word-break: break-all; } + .warn { color: var(--n-warn); } + .bad { color: var(--n-bad); } + .ok { color: var(--n-ok); } + .err { margin: 8px 0; padding: 8px 10px; border-radius: 10px; border-left: 4px solid var(--n-bad); background: var(--n-card2); font-size: 13px; } + .msg { margin: 8px 0; font-size: 13px; color: var(--n-fg2); } + .items { display: flex; flex-wrap: wrap; gap: 6px; margin: 8px 0; } + .acts { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin: 6px 0 10px; } + .bar { height: 6px; border-radius: 3px; background: var(--n-card2); overflow: hidden; margin: 6px 0; } + .bar > i { display: block; height: 100%; background: var(--n-accent); } + .bar > i.done { background: var(--n-ok); } + .prop { padding: 6px 0; border-top: 1px solid var(--n-line); } + .prop:first-child { border-top: 0; } + .prop .k { font-weight: 500; font-size: 13px; } +`;customElements.get("sem-next-setup-panel")||customElements.define("sem-next-setup-panel",class extends Bs{static get properties(){return{kind:{type:String},_options:{state:!0},_status:{state:!0},_err:{state:!0},_msg:{state:!0},_busy:{state:!0}}}static get styles(){return[Os,Nl]}constructor(){super(),this.kind="check",this._options=null,this._entryId="",this._status={},this._err="",this._msg="",this._busy=!1}_watchIds(){return[...super._watchIds(),Sl,"sensor.sem_solar_power","sensor.sem_grid_import_power","sensor.sem_grid_export_power","sensor.sem_battery_charge_power","sensor.sem_battery_discharge_power","sensor.sem_home_consumption_power","sensor.sem_charging_state","binary_sensor.sem_heat_pump_registered"]}set hass(e){const t=!this._hass&&e;super.hass=e,t&&this._loadOptions()}get hass(){return this._hass}async _loadOptions(){if(this._hass)try{const e=await this._hass.callService(kl,"get_config",{},void 0,!1,!0),t=e?.response?.config;t&&"object"==typeof t&&(this._options=t),e?.response?.entry_id&&(this._entryId=e.response.entry_id)}catch(e){this._fail(e)}}_fail(e){this._err=e&&e.message?String(e.message):String(e||this._t("next_apply_failed"))}async _save(e,t){if(this._hass&&e&&"saving"!==this._status[t]){this._err="",this._status={...this._status,[t]:"saving"};try{await this._hass.callService(kl,"set_option",{options:{...e},...this._entryId?{entry_id:this._entryId}:{}},void 0,!1),this._status={...this._status,[t]:"ok"},this._options={...this._options||{},...e},await this._loadOptions()}catch(e){this._status={...this._status,[t]:""},this._fail(e)}}}async _addCharger(e){if(this._busy||!this._hass)return;const t=this._options||{},s=xl(e,Dl(this._hass.states,t),(t.ev_chargers||[]).length);if("add"===s.action){this._busy=!0,this._err="";try{await this._hass.callService(kl,"set_option",{options:{ev_chargers:[s.charger]},...this._entryId?{entry_id:this._entryId}:{}},void 0,!1),await this._loadOptions()}catch(e){this._fail(e)}finally{this._busy=!1}}}async _flip(e){if(!this._busy&&this._hass&&!this._obs()){this._busy=!0,this._err="",this._msg="";try{const t=await this._hass.callService(kl,"battery"===e?"flip_battery_sign":"flip_grid_sign",{},void 0,!1,!0),s=t&&t.response?t.response:t;let i=!1;try{await navigator.clipboard.writeText(JSON.stringify(s||{},null,2)),i=!0}catch(e){i=!1}this._msg=this._t(i?"sign_flipped_copied":"sign_flipped")}catch(e){this._fail(e)}finally{this._busy=!1}}}_close(e=""){this.dispatchEvent(new CustomEvent("sem-next-close",{detail:e?{goto:e}:{},bubbles:!0,composed:!0}))}_openHaSettings(){this._go("/config/integrations/integration/"+kl)}_say(e){return e?e.text?e.text:e.vars?this._tf(e.key,e.vars):this._t(e.key):""}_roleLabel(e){const t=Zo[e];if(t&&t.labelKey)return this._t(t.labelKey);const s="next_role_"+e,i=this._t(s);return i&&i!==s?i:e}render(){if(!this._hass)return H;const e=e=>this._t(e),t="hardware"===this.kind?e("config_section_detected_hardware"):e("next_setup_check");return j`
+
+

${t}

+ ${this._btn(e("next_panel_close"),()=>this._close(),{small:!0,icon:"mdi:close"})} +
+ ${this._err?j``:H} + ${this._msg?j`
${this._msg}
`:H} + ${"hardware"===this.kind?this._hardware():this._check()} +
`}_check(){const e=e=>this._t(e),t=function(e,t){const s=tl(e||{},t||{}),i=s.filter(e=>!e.optional),r=i.filter(e=>e.done).length,a=i.length,n=dl(t=>(e||{})["sensor.sem_"+t]);return{items:s,done:r,total:a,pct:a?Math.round(r/a*100):100,allDone:r===a,sources:n,hasBattery:n.some(e=>"battery"===e.key)}}(this._hass.states,this._options),s=t.allDone?e("config_setup_done"):this._tf("config_setup_progress",{done:t.done,total:t.total}),i=t.items.map(t=>this._row(t.done?"mdi:check-circle":"mdi:circle-outline",e(t.labelKey),t.done?j`✓`:t.optional?e("config_setup_optional"):this._link(e("config_setup_action"),()=>this._close(t.key)),"")),r=t.sources.map(t=>this._row("",e("config_check_"+t.key),(t.dir?e("config_check_"+t.dir)+" ":"")+pl(t.w),"")),a=this._obs();return j` + ${this._sec(s,j`
+
${i}
+

${e("config_overview_help")}

+
${this._btn(e("config_open_ha_settings"),()=>this._openHaSettings(),{small:!0,icon:"mdi:cog-outline"})}
`)} + ${this._sec(e("config_check_intro"),j`
${r}
+

${e("config_check_question")}

+
+ ${this._btn(e("config_check_grid_wrong"),()=>this._flip("grid"),{small:!0,icon:"mdi:swap-vertical-bold",disabled:this._busy||a})} + ${t.hasBattery?this._btn(e("config_check_battery_wrong"),()=>this._flip("battery"),{small:!0,icon:"mdi:swap-vertical-bold",disabled:this._busy||a}):H} +
+

${e("config_check_wrong_sensor")}

`)}`}_useBtn(e,t){const s=this._status[t];return e?j`${this._btn("saving"===s?this._t("config_proposed_using"):this._t("config_proposed_use"),()=>this._save(e,t),{small:!0,primary:!0,disabled:"saving"===s||this._obs()})}${"ok"===s?j` ✓`:H}`:H}_proposal(e,t){const s=e=>this._t(e);return j`
+
${e.role}
+
${e.what}${e.matchedKey?j` · ${e.matchedKey}`:H}
+
+ ${this._useBtn(e.write,t)} + ${e.saved?j`${s("config_proposed_already")}`:H} + ${e.why?j`${this._say(e.why)}`:H} +
+ ${e.alts.map((e,i)=>j`
+ ${s("config_proposed_or")} + ${e.entity}${e.matchedKey?" · "+e.matchedKey:""} + ${e.write&&!e.saved?this._useBtn(e.write,t+"_alt"+i):H} + ${e.why?j`${this._say(e.why)}`:H} +
`)} +
`}_hardware(){const e=e=>this._t(e),t=function(e,t,s){if(!e)return null;const i=t||{},r=s||[],a=(i.ev_chargers||[]).length,n=e.census||{},o=n.unknown_energy_domains_named||[],l=(e.near_misses||[]).map(e=>{const t=e.suggested_charger?.id?xl(e.suggested_charger,r,a):null,s=e.suggested_charger?.id?"add":yl(e);return{name:e.roster?.name||e.platform,labelKey:bl(e),roles:(e.charger_roles||[]).join(", "),entities:(e.entities||[]).map(e=>e.entity),action:s,plan:t,suggested:e.suggested_charger||null,watchKey:"watch"===s?vl(e):"",reportUrl:"report"===s?zl(e):"",proposals:Object.entries(e.proposed_roles||{}).map(([e,t])=>Ml(e,t,i))}}),c=(e.chargers||[]).map(e=>({platform:e.platform,name:e.name||"",control:e.control||"",configured:!!e.configured,mapped:Object.entries(e.mapped||{}).map(([e,t])=>({role:e,value:t&&(t.entity||t.value)||"—",kind:t&&t.device_class?t.domain+"/"+t.device_class:""})),unmapped:(e.unmapped||[]).map(e=>e.entity)})),_=(e.roster_proposals||[]).map(e=>({name:e.roster?.name||e.domain,proposals:Object.entries(e.proposed_roles||{}).map(([e,t])=>Ml(e,t,i))})),d=(e.disagreements||[]).filter(e=>"prober_only"===e.kind).map(e=>e.platform);return{unknown:(n.unknown_energy_domains||[]).map(e=>{const t=o.find(t=>t&&t.domain===e);return t&&t.name?{name:t.name,installs:t.installs||0}:{name:e,installs:0}}),nomatch:n.rows_matched_nothing||[],chargers:c,misses:l,roster:_,proberOnly:d,empty:!c.length&&!l.length&&!(e.prober_candidates||[]).length}}(this._attrs(Sl).detection_report||null,this._options,Dl(this._hass.states,this._options));if(!t)return j`

${e("config_detect_none")}

`;const s=this._obs(),i=[];t.unknown.length&&i.push(this._row("mdi:help-circle-outline",e("config_census_unknown"),"",t.unknown.map(t=>t.installs?t.name+" · "+t.installs+" "+e("config_census_installs"):t.name).join(", "),{cls:"warn"})),t.nomatch.length&&i.push(this._row("mdi:help-circle-outline",e("config_census_nomatch"),"",t.nomatch.join(", "),{cls:"warn"}));const r=t.chargers.map(t=>this._sec(e("config_detect_charger")+": "+t.platform+(t.name?" · "+t.name:""),j` +
${t.control?this._pill(t.control):H}${t.configured?this._pill(e("config_detect_configured"),"ok"):H}
+
${t.mapped.map(e=>this._row("",this._roleLabel(e.role),j`${e.value}`,e.kind))}
+ ${t.unmapped.length?j`

${e("config_detect_unmapped")}: ${t.unmapped.join(", ")}

`:H}`)),a=t.misses.map((t,i)=>this._sec("⚠ "+t.name,j` +

${this._tf(t.labelKey,{roles:t.roles})}

+
${t.entities.join(", ")}
+
+ ${"add"===t.action?t.plan&&"add"===t.plan.action?this._btn(e("config_near_miss_add"),()=>this._addCharger(t.suggested),{small:!0,primary:!0,icon:"mdi:ev-station",disabled:this._busy||s}):this._pill(e("config_detect_configured"),"ok"):H} + ${"watch"===t.action?j`${e(t.watchKey)}`:H} + ${"report"===t.action&&t.reportUrl?j`${e("config_near_miss_report")}`:H} +
+ ${t.proposals.length?j`
${e("config_proposed_roles")}${e("config_proposed_unconfirmed")}
+ ${t.proposals.map(e=>this._proposal(e,"prop_"+i+"_"+e.role))} +

${e("config_proposed_help")}

`:H}`,{cls:"warn"})),n=t.roster.map((t,s)=>this._sec("🧩 "+t.name,j` +
${e("config_proposed_unconfirmed")}
+ ${t.proposals.map(e=>this._proposal(e,"roster_"+s+"_"+e.role))}`));return j` +

${e("config_detect_intro")}

+ ${i.length?j`
${i}
`:H} + ${r} + ${a} + ${n} + ${t.roster.length?j`

${e("config_proposed_help")}

`:H} + ${t.proberOnly.map(t=>this._row("mdi:magnify",t,"",e("config_detect_prober_only")))} + ${t.empty?j`

${e("config_detect_nothing")}

`:H}`}});const El={power:ul,co2_intensity:gl,outdoor_temperature:fl},Fl=["sensor.sem_diag_version","sensor.sem_diag_grid_mode","sensor.sem_diag_grid_sign","sensor.sem_diag_battery_sign","sensor.sem_diag_charger_count","sensor.sem_diag_charger_control","sensor.sem_diag_battery_capacity","sensor.sem_diag_update_interval","sensor.sem_diag_sensors_unavailable","sensor.sem_diag_inputs_degraded","sensor.sem_diag_ed_config","sensor.sem_energy_plan","sensor.sem_charging_state","sensor.sem_controllable_devices_count","sensor.sem_tariff_provider","binary_sensor.sem_heat_pump_registered"],Al=["number.sem_electricity_import_rate","number.sem_electricity_export_rate","number.sem_demand_charge_rate","number.sem_cheap_price_threshold","number.sem_expensive_price_threshold","switch.sem_export_guard_enabled","number.sem_export_guard_engage_s","number.sem_export_guard_release_s","switch.sem_export_guard_override_external","number.sem_battery_priority_soc","number.sem_battery_buffer_soc","number.sem_battery_auto_start_soc","switch.sem_forecast_spending_enabled","switch.sem_battery_charge_pacing_enabled","switch.sem_battery_may_export","switch.sem_battery_may_assist_ev","number.sem_battery_max_discharge_power","number.sem_battery_morning_drain_floor_soc","switch.sem_battery_house_sink_enabled","number.sem_battery_assist_max_power","number.sem_battery_assist_min_surplus","number.sem_ev_enable_delay_seconds","number.sem_ev_disable_delay_seconds","number.sem_heat_pump_boost_offset","number.sem_hot_water_solar_target","number.sem_legionella_target_temp","number.sem_legionella_interval_hours","number.sem_system_size_kwp","select.sem_hints","number.sem_update_interval","switch.sem_ev_morning_window_enabled","number.sem_ev_morning_window_hours","number.sem_night_start_offset_min","number.sem_night_latest_end","switch.sem_vacation_mode","switch.sem_simulation_tab","number.sem_minimum_solar_power","number.sem_regulation_offset"],Tl=["ev_phases","minimum_current","maximum_current","ev_battery_capacity_kwh","ev_kwh_per_100km"],Ol={next_src_solar:{icon:"mdi:white-balance-sunny",color:Ts.solar},next_src_grid:{icon:"mdi:transmission-tower",color:Ts.grid},next_src_battery:{icon:"mdi:battery-charging",color:Ts.battery_out},next_src_car:{icon:"mdi:car-electric",color:Ts.ev},next_src_forecast:{icon:"mdi:weather-sunny",color:Ts.solar},next_src_price:{icon:"mdi:cash",color:Ts.fg2}},Bl=a` + .val.dirty { color: var(--n-accent); text-decoration-color: var(--n-accent); text-decoration-style: solid; } + .sent .edit { display: inline-flex; align-items: center; gap: 4px; vertical-align: baseline; } + .sent input, .sent select, .help input, .help select { font-size: 13px; padding: 2px 6px; max-width: 130px; min-height: 28px; } + .sent .ro { font-weight: 500; color: var(--n-fg); } + .fresh .tile .xs.warn { color: var(--n-warn); } + .fresh .tile .xs.bad { color: var(--n-bad); } + .fresh .tile .srcid { font-family: var(--code-font-family, monospace); overflow-wrap: anywhere; } + .confirm { display: inline-flex; gap: 6px; align-items: center; flex-wrap: wrap; font-size: 13px; } + .ct .lnk.ic { padding: 2px 4px; } + .ct .lnk.ic ha-icon { color: var(--n-fg3); } + .sent .edit.pick, .help .edit.pick { display: inline-flex; min-width: min(320px, 100%); max-width: 100%; vertical-align: middle; } + .edit.pick ha-entity-picker { flex: 1; min-width: 0; } + .chlist { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-top: 6px; } +`;ke("sem-next-system-card",class extends Bs{static get styles(){return[Os,Bl]}static get properties(){return{...super.properties,_pending:{state:!0},_editing:{state:!0},_options:{state:!0},_applying:{state:!0},_confirmObs:{state:!0},_diagBusy:{state:!0},_panel:{state:!0},_chargerBusy:{state:!0},_confirmRemove:{state:!0},_pickerReady:{state:!0}}}constructor(){super(),this._pending={},this._editing="",this._options=null,this._entryId="",this._applying=!1,this._confirmObs=!1,this._diagBusy=!1,this._optionsAsked=!1,this._panel="",this._chargerBusy=!1,this._confirmRemove="",this._pickerReady=!1}set hass(e){super.hass=e,e&&!this._optionsAsked&&(this._optionsAsked=!0,this._loadOptions())}get hass(){return this._hass}_watchIds(){const e=this._hass?.states||{},t=[];for(const e of this._chargers)for(const s of Tl)t.push(`number.sem_charger_${e}_${s}`);return[...super._watchIds(),...Fl,...Al,...t,...Qo(e),...bi.map(e=>e.entity),...sl(e).map(e=>e.entity)]}updated(e){if(super.updated(e),e.has("_editing")&&this._editing){const e=this.renderRoot?.querySelector(".edit input, .edit select");e&&(e.focus(),e.select&&e.select())}}async _loadOptions(){if(this._hass)try{const e=await this._hass.callService("solar_energy_management","get_config",{},void 0,void 0,!0),t=e?.response?.config;t&&"object"==typeof t&&(this._options=t),e?.response?.entry_id&&(this._entryId=e.response.entry_id)}catch(e){console.warn("[sem-next-system-card] get_config failed",e)}}_say(e,t=2500,s=!1){this._toast(e,s,t)}_inputsDegraded(){const e=String(this._str("sensor.sem_diag_inputs_degraded")||"").toLowerCase();return"true"===e||"on"===e}_freshRows(){const e=this._hass.states,t=Date.now(),s=e["sensor.sem_diag_ed_config"]?.attributes?.energy_dashboard,i=function(e,t=Date.now()){const s=[];for(const i of bi){const r=e?.[i.entity];if(!r)continue;const a=wi(r,t,{okS:i.okS??null,oldS:i.oldS??null}),n=null!=i.okS;s.push({key:i.key,entity:i.entity,state:a,age:n?$i(r,t):null,value:r.state})}return s}(e,t).map(e=>({...e,name:this._t(e.key),src:xi(e.key,s),...Ol[e.key]}));for(const s of sl(e)){const r=e[s.entity];i.push({key:s.id,name:s.name,entity:s.entity,src:s.entity,state:wi(r,t),age:null,icon:"mdi:power-plug",color:Ts.home})}return i}_freshTiles(e){return j`
${e.map(e=>{const t="ok"===e.state?"dim":"old"===e.state?"warn":"bad",s="ok"===e.state||"old"===e.state?(null!==(i=e.age)&&Number.isFinite(i)?i<90?`${Math.round(i)} s`:i<5400?`${Math.round(i/60)} min`:`${Math.round(i/3600)} h`:"")||this._t("next_fresh_ok"):this._t("next_fresh_"+e.state);var i;const r=this._hass.states[e.entity],a=r?"function"==typeof this._hass.formatEntityState?this._hass.formatEntityState(r):`${r.state}${r.attributes?.unit_of_measurement?" "+r.attributes.unit_of_measurement:""}`:"";return j`
this._moreInfo(e.src&&this._hass.states[e.src]?e.src:e.entity)}> + ${this._icon(e.icon)}
${e.name}
${a?a+" · ":""}${s}
+ ${e.src?j`
${e.src}
`:H}
`})}
`}_setupRow(){const e=tl(this._hass.states,this._options).filter(e=>!e.optional),t=e.filter(e=>e.done).length,s=t===e.length?this._t("config_setup_done"):this._tf("config_setup_progress",{done:t,total:e.length}),i=this._valLabel("diag_grid_sign"),r=this._valLabel("diag_battery_sign"),a=[i?`${this._t("next_grid_sign")} ${i}`:"",r?`${this._t("next_battery_sign")} ${r}`:""].filter(Boolean).join(" · ");return this._row("mdi:check-decagram",this._t("next_setup_check"),s,a,{onClick:()=>this._togglePanel("setup")})}_hardwareRow(){const e=this._str("sensor.sem_diag_charger_count"),t=this._valLabel("diag_charger_control"),s=this._num("sensor.sem_diag_battery_capacity"),i=this._valLabel("diag_grid_mode"),r=this._attrs("sensor.sem_diag_charger_control").detection_report||null,a=r?(r.near_misses||[]).length:null,n=el(this._hass.states).length,o=[""!==e?this._tf("next_chargers_n",{n:e}):"",t?`(${t})`:""].filter(Boolean).join(" ")||Ee,l=[n?this._tf("next_strings",{n:n}):"",null!==s&&s>0?this._tf("next_batt_cap",{kwh:s.toFixed(1)}):"",i?`${this._t("next_grid_mode")} ${i}`:"",null!==a?this._tf("next_unmapped",{n:a}):""].filter(Boolean).join(" · ");return this._row("mdi:radar",this._t("next_hardware"),o,l,{onClick:()=>this._togglePanel("hardware")})}_togglePanel(e){this._panel=this._panel===e?"":e}_setupPanel(e){return j`{this._panel=""}}>`}_unavailableRow(){const{n:e,m:t}=function(e){const t=lo(e,"sensor.sem_diag_sensors_unavailable");return{n:co(t)?_o(t.state):null,m:Object.keys(e||{}).filter(e=>/^[a-z_]+\.sem_/.test(e)).length}}(this._hass.states),s=null===e?Ee:this._tf("next_n_of_m",{n:e,m:t}),i=this._inputsDegraded()?this._t("next_inputs_degraded"):"";return this._row("mdi:lan-disconnect",this._t("next_unavailable"),s,i,{onClick:()=>this._moreInfo("sensor.sem_diag_sensors_unavailable")})}_versionRow(){const e=this._str("sensor.sem_diag_version"),t=this._hass.config?.version||"",s=this._num("number.sem_update_interval")??this._num("sensor.sem_diag_update_interval"),i=[t?`HA ${t}`:"",null!==s?this._tf("next_every_s",{s:Math.round(s)}):""].filter(Boolean).join(" · ");return this._row("mdi:new-box",this._t("version"),e||Ee,i,{onClick:()=>this._moreInfo("sensor.sem_diag_version")})}_observerButton(){const e=this._obs();return this._confirmObs?j`${this._t(e?"next_obs_confirm_off":"next_obs_confirm_on")} + ${this._btn(this._t("next_yes"),()=>{this._confirmObs=!1,this._toggleSwitch("switch.sem_observer_mode")},{small:!0,primary:!0})} + ${this._btn(this._t("next_cancel"),()=>{this._confirmObs=!1},{small:!0,ghost:!0})}`:this._btn(j`${this._t("next_observer")}: ${e?this._t("on"):this._t("off")} · ${this._t(e?"next_turn_off":"next_turn_on")}`,()=>{this._confirmObs=!0},{small:!0,icon:"mdi:eye-outline"})}_health(e){return this._sec(this._t("next_health"),j` + ${this._freshTiles(e)} +
${this._setupRow()}${"setup"===this._panel?this._setupPanel("setup"):H}${this._hardwareRow()}${"hardware"===this._panel?this._setupPanel("hardware"):H}${this._unavailableRow()}${this._versionRow()}
+
+ ${this._btn(this._t("copy_diagnostics"),()=>this._copyDiagnostics(),{small:!0,icon:"mdi:content-copy"})} + ${this._btn(this._t("config_diagnose"),()=>this._diagnose("all"),{small:!0,icon:"mdi:stethoscope",disabled:this._diagBusy})} + ${this._btn(this._t("config_open_ha_settings"),()=>this._go("/config/integrations/integration/solar_energy_management"),{small:!0,icon:"mdi:cog-outline"})} + ${this._observerButton()} +
+`,{cls:"span2"})}_refused(){const e=this._attrs("switch.sem_observer_mode"),t=this._obs(),s=Object.entries(e.withheld_commands||{}).flatMap(([e,t])=>(Array.isArray(t)?t:[t]).filter(e=>e&&"object"==typeof e).map(t=>({...t,name:t.name||e,device_id:t.device_id||e,action:t.action||t.service||""}))),i=Object.values(e.would_decisions||{}).filter(e=>e&&e.action&&"idle"!==e.action),r=t?i:s,a=e=>this._t(e),n=r.length?j`
${r.slice(0,8).map(e=>this._row("mdi:hand-back-left-outline",`${oi(e,a)}${e.action?" · "+e.action:""}`,Number.isFinite(Number(e.power_w))?this._kw(Number(e.power_w)):"",pi(e,a)||e.reason||""))}
`:j`
${t?this._t("next_would_none"):this._t("next_refused_none")}
`;return this._sec(t?this._t("next_would_do"):this._t("next_held_back"),n)}_ctx(){const e=this._hass.services||{},t=[];for(const s of Object.keys(e.notify||{}))t.push({value:s,label:"notify."+s});for(const s of Object.keys(e.rest_command||{}))t.push({value:s,label:"rest_command."+s});return{options:this._options,currency:this._currency(),chargers:this._chargerList(),labels:{control:this._valLabel("diag_charger_control"),provider:this._valLabel("tariff_provider")||this._str("sensor.sem_tariff_provider"),gridSign:this._valLabel("diag_grid_sign"),battSign:this._valLabel("diag_battery_sign")},notifyServices:t}}_chargerList(){const e=[],t=new Set;for(const s of this._options?.ev_chargers||[])s&&s.id&&!t.has(s.id)&&(t.add(s.id),e.push({cid:s.id,name:this._chargers.includes(s.id)?this._chargerName(s.id):s.name||s.id}));for(const s of this._chargers)t.has(s)||(t.add(s),e.push({cid:s,name:this._chargerName(s)}));return e}_part(e){return"string"==typeof e?e:e&&"object"==typeof e?e.ro?j`${e.value}`:"action"===e.kind&&e.nav?this._val(this._t(e.labelKey),()=>this._go(function(e,t){const s={open_battery:"devices"}[t];if(!s)return e||"";const i=String(e||"").split("/").filter(Boolean);return i.length?i[i.length-1]=s:i.push(s),"/"+i.join("/")}(window.location.pathname,e.action))):"action"===e.kind?this._val(this._t(e.labelKey),()=>this._action(e.action),{disabled:this._obs()}):this._slot(e):""}_slot(e){const t=zo(e),s=this._pending[t],i=s?s.value:e.value,r=this._obs(),a=e=>this._t(e);return"switch"===e.kind?j``:this._editing!==t||r?j``:this._editor(e,i)}_edit(e,t){this._editing=t,"entity"===e.kind&&this._ensureEntityPicker()}async _ensureEntityPicker(){if(customElements.get("ha-entity-picker"))this._pickerReady=!0;else{try{const e=await(window.loadCardHelpers?.());if(e?.createCardElement){const t=await e.createCardElement({type:"entities",entities:[]});await(t.constructor?.getConfigElement?.())}}catch(e){}this._pickerReady=!!customElements.get("ha-entity-picker")}}_entityEditor(e,t,s){const i=t=>{this._stage(e,t||""),s()},r=e=>{"Escape"===e.key&&(e.preventDefault(),s())};if(!customElements.get("ha-entity-picker"))return j`i(e.target.value.trim())} @keydown=${e=>{r(e),"Enter"===e.key&&(e.preventDefault(),i(e.target.value.trim()))}}>`;const a=e.deviceClass;return j`i(e.detail?.value||"")}> + ${this._btn(this._t("next_cancel"),s,{small:!0,ghost:!0})}`}_editor(e,t){const s=()=>{this._editing===zo(e)&&(this._editing="")},i=e=>{"Enter"!==e.key&&"Escape"!==e.key||(e.preventDefault(),e.target.blur())},r=e=>this._t(e);return"entity"===e.kind?this._entityEditor(e,t,s):"select"===e.kind?j``:"number"===e.kind?j`this._stage(e,t.target.value)} @blur=${s} @keydown=${i}>${e.unit?j` ${e.unit}`:H}`:j`this._stage(e,t.target.value)} @blur=${s} @keydown=${i}>`}_stage(e,t){const s=zo(e);if(!s)return;let i=t;if("number"===e.kind&&(i=Number(t),""===t||!Number.isFinite(i)))return;"switch"===e.kind&&(i=!!t);const r="number"===e.kind?Number(e.value)===i:String(e.value)===String(i),a={...this._pending};r?delete a[s]:a[s]={slot:e,value:i},this._pending=a}_group(e){const t=e.count||0,s=j``;return this._sec(this._t(e.titleKey),j` +

${e.sentence.map(e=>this._part(e))}

+ ${"chargers"===e.key?this._chargerTools():H} + ${t?j`

${e.more.map(e=>this._part(e))}${this._tf("config_more_in_all",{n:t})}

`:H}`,{cls:"group",extra:s})}_chargerTools(){const e=this._obs()||this._chargerBusy||this._applying||!this._options,t=this._chargerList(),s=e=>this._t(e);return j`
+ ${this._btn(s("config_ev_add_charger"),()=>this._addCharger(),{small:!0,icon:"mdi:plus",disabled:e})} + ${t.length?j`${t.map(t=>this._confirmRemove===t.cid?j`${s("config_ev_remove_confirm")} ${t.name} + ${this._btn(s("next_yes"),()=>this._removeCharger(t.cid),{small:!0,primary:!0,disabled:e})} + ${this._btn(s("next_cancel"),()=>{this._confirmRemove=""},{small:!0,ghost:!0})}`:this._btn(s("config_ev_remove")+" "+t.name,()=>{this._confirmRemove=t.cid},{small:!0,ghost:!0,icon:"mdi:delete-outline",disabled:e}))}`:H} +
`}async _setOptions(e){await this._hass.callService("solar_energy_management","set_option",{options:e,...this._entryId?{entry_id:this._entryId}:{}},void 0,!1)}async _addCharger(){if(this._chargerBusy||!this._options||this._obs())return;const e=function(e,t,s){const i=Array.isArray(e)?e:[],r=new Set([...i.map(e=>e&&e.id).filter(Boolean),...t||[]]);let a="ev_charger",n=1;for(;r.has(a);)a="ev_charger_"+n++;return{id:a,name:`${s} ${i.length+1}`,ev_min_current:6,ev_surplus_priority:i.length+3}}(this._options.ev_chargers,this._chargers,this._t("config_ev_new_charger"));this._chargerBusy=!0;try{await this._setOptions({ev_chargers:[e]}),await this._loadOptions(),this._say(this._t("config_saved"))}catch(e){console.error("[sem-next-system-card] add charger failed",e),this._say(e?.message||this._t("next_apply_failed"),8e3,!0)}finally{this._chargerBusy=!1}}async _removeCharger(e){if(!this._chargerBusy&&e&&!this._obs()){this._chargerBusy=!0,this._confirmRemove="";try{await this._hass.callService("solar_energy_management","remove_charger",{charger_id:e},void 0,!1);const t={...this._pending};for(const s of Object.keys(t))s.startsWith(`opt:ev_chargers:${e}:`)&&delete t[s];this._pending=t,await this._loadOptions(),this._say(this._t("config_saved"))}catch(e){console.error("[sem-next-system-card] remove charger failed",e),this._say(e?.message||this._t("next_apply_failed"),8e3,!0)}finally{this._chargerBusy=!1}}}_applyBar(){const e=Object.keys(this._pending).length;if(!e&&!this._applying)return H;const t=ll(this._pending,this._hass.states),s=this._applying?this._t("config_applying"):this._tf("next_changes_waiting",{n:e})+(t.reload?" · "+this._t("next_reloads"):"");return j`
${s} + ${this._btn(this._t("config_discard"),()=>this._discard(),{ghost:!0,disabled:this._applying})} + ${this._btn(this._t("config_apply"),()=>this._apply(),{primary:!0,disabled:this._applying})}
`}_discard(){this._pending={},this._editing=""}async _apply(){if(this._applying||!Object.keys(this._pending).length||!this._hass)return;this._applying=!0,this._editing="";const e=ll(this._pending,this._hass.states);try{for(const t of e.entityWrites)await this._hass.callService(t.domain,t.service,t.data,void 0,!1);Object.keys(e.options).length&&await this._hass.callService("solar_energy_management","set_option",{options:e.options,...this._entryId?{entry_id:this._entryId}:{}},void 0,!1);for(const t of e.listEdits||[])await this._setOptions(t);this._pending={},this._say(this._t("config_saved")),await this._loadOptions()}catch(e){console.error("[sem-next-system-card] apply failed",e),this._say(e?.message||this._t("next_apply_failed"),8e3,!0)}finally{this._applying=!1}}async _action(e){if(this._hass&&!this._obs())try{if("flip_grid_sign"===e||"flip_battery_sign"===e){const t=await this._hass.callService("solar_energy_management",e,{},void 0,!1,!0),s=t&&t.response?t.response:t,i=await this._writeClipboard(JSON.stringify(s||{},null,2),!0);this._say(this._t(i?"sign_flipped_copied":"sign_flipped"),6e3)}else"reset_sign_detection"===e?(await this._hass.callService("solar_energy_management","reset_sign_detection",{},void 0,!1),this._say(this._t("sign_relearn_started"),4e3)):"purge_status_history"===e&&(await this._hass.callService("solar_energy_management","purge_status_history",{},void 0,!1),this._say(this._t("next_done")))}catch(t){console.error("[sem-next-system-card] action failed",e,t),this._say(t?.message||this._t("next_apply_failed"),8e3,!0)}}async _diagnose(e){if(this._hass&&!this._diagBusy){this._diagBusy=!0;try{const t=await this._hass.callService("solar_energy_management","diagnose",{section:e||"all"},void 0,!1,!0),s=t?.response||t;await this._writeClipboard(JSON.stringify(s,null,2))}catch(e){console.error("[sem-next-system-card] diagnose failed",e),this._say(e?.message||this._t("copy_failed"),8e3,!0)}finally{this._diagBusy=!1}}}_copyDiagnostics(){const e=e=>this._str(e)||"—",t=this._num("sensor.sem_diag_battery_capacity"),s=null!==t&&t>0?t.toFixed(1):"—",i=`SEM ${e("sensor.sem_diag_version")} | Grid: ${e("sensor.sem_diag_grid_mode")} | Chargers: ${e("sensor.sem_diag_charger_count")} (${e("sensor.sem_diag_charger_control")}) | Battery: ${s}kWh | Unavailable: ${this._str("sensor.sem_diag_sensors_unavailable")||Ee}`,r=this._attrs("sensor.sem_diag_ed_config").energy_dashboard;let a;if(r){const e=e=>e||"—";a=`Solar: pwr=${e(r.solar?.power)} [${e(r.solar?.power_source)}] energy=${e(r.solar?.energy)}\nGrid: pwr=${e(r.grid?.power)} [${e(r.grid?.power_source)}] imp=${e(r.grid?.import_energy)} exp=${e(r.grid?.export_energy)}\nBattery: pwr=${e(r.battery?.power)} [${e(r.battery?.power_source)}] chg=${e(r.battery?.charge_energy)} dis=${e(r.battery?.discharge_energy)}`}else a=`Config: ${e("sensor.sem_diag_ed_config")}`;this._writeClipboard(`${i}\n${a}`)}async _writeClipboard(e,t=!1){const s=e=>(t||this._say(this._t(e?"copied":"copy_failed"),e?2500:8e3,!e),e);if(navigator?.clipboard?.writeText&&!1!==window.isSecureContext)try{return await navigator.clipboard.writeText(e),s(!0)}catch(e){}try{const t=document.createElement("textarea");t.value=e,t.setAttribute("readonly",""),t.style.position="fixed",t.style.top="0",t.style.left="0",t.style.width="1px",t.style.height="1px",t.style.opacity="0",document.body.appendChild(t),t.focus(),t.select();const i=document.execCommand("copy");return document.body.removeChild(t),s(!!i)}catch(e){return console.error("[sem-next-system-card] clipboard fallback threw",e),s(!1)}}render(){if(!this._hass)return H;const e=this._freshRows(),t=e.filter(e=>"ok"!==e.state).length+(this._inputsDegraded()?1:0),s=t?this._tf("next_sys_lead_bad",{n:t}):this._t("next_sys_lead_ok"),i=this._obs()?`${this._t("next_sys_sub")} ${this._t("next_observing_hint")}`:this._t("next_sys_sub"),r=ol(this._hass.states,e=>this._t(e),this._ctx());return this._frame({main:s,sub:i},j`
+ ${this._health(e)} + ${this._refused()} + ${r.map(e=>this._group(e))} +
+ ${this._applyBar()}`)}},{type:"sem-next-system-card",name:"SEM · System",description:"Health first: source freshness, setup check, what SEM held back, and every setting as a sentence",documentationURL:"https://github.com/traktore-org/sem-community/blob/develop/docs/DASHBOARD_GUIDE.md#sem-next-system-card",preview:!0});const Il=["now","night_open","ev_charge_start","ev_min_reached","ev_deadline"];const Ll=36e5,Rl=["ev_charge_start","ev_min_reached","ev_deadline"];const Wl="sensor.sem_",Pl=["#8DC892","#64B5F6"];ke("sem-ev-status-card",class extends Re{static get properties(){return{...super.properties,_showHelp:{state:!0},_sessions:{state:!0},_allSettings:{state:!0},_folds:{state:!0}}}constructor(){super(),this._chargers=[],this._lastStateCount=0,this._showHelp=!1,this._sessions={},this._boundVisibility=()=>{document.hidden||this.requestUpdate()},this._allSettings=!1,this._folds={},this._boundViewMode=e=>this._applyViewMode(e&&e.detail&&"boolean"==typeof e.detail.all?e.detail.all:Fs())}connectedCallback(){super.connectedCallback(),document.addEventListener("visibilitychange",this._boundVisibility),this._applyViewMode(Fs()),window.addEventListener(Es,this._boundViewMode)}disconnectedCallback(){super.disconnectedCallback(),this._pauseTimer&&(clearInterval(this._pauseTimer),this._pauseTimer=null),document.removeEventListener("visibilitychange",this._boundVisibility),window.removeEventListener(Es,this._boundViewMode)}_applyViewMode(e){e!==this._allSettings&&(this._allSettings=e,this._folds={})}_setView(e){As(e),this._applyViewMode(e)}_foldOpen(e,t){const s=(this._folds[e]||{})[t];return"boolean"==typeof s?s:this._allSettings}_toggleFold(e,t){this._folds={...this._folds,[e]:{...this._folds[e]||{},[t]:!this._foldOpen(e,t)}}}_renderFold(e,t,s,i){const r=this._foldOpen(e,t);return j` +
+ + ${r?j`
${i}
`:H} +
`}_sessionState(e){return this._sessions[e]||{open:!1,loading:!1,rows:null,month:null,all:!1}}_setSessionState(e,t){this._sessions={...this._sessions,[e]:{...this._sessionState(e),...t}}}_toggleSessions(e){const t=this._sessionState(e);this._setSessionState(e,{open:!t.open})}_watchSessions(e,t){this._sessWatch||(this._sessWatch={});for(const s of this._chargers){const i=e.states[`binary_sensor.sem_charger_${s}_connected`]?.state||"",r=parseFloat(e.states[`${t}charger_${s}_session_energy`]?.state)||0,a=this._sessWatch[s];this._sessWatch[s]={conn:i,sess:r};const n=a&&("on"===a.conn&&"off"===i||a.sess>0&&0===r);a&&!n||this._loadSessions(s)}}async _loadSessions(e){if(this._sessionState(e).loading||!this._hass?.callWS)return;this._setSessionState(e,{loading:!0});let t=[];try{const s=await this._hass.callWS({type:"solar_energy_management/session_history",charger_id:e});t=Array.isArray(s?.rows)?s.rows:[]}catch(e){t=[]}const s=new Date,i=`${s.getFullYear()}-${String(s.getMonth()+1).padStart(2,"0")}`,r=this._sessionState(e).month;this._setSessionState(e,{loading:!1,rows:t,month:r||i})}async _downloadSessionsXlsx(e){if(this._hass?.callWS)try{const t=await this._hass.callWS({type:"solar_energy_management/session_history/xlsx",charger_id:e});t?.base64&&(Wr(t.filename||"sem-sessions.xlsx",t.base64,"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"),this._setSessionState(e,{error:null}))}catch(t){console.warn("SEM: session download failed",t),this._setSessionState(e,{error:"download"})}}_renderSessions(e){const t=this._sessionState(e),s=this._hass?.language||"en",i=(e,t)=>(this._t(e)||"").split("{n}").join(String(t)),r=t.rows||[],a=new Date,n=`${a.getFullYear()}-${String(a.getMonth()+1).padStart(2,"0")}`,o=ea(Jr(r,n)),l=t.rows?i("sessions_this_month",o.count)+" · "+o.kwh.toFixed(1)+" kWh":"",c=t.month||n,_=Jr(r,c),d=ea(_),p=t.all?_:_.slice(0,6),h=_.length-p.length,u=d.currency||fe(this._hass)||"";let m=c;try{m=new Date(c+"-15T12:00:00Z").toLocaleDateString(s,{month:"long",year:"numeric",timeZone:"UTC"})}catch(e){}const g=r.length?Zr(r[r.length-1]):c,f=c>g,y=c +
this._toggleSessions(e)}> + + + ${this._t("sessions_title")} + + ${l} ${t.open?"▴":"▾"} +
+ ${t.open?t.rows?j` +
+ + f&&this._setSessionState(e,{month:Xr(c,-1),all:!1})}>‹ + ${m} + y&&this._setSessionState(e,{month:Xr(c,1),all:!1})}>› + + ${d.kwh.toFixed(1)} kWh · ${d.solarPct} % ${this._t("sessions_solar_word")}${null===d.cost?"":" · "+d.cost.toFixed(2)+" "+u}${null===d.co2?"":" · "+d.co2.toFixed(1)+" kg CO₂"} +
+ ${0===_.length?j` +
${this._t("sessions_none")}
`:j` +
+ ${this._t("sessions_col_session")} + kWh + ${this._t("sessions_col_solar")} + ${u} + ${this._t("sessions_col_co2")} + ${this._t("sessions_col_min")} +
+ ${p.map(e=>{const t=ta(e,s);return j` +
+ ${t.day} ${t.span} + ${t.kwh} + ${t.solar} + ${t.cost} + ${t.co2} + ${t.min} +
`})}`} +
+ h>0&&this._setSessionState(e,{all:!0})}>${h>0?i("sessions_more",h)+" · ":""}${i("sessions_stored",r.length)} + ${r.length?j` + this._downloadSessionsXlsx(e)}> + ${this._t("sessions_xlsx")} + + ${"download"===t.error?j`${this._t("sessions_download_failed")}`:H}`:H} +
`:j` +
${this._t("sessions_loading")}
`:H} +
+ `}_toggleHelp(){this._showHelp=!this._showHelp}set hass(e){this._hass,this._hass=e;const t=e?.language,s="function"==typeof semLocalize;let i=!1;if((t!==this._lang||s&&!this._localizeReady)&&(this._lang=t,this._localizeReady=s,i=!0),this._isFrozen()&&!i)return;const r=Object.keys(e.states).length;if(r!==this._lastStateCount){this._lastStateCount=r;const t=[];for(const s of Object.keys(e.states)){if(s.includes("_flow_"))continue;const e=s.match(/^sensor\.sem_charger_(.+)_power$/);e&&t.push(e[1])}this._chargers=t}const a=this._config?.entity_prefix||Wl;this._watchSessions(e,a);let n=["ev_connected","ev_charging","ev_power","calculated_current","session_energy","session_solar_share","session_cost","daily_ev_energy","energy_ev_solar_percentage","charging_state"].map(t=>{const s="ev_connected"===t||"ev_charging"===t?"binary_sensor.sem_":a;return e.states[`${s}${t}`]?.state||""}).join(",");if(this._chargers.length>=1){n+="|"+this._chargers.map(t=>[`charger_${t}_power`,`charger_${t}_session_energy`,`charger_${t}_session_energy_external`,`charger_${t}_daily_energy`,`charger_${t}_session_solar_share`,`charger_${t}_estimated_soc`,`charger_${t}_vehicle_soc`,`charger_${t}_commanded_current`].map(t=>e.states[`${a}${t}`]?.state||"").join(":")).join("|"),n+="|"+this._chargers.map(t=>e.states[`time.sem_charger_${t}_target_time`]?.state||"").join(":");const t=e.states[`${a}charging_state`]?.attributes||{};n+="|"+[t.ev_tariff_waiting,t.ev_deadline_reachable,t.ev_next_cheap_window].join(":"),n+="|"+this._chargers.map(e=>!0===((t.per_charger_stop_war||{})[e]||{}).standing_down?"1":"0").join(":"),n+="|"+JSON.stringify([t.per_charger_departure||{},t.battery_boost||null,Math.round(Number(t.battery_soc)||0)]),n+="|"+this._chargers.map(t=>e.states[`number.sem_charger_${t}_daily_ev_target`]?.state||"").join(":"),n+="|"+this._chargers.map(t=>[e.states[`select.sem_charger_${t}_ev_target_type`]?.state||"",e.states[`number.sem_charger_${t}_target_soc`]?.state||"",e.states[`number.sem_charger_${t}_daily_ev_target_max`]?.state||"",e.states[`number.sem_charger_${t}_target_soc_max`]?.state||"",e.states[`number.sem_charger_${t}_ev_battery_capacity_kwh`]?.state||"",e.states[`number.sem_charger_${t}_ev_kwh_per_100km`]?.state||""].join(":")).join("|"),n+="|"+(e.states[`${a}ev_remaining_range`]?.state||"")}n+="|"+this._localizeReady+"|"+this._lang,(n!==this._lastKey||i)&&(this._lastKey=n,this._scheduleUpdate())}get hass(){return this._hass}setConfig(e){super.setConfig(e),this._prefix=e.entity_prefix||Wl}_binaryState(e){const t=this._hass?.states[`binary_sensor.sem_${e}`];return"on"===t?.state}_val(e,t=null){return Ae(this._hass?.states[`${this._prefix}${e}`])??t}_valStr(e){const t=this._hass?.states[`${this._prefix}${e}`];return t?.state||""}_entityVal(e,t=null){const s=this._frozenEntities[e];return s?s.value:Ae(this._hass?.states[e])??t}_fmt(e,t=1){return null==e||isNaN(e)?"—":e.toFixed(t)}_chargerName(e){const t=ls(this._hass?.states,e,this._prefix);return t===String(e).replace(/_/g," ")?t.replace(/\b\w/g,e=>e.toUpperCase()):t}_renderSocGauge(e,t=!1){const s=null!=e?Math.max(0,Math.min(100,e)):0,i=s>60?"#8DC892":s>30?"#ff9800":"#f06292",r=Math.max(2,s/100*52);return j` + + + + + + ${null!=e?(t?"~":"")+Math.round(e)+"%":"—"} + + + `}updated(e){super.updated?.(e);const t=Object.keys(this._hass?.states||{}).some(e=>e.startsWith("select.sem_charger_")&&e.endsWith("_charge_mode")&&"off"===this._hass.states[e]?.state&&this._hass.states[e]?.attributes?.paused_until);t&&!this._pauseTimer?this._pauseTimer=setInterval(()=>this.requestUpdate(),6e4):!t&&this._pauseTimer&&(clearInterval(this._pauseTimer),this._pauseTimer=null)}_departureFor(e,t){const s={...((t||{}).per_charger_departure||{})[e]||{}},i=(this._depPending||{})[e];return i&&Date.now()-i.at<2e4&&Object.assign(s,i.values),s}_writeDeparture(e,t){const s=((this._depPending||{})[e]||{}).values||{};this._depPending={...this._depPending||{},[e]:{at:Date.now(),values:{...s,...t}}},this.requestUpdate();const i={charger_id:e};"by_weekday"in t&&(i.by_weekday=t.by_weekday),"one_block"in t&&(i.one_block=t.one_block),"late_min"in t&&(i.late_charge_min=t.late_min),this._callService("solar_energy_management","set_charger_departure",i)}_fmtT(e,t){let s=this._t(e)||"";for(const[e,i]of Object.entries(t||{}))s=s.split(`{${e}}`).join(String(i));return s}_hhmm(e){return e.toLocaleTimeString([],{hour:"2-digit",minute:"2-digit",timeZone:this._hass?.config?.time_zone||void 0})}_dayName(e,t){const s=["mon","tue","wed","thu","fri","sat","sun"];try{return new Intl.DateTimeFormat(this._hass?.language||"en",{weekday:t,timeZone:"UTC"}).format(new Date(Date.UTC(2024,0,1+e,12)))}catch(t){return s[e]}}_nextDeparture(e){const t=["mon","tue","wed","thu","fri","sat","sun"],s=e.next?new Date(e.next):null;if(!s||isNaN(s))return{next:null,key:null,label:""};let i=null;try{i=new Intl.DateTimeFormat("en-US",{weekday:"short",timeZone:this._hass?.config?.time_zone||void 0}).format(s).toLowerCase().slice(0,3)}catch(e){i=null}return{next:s,key:i,label:t.includes(i)?`${this._dayName(t.indexOf(i),"short")} ${this._hhmm(s)}`:this._hhmm(s)}}_renderDepartureRows(e,t,s){const i=["mon","tue","wed","thu","fri","sat","sun"],r=t.by_weekday||{},a=(e,t)=>this._dayName(e,t),{next:n,key:o}=this._nextDeparture(t),l=!!n,c=this._depEdit&&this._depEdit.id===e?this._depEdit.day:null,_=(t,i)=>{const a={...r};i&&i!==s?a[t]=i:delete a[t],this._writeDeparture(e,{by_weekday:a})},d=t=>{this._depEdit=c===t?null:{id:e,day:t},this.requestUpdate()},p=!!t.one_block,h=[0,15,30,45,60,90,120],u=Number(t.late_min)||0,m=h.includes(u)?h:[...h,u].sort((e,t)=>e-t);let g="";if(u>0&&l){const e=t.late||null,s=e&&e.start?new Date(e.start):new Date(n.getTime()-6e4*u),i=e&&e.end?new Date(e.end):n;if(!isNaN(s)&&!isNaN(i)){const t=`${this._hhmm(s)}–${this._hhmm(i)}`;g=e?this._fmtT("ev_dep_late_hint_plan",{span:t,kwh:Number(e.kwh||0).toFixed(1),cost:`${Number(e.cost||0).toFixed(2)} ${fe(this._hass)}`}):this._fmtT("ev_dep_late_hint",{span:t})}}const f=e=>this._showHelp?j` +
+
${this._t(e)}
+
`:H;return j` +
+ ${i.map((e,t)=>j` + `)} +
+ ${c?j` +
+ ${a(i.indexOf(c),"long")} + + e.stopPropagation()} + @change=${e=>_(c,e.target.value)}> + ${r[c]?j` + `:H} + +
`:H} +
+
+ ${this._fmtT("ev_dep_tap_hint",{time:s})} +
+
+ ${f("ev_dep_help_days")} +
+ ${this._t("ev_dep_one_block")} + + {t.stopPropagation(),this._writeDeparture(e,{one_block:!p})}}> + + + +
+ ${f("ev_dep_help_one_block")} +
+ ${this._t("ev_dep_late_charge")} + + + +
+ ${g?j` +
+
${g}
+
`:H} + ${f("ev_dep_help_late")} + `}_renderBoostRow(e,t,s){if(!("sensor.sem_battery_soc"in(this._hass?.states||{})))return H;const i=s.battery_boost||null,r=s.battery_boost_preview||{},a=!(!i||!i.active||i.charger_id!==e),n=!1!==t.boost_allowed,o=Math.round(Number(r.default_floor??50)),l=(this._boostFloor||{})[e],c=null!=l?l:o,_=[10,20,30,40,50,60,70,80,90],d=_.includes(c)?_:[..._,c].sort((e,t)=>e-t),p=Number(s.battery_soc),h=Number.isFinite(p),u=Number(r.capacity_kwh)||0,m=this._boostError&&this._boostError.id===e?this._boostError.message:null,g=this._showHelp?j` +
+
${this._t("ev_boost_help")}
+
`:H;if(a){const e=Math.round(Number(i.floor_soc)),t=Number(i.start_soc),s=Number.isFinite(Number(i.soc))?Number(i.soc):p,r=Number.isFinite(t)&&Number.isFinite(s)&&t>e?Math.max(0,Math.min(100,(t-s)/(t-e)*100)):null;return j` +
+ ${this._t("ev_boost_title")} + + ${this._fmtT("ev_boost_down_to",{floor:e})} + + +
+
+
${this._fmtT("ev_boost_left",{kwh:Number(i.remaining_kwh||0).toFixed(1),soc:Number.isFinite(s)?Math.round(s):"—",floor:e})}
+ ${null!=r?j`
`:H} +
${this._fmtT("ev_boost_ends",{floor:e})}
+
+ ${g} + `}const f=h&&u>0?Math.max(0,(p-c)/100*u):null,y=i&&!i.active&&i.last_end&&i.last_end.charger_id===e?i.last_end:null,b=y?new Date(y.at):null,v=!!(b&&!isNaN(b)&&Date.now()-b.getTime()<432e5);let x=H;if(n)if(m)x=j` +
+ + ${m} +
`;else if(v){const e={unplugged:"ev_boost_end_unplugged","mode changed":"ev_boost_end_mode_changed","permission off":"ev_boost_end_permission_off","battery at its floor":"ev_boost_end_floor",stopped:"ev_boost_end_stopped","charger removed":"ev_boost_end_charger_removed"}[y.reason];x=j` +
+
${this._fmtT("ev_boost_last_end",{time:this._hhmm(b),reason:e?this._t(e):y.reason})}
+
`}else x=j` +
+
${this._fmtT("ev_boost_hint",{soc:h?Math.round(p):"—",kwh:null!=f?f.toFixed(1):"—"})}
+
`;else x=j` +
+ + ${this._t("ev_boost_not_permitted")} +
`;return j` +
+ ${this._t("ev_boost_title")} + + + + +
+ ${x} + ${g} + `}async _startBoost(e,t){if(this._hass){this._boostError=null;try{await this._hass.callService("solar_energy_management","start_battery_boost",{charger_id:e,floor_soc:t})}catch(t){this._boostError={id:e,message:t&&t.message||String(t)}}this.requestUpdate()}}_renderPlanStrip(e){const t=this._hass?.states["sensor.sem_charging_state"],s=e?t?.attributes?.per_charger_plans?.[e]:null,i=Array.isArray(s)&&s.length>0?s:t?.attributes?.today_plan||[];if(!Array.isArray(i)||i.length<2)return H;const r=new Set(["ev_charge_start","ev_min_reached","ev_deadline"]);if(!i.some(e=>r.has(e.kind)))return H;const a=Date.now(),n=i.filter(e=>["now","night_open","ev_charge_start","ev_min_reached","ev_deadline"].includes(e.kind));n.sort((e,t)=>new Date(e.when)-new Date(t.when));const{end:o,hours:l}=function(e,t){const s=t+432e5,i=t+864e5;let r=0;for(const t of e||[]){if(!t||!Rl.includes(t.kind))continue;const e=new Date(t.when).getTime();Number.isFinite(e)&&e>r&&(r=e)}const a=r>t?Math.ceil((r+1)/Ll)*Ll:0,n=Math.min(i,Math.max(s,a));return{end:n,hours:Math.max(1,Math.round((n-t)/Ll))}}(n,a),c=o-a,_=e=>Math.max(0,Math.min(100,(e-a)/c*100)),d=function(e,{now:t,end:s}){const i=(e||[]).filter(e=>e&&Il.includes(e.kind)).map(e=>({...e,t:new Date(e.when).getTime()})).filter(e=>Number.isFinite(e.t)).sort((e,t)=>e.t-t.t),r=i.filter(e=>"ev_charge_start"===e.kind),a=e=>"plan_ev_charge_estimate"===e.detail?"estimate":"plan_ev_charge_late"===e.detail?"late":"charging",n=[];let o=t,l="idle",c=null;const _=()=>{const e=Math.min(c,s);e>o&&n.push({s:o,e:e,state:l}),o=Math.max(o,e),l="wait",c=null};for(const e of i){null!=c&&co&&n.push({s:o,e:t,state:l}),o=Math.max(o,t),"night_open"===e.kind){const t=r.find(t=>Math.abs(t.t-e.t)<=6e4);l=t?a(t):"wait"}else if("ev_charge_start"===e.kind){l=a(e);const t=e.until?new Date(e.until).getTime():NaN;c=Number.isFinite(t)&&t>e.t?t:null}else"ev_min_reached"!==e.kind&&"ev_deadline"!==e.kind||(l="done",c=null)}return null!=c&&c({idle:"#566072",wait:"#8353d1",estimate:"#b8a6e8",charging:"#8DC892",done:"#4db6ac",late:"#5BC8D8"}[e]||"#566072"),m=d.some(e=>"late"===e.state),g=e=>"cheap"===e?"#43a047":"#f06292",f=e=>(this._t(e)||"").split("{hours}").join(l),y=this._hass?.config?.time_zone||void 0,b=[];for(let e=0;e<=4;e++){const t=a+c*e/4,s=new Date(t).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit",timeZone:y});b.push({x:_(t),label:s})}return j` +
+
+ + ${f("plan_strip_title")} +
+ + ${m?q` + + + + + + `:H} + ${d.map(e=>q` + + `)} + ${p.map(e=>q` + + `)} + +
+ ${b.map(e=>j` + ${e.label} + `)} +
+
${this._t("plan_strip_idle")} ${this._t("plan_strip_wait")} ${this._t("plan_strip_estimate")} ${this._t("plan_strip_charging")} ${this._t("plan_strip_done")} - ${g?H`${this._t("plan_strip_late")}`:G} - ${this._t("plan_strip_cheap")} - ${this._t("plan_strip_expensive")} + ${m?j`${this._t("plan_strip_late")}`:H} + ${this._t("plan_strip_cheap")} + ${this._t("plan_strip_expensive")}
- ${this._showHelp?H` + ${this._showHelp?j`
${f("plan_strip_help")}
- `:G} + `:H}
- `}_renderRangeSlider(e,t,s){const i=this._hass?.states[e],r=this._hass?.states[t],a=s?"%":" kWh",o=e=>s?String(Math.round(e)):this._fmt(e,e%1==0?0:1);if(!i||!r){const t=this._entityVal(e,s?80:10);return H`
+ `}_renderRangeSlider(e,t,s){const i=this._hass?.states[e],r=this._hass?.states[t],a=s?"%":" kWh",n=e=>s?String(Math.round(e)):this._fmt(e,e%1==0?0:1);if(!i||!r){const t=this._entityVal(e,s?80:10);return j`
${this._t("charge_to")} this.dispatchEvent(new CustomEvent("hass-more-info",{bubbles:!0,composed:!0,detail:{entityId:e}}))} - >${o(t)}${a}
`}const n=Math.min(parseFloat(i.attributes.min??0),parseFloat(r.attributes.min??0)),l=Math.max(parseFloat(i.attributes.max??100),parseFloat(r.attributes.max??100)),c=l-n||1;let d=this._entityVal(e,s?80:10),p=this._entityVal(t,100);p=l-1e-6,g=parseFloat(this._hass?.states[e]?.attributes?.step)||(l>50?1:5),m=Math.max(g,.02*(l-n)),f=p-d${n(t)}${a}
`}const o=Math.min(parseFloat(i.attributes.min??0),parseFloat(r.attributes.min??0)),l=Math.max(parseFloat(i.attributes.max??100),parseFloat(r.attributes.max??100)),c=l-o||1;let _=this._entityVal(e,s?80:10),d=this._entityVal(t,100);d<_&&(d=_);const p=(_-o)/c*100,h=(d-o)/c*100,u=d>=l-1e-6,m=parseFloat(this._hass?.states[e]?.attributes?.step)||(l>50?1:5),g=Math.max(m,.02*(l-o)),f=d-_
- ${this._t(xs)} ${o(d)}${a} - ${this._t($s)} ${u?this._t("full"):o(p)+a} + ${this._t(Xs)} ${n(_)}${a} + ${this._t(Js)} ${u?this._t("full"):n(d)+a}
-
-
this._rangeHandleStart(s,"min",e,t,n,l)}>
+
+
this._rangeHandleStart(s,"min",e,t,o,l)}>
this._rangeHandleStart(s,"max",e,t,n,l)}>
- ${f?H` + @pointerdown=${s=>this._rangeHandleStart(s,"max",e,t,o,l)}>
+ ${f?j` {s.stopPropagation();const i=p-m;i>=n?this._setNumber(e,i):this._setNumber(t,Math.min(l,d+m))}}> + @click=${s=>{s.stopPropagation();const i=d-g;i>=o?this._setNumber(e,i):this._setNumber(t,Math.min(l,_+g))}}> - `:G} + `:H} - `}_rangeHandleStart(e,t,s,i,r,a){e.stopPropagation(),e.preventDefault();const o=e.currentTarget.closest(".range-track");if(!o)return;const n="min"===t?s:i,l="min"===t?i:s,c=this._hass?.states[n],d=parseFloat(c?.attributes?.step)||(a>50?1:.5),p=a-r||1,_=e=>{const s=o.getBoundingClientRect();let i=(e-s.left)/(s.width||1);i=Math.max(0,Math.min(1,i));let n=Math.round((r+i*p)/d)*d;const c=this._entityVal(l,"min"===t?a:r);return n="min"===t?Math.min(n,c):Math.max(n,c),Math.max(r,Math.min(a,n))},h=e=>{this._freezeEntity(n,_(e.clientX)),this.requestUpdate()},u=e=>{window.removeEventListener("pointermove",h),window.removeEventListener("pointerup",u),window.removeEventListener("pointercancel",u),this._setNumber(n,_(e.clientX))};window.addEventListener("pointermove",h),window.addEventListener("pointerup",u),window.addEventListener("pointercancel",u)}_renderChargerSection(e,t){const s=Ns[t%Ns.length],i=this._val(`charger_${e}_power`),r=this._val(`charger_${e}_session_energy_external`),a=this._val(`charger_${e}_session_energy`),o=r>0?r:a,n=this._val(`charger_${e}_daily_energy`),l=this._val("energy_ev_solar_percentage"),{soc:c,isEstimate:d}=ps(this._val(`charger_${e}_vehicle_soc`,null),this._val("vehicle_soc",null),this._val(`charger_${e}_estimated_soc`,null),this._chargers.length),p=this._stateAttrs(`sensor.sem_charger_${e}_estimated_soc`),_=p.energy_accounted_soc,h=!0===p.estimate_stop_active,u=this._val(`charger_${e}_vehicle_soc`,null),g=p.vehicle_soc_last??u;let m=null;const f=Date.parse(p.vehicle_soc_last_at??"");Number.isNaN(f)||(m=Math.round((Date.now()-f)/6e4));const v=e=>(this._t(e)||"").replace(/\{soc\}/g,null!=g?Math.round(g):"—").replace(/\{age\}/g,null!=m?m:"—").replace(/\{est\}/g,null!=_?Math.round(_):"—"),y=null!=g&&null!=_&&null!=m&&m>=5&&(_-g>=1||null==u),b=this._chargerName(e),x=this._hass?.states[`binary_sensor.sem_charger_${e}_connected`],$="on"===x?.state,w=i>50,k=(this._stateAttrs(`${this._prefix}charging_state`).per_charger_stop_war||{})[e],S=this._t(function({isCharging:e,isConnected:t,standDown:s}){return e&&s&&!0===s.standing_down?"charger_status_stood_down":e?"charging":t?"connected":"idle"}({isCharging:w,isConnected:$,standDown:k})),C=this._val(`charger_${e}_commanded_current`,0),z=this._entityVal(`number.sem_charger_${e}_minimum_current`,6),M=this._entityVal(`number.sem_charger_${e}_ev_battery_capacity_kwh`,40),D=this._entityVal(`number.sem_charger_${e}_ev_kwh_per_100km`,18),E="select.sem_pause_duration",N=this._stateAttrs(E),F=this._stateStr(E)||"1_hour",T=N.options||[],A=`button.sem_charger_${e}_pause_charging`,B=!!this._hass?.states?.[A],R=`select.sem_charger_${e}_charge_mode`,O=this._stateAttrs(R),I=this._stateStr(R)||"min_plus_solar",L="off"===I?O.paused_until:null,P=L?new Date(L):null,U=!(!P||isNaN(P)),q=U?Math.max(0,Math.round((P-Date.now())/6e4)):0,W=O.pause_resume_mode||"",j=U?P.toLocaleTimeString([],{hour:"2-digit",minute:"2-digit",timeZone:this._hass?.config?.time_zone||void 0}):"",K=q>=60?`${Math.floor(q/60)} h ${q%60} min`:`${q} min`,V=O.options||Ss,Y=!1!==O.tariff_available,Z=O.modes_needing_tariff||[],X=e=>!Y&&Z.includes(e),Q=this._stateAttrs("sensor.sem_battery_spendable_kwh"),J="learning"===Q.phase,ee=(this._t("charge_mode_battery_learning_info")||"").replace(/\{n\}/g,Q.nights_sealed??"?").replace(/\{total\}/g,Q.nights_required??"?"),te=Object.fromEntries(Ss.map(e=>{return[e,this._t((t=e,"charge_mode_"+String(t||"")))];var t})),se=Math.round(this._entityVal("number.sem_battery_buffer_soc",70)),ie=Math.round(this._entityVal("number.sem_battery_priority_soc",30)),re=e=>(this._t(e)||"").replace(/\{buffer\}/g,se).replace(/\{priority\}/g,ie),ae=re(`charge_mode_hint_${I}_surplus`),oe=re(`charge_mode_hint_${I}_overnight`),ne=re(`charge_mode_hint_${I}_battery`),le=`select.sem_charger_${e}_ev_target_type`,ce=this._stateStr(le)||"kwh",de=this._stateAttrs(le).options||["kwh"],pe="soc"===ce,_e=pe?`number.sem_charger_${e}_target_soc`:`number.sem_charger_${e}_daily_ev_target`,he=pe?`number.sem_charger_${e}_target_soc_max`:`number.sem_charger_${e}_daily_ev_target_max`,ge=`time.sem_charger_${e}_target_time`,me=this._stateStr(ge),fe=me?me.slice(0,5):"—",ve=this._stateAttrs(`${this._prefix}charging_state`),ye=!1===ve.ev_deadline_reachable,be=this._departureFor(e,ve),xe=me?me.slice(0,5):be.default||"07:00",$e=ve.ev_next_cheap_window;let we="";if($e)try{const e=new Date($e);isNaN(e)||(we=e.toLocaleTimeString([],{hour:"2-digit",minute:"2-digit",timeZone:this._hass?.config?.time_zone||void 0}))}catch(e){}const ke="solar_plus_cheap"===I&&we,Se=`select.sem_charger_${e}_phase_mode`,Ce=!!this._hass?.states?.[Se],ze=(ve.per_charger_phases||{})[e]||{},Me=this._stateStr(Se)||"auto",De=this._stateAttrs(Se).options||["auto","1","3"],Ee={auto:this._t("phase_mode_auto"),1:this._t("phase_mode_1"),3:this._t("phase_mode_3")};let Ne="";Ne="stopping"===ze.switch_state?this._t("phase_status_stopping"):"settling"===ze.switch_state?this._t("phase_status_settling"):ze.active_phases?(this._t("phase_status_measured")||"{n}-phase measured").replace("{n}",ze.active_phases):ze.believed_phases?(this._t("phase_status_believed")||"{n}-phase").replace("{n}",ze.believed_phases):this._t("phase_status_unknown");const Fe=this._entityVal(_e,pe?80:10),Te=null==(pe?c:n)?null:pe?Math.max(0,(Fe-c)/100*M):Math.max(0,Fe-n),Ae=null!=Te&&D>0?Math.round(Te/D*100):null,Be=de.length>1?H`e.stopPropagation()} @change=${e=>this._selectOption(le,e.target.value)}> - ${de.map(e=>H``)} - `:H`${pe?"%":"kWh"}`,Re=this._allSettings,Oe=this._nextDeparture(be),[Ie,Le=""]=(this._t("ev_dep_next")||"{when}").split("{when}"),Pe=Object.keys(be.by_weekday||{}).length>0,Ue=e=>this.dispatchEvent(new CustomEvent("hass-more-info",{bubbles:!0,composed:!0,detail:{entityId:e}})),qe=`number.sem_charger_${e}_minimum_current`,He=`number.sem_charger_${e}_ev_battery_capacity_kwh`,We=`number.sem_charger_${e}_ev_kwh_per_100km`,je=e=>this._showHelp?H`
${this._t(e)}
`:G,Ge=H` + ${_e.map(e=>j``)} + `:j`${de?"%":"kWh"}`,Be=this._allSettings,Ie=this._nextDeparture(ve),[Le,Re=""]=(this._t("ev_dep_next")||"{when}").split("{when}"),We=Object.keys(ve.by_weekday||{}).length>0,Pe=e=>this.dispatchEvent(new CustomEvent("hass-more-info",{bubbles:!0,composed:!0,detail:{entityId:e}})),Ue=`number.sem_charger_${e}_minimum_current`,je=`number.sem_charger_${e}_ev_battery_capacity_kwh`,qe=`number.sem_charger_${e}_ev_kwh_per_100km`,Ke=e=>this._showHelp?j`
${this._t(e)}
`:H,He=j` this._toggleHelp()} style="--mdc-icon-size:16px" - >`,Ke=H` - ${B?H` + >`,Ge=j` + ${O?j`
${this._t("pause_charging_for")}
- `:G} - ${Ce?H` + `:H} + ${Ce?j`
${this._t("phase_mode")} @@ -3665,54 +4733,55 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow
- ${Ne} + ${Ee}
- `:G} - ${this._renderDepartureRows(e,be,xe)} - ${this._renderBoostRow(e,be,ve)} - `,Ve=H` - ${Re?G:H` + `:H} + ${this._renderDepartureRows(e,ve,xe)} + ${this._renderBoostRow(e,ve,ye)} + `,Ve=j` + ${Be?H:j`
${this._t("session")} - ${this._fmt(o,1)} kWh - ${C>0?H` + ${this._fmt(n,1)} kWh + ${C>0?j` ${this._t("commanded_current")} - ${Math.round(C)} A`:G} + ${Math.round(C)} A`:H}
`} ${this._renderPlanStrip(e)} - ${h?H` + ${h?j`
- ${v("soc_target_reached_est")}
- ${v("soc_confirms_next_update")}
-
`:y?H` + ${y("soc_target_reached_est")}
+ ${y("soc_confirms_next_update")}
+ `:b?j`
- ${v("soc_info_line")} -
`:G} - ${"solar_plus_battery"===I&&J?H` + ${y("soc_info_line")} + `:H} + ${"solar_plus_battery"===L&&Q?j`
${ee}
- `:G} + `:H} ${this._renderSessions(e)} - `;return H` + `;return j`
- ${b} - ${S}${Re&&C>0?H` (${Math.round(C)} A)`:G} + ${v} + ${S}${Be&&C>0?j` (${Math.round(C)} A)`:H}
+ ${this._chargerWhy()}
-
- ${this._renderSocGauge(c,d)} - ${d?this._t("soc_estimated_label"):"SOC"} +
+ ${this._renderSocGauge(c,_)} + ${_?this._t("soc_estimated_label"):"SOC"}
@@ -3722,13 +4791,13 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow
${this._t("today")} - ${this._fmt(n,1)} kWh + ${this._fmt(o,1)} kWh
- ${Re?H` + ${Be?j`
${this._t("session")} - ${this._fmt(o,1)} kWh -
`:G} + ${this._fmt(n,1)} kWh +
`:H}
${this._t("solar_share")} ${this._fmt(l,0)}% @@ -3743,12 +4812,12 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow
- ${this._t(ws)} - ${null!=Ae&&Ae>0?H`· +${Ae} km`:G} + ${this._t(Qs)} + ${null!=Te&&Te>0?j`· +${Te} km`:H} - ${Be} + ${Oe}
- ${this._renderRangeSlider(_e,he,pe)} + ${this._renderRangeSlider(pe,he,de)}
- ${q>0?H`${(this._t("pause_status_line")||"").replace("{left}",K).replace("{mode}",te[W]||W).replace("{time}",j)} + ${U>0?j`${(this._t("pause_status_line")||"").replace("{left}",G).replace("{mode}",te[q]||q).replace("{time}",K)} ${this._t("pause_resume_hint")}`:this._t("pause_resuming")}
- `:G} - ${this._showHelp?H` + `:H} + ${this._showHelp?j`
${this._t("hint_label_surplus")}: @@ -3792,99 +4861,99 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow
${this._t("hint_label_overnight")}: - ${oe} + ${ne}
${this._t("hint_label_battery")}: - ${ne} + ${oe}
- ${ke?H` + ${ke?j`
${this._t("ev_next_cheap")}: ${we}
- `:G} + `:H}
- `:ke?H` + `:ke?j`
${this._t("ev_next_cheap")}: ${we}
- `:G} + `:H}
Ue(ge)}> - ${this._t(ks)} + @click=${()=>Pe(me)}> + ${this._t(ei)} ${fe} - ${Pe?H`· ${this._t("ev_dep_most_days")}`:G} + ${We?j`· ${this._t("ev_dep_most_days")}`:H}
- ${(Pe||be.from_calendar)&&Oe.label?H` + ${(We||ve.from_calendar)&&Ie.label?j`
- ${be.from_calendar?this._fmtT("ev_dep_next_calendar",{when:Oe.label,event:be.calendar_summary||""}):H`${Ie}${Oe.label}${Le}`} + ${ve.from_calendar?this._fmtT("ev_dep_next_calendar",{when:Ie.label,event:ve.calendar_summary||""}):j`${Le}${Ie.label}${Re}`}
-
`:G} - ${ye?H` +
`:H} + ${be?j`
${this._t("ev_deadline_unreachable_short")}
- `:G} - ${this._renderFold(e,"more","ev_fold_more",Ke)} + `:H} + ${this._renderFold(e,"more","ev_fold_more",Ge)}
${this._renderFold(e,"details","ev_fold_details",Ve)} - ${Re?H` + ${Be?j`
${""}
-
Ue(qe)}> +
Pe(Ue)}> - ${this._fmt(z,0)}A + ${this._fmt(M,0)}A
- ${je("tile_help_min_amps")} + ${Ke("tile_help_min_amps")}
-
Ue(He)}> +
Pe(je)}> - ${this._fmt(M,0)} kWh + ${this._fmt(z,0)} kWh
- ${je("tile_help_capacity")} + ${Ke("tile_help_capacity")}
-
Ue(We)}> +
Pe(qe)}> ${this._fmt(D,0)} kWh/100km
- ${je("tile_help_consumption")} + ${Ke("tile_help_consumption")}
- ${Ge} -
`:H` + ${He} +
`:j`
${this._t("ev_set_once")}: - + · - + · - - ${Ge} + + ${He}
- ${this._showHelp?H` + ${this._showHelp?j`
- ${je("tile_help_capacity")} - ${je("tile_help_min_amps")} - ${je("tile_help_consumption")} -
`:G}`} + ${Ke("tile_help_capacity")} + ${Ke("tile_help_min_amps")} + ${Ke("tile_help_consumption")} +
`:H}`}
- `}render(){if(!this._config||!this._hass)return G;const e=this._binaryState("ev_connected"),t=(this._chargers||[]).map(e=>this._val(`charger_${e}_power`)),s=t.length?t.every(e=>null==e)?null:t.reduce((e,t)=>e+(t??0),0):this._val("ev_power"),i=s>50;this._val("calculated_current",0),this._val("session_energy",0);const r=this._val("energy_ev_solar_percentage"),a=this._val("session_cost"),o=this._val("daily_ev_energy");this._valStr("charging_state");const n=fe(this._hass),l=i?"wrap state-charging":e?"wrap state-connected":"wrap state-disconnected",c=i?this._t("charging"):e?this._t("connected"):this._t("disconnected"),d=i?"status-value charging":e?"status-value connected":"status-value disconnected",p=i?"0.6":e?"0.25":"0.08",_=i?"1":"0";return H` + `}render(){if(!this._config||!this._hass)return H;const e=this._binaryState("ev_connected"),t=(this._chargers||[]).map(e=>this._val(`charger_${e}_power`)),s=t.length?t.every(e=>null==e)?null:t.reduce((e,t)=>e+(t??0),0):this._val("ev_power"),i=s>50;this._val("calculated_current",0),this._val("session_energy",0);const r=this._val("energy_ev_solar_percentage"),a=this._val("session_cost"),n=this._val("daily_ev_energy");this._valStr("charging_state");const o=fe(this._hass),l=i?"wrap state-charging":e?"wrap state-connected":"wrap state-disconnected",c=i?this._t("charging"):e?this._t("connected"):this._t("disconnected"),_=i?"status-value charging":e?"status-value connected":"status-value disconnected",d=i?"0.6":e?"0.25":"0.08",p=i?"1":"0";return j` @@ -3902,7 +4971,7 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow
- ${this._chargers.length>=1?H` + ${this._chargers.length>=1?j`
@@ -3912,11 +4981,11 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow -
`:G} +
`:H}
- + @@ -3926,13 +4995,13 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow - +
- ${this._chargers.length>=1?H` + ${this._chargers.length>=1?j` - ${this._chargers.length>1?G:H` + ${this._chargers.length>1?H:j`
${this._t("session_cost")} - ${this._fmt(a,2)} ${n} + ${this._fmt(a,2)} ${o}
`} - ${this._chargers.length>=1?H` + ${this._chargers.length>=1?j`
${this._chargers.map((e,t)=>this._renderChargerSection(e,t))}
- `:G} + `:H}
`}static get styles(){return a` @@ -4180,6 +5249,9 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow border-radius: 12px; padding: 12px 14px; } + .charger-why { + font-size: 12.5px; opacity: .75; margin: -4px 0 10px; + } .charger-header { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; @@ -4638,41 +5710,41 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow .hero { flex-direction: column; gap: 12px; } .ev-icon-area { width: 80px; height: 80px; } } - `}getCardSize(){return this._chargers.length>=1?3+2*this._chargers.length:3}static getStubConfig(){return{}}},{type:"sem-ev-status-card",name:"SEM EV Status",description:"Lumina-styled EV charging hero card with per-charger intelligence and settings",documentationURL:"https://github.com/traktore-org/sem-community/blob/develop/docs/DASHBOARD_GUIDE.md#sem-ev-status-card"});const Fs=["sensor.sem_solar_power","sensor.sem_battery_soc","sensor.sem_autarky_rate","sensor.sem_ev_power","sensor.sem_energy_optimization_score","sensor.sem_energy_tip","sensor.sem_best_surplus_window","sensor.sem_forecast_remaining_today_kwh","sensor.sem_current_vs_peak_percentage","sensor.sem_consecutive_peak_15min","sensor.sem_target_peak_limit","sensor.sem_daily_co2_avoided","sensor.sem_lifetime_co2_avoided"];ke("sem-home-status-card",class extends Le{static get watchedEntities(){return Fs}static get watchedAttributes(){return{"sensor.sem_target_peak_limit":["peak_limit_unlimited"]}}setConfig(e){super.setConfig(e),this._prefix=e.entity_prefix||"sensor.sem_"}_val(e,t=null){return Te(this._hass?.states[`${this._prefix}${e}`])??t}_valStr(e){const t=this._hass?.states[`${this._prefix}${e}`];return t&&"unavailable"!==t.state&&"unknown"!==t.state?t.state:""}_scoreColor(e){return null==e?"#888":e>=80?"#8DC892":e>=50?"#ff9800":"#f44336"}_peakColor(e){return null==e?"#888":e>90?"#f44336":e>70?"#ff9800":"#8DC892"}_peakStatusKey(e){return e>90?"peak_critical":e>70?"peak_warning":"peak_safe"}_renderChip(e,t,s){return H` + `}getCardSize(){return this._chargers.length>=1?3+2*this._chargers.length:3}static getStubConfig(){return{}}},{type:"sem-ev-status-card",name:"SEM EV Status",description:"Lumina-styled EV charging hero card with per-charger intelligence and settings",documentationURL:"https://github.com/traktore-org/sem-community/blob/develop/docs/DASHBOARD_GUIDE.md#sem-ev-status-card"});const Ul=["sensor.sem_solar_power","sensor.sem_battery_soc","sensor.sem_autarky_rate","sensor.sem_ev_power","sensor.sem_energy_optimization_score","sensor.sem_energy_tip","sensor.sem_best_surplus_window","sensor.sem_forecast_remaining_today_kwh","sensor.sem_current_vs_peak_percentage","sensor.sem_consecutive_peak_15min","sensor.sem_target_peak_limit","sensor.sem_daily_co2_avoided","sensor.sem_lifetime_co2_avoided"];ke("sem-home-status-card",class extends Re{static get watchedEntities(){return Ul}static get watchedAttributes(){return{"sensor.sem_target_peak_limit":["peak_limit_unlimited"]}}setConfig(e){super.setConfig(e),this._prefix=e.entity_prefix||"sensor.sem_"}_val(e,t=null){return Ae(this._hass?.states[`${this._prefix}${e}`])??t}_valStr(e){const t=this._hass?.states[`${this._prefix}${e}`];return t&&"unavailable"!==t.state&&"unknown"!==t.state?t.state:""}_scoreColor(e){return null==e?"#888":e>=80?"#8DC892":e>=50?"#ff9800":"#f44336"}_peakColor(e){return null==e?"#888":e>90?"#f44336":e>70?"#ff9800":"#8DC892"}_peakStatusKey(e){return e>90?"peak_critical":e>70?"peak_warning":"peak_safe"}_renderChip(e,t,s){return j`
${s}
- `}render(){if(!this._config||!this._hass)return G;const e=this._val("solar_power",null),t=Be(this._val("battery_soc",null)),s=this._val("autarky_rate",null),i=this._val("ev_power"),r=this._val("energy_optimization_score"),a=this._scoreColor(r),o=this._valStr("energy_tip"),n=this._valStr("best_surplus_window")||"—",l=Ae(this._val("forecast_remaining_today_kwh"),1),c=this._val("current_vs_peak_percentage"),d=this._peakColor(c),p=Ae(this._val("consecutive_peak_15min"),1),_=this._hass.states[`${this._prefix}target_peak_limit`],h=We(_,this._t(et)),u=He(_),g=Ae(this._val("daily_co2_avoided"),2),m=Ae(this._val("lifetime_co2_avoided"),1);return H` + `}render(){if(!this._config||!this._hass)return H;const e=this._val("solar_power",null),t=Oe(this._val("battery_soc",null)),s=this._val("autarky_rate",null),i=this._val("ev_power"),r=this._val("energy_optimization_score"),a=this._scoreColor(r),n=this._valStr("energy_tip"),o=this._valStr("best_surplus_window")||"—",l=Te(this._val("forecast_remaining_today_kwh"),1),c=this._val("current_vs_peak_percentage"),_=this._peakColor(c),d=Te(this._val("consecutive_peak_15min"),1),p=this._hass.states[`${this._prefix}target_peak_limit`],h=qe(p,this._t(st)),u=je(p),m=Te(this._val("daily_co2_avoided"),2),g=Te(this._val("lifetime_co2_avoided"),1);return j`
- ${this._renderChip("mdi:solar-power","#ff9800",null==e?Ne:ue(e))} + ${this._renderChip("mdi:solar-power","#ff9800",null==e?Ee:ue(e))} ${this._renderChip("mdi:battery","#4db6ac",t.label)} - ${this._renderChip("mdi:leaf","#8DC892",null==s?Ne:`${s.toFixed(0)}%`)} + ${this._renderChip("mdi:leaf","#8DC892",null==s?Ee:`${s.toFixed(0)}%`)} ${this._renderChip("mdi:car-electric","#8DC892",i>0?ue(i):"—")}
- ${Ae(r,0)} + ${Te(r,0)}
-
+
- ${o||""} + ${n||""} - ${this._t("best_window")}: ${n} · ${this._t("surplus")}: ${l} kWh + ${this._t("best_window")}: ${o} · ${this._t("surplus")}: ${l} kWh
-
+
@@ -4680,12 +5752,12 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow
- ${Ae(c,0)}% + ${Te(c,0)}%
- ${p} kW / ${h}${u?"":` (${Ae(c,0)}%)`} - - ${null==c?Ne:this._t(this._peakStatusKey(c))} + ${d} kW / ${h}${u?"":` (${Te(c,0)}%)`} + + ${null==c?Ee:this._t(this._peakStatusKey(c))}
@@ -4699,7 +5771,7 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow
- ${g} ${this._kgUnit("daily_co2_avoided")} + ${m} ${this._kgUnit("daily_co2_avoided")} ${this._t("co2_today")}
@@ -4707,7 +5779,7 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow
- ${m} ${this._kgUnit("lifetime_co2_avoided")} + ${g} ${this._kgUnit("lifetime_co2_avoided")} ${this._t("co2_lifetime")}
@@ -4851,7 +5923,7 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow @media (max-width: 400px) { .env-row { flex-direction: column; } } - `}getCardSize(){return 5}static getStubConfig(){return{entity_prefix:"sensor.sem_"}}},{type:"sem-home-status-card",name:"SEM Home Status Card",description:"Consolidated status panel for the SEM Home tab",documentationURL:"https://github.com/traktore-org/sem-community/blob/develop/docs/DASHBOARD_GUIDE.md#sem-home-status-card",preview:!1});const Ts="sensor.sem_tariff_current_import_rate";ke("sem-price-card",class extends Le{constructor(){super(),this._boundVisibility=()=>{document.hidden||this.requestUpdate()}}connectedCallback(){super.connectedCallback(),document.addEventListener("visibilitychange",this._boundVisibility)}disconnectedCallback(){super.disconnectedCallback(),document.removeEventListener("visibilitychange",this._boundVisibility)}setConfig(e){super.setConfig(e),this._entity=e.entity||Ts,this._compact=!!e.compact}set hass(e){this._hass=e;const t=e?.states[this._entity],s=t?.attributes||{},i=[t?.state,s.price_level,s.next_cheap_start,(s.upcoming||[]).length,this._lang].join("|"),r="function"==typeof semLocalize;(i!==this._lastKey||r&&!this._localizeReady)&&(this._lastKey=i,this._lang=e?.language,this._localizeReady=r,this.requestUpdate())}get hass(){return this._hass}_hm(e){if(!e)return null;const t=this._hass?.config?.time_zone||void 0;try{return new Date(e).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit",timeZone:t})}catch(e){return null}}_levelInfo(e){return{color:Zt(e,"#9e9e9e"),key:Yt(e)}}render(){if(!this._hass||!this._config)return G;const e=this._hass.states[this._entity];if(!e||"unavailable"===e.state||"unknown"===e.state)return H`
${this._t("current_electricity_price")}: —
`;const t=e.attributes||{};if(!1===t.is_dynamic)return this.style.display="none",G;this.style.display="";const s=parseFloat(e.state),i=t.currency||"",r=this._levelInfo(t.price_level),a=e=>null==e||isNaN(e)?"—":Number(e).toFixed(2),o=Array.isArray(t.upcoming)?t.upcoming:[],n=this._hm(t.next_cheap_start),l=this._hm(t.next_cheap_end);return this._compact?H` + `}getCardSize(){return 5}static getStubConfig(){return{entity_prefix:"sensor.sem_"}}},{type:"sem-home-status-card",name:"SEM Home Status Card",description:"Consolidated status panel for the SEM Home tab",documentationURL:"https://github.com/traktore-org/sem-community/blob/develop/docs/DASHBOARD_GUIDE.md#sem-home-status-card",preview:!1});const jl="sensor.sem_tariff_current_import_rate";ke("sem-price-card",class extends Re{constructor(){super(),this._boundVisibility=()=>{document.hidden||this.requestUpdate()}}connectedCallback(){super.connectedCallback(),document.addEventListener("visibilitychange",this._boundVisibility)}disconnectedCallback(){super.disconnectedCallback(),document.removeEventListener("visibilitychange",this._boundVisibility)}setConfig(e){super.setConfig(e),this._entity=e.entity||jl,this._compact=!!e.compact}set hass(e){this._hass=e;const t=e?.states[this._entity],s=t?.attributes||{},i=[t?.state,s.price_level,s.next_cheap_start,(s.upcoming||[]).length,this._lang].join("|"),r="function"==typeof semLocalize;(i!==this._lastKey||r&&!this._localizeReady)&&(this._lastKey=i,this._lang=e?.language,this._localizeReady=r,this.requestUpdate())}get hass(){return this._hass}_hm(e){if(!e)return null;const t=this._hass?.config?.time_zone||void 0;try{return new Date(e).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit",timeZone:t})}catch(e){return null}}_levelInfo(e){return{color:Vt(e,"#9e9e9e"),key:Gt(e)}}render(){if(!this._hass||!this._config)return H;const e=this._hass.states[this._entity];if(!e||"unavailable"===e.state||"unknown"===e.state)return j`
${this._t("current_electricity_price")}: —
`;const t=e.attributes||{};if(!1===t.is_dynamic)return this.style.display="none",H;this.style.display="";const s=parseFloat(e.state),i=t.currency||"",r=this._levelInfo(t.price_level),a=e=>null==e||isNaN(e)?"—":Number(e).toFixed(2),n=Array.isArray(t.upcoming)?t.upcoming:[],o=this._hm(t.next_cheap_start),l=this._hm(t.next_cheap_end);return this._compact?j`
${this._t("current_electricity_price")} @@ -4860,15 +5932,15 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow ${this._t(r.key)} - ${n?H`${this._t("price_next_cheap")}: ${n}`:G} + ${o?j`${this._t("price_next_cheap")}: ${o}`:H}
- `:H` + `:j`
${this._t("current_electricity_price")} - ${t.provider&&"unknown"!==t.provider?H`${t.provider}`:G} + ${t.provider&&"unknown"!==t.provider?j`${t.provider}`:H}
${a(s)} @@ -4879,19 +5951,19 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow
${this._t("today")}: min ${a(t.today_min)} · avg ${a(t.today_avg)} · max ${a(t.today_max)} - ${n?H`${this._t("price_next_cheap")}: ${n}${l?"–"+l:""}`:G} + ${o?j`${this._t("price_next_cheap")}: ${o}${l?"–"+l:""}`:H}
- ${o.length>=2?this._renderStrip(o):G} + ${n.length>=2?this._renderStrip(n):H}
- `}_renderStrip(e){const t=Date.now(),s=e.map(e=>({t:new Date(e.t).getTime(),price:e.price,level:e.level})).filter(e=>!isNaN(e.t)).sort((e,t)=>e.t-t.t).filter(e=>e.t>=t-36e5).slice(0,24);if(s.length<2)return G;const i=s.map(e=>e.price),r=Math.min(...i),a=Math.max(...i)-r||1,o=s.length,n=(320-2*(o-1))/o,l=s.map((e,s)=>{const i=6+52*((e.price-r)/a),o=s*(n+2),l=64-i,c=this._levelInfo(e.level).color,d=e.t<=t&&t`}),c=s.map((e,t)=>{if(t%6!=0)return G;const s=t*(n+2)+n/2,i=new Date(e.t).getHours();return W`${String(i).padStart(2,"0")}`});return H` + `}_renderStrip(e){const t=Date.now(),s=e.map(e=>({t:new Date(e.t).getTime(),price:e.price,level:e.level})).filter(e=>!isNaN(e.t)).sort((e,t)=>e.t-t.t).filter(e=>e.t>=t-36e5).slice(0,24);if(s.length<2)return H;const i=s.map(e=>e.price),r=Math.min(...i),a=Math.max(...i)-r||1,n=s.length,o=(320-2*(n-1))/n,l=s.map((e,s)=>{const i=6+52*((e.price-r)/a),n=s*(o+2),l=64-i,c=this._levelInfo(e.level).color,_=e.t<=t&&t`}),c=s.map((e,t)=>{if(t%6!=0)return H;const s=t*(o+2)+o/2,i=new Date(e.t).getHours();return q`${String(i).padStart(2,"0")}`});return j` ${l}${c} - `}getCardSize(){return 2}static getStubConfig(){return{entity:Ts}}static get styles(){return a` + `}getCardSize(){return 2}static getStubConfig(){return{entity:jl}}static get styles(){return a` ha-card { background: var(--ha-card-background, var(--card-background-color)); } .wrap { padding: 12px 14px; } .wrap.empty { color: var(--secondary-text-color,#999); } @@ -4921,7 +5993,7 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow border-radius: 9px; border: 1px solid; text-transform: capitalize; } .chip-next { margin-left: auto; font-size: 12px; color: var(--secondary-text-color,#aaa); } .chip-next b { font-variant-numeric: tabular-nums; } - `}},{type:"sem-price-card",name:"SEM Price Card",description:"Dynamic electricity price: current price, level, today range, next cheap window, and an hourly price strip (#257)",documentationURL:"https://github.com/traktore-org/sem-community/blob/develop/docs/DASHBOARD_GUIDE.md#sem-price-card",preview:!1});const As=[{id:"info",icon:"mdi:information-outline",color:"#96CAEE",titleKey:"system_info",subtitleFn:e=>`v${e._val("diag_version")||"—"} · ${e._valLabel("diag_grid_mode")||"—"}`},{id:"health",icon:"mdi:heart-pulse",color:"#8DC892",titleKey:"health_overview",subtitleFn:e=>{const t=e._valFmt("solar_power"),s=e._valFmt("battery_soc");return`${t}W ${e._t("solar")} · SOC ${s}%`}},{id:"diag",icon:"mdi:bug-outline",color:"#ff9800",titleKey:"diagnostics",subtitleFn:()=>""}],Bs=[{key:"solar_power",icon:"mdi:solar-power",color:"#ff9800",suffix:"W"},{key:"grid_power",icon:"mdi:transmission-tower",color:"#488fc2",suffix:"W"},{key:"battery_soc",icon:"mdi:battery",color:"#4db6ac",suffix:"%"},{key:"forecast_today_kwh",icon:"mdi:weather-sunny",color:"#ff9800",suffix:"kWh"},{key:"tariff_current_import_rate",icon:"mdi:tag",color:"#96CAEE",suffix:""}],Rs=["sensor.sem_diag_version","sensor.sem_diag_grid_mode","sensor.sem_diag_battery_capacity","sensor.sem_diag_update_interval","sensor.sem_diag_charger_count","sensor.sem_diag_charger_control","sensor.sem_diag_sensors_unavailable","sensor.sem_diag_ed_config","sensor.sem_solar_power","sensor.sem_grid_power","sensor.sem_battery_soc","sensor.sem_ev_power","sensor.sem_forecast_today_kwh","sensor.sem_tariff_current_import_rate","switch.sem_observer_mode","sensor.sem_home_consumption_power","sensor.sem_grid_import_power","sensor.sem_grid_export_power","sensor.sem_autarky_rate","sensor.sem_self_consumption_rate","sensor.sem_battery_power","sensor.sem_night_charging_status","sensor.sem_battery_status","sensor.sem_grid_status","sensor.sem_charging_state"];ke("sem-system-card",class extends Le{static get watchedEntities(){return Rs}constructor(){super(),this._collapsed={info:!1,health:!1,diag:!0},this._copyFeedback=""}setConfig(e){super.setConfig(e),this._prefix=e.entity_prefix||"sensor.sem_"}_val(e){const t=this._hass?.states[`${this._prefix}${e}`];return t&&"unavailable"!==t.state&&"unknown"!==t.state?t.state:""}_valFmt(e,t=0){return function(e,t=0){return Fe(e)?Ne:parseFloat(e.state).toFixed(t)}(this._hass?.states[`${this._prefix}${e}`],t)}_valNum(e,t=null){return Te(this._hass?.states[`${this._prefix}${e}`])??t}_switchOn(e){return"on"===this._hass?.states[`switch.sem_${e}`]?.state}_available(e){const t=this._hass?.states[e];return!(!t||"unavailable"===t.state||"unknown"===t.state)}_toggleSection(e){this._collapsed={...this._collapsed,[e]:!this._collapsed[e]},this.requestUpdate()}_copyDiagnostics(){const e=this._val("diag_version")||"—",t=this._val("diag_grid_mode")||"—",s=this._val("diag_charger_count")||"—",i=this._val("diag_charger_control")||"—",r=this._valNum("diag_battery_capacity"),a=`SEM ${e} | Grid: ${t} | Chargers: ${s} (${i}) | Battery: ${r>0?r.toFixed(1):"—"}kWh | Unavailable: ${this._val("diag_sensors_unavailable")||Ne}`,o=this._hass?.states?.["sensor.sem_diag_ed_config"]?.attributes?.energy_dashboard;let n;if(o){const e=e=>e||"—";n=`Solar: pwr=${e(o.solar?.power)} [${e(o.solar?.power_source)}] energy=${e(o.solar?.energy)}\nGrid: pwr=${e(o.grid?.power)} [${e(o.grid?.power_source)}] imp=${e(o.grid?.import_energy)} exp=${e(o.grid?.export_energy)}\nBattery: pwr=${e(o.battery?.power)} [${e(o.battery?.power_source)}] chg=${e(o.battery?.charge_energy)} dis=${e(o.battery?.discharge_energy)}`}else n=`Config: ${this._val("diag_ed_config")||"—"}`;const l=`${a}\n${n}`;this._writeClipboard(l)}async _writeClipboard(e){const t=e=>{this._copyFeedback=this._t(e),this.requestUpdate(),setTimeout(()=>{this._copyFeedback="",this.requestUpdate()},2e3)};if(navigator?.clipboard?.writeText&&!1!==window.isSecureContext)try{return await navigator.clipboard.writeText(e),void t("copied")}catch(e){console.warn("[SEM System] modern clipboard API failed, falling back to execCommand",e)}try{const s=document.createElement("textarea");s.value=e,s.setAttribute("readonly",""),s.style.position="fixed",s.style.top="0",s.style.left="0",s.style.width="1px",s.style.height="1px",s.style.opacity="0",document.body.appendChild(s),s.focus(),s.select();const i=document.execCommand("copy");document.body.removeChild(s),i?t("copied"):(t("copy_failed"),console.error('[SEM System] execCommand("copy") returned false'))}catch(e){t("copy_failed"),console.error("[SEM System] legacy clipboard fallback threw",e)}}_renderSectionHeader(e,t){const s=this._collapsed[e.id],i=s?"rotate(-90deg)":"rotate(0deg)";return H` + `}},{type:"sem-price-card",name:"SEM Price Card",description:"Dynamic electricity price: current price, level, today range, next cheap window, and an hourly price strip (#257)",documentationURL:"https://github.com/traktore-org/sem-community/blob/develop/docs/DASHBOARD_GUIDE.md#sem-price-card",preview:!1});const ql=[{id:"info",icon:"mdi:information-outline",color:"#96CAEE",titleKey:"system_info",subtitleFn:e=>`v${e._val("diag_version")||"—"} · ${e._valLabel("diag_grid_mode")||"—"}`},{id:"health",icon:"mdi:heart-pulse",color:"#8DC892",titleKey:"health_overview",subtitleFn:e=>{const t=e._valFmt("solar_power"),s=e._valFmt("battery_soc");return`${t}W ${e._t("solar")} · SOC ${s}%`}},{id:"diag",icon:"mdi:bug-outline",color:"#ff9800",titleKey:"diagnostics",subtitleFn:()=>""}],Kl=[{key:"solar_power",icon:"mdi:solar-power",color:"#ff9800",suffix:"W"},{key:"grid_power",icon:"mdi:transmission-tower",color:"#488fc2",suffix:"W"},{key:"battery_soc",icon:"mdi:battery",color:"#4db6ac",suffix:"%"},{key:"forecast_today_kwh",icon:"mdi:weather-sunny",color:"#ff9800",suffix:"kWh"},{key:"tariff_current_import_rate",icon:"mdi:tag",color:"#96CAEE",suffix:""}],Hl=["sensor.sem_diag_version","sensor.sem_diag_grid_mode","sensor.sem_diag_battery_capacity","sensor.sem_diag_update_interval","sensor.sem_diag_charger_count","sensor.sem_diag_charger_control","sensor.sem_diag_sensors_unavailable","sensor.sem_diag_ed_config","sensor.sem_solar_power","sensor.sem_grid_power","sensor.sem_battery_soc","sensor.sem_ev_power","sensor.sem_forecast_today_kwh","sensor.sem_tariff_current_import_rate","switch.sem_observer_mode","sensor.sem_home_consumption_power","sensor.sem_grid_import_power","sensor.sem_grid_export_power","sensor.sem_autarky_rate","sensor.sem_self_consumption_rate","sensor.sem_battery_power","sensor.sem_night_charging_status","sensor.sem_battery_status","sensor.sem_grid_status","sensor.sem_charging_state"];ke("sem-system-card",class extends Re{static get watchedEntities(){return Hl}constructor(){super(),this._collapsed={info:!1,health:!1,diag:!0},this._copyFeedback=""}setConfig(e){super.setConfig(e),this._prefix=e.entity_prefix||"sensor.sem_"}_val(e){const t=this._hass?.states[`${this._prefix}${e}`];return t&&"unavailable"!==t.state&&"unknown"!==t.state?t.state:""}_valFmt(e,t=0){return function(e,t=0){return Fe(e)?Ee:parseFloat(e.state).toFixed(t)}(this._hass?.states[`${this._prefix}${e}`],t)}_valNum(e,t=null){return Ae(this._hass?.states[`${this._prefix}${e}`])??t}_switchOn(e){return"on"===this._hass?.states[`switch.sem_${e}`]?.state}_available(e){const t=this._hass?.states[e];return!(!t||"unavailable"===t.state||"unknown"===t.state)}_toggleSection(e){this._collapsed={...this._collapsed,[e]:!this._collapsed[e]},this.requestUpdate()}_copyDiagnostics(){const e=this._val("diag_version")||"—",t=this._val("diag_grid_mode")||"—",s=this._val("diag_charger_count")||"—",i=this._val("diag_charger_control")||"—",r=this._valNum("diag_battery_capacity"),a=`SEM ${e} | Grid: ${t} | Chargers: ${s} (${i}) | Battery: ${r>0?r.toFixed(1):"—"}kWh | Unavailable: ${this._val("diag_sensors_unavailable")||Ee}`,n=this._hass?.states?.["sensor.sem_diag_ed_config"]?.attributes?.energy_dashboard;let o;if(n){const e=e=>e||"—";o=`Solar: pwr=${e(n.solar?.power)} [${e(n.solar?.power_source)}] energy=${e(n.solar?.energy)}\nGrid: pwr=${e(n.grid?.power)} [${e(n.grid?.power_source)}] imp=${e(n.grid?.import_energy)} exp=${e(n.grid?.export_energy)}\nBattery: pwr=${e(n.battery?.power)} [${e(n.battery?.power_source)}] chg=${e(n.battery?.charge_energy)} dis=${e(n.battery?.discharge_energy)}`}else o=`Config: ${this._val("diag_ed_config")||"—"}`;const l=`${a}\n${o}`;this._writeClipboard(l)}async _writeClipboard(e){const t=e=>{this._copyFeedback=this._t(e),this.requestUpdate(),setTimeout(()=>{this._copyFeedback="",this.requestUpdate()},2e3)};if(navigator?.clipboard?.writeText&&!1!==window.isSecureContext)try{return await navigator.clipboard.writeText(e),void t("copied")}catch(e){console.warn("[SEM System] modern clipboard API failed, falling back to execCommand",e)}try{const s=document.createElement("textarea");s.value=e,s.setAttribute("readonly",""),s.style.position="fixed",s.style.top="0",s.style.left="0",s.style.width="1px",s.style.height="1px",s.style.opacity="0",document.body.appendChild(s),s.focus(),s.select();const i=document.execCommand("copy");document.body.removeChild(s),i?t("copied"):(t("copy_failed"),console.error('[SEM System] execCommand("copy") returned false'))}catch(e){t("copy_failed"),console.error("[SEM System] legacy clipboard fallback threw",e)}}_renderSectionHeader(e,t){const s=this._collapsed[e.id],i=s?"rotate(-90deg)":"rotate(0deg)";return j`
- `}_renderInfoSection(e){const t=this._valNum("diag_battery_capacity"),s=this._valNum("diag_update_interval"),i=this._val("diag_charger_count")||"—",r=this._valLabel("diag_charger_control"),a=r?`${i} (${r})`:i;return H` + `}_renderInfoSection(e){const t=this._valNum("diag_battery_capacity"),s=this._valNum("diag_update_interval"),i=this._val("diag_charger_count")||"—",r=this._valLabel("diag_charger_control"),a=r?`${i} (${r})`:i;return j`
${this._t("version")} ${this._val("diag_version")||"—"} @@ -4962,18 +6034,18 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow
${this._t("unavailable_sensors")} - ${this._val("diag_sensors_unavailable")||Ne} + ${this._val("diag_sensors_unavailable")||Ee}
- `}_renderHealthSection(e){return H` + `}_renderHealthSection(e){return j`
- ${Bs.map(e=>{const t=`${this._prefix}${e.key}`,s=this._available(t),i=this._hass?.states[t],r=i?i.state:"—";let a=e.suffix;if("tariff_current_import_rate"===e.key){const e=i?.attributes?.currency;a=e?` ${e}/kWh`:""}return H` + ${Kl.map(e=>{const t=`${this._prefix}${e.key}`,s=this._available(t),i=this._hass?.states[t],r=i?i.state:"—";let a=e.suffix;if("tariff_current_import_rate"===e.key){const e=i?.attributes?.currency;a=e?` ${e}/kWh`:""}return j`
${s?`${r}${a}`:"—"}
`})}
- `}_renderToggle(e,t,s){const i="on"===this._hass?.states[e]?.state;return H` + `}_renderToggle(e,t,s){const i="on"===this._hass?.states[e]?.state;return j`
${this._t(t)}
- `}_renderDiagBlock(e,t){return H` + `}_renderDiagBlock(e,t){return j`
${this._t(e)} ${t}
- `}_renderDiagSection(e){const t=`Solar ${this._valFmt("solar_power")}W · Grid ${this._valFmt("grid_power")}W · Battery ${this._valFmt("battery_power")}W · SOC ${this._valFmt("battery_soc")}% · EV ${this._valFmt("ev_power")}W`,s=`Home ${this._valFmt("home_consumption_power")}W · Import ${this._valFmt("grid_import_power")}W · Export ${this._valFmt("grid_export_power")}W · Autarky ${this._valFmt("autarky_rate")}% · Self ${this._valFmt("self_consumption_rate")}%`,i=this._val("charging_state")||"—",r=this._val("night_charging_status")||"—",a=this._val("battery_status")||"—",o=this._val("grid_status")||"—",n=`${i} · ${this._t("night")}: ${r} · ${this._t("battery")}: ${a} · ${this._t("grid")}: ${o}`;return H` + `}_renderDiagSection(e){const t=`Solar ${this._valFmt("solar_power")}W · Grid ${this._valFmt("grid_power")}W · Battery ${this._valFmt("battery_power")}W · SOC ${this._valFmt("battery_soc")}% · EV ${this._valFmt("ev_power")}W`,s=`Home ${this._valFmt("home_consumption_power")}W · Import ${this._valFmt("grid_import_power")}W · Export ${this._valFmt("grid_export_power")}W · Autarky ${this._valFmt("autarky_rate")}% · Self ${this._valFmt("self_consumption_rate")}%`,i=this._val("charging_state")||"—",r=this._val("night_charging_status")||"—",a=this._val("battery_status")||"—",n=this._val("grid_status")||"—",o=`${i} · ${this._t("night")}: ${r} · ${this._t("battery")}: ${a} · ${this._t("grid")}: ${n}`;return j` ${this._renderDiagBlock("source_sensors",t)} ${this._renderDiagBlock("derived_values",s)} - ${this._renderDiagBlock("mode_status",n)} - `}_renderSection(e,t,s){const i=this._collapsed[e.id];return H` + ${this._renderDiagBlock("mode_status",o)} + `}_renderSection(e,t,s){const i=this._collapsed[e.id];return j`
${this._renderSectionHeader(e,s)}
@@ -5001,13 +6073,13 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow
- `}render(){if(!this._config)return G;const e=this._theme(),t=!1!==e.isDark,s=e.accent||"#42a5f5",i={info:e=>this._renderInfoSection(e),health:e=>this._renderHealthSection(e),diag:e=>this._renderDiagSection(e)};return H` + `}render(){if(!this._config)return H;const e=this._theme(),t=!1!==e.isDark,s=e.accent||"#42a5f5",i={info:e=>this._renderInfoSection(e),health:e=>this._renderHealthSection(e),diag:e=>this._renderDiagSection(e)};return j`
- ${As.map(t=>this._renderSection(t,i[t.id],e))} + ${ql.map(t=>this._renderSection(t,i[t.id],e))}
- `}getCardSize(){return 8}static getStubConfig(){return{entity_prefix:"sensor.sem_"}}},{type:"sem-system-card",name:"SEM System Card",description:"Integration health — version, detected hardware, grid mode, sensor availability",documentationURL:"https://github.com/traktore-org/sem-community/blob/develop/docs/DASHBOARD_GUIDE.md#sem-system-card",preview:!1});const Os={now:{icon:"mdi:clock-outline",color:"#5BC8D8"},solar_peak:{icon:"mdi:weather-sunny",color:"#ff9800"},solar_end:{icon:"mdi:weather-sunset",color:"#ff9800"},cheap_start:{icon:"mdi:arrow-down-bold",color:"#8DC892"},cheap_end:{icon:"mdi:arrow-up-bold",color:"#ff9800"},expensive_start:{icon:"mdi:flash-alert",color:"#f06292"},expensive_end:{icon:"mdi:flash-off",color:"#8DC892"},night_open:{icon:"mdi:weather-night",color:"#8353d1"},night_end:{icon:"mdi:weather-sunset-up",color:"#ff9800"},ev_charge_start:{icon:"mdi:ev-station",color:"#8DC892"},ev_min_reached:{icon:"mdi:battery-charging-80",color:"#4db6ac"},ev_target_reached:{icon:"mdi:car-electric",color:"#8DC892"},ev_deadline:{icon:"mdi:clock-end",color:"#f06292"},ev_wait:{icon:"mdi:pause-circle",color:"#8353d1"},battery_full:{icon:"mdi:battery-charging-100",color:"#f06292"},battery_empty:{icon:"mdi:battery-low",color:"#4db6ac"},device_run:{icon:"mdi:power-plug",color:"#5BC8D8"},device_done:{icon:"mdi:check-circle",color:"#8DC892"},export_closed:{icon:"mdi:transmission-tower-off",color:"#f06292"},export_reopens:{icon:"mdi:transmission-tower",color:"#8353d1"}};ke("sem-today-plan-card",class extends Le{setConfig(e){super.setConfig(e),this._entity=e.entity||"sensor.sem_charging_state"}set hass(e){this._hass=e;const t=(e?.states[this._entity]?.attributes||{}).today_plan||[],s=[t.length,t[0]?.when,t.at(-1)?.when,this._lang].join("|"),i="function"==typeof semLocalize;(s!==this._lastKey||i&&!this._localizeReady)&&(this._lastKey=s,this._lang=e?.language,this._localizeReady=i,this.requestUpdate())}get hass(){return this._hass}_hm(e){if(!e)return"—";try{const t=new Date(e),s=this._hass?.config?.time_zone||void 0,i=ge(e,s),r=e=>e.toLocaleDateString("en-CA",{timeZone:s}),a=r(t),o=new Date;if(a===r(o))return i;if(a===r(new Date(o.getTime()+864e5))){return`${this._t("tomorrow")||"Tomorrow"} ${i}`}return`${t.toLocaleDateString([],{weekday:"short",timeZone:s})} ${i}`}catch(e){return"—"}}_format(e,t){let s=this._t(e);if(!s||s===e)return null;for(const[e,i]of Object.entries(t||{}))s=s.replace(new RegExp(`\\{${e}\\}`,"g"),i);return s}render(){if(!this._hass||!this._config)return G;const e=this._hass.states[this._entity],t=e?.attributes?.today_plan||[];return!Array.isArray(t)||t.length<=1?(this.style.display="none",G):(this.style.display="",H` + `}getCardSize(){return 8}static getStubConfig(){return{entity_prefix:"sensor.sem_"}}},{type:"sem-system-card",name:"SEM System Card",description:"Integration health — version, detected hardware, grid mode, sensor availability",documentationURL:"https://github.com/traktore-org/sem-community/blob/develop/docs/DASHBOARD_GUIDE.md#sem-system-card",preview:!1});const Gl={now:{icon:"mdi:clock-outline",color:"#5BC8D8"},solar_peak:{icon:"mdi:weather-sunny",color:"#ff9800"},solar_end:{icon:"mdi:weather-sunset",color:"#ff9800"},cheap_start:{icon:"mdi:arrow-down-bold",color:"#8DC892"},cheap_end:{icon:"mdi:arrow-up-bold",color:"#ff9800"},expensive_start:{icon:"mdi:flash-alert",color:"#f06292"},expensive_end:{icon:"mdi:flash-off",color:"#8DC892"},night_open:{icon:"mdi:weather-night",color:"#8353d1"},night_end:{icon:"mdi:weather-sunset-up",color:"#ff9800"},ev_charge_start:{icon:"mdi:ev-station",color:"#8DC892"},ev_min_reached:{icon:"mdi:battery-charging-80",color:"#4db6ac"},ev_target_reached:{icon:"mdi:car-electric",color:"#8DC892"},ev_deadline:{icon:"mdi:clock-end",color:"#f06292"},ev_wait:{icon:"mdi:pause-circle",color:"#8353d1"},battery_full:{icon:"mdi:battery-charging-100",color:"#f06292"},battery_empty:{icon:"mdi:battery-low",color:"#4db6ac"},device_run:{icon:"mdi:power-plug",color:"#5BC8D8"},device_done:{icon:"mdi:check-circle",color:"#8DC892"},export_closed:{icon:"mdi:transmission-tower-off",color:"#f06292"},export_reopens:{icon:"mdi:transmission-tower",color:"#8353d1"}};ke("sem-today-plan-card",class extends Re{setConfig(e){super.setConfig(e),this._entity=e.entity||"sensor.sem_charging_state"}set hass(e){this._hass=e;const t=(e?.states[this._entity]?.attributes||{}).today_plan||[],s=[t.length,t[0]?.when,t.at(-1)?.when,this._lang].join("|"),i="function"==typeof semLocalize;(s!==this._lastKey||i&&!this._localizeReady)&&(this._lastKey=s,this._lang=e?.language,this._localizeReady=i,this.requestUpdate())}get hass(){return this._hass}_hm(e){if(!e)return"—";try{const t=new Date(e),s=this._hass?.config?.time_zone||void 0,i=me(e,s),r=e=>e.toLocaleDateString("en-CA",{timeZone:s}),a=r(t),n=new Date;if(a===r(n))return i;if(a===r(new Date(n.getTime()+864e5))){return`${this._t("tomorrow")||"Tomorrow"} ${i}`}return`${t.toLocaleDateString([],{weekday:"short",timeZone:s})} ${i}`}catch(e){return"—"}}_format(e,t){let s=this._t(e);if(!s||s===e)return null;for(const[e,i]of Object.entries(t||{}))s=s.replace(new RegExp(`\\{${e}\\}`,"g"),i);return s}render(){if(!this._hass||!this._config)return H;const e=this._hass.states[this._entity],t=e?.attributes?.today_plan||[];return!Array.isArray(t)||t.length<=1?(this.style.display="none",H):(this.style.display="",j`
@@ -5202,13 +6274,13 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow ${this._t("today_plan_title")}
    - ${t.map(e=>{const t=Os[e.kind]||Os.now,s=this._format(e.label,e.values)||e.label,i=e.detail?this._format(e.detail,e.values):null;return H` + ${t.map(e=>{const t=Gl[e.kind]||Gl.now,s=this._format(e.label,e.values)||e.label,i=e.detail?this._format(e.detail,e.values):null;return j`
  • ${this._hm(e.when)}
    ${s} - ${i?H`${i}`:G} + ${i?j`${i}`:H}
  • `})} @@ -5261,27 +6333,27 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow .detail { font-size: 11px; opacity: 0.65; line-height: 1.2; } - `}},{type:"sem-today-plan-card",name:"SEM Today's Plan",description:"Forward-looking schedule combining tariff, solar, and EV",documentationURL:"https://github.com/traktore-org/sem-community/blob/develop/docs/DASHBOARD_GUIDE.md#sem-today-plan-card"});const Is=e=>null!=e&&""!==e&&Number.isFinite(Number(e))?Number(e):null,Ls={peak_shed:"hq_because_peak",phase_guard:"hq_because_phase",phase_shed:"hq_because_phase",vacation:"hq_because_away",plan_wait:"hq_because_plan_wait",no_source:"hq_because_no_sun",surplus_short:"hq_because_no_sun",target_met:"hq_because_ran_enough",daily_max:"hq_because_daily_max",stop_condition:"hq_because_stop",at_target:"hq_because_stop",load_override_stop:"hq_because_paused",step_down_price:"hq_because_held_back",step_down_peak:"hq_because_held_back",no_season:"hq_because_no_season",battery_reserve:"hq_because_battery_reserve"};function Ps(e,t={},s=!1){const i=e&&e.comfort;if(!i)return null;const r="cool"===i.hvac,a=Is(i.keep_c),o=Is(i.start_c),n=Is(i.max_c);if(null===a)return null;const l="function"==typeof t.fmtTemp?t.fmtTemp:e=>Math.round(10*e)/10+" °C",c=e=>r&&Us[e]||e,d=(e,t={},s="",i="")=>r?{key:c(e),params:t,cls:s,why:i,cool:!0}:{key:e,params:t,cls:s,why:i},p=e.intent||{},_=String(p.code||""),h=Is(i.reading_c);if(s)return i.own_run||"legionella"===_?d("hq_st_legionella",{},"ok"):"comfort_floor"===_||"comfort_floor_away"===_||"forced"===i.state?d("hq_st_too_cold",{},"warn"):"comfort_goal_battery"===_||"comfort_goal"!==_&&"tier2_battery"===p.source?d("hq_st_heating_battery",{keep:l(a)},"ok"):"comfort_goal"===_?d("hq_st_heating_to",{keep:l(a)},"ok"):"solar"===p.source||"tier1_battery"===p.source?d("hq_st_heating_sun",{max:l(null===n?a:n)},"ok"):null;if("off"===e.mergedMode)return null;const u=(e,t={},s="",i="")=>({...d(e,t,s,i),next:!0});if(null===h)return u("hq_st_no_reading",{},"warn");if(r?h<=a:h>=a)return u("hq_st_warm",{reading:l(h)});const g=null===o?a:o;if(r?hg)return u("hq_st_warm_again",{start:l(g)});if(t.plannedAt)return u("hq_st_heats_at",{keep:l(a),time:t.plannedAt});if(!0===i.may_use_battery)return"battery_reserve"===_?{...u("hq_st_waits_battery",{keep:l(a)},"warn"),battery:!0}:{...u("hq_st_heats_tonight_battery",{keep:l(a)}),battery:!0};if(!1===i.may_buy)return{...u("hq_st_waits_sun",{keep:l(a)}),noBuy:!0};const m=function(e){return!e||e.on?"":Ls[String(e.code||"")]||""}(e.intent);return u("hq_st_below_none",{keep:l(a)},"warn","hq_because_no_sun"===m?"":m)}const Us={hq_st_too_cold:"hq_st_too_warm",hq_st_heating_to:"hq_st_cooling_to",hq_st_heating_battery:"hq_st_cooling_battery",hq_st_heating_sun:"hq_st_cooling_sun",hq_st_heats_at:"hq_st_cools_at",hq_st_heats_tonight_battery:"hq_st_cools_tonight_battery",hq_st_waits_battery:"hq_st_waits_battery_cool",hq_st_below_none:"hq_st_above_none",hq_st_warm_again:"hq_st_cool_again",hq_st_warm:"hq_st_cool",hq_st_waits_sun:"hq_st_waits_sun_cool"};const qs={off:"hq_buy_no",grid:"hq_buy_cheap",battery:"hq_buy_battery"};function Hs(e,t){const s=e&&Array.isArray(e.notes)?e.notes:null;if(!s||!s.length)return e&&e.note||null;const i=[];for(const r of s){const s="plan_note_"+(r&&r.code);let a=t(s);if(!a||a===s)return e.note||null;for(const[e,t]of Object.entries(r&&r.params||{}))a=a.replace(new RegExp("\\{"+e+"\\}","g"),String(t));i.push(a)}return i.join("; ")}const Ws={ev:{icon:"mdi:ev-station",color:"#8DC892",label:"energy_plan_kind_ev"},load:{icon:"mdi:power-plug",color:"#5BC8D8",label:"energy_plan_kind_load"},battery:{icon:"mdi:home-battery",color:"#f06292",label:"energy_plan_kind_battery"},comfort:{icon:"mdi:thermometer",color:"#4db6ac",label:"energy_plan_kind_comfort"}},js=Object.fromEntries(Object.entries(Ws).map(([e,t])=>[e,t.label])),Gs={actuation:{docs:"https://github.com/traktore-org/sem-community/blob/develop/docs/ENERGY_PLANNER.md#actuation-g4"},arbitrage:{docs:"https://github.com/traktore-org/sem-community/blob/develop/docs/ENERGY_PLANNER.md#the-arbitrage-advisor"}},Ks={fits:{icon:"mdi:check-circle",color:"#8DC892"},partial:{icon:"mdi:circle-slice-4",color:"#ff9800"},yields:{icon:"mdi:alert-circle-outline",color:"#f06292"}};class Vs extends Le{setConfig(e){super.setConfig(e),this._entity=e.entity||"sensor.sem_energy_plan"}set hass(e){this._hass=e;const t=e?.states[this._entity],s=t?.attributes||{},i=!0===s.actuation&&Array.isArray(s.demands)?s.demands.map(e=>this._liveState(e,s.blocks)).join(","):"",r=s.tomorrow||{},a=s.coverage?Object.entries(s.coverage).map(([e,t])=>e+"="+t).join(","):"",o=[t?.state,s.computed_at,s.actuation,i,a,this._view||"today",r.prices,r.forecast_kwh,r.stamps_at,e?.states["switch.sem_energy_plan_actuation"]?.state,e?.states["sensor.sem_night_start_time"]?.state,e?.language].join("|"),n="function"==typeof semLocalize;(o!==this._lastKey||n&&!this._localizeReady)&&(this._lastKey=o,this._lang=e?.language,this._localizeReady=n,this.requestUpdate())}get hass(){return this._hass}_liveState(e,t){if(!e||"battery"===e.kind)return null;if("fits"!==e.status)return"reactive";const s=Date.now();let i=null,r=!1;for(const a of Array.isArray(t)?t:[]){if(a.id!==e.id)continue;const t=Date.parse(a.start),o=Date.parse(a.end);if(Number.isFinite(t)&&Number.isFinite(o)){if(r=!0,t<=s&&ss&&(null===i||t[e,t.label])),Zl={actuation:{docs:"https://github.com/traktore-org/sem-community/blob/develop/docs/ENERGY_PLANNER.md#actuation-g4"},arbitrage:{docs:"https://github.com/traktore-org/sem-community/blob/develop/docs/ENERGY_PLANNER.md#the-arbitrage-advisor"}},Xl={fits:{icon:"mdi:check-circle",color:"#8DC892"},partial:{icon:"mdi:circle-slice-4",color:"#ff9800"},yields:{icon:"mdi:alert-circle-outline",color:"#f06292"}};class Jl extends Re{setConfig(e){super.setConfig(e),this._entity=e.entity||"sensor.sem_energy_plan"}set hass(e){this._hass=e;const t=e?.states[this._entity],s=t?.attributes||{},i=!0===s.actuation&&Array.isArray(s.demands)?s.demands.map(e=>this._liveState(e,s.blocks)).join(","):"",r=s.tomorrow||{},a=s.coverage?Object.entries(s.coverage).map(([e,t])=>e+"="+t).join(","):"",n=[t?.state,s.computed_at,s.actuation,i,a,this._view||"today",r.prices,r.forecast_kwh,r.stamps_at,e?.states["switch.sem_energy_plan_actuation"]?.state,e?.states["sensor.sem_night_start_time"]?.state,e?.language].join("|"),o="function"==typeof semLocalize;(n!==this._lastKey||o&&!this._localizeReady)&&(this._lastKey=n,this._lang=e?.language,this._localizeReady=o,this.requestUpdate())}get hass(){return this._hass}_liveState(e,t){if(!e||"battery"===e.kind)return null;if("fits"!==e.status)return"reactive";const s=Date.now();let i=null,r=!1;for(const a of Array.isArray(t)?t:[]){if(a.id!==e.id)continue;const t=Date.parse(a.start),n=Date.parse(a.end);if(Number.isFinite(t)&&Number.isFinite(n)){if(r=!0,t<=s&&ss&&(null===i||t${this._t("energy_plan_reactive")} · ${this._t(t)}`:G}_liveChip(e){if(!e)return G;if("now"===e)return H`▶ ${this._t("energy_plan_live_now")}`;if(e.startsWith("wait:")){const t=this._hm(new Date(Number(e.slice(5))).toISOString());return H`${this._format("energy_plan_live_wait",{time:t})||t}`}return"done"===e?H`${this._t("energy_plan_live_done")}`:H`${this._t("energy_plan_live_reactive")}`}_hm(e){return e?ge(e,this._hass?.config?.time_zone||void 0):"—"}_demandName(e,t,s){return function(e,t,s,i,r){if(e)return e;const a=String(t||"").split(":")[0];return r(i[s]||i[a]||i.load)}(e,t,s,js,e=>this._t(e))}_format(e,t){let s=this._t(e);if(!s||s===e)return null;for(const[e,i]of Object.entries(t||{}))s=s.replace(new RegExp("\\{"+e+"\\}","g"),i);return s}_runs(e,t,s,i){const r=[];for(const[t,s]of e.entries()){const e=i(s,t),a=Date.parse(s.start),o=Date.parse(s.end);if(!Number.isFinite(a)||!Number.isFinite(o))continue;const n=r[r.length-1];n&&n.v===e&&Math.abs(n.endMs-a)<1e3?n.endMs=o:r.push({v:e,startMs:a,endMs:o})}return r.map(e=>({v:e.v,left:(e.startMs-t)/s*100,width:(e.endMs-e.startMs)/s*100}))}_modeChip(e){return H`${this._t(e?"energy_plan_active":"energy_plan_shadow")}`}_docsLink(e="actuation"){return H`${this._t("energy_plan_reactive")} · ${this._t(t)}`:H}_liveChip(e){if(!e)return H;if("now"===e)return j`▶ ${this._t("energy_plan_live_now")}`;if(e.startsWith("wait:")){const t=this._hm(new Date(Number(e.slice(5))).toISOString());return j`${this._format("energy_plan_live_wait",{time:t})||t}`}return"done"===e?j`${this._t("energy_plan_live_done")}`:j`${this._t("energy_plan_live_reactive")}`}_hm(e){return e?me(e,this._hass?.config?.time_zone||void 0):"—"}_demandName(e,t,s){return function(e,t,s,i,r){if(e)return e;const a=String(t||"").split(":")[0];return r(i[s]||i[a]||i.load)}(e,t,s,Yl,e=>this._t(e))}_format(e,t){let s=this._t(e);if(!s||s===e)return null;for(const[e,i]of Object.entries(t||{}))s=s.replace(new RegExp("\\{"+e+"\\}","g"),i);return s}_runs(e,t,s,i){const r=[];for(const[t,s]of e.entries()){const e=i(s,t),a=Date.parse(s.start),n=Date.parse(s.end);if(!Number.isFinite(a)||!Number.isFinite(n))continue;const o=r[r.length-1];o&&o.v===e&&Math.abs(o.endMs-a)<1e3?o.endMs=n:r.push({v:e,startMs:a,endMs:n})}return r.map(e=>({v:e.v,left:(e.startMs-t)/s*100,width:(e.endMs-e.startMs)/s*100}))}_modeChip(e){return j`${this._t(e?"energy_plan_active":"energy_plan_shadow")}`}_docsLink(e="actuation"){return j` - `}_viewToggle(e){if(!e)return G;const t=this._view||"today",s=(e,s)=>H` + `}_viewToggle(e){if(!e)return H;const t=this._view||"today",s=(e,s)=>j` `;return H` + ${this._t(s)}`;return j` ${s("today","energy_plan_today")}${s("tomorrow","energy_plan_tomorrow")} - `}_renderTomorrow(e){const t=e.provisional||null,s=Date.parse(e.stamps_at),i=Date.parse(e.night_open),r=i-s,a=Number.isFinite(r)&&r>0,o=e=>(Date.parse(e)-s)/r*100,n=(e,t,s)=>{const i=Math.max(0,o(e.start)),r=Math.min(100,o(e.end))-i;return!Number.isFinite(i)||!Number.isFinite(r)||r<=0?G:H`
    `},l=H` +
    `}_renderTomorrow(e){const t=e.provisional||null,s=Date.parse(e.stamps_at),i=Date.parse(e.night_open),r=i-s,a=Number.isFinite(r)&&r>0,n=e=>(Date.parse(e)-s)/r*100,o=(e,t,s)=>{const i=Math.max(0,n(e.start)),r=Math.min(100,n(e.end))-i;return!Number.isFinite(i)||!Number.isFinite(r)||r<=0?H:j`
    `},l=j`
    - ${(e.surplus_windows||[]).map(e=>n(e,"tmw-sun"))} - ${(e.cheap_windows||[]).map(e=>n(e,"cheap"))} + ${(e.surplus_windows||[]).map(e=>o(e,"tmw-sun"))} + ${(e.cheap_windows||[]).map(e=>o(e,"cheap"))}
    - `,c=H` + `,c=j`
    - ${(e.cheap_windows||[]).map(e=>n(e,"cheap"))} + ${(e.cheap_windows||[]).map(e=>o(e,"cheap"))}
    - `,d=[];if(a){const e=new Date(s);e.setMinutes(0,0,0),e.getTime()94)break;d.push({left:e,text:this._hm(a.toISOString())})}}const p=t&&Array.isArray(t.soc_curve)?t.soc_curve:[],_=[];for(let e=0;e+1p[e].kwh+.01});const h=e.known_asks||[],u="final"!==e.prices;return H` + `,_=[];if(a){const e=new Date(s);e.setMinutes(0,0,0),e.getTime()94)break;_.push({left:e,text:this._hm(a.toISOString())})}}const d=t&&Array.isArray(t.soc_curve)?t.soc_curve:[],p=[];for(let e=0;e+1d[e].kwh+.01});const h=e.known_asks||[],u="final"!==e.prices;return j`
    @@ -5295,46 +6367,46 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow ${this._docsLink()}
    - ${a?H` + ${a?j`
    ${c} - ${d.map(e=>H` + ${_.map(e=>j` ${e.text} `)}
    - `:G} - ${p.length>1?H` + `:H} + ${d.length>1?j`
    ${this._t("energy_plan_kind_battery")}
    - ${a?H` + ${a?j`
    ${l} - ${_.map(e=>n(e,"run",e.rising?"#f06292":"#4db6ac"))} + ${p.map(e=>o(e,"run",e.rising?"#f06292":"#4db6ac"))}
    - `:G} + `:H}
    - ${p[0].kwh.toFixed(1)} → ${p[p.length-1].kwh.toFixed(1)} kWh + ${d[0].kwh.toFixed(1)} → ${d[d.length-1].kwh.toFixed(1)} kWh
    - `:G} - ${h.map((e,s)=>{const i=Ws[e.kind]||Ws.load,r=((e,s)=>t&&Array.isArray(t.blocks)?t.blocks.filter(t=>t.id===`${e}:${s}`):[])(e.kind,s),o=[this._demandName(e.label,null,e.kind),r.map(e=>`${this._hm(e.start)}–${this._hm(e.end)}`).join("\n")||null,this._t("energy_plan_provisional_tip")].filter(Boolean).join("\n");return H` -
    + `:H} + ${h.map((e,s)=>{const i=Vl[e.kind]||Vl.load,r=((e,s)=>t&&Array.isArray(t.blocks)?t.blocks.filter(t=>t.id===`${e}:${s}`):[])(e.kind,s),n=[this._demandName(e.label,null,e.kind),r.map(e=>`${this._hm(e.start)}–${this._hm(e.end)}`).join("\n")||null,this._t("energy_plan_provisional_tip")].filter(Boolean).join("\n");return j` +
    ${this._demandName(e.label,null,e.kind)}
    - ${a?H` + ${a?j`
    ${l} - ${r.map(e=>n(e,"run",i.color))} + ${r.map(e=>o(e,"run",i.color))}
    - `:G} + `:H}
    ${(e.kwh||0).toFixed(1)} kWh
    @@ -5343,7 +6415,7 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow
    ${this._t("energy_plan_legend_surplus")} ${this._t("energy_plan_legend_cheap")} - ${p.length>1?H`${this._t("energy_plan_legend_battery_charge")}`:G} + ${d.length>1?j`${this._t("energy_plan_legend_battery_charge")}`:H}
    ☀ ${(e.forecast_kwh||0).toFixed(1)} kWh · @@ -5351,10 +6423,10 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow
    - `}_renderReview(){const e=this._hass?.states?.[this._entity]?.attributes?.review;if(!e)return G;const t=[];for(const s of e.demands||[]){const e=this._format("energy_plan_review_"+s.code,{nights:s.nights,asked:(s.asked_kwh||0).toFixed(1),suggested:null==s.suggested_kwh?"":Number(s.suggested_kwh).toFixed(1),actual:(s.last_kwh||0).toFixed(1)});e&&t.push({d:s,text:e,k:Ws[s.kind]||Ws.load})}const s=e.battery,i=s?this._format("energy_plan_review_"+s.code,{drained:(s.drained||0).toFixed(1),clipped:(s.clipped_h||0).toFixed(1),full_at:s.full_at_ts?new Date(1e3*s.full_at_ts).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"}):""}):null,r=e.self_consumption,a=r?this._format("energy_plan_review_"+r.code,{actual:Math.round(100*(r.actual_share||0)),predicted:Math.round(100*(r.predicted_share||0))}):null;return t.length||a||i?H` + `}_renderReview(){const e=this._hass?.states?.[this._entity]?.attributes?.review;if(!e)return H;const t=[];for(const s of e.demands||[]){const e=this._format("energy_plan_review_"+s.code,{nights:s.nights,asked:(s.asked_kwh||0).toFixed(1),suggested:null==s.suggested_kwh?"":Number(s.suggested_kwh).toFixed(1),actual:(s.last_kwh||0).toFixed(1)});e&&t.push({d:s,text:e,k:Vl[s.kind]||Vl.load})}const s=e.battery,i=s?this._format("energy_plan_review_"+s.code,{drained:(s.drained||0).toFixed(1),clipped:(s.clipped_h||0).toFixed(1),full_at:s.full_at_ts?new Date(1e3*s.full_at_ts).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"}):""}):null,r=e.self_consumption,a=r?this._format("energy_plan_review_"+r.code,{actual:Math.round(100*(r.actual_share||0)),predicted:Math.round(100*(r.predicted_share||0))}):null;return t.length||a||i?j`
    ${this._t("energy_plan_review_title")}
    - ${t.map(e=>H` + ${t.map(e=>j`
    @@ -5362,22 +6434,22 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow ${e.text}
    `)} - ${i?H` + ${i?j`
    ${this._t("battery")} ${i} -
    `:G} - ${a?H` +
    `:H} + ${a?j`
    ${a}
    - `:G} + `:H}
    - `:G}_renderArb(e){return e&&e.enabled?H` + `:H}_renderArb(e){return e&&e.enabled?j`
    @@ -5385,7 +6457,7 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow ${e.reason||""} ${this._docsLink("arbitrage")}
    - `:G}_coldHeaters(e,t){const s=this._hass&&this._hass.states["sensor.sem_controllable_devices_count"],i=e.length?e[e.length-1].end:"",r=i&&Number.isFinite(Date.parse(i))?new Date(i):null,a=e=>String(e).padStart(2,"0"),o=r?a(r.getHours())+":"+a(r.getMinutes()):"",n=this._hass?.config?.unit_system?.temperature||"°C";return function(e,t,s){const i=Array.isArray(t)?t:[],r=[];for(const[t,a]of Object.entries(e||{})){const e=a&&a.comfort;if(!e||null==e.keep_c||null==e.reading_c)continue;const o="cool"===e.hvac,n=Number(e.keep_c),l=Number(e.start_c??e.keep_c),c=Number(e.reading_c);if(![n,l,c].every(Number.isFinite))continue;if(o?cl)continue;if(a.is_on||a.intent&&a.intent.on)continue;if("off"===a.control_mode||!0===a.user_hands_off)continue;if(!1===e.may_buy)continue;const d=i.some(e=>e&&(String(e.id)==="comfort:"+t||String(e.id)==="load:"+t)&&Date.parse(e.end)>s);d||r.push(o?{id:t,name:a.name||t,keepC:n,cool:!0}:{id:t,name:a.name||t,keepC:n})}return r}(s&&s.attributes&&s.attributes.devices,t,Date.now()).map(e=>(this._t((e.cool?"hq_plan_above_no_hour":"hq_plan_below_no_hour")+(o?"":"_plain"))||"").replace("{name}",e.name).replace("{keep}",(e=>Math.round(10*("°F"===n?9*e/5+32:e))/10+" "+n)(e.keepC)).replace("{time}",o))}_renderIdle(e,t="energy_plan_idle",s="",i=!1,r="",a=[],o=[],n=null,l=[]){const c=(a||[]).map(e=>this._t("energy_plan_whyc_"+e)).filter(Boolean).join(" · ");return H` + `:H}_coldHeaters(e,t){const s=this._hass&&this._hass.states["sensor.sem_controllable_devices_count"],i=e.length?e[e.length-1].end:"",r=i&&Number.isFinite(Date.parse(i))?new Date(i):null,a=e=>String(e).padStart(2,"0"),n=r?a(r.getHours())+":"+a(r.getMinutes()):"",o=this._hass?.config?.unit_system?.temperature||"°C";return ys(s&&s.attributes&&s.attributes.devices,t,Date.now()).map(e=>(this._t((e.cool?"hq_plan_above_no_hour":"hq_plan_below_no_hour")+(n?"":"_plain"))||"").replace("{name}",e.name).replace("{keep}",(e=>Math.round(10*("°F"===o?9*e/5+32:e))/10+" "+o)(e.keepC)).replace("{time}",n))}_renderIdle(e,t="energy_plan_idle",s="",i=!1,r="",a=[],n=[],o=null,l=[]){const c=(a||[]).map(e=>this._t("energy_plan_whyc_"+e)).filter(Boolean).join(" · ");return j`
    @@ -5395,12 +6467,12 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow ${this._modeChip(e)} ${this._docsLink()}
    - ${(l||[]).length?l.map(e=>H`
    ${e}
    `):H`
    ${this._t(t)}${s}
    `} - ${c?H`
    ${c}
    `:r?H`
    ${r}
    `:G} - ${(o||[]).length?H` + ${(l||[]).length?l.map(e=>j`
    ${e}
    `):j`
    ${this._t(t)}${s}
    `} + ${c?j`
    ${c}
    `:r?j`
    ${r}
    `:H} + ${(n||[]).length?j`
    ${this._t("energy_plan_not_scheduled")}
    - ${o.map(e=>H` + ${n.map(e=>j`
    @@ -5409,30 +6481,30 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow
    `)}
    - `:G} - ${this._renderArb(n)} + `:H} + ${this._renderArb(o)} ${this._renderReview()}
    - `}render(){if(!this._hass||!this._config)return G;const e=this._hass.states[this._entity],t=e?.state;if(!e||"unavailable"===t||"unknown"===t)return this.style.display="none",G;this.style.display="";const s=e.attributes||{},i=!0===s.actuation||void 0===s.actuation&&"on"===this._hass.states["switch.sem_energy_plan_actuation"]?.state;if("tomorrow"===(this._view||"today")&&s.tomorrow)return this._renderTomorrow(s.tomorrow);if("pending"===t){const e=this._hass.states["sensor.sem_night_start_time"]?.state,t=e&&/^\d{1,2}:\d{2}$/.test(e)?` (~${e})`:"";return this._renderIdle(i,"energy_plan_pending",t,!!s.tomorrow)}const r=Array.isArray(s.demands)?s.demands:[],a=Array.isArray(s.slots)?s.slots:[],o=Array.isArray(s.blocks)?s.blocks:[];if("idle"===t||!r.length)return this._renderIdle(i,"energy_plan_idle","",!!s.tomorrow,s.why||"",s.why_codes||[],s.not_scheduled||[],s.arbitrage,this._coldHeaters(a,o));const n=a.length?Date.parse(a[0].start):NaN,l=a.length?Date.parse(a[a.length-1].end):NaN,c=l-n,d=a.length>0&&Number.isFinite(c)&&c>0,p=this._runs(a,n,c,e=>!!e.cheap).filter(e=>e.v);let _=null;const h=this._hass.states["sensor.sem_night_start_time"]?.state;if(d&&h&&/^\d{1,2}:\d{2}$/.test(h)){const[e,t]=h.split(":").map(Number);for(const s of[0,1]){const i=new Date(n);i.setDate(i.getDate()+s),i.setHours(e,t,0,0);const r=i.getTime();if(r>=n&&r=n&&u0&&Number.isFinite(c)&&c>0,d=this._runs(a,o,c,e=>!!e.cheap).filter(e=>e.v);let p=null;const h=this._hass.states["sensor.sem_night_start_time"]?.state;if(_&&h&&/^\d{1,2}:\d{2}$/.test(h)){const[e,t]=h.split(":").map(Number);for(const s of[0,1]){const i=new Date(o);i.setDate(i.getDate()+s),i.setHours(e,t,0,0);const r=i.getTime();if(r>=o&&r=o&&u - ${p.map(e=>H` + ${d.map(e=>j`
    `)} - ${null!==_?H` -
    `:G} - ${null!==g?H` -
    `:G} + ${null!==p?j` +
    `:H} + ${null!==m?j` +
    `:H}
    - `;let f=a.filter(e=>0===new Date(e.start).getMinutes());f.length||(f=a);const v=Math.max(1,Math.ceil(f.length/8)),y=f.filter((e,t)=>t%v===0).map(e=>({left:(Date.parse(e.start)-n)/c*100,text:this._hm(e.start)})).filter(e=>e.left<=94),b=function(e){if(!Array.isArray(e))return null;const t=new Set;for(const s of e){if(!Array.isArray(s))continue;const e=Number(s[0]),i=Number(s[1]);if(Number.isInteger(e)&&Number.isInteger(i))for(let s=e;s<=i;s++)t.add(s)}return t}(s.batt_runs),x=this._runs(a,n,c,(e,t)=>{return s=e,i=b?b.has(t):void 0,(s&&s.home_grid_w||0)>1?"grid":void 0===i||i?"batt":"sun";var s,i}),$=new Set(x.map(e=>e.v)),w=!1!==s.has_battery,k=w?s.takeover?this._format("energy_plan_takeover",{time:this._hm(s.takeover)}):this._t("energy_plan_all_night"):"",S=w?s.takeover?H` + `;let f=a.filter(e=>0===new Date(e.start).getMinutes());f.length||(f=a);const y=Math.max(1,Math.ceil(f.length/8)),b=f.filter((e,t)=>t%y===0).map(e=>({left:(Date.parse(e.start)-o)/c*100,text:this._hm(e.start)})).filter(e=>e.left<=94),v=function(e){if(!Array.isArray(e))return null;const t=new Set;for(const s of e){if(!Array.isArray(s))continue;const e=Number(s[0]),i=Number(s[1]);if(Number.isInteger(e)&&Number.isInteger(i))for(let s=e;s<=i;s++)t.add(s)}return t}(s.batt_runs),x=this._runs(a,o,c,(e,t)=>{return s=e,i=v?v.has(t):void 0,(s&&s.home_grid_w||0)>1?"grid":void 0===i||i?"batt":"sun";var s,i}),$=new Set(x.map(e=>e.v)),w=!1!==s.has_battery,k=w?s.takeover?this._format("energy_plan_takeover",{time:this._hm(s.takeover)}):this._t("energy_plan_all_night"):"",S=w?s.takeover?j` ${this._hm(s.takeover)} - `:H` + `:j` ${this._t("energy_plan_all_night_short")} - `:G,C=fe(this._hass),z=Number(s.total_cost),M=Number.isFinite(z)?`${this._t("energy_plan_est")} ${z.toFixed(2)} ${C}`:null,D=!1!==s.fits,E={};for(const e of o)(E[e.id]=E[e.id]||[]).push(e);return H` + `:H,C=fe(this._hass),M=Number(s.total_cost),z=Number.isFinite(M)?`${this._t("energy_plan_est")} ${M.toFixed(2)} ${C}`:null,D=!1!==s.fits,N={};for(const e of n)(N[e.id]=N[e.id]||[]).push(e);return j`
    @@ -5451,79 +6523,79 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow ${D?this._t("energy_plan_fits"):this._t("energy_plan_yields")} - ${M?H`${M}`:G} + ${z?j`${z}`:H}
    -
    - ${d?H` +
    + ${_?j`
    - ${m} - ${y.map(e=>H` + ${g} + ${b.map(e=>j` ${e.text} `)} - ${a.map(e=>{const t=(Date.parse(e.start)-n)/c*100,s=(Date.parse(e.end)-Date.parse(e.start))/c*100;if(!Number.isFinite(t)||!Number.isFinite(s))return G;const i=null===e.price||void 0===e.price?"—":`${e.price} ${C}`,r=Number.isFinite(e.co2)?` · ${Math.round(1e3*e.co2)} g/kWh`:"",a=`${this._hm(e.start)}–${this._hm(e.end)} · ${i}${r}`+(e.cheap?` · ${this._t("energy_plan_legend_cheap")}`:"");return H`
    {const t=(Date.parse(e.start)-o)/c*100,s=(Date.parse(e.end)-Date.parse(e.start))/c*100;if(!Number.isFinite(t)||!Number.isFinite(s))return H;const i=null===e.price||void 0===e.price?"—":`${e.price} ${C}`,r=Number.isFinite(e.co2)?` · ${Math.round(1e3*e.co2)} g/kWh`:"",a=`${this._hm(e.start)}–${this._hm(e.end)} · ${i}${r}`+(e.cheap?` · ${this._t("energy_plan_legend_cheap")}`:"");return j`
    `})}
    - `:G} + `:H}
    ${this._t("energy_plan_home")}
    - ${d?H` + ${_?j`
    - ${m} - ${x.map(e=>H` + ${g} + ${x.map(e=>j`
    `)}
    - `:G} + `:H}
    ${S}
    - ${r.map(e=>{const t=Ws[e.kind]||Ws.load,r=Ks[e.status]||Ks.yields,a=E[e.id]||[],l=e.label||this._t(t.label),p="fits"===e.status?`${(e.planned_kwh||0).toFixed(1)} kWh`:`${(e.planned_kwh||0).toFixed(1)}/${(e.needed_kwh||0).toFixed(1)} kWh`,_=[l,"comfort"===e.kind?this._t("energy_plan_comfort_tip"):null,a.map(e=>`${this._hm(e.start)}–${this._hm(e.end)} · ${(e.power_w/1e3).toFixed(1)} kW${null!=e.price?` · ${e.price}`:""}`).join("\n")||null,`${(e.planned_kwh||0).toFixed(1)} / ${(e.needed_kwh||0).toFixed(1)} kWh · ${this._t("energy_plan_est")} ${(e.est_cost||0).toFixed(2)} ${C}`,Hs(e,e=>this._t(e))].filter(Boolean).join("\n"),h=i?this._liveState(e,o):null,u=Hs(e,e=>this._t(e))||("fits"!==e.status?this._format("energy_plan_yield_reason",{planned:(e.planned_kwh||0).toFixed(1),needed:(e.needed_kwh||0).toFixed(1)}):null);return H` -
    + ${r.map(e=>{const t=Vl[e.kind]||Vl.load,r=Xl[e.status]||Xl.yields,a=N[e.id]||[],l=e.label||this._t(t.label),d="fits"===e.status?`${(e.planned_kwh||0).toFixed(1)} kWh`:`${(e.planned_kwh||0).toFixed(1)}/${(e.needed_kwh||0).toFixed(1)} kWh`,p=[l,"comfort"===e.kind?this._t("energy_plan_comfort_tip"):null,a.map(e=>`${this._hm(e.start)}–${this._hm(e.end)} · ${(e.power_w/1e3).toFixed(1)} kW${null!=e.price?` · ${e.price}`:""}`).join("\n")||null,`${(e.planned_kwh||0).toFixed(1)} / ${(e.needed_kwh||0).toFixed(1)} kWh · ${this._t("energy_plan_est")} ${(e.est_cost||0).toFixed(2)} ${C}`,Ws(e,e=>this._t(e))].filter(Boolean).join("\n"),h=i?this._liveState(e,n):null,u=Ws(e,e=>this._t(e))||("fits"!==e.status?this._format("energy_plan_yield_reason",{planned:(e.planned_kwh||0).toFixed(1),needed:(e.needed_kwh||0).toFixed(1)}):null);return j` +
    ${l}
    ${this._liveChip(h)} - ${i?this._covChip((s.coverage||{})[e.id]):G} + ${i?this._covChip((s.coverage||{})[e.id]):H}
    - ${d?H` + ${_?j`
    - ${m} - ${a.map(e=>{const s=(Date.parse(e.start)-n)/c*100,i=(Date.parse(e.end)-Date.parse(e.start))/c*100;return H` + ${g} + ${a.map(e=>{const s=(Date.parse(e.start)-o)/c*100,i=(Date.parse(e.end)-Date.parse(e.start))/c*100;return j`
    `})}
    - `:G} -
    + `:H} +
    - ${p} + ${d}
    - ${u?H` + ${u?j`
    ${u}
    - ${d?H`
    `:G} - `:G} + ${_?j`
    `:H} + `:H} `})}
    - ${d?H` + ${_?j`
    - ${$.has("sun")?H`${this._t("energy_plan_legend_sun")}`:G} - ${$.has("batt")?H`${this._t("energy_plan_legend_battery")}`:G} - ${$.has("grid")?H`${this._t("energy_plan_legend_grid")}`:G} + ${$.has("sun")?j`${this._t("energy_plan_legend_sun")}`:H} + ${$.has("batt")?j`${this._t("energy_plan_legend_battery")}`:H} + ${$.has("grid")?j`${this._t("energy_plan_legend_grid")}`:H} ${this._t("energy_plan_legend_cheap")}
    - `:H` + `:j`
    @@ -5531,17 +6603,17 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow
    `} - ${s.battery_fleet_partial?H` + ${s.battery_fleet_partial?j`
    ${this._t("energy_plan_fleet_partial")}
    - `:G} + `:H} - ${(s.not_scheduled||[]).length?H` + ${(s.not_scheduled||[]).length?j`
    ${this._t("energy_plan_not_scheduled")}
    - ${s.not_scheduled.map(e=>H` + ${s.not_scheduled.map(e=>j`
    @@ -5550,7 +6622,7 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow
    `)}
    - `:G} + `:H} ${this._renderArb(s.arbitrage)} @@ -5629,614 +6701,253 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow padding-top: 6px; border-top: 1px solid rgba(255, 255, 255, 0.06); } - .rev-h { - font-size: 10px; - text-transform: uppercase; - letter-spacing: 0.5px; - color: var(--secondary-text-color, #8a93a5); - margin-bottom: 2px; - } - .rev-row { - display: flex; - align-items: baseline; - gap: 6px; - font-size: 11px; - color: var(--secondary-text-color, #8a93a5); - } - .rev-row ha-icon { align-self: center; } - .rev-name { - color: var(--primary-text-color, #e1e1e1); - white-space: nowrap; - } - .rev-txt { min-width: 0; } - .chip-active { - background: rgba(141,200,146,0.18); color: #8DC892; - border-color: rgba(141,200,146,0.40); - } - .docs-link { - display: inline-flex; align-items: center; - color: var(--secondary-text-color); - opacity: 0.6; transition: opacity 0.15s; - } - .docs-link:hover { opacity: 1; color: #8353d1; } - .stamp { - margin-left: auto; font-size: 11px; - color: var(--secondary-text-color); - font-variant-numeric: tabular-nums; - } - .verdict { - display: flex; align-items: center; gap: 6px; - font-size: 13px; font-weight: 500; - color: var(--primary-text-color); - margin-bottom: 10px; - } - .cost { - margin-left: auto; font-size: 12px; font-weight: 400; - color: var(--secondary-text-color); - font-variant-numeric: tabular-nums; - } - .idle { - font-size: 13px; color: var(--secondary-text-color); - padding: 2px 0 2px; - } - .strip { - display: grid; - /* The label column grows with the card: on a panel-width - System tab a fixed 82px truncated "Sim Pool Pump" to - "Sim Pool P…" with 700px going spare. */ - grid-template-columns: minmax(82px, 16%) 1fr 92px; - align-items: center; - row-gap: 4px; column-gap: 8px; - } - /* No timeline (the recorder dropped it) — the label takes the - track's room so the demand names still read in full. */ - .strip.nostrip { grid-template-columns: 1fr auto; } - .lbl { - display: flex; align-items: center; gap: 4px; - min-width: 0; font-size: 11px; - color: var(--primary-text-color); - } - /* (#638 G4) a row wearing a live chip stacks name over chip. */ - .lbl.col { - flex-direction: column; align-items: flex-start; gap: 2px; - } - .lname { - display: flex; align-items: center; gap: 4px; min-width: 0; - max-width: 100%; - } - .live { - font-size: 8.5px; letter-spacing: 0.05em; text-transform: uppercase; - padding: 0 5px; border-radius: 7px; white-space: nowrap; - } - .live.now { - background: rgba(141,200,146,0.20); color: #8DC892; - border: 1px solid rgba(141,200,146,0.45); - } - .live.wait { - background: rgba(91,200,216,0.12); color: #5BC8D8; - border: 1px solid rgba(91,200,216,0.30); - font-variant-numeric: tabular-nums; - } - .live.done { - background: rgba(255,255,255,0.06); color: var(--secondary-text-color); - border: 1px solid rgba(255,255,255,0.12); - } - .live.react { - background: rgba(131,83,209,0.16); color: #b39ddb; - border: 1px solid rgba(131,83,209,0.35); - } - /* (#638 G4) invisible hover cells on the axis: per-slot price. */ - .slotcell { position: absolute; top: 0; bottom: 0; } - .name { - overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - } - .track { - position: relative; height: 14px; border-radius: 4px; - background: rgba(255,255,255,0.04); - overflow: hidden; - } - .track.axis { - height: 13px; background: transparent; overflow: visible; - } - .band { - position: absolute; inset: 0; pointer-events: none; - } - .cheap { - position: absolute; top: 0; bottom: 0; - background: rgba(141,200,146,0.17); - box-shadow: inset 1px 0 0 rgba(141,200,146,0.30), - inset -1px 0 0 rgba(141,200,146,0.30); - } - .seg { - position: absolute; top: 2px; bottom: 2px; border-radius: 3px; - } - .seg.batt { background: #4db6ac; } - .seg.grid { background: #488fc2; } - .seg.sun { background: #ff9800; } - .seg.run { top: 1px; bottom: 1px; } - .tick { - position: absolute; top: 0; - transform: translateX(-50%); - font-size: 9px; color: var(--secondary-text-color); - font-variant-numeric: tabular-nums; - white-space: nowrap; - } - /* The first tick sits at 0% — centring it would hang half the - label off the left edge of the track. Left-align that one. */ - .tick.first { - transform: none; - font-size: 9px; color: var(--secondary-text-color); - font-variant-numeric: tabular-nums; - white-space: nowrap; - } - .stat { - display: flex; align-items: center; gap: 4px; - justify-content: flex-end; - font-size: 11px; color: var(--secondary-text-color); - font-variant-numeric: tabular-nums; - white-space: nowrap; - } - .note { - font-size: 10px; opacity: 0.6; line-height: 1.2; - color: var(--secondary-text-color); - } - .legend { - display: flex; flex-wrap: wrap; gap: 10px; - margin-top: 9px; font-size: 10px; - color: var(--secondary-text-color); - } - .key { display: inline-flex; align-items: center; gap: 4px; } - .sw { - width: 10px; height: 8px; border-radius: 2px; display: inline-block; - } - .sw.batt { background: #4db6ac; } - .sw.grid { background: #488fc2; } - .sw.sun { background: #ff9800; } - .sw.cheapkey { background: rgba(141,200,146,0.35); } - .warn { - display: flex; align-items: center; gap: 5px; - margin-top: 8px; font-size: 11px; color: #ffb74d; + .rev-h { + font-size: 10px; + text-transform: uppercase; + letter-spacing: 0.5px; + color: var(--secondary-text-color, #8a93a5); + margin-bottom: 2px; } - .head { flex-wrap: wrap; row-gap: 4px; } - .why { - margin-top: 4px; font-size: 10px; opacity: 0.55; - color: var(--secondary-text-color); - overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + .rev-row { + display: flex; + align-items: baseline; + gap: 6px; + font-size: 11px; + color: var(--secondary-text-color, #8a93a5); } - .asks { - display: flex; flex-wrap: wrap; gap: 6px 10px; - align-items: center; margin-top: 8px; font-size: 11px; + .rev-row ha-icon { align-self: center; } + .rev-name { + color: var(--primary-text-color, #e1e1e1); + white-space: nowrap; } - .asks-lbl { - font-weight: 600; color: var(--secondary-text-color, #8a93a5); + .rev-txt { min-width: 0; } + .chip-active { + background: rgba(141,200,146,0.18); color: #8DC892; + border-color: rgba(141,200,146,0.40); } - .ask { display: inline-flex; align-items: center; gap: 3px; } - .prov { opacity: 0.85; } - .vtoggle { - display: inline-flex; border: 1px solid rgba(255,255,255,0.14); - border-radius: 8px; overflow: hidden; margin-left: 2px; + .docs-link { + display: inline-flex; align-items: center; + color: var(--secondary-text-color); + opacity: 0.6; transition: opacity 0.15s; } - .vbtn { - background: none; border: none; cursor: pointer; - font-size: 10px; padding: 2px 7px; - color: var(--secondary-text-color, #8a93a5); + .docs-link:hover { opacity: 1; color: #8353d1; } + .stamp { + margin-left: auto; font-size: 11px; + color: var(--secondary-text-color); + font-variant-numeric: tabular-nums; } - .vbtn-on { - background: rgba(131,83,209,0.25); color: var(--primary-text-color, #fff); + .verdict { + display: flex; align-items: center; gap: 6px; + font-size: 13px; font-weight: 500; + color: var(--primary-text-color); + margin-bottom: 10px; } - .tmw-track { position: relative; height: 18px; margin-top: 8px; } - .tmw-sun { - position: absolute; top: 0; height: 100%; - background: rgba(255,152,0,0.20); border-radius: 3px; + .cost { + margin-left: auto; font-size: 12px; font-weight: 400; + color: var(--secondary-text-color); + font-variant-numeric: tabular-nums; } - .tmw-cheap { - position: absolute; top: 30%; height: 40%; - background: rgba(141,200,146,0.5); border-radius: 3px; + .idle { + font-size: 13px; color: var(--secondary-text-color); + padding: 2px 0 2px; } - .tmw-sun-key { background: rgba(255,152,0,0.45); } - .nightband { - position: absolute; top: 0; height: 100%; - background: rgba(131,83,209,0.10); + .strip { + display: grid; + /* The label column grows with the card: on a panel-width + System tab a fixed 82px truncated "Sim Pool Pump" to + "Sim Pool P…" with 700px going spare. */ + grid-template-columns: minmax(82px, 16%) 1fr 92px; + align-items: center; + row-gap: 4px; column-gap: 8px; } - .nowline { - position: absolute; top: -2px; height: calc(100% + 4px); - width: 2px; background: #5BC8D8; opacity: 0.8; + /* No timeline (the recorder dropped it) — the label takes the + track's room so the demand names still read in full. */ + .strip.nostrip { grid-template-columns: 1fr auto; } + .lbl { + display: flex; align-items: center; gap: 4px; + min-width: 0; font-size: 11px; + color: var(--primary-text-color); } - .arb { - display: flex; align-items: center; gap: 5px; - margin-top: 8px; font-size: 11px; - color: var(--secondary-text-color, #8a93a5); + /* (#638 G4) a row wearing a live chip stacks name over chip. */ + .lbl.col { + flex-direction: column; align-items: flex-start; gap: 2px; } - .arb-lbl { font-weight: 600; white-space: nowrap; } - .arb-txt { - overflow: hidden; text-overflow: ellipsis; - white-space: nowrap; flex: 1; min-width: 0; + .lname { + display: flex; align-items: center; gap: 4px; min-width: 0; + max-width: 100%; } - .foot { - margin-top: 8px; font-size: 10px; opacity: 0.5; - color: var(--secondary-text-color); + .live { + font-size: 8.5px; letter-spacing: 0.05em; text-transform: uppercase; + padding: 0 5px; border-radius: 7px; white-space: nowrap; } - `}}ke("sem-energy-plan-card",Vs,{type:"sem-energy-plan-card",name:"SEM Energy Plan",description:"The joint plan for the energy day — when each demand runs and where the battery hands over",documentationURL:"https://github.com/traktore-org/sem-community/blob/develop/docs/DASHBOARD_GUIDE.md#sem-energy-plan-card"}),ke("sem-overnight-plan-card",class extends Vs{});const Ys={sunny:"☀️",mixed:"⛅",grey:"☁️",car:"🚗",cheap:"💸",negative:"💸",gap:"⚠️"},Zs=["sunny","mixed","grey","car","cheap","negative","gap"];function Xs(e){return!!e&&(!1!==e.selectable&&!(e.tags||[]).includes("gap"))}const Qs=["last30","last90","last365"],Js={last30:30,last90:90,last365:365},ei=e=>(e||[]).filter(Xs).map(e=>String(e.date)).sort();function ti(e){return e.length?{from:e[0],to:e[e.length-1]}:null}function si(e,t){const s=ei(e),i=Math.max(0,Math.min(s.length,Math.round(Number(t)||0)));return i?ti(s.slice(s.length-i)):null}function ii(e,t){const s=new Date(Date.UTC(e.getFullYear(),e.getMonth(),e.getDate()));return s.setUTCDate(s.getUTCDate()-t),s.toISOString().slice(0,10)}function ri(e,t,s=new Date){const i=Js[t];if(!i)return null;const r=ii(s,i),a=ii(s,0);return ti(ei(e).filter(e=>e>=r&&ee>=t.from&&e<=t.to).length:0}function oi(e){const t=/^(\d{4})-(\d{2})-(\d{2})/.exec(String(e||""));return t?`${t[3]}.${t[2]}`:String(e||"")}function ni(e,t="en"){const s=/^(\d{4})-(\d{2})-(\d{2})/.exec(String(e||""));if(!s)return String(e||"");const i=new Date(Date.UTC(+s[1],+s[2]-1,+s[3]));let r="";try{r=new Intl.DateTimeFormat(t,{weekday:"short",timeZone:"UTC"}).format(i)}catch(e){r=new Intl.DateTimeFormat("en",{weekday:"short",timeZone:"UTC"}).format(i)}return`${r} ${s[3]}.${s[2]}`}function li(e){const t=Number(e);if(null==e||!Number.isFinite(t))return"—";const s=Math.round(100*t)/100;return 0===s?"0.00":(s<0?"−":"")+Math.abs(s).toFixed(2)}function ci(e){const t=Number(e);if(null==e||!Number.isFinite(t))return{text:"—",cls:"same"};const s=Math.round(100*t)/100;return 0===s?{text:"0.00",cls:"same"}:s<0?{text:"−"+Math.abs(s).toFixed(2),cls:"cheaper"}:{text:"+"+s.toFixed(2),cls:"dearer"}}function di(e,t){const s=Number(t),i=Number(e);if(!Number.isFinite(s)||!Number.isFinite(i)||0===s)return"";const r=Math.round(i/Math.abs(s)*1e3)/10;return 0===r?"0.0 %":(r<0?"−":"+")+Math.abs(r).toFixed(1)+" %"}const pi=["simulated","delta"];function _i(e){return pi.includes(e)}const hi=["sunny","mixed","grey","car","cheap"];function ui(e,t){const s=e||[];return t?s.filter(e=>(e.tags||[]).includes(t)):s}function gi(e){const t=e||{},s=t.net_delta??null;return{days:Number(t.days)||0,actual:li(t.actual),simulated:li(t.simulated),delta:ci(t.delta),net:null===s?null:ci(s),pct:di(t.delta,t.actual),kwhOverLimit:Number(t.kwh_over_limit)||0,indicativeDays:Number(t.indicative_days)||0}}const mi=e=>{const t=Number(e);return null!=e&&Number.isFinite(t)?t.toFixed(1):"—"};function fi(e){const t=e||{};return{capacity_kwh:t.capacity_kwh??"",reserve_pct:t.reserve_pct??"",peak_limit_kw:t.peak_limit_kw??"",import_mode:"recorded",import_rate:t.import_rate??"",export_mode:"recorded",export_rate:t.export_rate??"",battery_mode:t.battery_mode??"auto",ev_mode:"recorded",max_charge_w:t.max_charge_w??"",max_discharge_w:t.max_discharge_w??"",pv_mode:t.system_size_kwp?"kwp":"factor",pv_extra_kwp:"",pv_factor:1,battery_to_ev:!1!==t.battery_to_ev}}const vi=["auto","self_consumption","force_charge","force_discharge","off"],yi=["recorded","solar_only","solar_plus_battery","solar_plus_cheap","min_plus_solar","always_max","off"],bi=e=>{if(""===e||null==e)return null;const t=Number(e);return Number.isFinite(t)?t:null};function xi(e,t,s){const i=t||{},r=s||{};if("import_rate"===e)return"flat"===i.import_mode;if("export_rate"===e)return"flat"===i.export_mode;if("battery_mode"===e)return(i.battery_mode||"auto")!==(r.battery_mode||"auto");if("ev_mode"===e)return"recorded"!==(i.ev_mode||"recorded");if("battery_to_ev"===e)return!1!==i.battery_to_ev!=(!1!==r.battery_to_ev);if("pv"===e)return"kwp"===i.pv_mode?0!==(bi(i.pv_extra_kwp)||0):1!==(bi(i.pv_factor)??1);return bi(r[e])!==bi(i[e])}function $i(e){const t=e||{},s={capacity_kwh:bi(t.capacity_kwh),reserve_pct:bi(t.reserve_pct)},i=bi(t.peak_limit_kw);s.peak_limit_kw=i&&i>0?i:null;for(const e of["import","export"]){const i=bi(t[`${e}_rate`]);s[`${e}_rate`]="flat"===t[`${e}_mode`]&&null!==i?i:"recorded"}t.battery_mode&&(s.battery_mode=t.battery_mode),s.ev_mode=t.ev_mode||"recorded";for(const e of["max_charge_w","max_discharge_w"]){const i=bi(t[e]);null!==i&&(s[e]=i)}if("kwp"===t.pv_mode){const e=bi(t.pv_extra_kwp);e&&(s.pv_extra_kwp=e)}else{const e=bi(t.pv_factor);null!==e&&1!==e&&(s.pv_factor=e)}return!1===t.battery_to_ev&&(s.battery_to_ev=!1),s}const wi=["plus_battery","plus_pv","cheap_chargers","no_battery_ev"];function ki(e,t,s){if(s?.running)return!1;if(!e?.from||!e?.to)return!1;const i=$i(t);return null!==i.capacity_kwh&&i.capacity_kwh>0&&null!==i.reserve_pct}function Si(e,t){let s=String(e||"");for(const[e,i]of Object.entries(t||{}))s=s.split(`{${e}}`).join(String(i));return s}const Ci="#8353d1",zi={sunny:"sim_tag_sunny",mixed:"sim_tag_mixed",grey:"sim_tag_grey",car:"sim_tag_car",cheap:"sim_tag_cheap",negative:"sim_tag_cheap",gap:"sim_tag_gap"},Mi={last30:"sim_pick_last30",last90:"sim_pick_last90",last365:"sim_pick_last365"},Di=e=>`battery_mode_${e}`,Ei=e=>"recorded"===e?"sim_as_recorded_opt":`charge_mode_${e}`,Ni={sunny:"sim_tile_sunny",mixed:"sim_tile_mixed",grey:"sim_tile_grey",car:"sim_tile_car",cheap:"sim_tile_cheap"};ke("sem-simulation-card",class extends Le{static get properties(){return{...super.properties,_days:{state:!0},_defaults:{state:!0},_form:{state:!0},_period:{state:!0},_status:{state:!0},_result:{state:!0},_expanded:{state:!0},_filter:{state:!0},_openDay:{state:!0},_error:{state:!0}}}constructor(){super(),this._days=null,this._defaults=null,this._form=null,this._period=null,this._status=null,this._result=null,this._expanded=!1,this._filter=null,this._openDay=null,this._error=null,this._pollTimer=null,this._loading=!1}set hass(e){const t=!this._hass;super.hass=e,t&&this.isConnected&&this._load()}get hass(){return this._hass}connectedCallback(){super.connectedCallback(),this._hass&&this._load()}disconnectedCallback(){super.disconnectedCallback(),this._stopPolling()}async _ws(e,t={}){return this._hass.callWS({type:`solar_energy_management/simulation/${e}`,...t})}async _load(){if(!this._loading&&this._hass?.callWS){this._loading=!0;try{const[e,t,s]=await Promise.all([this._ws("days").catch(()=>null),this._ws("defaults").catch(()=>null),this._ws("status").catch(()=>null)]);e&&(this._days=e),t&&(this._defaults=t,this._form||(this._form=fi(t))),this._onStatus(s),s?.running||this._result||await this._loadResult()}finally{this._loading=!1}}}_onStatus(e){e&&(this._status=e,e.running?this._schedulePoll():(this._stopPolling(),"failed"===e.status&&(this._error={kind:"failed",text:e.error||""})))}_schedulePoll(){!this._pollTimer&&this.isConnected&&(this._pollTimer=setTimeout(()=>this._poll(),2e3))}_stopPolling(){this._pollTimer&&clearTimeout(this._pollTimer),this._pollTimer=null}async _poll(){if(this._pollTimer=null,!this.isConnected||!this._hass?.callWS)return;let e=null;try{e=await this._ws("status")}catch(t){e=null}if(!this.isConnected)return;if(!e)return void this._schedulePoll();const t=this._status?.running;this._onStatus(e),t&&!e.running&&await this._loadResult(e.run_id)}async _loadResult(e){try{const t=await this._ws("result",e?{run_id:e}:{});this._result=t||null,this._expanded=!1,this._filter=null,this._openDay=null}catch(e){}}async _start(){if(!ki(this._period,this._form,this._status))return;const e=(t=this._period,s=this._form,{from:t.from,to:t.to,params:$i(s)});var t,s;this._error=null;try{await this._ws("start",e)}catch(e){const t=e?.code||"";return this._error="busy"===t?{kind:"busy"}:{kind:"invalid"===t?"invalid":"failed",text:e?.message||""},void("busy"===t&&this._poll())}this._status={running:!0,status:"running",done:0,total:ai(this._days?.days,this._period),started_at:(new Date).toISOString()},this._schedulePoll()}async _cancel(){try{await this._ws("cancel")}catch(e){}this._stopPolling(),this._poll()}async _download(){try{const e=await this._ws("xlsx",this._result?.run_id?{run_id:this._result.run_id}:{});e?.base64&&(gs(e.filename||"sem-simulation.xlsx",e.base64,"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"),"download"===this._error?.kind&&(this._error=null))}catch(e){console.warn("SEM: simulation download failed",e),this._error={kind:"download"}}}_setForm(e){this._form={...this._form||fi(this._defaults),...e}}_pickPeriod(e){this._period=ri(this._days?.days||[],e)}_pickMonth(e){e&&(this._period=function(e,t){return ti(ei(e).filter(e=>e.slice(0,7)===t))}(this._days?.days||[],e))}_pickRecent(e){this._period=si(this._days?.days||[],e)}_preset(e){let t=null;if("plus_pv"===e){const e=!!this._defaults?.system_size_kwp,s=globalThis.prompt?.(this._t(e?"sim_preset_pv_ask_kwp":"sim_preset_pv_ask_factor"));if(null==s||""===String(s).trim())return;if(t=Number(String(s).replace(",",".")),!Number.isFinite(t))return}this._form=function(e,t,s,i=null){const r={...e||fi(t)},a=t||{},o=(e,t)=>{const s=bi(e)??bi(t);return null===s?e:Math.round(2*s*10)/10};switch(s){case"plus_battery":r.capacity_kwh=o(r.capacity_kwh,a.capacity_kwh),r.max_charge_w=o(r.max_charge_w,a.max_charge_w),r.max_discharge_w=o(r.max_discharge_w,a.max_discharge_w);break;case"plus_pv":{const e=bi(i);if(null===e)break;a.system_size_kwp?(r.pv_mode="kwp",r.pv_extra_kwp=e):(r.pv_mode="factor",r.pv_factor=e);break}case"cheap_chargers":r.ev_mode="solar_plus_cheap";break;case"no_battery_ev":r.battery_to_ev=!1}return r}(this._form,this._defaults,e,t)}_setFilter(e){this._filter=this._filter===e?null:e,this._expanded=!1,this._openDay=null}_toggleDay(e){this._openDay=this._openDay===e?null:e}_currency(){return this._result?.currency||this._defaults?.currency||fe(this._hass)||""}_est(){return H`${this._t("sim_est")}`}_tagTitle(e){return(e||[]).map(e=>zi[e]?this._t(zi[e]):e).join(" · ")}_renderHeader(){if(!1===this._config?.header)return G;const e=this._days||{};return H` -
    -
    - - -
    -
    -
    ${this._t("sim_title")}
    -
    ${this._t("sim_sub")}
    -
    -
    -
    ${e.stored??"—"}
    ${this._t("sim_days_stored")}
    -
    ${ai(e.days,this._period)}
    ${this._t("sim_in_period")}
    -
    ${e.gaps??"—"}
    ${this._t("sim_data_gaps")}
    -
    -
    `}_renderPeriod(){const e=this._days?.days||[],t=function(e,t,s=new Date){if(!t)return null;for(const i of Qs){const r=ri(e,i,s);if(r&&r.from===t.from&&r.to===t.to)return i}return null}(e,this._period),s=this._period,i=ai(e,s),r=function(e,t){return t?(e||[]).filter(e=>!Xs(e)&&String(e.date)>=t.from&&String(e.date)<=t.to).length:0}(e,s);return H` -
    -
    ${this._t("sim_period")}
    - ${e.length?H` -
    - ${Qs.map(e=>H`this._pickPeriod(e)}>${this._t(Mi[e])}`)} - -
    - ${this._renderRecentSlider(e)} -
    ${s?Si(this._t("sim_period_span"),{from:ni(s.from,this._hass?.language||"en"),to:ni(s.to,this._hass?.language||"en"),n:i}):this._t("sim_pick_period_hint")}
    - ${r>0?H`
    ${Si(this._t("sim_period_gaps"),{n:r})}
    `:G} -
    ${this._t("sim_period_note")}
    `:H` -
    ${this._days?this._t("sim_no_days"):this._t("sim_loading")}
    `} -
    `}_renderRecentSlider(e){const t=function(e){return ei(e).length}(e);if(t<2)return G;const s=function(e,t){if(!t)return 0;const s=ei(e),i=s.filter(e=>e>=t.from&&e<=t.to);if(!i.length||i[i.length-1]!==s[s.length-1])return null;const r=si(e,i.length);return r&&r.from===t.from&&r.to===t.to?i.length:null}(e,this._period),i=s??0,r=i?si(e,i):null,a=r?`${oi(r.from)} – ${oi(r.to)}`:"";return H` -
    - - this._pickRecent(e.target.value)}> - ${a} -
    `}_renderNumberRow(e,t,s,i,r=null){const a=this._form||{},o=xi(e,a,this._defaults);return H` -
    - ${this._t(t)} - ${i}→ - this._setForm({[e]:t.target.value})}>${s} -
    `}_renderRateRow(e,t){const s=this._form||{},i=s[`${e}_mode`]||"recorded",r=`${e}_rate`,a=xi(r,s,this._defaults);return H` -
    - ${this._t(t)} - ${this._t("sim_as_recorded")}→ - - ${"flat"===i?H`this._setForm({[r]:e.target.value})}>${this._currency()}/kWh`:G} -
    `}_renderModeRow(e,t,s,i,r){const a=this._form||{},o=a[e]||s[0],n=xi(e,a,this._defaults);return H` -
    - ${this._t(t)} - ${this._t(i(r))}→ - -
    `}_renderPvRow(){const e=this._form||{},t=this._defaults||{},s="kwp"===e.pv_mode&&t.system_size_kwp,i=xi("pv",e,t);return H` -
    - ${this._t("sim_pv")} - ${t.system_size_kwp?`${t.system_size_kwp} kWp`:this._t("sim_as_recorded")}→ - ${t.system_size_kwp?H``:G} - this._setForm(s?{pv_extra_kwp:e.target.value}:{pv_factor:e.target.value})}>${s?"kWp":"×"} -
    `}_renderEvAssistRow(){const e=this._form||{},t=this._defaults||{},s=!1===e.battery_to_ev?"no":"yes",i=xi("battery_to_ev",e,t);return H` -
    - ${this._t("sim_battery_to_ev")} - ${this._t(!1===t.battery_to_ev?"sim_no":"sim_yes")}→ - -
    `}_renderWhatIf(){const e=this._defaults;if(!e)return G;const t=e.peak_limit_kw?`${e.peak_limit_kw} kW`:this._t("sim_none"),s=e=>e?`${e} W`:"—";return H` -
    -
    ${this._t("sim_what_if")}
    -
    - ${this._t("sim_presets")} - ${wi.map(e=>H`this._preset(e)}>${this._t((e=>`sim_preset_${e}`)(e))}`)} -
    -
    - ${this._renderNumberRow("capacity_kwh","sim_battery_size","kWh",`${e.capacity_kwh??"—"} kWh`,150)} - ${this._renderNumberRow("max_charge_w","sim_charge_power","W",s(e.max_charge_w))} - ${this._renderNumberRow("max_discharge_w","sim_discharge_power","W",s(e.max_discharge_w))} - ${this._renderNumberRow("reserve_pct","sim_battery_reserve","%",`${e.reserve_pct??"—"} %`)} - ${this._renderNumberRow("peak_limit_kw","sim_peak_limit","kW",t)} - ${this._renderPvRow()} - ${this._renderRateRow("import","sim_import_price")} - ${this._renderRateRow("export","sim_export_value")} - ${this._renderModeRow("battery_mode","sim_battery_mode",vi,Di,e.battery_mode||"auto")} - ${this._renderModeRow("ev_mode","sim_ev_mode",yi,Ei,"recorded")} - ${this._renderEvAssistRow()} -
    -
    `}_renderRun(){const e=this._status||{},t=!!e.running,s=ki(this._period,this._form,e),i=t?function(e,t=Date.now()){const s=Number(e?.done)||0,i=Number(e?.total)||0,r=Date.parse(e?.started_at||"");if(!s||!i||s>=i||!Number.isFinite(r))return null;const a=(t-r)/s;return a>0?Math.max(1,Math.ceil(a*(i-s)/6e4)):null}(e):null,r=ai(this._days?.days,this._period);let a="";t&&(a=Si(this._t("sim_running"),{x:Math.min((e.done||0)+1,e.total||0),y:e.total||0}),null!==i&&(a+=" · "+Si(this._t("sim_minutes_left"),{n:i})));const o=this._error;let n="";return"busy"===o?.kind?n=this._t("sim_busy"):"failed"!==o?.kind&&"invalid"!==o?.kind||(n=Si(this._t("sim_failed"),{error:o.text||""})),H` -
    -
    - -
    - ${t?H` -
    - ${a}`:H` - ${r?Si(this._t("sim_n_days"),{n:r}):this._t("sim_pick_period_hint")}`} -
    - ${t?H``:G} -
    - ${n?H`
    ${n}
    `:G} -
    `}_renderDetail(e){const t=function(e){const t=e||{},s=e=>({actual:mi(t[`actual_${e}_kwh`]),simulated:mi(t[`simulated_${e}_kwh`])});return{lines:[{key:"sim_grid_in",...s("grid_import")},{key:"sim_grid_out",...s("grid_export")},{key:"sim_batt_in",...s("battery_charge")},{key:"sim_batt_out",...s("battery_discharge")},{key:"sim_pack_end",actual:mi(t.actual_end_kwh),simulated:mi(t.simulated_end_kwh)}],net:void 0===t.net_delta||null===t.net_delta?null:ci(t.net_delta),story:[...t.story||[]]}}(e);return H` -
    - ${t.lines.map(e=>H`
    ${this._t(e.key)} - ${e.actual} → ${e.simulated} kWh ${this._est()}
    `)} - ${t.net?H`
    ${Si(this._t("sim_day_net"),{d:""})} - ${t.net.text}
    `:G} - ${t.story.length?H`
    ${t.story.join(" · ")}
    `:G} -
    `}_renderRow(e,t){const s=ci(e.delta),i=(e.reasons||[]).join(" · "),r=this._openDay===e.date;return H` -
    this._toggleDay(e.date)}> - ${ni(e.date,t)}${e.indicative?H` ⚠️`:G} - ${function(e){const t=new Set(e||[]),s=[];for(const e of Zs)t.has(e)&&!s.includes(Ys[e])&&s.push(Ys[e]);return s.join("")}(e.tags)} - ${li(e.actual_cost)}${_i("actual")?this._est():G} - ${li(e.simulated_cost)} ${_i("simulated")?this._est():G} - ${s.text} ${_i("delta")?this._est():G} -
    - ${r?this._renderDetail(e):G}`}_renderMonths(e){const{rows:t,helps:s}=function(e){const t=(e||[]).map(e=>({month:String(e.month||""),days:Number(e.days)||0,delta:ci(e.net_delta??e.delta),value:Number(e.net_delta??e.delta)||0})),s=Math.max(0,...t.map(e=>Math.abs(e.value)));for(const e of t)e.share=s>0?Math.abs(e.value)/s:0;const i=t.filter(e=>Math.round(100*e.value)<0).map(e=>e.month);return{rows:t,helps:i}}(e.months);return t.length<2?G:H` -
    ${this._t("sim_months")}
    - ${t.map(e=>H` -
    ${e.month} - - ${e.delta.text}
    `)} -
    ${s.length?Si(this._t("sim_months_helps"),{months:s.join(", ")}):this._t("sim_months_none")} ${this._est()}
    `}_renderEnergy(e){const t=function(e){const t=e?.actual||{},s=e?.simulated||{};if(!e?.actual&&!e?.simulated)return[];const i=e=>null==e?"—":`${Number(e).toFixed(1)} %`,r=e=>null==e?"—":Number(e).toFixed(1);return[{key:"sim_grid_in",actual:mi(t.grid_import_kwh),simulated:mi(s.grid_import_kwh),unit:"kWh"},{key:"sim_grid_out",actual:mi(t.grid_export_kwh),simulated:mi(s.grid_export_kwh),unit:"kWh"},{key:"sim_self_use",actual:i(t.self_consumption_pct),simulated:i(s.self_consumption_pct),unit:""},{key:"sim_cycles",actual:r(t.cycles),simulated:r(s.cycles),unit:""}]}(e.energy);return t.length?H` -
    ${this._t("sim_energy")} - ${this._t("sim_actual_word")} → ${this._t("sim_simulated_word")}
    - ${t.map(e=>H`
    ${this._t(e.key)} - ${e.actual} → ${e.simulated}${e.unit?" "+e.unit:""} ${this._est()}
    `)}`:G}_renderResults(){const e=this._result;if(!e)return G;const t=this._hass?.language||"en",s=this._currency(),i=this._filter,{rows:r,more:a}=function(e,t=!1,s=8){const i=[...e||[]].sort((e,t)=>String(t.date).localeCompare(String(e.date)));return t||i.length<=s?{rows:i,more:0}:{rows:i.slice(0,s),more:i.length-s}}(ui(e.days,i),this._expanded),o=function(e,t,s=null){const i=Number(t?.days)||0,r=[];for(const t of hi){const a=e?.[t]||{},o=Number(a.days)||0;if("cheap"===t&&0===o)continue;const n="sunny"===t||"mixed"===t||"grey"===t;r.push({tag:t,glyph:Ys[t],days:o,of:n?null:i,delta:o?ci(a.delta):null,actual:o?Number(a.actual)||0:null,simulated:o?Number(a.simulated)||0:null,on:s===t})}return r}(e.per_tag,e.total,i),n=gi(function(e,t){return t?e?.per_tag?.[t]||{days:0}:e?.total||{}}(e,i)),l=gi(e.total),c=e.skipped||[],d=function(e){const t=e?.calibration,s=e=>null==e?null:Number(e);if(!t||void 0===e?.calibrated)return{ok:!0,caution:!1,pct:null,days:null,leftOut:0,carDays:0,carPct:null,repriced:!1};const i=s(t.error_pct),r=s(t.max_pct);return{ok:!!e.calibrated,caution:!!e.calibrated&&null!==i&&null!==r&&i>r,pct:i,days:s(t.days),leftOut:Number(t.left_out)||0,carDays:Number(t.car_days)||0,carPct:s(t.car_error_pct),repriced:!!e.repriced}}(e);if(!d.ok)return H` -
    -
    ${this._t("sim_results")} · ${Si(this._t("sim_last_run"),{n:l.days})}
    -
    ${Si(this._t("sim_cant_simulate"),{pct:null===d.pct?"?":d.pct})}
    - ${this._renderCalibLines(d)} -
    ${this._t("sim_limits")}
    -
    `;const p=e.stored_energy||null,_=e.ev||null,h=Number(_?.not_delivered_kwh)||0;return H` -
    -
    ${this._t("sim_results")} · ${Si(this._t("sim_last_run"),{n:l.days})} - this._download()}> - ${this._t("sessions_xlsx")}
    - ${e.cancelled?H`
    ${this._t("sim_cancelled")}
    `:G} - ${"download"===this._error?.kind?H`
    ${this._t("sessions_download_failed")}
    `:G} - ${l.net?H`
    - ${this._t("sim_net_total")} - ${l.net.text} ${s} ${this._est()} - ${this._t("sim_col_diff")} ${l.delta.text} ${s}
    `:G} -
    -
    this._setFilter(null)}> -
    ${l.delta.text}
    -
    ${this._t("sim_filter_all")} · ${Si(this._t("sim_n_days"),{n:l.days})}
    -
    - ${o.map(e=>H` -
    e.days&&this._setFilter(e.tag)}> -
    ${e.delta?e.delta.text:"—"}
    -
    ${e.glyph} ${this._t(Ni[e.tag])} · ${e.days?null===e.of?Si(this._t("sim_n_days"),{n:e.days}):Si(this._t("sim_n_of"),{n:e.days,m:e.of}):this._t("sim_none_in_period")}
    -
    `)} -
    -
    ${this._t("sim_filter_hint")}
    -
    ${this._t("sim_col_day")}${this._t("sim_col_tags")} - ${this._t("sim_col_actual")}${d.repriced?"*":""} ${s} - ${this._t("sim_col_simulated")} ${s} - ${this._t("sim_col_diff")}
    - ${r.map(e=>this._renderRow(e,t))} - ${a>0?H`
    {this._expanded=!0}}>${Si(this._t("sim_more_days"),{n:a})} ▾
    `:G} - ${this._expanded&&ui(e.days,i).length>8?H`
    {this._expanded=!1}}>${this._t("sim_fewer")} ▴
    `:G} -
    - ${i?H`${Ni[i]?this._t(Ni[i]):i} · `:G}${Si(this._t("sim_n_days"),{n:n.days})} · ${this._t("sim_actual_word")} ${n.actual} → ${this._t("sim_simulated_word")} ${n.simulated} ${this._est()} ${s} - ${n.delta.text} ${s}${n.pct?" · "+n.pct:""} -
    - ${i&&n.net?H`
    ${Si(this._t("sim_day_net"),{d:n.net.text+" "+s})} ${this._est()}
    `:G} - ${p&&Math.abs(Number(p.kwh)||0)>=.05?H`
    ${Si(this._t("sim_stored_sum"),{kwh:(Number(p.kwh)>=0?"+":"")+Number(p.kwh).toFixed(1),value:li(p.value)+" "+s})} ${this._est()}
    `:G} - ${this._renderMonths(e)} - ${this._renderEnergy(e)} - ${_&&((Number(_.moved_kwh)||0)>0||(Number(_.not_delivered_kwh)||0)>0)?H`
    ${Si(this._t("sim_ev_moved"),{moved:Number(_.moved_kwh).toFixed(1),short:Number(_.not_delivered_kwh).toFixed(1)})} ${this._est()}
    `:G} - ${h>0&&l.net?H`
    ${Si(this._t("sim_net_ev_note"),{kwh:h.toFixed(1),value:li(_.not_delivered_value)+" "+s})} ${this._est()}
    `:G} - ${d.repriced?H`
    ${this._t("sim_repriced_note")}
    `:G} - ${this._renderCalibLines(d)} - ${l.kwhOverLimit>0?H`
    ${Si(this._t("sim_over_limit"),{n:l.kwhOverLimit.toFixed(1)})} ${this._est()}
    `:G} - ${c.length?H`
    ${Si(this._t("sim_skipped"),{n:c.length})}
    `:G} -
    ${this._est()} ${this._t("sim_est_legend")}
    -
    ${this._t("sim_limits")}
    -
    `}_renderCalibLines(e){return H` - ${e.caution?H`
    ${Si(this._t("sim_caution"),{pct:e.pct})}
    `:G} - ${null===e.days?G:H`
    ${Si(this._t("sim_days_checked"),{n:e.days,m:e.leftOut})}
    `} - ${e.carDays>0&&null!==e.carPct?H`
    ${Si(this._t("sim_car_error"),{pct:e.carPct,n:e.carDays})}
    `:G}`}render(){return H` -
    - ${this._renderHeader()} - ${this._renderPeriod()} - ${this._renderWhatIf()} - ${this._renderRun()} - ${this._renderResults()} -
    `}getCardSize(){return 12}static get styles(){return[Ie,a` - :host { display: block; } - .sim { color: var(--primary-text-color); } - ha-card { display: block; margin: 8px 0; overflow: hidden; } - .wrap { - padding: 14px 20px; position: relative; - background: radial-gradient(ellipse 70% 60% at 50% 25%, #8353d10f 0%, transparent 100%), - radial-gradient(circle at 2px 2px, rgba(128,128,128,0.05) 0.7px, transparent 0.7px); - background-size: 100% 100%, 50px 50px; + .live.now { + background: rgba(141,200,146,0.20); color: #8DC892; + border: 1px solid rgba(141,200,146,0.45); } - .header-wrap { - display: flex; align-items: center; gap: 16px; padding: 16px 20px; - background: radial-gradient(ellipse 80% 70% at 20% 50%, #8353d10d 0%, transparent 70%), - radial-gradient(circle at 2px 2px, rgba(128,128,128,0.05) 0.7px, transparent 0.7px); - background-size: 100% 100%, 50px 50px; + .live.wait { + background: rgba(91,200,216,0.12); color: #5BC8D8; + border: 1px solid rgba(91,200,216,0.30); + font-variant-numeric: tabular-nums; } - .icon-ring { position: relative; flex-shrink: 0; width: 64px; height: 64px; } - .icon-ring svg { width: 100%; height: 100%; } - .icon-ring ha-icon { - position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); - color: #8353d1; --mdc-icon-size: 26px; opacity: .85; + .live.done { + background: rgba(255,255,255,0.06); color: var(--secondary-text-color); + border: 1px solid rgba(255,255,255,0.12); } - .title-area { flex: 1 1 auto; min-width: 140px; } - .tab-title { font-size: 20px; font-weight: 700; color: #8353d1; letter-spacing: .5px; text-shadow: 0 0 12px #8353d140; } - .tab-subtitle { font-size: 12px; color: var(--secondary-text-color); margin-top: 2px; font-weight: 500; } - .stats { display: flex; gap: 16px; flex-shrink: 0; } - .stat { text-align: center; min-width: 50px; } - .stat-value { font-size: 15px; font-weight: 700; font-variant-numeric: tabular-nums; } - .stat-label { font-size: 11px; color: var(--secondary-text-color); font-weight: 500; margin-top: 1px; } - .ct-title { - font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: var(--secondary-text-color); - display: flex; align-items: center; gap: 5px; margin-bottom: 6px; + .live.react { + background: rgba(131,83,209,0.16); color: #b39ddb; + border: 1px solid rgba(131,83,209,0.35); } - .ct-title ha-icon { --mdc-icon-size: 14px; color: #8353d1; } - .ct-title .right { - margin-left: auto; text-transform: none; letter-spacing: 0; font-size: 12px; font-weight: 600; - color: #5BC8D8; display: flex; gap: 4px; align-items: center; cursor: pointer; + /* (#638 G4) invisible hover cells on the axis: per-slot price. */ + .slotcell { position: absolute; top: 0; bottom: 0; } + .name { + overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } - .ct-title .right ha-icon { color: #5BC8D8; } - .chip { - display: inline-flex; align-items: center; gap: 4px; background: var(--secondary-background-color); - border: 1px solid var(--divider-color); border-radius: 12px; padding: 3px 10px; font-size: 11px; - font-weight: 500; font-variant-numeric: tabular-nums; cursor: pointer; user-select: none; + .track { + position: relative; height: 14px; border-radius: 4px; + background: rgba(255,255,255,0.04); + overflow: hidden; } - .chip.on { background: #8353d122; border-color: #8353d1; color: #8353d1; font-weight: 700; } - .picks { display: flex; gap: 6px; flex-wrap: wrap; margin: 2px 0 10px; } - .recent { display: grid; grid-template-columns: 1fr auto; gap: 2px 8px; align-items: center; margin: 0 0 10px; font-size: 12px; } - .recent label { font-weight: 600; } - .recent small { color: var(--secondary-text-color); font-variant-numeric: tabular-nums; } - .recent input { grid-column: 1 / -1; width: 100%; min-height: 36px; accent-color: #8353d1; } - .span { font-size: 13px; font-weight: 600; font-variant-numeric: tabular-nums; } - .lbl-small { font-size: 11px; color: var(--secondary-text-color); align-self: center; } - .legend { font-size: 11px; color: var(--secondary-text-color); margin-top: 8px; } - .note { font-size: 12px; color: var(--secondary-text-color); margin: 2px 0 6px; } - .err { font-size: 12px; color: var(--error-color, #db4437); margin-top: 8px; } - .warn { font-size: 12px; color: var(--warning-color, #ff9800); margin-top: 8px; } - .group { - margin-top: 8px; padding: 6px 12px; border: 1px solid var(--divider-color); - border-radius: 10px; background: rgba(255,255,255,0.025); + .track.axis { + height: 13px; background: transparent; overflow: visible; } - .ct-row { display: flex; align-items: center; min-height: 34px; gap: 8px; border-bottom: 1px solid var(--divider-color); } - .ct-row:last-child { border-bottom: 0; } - .ct-row .lbl { flex: 1 1 auto; font-size: 12px; font-weight: 500; } - .cur { font-size: 11px; color: var(--secondary-text-color); font-variant-numeric: tabular-nums; } - .arrow { color: var(--secondary-text-color); font-size: 12px; } - .inp { - display: inline-flex; align-items: center; gap: 4px; background: var(--secondary-background-color); - border: 1px solid var(--divider-color); border-radius: 8px; padding: 3px 8px; font-size: 12px; - font-weight: 700; font-variant-numeric: tabular-nums; min-width: 56px; justify-content: flex-end; + .band { + position: absolute; inset: 0; pointer-events: none; } - .inp input { - width: 48px; border: 0; background: transparent; color: inherit; font: inherit; text-align: right; - padding: 0; -moz-appearance: textfield; + .cheap { + position: absolute; top: 0; bottom: 0; + background: rgba(141,200,146,0.17); + box-shadow: inset 1px 0 0 rgba(141,200,146,0.30), + inset -1px 0 0 rgba(141,200,146,0.30); } - .inp input::-webkit-outer-spin-button, .inp input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } - .inp input:focus { outline: none; } - .inp.changed, .sel-ctl.changed { border-color: #8353d1; color: #8353d1; } - .inp small { font-weight: 500; color: var(--secondary-text-color); } - .sel-ctl { - appearance: none; -webkit-appearance: none; background-color: var(--secondary-background-color); - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23bbbbbb' d='M7 10l5 5 5-5z'/%3E%3C/svg%3E"); - background-repeat: no-repeat; background-position: right 6px center; background-size: 14px; - color: var(--primary-text-color); border: 1px solid var(--divider-color); border-radius: 8px; - padding: 4px 24px 4px 10px; font-size: 12px; font-weight: 700; font-family: inherit; + .seg { + position: absolute; top: 2px; bottom: 2px; border-radius: 3px; } - .runrow { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; } - .btn { - display: inline-flex; align-items: center; gap: 6px; border-radius: 10px; padding: 8px 16px; - font-size: 13px; font-weight: 700; border: 1px solid #8353d1; background: #8353d1; color: #fff; - cursor: pointer; font-family: inherit; + .seg.batt { background: #4db6ac; } + .seg.grid { background: #488fc2; } + .seg.sun { background: #ff9800; } + .seg.run { top: 1px; bottom: 1px; } + .tick { + position: absolute; top: 0; + transform: translateX(-50%); + font-size: 9px; color: var(--secondary-text-color); + font-variant-numeric: tabular-nums; + white-space: nowrap; } - .btn[disabled] { opacity: .45; cursor: not-allowed; } - .btn.sec { background: transparent; color: var(--primary-text-color); border-color: var(--divider-color); } - .btn ha-icon { --mdc-icon-size: 16px; } - .prog { flex: 1 1 220px; display: flex; flex-direction: column; gap: 4px; } - .bar { height: 6px; border-radius: 3px; background: var(--secondary-background-color); overflow: hidden; } - .bar i { display: block; height: 100%; background: linear-gradient(90deg, #8353d1, #5BC8D8); transition: width .4s; } - .prog small { font-size: 11px; color: var(--secondary-text-color); } - .trow, .thead { - display: grid; grid-template-columns: 1.5fr 1fr .9fr 1.1fr 1.1fr; gap: 6px; align-items: center; - min-height: 28px; border-bottom: 1px solid var(--divider-color); font-size: 12px; + /* The first tick sits at 0% — centring it would hang half the + label off the left edge of the track. Left-align that one. */ + .tick.first { + transform: none; + font-size: 9px; color: var(--secondary-text-color); font-variant-numeric: tabular-nums; + white-space: nowrap; } - .thead { font-size: 11px; font-weight: 500; color: var(--secondary-text-color); min-height: 22px; } - .num { text-align: right; } - .warn { font-size: 11px; cursor: help; } - .delta { font-weight: 700; } - .cheaper { color: #8DC892; } - .dearer { color: #ff9800; } - .same, .none { color: var(--secondary-text-color); } - .est { - font-style: normal; font-size: 9px; font-weight: 600; color: #8353d1; border: 1px solid #8353d166; - border-radius: 6px; padding: 0 4px; vertical-align: middle; + .stat { + display: flex; align-items: center; gap: 4px; + justify-content: flex-end; + font-size: 11px; color: var(--secondary-text-color); + font-variant-numeric: tabular-nums; + white-space: nowrap; } - .tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 8px; margin-top: 10px; } - .tile { border: 1px solid var(--divider-color); border-radius: 10px; padding: 8px 10px; background: rgba(255,255,255,0.025); } - .tile .v { font-size: 16px; font-weight: 700; font-variant-numeric: tabular-nums; } - .tile .l { font-size: 11px; color: var(--secondary-text-color); font-weight: 500; } - .total { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; flex-wrap: wrap; margin-top: 10px; font-size: 12px; } - .total b { font-size: 14px; } - .more { font-size: 11px; color: var(--secondary-text-color); padding: 6px 0 0; cursor: pointer; } - .tap { cursor: pointer; } - .tile.on { border-color: #8353d1; background: #8353d11c; } - .tile.empty { opacity: .55; cursor: default; } - .trow.open { background: #8353d10f; } - .detail { padding: 6px 8px 8px; border-bottom: 1px solid var(--divider-color); background: #8353d108; } - .dl { display: flex; justify-content: space-between; gap: 8px; font-size: 12px; min-height: 22px; align-items: center; font-variant-numeric: tabular-nums; } - .why { font-size: 11px; color: var(--secondary-text-color); margin-top: 4px; } - .headline { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin: 4px 0 8px; font-size: 13px; } - .headline b { font-size: 18px; } - .headline small { color: var(--secondary-text-color); } - .ct-title.sub { margin-top: 14px; } - .ct-title .right.muted { color: var(--secondary-text-color); cursor: default; font-weight: 500; } - .mrow { display: grid; grid-template-columns: 80px 1fr auto; gap: 8px; align-items: center; font-size: 12px; min-height: 22px; font-variant-numeric: tabular-nums; } - .mbar { height: 6px; border-radius: 3px; background: var(--secondary-background-color); overflow: hidden; } - .mbar i { display: block; height: 100%; } - .mbar i.cheaper { background: #8DC892; } - .mbar i.dearer { background: #ff9800; } - .mbar i.same { background: var(--divider-color); } - @media (max-width: 600px) { - .header-wrap { flex-wrap: wrap; } - .trow, .thead { grid-template-columns: 1.3fr .8fr .9fr 1.1fr 1.1fr; font-size: 11px; } - .tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); } - .mrow { grid-template-columns: 64px 1fr auto; } + .note { + font-size: 10px; opacity: 0.6; line-height: 1.2; + color: var(--secondary-text-color); + } + .legend { + display: flex; flex-wrap: wrap; gap: 10px; + margin-top: 9px; font-size: 10px; + color: var(--secondary-text-color); + } + .key { display: inline-flex; align-items: center; gap: 4px; } + .sw { + width: 10px; height: 8px; border-radius: 2px; display: inline-block; + } + .sw.batt { background: #4db6ac; } + .sw.grid { background: #488fc2; } + .sw.sun { background: #ff9800; } + .sw.cheapkey { background: rgba(141,200,146,0.35); } + .warn { + display: flex; align-items: center; gap: 5px; + margin-top: 8px; font-size: 11px; color: #ffb74d; + } + .head { flex-wrap: wrap; row-gap: 4px; } + .why { + margin-top: 4px; font-size: 10px; opacity: 0.55; + color: var(--secondary-text-color); + overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + } + .asks { + display: flex; flex-wrap: wrap; gap: 6px 10px; + align-items: center; margin-top: 8px; font-size: 11px; + } + .asks-lbl { + font-weight: 600; color: var(--secondary-text-color, #8a93a5); + } + .ask { display: inline-flex; align-items: center; gap: 3px; } + .prov { opacity: 0.85; } + .vtoggle { + display: inline-flex; border: 1px solid rgba(255,255,255,0.14); + border-radius: 8px; overflow: hidden; margin-left: 2px; + } + .vbtn { + background: none; border: none; cursor: pointer; + font-size: 10px; padding: 2px 7px; + color: var(--secondary-text-color, #8a93a5); + } + .vbtn-on { + background: rgba(131,83,209,0.25); color: var(--primary-text-color, #fff); + } + .tmw-track { position: relative; height: 18px; margin-top: 8px; } + .tmw-sun { + position: absolute; top: 0; height: 100%; + background: rgba(255,152,0,0.20); border-radius: 3px; + } + .tmw-cheap { + position: absolute; top: 30%; height: 40%; + background: rgba(141,200,146,0.5); border-radius: 3px; + } + .tmw-sun-key { background: rgba(255,152,0,0.45); } + .nightband { + position: absolute; top: 0; height: 100%; + background: rgba(131,83,209,0.10); + } + .nowline { + position: absolute; top: -2px; height: calc(100% + 4px); + width: 2px; background: #5BC8D8; opacity: 0.8; } - `]}},{type:"sem-simulation-card",name:"SEM Simulation",description:"Replay a recorded period with another battery, solar size, peak limit or price; estimated cost per day type and month",documentationURL:"https://github.com/traktore-org/sem-community/blob/develop/docs/DASHBOARD_GUIDE.md#sem-simulation-card"});const Fi={charger:{icon:"mdi:ev-station",color:"#8DC892"},battery:{icon:"mdi:home-battery",color:"#4db6ac"},load:{icon:"mdi:power-plug",color:"#5BC8D8"},export:{icon:"mdi:transmission-tower-export",color:"#8353d1"}},Ti=e=>`${e?.device_kind||""}:${e?.device_id||""}`;function Ai(e,t,s=!1){const i=e?.name;if(i&&i!==e.device_id)return i;const r=t(`why_kind_${e?.device_kind}`);return r&&r!==`why_kind_${e?.device_kind}`?s?`${r} ${e.device_id}`:r:i||e?.device_id||""}function Bi(e,t){if(!e)return"";const[s,...i]=String(e).split(" "),r=i.join(" ");if("charge"===s&&r)return r;const a=`why_verb_${s}`,o=t(a),n=o&&o!==a?o:s.replace(/_/g," ");return r?`${n} ${r}`:n}const Ri=e=>"number"==typeof e&&Number.isFinite(e)?e:null;function Oi(e){const t=Ri(e);return null===t?"":Math.abs(t)>=1e3?`${(t/1e3).toFixed(1)} kW`:`${Math.round(t)} W`}const Ii=(e,t)=>{try{return e.toLocaleDateString("en-CA",{timeZone:t||void 0})}catch(t){return e.toDateString()}};function Li(e,t=new Date,s){const i=new Date(e);return!Number.isNaN(i.getTime())&&Ii(i,s)===Ii(t,s)}ke("sem-why-card",class extends Le{static get properties(){return{...super.properties,_records:{state:!0},_chip:{state:!0},_allSettings:{state:!0},_foldOpen:{state:!0},_error:{state:!0}}}constructor(){super(),this._records=null,this._chip="",this._allSettings=!1,this._foldOpen=null,this._error=!1,this._loading=!1,this._stamp=null,this._loadedStamp=void 0,this._failedAt=0,this._boundViewMode=e=>this._applyViewMode(e&&e.detail&&"boolean"==typeof e.detail.all?e.detail.all:Ms())}setConfig(e){super.setConfig(e||{}),this._prefix=this._config.entity_prefix||"sensor.sem_"}connectedCallback(){super.connectedCallback(),this._applyViewMode(Ms()),window.addEventListener(zs,this._boundViewMode)}disconnectedCallback(){super.disconnectedCallback(),window.removeEventListener(zs,this._boundViewMode)}set hass(e){super.hass=e,this._stamp=function(e,t="sensor.sem_"){const s=[e?.[`${t}charging_state`]?.attributes?.per_charger_last_decision,e?.[`${t}battery_status`]?.attributes?.per_battery_last_decision,e?.[`${t}load_management_status`]?.attributes?.per_device_last_decision];let i="",r=0;for(const e of s)for(const t of Object.values(e||{})){r+=1;const e=String(t?.time||"");e>i&&(i=e)}return`${r}|${i}`}(e?.states,this._prefix||"sensor.sem_"),this._maybeLoad()}get hass(){return this._hass}_maybeLoad(e=Date.now()){!this._loading&&this._hass?.callWS&&this._stamp!==this._loadedStamp&&(this._failedAt&&e-this._failedAt<3e4||this._load(this._stamp))}async _load(e=this._stamp){if(this._loading||!this._hass?.callWS)return;this._loading=!0;let t=!1;try{const s=await this._hass.callWS({type:"solar_energy_management/decision_log"});this._records=Array.isArray(s?.records)?s.records:[],this._error=!1,this._loadedStamp=e,this._failedAt=0,t=!0}catch(e){null===this._records&&(this._records=[]),this._error=!0,this._failedAt=Date.now()}finally{this._loading=!1}this.requestUpdate(),t&&this._stamp!==this._loadedStamp&&this._maybeLoad()}_applyViewMode(e){e!==this._allSettings&&(this._allSettings=e,this._foldOpen=null)}_setView(e){Ds(e),this._applyViewMode(e)}_isFoldOpen(){return"boolean"==typeof this._foldOpen?this._foldOpen:this._allSettings}_toggleFold(){this._foldOpen=!this._isFoldOpen()}_setChip(e){this._chip=e}_fill(e,t){return(this._t(e)||"").split("{n}").join(String(t))}_renderRow(e,t){const s=e=>this._t(e),i=Fi[e.device_kind]||Fi.load,r=this._hass?.config?.time_zone,a=this._hass?.language||"en",o=this._hass?.config?.currency||"",n=function(e,t,s=""){const i=e||{},r=[],a=e=>`${e.toFixed(2)}${s?` ${s}`:""}/kWh`;return null!==Ri(i.solar_w)&&r.push({icon:"mdi:white-balance-sunny",color:"#ff9800",text:Oi(i.solar_w)}),null!==Ri(i.soc)&&r.push({icon:"mdi:battery-medium",color:"#4db6ac",text:`${Math.round(i.soc)} %`}),null!==Ri(i.import_price)&&r.push({text:`${t("why_in_import")} ${a(i.import_price)}`}),null!==Ri(i.export_rate)&&r.push({text:`${t("why_in_export")} ${a(i.export_rate)}`}),null!==Ri(i.surplus_w)&&r.push({text:`${t("why_in_surplus")} ${Oi(i.surplus_w)}`}),null!==Ri(i.budget_w)&&r.push({text:`${t("why_in_budget")} ${Oi(i.budget_w)}`}),null!==Ri(i.target_soc)?r.push({text:`${t("why_in_target")} ${Math.round(i.target_soc)} %`}):null!==Ri(i.target_kwh)&&i.target_kwh>0&&r.push({text:`${t("why_in_target")} ${i.target_kwh.toFixed(1)} kWh`}),r}(e.inputs,s,o),l=function(e,t){const s=e?.reason_code;if(!s)return null;const i=`why_reason_${s}`,r=t(i);if(!r||r===i)return null;const a=e.params||{};let o=!1;const n=String(r).replace(/\{(\w+)\}/g,(e,t)=>{const s=a[t];return null==s||""===s?(o=!0,e):String(s)});return o?null:n}(e,s),c=l||e.text;return H` -
    -
    ${function(e,t="en",s=new Date,i){const r=new Date(e);if(Number.isNaN(r.getTime()))return"—";const a={hour:"2-digit",minute:"2-digit",hour12:!1,timeZone:i||void 0};let o,n;try{o=r.toLocaleTimeString(t,a)}catch(e){o=r.toLocaleTimeString([],a)}if(Li(e,s,i))return o;try{n=r.toLocaleDateString(t,{weekday:"short",timeZone:i||void 0})}catch(e){n=""}return n?`${n} ${o}`:o}(e.time,a,new Date,r)}
    + .arb { + display: flex; align-items: center; gap: 5px; + margin-top: 8px; font-size: 11px; + color: var(--secondary-text-color, #8a93a5); + } + .arb-lbl { font-weight: 600; white-space: nowrap; } + .arb-txt { + overflow: hidden; text-overflow: ellipsis; + white-space: nowrap; flex: 1; min-width: 0; + } + .foot { + margin-top: 8px; font-size: 10px; opacity: 0.5; + color: var(--secondary-text-color); + } + `}}ke("sem-energy-plan-card",Jl,{type:"sem-energy-plan-card",name:"SEM Energy Plan",description:"The joint plan for the energy day — when each demand runs and where the battery hands over",documentationURL:"https://github.com/traktore-org/sem-community/blob/develop/docs/DASHBOARD_GUIDE.md#sem-energy-plan-card"}),ke("sem-overnight-plan-card",class extends Jl{});ke("sem-why-card",class extends Re{static get properties(){return{...super.properties,_records:{state:!0},_chip:{state:!0},_allSettings:{state:!0},_foldOpen:{state:!0},_error:{state:!0}}}constructor(){super(),this._records=null,this._chip="",this._allSettings=!1,this._foldOpen=null,this._error=!1,this._loading=!1,this._stamp=null,this._loadedStamp=void 0,this._failedAt=0,this._boundViewMode=e=>this._applyViewMode(e&&e.detail&&"boolean"==typeof e.detail.all?e.detail.all:Fs())}setConfig(e){super.setConfig(e||{}),this._prefix=this._config.entity_prefix||"sensor.sem_"}connectedCallback(){super.connectedCallback(),this._applyViewMode(Fs()),window.addEventListener(Es,this._boundViewMode)}disconnectedCallback(){super.disconnectedCallback(),window.removeEventListener(Es,this._boundViewMode)}set hass(e){super.hass=e,this._stamp=function(e,t="sensor.sem_"){const s=[e?.[`${t}charging_state`]?.attributes?.per_charger_last_decision,e?.[`${t}battery_status`]?.attributes?.per_battery_last_decision,e?.[`${t}load_management_status`]?.attributes?.per_device_last_decision];let i="",r=0;for(const e of s)for(const t of Object.values(e||{})){r+=1;const e=String(t?.time||"");e>i&&(i=e)}return`${r}|${i}`}(e?.states,this._prefix||"sensor.sem_"),this._maybeLoad()}get hass(){return this._hass}_maybeLoad(e=Date.now()){!this._loading&&this._hass?.callWS&&this._stamp!==this._loadedStamp&&(this._failedAt&&e-this._failedAt<3e4||this._load(this._stamp))}async _load(e=this._stamp){if(this._loading||!this._hass?.callWS)return;this._loading=!0;let t=!1;try{const s=await this._hass.callWS({type:"solar_energy_management/decision_log"});this._records=Array.isArray(s?.records)?s.records:[],this._error=!1,this._loadedStamp=e,this._failedAt=0,t=!0}catch(e){null===this._records&&(this._records=[]),this._error=!0,this._failedAt=Date.now()}finally{this._loading=!1}this.requestUpdate(),t&&this._stamp!==this._loadedStamp&&this._maybeLoad()}_applyViewMode(e){e!==this._allSettings&&(this._allSettings=e,this._foldOpen=null)}_setView(e){As(e),this._applyViewMode(e)}_isFoldOpen(){return"boolean"==typeof this._foldOpen?this._foldOpen:this._allSettings}_toggleFold(){this._foldOpen=!this._isFoldOpen()}_setChip(e){this._chip=e}_fill(e,t){return(this._t(e)||"").split("{n}").join(String(t))}_renderRow(e,t){const s=e=>this._t(e),i=ai[e.device_kind]||ai.load,r=this._hass?.config?.time_zone,a=this._hass?.language||"en",n=this._hass?.config?.currency||"",o=function(e,t,s=""){const i=e||{},r=[],a=e=>`${e.toFixed(2)}${s?` ${s}`:""}/kWh`;return null!==hi(i.solar_w)&&r.push({icon:"mdi:white-balance-sunny",color:"#ff9800",text:ui(i.solar_w)}),null!==hi(i.soc)&&r.push({icon:"mdi:battery-medium",color:"#4db6ac",text:`${Math.round(i.soc)} %`}),null!==hi(i.import_price)&&r.push({text:`${t("why_in_import")} ${a(i.import_price)}`}),null!==hi(i.export_rate)&&r.push({text:`${t("why_in_export")} ${a(i.export_rate)}`}),null!==hi(i.surplus_w)&&r.push({text:`${t("why_in_surplus")} ${ui(i.surplus_w)}`}),null!==hi(i.budget_w)&&r.push({text:`${t("why_in_budget")} ${ui(i.budget_w)}`}),null!==hi(i.target_soc)?r.push({text:`${t("why_in_target")} ${Math.round(i.target_soc)} %`}):null!==hi(i.target_kwh)&&i.target_kwh>0&&r.push({text:`${t("why_in_target")} ${i.target_kwh.toFixed(1)} kWh`}),r}(e.inputs,s,n),l=pi(e,s),c=l||e.text;return j` +
    +
    ${fi(e.time,a,new Date,r)}
    - ${Ai(e,s,t[e.device_kind]>1)} - ${function(e,t){const s=Bi(e?.new,t);return e?.old?`${Bi(e.old,t)} → ${s}`:s}(e,s)} - ${e.observer?H`${s("why_observer")}`:G} + ${oi(e,s,t[e.device_kind]>1)} + ${di(e,s)} + ${e.observer?j`${s("why_observer")}`:H}
    - ${c?H`
    ${c}
    `:G} - ${n.length?H` + ${c?j`
    ${c}
    `:H} + ${o.length?j`
    - ${n.map(e=>H`${e.icon?H``:G}${e.text}`)} -
    `:G} + ${o.map(e=>j`${e.icon?j``:H}${e.text}`)} +
    `:H}
    -
    `}render(){if(!this._hass||!this._config)return G;const e=e=>this._t(e),t=this._records||[],s=function(e,t){const s=new Map;for(const t of e||[]){const e=Ti(t);s.has(e)||s.set(e,t)}const i={};for(const e of s.values())i[e.device_kind]=(i[e.device_kind]||0)+1;return[...s.entries()].map(([e,s])=>({key:e,kind:s.device_kind,name:Ai(s,t,i[s.device_kind]>1)}))}(t,e),i=s.some(e=>e.key===this._chip)?this._chip:"",r={};for(const e of s)r[e.kind]=(r[e.kind]||0)+1;const a=function(e,t){const s=e||[];return t?s.filter(e=>Ti(e)===t):s}(t,i),{shown:o,earlier:n}=function(e,t=4){const s=e||[];return{shown:s.slice(0,t),earlier:s.slice(t)}}(a),l=this._isFoldOpen(),c=this._hass?.config?.time_zone;return H` +
    `}render(){if(!this._hass||!this._config)return H;const e=e=>this._t(e),t=this._records||[],s=li(t,e),i=s.some(e=>e.key===this._chip)?this._chip:"",r={};for(const e of s)r[e.kind]=(r[e.kind]||0)+1;const a=ci(t,i),{shown:n,earlier:o}=function(e,t=4){const s=e||[];return{shown:s.slice(0,t),earlier:s.slice(t)}}(a),l=this._isFoldOpen(),c=this._hass?.config?.time_zone;return j`
    @@ -6250,36 +6961,36 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow @click=${()=>this._setView(!0)}>${e("ev_view_all")}
    - ${s.length?H` + ${s.length?j`
    - ${s.map(e=>H` + ${s.map(e=>j` `)} -
    `:G} - ${this._error?H`
    ${e("why_load_error")}
    `:G} - ${null===this._records||a.length||this._error?G:H`
    ${e("why_empty")}
    `} +
    `:H} + ${this._error?j`
    ${e("why_load_error")}
    `:H} + ${null===this._records||a.length||this._error?H:j`
    ${e("why_empty")}
    `}
    - ${o.map(e=>this._renderRow(e,r))} + ${n.map(e=>this._renderRow(e,r))}
    - ${n.length?H` + ${o.length?j`
    - ${l?H`
    - ${n.map(e=>this._renderRow(e,r))} -
    `:G} -
    `:G} + ${l?j`
    + ${o.map(e=>this._renderRow(e,r))} +
    `:H} +
    `:H}
    - `}static get styles(){return[Ie,a` + `}static get styles(){return[Le,a` :host { display: block; } ha-card { display: block; } .wrap { padding: 14px 16px 6px; } @@ -6352,7 +7063,7 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow border-top: 1px solid rgba(255,255,255,0.06); } .fold-btn:hover { color: var(--primary-text-color, #e0e0e0); } - `]}},{type:"sem-why-card",name:"SEM Why?",description:"Why SEM changed a device: each command change, its reason and the numbers behind it",documentationURL:"https://github.com/traktore-org/sem-community/blob/develop/docs/DASHBOARD_GUIDE.md#sem-why-card"});const Pi=2,Ui=e=>(...t)=>({_$litDirective$:e,values:t});let qi=class{constructor(e){}get _$AU(){return this._$AM._$AU}_$AT(e,t,s){this._$Ct=e,this._$AM=t,this._$Ci=s}_$AS(e,t){return this.update(e,t)}update(e,t){return this.render(...t)}};class Hi extends qi{constructor(e){if(super(e),this.it=G,e.type!==Pi)throw Error(this.constructor.directiveName+"() can only be used in child bindings")}render(e){if(e===G||null==e)return this._t=void 0,this.it=e;if(e===j)return e;if("string"!=typeof e)throw Error(this.constructor.directiveName+"() called with a non-string value");if(e===this.it)return this._t;this.it=e;const t=[e];return t.raw=t,this._t={_$litType$:this.constructor.resultType,strings:t,values:[]}}}Hi.directiveName="unsafeHTML",Hi.resultType=1;let Wi=class extends Hi{};Wi.directiveName="unsafeSVG",Wi.resultType=2;const ji=Ui(Wi),Gi={solar:{nameKey:"solar",color:"#ff9800"},grid:{nameKey:"grid",color_import:"#488fc2",color_export:"#8353d1"},battery:{nameKey:"battery",color:"#4db6ac"},home:{nameKey:"home",color:"#5BC8D8"},ev:{nameKey:"ev_charger",color:"#8DC892"},inverter:{nameKey:"inverter",color:"#96CAEE"}};ke("sem-flow-card",class extends Le{constructor(){super(),this._lastKey="",this._animFrames={},this._currentValues={},this._compact=!1,this._visible=!0,this._deviceConfigSig="",this._devicePositions=[],this._updateTimer=null,this._resizeObserver=null,this._intersectionObserver=null,this._resizeTimeout=null,this._mode="prefix",this._prefix="sensor.sem_",this._entities=null}setConfig(e){if(this._config=e,e.entity_prefix)this._mode="prefix",this._prefix=e.entity_prefix;else{if(!e.entities)throw new Error('sem-flow-card requires either "entities" or "entity_prefix" config');this._mode="entities",this._entities=e.entities}this._showLabels=!1!==e.show_labels,this._showValues=!1!==e.show_values,this._showGlow=!1!==e.show_glow,this._showInverter=!1!==e.show_inverter,this._showEv=!1!==e.show_ev,this._showBattery=!1!==e.show_battery,this.requestUpdate()}set hass(e){this._hass,this._hass=e;const t=e?.language;if(t!==this._lang)return this._lang=t,void this.requestUpdate();const s=be(e,this._prefix);if(s!==this._lastPVKey)return this._lastPVKey=s,void this.requestUpdate();this._visible&&(clearTimeout(this._updateTimer),this._updateTimer=setTimeout(()=>this._updateFlowsImperative(),100))}get hass(){return this._hass}firstUpdated(){this.isConnected&&this._observe()}connectedCallback(){super.connectedCallback(),this.hasUpdated&&this._observe()}_observe(){this._unobserve(),this._resizeTimeout=null,this._resizeObserver=new ResizeObserver(e=>{this._resizeTimeout&&clearTimeout(this._resizeTimeout),this._resizeTimeout=setTimeout(()=>{for(const t of e){const e=t.contentRect.width<400;e!==this._compact&&(this._compact=e,this._lastKey="",this._deviceConfigSig="",this.requestUpdate())}},100)}),this._resizeObserver.observe(this),this._intersectionObserver=new IntersectionObserver(e=>{this._visible=e[0].isIntersecting;const t=this.renderRoot.querySelector("svg");t&&(t.style.animationPlayState=this._visible?"running":"paused"),this._visible&&this._hass&&this._updateFlowsImperative()},{threshold:.01}),this._intersectionObserver.observe(this),this._onVisibility=()=>{"visible"===document.visibilityState&&this._hass&&(this._visible=!0,this._updateFlowsImperative())},document.addEventListener("visibilitychange",this._onVisibility)}_unobserve(){this._resizeObserver&&(this._resizeObserver.disconnect(),this._resizeObserver=null),this._intersectionObserver&&(this._intersectionObserver.disconnect(),this._intersectionObserver=null),clearTimeout(this._resizeTimeout),this._onVisibility&&(document.removeEventListener("visibilitychange",this._onVisibility),this._onVisibility=null)}disconnectedCallback(){super.disconnectedCallback(),this._unobserve(),clearTimeout(this._updateTimer);for(const e of Object.keys(this._animFrames))cancelAnimationFrame(this._animFrames[e]);this._animFrames={}}updated(){this._setupClickHandlers(),this._updateFlowsImperative()}static get styles(){return a` + `]}},{type:"sem-why-card",name:"SEM Why?",description:"Why SEM changed a device: each command change, its reason and the numbers behind it",documentationURL:"https://github.com/traktore-org/sem-community/blob/develop/docs/DASHBOARD_GUIDE.md#sem-why-card"});class Ql extends Dr{constructor(e){if(super(e),this.it=H,e.type!==Mr)throw Error(this.constructor.directiveName+"() can only be used in child bindings")}render(e){if(e===H||null==e)return this._t=void 0,this.it=e;if(e===K)return e;if("string"!=typeof e)throw Error(this.constructor.directiveName+"() called with a non-string value");if(e===this.it)return this._t;this.it=e;const t=[e];return t.raw=t,this._t={_$litType$:this.constructor.resultType,strings:t,values:[]}}}Ql.directiveName="unsafeHTML",Ql.resultType=1;class ec extends Ql{}ec.directiveName="unsafeSVG",ec.resultType=2;const tc=zr(ec),sc={solar:{nameKey:"solar",color:"#ff9800"},grid:{nameKey:"grid",color_import:"#488fc2",color_export:"#8353d1"},battery:{nameKey:"battery",color:"#4db6ac"},home:{nameKey:"home",color:"#5BC8D8"},ev:{nameKey:"ev_charger",color:"#8DC892"},inverter:{nameKey:"inverter",color:"#96CAEE"}};ke("sem-flow-card",class extends Re{constructor(){super(),this._lastKey="",this._animFrames={},this._currentValues={},this._compact=!1,this._visible=!0,this._deviceConfigSig="",this._devicePositions=[],this._updateTimer=null,this._resizeObserver=null,this._intersectionObserver=null,this._resizeTimeout=null,this._mode="prefix",this._prefix="sensor.sem_",this._entities=null}setConfig(e){if(this._config=e,e.entity_prefix)this._mode="prefix",this._prefix=e.entity_prefix;else{if(!e.entities)throw new Error('sem-flow-card requires either "entities" or "entity_prefix" config');this._mode="entities",this._entities=e.entities}this._showLabels=!1!==e.show_labels,this._showValues=!1!==e.show_values,this._showGlow=!1!==e.show_glow,this._showInverter=!1!==e.show_inverter,this._showEv=!1!==e.show_ev,this._showBattery=!1!==e.show_battery,this.requestUpdate()}set hass(e){this._hass,this._hass=e;const t=e?.language;if(t!==this._lang)return this._lang=t,void this.requestUpdate();const s=ve(e,this._prefix);if(s!==this._lastPVKey)return this._lastPVKey=s,void this.requestUpdate();this._visible&&(clearTimeout(this._updateTimer),this._updateTimer=setTimeout(()=>this._updateFlowsImperative(),100))}get hass(){return this._hass}firstUpdated(){this.isConnected&&this._observe()}connectedCallback(){super.connectedCallback(),this.hasUpdated&&this._observe()}_observe(){this._unobserve(),this._resizeTimeout=null,this._resizeObserver=new ResizeObserver(e=>{this._resizeTimeout&&clearTimeout(this._resizeTimeout),this._resizeTimeout=setTimeout(()=>{for(const t of e){const e=t.contentRect.width<400;e!==this._compact&&(this._compact=e,this._lastKey="",this._deviceConfigSig="",this.requestUpdate())}},100)}),this._resizeObserver.observe(this),this._intersectionObserver=new IntersectionObserver(e=>{this._visible=e[0].isIntersecting;const t=this.renderRoot.querySelector("svg");t&&(t.style.animationPlayState=this._visible?"running":"paused"),this._visible&&this._hass&&this._updateFlowsImperative()},{threshold:.01}),this._intersectionObserver.observe(this),this._onVisibility=()=>{"visible"===document.visibilityState&&this._hass&&(this._visible=!0,this._updateFlowsImperative())},document.addEventListener("visibilitychange",this._onVisibility)}_unobserve(){this._resizeObserver&&(this._resizeObserver.disconnect(),this._resizeObserver=null),this._intersectionObserver&&(this._intersectionObserver.disconnect(),this._intersectionObserver=null),clearTimeout(this._resizeTimeout),this._onVisibility&&(document.removeEventListener("visibilitychange",this._onVisibility),this._onVisibility=null)}disconnectedCallback(){super.disconnectedCallback(),this._unobserve(),clearTimeout(this._updateTimer);for(const e of Object.keys(this._animFrames))cancelAnimationFrame(this._animFrames[e]);this._animFrames={}}updated(){this._setupClickHandlers(),this._updateFlowsImperative()}static get styles(){return a` :host { display: block; } ha-card { overflow: hidden; padding: 0; @@ -6370,12 +7081,12 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow .clickable-node:hover { opacity: 0.85; } .device-clickable { cursor: pointer; } .device-clickable:hover { opacity: 0.85; } - `}render(){if(!this._config)return G;const e=this._getLayout(),t=e.solar,s=e.inverter,i=e.battery,r=e.grid,a=e.home,o=e.ev,n=(2*Math.PI*e.socR).toFixed(1),l=(2*Math.PI*e.autarkyR).toFixed(1),c=e.font.label,d=e.font.value,p=e.font.sub,_=e.font.homeVal,h="'Segoe UI','Roboto',sans-serif",u=this._getNodeColor("grid_import"),g=this._getNodeColor("grid_export"),m=this._getNodeColor("solar"),f=this._getNodeColor("battery"),v=this._getNodeColor("home"),y=this._getNodeColor("ev"),b=Gi.inverter.color,x=this._hasNode("solar"),$=this._hasNode("battery"),w=this._hasNode("grid"),k=this._hasNode("ev"),S=this._showInverter&&this._hasNode("inverter"),C=xe(this._hass,this._prefix);return H` + `}render(){if(!this._config)return H;const e=this._getLayout(),t=e.solar,s=e.inverter,i=e.battery,r=e.grid,a=e.home,n=e.ev,o=(2*Math.PI*e.socR).toFixed(1),l=(2*Math.PI*e.autarkyR).toFixed(1),c=e.font.label,_=e.font.value,d=e.font.sub,p=e.font.homeVal,h="'Segoe UI','Roboto',sans-serif",u=this._getNodeColor("grid_import"),m=this._getNodeColor("grid_export"),g=this._getNodeColor("solar"),f=this._getNodeColor("battery"),y=this._getNodeColor("home"),b=this._getNodeColor("ev"),v=sc.inverter.color,x=this._hasNode("solar"),$=this._hasNode("battery"),w=this._hasNode("grid"),k=this._hasNode("ev"),S=this._showInverter&&this._hasNode("inverter"),C=xe(this._hass,this._prefix);return j` - ${C.length>=2?H` + ${C.length>=2?j`
    - ${C.map(e=>H` + ${C.map(e=>j`
    `)}
    - `:G} + `:H} @@ -6395,57 +7106,57 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow - ${this._svgRaw(this._glowFilter("glowSolar",m,8))} + ${this._svgRaw(this._glowFilter("glowSolar",g,8))} ${this._svgRaw(this._glowFilter("glowBattery",f,8))} ${this._svgRaw(this._glowFilter("glowGridImport",u,8))} - ${this._svgRaw(this._glowFilter("glowGridExport",g,8))} - ${this._svgRaw(this._glowFilter("glowHome",v,10))} - ${this._svgRaw(this._glowFilter("glowEV",y,8))} - ${this._svgRaw(this._glowFilter("glowInverter",b,6))} + ${this._svgRaw(this._glowFilter("glowGridExport",m,8))} + ${this._svgRaw(this._glowFilter("glowHome",y,10))} + ${this._svgRaw(this._glowFilter("glowEV",b,8))} + ${this._svgRaw(this._glowFilter("glowInverter",v,6))} - ${this._svgRaw(x?this._track(e.paths.solar,m):"")} - ${this._svgRaw(x||S?this._track(e.paths.home,v):"")} + ${this._svgRaw(x?this._track(e.paths.solar,g):"")} + ${this._svgRaw(x||S?this._track(e.paths.home,y):"")} ${this._svgRaw($?this._track(e.paths.battery,f):"")} ${this._svgRaw(w?``:"")} - ${this._svgRaw(k?this._track(e.paths.ev,y):"")} + ${this._svgRaw(k?this._track(e.paths.ev,b):"")} - ${this._svgRaw(x?``:"")} + ${this._svgRaw(x?``:"")} ${this._svgRaw($?``:"")} ${this._svgRaw(w?``:"")} - ${this._svgRaw(x||S?``:"")} - ${this._svgRaw(k?``:"")} + ${this._svgRaw(x||S?``:"")} + ${this._svgRaw(k?``:"")} - ${this._svgRaw(x?`\n \n ${this._glowRing(t,m)}\n \n \n \n \n \n \n \n \n \n \n ${this._escSvg(this._getNodeName("solar"))}\n — W\n  \n `:"")} + ${this._svgRaw(x?`\n \n ${this._glowRing(t,g)}\n \n \n \n \n \n \n \n \n \n \n ${this._escSvg(this._getNodeName("solar"))}\n — W\n  \n `:"")} - ${this._svgRaw(S?`\n \n \n \n \n  \n `:"")} + ${this._svgRaw(S?`\n \n \n \n \n  \n `:"")} - ${this._svgRaw($?`\n \n ${this._glowRing(i,f)}\n \n \n \n \n \n \n \n \n ${this._escSvg(this._getNodeName("battery"))}\n —%\n — W\n \n  \n `:"")} + ${this._svgRaw($?`\n \n ${this._glowRing(i,f)}\n \n \n \n \n \n \n \n \n ${this._escSvg(this._getNodeName("battery"))}\n —%\n — W\n \n  \n `:"")} - ${this._svgRaw(w?`\n \n ${this._glowRing(r,u)}\n \n \n \n \n \n \n \n \n \n ${this._escSvg(this._getNodeName("grid"))}\n — W\n GRID\n  \n `:"")} + ${this._svgRaw(w?`\n \n ${this._glowRing(r,u)}\n \n \n \n \n \n \n \n \n \n ${this._escSvg(this._getNodeName("grid"))}\n — W\n GRID\n  \n `:"")} - ${this._svgRaw(this._glowRing(a,v,1.4))} - - + ${this._svgRaw(this._glowRing(a,y,1.4))} + + - + - ${this._escSvg(this._getNodeName("home"))} - — W - \u00A0 - \u00A0 + ${this._escSvg(this._getNodeName("home"))} + — W + \u00A0 + \u00A0 - ${this._svgRaw(k?`\n \n ${this._glowRing(o,y)}\n \n \n \n \n \n \n \n \n \n ${this._escSvg(this._getNodeName("ev"))}\n — W\n  \n \n `:"")} + ${this._svgRaw(k?`\n \n ${this._glowRing(n,b)}\n \n \n \n \n \n \n \n \n \n ${this._escSvg(this._getNodeName("ev"))}\n — W\n  \n \n `:"")} @@ -6454,7 +7165,7 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow fill="var(--secondary-text-color,#808080)" opacity="0.15">SEM - `}_svgRaw(e){return e?ji(e):G}_escSvg(e){return e?String(e).replace(/&/g,"&").replace(//g,">").replace(/"/g,"""):""}_updateFlowsImperative(){if(!this._hass)return;const e=this._powerSnapshot(),t=e=>{const t=parseFloat(e);return isNaN(t)?0:t},s=e=>this._getState(e)??0;let i,r=e?t(e.solar_w):this._getState("solar_power")??0;this._entities?.solar?.reverse&&(r=-r);const a="entities"===this._mode&&(this._entities?.battery?.charge||this._entities?.battery?.discharge);if(a)i=s("battery_charge_power")-s("battery_discharge_power");else if(e)i=t(e.battery_w);else{let e=s("battery_power");i=this._entities?.battery?.reverse?-e:e}let o,n;const l="entities"===this._mode&&this._entities?.grid?.entity;if(l){const e=s("grid_power"),t=this._entities.grid.reverse;o=Math.max(0,t?-e:e),n=Math.max(0,t?e:-e)}else e?(o=t(e.grid_import_w),n=t(e.grid_export_w)):(o=s("grid_import_power"),n=s("grid_export_power"));let c=e?t(e.ev_w):this._getState("ev_power")??0;this._entities?.ev?.invert&&(c=-c);const d=e?null==e.battery_soc?null:t(e.battery_soc):this._getState("battery_soc"),p=this._getState("autarky_rate"),_=this._isOut("solar_power"),h=l?this._isOut("grid_power"):this._pairOut("grid_import_power","grid_export_power"),u=a?this._pairOut("battery_charge_power","battery_discharge_power"):this._isOut("battery_power"),g=this._isOut("ev_power"),m=Math.max(0,i),f=Math.max(0,-i);let v;if(e&&null!==e.home_w&&void 0!==e.home_w)v=t(e.home_w);else{const e=this._getEntityId("home_consumption_power");e&&this._hass?.states[e]?(v=this._getState("home_consumption_power"),null!=v&&this._entities?.home?.invert&&(v=-v)):v=_||h?null:Math.max(0,r+o+f-n-m-c)}const y=this._getStateStr("daily_solar_energy"),b=this._getStateStr("daily_ev_energy"),x=this._getStateStr("daily_grid_import_energy"),$=this._getStateStr("daily_grid_export_energy"),w=this._getStateStr("daily_battery_energy"),k=this._getStateStr("daily_home_energy"),S={solar:r,battery:i,gridImport:o,gridExport:n,home:v,ev:c,soc:d,autarky:p,dailySolar:y,dailyEv:b,dailyGridImport:x,dailyGridExport:$,dailyBattery:w,dailyHome:k,solarOut:_,gridOut:h,battOut:u,evOut:g},C=JSON.stringify(S);if(this._lastKey===C)return;this._lastKey=C,this._animateValue("val-solar",_?null:r);const z=i>10?"▼ ":i<-10?"▲ ":"";this._animateValue("val-battery-power",u?null:Math.abs(i),800,e=>z+ue(e));const M=o>n,D=M?"↓ ":n>10?"↑ ":"";this._animateValue("val-grid",h?null:M?o:n,800,e=>D+ue(e)),this._animateValue("val-home",v),this._animateValue("val-ev",g?null:c);const E=this._getState("ev_charger_count");this._setText("val-ev-subtitle",E>1?`(${E} ${this._t("chargers")})`:""),this._animateValue("val-battery-soc",d,800,e=>`${Ae(e,0)}%`);const N=this._getEntityId("autarky_rate");N&&this._animateValue("val-autarky",p,800,e=>`⚡ ${Ae(e,0)}% ${this._t("self_word")}`),this._setText("val-inverter-status",this._getStateStr("charging_state"));const F=this._t("today");this._setText("val-today-solar",y?`${F} ${y} kWh`:""),this._setText("val-today-ev",b?`${F} ${b} kWh`:""),this._setText("val-today-battery",w?`${F} ${w} kWh`:""),this._setText("val-today-home",k?`${F} ${k} kWh`:"");const T=[];if(x&&T.push(`↓${x}`),$&&T.push(`↑${$}`),x&&$){const e=(parseFloat(x)-parseFloat($)).toFixed(1);T.push(`Net ${e>0?"+":""}${e}`)}this._setText("val-today-grid",T.length?T.join(" ")+" kWh":"");const A=this._getLayout(),B=this.renderRoot.getElementById("soc-arc");if(B){const e=2*Math.PI*A.socR;B.style.strokeDashoffset=(e*(1-(d??0)/100)).toFixed(1),B.style.animation=m>10?"socPulse 2s ease-in-out infinite":f>10?"socDrain 2.5s ease-in-out infinite":"none"}const R=this.renderRoot.getElementById("autarky-arc");if(R&&N&&p>0){const e=2*Math.PI*A.autarkyR;R.style.strokeDashoffset=(e*(1-p/100)).toFixed(1),R.style.stroke=this._autarkyColor(p),R.style.opacity="0.75"}else R&&(R.style.opacity="0");const O=M?this._getNodeColor("grid_import"):n>10?this._getNodeColor("grid_export"):this._getNodeColor("grid_import");this._updateGridColor(O,M);const I=this.renderRoot.getElementById("label-grid");I&&(I.textContent=M?this._t("importing"):n>10?this._t("exporting"):this._t("grid"));const L=m>10?"#f06292":f>10?"#4db6ac":this._getNodeColor("battery"),P=this.renderRoot.getElementById("label-battery-state");P&&(P.textContent=m>10?this._t("charging"):f>10?this._t("discharging"):"");for(const e of["val-battery-soc","val-battery-power","label-battery-state","val-today-battery"]){const t=this.renderRoot.getElementById(e);t&&t.setAttribute("fill",L)}const U=this.renderRoot.getElementById("soc-arc");U&&(m>10||f>10)&&(U.style.stroke=L),this._updateFlow("flow-solar",r>10,!1,me(r)),this._updateFlow("flow-battery",Math.abs(i)>10,i<0,me(i),L),this._updateFlow("flow-grid",o>10||n>10,M,me(o||n),O),this._updateFlow("flow-home",v>10,!1,me(v??0)),this._updateFlow("flow-ev",c>10,!1,me(c)),this._setGlowIntensity("node-solar",r,1e4),this._setGlowIntensity("node-battery",Math.abs(i),5e3),this._setGlowIntensity("node-grid",Math.max(o,n),1e4),this._setGlowIntensity("node-home",v??0,8e3),this._setGlowIntensity("node-ev",c,11e3),this._updateDeviceLabels()}_updateFlow(e,t,s,i,r){const a=this.renderRoot.getElementById(e);if(!a)return;if(a.style.opacity=t?"1":"0",!t)return void(a.dataset.sig="");const o=r||a.dataset.color;r&&(a.dataset.color=r);const n=a.dataset.pathD,l=parseInt(a.dataset.count,10)||2,c=`${s?"r":"f"}:${i.toFixed(1)}:${o}`;a.dataset.sig!==c&&(a.dataset.sig=c,a.innerHTML=this._flowEffects(n,o,l,i,s))}_flowEffects(e,t,s,i,r){const a=i.toFixed(1),o=r?' keyPoints="1;0" keyTimes="0;1"':"";let n=`\n \n `;for(let r=0;r\n \n \n \n \n `}return n}_updateGridColor(e,t){const s=this.renderRoot.getElementById("node-grid");s&&s.setAttribute("filter",`url(#glowGrid${t?"Import":"Export"})`);const i=this.renderRoot.getElementById("grid-circle");i&&(i.setAttribute("stroke",e),i.setAttribute("fill",this._hexToRgba(e,.07)));const r=this.renderRoot.querySelector("#node-grid .glow-ring");r&&r.setAttribute("stroke",e);const a=this.renderRoot.getElementById("grid-icon");a&&a.setAttribute("stroke",e);for(const t of["val-grid","label-grid","val-today-grid"]){const s=this.renderRoot.getElementById(t);s&&s.setAttribute("fill",e)}const o=this.renderRoot.getElementById("track-grid");o&&o.setAttribute("stroke",e)}_updateDeviceLabels(){const e=this.renderRoot.getElementById("device-labels");if(!e)return;const t=this._getDeviceList(),s=t.map(([e,t],s)=>`${t.power_entity}:${t.name}:${t.color||_e[s%_e.length]}:${t.icon_override||""}:${t.daily_energy_entity||""}`).join("|");this._deviceConfigSig!==s&&(this._deviceConfigSig=s,this._buildDeviceDOM(e,t)),this._updateDeviceValues(t)}_getDeviceList(){let e=[];if("entities"===this._mode&&this._entities?.individual)e=this._entities.individual.map((e,t)=>[e.entity||`device_${t}`,{name:e.name||e.entity?.split(".").pop()||`Device ${t+1}`,power_entity:e.entity,device_type:e.device_type||"appliance",is_on:!1,current_power:0,color:e.color,icon_override:e.icon,daily_energy_entity:e.daily_energy}]);else if("prefix"===this._mode){const t=this._hass.states[`${this._prefix}controllable_devices_count`];t?.attributes?.devices&&(e=Object.entries(t.attributes.devices).filter(([,e])=>e.power_entity||e.current_power>0).sort((e,t)=>(e[1].priority||5)-(t[1].priority||5)))}return e.slice(0,6)}_buildDeviceDOM(e,t){if(!t.length)return e.innerHTML="",void(this._devicePositions=[]);const s="'Segoe UI','Roboto',sans-serif",i=this._getLayout(),r=i.home,a=this._compact,o=a?26:24,n=a?2:Math.min(t.length,3),l=a?30:60,c=((a?500:1e3)-2*l)/n,d=i.deviceY,p=a?18:20;let _="";this._devicePositions=[],t.forEach(([e,t],i)=>{let h=t.name||e;h.length>p&&(h=h.substring(0,p-1)+"…");const u=t.color||_e[i%_e.length],g=this._deviceIcon(t.device_type,t.name||e),m=i%n,f=Math.floor(i/n),v=l+m*c+c/2,y=d+f*(a?100:90);this._devicePositions.push({cx:v,cy:y,nodeR:o,color:u}),_+=``,_+=``;const b=t.power_entity?` data-entity="${t.power_entity}"`:"";_+=``,_+=``,_+=`${g}`,_+=`${this._escSvg(h)}`,_+=`0 W`,_+=``,_+=""}),e.innerHTML=_,e.querySelectorAll(".device-clickable[data-entity]").forEach(e=>{const t=parseInt(e.dataset.idx);this._setupNodeActions(e,`device_${t}`,e.dataset.entity)}),t.forEach((e,t)=>{delete this._currentValues[`dev-val-${t}`]})}_updateDeviceValues(e){const t=this._getLayout().home;e.forEach(([e,s],i)=>{const r=s.power_entity?this._hass.states[s.power_entity]:null,a=r?Te(r):s.current_power||0,o=a>5,n=s.color||_e[i%_e.length],l=this._devicePositions[i];if(!l)return;if(this._animateValue(`dev-val-${i}`,a),s.daily_energy_entity){const e=this._hass.states[s.daily_energy_entity];this._setText(`dev-daily-${i}`,e?`${this._t("today")} ${Fe(e)?Ne:e.state} kWh`:"")}const c=this.renderRoot.getElementById(`dev-conn-${i}`);c&&c.setAttribute("opacity",o?"0.3":"0.1");const d=this.renderRoot.getElementById(`dev-circle-${i}`);d&&(d.setAttribute("fill",`rgba(128,128,128,${o?.08:.03})`),d.setAttribute("opacity",o?"1":"0.4"));const p=this.renderRoot.getElementById(`dev-val-${i}`);p&&p.setAttribute("opacity",o?"1":"0.5");const _=this.renderRoot.getElementById(`dev-group-${i}`);if(_){const e=_.querySelector("g[transform]");e&&e.setAttribute("opacity",o?"0.7":"0.35")}const h=this.renderRoot.getElementById(`dev-flow-${i}`);if(h)if(a>5){const e=me(a).toFixed(1);if(h.dataset.sig!==e){h.dataset.sig=e;const s=`M${t.cx},${t.cy+t.r} C${t.cx},${t.cy+t.r+30} ${l.cx},${l.cy-40} ${l.cx},${l.cy-l.nodeR}`;h.innerHTML=`\n \n \n \n \n \n `}}else""!==h.dataset.sig&&(h.dataset.sig="",h.innerHTML="")})}_setupClickHandlers(){const e={"node-solar":"solar_power","val-solar":"solar_power","val-today-solar":"daily_solar_energy","node-battery":"battery_soc","val-battery-soc":"battery_soc","val-battery-power":"battery_power","label-battery-state":"battery_power","val-today-battery":"daily_battery_energy","node-grid":"grid_import_power","val-grid":"grid_import_power","label-grid":"grid_import_power","val-today-grid":"daily_grid_import_energy","node-home":"home_consumption_power","val-home":"home_consumption_power","val-autarky":"autarky_rate","val-today-home":"daily_home_energy","node-ev":"ev_power","val-ev":"ev_power","val-today-ev":"daily_ev_energy","val-inverter-status":"charging_state"};for(const[t,s]of Object.entries(e)){const e=this._getEntityId(s);if(!e)continue;const i=this.renderRoot.getElementById(t);i&&(i.setAttribute("data-entity",e),i.style.cursor="pointer")}const t=this.renderRoot.querySelector("svg");t&&!t._semClickBound&&(t._semClickBound=!0,t.addEventListener("click",e=>{let s=e.target;for(let e=0;e<5&&s&&s!==t;e++){const e=s.getAttribute?.("data-entity");if(e)return void this._fireMoreInfo(e);s=s.parentElement}}));const s=[{ids:["node-solar"],node:"solar",key:"solar_power"},{ids:["node-battery"],node:"battery",key:"battery_soc"},{ids:["node-grid"],node:"grid",key:"grid_import_power"},{ids:["node-home"],node:"home",key:"home_consumption_power"},{ids:["node-ev"],node:"ev",key:"ev_power"}];for(const{ids:e,node:t,key:i}of s){const s=this._getEntityId(i);if(s)for(const i of e){const e=this.renderRoot.getElementById(i);e&&!e._semActionsBound&&(e._semActionsBound=!0,e.classList.add("clickable-node"),this._setupNodeActions(e,t,s))}}}_setupNodeActions(e,t,s){let i=null,r=!1,a=0,o=null;e.style.cursor="pointer",e.addEventListener("pointerdown",()=>{r=!1,i=setTimeout(()=>{r=!0;const e=this._getActionConfig(t,"hold_action");"none"!==e.action&&this._handleAction(e,s)},500)}),e.addEventListener("pointerup",()=>clearTimeout(i)),e.addEventListener("pointercancel",()=>{clearTimeout(i),r=!1}),e.addEventListener("click",()=>{if(r)return void(r=!1);const e=this._getActionConfig(t,"double_tap_action");if(!e||"none"===e.action)return void this._handleAction(this._getActionConfig(t,"tap_action"),s);const i=Date.now();i-a<300?(clearTimeout(o),a=0,this._handleAction(e,s)):(a=i,o=setTimeout(()=>{a=0,this._handleAction(this._getActionConfig(t,"tap_action"),s)},300))})}_getActionConfig(e,t){const s=this._entities;if(!s)return{action:"tap_action"===t?"more-info":"none"};let i;if(e.startsWith("device_")){const t=parseInt(e.split("_")[1]);i=s.individual?.[t]}else{i={solar:s.solar,battery:s.battery,grid:s.grid,home:s.home,ev:s.ev||s.individual?.[0]}[e]}const r=i?.[t];return r?"string"==typeof r?{action:r}:r:{action:"tap_action"===t?"more-info":"none"}}_handleAction(e,t){switch(e||(e={action:"more-info"}),e.action){case"more-info":this._fireMoreInfo(e.entity||t);break;case"toggle":this._hass&&this._hass.callService("homeassistant","toggle",{entity_id:e.entity||t});break;case"navigate":e.navigation_path&&(window.history.pushState(null,"",e.navigation_path),window.dispatchEvent(new CustomEvent("location-changed")));break;case"call-service":if(e.service&&this._hass){const[t,s]=e.service.split(".");this._hass.callService(t,s,e.service_data||{})}break;case"url":e.url_path&&window.open(e.url_path,"_blank")}}_setGlowIntensity(e,t,s){const i=this.renderRoot.querySelector(`#${e} .glow-ring`);if(!i)return;const r=Math.min(1,Math.abs(t)/s);i.style.opacity=(.15+.85*r).toFixed(2)}_animateValue(e,t,s=800,i=null){const r=this.renderRoot.getElementById(e);if(!r)return;this._animFrames[e]&&cancelAnimationFrame(this._animFrames[e]);const a=i||(e=>ue(e));if(null==t)return this._animFrames[e]&&(cancelAnimationFrame(this._animFrames[e]),delete this._animFrames[e]),this._currentValues[e]=null,void(r.textContent=a(null));const o=this._currentValues[e]||0;if(this._currentValues[e]=t,Math.abs(o-t)<.5)return void(r.textContent=a(t));const n=performance.now(),l=i=>{const c=Math.min(1,(i-n)/s),d=c<.5?2*c*c:1-Math.pow(-2*c+2,2)/2;r.textContent=a(o+(t-o)*d),c<1?this._animFrames[e]=requestAnimationFrame(l):delete this._animFrames[e]};this._animFrames[e]=requestAnimationFrame(l),setTimeout(()=>{this._currentValues[e]===t&&(this._animFrames[e]&&(cancelAnimationFrame(this._animFrames[e]),delete this._animFrames[e]),r.textContent=a(t))},s+250)}_setText(e,t){const s=this.renderRoot.getElementById(e);s&&(s.textContent=t)}_getState(e){if(!this._hass)return null;const t="prefix"===this._mode?`${this._prefix}${e}`:this._resolveEntity(e);return t?Te(this._hass.states[t]):null}_isOut(e){const t=this._getEntityId(e);return!!t&&null==Te(this._hass?.states[t])}_pairOut(e,t){return Oe(this._hass?.states,[this._getEntityId(e),this._getEntityId(t)])}_powerSnapshot(){if("prefix"!==this._mode||!this._hass)return null;const e=this._hass.states[this._prefix+"home_consumption_power"],t=e&&e.attributes&&e.attributes.power_snapshot;return t&&"object"==typeof t?t:null}_getStateStr(e){if(!this._hass)return"";const t="prefix"===this._mode?`${this._prefix}${e}`:this._resolveEntity(e);if(!t)return"";const s=this._hass.states[t];return s&&"unavailable"!==s.state&&"unknown"!==s.state?s.state:""}_getEntityId(e){return"prefix"===this._mode?`${this._prefix}${e}`:this._resolveEntity(e)}_resolveEntity(e){const t=this._entities;if(!t)return null;return{solar_power:t.solar?.entity,battery_power:t.battery?.entity,battery_charge_power:t.battery?.charge,battery_discharge_power:t.battery?.discharge,grid_power:t.grid?.entity,grid_import_power:t.grid?.consumption,grid_export_power:t.grid?.production,ev_power:t.ev?.entity||t.individual?.[0]?.entity,battery_soc:t.battery?.state_of_charge,home_consumption_power:t.home?.entity,charging_state:t.inverter?.entity,daily_solar_energy:t.solar?.daily_energy,daily_ev_energy:t.ev?.daily_energy||t.individual?.[0]?.daily_energy,daily_grid_import_energy:t.grid?.daily_import_energy,daily_grid_export_energy:t.grid?.daily_export_energy,daily_battery_energy:t.battery?.daily_energy,daily_home_energy:t.home?.daily_energy,autarky_rate:t.home?.autarky,ev_charger_count:t.ev?.charger_count||"sensor.sem_ev_charger_count"}[e]||null}_hasNode(e){if("ev"===e&&!this._showEv)return!1;if("battery"===e&&!this._showBattery)return!1;if("prefix"===this._mode)return!0;const t=this._entities;if(!t)return!1;return{solar:!!t.solar?.entity,battery:!!(t.battery?.entity||t.battery?.charge||t.battery?.discharge),grid:!(!t.grid?.consumption&&!t.grid?.entity),home:!0,ev:!(!t.ev?.entity&&!t.individual?.[0]?.entity),inverter:!!t.inverter?.entity||!!t.solar?.entity}[e]||!1}_getNodeColor(e){const t=this._entities,s={solar:Gi.solar.color,battery:Gi.battery.color,grid:Gi.grid.color_import,grid_import:Gi.grid.color_import,grid_export:Gi.grid.color_export,home:Gi.home.color,ev:Gi.ev.color,inverter:Gi.inverter.color};if(!t)return s[e]||"#888";return{solar:t.solar?.color,battery:t.battery?.color,grid:t.grid?.color_import,grid_import:t.grid?.color_import,grid_export:t.grid?.color_export,home:t.home?.color,ev:t.ev?.color||t.individual?.[0]?.color}[e]||s[e]||"#888"}_getNodeName(e){const t=this._entities,s=Gi[e]?.nameKey,i=s?this._t(s):e;if(!t)return i;return{solar:t.solar?.name,battery:t.battery?.name,grid:t.grid?.name,home:t.home?.name,ev:t.ev?.name||t.individual?.[0]?.name}[e]||i}_getLayout(){return this._compact?{vb:"0 0 500 1100",solar:{cx:250,cy:70,r:48},inverter:{cx:250,cy:225,r:20},battery:{cx:100,cy:340,r:48},grid:{cx:400,cy:340,r:48},home:{cx:250,cy:510,r:60},ev:{cx:100,cy:660,r:42},socR:33,autarkyR:48,paths:{solar:"M250,118 L250,205",home:"M250,245 L250,450",battery:"M230,230 C180,260 120,290 100,292",grid:"M270,230 C320,260 380,290 400,292",ev:"M230,240 C180,380 130,560 100,618"},font:{label:14,value:22,sub:12,homeVal:26},deviceY:810}:{vb:"0 0 1000 800",solar:{cx:500,cy:65,r:50},inverter:{cx:500,cy:210,r:20},battery:{cx:150,cy:270,r:50},grid:{cx:850,cy:270,r:50},home:{cx:500,cy:385,r:62},ev:{cx:150,cy:460,r:44},socR:40,autarkyR:50,paths:{solar:"M500,115 L500,190",home:"M500,230 L500,323",battery:"M480,215 C380,230 250,245 200,270",grid:"M520,215 C620,230 750,245 800,270",ev:"M480,225 C380,330 250,410 195,460"},font:{label:13,value:20,sub:11,homeVal:24},deviceY:590}}_glowFilter(e,t,s){return`\n \n \n \n \n `}_glowRing(e,t,s=1.2){const i=e.r+5;return`\n \n \n `}_track(e,t){return``}_hexToRgba(e,t){const s=e.match(/^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i);return s?`rgba(${parseInt(s[1],16)},${parseInt(s[2],16)},${parseInt(s[3],16)},${t})`:`rgba(128,128,128,${t})`}_autarkyColor(e){if((e=Math.max(0,Math.min(100,e)))<=50){const t=e/50;return`rgb(220,${Math.round(50+150*t)},50)`}const t=(e-50)/50;return`rgb(${Math.round(220-170*t)},200,50)`}_deviceIcon(e,t){const s=(t||"").toLowerCase();return"ev_charger"===(e||"").toLowerCase()||s.includes("keba")||s.includes("charger")||s.includes("wallbox")?'':s.includes("heiz")||s.includes("heat")||s.includes("warm")||s.includes("boiler")?'':s.includes("wash")||s.includes("spül")||s.includes("geschirr")||s.includes("wasch")?'':s.includes("dryer")||s.includes("trockn")?'':s.includes("pool")||s.includes("pump")?'':s.includes("klima")||s.includes("ac")||s.includes("cool")||s.includes("air")?'':s.includes("light")||s.includes("licht")||s.includes("lamp")?'':s.includes("shelly")||s.includes("plug")||s.includes("switch")||s.includes("steckdose")?'':''}getCardSize(){return 8}static async getConfigElement(){return document.createElement("sem-flow-card-editor")}static getStubConfig(e){const t=e?Object.keys(e.states):[],s=e=>{for(const s of e){const e=t.find(e=>e.includes(s));if(e)return e}return null};return{entities:{solar:{entity:s(["solar_power","pv_power"])||"sensor.solar_power"},grid:{consumption:s(["grid_import","grid_consumption"])||"sensor.grid_import_power",production:s(["grid_export","grid_feed"])||"sensor.grid_export_power"},battery:{entity:s(["battery_power","batt_power"])||"sensor.battery_power",state_of_charge:s(["battery_soc","battery_level"])||"sensor.battery_soc"},home:{entity:s(["home_consumption","house_power"])||"sensor.home_consumption_power"}}}}},{type:"sem-flow-card",name:"SEM Flow Card",description:"Animated energy flow diagram — works with any HA entities",documentationURL:"https://github.com/traktore-org/sem-community/blob/develop/docs/DASHBOARD_GUIDE.md#sem-flow-card"});const Ki=864e5,Vi=36e5;function Yi(e){const t=e?.state;if(null==t||""===t||"unavailable"===t||"unknown"===t)return null;const s=parseFloat(t);return!Number.isFinite(s)||s<0||s>100?null:s}function Zi(e){const{soc:t,isEstimate:s}=e||{},i=Be(t);return i.known?`${s?"~":""}${i.label}`:""}const Xi="sensor.sem_",Qi=["solar_power","battery_power","grid_import_power","grid_export_power","ev_power","battery_soc","battery_temperature","charging_state","home_consumption_power","daily_solar_energy","daily_battery_charge_energy","daily_battery_discharge_energy","daily_ev_energy","daily_home_energy","daily_grid_import_energy","daily_grid_export_energy","forecast_today_kwh","controllable_devices_count"],Ji=2551442889.6,er=Date.parse("2000-01-06T18:14:00Z"),tr={new_moon:{k:0,waxing:!0},waxing_crescent:{k:.25,waxing:!0},first_quarter:{k:.5,waxing:!0},waxing_gibbous:{k:.75,waxing:!0},full_moon:{k:1,waxing:!0},waning_gibbous:{k:.75,waxing:!1},last_quarter:{k:.5,waxing:!1},waning_crescent:{k:.25,waxing:!1}};ke("sem-system-diagram-card",class extends Le{static properties={_solarDisp:{state:!0},_battDisp:{state:!0},_homeDisp:{state:!0},_evDisp:{state:!0},_gridDisp:{state:!0},_gridDispMode:{state:!0},_sunTransform:{state:!0},_moonTransform:{state:!0},_sunPowerX:{state:!0},_sunPowerY:{state:!0},_sunPtX:{state:!0},_sunPtY:{state:!0}};static get watchedEntities(){return Qi.map(e=>`${Xi}${e}`)}constructor(){super(),this._prefix=Xi,this._mode="prefix",this._entities=null,this._compact=!1,this._visible=!0,this._resizeObserver=null,this._intersectionObserver=null,this._resizeTimeout=null,this._counterRaf=null,this._targets=null,this._lastDiagKey="",this._sunSparkProps=null,this._sunSparkSig="",this._sparkWaveD=""}setConfig(e){super.setConfig(e),e.entities&&!e.entity_prefix?(this._mode="entities",this._entities=e.entities):(this._mode="prefix",this._entities=null),this._prefix=e.entity_prefix||Xi,this._showEv=!1!==e.show_ev,this._showBattery=!1!==e.show_battery,this._showEvSoc=!1!==e.show_ev_soc}_eid(e){if("entities"!==this._mode)return`${this._prefix}${e}`;const t=this._entities;if(!t)return null;return{solar_power:t.solar?.entity,battery_power:t.battery?.entity,battery_charge_power:t.battery?.charge,battery_discharge_power:t.battery?.discharge,grid_power:t.grid?.entity,grid_import_power:t.grid?.consumption,grid_export_power:t.grid?.production,ev_power:t.ev?.entity||t.individual?.[0]?.entity,battery_soc:t.battery?.state_of_charge,ev_soc:t.ev?.state_of_charge,battery_temperature:t.battery?.temperature,home_consumption_power:t.home?.entity,charging_state:t.inverter?.entity,daily_solar_energy:t.solar?.daily_energy,daily_ev_energy:t.ev?.daily_energy||t.individual?.[0]?.daily_energy,daily_grid_import_energy:t.grid?.daily_import_energy,daily_grid_export_energy:t.grid?.daily_export_energy,daily_battery_charge_energy:t.battery?.daily_charge_energy,daily_battery_discharge_energy:t.battery?.daily_discharge_energy,daily_home_energy:t.home?.daily_energy,autarky_rate:t.home?.autarky,self_consumption_rate:t.home?.self_consumption,forecast_today_kwh:t.solar?.forecast_remaining,controllable_devices_count:t.devices?.count_entity}[e]||null}_powerSnapshot(){if("prefix"!==this._mode||!this._hass)return null;const e=this._hass.states[this._prefix+"home_consumption_power"],t=e&&e.attributes&&e.attributes.power_snapshot;return t&&"object"==typeof t?t:null}_snapNum(e){const t=parseFloat(e);return isNaN(t)?0:t}_flowSolar(){const e=this._powerSnapshot();if(e)return this._snapNum(e.solar_w);const t=this._val("solar_power")??0;return this._entities?.solar?.reverse?-t:t}_flowBattery(){const e=this._entities;if(e?.battery?.charge||e?.battery?.discharge)return this._val("battery_charge_power",0)-this._val("battery_discharge_power",0);const t=this._powerSnapshot();if(t&&null!==t.battery_soc&&void 0!==t.battery_soc)return this._snapNum(t.battery_w);const s=this._val("battery_power",0);return e?.battery?.reverse?-s:s}_flowGridPair(){const e=this._entities;if("entities"===this._mode&&e?.grid?.entity){const t=this._state(e.grid.entity,0),s=e.grid.reverse;return{gridImport:Math.max(0,s?-t:t),gridExport:Math.max(0,s?t:-t)}}const t=this._powerSnapshot();return t?{gridImport:this._snapNum(t.grid_import_w),gridExport:this._snapNum(t.grid_export_w)}:{gridImport:this._val("grid_import_power",0),gridExport:this._val("grid_export_power",0)}}_flowEv(){const e=this._powerSnapshot();if(e)return this._snapNum(e.ev_w);const t=this._val("ev_power")??0;return this._entities?.ev?.invert?-t:t}_flowHome(e,t,s,i,r,a){const o=this._powerSnapshot();if(o&&null!==o.home_w&&void 0!==o.home_w)return Math.max(0,this._snapNum(o.home_w));const n=this._eid("home_consumption_power"),l=n?this._hass?.states[n]:null;if(l&&"unavailable"!==l.state&&"unknown"!==l.state){let e=this._state(n,0);return this._entities?.home?.invert&&(e=-e),Math.max(0,e)}return Math.max(0,e+t+r-s-i-a)}set hass(e){this._hass=e;const t=e?.language,s="function"==typeof semLocalize;let i=!1;if((t!==this._lang||s&&!this._localizeReady)&&(this._lang=t,this._localizeReady=s,i=!0),this._isFrozen()&&!i)return;if(!i&&"entities"!==this._mode&&Re(e?.states,Qi.map(e=>this._eid(e))))return;const r=Qi.filter(e=>!(!this._showEv&&"ev_power"===e)&&!!(this._showBattery||"battery_power"!==e&&"battery_soc"!==e&&"battery_temperature"!==e));let a=r.map(t=>{const s=this._eid(t);return s&&e?.states[s]?.state||""}).join(",")+"|"+t;a+="|"+be(e,this._prefix),this._showEv&&(a+="|"+(e?.states["binary_sensor.sem_ev_connected"]?.state||""),a+="|"+(e?.states["binary_sensor.sem_ev_charging"]?.state||""),a+="|"+this._evSocLabel(e));const o=e?.states["sun.sun"];if(a+="|"+(o?.attributes?.elevation??"")+":"+(o?.attributes?.next_rising||"")+":"+(o?.attributes?.next_setting||""),a+="|"+(e?.states["sensor.moon"]?.state||""),a===this._lastDiagKey&&!i)return;this._lastDiagKey=a;const n=this._flowSolar(),l=this._flowBattery(),c=Math.max(0,l),d=Math.max(0,-l),{gridImport:p,gridExport:_}=this._flowGridPair(),h=this._flowEv(),u=this._flowHome(n,p,_,c,d,h);this._targets={solar:n,batt:Math.abs(l),home:u,ev:h,grid:_>10?_:p>10?p:0,gridMode:_>10?"↑":p>10?"↓":null},this._startTickIfIdle(),this._scheduleUpdate()}get hass(){return this._hass}_val(e,t=null){const s=this._eid(e);return s?this._state(s,t):t}_rawOrMissing(e){const t=this._eid(e),s=t?this._hass?.states[t]:void 0;return Fe(s)?Ne:s.state}_valStr(e){const t=this._eid(e);return t?this._stateStr(t):""}_evSocLabel(e){const t=e?.states;if(!t||!this._showEvSoc)return"";if("entities"===this._mode){const e=this._eid("ev_soc");return e?Zi({soc:Yi(t[e])}):""}return Zi(function(e,t,s){if(1!==s.length)return{soc:null,isEstimate:!1};const i=s[0];return ps(Yi(e[`${t}charger_${i}_vehicle_soc`]),Yi(e[`${t}vehicle_soc`]),Yi(e[`${t}charger_${i}_estimated_soc`]),s.length)}(t,this._prefix,this._evChargerIds(t)))}_evChargerIds(e){const t=`${this._prefix}|${Object.keys(e).length}`;return t!==this._evIdsSig&&(this._evIdsSig=t,this._evIds=function(e,t){const s=`${t}charger_`,i=[];for(const t of e){if(!t.startsWith(s)||!t.endsWith("_power"))continue;if(t.includes("_flow_"))continue;const e=t.slice(s.length,t.length-6);e&&i.push(e)}return i}(Object.keys(e),this._prefix)),this._evIds}_unitStr(e){const t=this._eid(e);return t?this._unitOf(t):""}_readWithHold(e,t,s){const i=this._eid(e),r=i?this._hass?.states[i]:void 0,a=r?.state,o=r&&"unavailable"!==a&&"unknown"!==a,n=Date.now(),l=t+"Ts",c=t.startsWith("this.")?t.slice(5):t,d=l.startsWith("this.")?l.slice(5):l;if(o){const e=parseFloat(a);if(!Number.isNaN(e))return this[c]=e,this[d]=n,{value:e,stale:!1}}return null!=this[c]&&n-(this[d]||0){const s=e??0,i=t-s;return Math.abs(i)<.5?t:s+.18*i},t=()=>{if(!this._targets)return void(this._counterRaf=null);const s=this._targets,i=e(this._solarDisp,s.solar),r=e(this._battDisp,s.batt),a=e(this._homeDisp,s.home),o=e(this._evDisp,s.ev),n=e(this._gridDisp,s.grid),l=i===s.solar&&r===s.batt&&a===s.home&&o===s.ev&&n===s.grid&&this._gridDispMode===s.gridMode;this._solarDisp=i,this._battDisp=r,this._homeDisp=a,this._evDisp=o,this._gridDisp=n,this._gridDispMode=s.gridMode,this._counterRaf=l?null:requestAnimationFrame(t)};this._counterRaf=requestAnimationFrame(t)}firstUpdated(){this.isConnected&&this._observe()}connectedCallback(){super.connectedCallback(),this.hasUpdated&&(this._observe(),this._targets&&this._startTickIfIdle())}_observe(){this._unobserve(),this._resizeObserver=new ResizeObserver(e=>{this._resizeTimeout&&clearTimeout(this._resizeTimeout),this._resizeTimeout=setTimeout(()=>{for(const t of e){const e=t.contentRect.width<500;e!==this._compact&&(this._compact=e,this._sunSparkSig="",this._lastDiagKey="",this.requestUpdate())}},100)}),this._resizeObserver.observe(this),this._intersectionObserver=new IntersectionObserver(e=>{this._visible=e[0].isIntersecting;const t=this.renderRoot.querySelector("svg");t&&(t.style.animationPlayState=this._visible?"running":"paused")},{threshold:.01}),this._intersectionObserver.observe(this)}_unobserve(){this._resizeObserver&&(this._resizeObserver.disconnect(),this._resizeObserver=null),this._intersectionObserver&&(this._intersectionObserver.disconnect(),this._intersectionObserver=null),clearTimeout(this._resizeTimeout)}disconnectedCallback(){super.disconnectedCallback(),this._unobserve(),this._counterRaf&&(cancelAnimationFrame(this._counterRaf),this._counterRaf=null)}updated(e){if(super.updated(e),!this._hass)return;const t=this.renderRoot.querySelector("#sun-arc-path");if(!t)return;const s=this._getLayout(),i=this._computeSunPose();try{const e=t.getTotalLength(),r=t.getPointAtLength(i.pos*e),a=r.x-s.sunX,o=r.y-s.sunY,n=`translate(${a.toFixed(1)},${o.toFixed(1)}) translate(${s.sunX},${s.sunY}) scale(${i.scale.toFixed(2)}) translate(${(-s.sunX).toFixed(1)},${(-s.sunY).toFixed(1)})`,l=i.isNight?`translate(${a.toFixed(1)},${o.toFixed(1)})`:"",c=r.x.toFixed(1),d=(r.y+s.sunR*i.scale+16).toFixed(1),p=+r.x.toFixed(1),_=+r.y.toFixed(1);if(this._sunTransform===n&&this._moonTransform===l&&this._sunPowerX===c&&this._sunPowerY===d&&this._sunPtX===p&&this._sunPtY===_)return;this._sunTransform=n,this._moonTransform=l,this._sunPowerX=c,this._sunPowerY=d,this._sunPtX=p,this._sunPtY=_}catch(e){}}_computeSunPose(){const e=this._hass?.states["sun.sun"],t=(e&&parseFloat(e.attributes?.elevation)||-90)<0,s=this._val("solar_power",0),i=e?.attributes;let r=.5;if(i){const e=i.next_rising?new Date(i.next_rising).getTime():0,s=i.next_setting?new Date(i.next_setting).getTime():0,a=Date.now();if(!t&&e&&s){const t=e-864e5,i=s-t;i>0&&(r=(a-t)/i)}else if(t&&e&&s){const t=function(e,t,s){if(!t||!s)return null;const i=s-e828e5?t-Ki:t,a=r-i;return a<=0?Math.abs(e-r)<=Math.abs(e-i)?0:1:1-(e-i)/a}(a,e,s);null!==t&&(r=t)}else r=e&&a/g,">").replace(/"/g,"""):""}_updateFlowsImperative(){if(!this._hass)return;const e=this._powerSnapshot(),t=e=>{const t=parseFloat(e);return isNaN(t)?0:t},s=e=>this._getState(e)??0;let i,r=e?t(e.solar_w):this._getState("solar_power")??0;this._entities?.solar?.reverse&&(r=-r);const a="entities"===this._mode&&(this._entities?.battery?.charge||this._entities?.battery?.discharge);if(a)i=s("battery_charge_power")-s("battery_discharge_power");else if(e)i=t(e.battery_w);else{let e=s("battery_power");i=this._entities?.battery?.reverse?-e:e}let n,o;const l="entities"===this._mode&&this._entities?.grid?.entity;if(l){const e=s("grid_power"),t=this._entities.grid.reverse;n=Math.max(0,t?-e:e),o=Math.max(0,t?e:-e)}else e?(n=t(e.grid_import_w),o=t(e.grid_export_w)):(n=s("grid_import_power"),o=s("grid_export_power"));let c=e?t(e.ev_w):this._getState("ev_power")??0;this._entities?.ev?.invert&&(c=-c);const _=e?null==e.battery_soc?null:t(e.battery_soc):this._getState("battery_soc"),d=this._getState("autarky_rate"),p=this._isOut("solar_power"),h=l?this._isOut("grid_power"):this._pairOut("grid_import_power","grid_export_power"),u=a?this._pairOut("battery_charge_power","battery_discharge_power"):this._isOut("battery_power"),m=this._isOut("ev_power"),g=Math.max(0,i),f=Math.max(0,-i);let y;if(e&&null!==e.home_w&&void 0!==e.home_w)y=t(e.home_w);else{const e=this._getEntityId("home_consumption_power");e&&this._hass?.states[e]?(y=this._getState("home_consumption_power"),null!=y&&this._entities?.home?.invert&&(y=-y)):y=p||h?null:Math.max(0,r+n+f-o-g-c)}const b=this._getStateStr("daily_solar_energy"),v=this._getStateStr("daily_ev_energy"),x=this._getStateStr("daily_grid_import_energy"),$=this._getStateStr("daily_grid_export_energy"),w=this._getStateStr("daily_battery_energy"),k=this._getStateStr("daily_home_energy"),S={solar:r,battery:i,gridImport:n,gridExport:o,home:y,ev:c,soc:_,autarky:d,dailySolar:b,dailyEv:v,dailyGridImport:x,dailyGridExport:$,dailyBattery:w,dailyHome:k,solarOut:p,gridOut:h,battOut:u,evOut:m},C=JSON.stringify(S);if(this._lastKey===C)return;this._lastKey=C,this._animateValue("val-solar",p?null:r);const M=i>10?"▼ ":i<-10?"▲ ":"";this._animateValue("val-battery-power",u?null:Math.abs(i),800,e=>M+ue(e));const z=n>o,D=z?"↓ ":o>10?"↑ ":"";this._animateValue("val-grid",h?null:z?n:o,800,e=>D+ue(e)),this._animateValue("val-home",y),this._animateValue("val-ev",m?null:c);const N=this._getState("ev_charger_count");this._setText("val-ev-subtitle",N>1?`(${N} ${this._t("chargers")})`:""),this._animateValue("val-battery-soc",_,800,e=>`${Te(e,0)}%`);const E=this._getEntityId("autarky_rate");E&&this._animateValue("val-autarky",d,800,e=>`⚡ ${Te(e,0)}% ${this._t("self_word")}`),this._setText("val-inverter-status",this._getStateStr("charging_state"));const F=this._t("today");this._setText("val-today-solar",b?`${F} ${b} kWh`:""),this._setText("val-today-ev",v?`${F} ${v} kWh`:""),this._setText("val-today-battery",w?`${F} ${w} kWh`:""),this._setText("val-today-home",k?`${F} ${k} kWh`:"");const A=[];if(x&&A.push(`↓${x}`),$&&A.push(`↑${$}`),x&&$){const e=(parseFloat(x)-parseFloat($)).toFixed(1);A.push(`Net ${e>0?"+":""}${e}`)}this._setText("val-today-grid",A.length?A.join(" ")+" kWh":"");const T=this._getLayout(),O=this.renderRoot.getElementById("soc-arc");if(O){const e=2*Math.PI*T.socR;O.style.strokeDashoffset=(e*(1-(_??0)/100)).toFixed(1),O.style.animation=g>10?"socPulse 2s ease-in-out infinite":f>10?"socDrain 2.5s ease-in-out infinite":"none"}const B=this.renderRoot.getElementById("autarky-arc");if(B&&E&&d>0){const e=2*Math.PI*T.autarkyR;B.style.strokeDashoffset=(e*(1-d/100)).toFixed(1),B.style.stroke=this._autarkyColor(d),B.style.opacity="0.75"}else B&&(B.style.opacity="0");const I=z?this._getNodeColor("grid_import"):o>10?this._getNodeColor("grid_export"):this._getNodeColor("grid_import");this._updateGridColor(I,z);const L=this.renderRoot.getElementById("label-grid");L&&(L.textContent=z?this._t("importing"):o>10?this._t("exporting"):this._t("grid"));const R=g>10?"#f06292":f>10?"#4db6ac":this._getNodeColor("battery"),W=this.renderRoot.getElementById("label-battery-state");W&&(W.textContent=g>10?this._t("charging"):f>10?this._t("discharging"):"");for(const e of["val-battery-soc","val-battery-power","label-battery-state","val-today-battery"]){const t=this.renderRoot.getElementById(e);t&&t.setAttribute("fill",R)}const P=this.renderRoot.getElementById("soc-arc");P&&(g>10||f>10)&&(P.style.stroke=R),this._updateFlow("flow-solar",r>10,!1,ge(r)),this._updateFlow("flow-battery",Math.abs(i)>10,i<0,ge(i),R),this._updateFlow("flow-grid",n>10||o>10,z,ge(n||o),I),this._updateFlow("flow-home",y>10,!1,ge(y??0)),this._updateFlow("flow-ev",c>10,!1,ge(c)),this._setGlowIntensity("node-solar",r,1e4),this._setGlowIntensity("node-battery",Math.abs(i),5e3),this._setGlowIntensity("node-grid",Math.max(n,o),1e4),this._setGlowIntensity("node-home",y??0,8e3),this._setGlowIntensity("node-ev",c,11e3),this._updateDeviceLabels()}_updateFlow(e,t,s,i,r){const a=this.renderRoot.getElementById(e);if(!a)return;if(a.style.opacity=t?"1":"0",!t)return void(a.dataset.sig="");const n=r||a.dataset.color;r&&(a.dataset.color=r);const o=a.dataset.pathD,l=parseInt(a.dataset.count,10)||2,c=`${s?"r":"f"}:${i.toFixed(1)}:${n}`;a.dataset.sig!==c&&(a.dataset.sig=c,a.innerHTML=this._flowEffects(o,n,l,i,s))}_flowEffects(e,t,s,i,r){const a=i.toFixed(1),n=r?' keyPoints="1;0" keyTimes="0;1"':"";let o=`\n \n `;for(let r=0;r\n \n \n \n \n `}return o}_updateGridColor(e,t){const s=this.renderRoot.getElementById("node-grid");s&&s.setAttribute("filter",`url(#glowGrid${t?"Import":"Export"})`);const i=this.renderRoot.getElementById("grid-circle");i&&(i.setAttribute("stroke",e),i.setAttribute("fill",this._hexToRgba(e,.07)));const r=this.renderRoot.querySelector("#node-grid .glow-ring");r&&r.setAttribute("stroke",e);const a=this.renderRoot.getElementById("grid-icon");a&&a.setAttribute("stroke",e);for(const t of["val-grid","label-grid","val-today-grid"]){const s=this.renderRoot.getElementById(t);s&&s.setAttribute("fill",e)}const n=this.renderRoot.getElementById("track-grid");n&&n.setAttribute("stroke",e)}_updateDeviceLabels(){const e=this.renderRoot.getElementById("device-labels");if(!e)return;const t=this._getDeviceList(),s=t.map(([e,t],s)=>`${t.power_entity}:${t.name}:${t.color||pe[s%pe.length]}:${t.icon_override||""}:${t.daily_energy_entity||""}`).join("|");this._deviceConfigSig!==s&&(this._deviceConfigSig=s,this._buildDeviceDOM(e,t)),this._updateDeviceValues(t)}_getDeviceList(){let e=[];if("entities"===this._mode&&this._entities?.individual)e=this._entities.individual.map((e,t)=>[e.entity||`device_${t}`,{name:e.name||e.entity?.split(".").pop()||`Device ${t+1}`,power_entity:e.entity,device_type:e.device_type||"appliance",is_on:!1,current_power:0,color:e.color,icon_override:e.icon,daily_energy_entity:e.daily_energy}]);else if("prefix"===this._mode){const t=this._hass.states[`${this._prefix}controllable_devices_count`];t?.attributes?.devices&&(e=Object.entries(t.attributes.devices).filter(([,e])=>e.power_entity||e.current_power>0).sort((e,t)=>(e[1].priority||5)-(t[1].priority||5)))}return e.slice(0,6)}_buildDeviceDOM(e,t){if(!t.length)return e.innerHTML="",void(this._devicePositions=[]);const s="'Segoe UI','Roboto',sans-serif",i=this._getLayout(),r=i.home,a=this._compact,n=a?26:24,o=a?2:Math.min(t.length,3),l=a?30:60,c=((a?500:1e3)-2*l)/o,_=i.deviceY,d=a?18:20;let p="";this._devicePositions=[],t.forEach(([e,t],i)=>{let h=t.name||e;h.length>d&&(h=h.substring(0,d-1)+"…");const u=t.color||pe[i%pe.length],m=this._deviceIcon(t.device_type,t.name||e),g=i%o,f=Math.floor(i/o),y=l+g*c+c/2,b=_+f*(a?100:90);this._devicePositions.push({cx:y,cy:b,nodeR:n,color:u}),p+=``,p+=``;const v=t.power_entity?` data-entity="${t.power_entity}"`:"";p+=``,p+=``,p+=`${m}`,p+=`${this._escSvg(h)}`,p+=`0 W`,p+=``,p+=""}),e.innerHTML=p,e.querySelectorAll(".device-clickable[data-entity]").forEach(e=>{const t=parseInt(e.dataset.idx);this._setupNodeActions(e,`device_${t}`,e.dataset.entity)}),t.forEach((e,t)=>{delete this._currentValues[`dev-val-${t}`]})}_updateDeviceValues(e){const t=this._getLayout().home;e.forEach(([e,s],i)=>{const r=s.power_entity?this._hass.states[s.power_entity]:null,a=r?Ae(r):s.current_power||0,n=a>5,o=s.color||pe[i%pe.length],l=this._devicePositions[i];if(!l)return;if(this._animateValue(`dev-val-${i}`,a),s.daily_energy_entity){const e=this._hass.states[s.daily_energy_entity];this._setText(`dev-daily-${i}`,e?`${this._t("today")} ${Fe(e)?Ee:e.state} kWh`:"")}const c=this.renderRoot.getElementById(`dev-conn-${i}`);c&&c.setAttribute("opacity",n?"0.3":"0.1");const _=this.renderRoot.getElementById(`dev-circle-${i}`);_&&(_.setAttribute("fill",`rgba(128,128,128,${n?.08:.03})`),_.setAttribute("opacity",n?"1":"0.4"));const d=this.renderRoot.getElementById(`dev-val-${i}`);d&&d.setAttribute("opacity",n?"1":"0.5");const p=this.renderRoot.getElementById(`dev-group-${i}`);if(p){const e=p.querySelector("g[transform]");e&&e.setAttribute("opacity",n?"0.7":"0.35")}const h=this.renderRoot.getElementById(`dev-flow-${i}`);if(h)if(a>5){const e=ge(a).toFixed(1);if(h.dataset.sig!==e){h.dataset.sig=e;const s=`M${t.cx},${t.cy+t.r} C${t.cx},${t.cy+t.r+30} ${l.cx},${l.cy-40} ${l.cx},${l.cy-l.nodeR}`;h.innerHTML=`\n \n \n \n \n \n `}}else""!==h.dataset.sig&&(h.dataset.sig="",h.innerHTML="")})}_setupClickHandlers(){const e={"node-solar":"solar_power","val-solar":"solar_power","val-today-solar":"daily_solar_energy","node-battery":"battery_soc","val-battery-soc":"battery_soc","val-battery-power":"battery_power","label-battery-state":"battery_power","val-today-battery":"daily_battery_energy","node-grid":"grid_import_power","val-grid":"grid_import_power","label-grid":"grid_import_power","val-today-grid":"daily_grid_import_energy","node-home":"home_consumption_power","val-home":"home_consumption_power","val-autarky":"autarky_rate","val-today-home":"daily_home_energy","node-ev":"ev_power","val-ev":"ev_power","val-today-ev":"daily_ev_energy","val-inverter-status":"charging_state"};for(const[t,s]of Object.entries(e)){const e=this._getEntityId(s);if(!e)continue;const i=this.renderRoot.getElementById(t);i&&(i.setAttribute("data-entity",e),i.style.cursor="pointer")}const t=this.renderRoot.querySelector("svg");t&&!t._semClickBound&&(t._semClickBound=!0,t.addEventListener("click",e=>{let s=e.target;for(let e=0;e<5&&s&&s!==t;e++){const e=s.getAttribute?.("data-entity");if(e)return void this._fireMoreInfo(e);s=s.parentElement}}));const s=[{ids:["node-solar"],node:"solar",key:"solar_power"},{ids:["node-battery"],node:"battery",key:"battery_soc"},{ids:["node-grid"],node:"grid",key:"grid_import_power"},{ids:["node-home"],node:"home",key:"home_consumption_power"},{ids:["node-ev"],node:"ev",key:"ev_power"}];for(const{ids:e,node:t,key:i}of s){const s=this._getEntityId(i);if(s)for(const i of e){const e=this.renderRoot.getElementById(i);e&&!e._semActionsBound&&(e._semActionsBound=!0,e.classList.add("clickable-node"),this._setupNodeActions(e,t,s))}}}_setupNodeActions(e,t,s){let i=null,r=!1,a=0,n=null;e.style.cursor="pointer",e.addEventListener("pointerdown",()=>{r=!1,i=setTimeout(()=>{r=!0;const e=this._getActionConfig(t,"hold_action");"none"!==e.action&&this._handleAction(e,s)},500)}),e.addEventListener("pointerup",()=>clearTimeout(i)),e.addEventListener("pointercancel",()=>{clearTimeout(i),r=!1}),e.addEventListener("click",()=>{if(r)return void(r=!1);const e=this._getActionConfig(t,"double_tap_action");if(!e||"none"===e.action)return void this._handleAction(this._getActionConfig(t,"tap_action"),s);const i=Date.now();i-a<300?(clearTimeout(n),a=0,this._handleAction(e,s)):(a=i,n=setTimeout(()=>{a=0,this._handleAction(this._getActionConfig(t,"tap_action"),s)},300))})}_getActionConfig(e,t){const s=this._entities;if(!s)return{action:"tap_action"===t?"more-info":"none"};let i;if(e.startsWith("device_")){const t=parseInt(e.split("_")[1]);i=s.individual?.[t]}else{i={solar:s.solar,battery:s.battery,grid:s.grid,home:s.home,ev:s.ev||s.individual?.[0]}[e]}const r=i?.[t];return r?"string"==typeof r?{action:r}:r:{action:"tap_action"===t?"more-info":"none"}}_handleAction(e,t){switch(e||(e={action:"more-info"}),e.action){case"more-info":this._fireMoreInfo(e.entity||t);break;case"toggle":this._hass&&this._hass.callService("homeassistant","toggle",{entity_id:e.entity||t});break;case"navigate":e.navigation_path&&(window.history.pushState(null,"",e.navigation_path),window.dispatchEvent(new CustomEvent("location-changed")));break;case"call-service":if(e.service&&this._hass){const[t,s]=e.service.split(".");this._hass.callService(t,s,e.service_data||{})}break;case"url":e.url_path&&window.open(e.url_path,"_blank")}}_setGlowIntensity(e,t,s){const i=this.renderRoot.querySelector(`#${e} .glow-ring`);if(!i)return;const r=Math.min(1,Math.abs(t)/s);i.style.opacity=(.15+.85*r).toFixed(2)}_animateValue(e,t,s=800,i=null){const r=this.renderRoot.getElementById(e);if(!r)return;this._animFrames[e]&&cancelAnimationFrame(this._animFrames[e]);const a=i||(e=>ue(e));if(null==t)return this._animFrames[e]&&(cancelAnimationFrame(this._animFrames[e]),delete this._animFrames[e]),this._currentValues[e]=null,void(r.textContent=a(null));const n=this._currentValues[e]||0;if(this._currentValues[e]=t,Math.abs(n-t)<.5)return void(r.textContent=a(t));const o=performance.now(),l=i=>{const c=Math.min(1,(i-o)/s),_=c<.5?2*c*c:1-Math.pow(-2*c+2,2)/2;r.textContent=a(n+(t-n)*_),c<1?this._animFrames[e]=requestAnimationFrame(l):delete this._animFrames[e]};this._animFrames[e]=requestAnimationFrame(l),setTimeout(()=>{this._currentValues[e]===t&&(this._animFrames[e]&&(cancelAnimationFrame(this._animFrames[e]),delete this._animFrames[e]),r.textContent=a(t))},s+250)}_setText(e,t){const s=this.renderRoot.getElementById(e);s&&(s.textContent=t)}_getState(e){if(!this._hass)return null;const t="prefix"===this._mode?`${this._prefix}${e}`:this._resolveEntity(e);return t?Ae(this._hass.states[t]):null}_isOut(e){const t=this._getEntityId(e);return!!t&&null==Ae(this._hass?.states[t])}_pairOut(e,t){return Ie(this._hass?.states,[this._getEntityId(e),this._getEntityId(t)])}_powerSnapshot(){if("prefix"!==this._mode||!this._hass)return null;const e=this._hass.states[this._prefix+"home_consumption_power"],t=e&&e.attributes&&e.attributes.power_snapshot;return t&&"object"==typeof t?t:null}_getStateStr(e){if(!this._hass)return"";const t="prefix"===this._mode?`${this._prefix}${e}`:this._resolveEntity(e);if(!t)return"";const s=this._hass.states[t];return s&&"unavailable"!==s.state&&"unknown"!==s.state?s.state:""}_getEntityId(e){return"prefix"===this._mode?`${this._prefix}${e}`:this._resolveEntity(e)}_resolveEntity(e){const t=this._entities;if(!t)return null;return{solar_power:t.solar?.entity,battery_power:t.battery?.entity,battery_charge_power:t.battery?.charge,battery_discharge_power:t.battery?.discharge,grid_power:t.grid?.entity,grid_import_power:t.grid?.consumption,grid_export_power:t.grid?.production,ev_power:t.ev?.entity||t.individual?.[0]?.entity,battery_soc:t.battery?.state_of_charge,home_consumption_power:t.home?.entity,charging_state:t.inverter?.entity,daily_solar_energy:t.solar?.daily_energy,daily_ev_energy:t.ev?.daily_energy||t.individual?.[0]?.daily_energy,daily_grid_import_energy:t.grid?.daily_import_energy,daily_grid_export_energy:t.grid?.daily_export_energy,daily_battery_energy:t.battery?.daily_energy,daily_home_energy:t.home?.daily_energy,autarky_rate:t.home?.autarky,ev_charger_count:t.ev?.charger_count||"sensor.sem_ev_charger_count"}[e]||null}_hasNode(e){if("ev"===e&&!this._showEv)return!1;if("battery"===e&&!this._showBattery)return!1;if("prefix"===this._mode)return!0;const t=this._entities;if(!t)return!1;return{solar:!!t.solar?.entity,battery:!!(t.battery?.entity||t.battery?.charge||t.battery?.discharge),grid:!(!t.grid?.consumption&&!t.grid?.entity),home:!0,ev:!(!t.ev?.entity&&!t.individual?.[0]?.entity),inverter:!!t.inverter?.entity||!!t.solar?.entity}[e]||!1}_getNodeColor(e){const t=this._entities,s={solar:sc.solar.color,battery:sc.battery.color,grid:sc.grid.color_import,grid_import:sc.grid.color_import,grid_export:sc.grid.color_export,home:sc.home.color,ev:sc.ev.color,inverter:sc.inverter.color};if(!t)return s[e]||"#888";return{solar:t.solar?.color,battery:t.battery?.color,grid:t.grid?.color_import,grid_import:t.grid?.color_import,grid_export:t.grid?.color_export,home:t.home?.color,ev:t.ev?.color||t.individual?.[0]?.color}[e]||s[e]||"#888"}_getNodeName(e){const t=this._entities,s=sc[e]?.nameKey,i=s?this._t(s):e;if(!t)return i;return{solar:t.solar?.name,battery:t.battery?.name,grid:t.grid?.name,home:t.home?.name,ev:t.ev?.name||t.individual?.[0]?.name}[e]||i}_getLayout(){return this._compact?{vb:"0 0 500 1100",solar:{cx:250,cy:70,r:48},inverter:{cx:250,cy:225,r:20},battery:{cx:100,cy:340,r:48},grid:{cx:400,cy:340,r:48},home:{cx:250,cy:510,r:60},ev:{cx:100,cy:660,r:42},socR:33,autarkyR:48,paths:{solar:"M250,118 L250,205",home:"M250,245 L250,450",battery:"M230,230 C180,260 120,290 100,292",grid:"M270,230 C320,260 380,290 400,292",ev:"M230,240 C180,380 130,560 100,618"},font:{label:14,value:22,sub:12,homeVal:26},deviceY:810}:{vb:"0 0 1000 800",solar:{cx:500,cy:65,r:50},inverter:{cx:500,cy:210,r:20},battery:{cx:150,cy:270,r:50},grid:{cx:850,cy:270,r:50},home:{cx:500,cy:385,r:62},ev:{cx:150,cy:460,r:44},socR:40,autarkyR:50,paths:{solar:"M500,115 L500,190",home:"M500,230 L500,323",battery:"M480,215 C380,230 250,245 200,270",grid:"M520,215 C620,230 750,245 800,270",ev:"M480,225 C380,330 250,410 195,460"},font:{label:13,value:20,sub:11,homeVal:24},deviceY:590}}_glowFilter(e,t,s){return`\n \n \n \n \n `}_glowRing(e,t,s=1.2){const i=e.r+5;return`\n \n \n `}_track(e,t){return``}_hexToRgba(e,t){const s=e.match(/^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i);return s?`rgba(${parseInt(s[1],16)},${parseInt(s[2],16)},${parseInt(s[3],16)},${t})`:`rgba(128,128,128,${t})`}_autarkyColor(e){if((e=Math.max(0,Math.min(100,e)))<=50){const t=e/50;return`rgb(220,${Math.round(50+150*t)},50)`}const t=(e-50)/50;return`rgb(${Math.round(220-170*t)},200,50)`}_deviceIcon(e,t){const s=(t||"").toLowerCase();return"ev_charger"===(e||"").toLowerCase()||s.includes("keba")||s.includes("charger")||s.includes("wallbox")?'':s.includes("heiz")||s.includes("heat")||s.includes("warm")||s.includes("boiler")?'':s.includes("wash")||s.includes("spül")||s.includes("geschirr")||s.includes("wasch")?'':s.includes("dryer")||s.includes("trockn")?'':s.includes("pool")||s.includes("pump")?'':s.includes("klima")||s.includes("ac")||s.includes("cool")||s.includes("air")?'':s.includes("light")||s.includes("licht")||s.includes("lamp")?'':s.includes("shelly")||s.includes("plug")||s.includes("switch")||s.includes("steckdose")?'':''}getCardSize(){return 8}static async getConfigElement(){return document.createElement("sem-flow-card-editor")}static getStubConfig(e){const t=e?Object.keys(e.states):[],s=e=>{for(const s of e){const e=t.find(e=>e.includes(s));if(e)return e}return null};return{entities:{solar:{entity:s(["solar_power","pv_power"])||"sensor.solar_power"},grid:{consumption:s(["grid_import","grid_consumption"])||"sensor.grid_import_power",production:s(["grid_export","grid_feed"])||"sensor.grid_export_power"},battery:{entity:s(["battery_power","batt_power"])||"sensor.battery_power",state_of_charge:s(["battery_soc","battery_level"])||"sensor.battery_soc"},home:{entity:s(["home_consumption","house_power"])||"sensor.home_consumption_power"}}}}},{type:"sem-flow-card",name:"SEM Flow Card",description:"Animated energy flow diagram — works with any HA entities",documentationURL:"https://github.com/traktore-org/sem-community/blob/develop/docs/DASHBOARD_GUIDE.md#sem-flow-card"});const ic=864e5,rc=36e5;function ac(e){const t=e?.state;if(null==t||""===t||"unavailable"===t||"unknown"===t)return null;const s=parseFloat(t);return!Number.isFinite(s)||s<0||s>100?null:s}function nc(e){const{soc:t,isEstimate:s}=e||{},i=Oe(t);return i.known?`${s?"~":""}${i.label}`:""}const oc="sensor.sem_",lc=["solar_power","battery_power","grid_import_power","grid_export_power","ev_power","battery_soc","battery_temperature","charging_state","home_consumption_power","daily_solar_energy","daily_battery_charge_energy","daily_battery_discharge_energy","daily_ev_energy","daily_home_energy","daily_grid_import_energy","daily_grid_export_energy","forecast_today_kwh","controllable_devices_count"],cc=2551442889.6,_c=Date.parse("2000-01-06T18:14:00Z"),dc={new_moon:{k:0,waxing:!0},waxing_crescent:{k:.25,waxing:!0},first_quarter:{k:.5,waxing:!0},waxing_gibbous:{k:.75,waxing:!0},full_moon:{k:1,waxing:!0},waning_gibbous:{k:.75,waxing:!1},last_quarter:{k:.5,waxing:!1},waning_crescent:{k:.25,waxing:!1}};ke("sem-system-diagram-card",class extends Re{static properties={_solarDisp:{state:!0},_battDisp:{state:!0},_homeDisp:{state:!0},_evDisp:{state:!0},_gridDisp:{state:!0},_gridDispMode:{state:!0},_sunTransform:{state:!0},_moonTransform:{state:!0},_sunPowerX:{state:!0},_sunPowerY:{state:!0},_sunPtX:{state:!0},_sunPtY:{state:!0}};static get watchedEntities(){return lc.map(e=>`${oc}${e}`)}constructor(){super(),this._prefix=oc,this._mode="prefix",this._entities=null,this._compact=!1,this._visible=!0,this._resizeObserver=null,this._intersectionObserver=null,this._resizeTimeout=null,this._counterRaf=null,this._targets=null,this._lastDiagKey="",this._sunSparkProps=null,this._sunSparkSig="",this._sparkWaveD=""}setConfig(e){super.setConfig(e),e.entities&&!e.entity_prefix?(this._mode="entities",this._entities=e.entities):(this._mode="prefix",this._entities=null),this._prefix=e.entity_prefix||oc,this._showEv=!1!==e.show_ev,this._showBattery=!1!==e.show_battery,this._showEvSoc=!1!==e.show_ev_soc}_eid(e){if("entities"!==this._mode)return`${this._prefix}${e}`;const t=this._entities;if(!t)return null;return{solar_power:t.solar?.entity,battery_power:t.battery?.entity,battery_charge_power:t.battery?.charge,battery_discharge_power:t.battery?.discharge,grid_power:t.grid?.entity,grid_import_power:t.grid?.consumption,grid_export_power:t.grid?.production,ev_power:t.ev?.entity||t.individual?.[0]?.entity,battery_soc:t.battery?.state_of_charge,ev_soc:t.ev?.state_of_charge,battery_temperature:t.battery?.temperature,home_consumption_power:t.home?.entity,charging_state:t.inverter?.entity,daily_solar_energy:t.solar?.daily_energy,daily_ev_energy:t.ev?.daily_energy||t.individual?.[0]?.daily_energy,daily_grid_import_energy:t.grid?.daily_import_energy,daily_grid_export_energy:t.grid?.daily_export_energy,daily_battery_charge_energy:t.battery?.daily_charge_energy,daily_battery_discharge_energy:t.battery?.daily_discharge_energy,daily_home_energy:t.home?.daily_energy,autarky_rate:t.home?.autarky,self_consumption_rate:t.home?.self_consumption,forecast_today_kwh:t.solar?.forecast_remaining,controllable_devices_count:t.devices?.count_entity}[e]||null}_powerSnapshot(){if("prefix"!==this._mode||!this._hass)return null;const e=this._hass.states[this._prefix+"home_consumption_power"],t=e&&e.attributes&&e.attributes.power_snapshot;return t&&"object"==typeof t?t:null}_snapNum(e){const t=parseFloat(e);return isNaN(t)?0:t}_flowSolar(){const e=this._powerSnapshot();if(e)return this._snapNum(e.solar_w);const t=this._val("solar_power")??0;return this._entities?.solar?.reverse?-t:t}_flowBattery(){const e=this._entities;if(e?.battery?.charge||e?.battery?.discharge)return this._val("battery_charge_power",0)-this._val("battery_discharge_power",0);const t=this._powerSnapshot();if(t&&null!==t.battery_soc&&void 0!==t.battery_soc)return this._snapNum(t.battery_w);const s=this._val("battery_power",0);return e?.battery?.reverse?-s:s}_flowGridPair(){const e=this._entities;if("entities"===this._mode&&e?.grid?.entity){const t=this._state(e.grid.entity,0),s=e.grid.reverse;return{gridImport:Math.max(0,s?-t:t),gridExport:Math.max(0,s?t:-t)}}const t=this._powerSnapshot();return t?{gridImport:this._snapNum(t.grid_import_w),gridExport:this._snapNum(t.grid_export_w)}:{gridImport:this._val("grid_import_power",0),gridExport:this._val("grid_export_power",0)}}_flowEv(){const e=this._powerSnapshot();if(e)return this._snapNum(e.ev_w);const t=this._val("ev_power")??0;return this._entities?.ev?.invert?-t:t}_flowHome(e,t,s,i,r,a){const n=this._powerSnapshot();if(n&&null!==n.home_w&&void 0!==n.home_w)return Math.max(0,this._snapNum(n.home_w));const o=this._eid("home_consumption_power"),l=o?this._hass?.states[o]:null;if(l&&"unavailable"!==l.state&&"unknown"!==l.state){let e=this._state(o,0);return this._entities?.home?.invert&&(e=-e),Math.max(0,e)}return Math.max(0,e+t+r-s-i-a)}set hass(e){this._hass=e;const t=e?.language,s="function"==typeof semLocalize;let i=!1;if((t!==this._lang||s&&!this._localizeReady)&&(this._lang=t,this._localizeReady=s,i=!0),this._isFrozen()&&!i)return;if(!i&&"entities"!==this._mode&&Be(e?.states,lc.map(e=>this._eid(e))))return;const r=lc.filter(e=>!(!this._showEv&&"ev_power"===e)&&!!(this._showBattery||"battery_power"!==e&&"battery_soc"!==e&&"battery_temperature"!==e));let a=r.map(t=>{const s=this._eid(t);return s&&e?.states[s]?.state||""}).join(",")+"|"+t;a+="|"+ve(e,this._prefix),this._showEv&&(a+="|"+(e?.states["binary_sensor.sem_ev_connected"]?.state||""),a+="|"+(e?.states["binary_sensor.sem_ev_charging"]?.state||""),a+="|"+this._evSocLabel(e));const n=e?.states["sun.sun"];if(a+="|"+(n?.attributes?.elevation??"")+":"+(n?.attributes?.next_rising||"")+":"+(n?.attributes?.next_setting||""),a+="|"+(e?.states["sensor.moon"]?.state||""),a===this._lastDiagKey&&!i)return;this._lastDiagKey=a;const o=this._flowSolar(),l=this._flowBattery(),c=Math.max(0,l),_=Math.max(0,-l),{gridImport:d,gridExport:p}=this._flowGridPair(),h=this._flowEv(),u=this._flowHome(o,d,p,c,_,h);this._targets={solar:o,batt:Math.abs(l),home:u,ev:h,grid:p>10?p:d>10?d:0,gridMode:p>10?"↑":d>10?"↓":null},this._startTickIfIdle(),this._scheduleUpdate()}get hass(){return this._hass}_val(e,t=null){const s=this._eid(e);return s?this._state(s,t):t}_rawOrMissing(e){const t=this._eid(e),s=t?this._hass?.states[t]:void 0;return Fe(s)?Ee:s.state}_valStr(e){const t=this._eid(e);return t?this._stateStr(t):""}_evSocLabel(e){const t=e?.states;if(!t||!this._showEvSoc)return"";if("entities"===this._mode){const e=this._eid("ev_soc");return e?nc({soc:ac(t[e])}):""}return nc(function(e,t,s){if(1!==s.length)return{soc:null,isEstimate:!1};const i=s[0];return Bi(ac(e[`${t}charger_${i}_vehicle_soc`]),ac(e[`${t}vehicle_soc`]),ac(e[`${t}charger_${i}_estimated_soc`]),s.length)}(t,this._prefix,this._evChargerIds(t)))}_evChargerIds(e){const t=`${this._prefix}|${Object.keys(e).length}`;return t!==this._evIdsSig&&(this._evIdsSig=t,this._evIds=function(e,t){const s=`${t}charger_`,i=[];for(const t of e){if(!t.startsWith(s)||!t.endsWith("_power"))continue;if(t.includes("_flow_"))continue;const e=t.slice(s.length,t.length-6);e&&i.push(e)}return i}(Object.keys(e),this._prefix)),this._evIds}_unitStr(e){const t=this._eid(e);return t?this._unitOf(t):""}_readWithHold(e,t,s){const i=this._eid(e),r=i?this._hass?.states[i]:void 0,a=r?.state,n=r&&"unavailable"!==a&&"unknown"!==a,o=Date.now(),l=t+"Ts",c=t.startsWith("this.")?t.slice(5):t,_=l.startsWith("this.")?l.slice(5):l;if(n){const e=parseFloat(a);if(!Number.isNaN(e))return this[c]=e,this[_]=o,{value:e,stale:!1}}return null!=this[c]&&o-(this[_]||0){const s=e??0,i=t-s;return Math.abs(i)<.5?t:s+.18*i},t=()=>{if(!this._targets)return void(this._counterRaf=null);const s=this._targets,i=e(this._solarDisp,s.solar),r=e(this._battDisp,s.batt),a=e(this._homeDisp,s.home),n=e(this._evDisp,s.ev),o=e(this._gridDisp,s.grid),l=i===s.solar&&r===s.batt&&a===s.home&&n===s.ev&&o===s.grid&&this._gridDispMode===s.gridMode;this._solarDisp=i,this._battDisp=r,this._homeDisp=a,this._evDisp=n,this._gridDisp=o,this._gridDispMode=s.gridMode,this._counterRaf=l?null:requestAnimationFrame(t)};this._counterRaf=requestAnimationFrame(t)}firstUpdated(){this.isConnected&&this._observe()}connectedCallback(){super.connectedCallback(),this.hasUpdated&&(this._observe(),this._targets&&this._startTickIfIdle())}_observe(){this._unobserve(),this._resizeObserver=new ResizeObserver(e=>{this._resizeTimeout&&clearTimeout(this._resizeTimeout),this._resizeTimeout=setTimeout(()=>{for(const t of e){const e=t.contentRect.width<500;e!==this._compact&&(this._compact=e,this._sunSparkSig="",this._lastDiagKey="",this.requestUpdate())}},100)}),this._resizeObserver.observe(this),this._intersectionObserver=new IntersectionObserver(e=>{this._visible=e[0].isIntersecting;const t=this.renderRoot.querySelector("svg");t&&(t.style.animationPlayState=this._visible?"running":"paused")},{threshold:.01}),this._intersectionObserver.observe(this)}_unobserve(){this._resizeObserver&&(this._resizeObserver.disconnect(),this._resizeObserver=null),this._intersectionObserver&&(this._intersectionObserver.disconnect(),this._intersectionObserver=null),clearTimeout(this._resizeTimeout)}disconnectedCallback(){super.disconnectedCallback(),this._unobserve(),this._counterRaf&&(cancelAnimationFrame(this._counterRaf),this._counterRaf=null)}updated(e){if(super.updated(e),!this._hass)return;const t=this.renderRoot.querySelector("#sun-arc-path");if(!t)return;const s=this._getLayout(),i=this._computeSunPose();try{const e=t.getTotalLength(),r=t.getPointAtLength(i.pos*e),a=r.x-s.sunX,n=r.y-s.sunY,o=`translate(${a.toFixed(1)},${n.toFixed(1)}) translate(${s.sunX},${s.sunY}) scale(${i.scale.toFixed(2)}) translate(${(-s.sunX).toFixed(1)},${(-s.sunY).toFixed(1)})`,l=i.isNight?`translate(${a.toFixed(1)},${n.toFixed(1)})`:"",c=r.x.toFixed(1),_=(r.y+s.sunR*i.scale+16).toFixed(1),d=+r.x.toFixed(1),p=+r.y.toFixed(1);if(this._sunTransform===o&&this._moonTransform===l&&this._sunPowerX===c&&this._sunPowerY===_&&this._sunPtX===d&&this._sunPtY===p)return;this._sunTransform=o,this._moonTransform=l,this._sunPowerX=c,this._sunPowerY=_,this._sunPtX=d,this._sunPtY=p}catch(e){}}_computeSunPose(){const e=this._hass?.states["sun.sun"],t=(e&&parseFloat(e.attributes?.elevation)||-90)<0,s=this._val("solar_power",0),i=e?.attributes;let r=.5;if(i){const e=i.next_rising?new Date(i.next_rising).getTime():0,s=i.next_setting?new Date(i.next_setting).getTime():0,a=Date.now();if(!t&&e&&s){const t=e-864e5,i=s-t;i>0&&(r=(a-t)/i)}else if(t&&e&&s){const t=function(e,t,s){if(!t||!s)return null;const i=s-e828e5?t-ic:t,a=r-i;return a<=0?Math.abs(e-r)<=Math.abs(e-i)?0:1:1-(e-i)/a}(a,e,s);null!==t&&(r=t)}else r=e&&a50?Math.min(1,.3+o/5e3):0,k=this._hass?.states["sun.sun"]?.attributes,S=this._hass?.config?.time_zone||void 0,C=k?.next_rising?ge(k.next_rising,S):"",z=k?.next_setting?ge(k.next_setting,S):"",M=ve(this).isDark,D=M?"#FCD170":"#a06a00",E=M?.45:.8,N=M?"#ff9800":"#b35c00",F=M?.4:.8,T=M?"#96CAEE":"#3d6e94",A=M?.6:.85,B=M?.35:.8,R=e.B.r/50,O=64*R,I=O-12*R,L=e.B.cy-O/2+8*R,P=I*g.fraction,U=L+I-P,q=m>10?"#f06292":"url(#battFillGrad)",j=m>10,K=l>n?"#8353d1":"#488fc2",V=l>10&&l>n?this._t("exporting"):n>10?this._t("importing"):"",Y=l>10&&l>n?"#8353d1":"#488fc2",Z=m>10?"#f06292":"#4db6ac",X=m>10?this._t("charging"):f>10?this._t("discharging"):"",Q=m>10?"#f06292":"#4db6ac",J="on"===this._hass?.states["binary_sensor.sem_ev_connected"]?.state;let ee="",te="#8DC892";"on"===this._hass?.states["binary_sensor.sem_ev_charging"]?.state||c>10?(ee=this._t("charging"),te="#8DC892"):J&&(ee=this._t("connected")||"Verbunden",te="rgba(141,200,146,0.6)");const se=this._showEv?this._evSocLabel(this._hass):"",ie=this._t("today"),re=`${ie} ${this._rawOrMissing("daily_solar_energy")} kWh`,ae=this._val("forecast_today_kwh"),oe=ae>0?`☀ ${ae.toFixed(1)} kWh ${this._t("forecast")||"Forecast"}`:"",ne=`${ie} ${this._rawOrMissing("daily_ev_energy")} kWh`,le=`${ie} ${Ae(this._val("daily_home_energy"),1)} kWh`,ce=`+${Ae(this._val("daily_battery_charge_energy"),1)} / -${Ae(this._val("daily_battery_discharge_energy"),1)} kWh`,de=`↓${Ae(this._val("daily_grid_import_energy"),1)} / ↑${Ae(this._val("daily_grid_export_energy"),1)} kWh`,pe=function(e,t,s={}){const{decimals:i=0,space:r=!1}=s;if(null==e||""===e||isNaN(parseFloat(e)))return"";const a=t||"°C";return`${parseFloat(e).toFixed(i)}${r?" ":""}${a}`}(this._valStr("inverter_temperature"),this._unitStr("inverter_temperature")),_e=this._valStr("charging_state"),he=t?22:30,fe=_e.length>he?_e.substring(0,he-1)+"…":_e,ye=[],be=["solar_power","grid_import_power","grid_export_power"];this._showBattery&&be.push("battery_power","battery_soc"),this._showEv&&be.push("ev_power");for(const e of be){const t=this._eid(e);t?Fe(this._hass?.states[t])&&ye.push(e):"prefix"===this._mode&&ye.push(e)}const ke=new Set(ye),Se=ke.has("solar_power"),Ce=this._hass?.states,ze="entities"===this._mode&&this._entities?.grid?.entity?Oe(Ce,[this._entities.grid.entity]):Oe(Ce,[this._eid("grid_import_power"),this._eid("grid_export_power")]),Me=ke.has("ev_power"),De=this._powerSnapshot()?.home_w,Ee=null==De&&Fe(this._hass?.states[this._eid("home_consumption_power")])&&(Se||ze),Ne=ue(Se?null:this._solarDisp??0),Te=(m>10?"↑ ":f>10?"↓ ":"")+ue(this._battDisp??0),Re=ue(Ee?null:this._homeDisp??0),Ie=ue(Me?null:this._evDisp??0),Le=ze?ue(null):this._gridDispMode?`${this._gridDispMode} ${ue(this._gridDisp??0)}`:ue(0),Pe="↑"===this._gridDispMode?"#8353d1":"#488fc2",Ue=xe(this._hass,this._prefix),qe=o>10,He=Math.abs(d)>10,We=d<0,je=m>10?"#f06292":"#4db6ac",Ge=n>10||l>10,Ke=n>l,Ve=l>n?"#8353d1":"#488fc2",Ye=v>10,Ze=c>10;return H` + `}render(){if(!this._config)return H;const e=this._getLayout(),t=this._compact,s="'Segoe UI','Roboto',sans-serif",i=e.font.label,r=e.font.value,a=e.font.sub,n=this._flowSolar(),{gridImport:o,gridExport:l}=this._flowGridPair(),c=this._flowEv();let _,d;this._entities?.battery?.charge||this._entities?.battery?.discharge?(_=this._flowBattery(),d=Ie(this._hass?.states,[this._eid("battery_charge_power"),this._eid("battery_discharge_power")])):(({value:_,stale:d}=this._readWithHold("battery_power","this._lastBattPower",6e4)),this._entities?.battery?.reverse&&(_=-_));const{value:p,stale:h}=this._readWithHold("battery_soc","this._lastBattSoc",6e4),u=d||h,m=Oe(p,h),g=Math.max(0,_),f=Math.max(0,-_),y=this._flowHome(n,o,l,g,f,c),b=this._computeSunPose(),{isNight:v}=b,x=v?this._computeMoonPhase():{k:0,waxing:!0},$=v?.3:.85,w=n>50?Math.min(1,.3+n/5e3):0,k=this._hass?.states["sun.sun"]?.attributes,S=this._hass?.config?.time_zone||void 0,C=k?.next_rising?me(k.next_rising,S):"",M=k?.next_setting?me(k.next_setting,S):"",z=ye(this).isDark,D=z?"#FCD170":"#a06a00",N=z?.45:.8,E=z?"#ff9800":"#b35c00",F=z?.4:.8,A=z?"#96CAEE":"#3d6e94",T=z?.6:.85,O=z?.35:.8,B=e.B.r/50,I=64*B,L=I-12*B,R=e.B.cy-I/2+8*B,W=L*m.fraction,P=R+L-W,U=g>10?"#f06292":"url(#battFillGrad)",K=g>10,G=l>o?"#8353d1":"#488fc2",V=l>10&&l>o?this._t("exporting"):o>10?this._t("importing"):"",Y=l>10&&l>o?"#8353d1":"#488fc2",Z=g>10?"#f06292":"#4db6ac",X=g>10?this._t("charging"):f>10?this._t("discharging"):"",J=g>10?"#f06292":"#4db6ac",Q="on"===this._hass?.states["binary_sensor.sem_ev_connected"]?.state;let ee="",te="#8DC892";"on"===this._hass?.states["binary_sensor.sem_ev_charging"]?.state||c>10?(ee=this._t("charging"),te="#8DC892"):Q&&(ee=this._t("connected")||"Verbunden",te="rgba(141,200,146,0.6)");const se=this._showEv?this._evSocLabel(this._hass):"",ie=this._t("today"),re=`${ie} ${this._rawOrMissing("daily_solar_energy")} kWh`,ae=this._val("forecast_today_kwh"),ne=ae>0?`☀ ${ae.toFixed(1)} kWh ${this._t("forecast")||"Forecast"}`:"",oe=`${ie} ${this._rawOrMissing("daily_ev_energy")} kWh`,le=`${ie} ${Te(this._val("daily_home_energy"),1)} kWh`,ce=`+${Te(this._val("daily_battery_charge_energy"),1)} / -${Te(this._val("daily_battery_discharge_energy"),1)} kWh`,_e=`↓${Te(this._val("daily_grid_import_energy"),1)} / ↑${Te(this._val("daily_grid_export_energy"),1)} kWh`,de=function(e,t,s={}){const{decimals:i=0,space:r=!1}=s;if(null==e||""===e||isNaN(parseFloat(e)))return"";const a=t||"°C";return`${parseFloat(e).toFixed(i)}${r?" ":""}${a}`}(this._valStr("inverter_temperature"),this._unitStr("inverter_temperature")),pe=this._valStr("charging_state"),he=t?22:30,fe=pe.length>he?pe.substring(0,he-1)+"…":pe,be=[],ve=["solar_power","grid_import_power","grid_export_power"];this._showBattery&&ve.push("battery_power","battery_soc"),this._showEv&&ve.push("ev_power");for(const e of ve){const t=this._eid(e);t?Fe(this._hass?.states[t])&&be.push(e):"prefix"===this._mode&&be.push(e)}const ke=new Set(be),Se=ke.has("solar_power"),Ce=this._hass?.states,Me="entities"===this._mode&&this._entities?.grid?.entity?Ie(Ce,[this._entities.grid.entity]):Ie(Ce,[this._eid("grid_import_power"),this._eid("grid_export_power")]),ze=ke.has("ev_power"),De=this._powerSnapshot()?.home_w,Ne=null==De&&Fe(this._hass?.states[this._eid("home_consumption_power")])&&(Se||Me),Ee=ue(Se?null:this._solarDisp??0),Ae=(g>10?"↑ ":f>10?"↓ ":"")+ue(this._battDisp??0),Be=ue(Ne?null:this._homeDisp??0),Le=ue(ze?null:this._evDisp??0),Re=Me?ue(null):this._gridDispMode?`${this._gridDispMode} ${ue(this._gridDisp??0)}`:ue(0),We="↑"===this._gridDispMode?"#8353d1":"#488fc2",Pe=xe(this._hass,this._prefix),Ue=n>10,je=Math.abs(_)>10,qe=_<0,Ke=g>10?"#f06292":"#4db6ac",He=o>10||l>10,Ge=o>l,Ve=l>o?"#8353d1":"#488fc2",Ye=y>10,Ze=c>10;return j` - ${Ue.length>=2?H` + ${Pe.length>=2?j`
    - ${Ue.map(e=>H` + ${Pe.map(e=>j`
    `)}
    - `:G} + `:H} @@ -6560,7 +7271,7 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow - ${null!=this._sunPtX&&o>50?this._sparkWavePath(e):G} + ${null!=this._sunPtX&&n>50?this._sparkWavePath(e):H} @@ -6572,10 +7283,10 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow stroke-width="2" stroke-dasharray="4,8"/> ${C} + fill="${D}" opacity="${N}">${C} ${z} + fill="${D}" opacity="${N}">${M} @@ -6589,13 +7300,13 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow fill="rgba(255,255,255,0.35)"/> - - ${x.k>.03?W`.03?q``:G} + stroke-width="4"/>`:H} @@ -6604,7 +7315,7 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow fill="none" stroke="rgba(200,220,255,0.5)" stroke-width="1"/> - + @@ -6614,10 +7325,10 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow ${o>10?ue(o):""} + fill="#FCD170" opacity="0.7" font-weight="600">${n>10?ue(n):""} - ${null!=this._sunPtX&&o>50?this._renderSunSpark(o):G} + ${null!=this._sunPtX&&n>50?this._renderSunSpark(n):H} @@ -6626,29 +7337,29 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow stroke-dasharray="5,7" opacity="0.22"/> - ${this._showBattery?W``:G} + ${this._showBattery?q``:H} - ${this._showEv?W``:G} + ${this._showEv?q``:H} - - ${this._renderFlow(!1,"#ff9800",me(o),e.paths.solar,2)} + + ${this._renderFlow(!1,"#ff9800",ge(n),e.paths.solar,2)} - ${this._showBattery?W` - ${this._renderFlow(We,je,me(d),e.paths.battery,3)} - `:G} - - ${this._renderFlow(Ke,Ve,me(n||l),e.paths.grid,3)} + ${this._showBattery?q` + ${this._renderFlow(qe,Ke,ge(_),e.paths.battery,3)} + `:H} + + ${this._renderFlow(Ge,Ve,ge(o||l),e.paths.grid,3)} - ${this._renderFlow(!1,"#5BC8D8",me(v),e.paths.home,2)} + ${this._renderFlow(!1,"#5BC8D8",ge(y),e.paths.home,2)} - ${this._showEv?W` - ${this._renderFlow(!1,"#8DC892",me(c),e.paths.ev,3)} - `:G} + ${this._showEv?q` + ${this._renderFlow(!1,"#8DC892",ge(c),e.paths.ev,3)} + `:H} this._showMoreInfo("solar_power")}> @@ -6660,7 +7371,7 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow this._showMoreInfo("solar_power")} text-anchor="middle" font-family="${s}" font-size="${r}" - font-weight="800" fill="#ff9800">${Ne} + font-weight="800" fill="#ff9800">${Ee} this._showMoreInfo("daily_solar_energy")} text-anchor="middle" font-family="${s}" font-size="${a+1}" @@ -6668,7 +7379,7 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow this._showMoreInfo("forecast_today_kwh")} text-anchor="middle" font-family="${s}" font-size="${a}" - fill="${N}" opacity="${F}" font-weight="500">${oe} + fill="${E}" opacity="${F}" font-weight="500">${ne} @@ -6676,10 +7387,10 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow ${pe} + fill="${A}" opacity="${T}" font-weight="600">${de} ${fe} + fill="${A}" opacity="${O}">${fe} - ${this._showBattery?W`this._showMoreInfo("battery_soc")}> - ${this._illustrationBattery(e.B.cx,e.B.cy,e.B.r,P,U,q,j,g.label)} + ${this._illustrationBattery(e.B.cx,e.B.cy,e.B.r,W,P,U,K,m.label)} this._showMoreInfo("battery_power")} text-anchor="middle" font-family="${s}" font-size="${r}" font-weight="800" fill="${Z}" - opacity="${u?.4:1}">${u?"— W":Te} + opacity="${u?.4:1}">${u?"— W":Ae} ${u?this._t("sensor_unavailable"):X} + fill="${J}" opacity="0.6">${u?this._t("sensor_unavailable"):X} this._showMoreInfo("daily_battery_charge_energy")} text-anchor="middle" font-family="${s}" font-size="${a+1}" - fill="#4db6ac" opacity="0.6" font-weight="600">${ce}`:G} + fill="#4db6ac" opacity="0.6" font-weight="600">${ce}`:H} this._showMoreInfo("grid_import_power")}> @@ -6719,18 +7430,18 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow ${this._t("grid")} + fill="${G}" letter-spacing="0.5">${this._t("grid")} this._showMoreInfo("grid_power")} text-anchor="middle" font-family="${s}" font-size="${r}" - font-weight="800" fill="${Pe}">${Le} + font-weight="800" fill="${We}">${Re} ${V} this._showMoreInfo("daily_grid_import_energy")} text-anchor="middle" font-family="${s}" font-size="${a+1}" - fill="#488fc2" opacity="0.6" font-weight="600">${de} + fill="#488fc2" opacity="0.6" font-weight="600">${_e} this._showMoreInfo("home_consumption_power")}> @@ -6742,14 +7453,14 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow this._showMoreInfo("home_consumption_power")} text-anchor="middle" font-family="${s}" font-size="${r}" - font-weight="800" fill="#5BC8D8">${Re} + font-weight="800" fill="#5BC8D8">${Be} this._showMoreInfo("daily_home_energy")} text-anchor="middle" font-family="${s}" font-size="${a+1}" fill="#5BC8D8" opacity="0.6" font-weight="600">${le} - ${this._showEv?W`this._showMoreInfo("ev_power")}> + ${this._showEv?q`this._showMoreInfo("ev_power")}> ${this._illustrationEV(e.E.cx,e.E.cy,e.E.r)} this._showMoreInfo("ev_power")} text-anchor="middle" font-family="${s}" font-size="${r}" - font-weight="800" fill="#8DC892">${Ie} + font-weight="800" fill="#8DC892">${Le} ${ee}${se?W`${ee?" · ":""}${se}`:G} + fill="${te}" fill-opacity="0.6">${ee}${se?q`${ee?" · ":""}${se}`:H} this._showMoreInfo("daily_ev_energy")} text-anchor="middle" font-family="${s}" font-size="${a+1}" - fill="#8DC892" opacity="0.6" font-weight="600">${ne}`:G} + fill="#8DC892" opacity="0.6" font-weight="600">${oe}`:H} - ${t?G:this._renderDeviceStrip(e.H)} + ${t?H:this._renderDeviceStrip(e.H)} - ${ye.length>0?H` + ${be.length>0?j`
    ⚠ ${ye.length} ${this._t("sensor_unavailable")}
    + style="font-family:'Segoe UI','Roboto',sans-serif;font-size:11px;color:#ef5350;opacity:0.75;white-space:nowrap">⚠ ${be.length} ${this._t("sensor_unavailable")}
    - `:G} + `:H} SEM
    - ${t?this._renderDeviceChips():G} + ${t?this._renderDeviceChips():H} - `}_illustrationSolarPanel(e,t,s){const i=s/50,r=86*i,a=58*i,o=e-r/2,n=t-a/2-4*i,l=r/3,c=a/2,d=6*i,p=12*i;return W` + `}_illustrationSolarPanel(e,t,s){const i=s/50,r=86*i,a=58*i,n=e-r/2,o=t-a/2-4*i,l=r/3,c=a/2,_=6*i,d=12*i;return q` - - - - - - - - - - - - - - - - - ${[0,1,2].map(e=>W` - - + + + + + + + + + + + ${[0,1,2].map(e=>q` + + `)} - - + + - - - - `}_sunRays(e,t,s){const i=[];for(let r=0;r<8;r++){const a=45*r*Math.PI/180,o=s+3,n=s+.7*s,l=.18,c=a-l,d=a+l,p=e+Math.cos(c)*o,_=t+Math.sin(c)*o,h=e+Math.cos(a)*n,u=t+Math.sin(a)*n,g=e+Math.cos(d)*o,m=t+Math.sin(d)*o;i.push(W``)}return i}_illustrationInverter(e,t,s){const i=s/20,r=38*i,a=28*i,o=e-r/2,n=t-a/2;return W` - `)}return i}_illustrationInverter(e,t,s){const i=s/20,r=38*i,a=28*i,n=e-r/2,o=t-a/2;return q` + - ${[0,1,2].map(e=>W` - q` + `)} AC - - - `}_illustrationBattery(e,t,s,i,r,a,o,n){const l=s/50,c=40*l,d=64*l,p=e-c/2,_=t-d/2,h=8*l,u=16*l,g=c-8*l,m=d-12*l,f=p+4*l,v=_+8*l;return W` + `}_illustrationBattery(e,t,s,i,r,a,n,o){const l=s/50,c=40*l,_=64*l,d=e-c/2,p=t-_/2,h=8*l,u=16*l,m=c-8*l,g=_-12*l,f=d+4*l,y=p+8*l;return q` - - - - - - ${[1,2,3,4].map(e=>W` - q` + `)} - ${n} + stroke="rgba(0,0,0,0.6)" stroke-width="${1.5*l}" paint-order="stroke">${o} - ${o?W` + ${n?q` - `:G} + `:H} - - `}_illustrationHouse(e,t,s){const i=s/62,r=80*i,a=52*i,o=e-r/2,n=t-a/2+8*i,l=n-38*i,c=e+18*i,d=l+8*i,p=10*i,_=18*i,h=16*i,u=14*i,g=n+12*i,m=o+10*i,f=o+r-10*i-h,v=14*i,y=24*i,b=e-v/2,x=n+a-y;return W` + `}_illustrationHouse(e,t,s){const i=s/62,r=80*i,a=52*i,n=e-r/2,o=t-a/2+8*i,l=o-38*i,c=e+18*i,_=l+8*i,d=10*i,p=18*i,h=16*i,u=14*i,m=o+12*i,g=n+10*i,f=n+r-10*i-h,y=14*i,b=24*i,v=e-y/2,x=o+a-b;return q` - - - - - - - - - - - - - - - - - - - `}_illustrationGrid(e,t,s){const i=s/50,r=72*i,a=4*i,o=t-r/2+4*i,n=o+.22*r,l=o+.42*r,c=44*i,d=32*i,p=3*i;return W` + `}_illustrationGrid(e,t,s){const i=s/50,r=72*i,a=4*i,n=t-r/2+4*i,o=n+.22*r,l=n+.42*r,c=44*i,_=32*i,d=3*i;return q` - - - - - - - - - - - - - - - `}_illustrationEV(e,t,s){const i=s/44,r=22*i,a=36*i,o=e-28*i,n=t-a/2,l=t+4*i,c=52*i,d=22*i,p=e+18*i-c/2,_=6*i;return W` + `}_illustrationEV(e,t,s){const i=s/44,r=22*i,a=36*i,n=e-28*i,o=t-a/2,l=t+4*i,c=52*i,_=22*i,d=e+18*i-c/2,p=6*i;return q` - - - AC - - - - - - - - - - - - - - `}_renderFlow(e,t,s,i,r){const a=s.toFixed(1),o=String(e?28:-28),n=e?{kp:"1;0",kt:"0;1"}:null,l=[];for(let e=0;e + keyPoints=${o?o.kp:H} + keyTimes=${o?o.kt:H} + begin="${n}"/> + keyPoints=${o?o.kp:H} + keyTimes=${o?o.kt:H} + begin="${n}"/> + keyPoints=${o?o.kp:H} + keyTimes=${o?o.kt:H} + begin="${n}"/> - `)}return W` + `)}return q` - - - ${l} - `}_sparkWavePath(e){const t=this._sunPtX,s=this._sunPtY,i=e.S.cx-t,r=e.S.cy-e.S.r-s,a=Math.sqrt(i*i+r*r),o=Math.max(2,Math.round(a/30)),n=this._val("solar_power",0),l=6+Math.min(8,n/1e3),c=Math.atan2(r,i),d=-Math.sin(c),p=Math.cos(c),_=8*o;let h=`M${t.toFixed(1)},${s.toFixed(1)}`;for(let e=1;e<=_;e++){const a=e/_,n=t+i*a,c=s+r*a,u=Math.sin(a*o*Math.PI*2)*l;h+=` L${(n+d*u).toFixed(1)},${(c+p*u).toFixed(1)}`}return this._sparkWaveD=h,W``}_renderSunSpark(e){const t=12-7*Math.min(1,e/1e4),s=3+Math.floor(Math.min(3,e/3e3)),i=`${t.toFixed(0)}:${s}`;if(this._sunSparkSig!==i){this._sunSparkSig=i,this._sunSparkProps=[];for(let e=0;e"gold"===e.kind?W` + filter="url(#glowSun)"/>`}_renderSunSpark(e){const t=12-7*Math.min(1,e/1e4),s=3+Math.floor(Math.min(3,e/3e3)),i=`${t.toFixed(0)}:${s}`;if(this._sunSparkSig!==i){this._sunSparkSig=i,this._sunSparkProps=[];for(let e=0;e"gold"===e.kind?q` - `:W` + `:q` - `)}_collectDevices(){const e=this._eid("controllable_devices_count"),t=e?this._hass?.states[e]:void 0;return t?.attributes?.devices?Object.entries(t.attributes.devices).filter(([,e])=>"ev_charger"!==e.device_type&&"battery"!==e.device_type).map(([e,t])=>{const s=t.power_entity?this._hass.states[t.power_entity]:null,i=s?Te(s):t.current_power||0;return{id:e,...t,power:i,name:this._localizedDeviceName(e,t)}}).sort((e,t)=>t.power-e.power).slice(0,3):[]}_localizedDeviceName(e,t){const s={heat_pump:"device_heat_pump",hot_water:"device_hot_water"},i={heat_pump:"Heat Pump",hot_water:"Hot Water"}[e];if(i&&(t.name||"").trim()===i){const t=this._t(s[e]);if(t)return t}return t.name||e}_renderDeviceChips(){const e=this._collectDevices();return e.length?H` + `)}_collectDevices(){const e=this._eid("controllable_devices_count"),t=e?this._hass?.states[e]:void 0;return t?.attributes?.devices?Object.entries(t.attributes.devices).filter(([,e])=>"ev_charger"!==e.device_type&&"battery"!==e.device_type).map(([e,t])=>{const s=t.power_entity?this._hass.states[t.power_entity]:null,i=s?Ae(s):t.current_power||0;return{id:e,...t,power:i,name:this._localizedDeviceName(e,t)}}).sort((e,t)=>t.power-e.power).slice(0,3):[]}_localizedDeviceName(e,t){const s={heat_pump:"device_heat_pump",hot_water:"device_hot_water"},i={heat_pump:"Heat Pump",hot_water:"Hot Water"}[e];if(i&&(t.name||"").trim()===i){const t=this._t(s[e]);if(t)return t}return t.name||e}_renderDeviceChips(){const e=this._collectDevices();return e.length?j`
    - ${e.map((e,t)=>{const s=he,i=e.is_on||e.power>5;let r=e.name||e.id;return r.length>14&&(r=r.substring(0,13)+"…"),H` + ${e.map((e,t)=>{const s=he,i=e.is_on||e.power>5;let r=e.name||e.id;return r.length>14&&(r=r.substring(0,13)+"…"),j`
    e.power_entity&&this._fireMoreInfo(e.power_entity)} style="flex:1;display:flex;flex-direction:column;align-items:center;gap:1px; @@ -7125,37 +7836,37 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow
    ${i?ue(e.power):this._t("off")}
    `})} -
    `:G}_renderDeviceStrip(e){const t=this._collectDevices();if(!t.length)return G;const s="'Segoe UI','Roboto',sans-serif";return t.map((t,i)=>{let r=(t.name||t.id).replace(/&/g,"&").replace(//g,">");r.length>22&&(r=r.substring(0,21)+"…");const a=he,o=t.is_on||t.power>5,n=310+50*i,l=540,c=`M${e.cx+e.r},${e.cy+10+8*i} C${e.cx+e.r+40},${e.cy+30+15*i} 510,${n-10} 524,${n}`,d=t.power>5?W` +
    `:H}_renderDeviceStrip(e){const t=this._collectDevices();if(!t.length)return H;const s="'Segoe UI','Roboto',sans-serif";return t.map((t,i)=>{let r=(t.name||t.id).replace(/&/g,"&").replace(//g,">");r.length>22&&(r=r.substring(0,21)+"…");const a=he,n=t.is_on||t.power>5,o=310+50*i,l=540,c=`M${e.cx+e.r},${e.cy+10+8*i} C${e.cx+e.r+40},${e.cy+30+15*i} 510,${o-10} 524,${o}`,_=t.power>5?q` - + - `:G;return W` + `:H;return q` - ${d} - - + stroke-dasharray="3,5" opacity="${n?.3:.1}"/> + ${_} + + ${this._deviceIcon(t.device_type,t.name||t.id)} - ${r} - ${ue(t.power)} - `})}_glowFilter(e,t,s){return W` + ${ue(t.power)} + `})}_glowFilter(e,t,s){return q` - `}_showMoreInfo(e){const t=this._eid(e);t&&this.dispatchEvent(new CustomEvent("hass-more-info",{bubbles:!0,composed:!0,detail:{entityId:t}}))}_getLayout(){return this._compact?{vb:"0 0 400 680",S:{cx:200,cy:100,r:44,labelY:154},I:{cx:200,cy:232,r:20},B:{cx:100,cy:360,r:44,labelY:414},G:{cx:300,cy:360,r:44,labelY:414},H:{cx:200,cy:540,r:48,labelY:598},E:{cx:84,cy:555,r:34,labelY:598},paths:{solar:"M200,144 L200,212",battery:"M188,242 C155,278 120,315 100,316",grid:"M212,242 C245,278 280,315 300,316",home:"M200,252 C200,390 200,460 200,492",ev:"M200,252 C200,400 140,490 84,521"},sunArc:"M30,68 Q200,6 370,68",sunX:200,sunY:20,sunR:12,sunRisingX:40,sunSettingX:360,sunLabelY:76,starX:[60,120,280,330,180],starY:[20,12,18,24,8],font:{label:11,value:15,sub:10},statusY:660,wmX:390,wmY:676}:{vb:"0 0 700 510",S:{cx:85,cy:140,r:48,labelY:200},I:{cx:220,cy:200,r:20},E:{cx:150,cy:390,r:42,labelY:442},B:{cx:340,cy:390,r:48,labelY:448},H:{cx:400,cy:230,r:52,labelY:292},G:{cx:570,cy:140,r:46,labelY:198},paths:{solar:"M118,148 C158,165 192,188 208,200",home:"M242,198 C300,200 350,210 360,218",battery:"M228,215 C260,280 320,340 338,342",grid:"M232,188 C320,130 450,110 524,130",ev:"M210,215 C180,275 162,330 150,348"},sunArc:"M20,52 Q330,2 640,52",sunX:330,sunY:12,sunR:12,sunRisingX:28,sunSettingX:632,sunLabelY:60,starX:[100,200,450,550,330],starY:[20,12,15,28,8],font:{label:11,value:16,sub:10},statusY:495,wmX:690,wmY:506}}_deviceIcon(e,t){const s=(t||"").toLowerCase();return"ev_charger"===(e||"").toLowerCase()||s.includes("keba")||s.includes("charger")||s.includes("wallbox")?W` + `}_showMoreInfo(e){const t=this._eid(e);t&&this.dispatchEvent(new CustomEvent("hass-more-info",{bubbles:!0,composed:!0,detail:{entityId:t}}))}_getLayout(){return this._compact?{vb:"0 0 400 680",S:{cx:200,cy:100,r:44,labelY:154},I:{cx:200,cy:232,r:20},B:{cx:100,cy:360,r:44,labelY:414},G:{cx:300,cy:360,r:44,labelY:414},H:{cx:200,cy:540,r:48,labelY:598},E:{cx:84,cy:555,r:34,labelY:598},paths:{solar:"M200,144 L200,212",battery:"M188,242 C155,278 120,315 100,316",grid:"M212,242 C245,278 280,315 300,316",home:"M200,252 C200,390 200,460 200,492",ev:"M200,252 C200,400 140,490 84,521"},sunArc:"M30,68 Q200,6 370,68",sunX:200,sunY:20,sunR:12,sunRisingX:40,sunSettingX:360,sunLabelY:76,starX:[60,120,280,330,180],starY:[20,12,18,24,8],font:{label:11,value:15,sub:10},statusY:660,wmX:390,wmY:676}:{vb:"0 0 700 510",S:{cx:85,cy:140,r:48,labelY:200},I:{cx:220,cy:200,r:20},E:{cx:150,cy:390,r:42,labelY:442},B:{cx:340,cy:390,r:48,labelY:448},H:{cx:400,cy:230,r:52,labelY:292},G:{cx:570,cy:140,r:46,labelY:198},paths:{solar:"M118,148 C158,165 192,188 208,200",home:"M242,198 C300,200 350,210 360,218",battery:"M228,215 C260,280 320,340 338,342",grid:"M232,188 C320,130 450,110 524,130",ev:"M210,215 C180,275 162,330 150,348"},sunArc:"M20,52 Q330,2 640,52",sunX:330,sunY:12,sunR:12,sunRisingX:28,sunSettingX:632,sunLabelY:60,starX:[100,200,450,550,330],starY:[20,12,15,28,8],font:{label:11,value:16,sub:10},statusY:495,wmX:690,wmY:506}}_deviceIcon(e,t){const s=(t||"").toLowerCase();return"ev_charger"===(e||"").toLowerCase()||s.includes("keba")||s.includes("charger")||s.includes("wallbox")?q` - `:s.includes("heiz")||s.includes("heat")||s.includes("warm")||s.includes("boiler")?W` + `:s.includes("heiz")||s.includes("heat")||s.includes("warm")||s.includes("boiler")?q` - `:s.includes("wash")||s.includes("spül")||s.includes("geschirr")||s.includes("wasch")?W``:s.includes("pool")||s.includes("pump")?W``:s.includes("klima")||s.includes("ac")||s.includes("cool")||s.includes("air")?W` + `:s.includes("wash")||s.includes("spül")||s.includes("geschirr")||s.includes("wasch")?q``:s.includes("pool")||s.includes("pump")?q``:s.includes("klima")||s.includes("ac")||s.includes("cool")||s.includes("air")?q` - `:s.includes("light")||s.includes("licht")||s.includes("lamp")?W` - `:W``}getCardSize(){return 7}static getStubConfig(){return{entity_prefix:"sensor.sem_"}}},{type:"sem-system-diagram-card",name:"SEM System Diagram",description:"Power flow visualization with inline SVG illustrations for each energy component",documentationURL:"https://github.com/traktore-org/sem-community/blob/develop/docs/DASHBOARD_GUIDE.md#sem-system-diagram-card"});const sr=["/local/custom_components/solar_energy_management/dashboard/card/vendor/chart.umd.min.js","https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js"],ir=["/local/custom_components/solar_energy_management/dashboard/card/vendor/chartjs-adapter-date-fns.bundle.min.js","https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns@3.0.0/dist/chartjs-adapter-date-fns.bundle.min.js"];function rr(e,t,s){if(!e.length)return void s(new Error("all sources failed"));const i=document.createElement("script");i.src=e[0],i.onload=()=>t(),i.onerror=()=>rr(e.slice(1),t,s),document.head.appendChild(i)}let ar=null;const or=pe||{solar:"#ff9800",gridImport:"#488fc2",gridExport:"#8353d1",batteryOut:"#4db6ac",home:"#5BC8D8",ev:"#8DC892"},nr={costs:{title:"energy_costs",y_label:"_currency_",stacked:!1,daily:[{suffix:"daily_costs",name:"Import",color:or.gridImport,type:"bar",negate:!0},{suffix:"daily_export_revenue",name:"export",color:or.gridExport,type:"bar"},{suffix:"daily_net_cost",name:"net",color:or.solar,type:"line",negate:!0}],monthly:[{suffix:"monthly_costs",name:"Import",color:or.gridImport,type:"bar",negate:!0},{suffix:"monthly_export_revenue",name:"export",color:or.gridExport,type:"bar"},{suffix:"monthly_net_cost",name:"net",color:or.solar,type:"line",negate:!0}]},savings:{title:"energy_savings",y_label:"_currency_",stacked:!0,daily:[{suffix:"daily_savings",name:"solar_savings",color:or.solar,type:"area"},{suffix:"daily_battery_savings",name:"battery_savings",color:or.batteryOut,type:"area"}],monthly:[{suffix:"monthly_savings",name:"solar_savings",color:or.solar,type:"area"},{suffix:"monthly_battery_savings",name:"battery_savings",color:or.batteryOut,type:"area"}]},energy:{title:"energy_balance",y_label:"kWh",stacked:!1,defaultPeriod:"7d",daily:[{suffix:"daily_solar_energy",name:"solar",color:or.solar,type:"bar"},{suffix:"daily_home_energy",name:"home",color:or.home,type:"bar"},{suffix:"daily_grid_import_energy",name:"grid_import",color:or.gridImport,type:"bar"},{suffix:"daily_grid_export_energy",name:"grid_export",color:or.gridExport,type:"bar"}],monthly:[{suffix:"monthly_solar_energy",name:"solar",color:or.solar,type:"bar"},{suffix:"monthly_home_energy",name:"home",color:or.home,type:"bar"},{suffix:"monthly_grid_import_energy",name:"grid_import",color:or.gridImport,type:"bar"},{suffix:"monthly_grid_export_energy",name:"grid_export",color:or.gridExport,type:"bar"}]},power:{title:"power_flow",y_label:"W",stacked:!1,hourly:[{suffix:"solar_power",name:"solar",color:or.solar,type:"area"},{suffix:"home_consumption_power",name:"home",color:or.home,type:"area"},{suffix:"grid_power",name:"grid",color:or.gridImport,type:"area"}]},forecast:{title:"Forecast vs Actual",y_label:"W",stacked:!1,hourly:[{suffix:"forecast_power_now_w",name:"forecast",color:or.solar,type:"area"},{suffix:"solar_power",name:"actual",color:or.batteryOut,type:"line"}]},battery:{title:"battery",y_label:"W",y2_label:"%",stacked:!1,hourly:[{suffix:"battery_power",name:"power",color:or.batteryOut,type:"area"},{suffix:"battery_soc",name:"soc",color:"#ff9800",type:"line",y_axis:1}]},ev:{title:"ev_charging",y_label:"W",stacked:!0,defaultPeriod:"today",y_min:0,y_suggested_max:2e3,hourly:[{suffix:"flow_solar_to_ev_power",name:"solar",color:or.solar,type:"area"},{suffix:"flow_battery_to_ev_power",name:"battery",color:or.batteryOut,type:"area"},{suffix:"flow_grid_to_ev_power",name:"grid",color:or.gridImport,type:"area"}],daily:[{suffix:"daily_ev_energy",name:"ev_energy",color:or.ev,type:"bar"}],monthly:[{suffix:"monthly_ev_energy",name:"ev_energy",color:or.ev,type:"bar"}]}};ke("sem-chart-card",class extends Le{constructor(){super(),this._chart=null,this._period=null,this._fetchTimer=null,this._cachedChartTheme=null,this._boundPeriodHandler=e=>this._onPeriodChange(e.detail),this._prefix="sensor.sem_",this._preset=null,this._emptyMsg="",this._fetchSeq=0}setConfig(e){if(!e.preset&&!e.series)throw new Error("sem-chart-card requires either preset or series config");this._config=e,this._prefix=e.entity_prefix||"sensor.sem_",this._preset=e.preset?nr[e.preset]:null,this.requestUpdate()}set hass(e){this._hass=e;const t=e?.language;if(t!==this._lang)return this._lang=t,void this.requestUpdate();this._period||this._setDefaultPeriod()}get hass(){return this._hass}connectedCallback(){super.connectedCallback(),document.addEventListener("sem-period-change",this._boundPeriodHandler),this._rollInterval=setInterval(()=>this._rollRelativePeriod(),3e5),this._boundVisibility=()=>{document.hidden||this._rollRelativePeriod()},document.addEventListener("visibilitychange",this._boundVisibility),this._period&&this._redraw()}disconnectedCallback(){super.disconnectedCallback(),document.removeEventListener("sem-period-change",this._boundPeriodHandler),this._boundVisibility&&document.removeEventListener("visibilitychange",this._boundVisibility),clearInterval(this._rollInterval),this._chart&&(this._chart.destroy(),this._chart=null),clearTimeout(this._fetchTimer),this._fetchSeq++}_redraw(){this._period&&(this._period.key===this._defaultKey()?this._setDefaultPeriod():this._onPeriodChange(this._period))}_rollRelativePeriod(){this._period&&this._hass&&this._period.key===this._defaultKey()&&this._setDefaultPeriod()}firstUpdated(){this._period||this._setDefaultPeriod()}static get styles(){return[Ie,a` + `:s.includes("light")||s.includes("licht")||s.includes("lamp")?q` + `:q``}getCardSize(){return 7}static getStubConfig(){return{entity_prefix:"sensor.sem_"}}},{type:"sem-system-diagram-card",name:"SEM System Diagram",description:"Power flow visualization with inline SVG illustrations for each energy component",documentationURL:"https://github.com/traktore-org/sem-community/blob/develop/docs/DASHBOARD_GUIDE.md#sem-system-diagram-card"});const pc=["/local/custom_components/solar_energy_management/dashboard/card/vendor/chart.umd.min.js","https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js"],hc=["/local/custom_components/solar_energy_management/dashboard/card/vendor/chartjs-adapter-date-fns.bundle.min.js","https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns@3.0.0/dist/chartjs-adapter-date-fns.bundle.min.js"];function uc(e,t,s){if(!e.length)return void s(new Error("all sources failed"));const i=document.createElement("script");i.src=e[0],i.onload=()=>t(),i.onerror=()=>uc(e.slice(1),t,s),document.head.appendChild(i)}let mc=null;const gc=de||{solar:"#ff9800",gridImport:"#488fc2",gridExport:"#8353d1",batteryOut:"#4db6ac",home:"#5BC8D8",ev:"#8DC892"},fc={costs:{title:"energy_costs",y_label:"_currency_",stacked:!1,daily:[{suffix:"daily_costs",name:"Import",color:gc.gridImport,type:"bar",negate:!0},{suffix:"daily_export_revenue",name:"export",color:gc.gridExport,type:"bar"},{suffix:"daily_net_cost",name:"net",color:gc.solar,type:"line",negate:!0}],monthly:[{suffix:"monthly_costs",name:"Import",color:gc.gridImport,type:"bar",negate:!0},{suffix:"monthly_export_revenue",name:"export",color:gc.gridExport,type:"bar"},{suffix:"monthly_net_cost",name:"net",color:gc.solar,type:"line",negate:!0}]},savings:{title:"energy_savings",y_label:"_currency_",stacked:!0,daily:[{suffix:"daily_savings",name:"solar_savings",color:gc.solar,type:"area"},{suffix:"daily_battery_savings",name:"battery_savings",color:gc.batteryOut,type:"area"}],monthly:[{suffix:"monthly_savings",name:"solar_savings",color:gc.solar,type:"area"},{suffix:"monthly_battery_savings",name:"battery_savings",color:gc.batteryOut,type:"area"}]},energy:{title:"energy_balance",y_label:"kWh",stacked:!1,defaultPeriod:"7d",daily:[{suffix:"daily_solar_energy",name:"solar",color:gc.solar,type:"bar"},{suffix:"daily_home_energy",name:"home",color:gc.home,type:"bar"},{suffix:"daily_grid_import_energy",name:"grid_import",color:gc.gridImport,type:"bar"},{suffix:"daily_grid_export_energy",name:"grid_export",color:gc.gridExport,type:"bar"}],monthly:[{suffix:"monthly_solar_energy",name:"solar",color:gc.solar,type:"bar"},{suffix:"monthly_home_energy",name:"home",color:gc.home,type:"bar"},{suffix:"monthly_grid_import_energy",name:"grid_import",color:gc.gridImport,type:"bar"},{suffix:"monthly_grid_export_energy",name:"grid_export",color:gc.gridExport,type:"bar"}]},power:{title:"power_flow",y_label:"W",stacked:!1,hourly:[{suffix:"solar_power",name:"solar",color:gc.solar,type:"area"},{suffix:"home_consumption_power",name:"home",color:gc.home,type:"area"},{suffix:"grid_power",name:"grid",color:gc.gridImport,type:"area"}]},forecast:{title:"Forecast vs Actual",y_label:"W",stacked:!1,hourly:[{suffix:"forecast_power_now_w",name:"forecast",color:gc.solar,type:"area"},{suffix:"solar_power",name:"actual",color:gc.batteryOut,type:"line"}]},battery:{title:"battery",y_label:"W",y2_label:"%",stacked:!1,hourly:[{suffix:"battery_power",name:"power",color:gc.batteryOut,type:"area"},{suffix:"battery_soc",name:"soc",color:"#ff9800",type:"line",y_axis:1}]},ev:{title:"ev_charging",y_label:"W",stacked:!0,defaultPeriod:"today",y_min:0,y_suggested_max:2e3,hourly:[{suffix:"flow_solar_to_ev_power",name:"solar",color:gc.solar,type:"area"},{suffix:"flow_battery_to_ev_power",name:"battery",color:gc.batteryOut,type:"area"},{suffix:"flow_grid_to_ev_power",name:"grid",color:gc.gridImport,type:"area"}],daily:[{suffix:"daily_ev_energy",name:"ev_energy",color:gc.ev,type:"bar"}],monthly:[{suffix:"monthly_ev_energy",name:"ev_energy",color:gc.ev,type:"bar"}]}};function yc(e,t){if(t.power_entity&&e){const t=Ae(e);return null==t?null:t/1e3}return(t.current_power||0)/1e3}ke("sem-chart-card",class extends Re{constructor(){super(),this._chart=null,this._period=null,this._fetchTimer=null,this._cachedChartTheme=null,this._boundPeriodHandler=e=>this._onPeriodChange(e.detail),this._prefix="sensor.sem_",this._preset=null,this._emptyMsg="",this._fetchSeq=0}setConfig(e){if(!e.preset&&!e.series)throw new Error("sem-chart-card requires either preset or series config");this._config=e,this._prefix=e.entity_prefix||"sensor.sem_",this._preset=e.preset?fc[e.preset]:null,this.requestUpdate()}set hass(e){this._hass=e;const t=e?.language;if(t!==this._lang)return this._lang=t,void this.requestUpdate();this._period||this._setDefaultPeriod()}get hass(){return this._hass}connectedCallback(){super.connectedCallback(),document.addEventListener("sem-period-change",this._boundPeriodHandler),this._rollInterval=setInterval(()=>this._rollRelativePeriod(),3e5),this._boundVisibility=()=>{document.hidden||this._rollRelativePeriod()},document.addEventListener("visibilitychange",this._boundVisibility),this._period&&this._redraw()}disconnectedCallback(){super.disconnectedCallback(),document.removeEventListener("sem-period-change",this._boundPeriodHandler),this._boundVisibility&&document.removeEventListener("visibilitychange",this._boundVisibility),clearInterval(this._rollInterval),this._chart&&(this._chart.destroy(),this._chart=null),clearTimeout(this._fetchTimer),this._fetchSeq++}_redraw(){this._period&&(this._period.key===this._defaultKey()?this._setDefaultPeriod():this._onPeriodChange(this._period))}_rollRelativePeriod(){this._period&&this._hass&&this._period.key===this._defaultKey()&&this._setDefaultPeriod()}firstUpdated(){this._period||this._setDefaultPeriod()}static get styles(){return[Le,a` :host { display: block; } .sem-chart-wrap { padding: 16px; @@ -7194,7 +7905,7 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow font-family: 'Segoe UI','Roboto',sans-serif; } .empty-msg.visible { display: flex; } - `]}render(){const e=this._preset||{},t=this._config?.title?this._t(this._config.title):this._t(e.title||"SEM Chart"),s=this._period?.labelKey?this._t(this._period.labelKey):"";return H` + `]}render(){const e=this._preset||{},t=this._config?.title?this._t(this._config.title):this._t(e.title||"SEM Chart"),s=this._period?.labelKey?this._t(this._period.labelKey):"";return j`
    @@ -7207,7 +7918,7 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow
    - `}_startOfDayInHaTz(e){return function(e,t){if(!t)return new Date(e.getFullYear(),e.getMonth(),e.getDate());try{const s=new Intl.DateTimeFormat("en-US",{timeZone:t,year:"numeric",month:"2-digit",day:"2-digit"}),i=Object.fromEntries(s.formatToParts(e).map(e=>[e.type,e.value])),r=new Date(Number(i.year),Number(i.month)-1,Number(i.day),0,0,0,0),a=new Intl.DateTimeFormat("en-US",{timeZone:t,hour:"2-digit",minute:"2-digit",hour12:!1}).formatToParts(r),o=Number(a.find(e=>"hour"===e.type).value),n=Number(a.find(e=>"minute"===e.type).value),l=(60*o+n)%1440,c=0===l?0:l<=720?-l:1440-l;return new Date(r.getTime()+60*c*1e3)}catch(t){return new Date(e.getFullYear(),e.getMonth(),e.getDate())}}(e,this._hass?.config?.time_zone)}_defaultKey(){const e=this._preset,t=this._config?.default_period||e&&e.defaultPeriod;return e&&("today"===t||"24h"===t||e.hourly&&!e.daily)?"today"===t?"today":"24h":"7d"===t?"7d":"week"}_setDefaultPeriod(){const e=new Date,t=this._defaultKey();if("today"===t||"24h"===t){const s="today"===t,i=s?this._startOfDayInHaTz(e):new Date(e.getTime()-864e5),r=s?"period_today":"last_24h";this._onPeriodChange({start:i,end:e,granularity:"hour",labelKey:r,key:t})}else if("7d"===t){const t=this._startOfDayInHaTz(e);t.setDate(t.getDate()-6),this._onPeriodChange({start:t,end:e,granularity:"day",labelKey:"last_7_days",key:"7d"})}else{const t=e.getDay()||7,s=this._startOfDayInHaTz(e);s.setDate(s.getDate()-(t-1)),this._onPeriodChange({start:s,end:e,granularity:"day",labelKey:"period_this_week",key:"week"})}}_onPeriodChange(e){this._period={...e,labelKey:e.labelKey||{today:"period_today",yesterday:"period_yesterday",week:"period_this_week",month:"period_this_month",year:"period_this_year","24h":"last_24h","7d":"last_7_days"}[e.key]},this.requestUpdate(),clearTimeout(this._fetchTimer),this._fetchTimer=setTimeout(()=>this._fetchAndRender(),150)}_resolveSeries(){if(this._config?.series)return this._config.series.map(e=>({entity:e.entity,name:e.name||e.entity,color:e.color||"#42A5F5",type:e.type||"bar",y_axis:e.y_axis||0}));const e=this._preset;if(!e)return[];const t=this._period?.granularity||"day";if(this._config?.per_charger&&"ev"===this._config?.preset&&"hour"===t)return this._discoverPerChargerSeries();const s="hour"===t&&e.hourly?e.hourly:"month"===t&&e.monthly?e.monthly:e.daily||e.hourly||[],i=s===e.daily||s===e.monthly;return s.map(e=>({entity:`${this._prefix}${e.suffix}`,name:this._t(e.name),color:e.color,type:e.type,y_axis:e.y_axis||0,negate:e.negate||!1,cumulative:i}))}_discoverPerChargerSeries(){const e=this._hass?.states||{},t=["#8DC892","#64B5F6","#FFB74D","#BA68C8","#4DB6AC","#F06292","#A1887F","#7986CB"],s=/^sensor\.sem_charger_(.+)_power$/,i=[];for(const t of Object.keys(e)){const r=t.match(s);if(!r)continue;const a=r[1],o=e[t]?.attributes?.charger_name||e[t]?.attributes?.friendly_name?.replace(/^SEM\s+/i,"").replace(/\s+Power$/i,"")||a.replace(/_/g," ");i.push({id:a,eid:t,friendly:o})}return i.sort((e,t)=>e.id.localeCompare(t.id)),i.map((e,s)=>({entity:e.eid,name:e.friendly,color:t[s%t.length],type:"area",y_axis:0}))}async _fetchAndRender(){if(!this._hass||!this._period||!this.isConnected)return;const e=this._resolveSeries();if(!e.length)return;const{start:t,end:s,granularity:i}=this._period,r=++this._fetchSeq,a=()=>r!==this._fetchSeq||!this.isConnected;let o;try{o=await this._fetchStatistics(e,t.toISOString(),s.toISOString(),i)}catch(e){if(a())return;return console.debug("sem-chart-card: fetch error",e),void this._showEmpty(this._t("data_unavailable"))}a()||(o&&!o.every(e=>!e.data.length)?(this._hideEmpty(),await this.updateComplete,a()||await this._renderChart(o,e,a)):this._showEmpty(this._t("no_data_for_period")))}async _fetchStatistics(e,t,s,i){const r=e.map(e=>e.entity),a="month"===i?"month":"hour"===i?"hour":"day",o={power:"W",energy:"kWh"};let n;try{n=await this._hass.callWS({type:"recorder/statistics_during_period",start_time:t,end_time:s,statistic_ids:r,period:a,types:["state","mean","max"],units:o})}catch{n=await this._hass.callWS({type:"history/statistics_during_period",start_time:t,end_time:s,statistic_ids:r,period:a,units:o})}const l=(this._config?.stacked??this._preset?.stacked??!1)&&"hour"===a;return e.map(e=>({data:(n[e.entity]||[]).map(t=>{let s=l?t.mean??t.state??0:t.max??t.state??t.mean??0;return e.negate&&(s=-s),{x:new Date(t.start),y:s}})}))}async _renderChart(e,t,s=()=>!1){const i=await(ar||(ar=new Promise((e,t)=>{const s=()=>rr(ir,()=>e(window.Chart),()=>e(window.Chart));if(window.Chart)return(()=>{try{return new window.Chart._adapters._date({}).formats(),!0}catch{return!1}})()?void e(window.Chart):void s();rr(sr,s,()=>t(new Error("Failed to load Chart.js")))}),ar));if(s())return;const r=this.renderRoot.querySelector("canvas");if(!r)return;const a=this._theme(),o=this._preset||{},n=this._config?.stacked??o.stacked??!1;let l=this._config?.y_label||o.y_label||"";"_currency_"===l&&(l=fe(this._hass));const c=o.y2_label||"",d=t.some(e=>1===e.y_axis),p=this._period?.granularity||"day",_=r.getContext("2d"),h=t.map((t,s)=>{const i="area"===t.type,r="bar"===t.type;return{label:t.name,data:e[s].data,cumulative:!!t.cumulative,backgroundColor:r?t.color+"CC":i?t.color+"30":"transparent",borderColor:t.color,borderWidth:r?0:2,fill:!!i&&(n&&s>0?"-1":"origin"),type:r?"bar":"line",tension:.4,pointRadius:0,pointHitRadius:10,pointHoverRadius:4,pointHoverBorderWidth:2,pointHoverBackgroundColor:t.color,pointHoverBorderColor:"#fff",yAxisID:1===t.y_axis?"y1":"y",order:r?2:1,borderRadius:r?4:0,barPercentage:.7}}),u="hour"===p?"hour":"month"===p?"month":"day",g={id:"crosshair",afterDraw(e){if(e.tooltip?._active?.length){const t=e.tooltip._active[0].element.x,s=e.scales.y,i=e.ctx;i.save(),i.beginPath(),i.moveTo(t,s.top),i.lineTo(t,s.bottom),i.lineWidth=1,i.strokeStyle="rgba(255,255,255,0.15)",i.stroke(),i.restore()}}},m={type:"bar",data:{datasets:h},plugins:[g,{id:"gradientFill",beforeDatasetsDraw(e){const s=e.ctx;e.data.datasets.forEach((i,r)=>{if("area"!==t[r]?.type)return;if(e.getDatasetMeta(r).hidden)return;const a=e.scales[i.yAxisID||"y"];if(!a)return;const o=s.createLinearGradient(0,a.top,0,a.bottom);o.addColorStop(0,i.borderColor+"60"),o.addColorStop(.6,i.borderColor+"18"),o.addColorStop(1,i.borderColor+"02"),i.backgroundColor=o})}}],options:{responsive:!0,maintainAspectRatio:!1,animation:{duration:300,easing:"easeOutQuart"},interaction:{mode:"index",intersect:!1},plugins:{legend:{display:!0,position:"bottom",labels:{color:a.textSec||"#9e9e9e",font:{size:11,weight:"500",family:"'Segoe UI','Roboto',sans-serif"},boxWidth:12,boxHeight:12,borderRadius:3,useBorderRadius:!0,padding:14,generateLabels:e=>function(e,t,s){return e.map(e=>{const i=t[e.datasetIndex];if(!i)return e;const r=i.cumulative?i.data.reduce((e,t)=>e+(t?.y??0),0):i.data[i.data.length-1]?.y??0,a="y1"===i.yAxisID?"%":s||"",o=Math.abs(r),n=o>=1e3?(r/1e3).toFixed(1)+"k":o<10?r.toFixed(1):r.toFixed(0);return e.text=`${e.text}: ${n} ${a}`,e})}(i.defaults.plugins.legend.labels.generateLabels(e),e.data.datasets,l)}},tooltip:{backgroundColor:a.tooltipBg||"rgba(15,18,25,0.94)",titleColor:a.tooltipText||"#e0e0e0",titleFont:{family:"'Segoe UI','Roboto',sans-serif",weight:"600",size:12},bodyColor:a.textSec||"#b0b0b0",bodyFont:{family:"'Segoe UI','Roboto',sans-serif",size:11},borderColor:a.tooltipBorder||"rgba(255,255,255,0.08)",borderWidth:1,cornerRadius:10,padding:{top:10,bottom:10,left:14,right:14},bodySpacing:6,displayColors:!0,boxPadding:4,callbacks:{title:e=>{if(!e||!e.length)return"";const t=new Date(e[0].parsed.x);if(isNaN(t))return"";const s=this._hass?.language||"en",i=this._hass?.config?.time_zone||void 0;try{return"hour"===p?t.toLocaleTimeString(s,{hour:"2-digit",minute:"2-digit",timeZone:i}):"month"===p?t.toLocaleDateString(s,{month:"short",year:"numeric",timeZone:i}):t.toLocaleDateString(s,{day:"numeric",month:"short",timeZone:i})}catch(e){return"hour"===p?t.toLocaleTimeString(s):t.toLocaleDateString(s)}},label:e=>{const t=e.parsed.y,s="y1"===e.dataset.yAxisID?"%":l,i=Math.abs(t),r=i>=1e3?(t/1e3).toFixed(1)+"k":i<10?t.toFixed(2):t.toFixed(1);return` ${e.dataset.label}: ${r} ${s}`}}}},scales:{x:{type:"time",min:this._period.start.toISOString(),max:this._period.end.toISOString(),time:{unit:u,tooltipFormat:"hour"===p?"HH:mm":"month"===p?"MMM yyyy":"dd MMM",displayFormats:{hour:"HH:mm",day:"dd MMM",month:"MMM"}},grid:{color:"rgba(255,255,255,0.02)",drawBorder:!1,drawTicks:!1},ticks:{color:a.textSec||"#757575",font:{size:10,family:"'Segoe UI','Roboto',sans-serif"},maxRotation:0,padding:6,callback:(e,t,s)=>{const i=s[t];if(!i)return e;const r=new Date(i.value);if(isNaN(r))return e;const a=this._hass?.language||"en",o=this._hass?.config?.time_zone||void 0;try{return"hour"===u?r.toLocaleTimeString(a,{hour:"2-digit",minute:"2-digit",timeZone:o}):"month"===u?r.toLocaleDateString(a,{month:"short",timeZone:o}):r.toLocaleDateString(a,{day:"numeric",month:"short",timeZone:o})}catch(e){return"hour"===u?r.toLocaleTimeString(a,{hour:"2-digit",minute:"2-digit"}):r.toLocaleDateString(a,{day:"numeric",month:"short"})}}},stacked:n},y:{position:"left",grid:{color:"rgba(255,255,255,0.04)",drawBorder:!1,drawTicks:!1},ticks:{color:a.textSec||"#757575",font:{size:10,family:"'Segoe UI','Roboto',sans-serif"},padding:8,callback:e=>{const t=Math.abs(e);return t>=1e3?(e/1e3).toFixed(1)+"k":t<.01&&t>0?"":e%1==0?e:e.toFixed(1)}},title:{display:!!l,text:l,color:a.textSec||"#757575",font:{size:11,family:"'Segoe UI','Roboto',sans-serif"}},stacked:n,beginAtZero:"W"!==l||0===this._preset?.y_min,...null!=this._preset?.y_min?{min:this._preset.y_min}:{},...null!=this._preset?.y_suggested_max?{suggestedMax:this._preset.y_suggested_max}:{}}}}};d&&(m.options.scales.y1={position:"right",grid:{drawOnChartArea:!1,drawTicks:!1},ticks:{color:"#ff9800",font:{size:10},padding:8,callback:e=>e+"%"},title:{display:!!c,text:c,color:"#ff9800",font:{size:11}},min:0,max:100}),this._chart&&(this._chart.destroy(),this._chart=null),this._chart=new i(_,m)}_showEmpty(e){this._emptyMsg=e,this.requestUpdate()}_hideEmpty(){this._emptyMsg&&(this._emptyMsg="",this.requestUpdate())}getCardSize(){return 5}static getStubConfig(){return{preset:"costs"}}},{type:"sem-chart-card",name:"SEM Chart",description:"Period-reactive chart with glassmorphism styling and built-in presets",documentationURL:"https://github.com/traktore-org/sem-community/blob/develop/docs/DASHBOARD_GUIDE.md#sem-chart-card"});const lr={heat:"comfort_hvac_heat",cool:"comfort_hvac_cool",by_forecast:"comfort_hvac_by_forecast"};const{I:cr}=oe,dr=e=>e,pr=()=>document.createComment(""),_r=(e,t,s)=>{const i=e._$AA.parentNode,r=void 0===t?e._$AB:t._$AA;if(void 0===s){const t=i.insertBefore(pr(),r),a=i.insertBefore(pr(),r);s=new cr(t,a,e,e.options)}else{const t=s._$AB.nextSibling,a=s._$AM,o=a!==e;if(o){let t;s._$AQ?.(e),s._$AM=e,void 0!==s._$AP&&(t=e._$AU)!==a._$AU&&s._$AP(t)}if(t!==r||o){let e=s._$AA;for(;e!==t;){const t=dr(e).nextSibling;dr(i).insertBefore(e,r),e=t}}}return s},hr=(e,t,s=e)=>(e._$AI(t,s),e),ur={},gr=(e,t=ur)=>e._$AH=t,mr=e=>{e._$AR(),e._$AA.remove()},fr=(e,t,s)=>{const i=new Map;for(let r=t;r<=s;r++)i.set(e[r],r);return i},vr=Ui(class extends qi{constructor(e){if(super(e),e.type!==Pi)throw Error("repeat() can only be used in text expressions")}dt(e,t,s){let i;void 0===s?s=t:void 0!==t&&(i=t);const r=[],a=[];let o=0;for(const t of e)r[o]=i?i(t,o):o,a[o]=s(t,o),o++;return{values:a,keys:r}}render(e,t,s){return this.dt(e,t,s).values}update(e,[t,s,i]){const r=(e=>e._$AH)(e),{values:a,keys:o}=this.dt(t,s,i);if(!Array.isArray(r))return this.ut=o,a;const n=this.ut??=[],l=[];let c,d,p=0,_=r.length-1,h=0,u=a.length-1;for(;p<=_&&h<=u;)if(null===r[p])p++;else if(null===r[_])_--;else if(n[p]===o[h])l[h]=hr(r[p],a[h]),p++,h++;else if(n[_]===o[u])l[u]=hr(r[_],a[u]),_--,u--;else if(n[p]===o[u])l[u]=hr(r[p],a[u]),_r(e,l[u+1],r[p]),p++,u--;else if(n[_]===o[h])l[h]=hr(r[_],a[h]),_r(e,r[p],r[_]),_--,h++;else if(void 0===c&&(c=fr(o,h,u),d=fr(n,p,_)),c.has(n[p]))if(c.has(n[_])){const t=d.get(o[h]),s=void 0!==t?r[t]:null;if(null===s){const t=_r(e,r[p]);hr(t,a[h]),l[h]=t}else l[h]=hr(s,a[h]),_r(e,r[p],s),r[t]=null;h++}else mr(r[_]),_--;else mr(r[p]),p++;for(;h<=u;){const t=_r(e,l[u+1]);hr(t,a[h]),l[h++]=t}for(;p<=_;){const e=r[p++];null!==e&&mr(e)}return this.ut=o,gr(e,l),j}});function yr(e){const t=[],s=new Set,i=r=>{var a;s.has(r.id)||(t.push(r),s.add(r.id),(a=r.id,e.filter(e=>(e.dependsOn||[]).includes(a)&&!s.has(e.id))).forEach(i))};return e.filter(e=>!(e.dependsOn||[]).length).forEach(i),e.forEach(e=>{s.has(e.id)||t.push(e)}),t}const br=["switch","current","input_boolean","service"],xr={switch:["switch"],current:["number","input_number"],input_boolean:["input_boolean"]};function $r(e){return String(e??"").replace(/&/g,"&").replace(/"/g,""").replace(//g,">")}const wr="width:100%;padding:8px;margin:6px 0 12px;border-radius:6px;border:1px solid rgba(255,255,255,0.1);background:rgba(0,0,0,0.2);color:inherit;box-sizing:border-box";const kr=e=>{if(null==e||""===e||"boolean"==typeof e)return null;const t=Number(e);return Number.isFinite(t)?t:null};function Sr(e){if(null==e)return null;const t=String(e).split(",").map(e=>e.trim()).filter(e=>""!==e);if(t.length<2||t.length>3)return null;const s=[];for(const e of t){const t=e.split(":");if(2!==t.length)return null;const i=kr(t[0].trim()),r=kr(t[1].trim());if(null===i||null===r||r<=0)return null;s.push({c:i,cop:r})}s.sort((e,t)=>e.c-t.c);for(let e=1;eString(Math.round(10*e)/10);function zr(e){const t=function(e){const t=(e||[]).filter(e=>null!==kr(e?.c)&&null!==kr(e?.cop));return t.length?[...t].sort((e,t)=>kr(e.c)-kr(t.c)).map(e=>`${Cr(kr(e.c))}:${Cr(kr(e.cop))}`).join(", "):""}(e);return""===t?"":Sr(t)?t:null}const Mr=["flat","curve","learned"];function Dr(e){const t=e||{},s=String(t.cop_source||"");return Mr.includes(s)?s:Sr(t.cop_curve)?"curve":"flat"}function Er(e,t){if(t.power_entity&&e){const t=Te(e);return null==t?null:t/1e3}return(t.current_power||0)/1e3}const Nr="https://github.com/traktore-org/sem-community/blob/develop/docs/ENERGY_PLANNER.md#comfort-banking",Fr=["controllable_devices_count","consecutive_peak_15min","load_management_status","target_peak_limit","grid_power","heat_pump_cost_of_heat"];ke("sem-load-priority-card",class extends Le{constructor(){super(),this.devices=[],this.targetPeakLimit=null,this.peakLimitUnlimited=!1,this.currentPeak=null,this.gridImportKw=null,this.slotAllowedW=null,this.slotUsedKwh=null,this.loadManagementStatus="normal",this._sortable=null,this._interacting=!1,this._lastDeviceSig="",this._showHelp=!1,this._goalOpen={},this._goalDrag=null}setConfig(e){this._config=e,this._prefix=e.entity_prefix||"sensor.sem_",this.requestUpdate()}set hass(e){this._hass,this._hass=e;const t=e?.language;if(t!==this._lang)return this._lang=t,this._lastDeviceSig="",this._lastReads=this._readStates(e),this._updateDeviceData(),void this.requestUpdate();if(this._interacting)return;if(this._isFrozen())return;if(this._priorityFrozenUntil&&Date.now()e===this._lastReads[t])||(this._lastReads=s,this._updateDeviceData(),this.requestUpdate())}_readStates(e){return Fr.map(t=>e?.states?.[`${this._prefix}${t}`])}get hass(){return this._hass}disconnectedCallback(){super.disconnectedCallback(),this._drag&&(window.removeEventListener("pointermove",this._dragMoveBound),window.removeEventListener("pointerup",this._dragEndBound),window.removeEventListener("pointercancel",this._dragEndBound),this._drag=null,this._interacting=!1)}async firstUpdated(){this._bindEvents(),this._ensureEntityPicker()}async updated(e){const t=this.devices.map(e=>e.id).join(",");t!==this._lastDeviceSig&&(this._lastDeviceSig=t,this._bindEvents())}static get styles(){return a` + `}_startOfDayInHaTz(e){return Ua(e,this._hass?.config?.time_zone)}_defaultKey(){const e=this._preset,t=this._config?.default_period||e&&e.defaultPeriod;return e&&("today"===t||"24h"===t||e.hourly&&!e.daily)?"today"===t?"today":"24h":"7d"===t?"7d":"week"}_setDefaultPeriod(){const e=new Date,t=this._defaultKey();if("today"===t||"24h"===t){const s="today"===t,i=s?this._startOfDayInHaTz(e):new Date(e.getTime()-864e5),r=s?"period_today":"last_24h";this._onPeriodChange({start:i,end:e,granularity:"hour",labelKey:r,key:t})}else if("7d"===t){const t=this._startOfDayInHaTz(e);t.setDate(t.getDate()-6),this._onPeriodChange({start:t,end:e,granularity:"day",labelKey:"last_7_days",key:"7d"})}else{const t=e.getDay()||7,s=this._startOfDayInHaTz(e);s.setDate(s.getDate()-(t-1)),this._onPeriodChange({start:s,end:e,granularity:"day",labelKey:"period_this_week",key:"week"})}}_onPeriodChange(e){this._period={...e,labelKey:e.labelKey||{today:"period_today",yesterday:"period_yesterday",week:"period_this_week",month:"period_this_month",year:"period_this_year","24h":"last_24h","7d":"last_7_days"}[e.key]},this.requestUpdate(),clearTimeout(this._fetchTimer),this._fetchTimer=setTimeout(()=>this._fetchAndRender(),150)}_resolveSeries(){if(this._config?.series)return this._config.series.map(e=>({entity:e.entity,name:e.name||e.entity,color:e.color||"#42A5F5",type:e.type||"bar",y_axis:e.y_axis||0}));const e=this._preset;if(!e)return[];const t=this._period?.granularity||"day";if(this._config?.per_charger&&"ev"===this._config?.preset&&"hour"===t)return this._discoverPerChargerSeries();const s="hour"===t&&e.hourly?e.hourly:"month"===t&&e.monthly?e.monthly:e.daily||e.hourly||[],i=s===e.daily||s===e.monthly;return s.map(e=>({entity:`${this._prefix}${e.suffix}`,name:this._t(e.name),color:e.color,type:e.type,y_axis:e.y_axis||0,negate:e.negate||!1,cumulative:i}))}_discoverPerChargerSeries(){const e=this._hass?.states||{},t=["#8DC892","#64B5F6","#FFB74D","#BA68C8","#4DB6AC","#F06292","#A1887F","#7986CB"],s=/^sensor\.sem_charger_(.+)_power$/,i=[];for(const t of Object.keys(e)){const r=t.match(s);if(!r)continue;const a=r[1],n=ls(e,a);i.push({id:a,eid:t,friendly:n})}return i.sort((e,t)=>e.id.localeCompare(t.id)),i.map((e,s)=>({entity:e.eid,name:e.friendly,color:t[s%t.length],type:"area",y_axis:0}))}async _fetchAndRender(){if(!this._hass||!this._period||!this.isConnected)return;const e=this._resolveSeries();if(!e.length)return;const{start:t,end:s,granularity:i}=this._period,r=++this._fetchSeq,a=()=>r!==this._fetchSeq||!this.isConnected;let n;try{n=await this._fetchStatistics(e,t.toISOString(),s.toISOString(),i)}catch(e){if(a())return;return console.debug("sem-chart-card: fetch error",e),void this._showEmpty(this._t("data_unavailable"))}a()||(n&&!n.every(e=>!e.data.length)?(this._hideEmpty(),await this.updateComplete,a()||await this._renderChart(n,e,a)):this._showEmpty(this._t("no_data_for_period")))}async _fetchStatistics(e,t,s,i){const r=e.map(e=>e.entity),a="month"===i?"month":"hour"===i?"hour":"day",n={power:"W",energy:"kWh"};let o;try{o=await this._hass.callWS({type:"recorder/statistics_during_period",start_time:t,end_time:s,statistic_ids:r,period:a,types:["state","mean","max"],units:n})}catch{o=await this._hass.callWS({type:"history/statistics_during_period",start_time:t,end_time:s,statistic_ids:r,period:a,units:n})}const l=(this._config?.stacked??this._preset?.stacked??!1)&&"hour"===a;return e.map(e=>({data:(o[e.entity]||[]).map(t=>{let s=l?t.mean??t.state??0:t.max??t.state??t.mean??0;return e.negate&&(s=-s),{x:new Date(t.start),y:s}})}))}async _renderChart(e,t,s=()=>!1){const i=await(mc||(mc=new Promise((e,t)=>{const s=()=>uc(hc,()=>e(window.Chart),()=>e(window.Chart));if(window.Chart)return(()=>{try{return new window.Chart._adapters._date({}).formats(),!0}catch{return!1}})()?void e(window.Chart):void s();uc(pc,s,()=>t(new Error("Failed to load Chart.js")))}),mc));if(s())return;const r=this.renderRoot.querySelector("canvas");if(!r)return;const a=this._theme(),n=this._preset||{},o=this._config?.stacked??n.stacked??!1;let l=this._config?.y_label||n.y_label||"";"_currency_"===l&&(l=fe(this._hass));const c=n.y2_label||"",_=t.some(e=>1===e.y_axis),d=this._period?.granularity||"day",p=r.getContext("2d"),h=t.map((t,s)=>{const i="area"===t.type,r="bar"===t.type;return{label:t.name,data:e[s].data,cumulative:!!t.cumulative,backgroundColor:r?t.color+"CC":i?t.color+"30":"transparent",borderColor:t.color,borderWidth:r?0:2,fill:!!i&&(o&&s>0?"-1":"origin"),type:r?"bar":"line",tension:.4,pointRadius:0,pointHitRadius:10,pointHoverRadius:4,pointHoverBorderWidth:2,pointHoverBackgroundColor:t.color,pointHoverBorderColor:"#fff",yAxisID:1===t.y_axis?"y1":"y",order:r?2:1,borderRadius:r?4:0,barPercentage:.7}}),u="hour"===d?"hour":"month"===d?"month":"day",m={id:"crosshair",afterDraw(e){if(e.tooltip?._active?.length){const t=e.tooltip._active[0].element.x,s=e.scales.y,i=e.ctx;i.save(),i.beginPath(),i.moveTo(t,s.top),i.lineTo(t,s.bottom),i.lineWidth=1,i.strokeStyle="rgba(255,255,255,0.15)",i.stroke(),i.restore()}}},g={type:"bar",data:{datasets:h},plugins:[m,{id:"gradientFill",beforeDatasetsDraw(e){const s=e.ctx;e.data.datasets.forEach((i,r)=>{if("area"!==t[r]?.type)return;if(e.getDatasetMeta(r).hidden)return;const a=e.scales[i.yAxisID||"y"];if(!a)return;const n=s.createLinearGradient(0,a.top,0,a.bottom);n.addColorStop(0,i.borderColor+"60"),n.addColorStop(.6,i.borderColor+"18"),n.addColorStop(1,i.borderColor+"02"),i.backgroundColor=n})}}],options:{responsive:!0,maintainAspectRatio:!1,animation:{duration:300,easing:"easeOutQuart"},interaction:{mode:"index",intersect:!1},plugins:{legend:{display:!0,position:"bottom",labels:{color:a.textSec||"#9e9e9e",font:{size:11,weight:"500",family:"'Segoe UI','Roboto',sans-serif"},boxWidth:12,boxHeight:12,borderRadius:3,useBorderRadius:!0,padding:14,generateLabels:e=>function(e,t,s){return e.map(e=>{const i=t[e.datasetIndex];if(!i)return e;const r=i.cumulative?i.data.reduce((e,t)=>e+(t?.y??0),0):i.data[i.data.length-1]?.y??0,a="y1"===i.yAxisID?"%":s||"",n=Math.abs(r),o=n>=1e3?(r/1e3).toFixed(1)+"k":n<10?r.toFixed(1):r.toFixed(0);return e.text=`${e.text}: ${o} ${a}`,e})}(i.defaults.plugins.legend.labels.generateLabels(e),e.data.datasets,l)}},tooltip:{backgroundColor:a.tooltipBg||"rgba(15,18,25,0.94)",titleColor:a.tooltipText||"#e0e0e0",titleFont:{family:"'Segoe UI','Roboto',sans-serif",weight:"600",size:12},bodyColor:a.textSec||"#b0b0b0",bodyFont:{family:"'Segoe UI','Roboto',sans-serif",size:11},borderColor:a.tooltipBorder||"rgba(255,255,255,0.08)",borderWidth:1,cornerRadius:10,padding:{top:10,bottom:10,left:14,right:14},bodySpacing:6,displayColors:!0,boxPadding:4,callbacks:{title:e=>{if(!e||!e.length)return"";const t=new Date(e[0].parsed.x);if(isNaN(t))return"";const s=this._hass?.language||"en",i=this._hass?.config?.time_zone||void 0;try{return"hour"===d?t.toLocaleTimeString(s,{hour:"2-digit",minute:"2-digit",timeZone:i}):"month"===d?t.toLocaleDateString(s,{month:"short",year:"numeric",timeZone:i}):t.toLocaleDateString(s,{day:"numeric",month:"short",timeZone:i})}catch(e){return"hour"===d?t.toLocaleTimeString(s):t.toLocaleDateString(s)}},label:e=>{const t=e.parsed.y,s="y1"===e.dataset.yAxisID?"%":l,i=Math.abs(t),r=i>=1e3?(t/1e3).toFixed(1)+"k":i<10?t.toFixed(2):t.toFixed(1);return` ${e.dataset.label}: ${r} ${s}`}}}},scales:{x:{type:"time",min:this._period.start.toISOString(),max:this._period.end.toISOString(),time:{unit:u,tooltipFormat:"hour"===d?"HH:mm":"month"===d?"MMM yyyy":"dd MMM",displayFormats:{hour:"HH:mm",day:"dd MMM",month:"MMM"}},grid:{color:"rgba(255,255,255,0.02)",drawBorder:!1,drawTicks:!1},ticks:{color:a.textSec||"#757575",font:{size:10,family:"'Segoe UI','Roboto',sans-serif"},maxRotation:0,padding:6,callback:(e,t,s)=>{const i=s[t];if(!i)return e;const r=new Date(i.value);if(isNaN(r))return e;const a=this._hass?.language||"en",n=this._hass?.config?.time_zone||void 0;try{return"hour"===u?r.toLocaleTimeString(a,{hour:"2-digit",minute:"2-digit",timeZone:n}):"month"===u?r.toLocaleDateString(a,{month:"short",timeZone:n}):r.toLocaleDateString(a,{day:"numeric",month:"short",timeZone:n})}catch(e){return"hour"===u?r.toLocaleTimeString(a,{hour:"2-digit",minute:"2-digit"}):r.toLocaleDateString(a,{day:"numeric",month:"short"})}}},stacked:o},y:{position:"left",grid:{color:"rgba(255,255,255,0.04)",drawBorder:!1,drawTicks:!1},ticks:{color:a.textSec||"#757575",font:{size:10,family:"'Segoe UI','Roboto',sans-serif"},padding:8,callback:e=>{const t=Math.abs(e);return t>=1e3?(e/1e3).toFixed(1)+"k":t<.01&&t>0?"":e%1==0?e:e.toFixed(1)}},title:{display:!!l,text:l,color:a.textSec||"#757575",font:{size:11,family:"'Segoe UI','Roboto',sans-serif"}},stacked:o,beginAtZero:"W"!==l||0===this._preset?.y_min,...null!=this._preset?.y_min?{min:this._preset.y_min}:{},...null!=this._preset?.y_suggested_max?{suggestedMax:this._preset.y_suggested_max}:{}}}}};_&&(g.options.scales.y1={position:"right",grid:{drawOnChartArea:!1,drawTicks:!1},ticks:{color:"#ff9800",font:{size:10},padding:8,callback:e=>e+"%"},title:{display:!!c,text:c,color:"#ff9800",font:{size:11}},min:0,max:100}),this._chart&&(this._chart.destroy(),this._chart=null),this._chart=new i(p,g)}_showEmpty(e){this._emptyMsg=e,this.requestUpdate()}_hideEmpty(){this._emptyMsg&&(this._emptyMsg="",this.requestUpdate())}getCardSize(){return 5}static getStubConfig(){return{preset:"costs"}}},{type:"sem-chart-card",name:"SEM Chart",description:"Period-reactive chart with glassmorphism styling and built-in presets",documentationURL:"https://github.com/traktore-org/sem-community/blob/develop/docs/DASHBOARD_GUIDE.md#sem-chart-card"});const bc="https://github.com/traktore-org/sem-community/blob/develop/docs/ENERGY_PLANNER.md#comfort-banking",vc=["controllable_devices_count","consecutive_peak_15min","load_management_status","target_peak_limit","grid_power","heat_pump_cost_of_heat"];ke("sem-load-priority-card",class extends Re{constructor(){super(),this.devices=[],this.targetPeakLimit=null,this.peakLimitUnlimited=!1,this.currentPeak=null,this.gridImportKw=null,this.slotAllowedW=null,this.slotUsedKwh=null,this.loadManagementStatus="normal",this._sortable=null,this._interacting=!1,this._lastDeviceSig="",this._showHelp=!1,this._goalOpen={},this._goalDrag=null}setConfig(e){this._config=e,this._prefix=e.entity_prefix||"sensor.sem_",this.requestUpdate()}set hass(e){this._hass,this._hass=e;const t=e?.language;if(t!==this._lang)return this._lang=t,this._lastDeviceSig="",this._lastReads=this._readStates(e),this._updateDeviceData(),void this.requestUpdate();if(this._interacting)return;if(this._isFrozen())return;if(this._priorityFrozenUntil&&Date.now()e===this._lastReads[t])||(this._lastReads=s,this._updateDeviceData(),this.requestUpdate())}_readStates(e){return vc.map(t=>e?.states?.[`${this._prefix}${t}`])}get hass(){return this._hass}disconnectedCallback(){super.disconnectedCallback(),this._drag&&(window.removeEventListener("pointermove",this._dragMoveBound),window.removeEventListener("pointerup",this._dragEndBound),window.removeEventListener("pointercancel",this._dragEndBound),this._drag=null,this._interacting=!1)}async firstUpdated(){this._bindEvents(),this._ensureEntityPicker()}async updated(e){const t=this.devices.map(e=>e.id).join(",");t!==this._lastDeviceSig&&(this._lastDeviceSig=t,this._bindEvents())}static get styles(){return a` /* (#913 follow-up) a refused service call, in the modal's error voice */ .svc-error { margin: 6px 0 8px; padding: 6px 10px; border-radius: 6px; background: rgba(244, 67, 54, .12); color: #f44336; @@ -7447,16 +8158,16 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow .ov-opt:last-child { border-right:none; } .ov-opt.on { background:#4db6ac; color:#fff; font-weight:600; } .empty { text-align:center; padding:20px 0; opacity:0.4; font-size:1em; } - `}_updateDeviceData(){if(!this._hass)return;const e=this._hass.states[`${this._prefix}target_peak_limit`],t=this._hass.states[`${this._prefix}consecutive_peak_15min`],s=this._hass.states[`${this._prefix}load_management_status`],i=this._hass.states[`${this._prefix}controllable_devices_count`];e&&(this.targetPeakLimit=Te(e)),e&&(this.peakLimitUnlimited=e.attributes?.peak_limit_unlimited||!1);const r=this._peakHold;if(r){this.peakLimitUnlimited===r.unlimited&&(r.unlimited||Math.abs(this.targetPeakLimit-r.kw)<.05)||Date.now()>=r.until?this._peakHold=null:(this.targetPeakLimit=r.kw,this.peakLimitUnlimited=r.unlimited)}t&&(this.currentPeak=Te(t)),e&&(this.slotAllowedW=e.attributes?.peak_slot_allowed_w??null,this.slotUsedKwh=e.attributes?.peak_slot_used_kwh??null);const a=this._hass.states[`${this._prefix}grid_power`];this.gridImportKw=a?function(e){const t="number"==typeof e?e:parseFloat(e);return Number.isFinite(t)?Math.max(0,-t)/1e3:null}(a.state):null,s&&(this.loadManagementStatus=s.state||"normal"),i?.attributes?.devices&&(this.devices=Object.entries(i.attributes.devices).map(([e,t])=>({id:e,name:t.name||e.replace(/^(load_device_|energy_dashboard_)/,"").replace(/_/g," "),power:Er(this._hass.states[t.power_entity],t),rating:t.power_rating||0,priority:t.priority||5,isOn:t.is_on||!1,isShed:t.is_shed||!1,shedReason:t.shed_reason||null,shedPhase:t.shed_phase||null,phase:t.goals&&t.goals.phase||t.phase||"unknown",behindRelay:!(!t.goals||!t.goals.behind_operator_relay),lockedByOperator:!0===t.locked_by_operator,phaseMeasured:!0===t.phase_measured,hasControlHandle:void 0!==t.has_control_handle?!1!==t.has_control_handle:!1!==t.is_controllable,isControllable:void 0!==t.user_hands_off?!0!==t.user_hands_off:!1!==t.is_controllable,isCritical:t.is_critical||!1,deviceType:t.device_type||"unknown",isAvailable:t.is_available||!1,hasManualMapping:t.has_manual_mapping||!1,energySensor:t.energy_sensor||"",control:t.control||null,controlEntity:t.control?.entity||t.switch_entity||"",controlType:t.control?.type||"switch",controlMode:t.control_mode||"peak_only",dependsOn:t.depends_on||[],goals:t.goals||null,progress:t.progress||null,comfort:t.comfort||null,intent:t.intent&&"object"==typeof t.intent?t.intent:null,blockedBy:t.blocked_by||null,soc:t.soc,icon:this._resolveDeviceIcon(t)})).sort((e,t)=>e.priority-t.priority))}render(){if(!this._config)return G;const e=this.peakLimitUnlimited,t=this._getPeakColor(),s=null==this.targetPeakLimit||null==this.currentPeak?null:this.targetPeakLimit-this.currentPeak,i=e||null==this.currentPeak?0:this.targetPeakLimit>0?Math.min(this.currentPeak/this.targetPeakLimit*100,100):0,r=e?null:qe({targetKw:this.targetPeakLimit,usedKwh:this.slotUsedKwh,allowedW:this.slotAllowedW});return H` + `}_updateDeviceData(){if(!this._hass)return;const e=this._hass.states[`${this._prefix}target_peak_limit`],t=this._hass.states[`${this._prefix}consecutive_peak_15min`],s=this._hass.states[`${this._prefix}load_management_status`],i=this._hass.states[`${this._prefix}controllable_devices_count`];e&&(this.targetPeakLimit=Ae(e)),e&&(this.peakLimitUnlimited=e.attributes?.peak_limit_unlimited||!1);const r=this._peakHold;if(r){this.peakLimitUnlimited===r.unlimited&&(r.unlimited||Math.abs(this.targetPeakLimit-r.kw)<.05)||Date.now()>=r.until?this._peakHold=null:(this.targetPeakLimit=r.kw,this.peakLimitUnlimited=r.unlimited)}t&&(this.currentPeak=Ae(t)),e&&(this.slotAllowedW=e.attributes?.peak_slot_allowed_w??null,this.slotUsedKwh=e.attributes?.peak_slot_used_kwh??null);const a=this._hass.states[`${this._prefix}grid_power`];this.gridImportKw=a?function(e){const t="number"==typeof e?e:parseFloat(e);return Number.isFinite(t)?Math.max(0,-t)/1e3:null}(a.state):null,s&&(this.loadManagementStatus=s.state||"normal"),i?.attributes?.devices&&(this.devices=Object.entries(i.attributes.devices).map(([e,t])=>({id:e,name:t.name||e.replace(/^(load_device_|energy_dashboard_)/,"").replace(/_/g," "),power:yc(this._hass.states[t.power_entity],t),rating:t.power_rating||0,ratingMeasured:!1!==t.rating_measured,priority:t.priority||5,isOn:t.is_on||!1,isShed:t.is_shed||!1,shedReason:t.shed_reason||null,shedPhase:t.shed_phase||null,phase:t.goals&&t.goals.phase||t.phase||"unknown",behindRelay:!(!t.goals||!t.goals.behind_operator_relay),lockedByOperator:!0===t.locked_by_operator,phaseMeasured:!0===t.phase_measured,hasControlHandle:void 0!==t.has_control_handle?!1!==t.has_control_handle:!1!==t.is_controllable,isControllable:void 0!==t.user_hands_off?!0!==t.user_hands_off:!1!==t.is_controllable,isCritical:t.is_critical||!1,deviceType:t.device_type||"unknown",isAvailable:t.is_available||!1,hasManualMapping:t.has_manual_mapping||!1,energySensor:t.energy_sensor||"",control:t.control||null,controlEntity:t.control?.entity||t.switch_entity||"",controlType:t.control?.type||"switch",controlMode:t.control_mode||"peak_only",dependsOn:t.depends_on||[],goals:t.goals||null,progress:t.progress||null,comfort:t.comfort||null,intent:t.intent&&"object"==typeof t.intent?t.intent:null,blockedBy:t.blocked_by||null,soc:t.soc,icon:this._resolveDeviceIcon(t)})).sort((e,t)=>e.priority-t.priority))}render(){if(!this._config)return H;const e=this.peakLimitUnlimited,t=this._getPeakColor(),s=null==this.targetPeakLimit||null==this.currentPeak?null:this.targetPeakLimit-this.currentPeak,i=e||null==this.currentPeak?0:this.targetPeakLimit>0?Math.min(this.currentPeak/this.targetPeakLimit*100,100):0,r=e?null:Ue({targetKw:this.targetPeakLimit,usedKwh:this.slotUsedKwh,allowedW:this.slotAllowedW});return j`
    - ${Ee("load_management_status",this.loadManagementStatus||"normal",e=>this._t(e)).toUpperCase()} + ${Ne("load_management_status",this.loadManagementStatus||"normal",e=>this._t(e)).toUpperCase()}
    ${this._t("peak_15min_avg")} - ${Ae(this.currentPeak,2)} kW - / ${e?this._t(et):Ae(this.targetPeakLimit,1)} + ${Te(this.currentPeak,2)} kW + / ${e?this._t(st):Te(this.targetPeakLimit,1)}
    @@ -7464,26 +8175,26 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow
    ${this._t("peak_15min_avg")} - ${Ae(this.currentPeak,2)} kW + ${Te(this.currentPeak,2)} kW
    - ${null===this.gridImportKw?G:H` + ${null===this.gridImportKw?H:j`
    ${this._t("peak_right_now")} ${this.gridImportKw.toFixed(2)} kW
    `}
    ${this._t("target_limit")} - ${e?this._t(et):Ae(this.targetPeakLimit,2)+" kW"} + ${e?this._t(st):Te(this.targetPeakLimit,2)+" kW"}
    - ${e?G:H` + ${e?H:j`
    ${this._t("margin")} - 0?"#4caf50":"#f44336"}">${Ae(s,2)} kW + 0?"#4caf50":"#f44336"}">${Te(s,2)} kW
    `}
    - ${null===r?G:H` + ${null===r?H:j`
    ${this._t("peak_slot")} ${r.label} ${r.usedKwh.toFixed(2)} / ${r.budgetKwh.toFixed(2)} kWh @@ -7492,7 +8203,7 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow
    - ${null===r.allowedKw?G:H` + ${null===r.allowedKw?H:j`
    ${this._t("peak_slot_allows")} ${r.allowedKw.toFixed(2)} kW @@ -7500,12 +8211,12 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow
    - ${this._t(Qe)} + ${this._t(et)} ${this._renderPeakSlider()} -
    ${this._t(e?st:tt)}
    +
    ${this._t(e?rt:it)}
    - ${this._showHelp?H` + ${this._showHelp?j`
    ${this._t("off")} — ${this._t("help_mode_off")}
    ${this._t("peak_only")} — ${this._t("help_mode_peak_only")}
    @@ -7518,7 +8229,7 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow
    ${this._t("peak_slot")} — ${this._t("help_peak_slot")}
    ${this._t("target_limit")} — ${this._t("help_device_peak")}
    ${this._t("daily_target")} — ${this._t("help_device_target")}
    -
    `:G} +
    `:H}
    - ${0===this.devices.length?H`
    ${this._t("no_devices_yet")}
    `:vr(this.devices,e=>e.id,(e,t)=>this._renderDevice(e,t+1))} + ${0===this.devices.length?j`
    ${this._t("no_devices_yet")}
    `:Rr(this.devices,e=>e.id,(e,t)=>this._renderDevice(e,t+1))}
    - ${this.devices.length>0?H`
    ${this._t("drag_hint")}
    `:G} + ${this.devices.length>0?j`
    ${this._t("drag_hint")}
    `:H}
    ${this._t("devices_auto_discovered").replace(//g," — ")}
    - `}_renderDevice(e,t){const s=e.isOn||e.power>.01,i="battery"===e.deviceType,r=e.dependsOn.length>0,a=this._getDependencyDepth(e),o=a>0?`margin-left:${24*a}px;border-left:2px solid rgba(255,152,0,${.3+.1*a});`:"";return H` -
    + `}_renderDevice(e,t){const s=e.isOn||e.power>.01,i="battery"===e.deviceType,r=e.dependsOn.length>0,a=this._getDependencyDepth(e),n=a>0?`margin-left:${24*a}px;border-left:2px solid rgba(255,152,0,${.3+.1*a});`:"";return j` +
    this._dragStart(t,e.id)} @@ -7545,29 +8256,29 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow
    - ${r?H`↪`:G} + ${r?j`↪`:H} ${e.name} - ${i?G:H` + ${i?H:j` ${e.isControllable?this._t("configure_device"):this._t("configure")} `}
    - ${i?H`${null!=e.soc?e.soc+"% · ":""}${s?ue(null==e.power?null:1e3*e.power):this._t("off")}`:s?ue(null==e.power?null:1e3*e.power):e.isShed?this._t("shed_label"):e.rating>0?H`~${ue(e.rating)}`:this._t("off")} + ${i?j`${null!=e.soc?e.soc+"% · ":""}${s?ue(null==e.power?null:1e3*e.power):this._t("off")}`:s?ue(null==e.power?null:1e3*e.power):e.isShed?this._t("shed_label"):e.rating>0?e.ratingMeasured?j`~${ue(e.rating)}`:j`${this._t("rating_not_measured")}`:this._t("off")}
    - ${e.blockedBy?H`
    ⏳ Waiting for: ${e.blockedBy}
    `:G} - ${e.dependsOn.length?H`
    ↪ ${this._t("requires")}: ${e.dependsOn.join(", ")}
    `:G} - ${e.isShed&&e.shedReason?H`
    ${(this._t((n=e.shedReason,"EMERGENCY"===String(n||"").toUpperCase()?"shed_emergency":"PHASE"===String(n||"").toUpperCase()?"shed_phase":"shed_peak"))||"").replace("{phase}",e.shedPhase||"?")}
    `:G} + ${e.blockedBy?j`
    ⏳ Waiting for: ${e.blockedBy}
    `:H} + ${e.dependsOn.length?j`
    ↪ ${this._t("requires")}: ${e.dependsOn.join(", ")}
    `:H} + ${e.isShed&&e.shedReason?j`
    ${(this._t(Vs(e.shedReason))||"").replace("{phase}",e.shedPhase||"?")}
    `:H}
    - ${e.lockedByOperator?H`${this._t("locked_by_grid_operator")}`:H`${s?this._t("on"):e.isShed?this._t("shed_label"):this._t("off")}`} + ${e.lockedByOperator?j`${this._t("locked_by_grid_operator")}`:j`${s?this._t("on"):e.isShed?this._t("shed_label"):this._t("off")}`} ${t} - ${i||e.lockedByOperator?G:this._renderControlVerdict(e)} + ${i||e.lockedByOperator?H:this._renderControlVerdict(e)}
    - ${i?H`${this._t("battery_role_label")}`:G} - ${"ev_charger"===e.deviceType||"ev_charging"===e.deviceType||i?G:H` + ${i?j`${this._t("battery_role_label")}`:H} + ${"ev_charger"===e.deviceType||"ev_charging"===e.deviceType||i?H:j` `} - ${i?G:H` + ${i?H:j` `} - ${i?G:this._renderPhase(e)} + ${i?H:this._renderPhase(e)}
    - ${"ev_charger"===e.deviceType||"ev_charging"===e.deviceType||i||"solar_only"!==this._mergedMode(e)&&"solar_battery"!==this._mergedMode(e)?G:H` + ${"ev_charger"===e.deviceType||"ev_charging"===e.deviceType||i||"solar_only"!==this._mergedMode(e)&&"solar_battery"!==this._mergedMode(e)?H:j`
    ${this._renderComfortChip(e)} ${this._renderGoalProgress(e)} - ${this._goalOpen[e.id]?this._renderGoalEditor(e):G} + ${this._goalOpen[e.id]?this._renderGoalEditor(e):H}
    -
    `;var n}_renderPhase(e){const t=(e.goals||{}).phase||e.phase||"unknown",s=e=>"unknown"===e?this._t("load_phase_unknown"):"3ph"===e?this._t("load_phase_3ph"):e;return"ev_charger"===e.deviceType||"ev_charging"===e.deviceType?"unknown"===t?G:H` - ${s(t)}`:H` +
    `}_renderPhase(e){const t=(e.goals||{}).phase||e.phase||"unknown",s=e=>"unknown"===e?this._t("load_phase_unknown"):"3ph"===e?this._t("load_phase_3ph"):e;return"ev_charger"===e.deviceType||"ev_charging"===e.deviceType?"unknown"===t?H:j` + ${s(t)}`:j` `}_mergedMode(e){const t=e.controlMode||"peak_only";return"surplus"!==t?t:(e.goals||{}).battery_assist_enabled?"solar_battery":"solar_only"}_mergedModeLabel(e){const t=this._mergedMode(e),s={off:"off",peak_only:"peak_only",solar_only:"load_mode_sem",solar_battery:"load_mode_sem_battery"}[t]||t;return this._t(s)}_applyMergedMode(e,t){const s="off"===t||"peak_only"===t?t:"surplus";if(e.controlMode=s,this._sendDeviceUpdate(e.id,"control_mode",s),"surplus"===s){const s="solar_battery"===t;e.goals=e.goals||{},e.goals.battery_assist_enabled=s,this._sendDeviceUpdate(e.id,"battery_assist_enabled",String(s)),"always"===e.goals.top_up_policy&&(e.goals.top_up_policy="solar_only",this._sendDeviceUpdate(e.id,"top_up_policy","solar_only"))}this.requestUpdate()}_hasTarget(e){return parseFloat((e.goals||{}).daily_min_runtime_min)>0}_goalPct(e){const t=e.goals,s=e.progress;if(!t||!s)return null;const i=parseFloat(t.daily_min_runtime_min);return i>0?Math.min(100,s.runtime_today_min/i*100):null}_renderGoalSlider(e){const t=e.goals||{},s=this._goalDrag;let i=(parseFloat(t.daily_min_runtime_min)||0)/60,r=parseFloat(t.daily_max_runtime_min)||0,a=r>0?r/60:12;s&&s.id===e.id&&("min"===s.handle?i=s.value:a=s.value),a=11.999999,c=e=>e%1==0?String(e):e.toFixed(1),d=a-i<.499999;return H` + `}_mergedMode(e){const t=e.controlMode||"peak_only";return"surplus"!==t?t:(e.goals||{}).battery_assist_enabled?"solar_battery":"solar_only"}_mergedModeLabel(e){const t=this._mergedMode(e),s={off:"off",peak_only:"peak_only",solar_only:"load_mode_sem",solar_battery:"load_mode_sem_battery"}[t]||t;return this._t(s)}_applyMergedMode(e,t){const s="off"===t||"peak_only"===t?t:"surplus";if(e.controlMode=s,this._sendDeviceUpdate(e.id,"control_mode",s),"surplus"===s){const s="solar_battery"===t;e.goals=e.goals||{},e.goals.battery_assist_enabled=s,this._sendDeviceUpdate(e.id,"battery_assist_enabled",String(s)),"always"===e.goals.top_up_policy&&(e.goals.top_up_policy="solar_only",this._sendDeviceUpdate(e.id,"top_up_policy","solar_only"))}this.requestUpdate()}_hasTarget(e){return parseFloat((e.goals||{}).daily_min_runtime_min)>0}_goalPct(e){const t=e.goals,s=e.progress;if(!t||!s)return null;const i=parseFloat(t.daily_min_runtime_min);return i>0?Math.min(100,s.runtime_today_min/i*100):null}_renderGoalSlider(e){const t=e.goals||{},s=this._goalDrag;let i=(parseFloat(t.daily_min_runtime_min)||0)/60,r=parseFloat(t.daily_max_runtime_min)||0,a=r>0?r/60:12;s&&s.id===e.id&&("min"===s.handle?i=s.value:a=s.value),a=11.999999,c=e=>e%1==0?String(e):e.toFixed(1),_=a-i<.499999;return j`
    - ${this._serviceError?H``:G} + ${this._serviceError?j``:H}
    ${this._t("at_least")} ${i<=0?this._t("no_target"):c(i)+" h"} ${this._t("up_to")} ${l?this._t("uncapped"):c(a)+" h"}
    -
    -
    +
    this._goalSliderStart(t,e,"min")}>
    -
    this._goalSliderStart(t,e,"max")}>
    - ${d?H` - this._splitGoalHandles(t,e,i,a)}> - `:G} + `:H}
    -
    `}_splitGoalHandles(e,t,s,i){e.stopPropagation();const r=.5,a=t.goals=t.goals||{};if(s-r>=0){const e=Math.round(60*(s-r));a.daily_min_runtime_min=e,this._sendDeviceUpdate(t.id,"daily_min_runtime_min",String(e))}else{const e=Math.min(11.5,i-r),s=Math.round(60*e);a.daily_max_runtime_min=s,this._sendDeviceUpdate(t.id,"daily_max_runtime_min",String(s))}this.requestUpdate()}_goalSliderStart(e,t,s){e.stopPropagation(),e.preventDefault();const i=e.currentTarget.parentElement,r=t.goals=t.goals||{},a=i.getBoundingClientRect(),o=(parseFloat(r.daily_min_runtime_min)||0)/60,n=parseFloat(r.daily_max_runtime_min)||0,l=n>0?n/60:12,c=e=>{let t=(e-a.left)/(a.width||1);t=Math.max(0,Math.min(1,t));let i=.5*Math.round(12*t/.5);return i="min"===s?Math.min(i,l):Math.max(i,o),i},d=e=>{this._goalDrag={id:t.id,handle:s,value:c(e)},this.requestUpdate()};d(e.clientX);const p=e=>d(e.clientX),_=e=>{window.removeEventListener("pointermove",p),window.removeEventListener("pointerup",_),window.removeEventListener("pointercancel",_);const i=c(e.clientX);if(this._goalDrag=null,"min"===s){const e=Math.round(60*i);r.daily_min_runtime_min=e,this._sendDeviceUpdate(t.id,"daily_min_runtime_min",String(e))}else{const e=i>=11.999999?0:Math.round(60*i);r.daily_max_runtime_min=e,this._sendDeviceUpdate(t.id,"daily_max_runtime_min",String(e))}this.requestUpdate()};window.addEventListener("pointermove",p),window.addEventListener("pointerup",_),window.addEventListener("pointercancel",_)}_renderPeakSlider(){const e=je,t=Ge,s=this._peakDrag;let i=this.peakLimitUnlimited?t:Math.min(t,Math.max(e,this.targetPeakLimit||e));null!=s&&(i=s);const r=(i-e)/(t-e)*100,a=i>=t-1e-6,o=null==s&&!this.peakLimitUnlimited&&null==this.targetPeakLimit?`${Ne} kW`:a?this._t(et):`${i.toFixed(1)} kW`;return H` +
    `}_splitGoalHandles(e,t,s,i){e.stopPropagation();const r=.5,a=t.goals=t.goals||{};if(s-r>=0){const e=Math.round(60*(s-r));a.daily_min_runtime_min=e,this._sendDeviceUpdate(t.id,"daily_min_runtime_min",String(e))}else{const e=Math.min(11.5,i-r),s=Math.round(60*e);a.daily_max_runtime_min=s,this._sendDeviceUpdate(t.id,"daily_max_runtime_min",String(s))}this.requestUpdate()}_goalSliderStart(e,t,s){e.stopPropagation(),e.preventDefault();const i=e.currentTarget.parentElement,r=t.goals=t.goals||{},a=i.getBoundingClientRect(),n=(parseFloat(r.daily_min_runtime_min)||0)/60,o=parseFloat(r.daily_max_runtime_min)||0,l=o>0?o/60:12,c=e=>{let t=(e-a.left)/(a.width||1);t=Math.max(0,Math.min(1,t));let i=.5*Math.round(12*t/.5);return i="min"===s?Math.min(i,l):Math.max(i,n),i},_=e=>{this._goalDrag={id:t.id,handle:s,value:c(e)},this.requestUpdate()};_(e.clientX);const d=e=>_(e.clientX),p=e=>{window.removeEventListener("pointermove",d),window.removeEventListener("pointerup",p),window.removeEventListener("pointercancel",p);const i=c(e.clientX);if(this._goalDrag=null,"min"===s){const e=Math.round(60*i);r.daily_min_runtime_min=e,this._sendDeviceUpdate(t.id,"daily_min_runtime_min",String(e))}else{const e=i>=11.999999?0:Math.round(60*i);r.daily_max_runtime_min=e,this._sendDeviceUpdate(t.id,"daily_max_runtime_min",String(e))}this.requestUpdate()};window.addEventListener("pointermove",d),window.addEventListener("pointerup",p),window.addEventListener("pointercancel",p)}_renderPeakSlider(){const e=Ke,t=He,s=this._peakDrag;let i=this.peakLimitUnlimited?t:Math.min(t,Math.max(e,this.targetPeakLimit||e));null!=s&&(i=s);const r=(i-e)/(t-e)*100,a=i>=t-1e-6,n=null==s&&!this.peakLimitUnlimited&&null==this.targetPeakLimit?`${Ee} kW`:a?this._t(st):`${i.toFixed(1)} kW`;return j`
    - ${o} + ${n}
    this._peakSliderStart(e)}>
    -
    `}_peakSliderStart(e){e.stopPropagation(),e.preventDefault();const t=je,s=Ge,i=Ve,r=e.currentTarget.parentElement.getBoundingClientRect(),a=e=>{let a=(e-r.left)/(r.width||1);return a=Math.max(0,Math.min(1,a)),Math.round((t+a*(s-t))/i)*i},o=e=>{this._peakDrag=a(e),this.requestUpdate()};o(e.clientX);const n=e=>o(e.clientX),l=e=>{window.removeEventListener("pointermove",n),window.removeEventListener("pointerup",l),window.removeEventListener("pointercancel",l);const t=function(e){const t=Ze(e);return null!==t&&t>=Ge-1e-6?{limited:!1,kw:Ge}:{limited:!0,kw:Xe(t)??je}}(a(e.clientX));this._peakDrag=null,this._commitPeakLimit(t.kw,!t.limited)};window.addEventListener("pointermove",n),window.addEventListener("pointerup",l),window.addEventListener("pointercancel",l)}_commitPeakLimit(e,t){this.targetPeakLimit=e,this.peakLimitUnlimited=t;const s={kw:e,unlimited:t,until:Date.now()+15e3};this._peakHold=s,this._sendTargetPeakUpdate(e,t,s),this.requestUpdate()}_onStopEntity(e,t){t.stopPropagation();const s=t.detail&&t.detail.value||"";e.goals=e.goals||{},e.goals.stop_entity=s,this._sendDeviceUpdate(e.id,"stop_entity",s)}_overnightSource(e){const t=e.goals||{};return t.battery_eligible_overnight?"battery":"cheap_hours"===t.top_up_policy?"grid":"off"}_renderOvernightPicker(e){const t=this._overnightSource(e),s=(s,i)=>H` +
    `}_peakSliderStart(e){e.stopPropagation(),e.preventDefault();const t=Ke,s=He,i=Ve,r=e.currentTarget.parentElement.getBoundingClientRect(),a=e=>{let a=(e-r.left)/(r.width||1);return a=Math.max(0,Math.min(1,a)),Math.round((t+a*(s-t))/i)*i},n=e=>{this._peakDrag=a(e),this.requestUpdate()};n(e.clientX);const o=e=>n(e.clientX),l=e=>{window.removeEventListener("pointermove",o),window.removeEventListener("pointerup",l),window.removeEventListener("pointercancel",l);const t=function(e){const t=Ze(e);return null!==t&&t>=He-1e-6?{limited:!1,kw:He}:{limited:!0,kw:Xe(t)??Ke}}(a(e.clientX));this._peakDrag=null,this._commitPeakLimit(t.kw,!t.limited)};window.addEventListener("pointermove",o),window.addEventListener("pointerup",l),window.addEventListener("pointercancel",l)}_commitPeakLimit(e,t){this.targetPeakLimit=e,this.peakLimitUnlimited=t;const s={kw:e,unlimited:t,until:Date.now()+15e3};this._peakHold=s,this._sendTargetPeakUpdate(e,t,s),this.requestUpdate()}_onStopEntity(e,t){t.stopPropagation();const s=t.detail&&t.detail.value||"";e.goals=e.goals||{},e.goals.stop_entity=s,this._sendDeviceUpdate(e.id,"stop_entity",s)}_overnightSource(e){const t=e.goals||{};return t.battery_eligible_overnight?"battery":"cheap_hours"===t.top_up_policy?"grid":"off"}_renderOvernightPicker(e){const t=this._overnightSource(e),s=(s,i)=>j` `;return H` + data-device="${e.id}">${i}`;return j`
    ${this._t("finish_overnight_from")} @@ -7652,11 +8363,11 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow ${s("grid",this._t("overnight_grid"))}
    -
    ${this._t("overnight_"+t+"_hint")}
    `}_setOvernightSource(e,t){const s=e.goals=e.goals||{},i="battery"===t,r="grid"===t?"cheap_hours":"solar_only";s.battery_eligible_overnight=i,s.top_up_policy=r,this._sendDeviceUpdate(e.id,"battery_eligible_overnight",String(i)),this._sendDeviceUpdate(e.id,"top_up_policy",r),this.requestUpdate()}_renderGoalProgress(e){const t=this._mergedMode(e);if("solar_only"!==t&&"solar_battery"!==t)return G;if("band"===this._questions(e).view)return G;const s=this._goalPct(e);if(null===s)return G;const i=e.goals,r=e.progress,a=r.targets_met||s>=100,o=e=>{const t=e/60;return t%1==0?String(t):t.toFixed(1)},n=o(r.runtime_today_min)+"/"+o(parseFloat(i.daily_min_runtime_min))+" "+this._t("hours_on_solar_today");return H` +
    ${this._t("overnight_"+t+"_hint")}
    `}_setOvernightSource(e,t){const s=e.goals=e.goals||{},i="battery"===t,r="grid"===t?"cheap_hours":"solar_only";s.battery_eligible_overnight=i,s.top_up_policy=r,this._sendDeviceUpdate(e.id,"battery_eligible_overnight",String(i)),this._sendDeviceUpdate(e.id,"top_up_policy",r),this.requestUpdate()}_renderGoalProgress(e){const t=this._mergedMode(e);if("solar_only"!==t&&"solar_battery"!==t)return H;if("band"===this._questions(e).view)return H;const s=this._goalPct(e);if(null===s)return H;const i=e.goals,r=e.progress,a=r.targets_met||s>=100,n=e=>{const t=e/60;return t%1==0?String(t):t.toFixed(1)},o=n(r.runtime_today_min)+"/"+n(parseFloat(i.daily_min_runtime_min))+" "+this._t("hours_on_solar_today");return j`
    - ${n}${a?" ✓":""} -
    `}_renderGoalEditor(e){const t=this._mergedMode(e);if("solar_only"!==t&&"solar_battery"!==t)return G;const s=this._questions(e);return"band"===s.view?H` + ${o}${a?" ✓":""} +
    `}_renderGoalEditor(e){const t=this._mergedMode(e);if("solar_only"!==t&&"solar_battery"!==t)return H;const s=this._questions(e);return"band"===s.view?j`
    ${this._renderHeaterQuestions(e,s)} ${this._renderBuyQuestion(e,s)} @@ -7668,7 +8379,7 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow ${this._renderCopCurve(e)} ${this._renderStopCondition(e)} -
    `:H` +
`:j`
@@ -7685,11 +8396,11 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow ${this._renderCopCurve(e)} ${this._renderStopCondition(e)} -
`}_renderStopCondition(e){const t=e.goals||{};return H` +
`}_renderStopCondition(e){const t=e.goals||{};return j`
${this._t("stop_condition")} - ${this.hass&&customElements.get("ha-entity-picker")?H` + ${this.hass&&customElements.get("ha-entity-picker")?j` this._onStopEntity(e,t)} @click=${e=>e.stopPropagation()}> - `:H` + `:j` `} @@ -7708,49 +8419,49 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow data-goal="stop_at" data-device="${e.id}">
-
${this._t("stop_condition_hint")}
`}_tf(e,t={}){let s=this._t(e)||"";for(const[e,i]of Object.entries(t||{}))s=s.split("{"+e+"}").join(String(i??""));return s}_hasBattery(){return(this.devices||[]).some(e=>"battery"===e.deviceType)}_questions(e){const t="°F"===(this.hass?.config?.unit_system?.temperature||"°C");return function({goals:e,comfort:t},{hasBattery:s=!1,toUnit:i=e=>e}={}){const r=e||{},a=Is(r.comfort_target),o=Is(r.comfort_limit),n=Is(r.comfort_offset)||0,l=null!==a&&a>0&&null!==o&&o>0,c=e=>Math.round(10*Number(i(e)))/10,d=l&&!!t&&!0===t.anchored&&null!==Is(t.keep_c)&&null!==Is(t.limit_c),p=d?c(t.keep_c):a,_=d?c(t.limit_c):o,h=t&&"cool"===t.hvac||l&&!(t&&t.hvac)&&o>a||!(t&&t.hvac)&&"cool"===r.hvac_mode,u=function(e){const t=e||{};return!0===t.battery_eligible_overnight||"true"===t.battery_eligible_overnight?"battery":"cheap_hours"===t.top_up_policy?"grid":"off"}(r),g="battery"!==u||s?u:"off",m=s?["off","grid","battery"]:["off","grid"];return l?{view:"band",cooling:h,keep:p,floor:_,offset:n,sunMax:d&&null!==Is(t.max_c)?c(t.max_c):Math.round(10*(h?p-n:p+n))/10,anchored:d,keepReadOnly:d,storedKeep:a,keepNoteKey:d?"hq_keep_anchored":null,buy:g,buyChoices:m,buySubKey:h?"hq_buy_sub_band_cool":"hq_buy_sub_band",buySubBatteryKey:s?"hq_buy_sub_band_battery":null,season:"by_forecast"===r.hvac_mode?h?"hq_season_cooling":"hq_season_heating":null,keys:h?{keepSub:"hq_keep_sub_cool",floor:"hq_ceiling",floorSub:"hq_ceiling_sub",sunMax:"hq_sun_min",sunMaxSub:"hq_sun_min_sub"}:{keepSub:"hq_keep_sub",floor:"hq_floor",floorSub:"hq_floor_sub",sunMax:"hq_sun_max",sunMaxSub:"hq_sun_max_sub"}}:{view:"time",buy:g,buyChoices:m,buySubKey:"hq_buy_sub_time"}}({goals:e.goals,comfort:e.comfort},{hasBattery:this._hasBattery(),toUnit:e=>t?9*Number(e)/5+32:Number(e)})}_plannedAt(e){const t=this._hass&&this._hass.states[this._prefix+"energy_plan"],s=function(e,t,s){const i=new Set((t||[]).map(String));return(Array.isArray(e)?e:[]).filter(e=>e&&i.has(String(e.id))&&Date.parse(e.start)>s).sort((e,t)=>Date.parse(e.start)-Date.parse(t.start))[0]||null}(t&&t.attributes&&t.attributes.blocks,["load:"+e.id,"comfort:"+e.id],Date.now());if(!s)return"";const i=new Date(s.start),r=e=>String(e).padStart(2,"0");return r(i.getHours())+":"+r(i.getMinutes())}_heaterSays(e){const t=e.isOn||e.power>.01;return Ps({comfort:e.comfort,intent:e.intent,mergedMode:this._mergedMode(e)},{fmtTemp:e=>this._displayTemp(e),plannedAt:this._plannedAt(e)},t)}_setGoal(e,t,s){e.goals={...e.goals||{},[t]:s},this._sendDeviceUpdate(e.id,t,String(s))}_solarTargetBounds(){const e=this._hass&&this._hass.states["number.sem_hot_water_solar_target"],t=this.hass?.config?.unit_system?.temperature||"°C";return e&&e.attributes&&"°C"===t?{min:e.attributes.min,max:e.attributes.max}:null}_syncSolarTarget(e,t){if(!/^hot_water/.test(String(e.id||""))||!this._hass)return;const s=this.hass?.config?.unit_system?.temperature||"°C",i=Number(t);if(!Number.isFinite(i))return;const r="°F"===s?5*(i-32)/9:i;this._hass.callService("solar_energy_management","set_option",{options:{hot_water_solar_target:Math.round(10*r)/10}}).catch(e=>this._showServiceError(e))}_onKeepChange(e,t,s){const i=parseFloat(s.target.value);Number.isFinite(i)&&(this._setGoal(e,"comfort_target",i),t.cooling||this._syncSolarTarget(e,i+(t.offset||0)),this.requestUpdate())}_onSunMaxChange(e,t,s){const i=function({id:e,keep:t,cooling:s=!1},i,r=null){const a=Is(t),o=Is(i),n=s?"hq_err_above_keep":"hq_err_below_keep";if(null===a||null===o)return{error:n,params:{keep:null===a?"":String(a)}};if(s?o>a:oNumber(r.max))return{error:"hq_err_above_max",params:{max:String(r.max)}};const c=Math.round(2*Math.abs(o-a))/2;return{writes:[{property:"comfort_offset",value:String(c)}],option:l?{key:"hot_water_solar_target",value:Math.round(10*(a+c))/10}:null}}({id:e.id,keep:t.keep,cooling:t.cooling},s.target.value,this._solarTargetBounds());if(this._hqError={...this._hqError||{}},i.error)return this._hqError[e.id]=this._tf(i.error,i.params),s.target.value=String(t.sunMax),void this.requestUpdate();delete this._hqError[e.id];for(const t of i.writes)this._setGoal(e,t.property,t.value);i.option&&this._syncSolarTarget(e,i.option.value),this.requestUpdate()}_renderHeaterQuestions(e,t){const s=this.hass?.config?.unit_system?.temperature||"°C",i={fmtTemp:e=>this._displayTemp(e)},r=this._heaterSays(e),a=function(e,t,s={}){if(!e)return null;const i="function"==typeof s.fmtTemp?s.fmtTemp:e=>Math.round(10*e)/10+" °C",r=!0===e.cool;if("hq_st_heats_at"===e.key||"hq_st_cools_at"===e.key)return{key:r?"hq_sub_sun_free_cool":"hq_sub_sun_free",params:{}};if(e.noBuy){const e=Is(t&&t.comfort&&t.comfort.limit_c);return{key:r?"hq_sub_no_buy_cool":"hq_sub_no_buy",params:{limit:null===e?"":i(e)}}}return null}(r,e,i),o=this._displayTemp(e.comfort?e.comfort.reading_c:null),n=r?function(e){const t=String(e||"").trim();if(!t)return"";const s=t.charAt(0).toUpperCase()+t.slice(1);return/[.!?]$/.test(s)?s:s+"."}(this._tf(r.key,r.params)+(r.why?" · "+this._t(r.why):"")):"",l=this._hqError&&this._hqError[e.id],c=(e,t,i=!1)=>H` +
${this._t("stop_condition_hint")}
`}_tf(e,t={}){let s=this._t(e)||"";for(const[e,i]of Object.entries(t||{}))s=s.split("{"+e+"}").join(String(i??""));return s}_hasBattery(){return(this.devices||[]).some(e=>"battery"===e.deviceType)}_questions(e){const t="°F"===(this.hass?.config?.unit_system?.temperature||"°C");return bs({goals:e.goals,comfort:e.comfort},{hasBattery:this._hasBattery(),toUnit:e=>t?9*Number(e)/5+32:Number(e)})}_plannedAt(e){const t=this._hass&&this._hass.states[this._prefix+"energy_plan"],s=us(t&&t.attributes&&t.attributes.blocks,["load:"+e.id,"comfort:"+e.id],Date.now());if(!s)return"";const i=new Date(s.start),r=e=>String(e).padStart(2,"0");return r(i.getHours())+":"+r(i.getMinutes())}_heaterSays(e){const t=e.isOn||e.power>.01;return ms({comfort:e.comfort,intent:e.intent,mergedMode:this._mergedMode(e)},{fmtTemp:e=>this._displayTemp(e),plannedAt:this._plannedAt(e)},t)}_setGoal(e,t,s){e.goals={...e.goals||{},[t]:s},this._sendDeviceUpdate(e.id,t,String(s))}_solarTargetBounds(){const e=this._hass&&this._hass.states["number.sem_hot_water_solar_target"],t=this.hass?.config?.unit_system?.temperature||"°C";return e&&e.attributes&&"°C"===t?{min:e.attributes.min,max:e.attributes.max}:null}_syncSolarTarget(e,t){if(!/^hot_water/.test(String(e.id||""))||!this._hass)return;const s=this.hass?.config?.unit_system?.temperature||"°C",i=Number(t);if(!Number.isFinite(i))return;const r="°F"===s?5*(i-32)/9:i;this._hass.callService("solar_energy_management","set_option",{options:{hot_water_solar_target:Math.round(10*r)/10}}).catch(e=>this._showServiceError(e))}_onKeepChange(e,t,s){const i=parseFloat(s.target.value);Number.isFinite(i)&&(this._setGoal(e,"comfort_target",i),t.cooling||this._syncSolarTarget(e,i+(t.offset||0)),this.requestUpdate())}_onSunMaxChange(e,t,s){const i=xs({id:e.id,keep:t.keep,cooling:t.cooling},s.target.value,this._solarTargetBounds());if(this._hqError={...this._hqError||{}},i.error)return this._hqError[e.id]=this._tf(i.error,i.params),s.target.value=String(t.sunMax),void this.requestUpdate();delete this._hqError[e.id];for(const t of i.writes)this._setGoal(e,t.property,t.value);i.option&&this._syncSolarTarget(e,i.option.value),this.requestUpdate()}_renderHeaterQuestions(e,t){const s=this.hass?.config?.unit_system?.temperature||"°C",i={fmtTemp:e=>this._displayTemp(e)},r=this._heaterSays(e),a=fs(r,e,i),n=this._displayTemp(e.comfort?e.comfort.reading_c:null),o=r?function(e){const t=String(e||"").trim();if(!t)return"";const s=t.charAt(0).toUpperCase()+t.slice(1);return/[.!?]$/.test(s)?s:s+"."}(this._tf(r.key,r.params)+(r.why?" · "+this._t(r.why):"")):"",l=this._hqError&&this._hqError[e.id],c=(e,t,i=!1)=>j` e.stopPropagation()}> - ${s}`,d=(e,t,s)=>H` + ${s}`,_=(e,t,s)=>j`
${e} ${s}
-
${t}
`;return H` - ${t.season?H`
${this._t(t.season)}
`:G} -
${o?this._tf("hq_now",{reading:o})+" ":""}${n}
- ${a?H`
${this._tf(a.key,a.params)}
`:G} +
${t}
`;return j` + ${t.season?j`
${this._t(t.season)}
`:H} +
${n?this._tf("hq_now",{reading:n})+" ":""}${o}
+ ${a?j`
${this._tf(a.key,a.params)}
`:H} ${this._renderBandStrip(e)} - ${d(this._t("hq_keep"),this._t(t.keys.keepSub),c(t.keep,s=>this._onKeepChange(e,t,s),t.keepReadOnly))} - ${t.keepNoteKey?H`
${this._t(t.keepNoteKey)}
`:G} - ${d(this._t(t.keys.floor),this._t(t.keys.floorSub),c(t.floor,s=>{for(const i of function(e,t){const s=Is(t);if(null===s)return{writes:[]};const i=e&&e.anchored&&null!==Is(e.storedKeep)&&null!==Is(e.keep)?Math.round(10*(Number(e.storedKeep)+(s-Number(e.keep))))/10:s;return{writes:[{property:"comfort_limit",value:String(i)}]}}(t,s.target.value).writes)this._setGoal(e,i.property,i.value);this.requestUpdate()}))} - ${d(this._t(t.keys.sunMax),this._t(t.keys.sunMaxSub),c(t.sunMax,s=>this._onSunMaxChange(e,t,s)))} - ${l?H`
${l}
`:G}`}_renderBuyQuestion(e,t){const s="band"===t.view?t.keep+" "+(this.hass?.config?.unit_system?.temperature||"°C"):"";return H` + ${_(this._t("hq_keep"),this._t(t.keys.keepSub),c(t.keep,s=>this._onKeepChange(e,t,s),t.keepReadOnly))} + ${t.keepNoteKey?j`
${this._t(t.keepNoteKey)}
`:H} + ${_(this._t(t.keys.floor),this._t(t.keys.floorSub),c(t.floor,s=>{for(const i of $s(t,s.target.value).writes)this._setGoal(e,i.property,i.value);this.requestUpdate()}))} + ${_(this._t(t.keys.sunMax),this._t(t.keys.sunMaxSub),c(t.sunMax,s=>this._onSunMaxChange(e,t,s)))} + ${l?j`
${l}
`:H}`}_renderBuyQuestion(e,t){const s="band"===t.view?t.keep+" "+(this.hass?.config?.unit_system?.temperature||"°C"):"";return j`
${this._t("hq_buy")} - ${t.buyChoices.map(s=>H` + ${t.buyChoices.map(s=>j` `)} + data-device="${e.id}">${this._t(vs[s])}`)}
-
${this._tf(t.buySubKey,{keep:s})}${t.buySubBatteryKey?" "+this._tf(t.buySubBatteryKey,{keep:s}):""}
`}_comfortChipMeta(e){const t=e.comfort;if(!t||!t.state||"disengaged"===t.state)return null;const s="cool"===(t.hvac||"cool");return{forced:{icon:"🔥",color:"#ffc107",label:this._t(s?"chip_cooling_now":"chip_heating_now")},willing:{icon:"❄",color:"#8DC892",label:this._t(s?"chip_pre_cooling":"chip_pre_heating")},banked:{icon:"✓",color:"#4db6ac",label:this._t("chip_banked")}}[t.state]||null}_displayTemp(e){if(null==e)return null;const t=this.hass?.config?.unit_system?.temperature||"°C",s="°F"===t?9*e/5+32:e;return`${Math.round(10*s)/10} ${t}`}_renderControlVerdict(e){const t=this._mergedMode(e);let s,i,r;return e.hasControlHandle?e.isControllable&&"off"!==t?(s="control_verdict_may_act",i="mdi:check-circle-outline",r="#8DC892"):(s="control_verdict_off",i="mdi:hand-back-left-outline",r="#ff9800"):(s="control_verdict_none",i="mdi:minus-circle-outline",r="#666"),H` +
${this._tf(t.buySubKey,{keep:s})}${t.buySubBatteryKey?" "+this._tf(t.buySubBatteryKey,{keep:s}):""}
`}_comfortChipMeta(e){const t=e.comfort;if(!t||!t.state||"disengaged"===t.state)return null;const s="cool"===(t.hvac||"cool");return{forced:{icon:"🔥",color:"#ffc107",label:this._t(s?"chip_cooling_now":"chip_heating_now")},willing:{icon:"❄",color:"#8DC892",label:this._t(s?"chip_pre_cooling":"chip_pre_heating")},banked:{icon:"✓",color:"#4db6ac",label:this._t("chip_banked")}}[t.state]||null}_displayTemp(e){if(null==e)return null;const t=this.hass?.config?.unit_system?.temperature||"°C",s="°F"===t?9*e/5+32:e;return`${Math.round(10*s)/10} ${t}`}_renderControlVerdict(e){const t=this._mergedMode(e);let s,i,r;return e.hasControlHandle?e.isControllable&&"off"!==t?(s="control_verdict_may_act",i="mdi:check-circle-outline",r="#8DC892"):(s="control_verdict_off",i="mdi:hand-back-left-outline",r="#ff9800"):(s="control_verdict_none",i="mdi:minus-circle-outline",r="#666"),j` ${this._t(s)} - `}_renderComfortChip(e){const t=this._heaterSays(e);if(t){const s={ok:"#8DC892",warn:"#ffc107"}[t.cls]||"#4db6ac",i="hq_st_warm"===t.key||"hq_st_cool"===t.key?null:this._displayTemp(e.comfort?e.comfort.reading_c:null),r=t.why?" · "+this._t(t.why):"";return H` + `}_renderComfortChip(e){const t=this._heaterSays(e);if(t){const s={ok:"#8DC892",warn:"#ffc107"}[t.cls]||"#4db6ac",i="hq_st_warm"===t.key||"hq_st_cool"===t.key?null:this._displayTemp(e.comfort?e.comfort.reading_c:null),r=t.why?" · "+this._t(t.why):"";return j`
- ${i?H`${i} \u00B7 `:G}${this._tf(t.key,t.params)}${r} -
`}const s=this._comfortChipMeta(e);if(!s)return G;const i=this._displayTemp(e.comfort.reading_c);return H` + ${i?j`${i} \u00B7 `:H}${this._tf(t.key,t.params)}${r} + `}const s=this._comfortChipMeta(e);if(!s)return H;const i=this._displayTemp(e.comfort.reading_c);return j`
- ${s.icon} ${s.label}${i?H` \u00B7 ${i}`:G} -
`}_renderThermometerRows(e,t=!0){const s=e.goals||{};return H` + ${s.icon} ${s.label}${i?j` \u00B7 ${i}`:H} + `}_renderThermometerRows(e,t=!0){const s=e.goals||{};return j`
${this._t("comfort_thermometer")} - ${this.hass&&customElements.get("ha-entity-picker")?H` + ${this.hass&&customElements.get("ha-entity-picker")?j` {const s=t.detail?.value??"";e.goals={...e.goals||{},comfort_entity:s},this._sendDeviceUpdate(e.id,"comfort_entity",s)}} @click=${e=>e.stopPropagation()}> - `:H` + `:j` this._sendDeviceUpdate(e.id,"comfort_entity",t.target.value)}>`}
- ${t?this._renderHvacRow(e):G}`}_renderHvacRow(e){const t=e.goals||{};return H` - ${t.hvac_mode?H` + ${t?this._renderHvacRow(e):H}`}_renderHvacRow(e){const t=e.goals||{};return j` + ${t.hvac_mode?j`
${this._t("comfort_hvac_mode")} -
`:G}`}_renderBandStrip(e){const t=e.goals||{},s=this.hass?.config?.unit_system?.temperature||"°C",i=parseFloat(t.comfort_target)||0,r=parseFloat(t.comfort_offset)||0,a=parseFloat(t.comfort_limit)||0;if(!(i>0&&a>0))return G;const o=a>i?i-r:i+r,n=Math.min(o,i,a)-3,l=Math.max(o,i,a)+3,c=e=>Math.max(0,Math.min(100,(e-n)/(l-n)*100)),d=e.comfort?e.comfort.reading_c:null,p=null==d?null:"°F"===s?9*d/5+32:d;return H` + `:H}`}_renderBandStrip(e){const t=e.goals||{},s=this.hass?.config?.unit_system?.temperature||"°C",i=parseFloat(t.comfort_target)||0,r=parseFloat(t.comfort_offset)||0,a=parseFloat(t.comfort_limit)||0;if(!(i>0&&a>0))return H;const n=a>i?i-r:i+r,o=Math.min(n,i,a)-3,l=Math.max(n,i,a)+3,c=e=>Math.max(0,Math.min(100,(e-o)/(l-o)*100)),_=e.comfort?e.comfort.reading_c:null,d=null==_?null:"°F"===s?9*_/5+32:_;return j`
-
-
+
+
- ${null!=p?H`
\u25B2
`:G} -
`}_renderComfortBand(e){const t=!["ev_charger","ev_charging","battery"].includes(e.deviceType);if(!e.comfort&&!t)return G;const s=e.goals||{},i=this.hass?.config?.unit_system?.temperature||"°C",r=parseFloat(s.comfort_target)||0,a=parseFloat(s.comfort_offset)||0,o=parseFloat(s.comfort_limit)||0,n=/^hot_water/.test(String(e.id||""))&&r>0,l=this._t("comfort_example").replace("{keep}",r+i).replace("{extra}",a+i).replace("{max}",r+a+i),c=(t,s,i)=>H` + ${null!=d?j`
\u25B2
`:H} + `}_renderComfortBand(e){const t=!["ev_charger","ev_charging","battery"].includes(e.deviceType);if(!e.comfort&&!t)return H;const s=e.goals||{},i=this.hass?.config?.unit_system?.temperature||"°C",r=parseFloat(s.comfort_target)||0,a=parseFloat(s.comfort_offset)||0,n=parseFloat(s.comfort_limit)||0,o=/^hot_water/.test(String(e.id||""))&&r>0,l=this._t("comfort_example").replace("{keep}",r+i).replace("{extra}",a+i).replace("{max}",r+a+i),c=(t,s,i)=>j` {const i=s.target.value;""!==i&&null!=i&&(e.goals={...e.goals||{},[t]:i},this._sendDeviceUpdate(e.id,t,String(i)))}} - @click=${e=>e.stopPropagation()}>`;return H` + @click=${e=>e.stopPropagation()}>`;return j`
${this._t("comfort_limit_label")} - ${c("comfort_limit",o,.5)} + ${c("comfort_limit",n,.5)} ${i}
${this._t("comfort_hint")}
- ${n?H` + ${o?j`
${l}
-
${this._t("comfort_both_limits")}
`:G}`}_antiCycleBounds(){const e=this._hass?.states?.[`${this._prefix}controllable_devices_count`];return function(e){const t=e&&e.anti_cycle_bounds;if(!t||null==t.min||null==t.max)return null;const s=Number(t.min),i=Number(t.max);return Number.isFinite(s)&&Number.isFinite(i)?{min:s,max:i}:null}(e?.attributes)}_onAntiCycleChange(e,t,s,i){const r=function(e,t){if(""===e||null==e)return null;const s=Number(e);if(!Number.isFinite(s)||s<1)return null;const i=t?Math.min(t.max,Math.max(t.min,s)):s;return i<1?null:i}(i.target.value,s);if(null==r){const s=(e.goals||{})[t];return void(i.target.value=null!=s?String(s):"")}i.target.value=String(r),e.goals={...e.goals||{},[t]:r},this._sendDeviceUpdate(e.id,t,String(r))}_renderAntiCycle(e){const t=e.goals||{},s=this._antiCycleBounds(),i=e=>function(e,t){const s=(e||{})[t];if(null==s)return"—";const i=Number(s);return Number.isFinite(i)?String(i):"—"}(t,e),r=(r,a)=>H` +
${this._t("comfort_both_limits")}
`:H}`}_antiCycleBounds(){const e=this._hass?.states?.[`${this._prefix}controllable_devices_count`];return ua(e?.attributes)}_onAntiCycleChange(e,t,s,i){const r=ma(i.target.value,s);if(null==r){const s=(e.goals||{})[t];return void(i.target.value=null!=s?String(s):"")}i.target.value=String(r),e.goals={...e.goals||{},[t]:r},this._sendDeviceUpdate(e.id,t,String(r))}_renderAntiCycle(e){const t=e.goals||{},s=this._antiCycleBounds(),i=(i,r)=>j` this._onAntiCycleChange(e,r,s,t)} - @click=${e=>e.stopPropagation()}>`;return H` + min=${s?Math.max(1,s.min):H} + max=${s?s.max:H} + .value="${null!=t[i]&&""!==t[i]?String(t[i]):""}" + placeholder="${(e=>ha(t,e))(r)}" + @change=${t=>this._onAntiCycleChange(e,i,s,t)} + @click=${e=>e.stopPropagation()}>`;return j`
${this._t("anti_cycle_min_run")} - ${r("min_on_time_min","min_on_effective_min")} + ${i("min_on_time_min","min_on_effective_min")} ${this._t("minutes_short")}
${this._t("anti_cycle_min_pause")} - ${r("min_off_time_min","min_off_effective_min")} + ${i("min_off_time_min","min_off_effective_min")} ${this._t("minutes_short")}
-
${this._t("anti_cycle_hint")}
`}_renderCopCurve(e){const t=e.goals||{};if(!Object.prototype.hasOwnProperty.call(t,"cop_curve"))return G;this._copRows||(this._copRows={});const s=this._copRows[e.id]||function(e){const t=(Sr(e)||[]).map(e=>({c:e.c,cop:e.cop}));for(;t.length<3;)t.push({c:null,cop:null});return t}(t.cop_curve),i="heat_pump"===e.id?function(e){if(!e)return null;const t=e.attributes||{},s=kr(t.cop_used);if(null===s||s<=0)return null;const i=["unknown","unavailable",""].includes(String(e.state??""));return{cop:s,source:["curve","learned"].includes(t.cop_source)?t.cop_source:"flat",outdoorC:["curve","learned"].includes(t.cop_source)?kr(t.outdoor_c):null,price:i?null:kr(e.state),unit:t.unit_of_measurement||""}}(this.hass?.states?.[`${this._prefix}heat_pump_cost_of_heat`]):null,r=function(e,t){if(!e)return"";const s=Cr(e.cop);let i=null!==e.outdoorC?t("hp_cop_now_at").replace("{cop}",s).replace("{temp}",`${Cr(e.outdoorC)} °C`):t("hp_cop_now").replace("{cop}",s);if(null!==e.price){const s=`${e.price.toFixed(3)} ${e.unit}`.trim();i+=` · ${t("hp_heat_costs").replace("{price}",s)}`}return i}(i,e=>this._t(e)),a=(i,r,a)=>{const o=""===String(a).trim()?null:Number(a),n=s.map((e,t)=>t===i?{...e,[r]:Number.isFinite(o)?o:null}:e);this._copRows={...this._copRows,[e.id]:n};const l=zr(n);null!==l&&l!==(t.cop_curve||"")&&(e.goals={...t,cop_curve:l},this._sendDeviceUpdate(e.id,"cop_curve",l)),this.requestUpdate()},o=e=>null==e?"":String(e);return H` +
${this._t("anti_cycle_hint")}
`}_renderCopCurve(e){const t=e.goals||{};if(!Object.prototype.hasOwnProperty.call(t,"cop_curve"))return H;this._copRows||(this._copRows={});const s=this._copRows[e.id]||function(e){const t=(ba(e)||[]).map(e=>({c:e.c,cop:e.cop}));for(;t.length<3;)t.push({c:null,cop:null});return t}(t.cop_curve),i=function(e,t){if(!e)return"";const s=va(e.cop);let i=null!==e.outdoorC?t("hp_cop_now_at").replace("{cop}",s).replace("{temp}",`${va(e.outdoorC)} °C`):t("hp_cop_now").replace("{cop}",s);if(null!==e.price){const s=`${e.price.toFixed(3)} ${e.unit}`.trim();i+=` · ${t("hp_heat_costs").replace("{price}",s)}`}return i}("heat_pump"===e.id?$a(this.hass?.states?.[`${this._prefix}heat_pump_cost_of_heat`]):null,e=>this._t(e)),r=(i,r,a)=>{const n=""===String(a).trim()?null:Number(a),o=s.map((e,t)=>t===i?{...e,[r]:Number.isFinite(n)?n:null}:e);this._copRows={...this._copRows,[e.id]:o};const l=function(e){const t=xa(e);return""===t?"":ba(t)?t:null}(o);null!==l&&l!==(t.cop_curve||"")&&(e.goals={...t,cop_curve:l},this._sendDeviceUpdate(e.id,"cop_curve",l)),this.requestUpdate()},a=e=>null==e?"":String(e);return j`
${this._t("hp_cop_curve_section")}
- ${r?H`
${r}
`:G} - ${s.map((e,t)=>H` + ${i?j`
${i}
`:H} + ${s.map((e,t)=>j`
${this._t("hp_cop_point_at")} a(t,"c",e.target.value)} + .value="${a(e.c)}" + @change=${e=>r(t,"c",e.target.value)} @click=${e=>e.stopPropagation()}> \u00b0C ${this._t("hp_cop_point_is")} a(t,"cop",e.target.value)} + .value="${a(e.cop)}" + @change=${e=>r(t,"cop",e.target.value)} @click=${e=>e.stopPropagation()}>
`)}
${this._t("hp_cop_curve_hint")}
- ${this._renderCopSource(e)}`}_renderCopSource(e){const t=e.goals||{};if(!Object.prototype.hasOwnProperty.call(t,"cop_source"))return G;const s=function(e,t){if(!e||"no_meter"===e.state)return"";if("looks_electric"===e.state)return t("hp_cop_looks_electric");const s=String(Math.round(kr(e.hours)||0));return"learned"===e.state?t("hp_cop_learned_from").replace("{hours}",s).replace("{tmin}",Cr(kr(e.tmin))).replace("{tmax}",Cr(kr(e.tmax))):t("hp_cop_learning").replace("{hours}",s)}(t.cop_learn,e=>this._t(e)),i=Dr(t),r=function(e){const t=e||{},s=Dr(t),i=["flat"];return(Sr(t.cop_curve)||"curve"===s)&&i.push("curve"),"learned"!==t.cop_learn?.state&&"learned"!==s||i.push("learned"),i}(t);return H` - ${s?H`
${s}
`:G} - ${r.length>1?H` + ${this._renderCopSource(e)}`}_renderCopSource(e){const t=e.goals||{};if(!Object.prototype.hasOwnProperty.call(t,"cop_source"))return H;const s=Ca(t.cop_learn,e=>this._t(e)),i=ka(t),r=Sa(t);return j` + ${s?j`
${s}
`:H} + ${r.length>1?j`
${this._t("hp_cop_source")} -
`:G}`}_renderOwnTariff(e){const t=e.goals||{},s=H` + `:H}`}_renderOwnTariff(e){const t=e.goals||{},s=j`
${this._t("device_tariff_section")} -
`;if(String(e.id||"").startsWith("heat_pump"))return H`${s}
${this._t("device_tariff_heat_pump")}
`;const i=(t,s)=>{e.goals={...e.goals||{},[t]:s},this._sendDeviceUpdate(e.id,t,String(s))},r=(e,s,r)=>this.hass&&customElements.get("ha-entity-picker")?H` + `;if(String(e.id||"").startsWith("heat_pump"))return j`${s}
${this._t("device_tariff_heat_pump")}
`;const i=(t,s)=>{e.goals={...e.goals||{},[t]:s},this._sendDeviceUpdate(e.id,t,String(s))},r=(e,s,r)=>this.hass&&customElements.get("ha-entity-picker")?j` i(e,t.detail?.value??"")} @click=${e=>e.stopPropagation()}> - `:H` + `:j` i(e,t.target.value.trim())} - @click=${e=>e.stopPropagation()}>`,a=parseFloat(t.tariff_rate)||0;return H` + @click=${e=>e.stopPropagation()}>`,a=parseFloat(t.tariff_rate)||0;return j` ${s}
${this._t("device_tariff_rate")} @@ -7913,7 +8624,7 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow ${this._t("device_tariff_meter")} ${r("tariff_meter",["sensor"],"energy")}
-
${this._t("device_tariff_hint")}
`}_dragStart(e,t){const s=this.devices.find(e=>e.id===t);if(!s||s.dependsOn&&s.dependsOn.length)return;if(null!=e.button&&0!==e.button)return;e.preventDefault();const i=e.currentTarget,r=i.closest(".device"),a=this.renderRoot.getElementById("device-list");if(!r||!a)return;const o=Array.from(a.querySelectorAll(".device")),n=o.map(e=>{const t=e.getBoundingClientRect();return t.top+t.height/2});this._interacting=!0;const l=o.indexOf(r);this._drag={id:t,handle:i,row:r,rows:o,centres:n,pointerY0:e.clientY,fromIndex:l,dropIndex:l,pointerId:e.pointerId},r.classList.add("dragging-row");try{i.setPointerCapture(e.pointerId)}catch(e){}this._dragMoveBound=this._dragMoveBound||this._dragMove.bind(this),this._dragEndBound=this._dragEndBound||this._dragEnd.bind(this),window.addEventListener("pointermove",this._dragMoveBound),window.addEventListener("pointerup",this._dragEndBound),window.addEventListener("pointercancel",this._dragEndBound)}_dragMove(e){const t=this._drag;if(!t)return;e.preventDefault(),t.row.style.transform=`translateY(${e.clientY-t.pointerY0}px)`;const s=function(e,t,s){let i=0;for(let r=0;re[r]&&i++;return i}(t.centres,t.fromIndex,e.clientY);s!==t.dropIndex&&(t.dropIndex=s,this._paintDropLine(t,s))}_paintDropLine(e,t){e.rows.forEach(e=>e.classList.remove("drop-above","drop-below"));const s=e.rows[te.classList.remove("drop-above","drop-below")),this._interacting=!1,this._drag=null,"pointercancel"!==e.type&&t.dropIndex!==t.fromIndex&&this._moveDeviceToIndex(t.id,t.dropIndex)}}_moveDeviceToIndex(e,t){this.devices=function(e,t,s){const i=e.slice(),r=i.findIndex(e=>e.id===t);if(-1===r)return i;const[a]=i.splice(r,1),o=Math.max(0,Math.min(s,i.length));return i.splice(o,0,a),function(e){return e.forEach((e,t)=>{e.priority=t+1}),e}(yr(i))}(this.devices,e,t),this.requestUpdate(),this._sendPriorityUpdate()}_bindEvents(){const e=e=>{const t=e.target.closest("[data-action]");if(!t)return;const s=t.dataset.action,i=t.dataset.device;if("controllable"===s){const e=this.devices.find(e=>e.id===i);e&&(e.isControllable=!e.isControllable,this._sendDeviceUpdate(i,"controllable",e.isControllable),this.requestUpdate())}else if("move-up"===s||"move-down"===s)this._moveDevice(i,"move-up"===s?-1:1);else if("configure"===s)this._showConfigureModal(i,t.dataset.name);else if("toggle-help"===s)this._showHelp=!this._showHelp,this.requestUpdate();else if("toggle-goal"===s)this._goalOpen[i]=!this._goalOpen[i],this.requestUpdate();else if("set-overnight"===s){const e=this.devices.find(e=>e.id===i);e&&this._setOvernightSource(e,t.dataset.overnight)}},t=e=>{const t=e.target.closest('[data-action="combined_mode"]');if(t){const e=t.dataset.device,s=this.devices.find(t=>t.id===e);return void(s&&this._applyMergedMode(s,t.value))}const s=e.target.closest("[data-goal]");if(s){const e=s.dataset.device,t=s.dataset.goal;let i=s.value;const r=this.devices.find(t=>t.id===e);return r&&(r.goals=r.goals||{},r.goals[t]=i),void this._sendDeviceUpdate(e,t,String(i))}const i=e.target.closest('[data-action="depends_on"]');if(i){const e=i.dataset.device,t=this.devices.find(t=>t.id===e);if(t){const s=i.value;t.dependsOn=s?[s]:[],this._sendDeviceUpdate(e,"depends_on",s),this._regroupChildren(),this.devices.forEach((e,t)=>{e.priority=t+1}),this.requestUpdate(),this._sendPriorityUpdate()}}},s=this.renderRoot.querySelector("ha-card");s&&!s._semEvtBound&&(s._semEvtBound=!0,s.addEventListener("click",e),s.addEventListener("change",t))}_moveDevice(e,t){const s=this.devices.findIndex(t=>t.id===e);if(-1===s)return;const i=s+t;i<0||i>=this.devices.length||this.devices[s].dependsOn.length||this._moveDeviceToIndex(e,i)}_getDependencyDepth(e){let t=0,s=e;const i=new Set;for(;s.dependsOn.length>0&&t<5;){i.add(s.id);const e=s.dependsOn[0];if(i.has(e))break;const r=this.devices.find(t=>t.id===e);if(!r)break;t++,s=r}return t}_regroupChildren(){this.devices=yr(this.devices)}_showServiceError(e){const t=e&&e.message?String(e.message):String(e||"");this._serviceError=t,this.requestUpdate(),clearTimeout(this._serviceErrorTimer),this._serviceErrorTimer=setTimeout(()=>{this._serviceError="",this.requestUpdate()},12e3)}_sendPriorityUpdate(){this._hass&&(this._hass.callService("solar_energy_management","update_device_priorities",{priorities:this.devices.map(e=>({device_id:e.id,priority:e.priority}))}).catch(e=>this._showServiceError(e)),this._priorityFrozenUntil=Date.now()+15e3)}_sendDeviceUpdate(e,t,s){this._hass&&this._hass.callService("solar_energy_management","update_device_config",{device_id:e,property:t,value:s}).catch(e=>this._showServiceError(e))}_sendTargetPeakUpdate(e,t=!1,s=null){this._hass&&this._hass.callService("solar_energy_management","update_target_peak",{target_peak_limit:e,peak_limit_unlimited:t}).catch(e=>{s&&this._peakHold===s&&(this._peakHold=null),this._lastReads=null,this._showServiceError(e)})}async _ensureEntityPicker(){if(!customElements.get("ha-entity-picker"))try{const e=await(window.loadCardHelpers?.());if(e?.createCardElement){const t=await e.createCardElement({type:"entities",entities:[]});await(t.constructor?.getConfigElement?.())}}catch(e){}}async _showConfigureModal(e,t){const s=this.renderRoot.getElementById("sem-config-modal");s&&s.remove();const i=function(e,t){return(e||[]).find(e=>e.id===t)||null}(this.devices,e),r=i?.energySensor||"",a=function(e){const t=e||{};return{type:br.includes(t.type)?t.type:"switch",entity:t.entity||"",service:t.service||"",param:t.param||"current",shed_value:null!=t.shed_value?t.shed_value:0,restore_value:null!=t.restore_value?t.restore_value:16}}(i?.control),o="manual_mapping"===i?.control?.discovered_via;await this._ensureEntityPicker();const n=document.createElement("div");n.id="sem-config-modal",n.style.cssText="position:fixed;inset:0;background:rgba(0,0,0,0.6);z-index:999;display:flex;align-items:center;justify-content:center";const l=i&&!["ev_charger","ev_charging","battery"].includes(i.deviceType);n.innerHTML=function({deviceName:e,values:t,t:s,hasManual:i=!1,behindRelay:r=null,inUse:a="",ownName:o=null,autoName:n=""}){const l=t,c="service"===l.type,d=(e,t)=>``;return`\n
\n
${s("configure_device")}: ${$r(e)}
\n ${a?`
${$r(s("cfg_in_use_hint").replace(/\{entity\}/g,a))}
`:""}\n ${null===o?"":`\n \n \n
${s("cfg_own_name_hint")}
`}\n \n \n
\n \n \n
\n
\n \n \n \n \n
\n
\n \n \n
\n
\n \n \n
\n
\n
\n ${null===r?"":`\n
\n ${s("cfg_behind_operator_relay")}\n \n
\n
${s("cfg_help_behind_operator_relay")}
\n `}\n \n
\n ${i?``:""}\n
\n \n \n
\n
`}({deviceName:t,values:a,t:e=>this._t(e),hasManual:o,behindRelay:l?!!i.behindRelay:null,ownName:l?i.goals&&i.goals.name||"":null,autoName:l&&(i.goals&&i.goals.automatic_name||i.name)||"",inUse:o||a.entity?"":i?.controlEntity||""}),this.renderRoot.appendChild(n);const c=n.querySelector("#cfg-type"),d=n.querySelector("#cfg-entity-group"),p=n.querySelector("#cfg-service-group"),_=n.querySelector("#cfg-entity"),h=n.querySelector(".cfg-error"),u=e=>{h.textContent=e,h.style.display="block"};let g=null;customElements.get("ha-entity-picker")&&(g=document.createElement("ha-entity-picker"),g.hass=this._hass,g.allowCustomEntity=!0,g.value=_.value,g.includeDomains=xr[c.value],g.style.cssText="display:block;margin:6px 0 12px",g.addEventListener("value-changed",e=>{_.value=e.detail.value||""}),_.style.display="none",_.parentNode.insertBefore(g,_));c.addEventListener("change",()=>{const e="service"===c.value;d.style.display=e?"none":"block",p.style.display=e?"block":"none",g&&!e&&(g.includeDomains=xr[c.value])});const m=n.querySelector("#cfg-relay");m&&i&&m.addEventListener("click",()=>{const e=!m.classList.contains("on");m.classList.toggle("on",e),m.setAttribute("aria-checked",String(e)),i.behindRelay=e,this._sendDeviceUpdate(i.id,"behind_operator_relay",String(e))});const f=n.querySelector("#cfg-name");f&&i&&f.addEventListener("change",()=>{const e=(t=f.value,String(null==t?"":t).replace(/\s+/g," ").trim().slice(0,60).trim());var t;if(f.value=e,i.goals=i.goals||{},(i.goals.name||"")===e)return;const s=i.goals.automatic_name||f.placeholder||i.name;i.goals.name=e,i.name=e||s,this._sendDeviceUpdate(i.id,"name",e),this.requestUpdate()}),n.addEventListener("click",e=>{e.target===n&&n.remove()}),n.querySelector("#cfg-cancel").addEventListener("click",()=>n.remove());const v=n.querySelector("#cfg-remove");v&&v.addEventListener("click",()=>{this._hass.callService("solar_energy_management","remove_device_control_mapping",{energy_sensor:r}).then(()=>{i&&(i.control=null,this.requestUpdate()),n.remove()}).catch(e=>u(e.message))}),n.querySelector("#cfg-save").addEventListener("click",()=>{const e=function(e){const t=t=>e.querySelector("#"+t);return{type:t("cfg-type")?.value||"switch",entity:(t("cfg-entity")?.value||"").trim(),service:(t("cfg-service")?.value||"").trim(),param:(t("cfg-param")?.value||"").trim()||"current",shed_value:t("cfg-shed")?.value,restore_value:t("cfg-restore")?.value}}(n);let t;g&&(e.entity=(g.value||"").trim());try{t=function(e,t){const s=br.includes(t.type)?t.type:"switch";if("service"===s){if(!t.service)throw new Error("mapping_service_required");return{energy_sensor:e,control_type:"service",service:t.service,param:t.param||"current",shed_value:Number(t.shed_value),restore_value:Number(t.restore_value)}}if(!t.entity)throw new Error("mapping_entity_required");return{energy_sensor:e,control_type:s,control_entity:t.entity}}(r,e)}catch(e){return void u(this._t(e.message))}this._hass.callService("solar_energy_management","set_device_control_mapping",t).then(()=>{i&&(i.control="service"===t.control_type?{type:"service",service:t.service,param:t.param,shed_value:t.shed_value,restore_value:t.restore_value,discovered_via:"manual_mapping"}:{type:t.control_type,entity:t.control_entity,discovered_via:"manual_mapping"},this.requestUpdate()),n.remove()}).catch(e=>u(e.message))})}_getPeakColor(){if(this.peakLimitUnlimited)return"#4caf50";if(null==this.currentPeak)return"#888";const e=this.targetPeakLimit>0?this.currentPeak/this.targetPeakLimit*100:0;return e>=100?"#f44336":e>=90?"#ff9800":e>=70?"#2196f3":"#4caf50"}_resolveDeviceIcon(e){for(const t of[e.switch_entity,e.power_entity,e.control?.entity])if(t){const e=this._hass?.states[t];if(e?.attributes?.icon)return e.attributes.icon}return{ev_charger:"mdi:ev-station",ev_charging:"mdi:ev-station",battery:"mdi:home-battery",heating:"mdi:radiator",heat_pump:"mdi:heat-pump",water_heater:"mdi:water-boiler",hot_water:"mdi:water-boiler",pool_pump:"mdi:pool",appliance:"mdi:washing-machine",climate:"mdi:thermostat",light:"mdi:lightbulb",printer:"mdi:printer",computer:"mdi:desktop-classic",network:"mdi:network",fan:"mdi:fan",dryer:"mdi:tumble-dryer",dishwasher:"mdi:dishwasher",freezer:"mdi:fridge",refrigerator:"mdi:fridge"}[e.device_type]||"mdi:power-plug"}getCardSize(){return Math.max(5,3+this.devices.length)}static getStubConfig(){return{entity_prefix:"sensor.sem_"}}},{type:"sem-load-priority-card",name:"SEM Load Priority Card",description:"Drag and drop interface for managing load shedding priorities",documentationURL:"https://github.com/traktore-org/sem-community/blob/develop/docs/DASHBOARD_GUIDE.md#sem-load-priority-card",preview:!0});const Tr=[{id:"surplus",icon:"mdi:solar-power",color:"#ff9800",titleKey:"surplus_control",subtitleFn:e=>{const t=Ae(e._valNum("surplus_active_devices"),0),s=Ae(e._valNum("surplus_total_devices"),0),i=Ae(e._valNum("surplus_allocated_w"),0);return`${t}/${s} ${e._t("devices")} · ${i}W`}},{id:"battery",icon:"mdi:battery-medium",color:"#4db6ac",titleKey:"battery_management",subtitleFn:e=>{const t=Be(e._valNum("battery_soc",null)).label,s=e._val("battery_status")||"";return`SOC ${t} · ${s?e._t(s.toLowerCase()):"—"}`}},{id:"hotwater",icon:"mdi:water-thermometer",color:"#f06292",titleKey:"hot_water_title",subtitleFn:()=>""},{id:"heatpump",icon:"mdi:heat-pump",color:"#4db6ac",titleKey:"heat_pump_title",subtitleFn:e=>{if(!e._bin("heat_pump_registered"))return"";const t=e._val("heat_pump_sg_ready_state");return t?`${e._valLabel("heat_pump_mode")||"—"} · SG-Ready ${t}`:""}},{id:"tariff",icon:"mdi:cash-multiple",color:"#96CAEE",titleKey:"tariff_pricing",subtitleFn:e=>{const t=e._valLabel("tariff_provider")||"—",s=e._val("tariff_price_level")||"";return s?`${t} · ${e._t(Yt(s))}`:t}},{id:"peak",icon:"mdi:flash-alert",color:"#ff9800",titleKey:"peak_load_management",subtitleFn:e=>{const t=Ae(e._valNum("consecutive_peak_15min"),1),s=Ae(e._valNum("monthly_consecutive_peak"),1);return`${e._t("peak")} ${t} kW · ${e._t("monthly_peak")} ${s} kW`}}],Ar=["sensor.sem_surplus_active_devices","sensor.sem_surplus_total_devices","sensor.sem_surplus_allocated_w","sensor.sem_surplus_total_w","sensor.sem_surplus_distributable_w","sensor.sem_surplus_unallocated_w","sensor.sem_battery_soc","sensor.sem_battery_status","sensor.sem_diag_battery_capacity","number.sem_legionella_target_temp","binary_sensor.sem_heat_pump_registered","sensor.sem_heat_pump_sg_ready_state","sensor.sem_heat_pump_mode","binary_sensor.sem_heat_pump_solar_boost","sensor.sem_tariff_provider","sensor.sem_tariff_price_level","sensor.sem_tariff_current_import_rate","sensor.sem_consecutive_peak_15min","sensor.sem_monthly_consecutive_peak","sensor.sem_current_vs_peak_percentage","sensor.sem_load_management_status","sensor.sem_load_management_recommendation","sensor.sem_peak_margin","sensor.sem_available_load_reduction","sensor.sem_controllable_devices_count","switch.sem_observer_mode","sensor.sem_target_peak_limit","sensor.sem_diag_grid_sign"],Br={"sensor.sem_target_peak_limit":["peak_limit_unlimited"],"sensor.sem_load_management_status":["shed_signal_active","shed_signal_cap_kw","shed_signal_since"]};ke("sem-control-card",class extends Le{static get watchedEntities(){return Ar}static get watchedAttributes(){return Br}static get properties(){return{...super.properties}}constructor(){super(),this._collapsed={surplus:!0,battery:!1,hotwater:!0,heatpump:!0,tariff:!1,peak:!0}}setConfig(e){super.setConfig(e),this._prefix=e.entity_prefix||"sensor.sem_"}_val(e){const t=this._hass?.states[`${this._prefix}${e}`];return t&&"unavailable"!==t.state&&"unknown"!==t.state?t.state:""}_valNum(e,t=null){return Te(this._hass?.states[`${this._prefix}${e}`])??t}_numVal(e,t=null){return Te(this._hass?.states[`number.sem_${e}`])??t}_switchOn(e){return"on"===this._hass?.states[`switch.sem_${e}`]?.state}_bin(e){return"on"===this._hass?.states[`binary_sensor.sem_${e}`]?.state}_toggleSection(e){this._collapsed={...this._collapsed,[e]:!this._collapsed[e]},this.requestUpdate()}_renderStepper(e,t,s){const i=this._hass?.states[e];if(!i)return G;const r=this._frozenEntities[e],a=r?r.value:Te(i),o=parseFloat(i.attributes.step)||1,n=i.attributes.unit_of_measurement||"",l=Ae(a,o<1?1:0)+(n?" "+n:"");return H` +
${this._t("device_tariff_hint")}
`}_dragStart(e,t){const s=this.devices.find(e=>e.id===t);if(!s||s.dependsOn&&s.dependsOn.length)return;if(null!=e.button&&0!==e.button)return;e.preventDefault();const i=e.currentTarget,r=i.closest(".device"),a=this.renderRoot.getElementById("device-list");if(!r||!a)return;const n=Array.from(a.querySelectorAll(".device")),o=n.map(e=>{const t=e.getBoundingClientRect();return t.top+t.height/2});this._interacting=!0;const l=n.indexOf(r);this._drag={id:t,handle:i,row:r,rows:n,centres:o,pointerY0:e.clientY,fromIndex:l,dropIndex:l,pointerId:e.pointerId},r.classList.add("dragging-row");try{i.setPointerCapture(e.pointerId)}catch(e){}this._dragMoveBound=this._dragMoveBound||this._dragMove.bind(this),this._dragEndBound=this._dragEndBound||this._dragEnd.bind(this),window.addEventListener("pointermove",this._dragMoveBound),window.addEventListener("pointerup",this._dragEndBound),window.addEventListener("pointercancel",this._dragEndBound)}_dragMove(e){const t=this._drag;if(!t)return;e.preventDefault(),t.row.style.transform=`translateY(${e.clientY-t.pointerY0}px)`;const s=Wi(t.centres,t.fromIndex,e.clientY);s!==t.dropIndex&&(t.dropIndex=s,this._paintDropLine(t,s))}_paintDropLine(e,t){e.rows.forEach(e=>e.classList.remove("drop-above","drop-below"));const s=e.rows[te.classList.remove("drop-above","drop-below")),this._interacting=!1,this._drag=null,"pointercancel"!==e.type&&t.dropIndex!==t.fromIndex&&this._moveDeviceToIndex(t.id,t.dropIndex)}}_moveDeviceToIndex(e,t){this.devices=Ri(this.devices,e,t),this.requestUpdate(),this._sendPriorityUpdate()}_bindEvents(){const e=e=>{const t=e.target.closest("[data-action]");if(!t)return;const s=t.dataset.action,i=t.dataset.device;if("controllable"===s){const e=this.devices.find(e=>e.id===i);e&&(e.isControllable=!e.isControllable,this._sendDeviceUpdate(i,"controllable",e.isControllable),this.requestUpdate())}else if("move-up"===s||"move-down"===s)this._moveDevice(i,"move-up"===s?-1:1);else if("configure"===s)this._showConfigureModal(i,t.dataset.name);else if("toggle-help"===s)this._showHelp=!this._showHelp,this.requestUpdate();else if("toggle-goal"===s)this._goalOpen[i]=!this._goalOpen[i],this.requestUpdate();else if("set-overnight"===s){const e=this.devices.find(e=>e.id===i);e&&this._setOvernightSource(e,t.dataset.overnight)}},t=e=>{const t=e.target.closest('[data-action="combined_mode"]');if(t){const e=t.dataset.device,s=this.devices.find(t=>t.id===e);return void(s&&this._applyMergedMode(s,t.value))}const s=e.target.closest("[data-goal]");if(s){const e=s.dataset.device,t=s.dataset.goal;let i=s.value;const r=this.devices.find(t=>t.id===e);return r&&(r.goals=r.goals||{},r.goals[t]=i),void this._sendDeviceUpdate(e,t,String(i))}const i=e.target.closest('[data-action="depends_on"]');if(i){const e=i.dataset.device,t=this.devices.find(t=>t.id===e);if(t){const s=i.value;t.dependsOn=s?[s]:[],this._sendDeviceUpdate(e,"depends_on",s),this._regroupChildren(),this.devices.forEach((e,t)=>{e.priority=t+1}),this.requestUpdate(),this._sendPriorityUpdate()}}},s=this.renderRoot.querySelector("ha-card");s&&!s._semEvtBound&&(s._semEvtBound=!0,s.addEventListener("click",e),s.addEventListener("change",t))}_moveDevice(e,t){const s=this.devices.findIndex(t=>t.id===e);if(-1===s)return;const i=s+t;i<0||i>=this.devices.length||this.devices[s].dependsOn.length||this._moveDeviceToIndex(e,i)}_getDependencyDepth(e){let t=0,s=e;const i=new Set;for(;s.dependsOn.length>0&&t<5;){i.add(s.id);const e=s.dependsOn[0];if(i.has(e))break;const r=this.devices.find(t=>t.id===e);if(!r)break;t++,s=r}return t}_regroupChildren(){this.devices=Ii(this.devices)}_showServiceError(e){const t=e&&e.message?String(e.message):String(e||"");this._serviceError=t,this.requestUpdate(),clearTimeout(this._serviceErrorTimer),this._serviceErrorTimer=setTimeout(()=>{this._serviceError="",this.requestUpdate()},12e3)}_sendPriorityUpdate(){this._hass&&(this._hass.callService("solar_energy_management","update_device_priorities",{priorities:this.devices.map(e=>({device_id:e.id,priority:e.priority}))}).catch(e=>this._showServiceError(e)),this._priorityFrozenUntil=Date.now()+15e3)}_sendDeviceUpdate(e,t,s){this._hass&&this._hass.callService("solar_energy_management","update_device_config",{device_id:e,property:t,value:s}).catch(e=>this._showServiceError(e))}_sendTargetPeakUpdate(e,t=!1,s=null){this._hass&&this._hass.callService("solar_energy_management","update_target_peak",{target_peak_limit:e,peak_limit_unlimited:t}).catch(e=>{s&&this._peakHold===s&&(this._peakHold=null),this._lastReads=null,this._showServiceError(e)})}async _ensureEntityPicker(){if(!customElements.get("ha-entity-picker"))try{const e=await(window.loadCardHelpers?.());if(e?.createCardElement){const t=await e.createCardElement({type:"entities",entities:[]});await(t.constructor?.getConfigElement?.())}}catch(e){}}async _showConfigureModal(e,t){const s=this.renderRoot.getElementById("sem-config-modal");s&&s.remove();const i=jr(this.devices,e),r=i?.energySensor||"",a=Kr(i?.control),n="manual_mapping"===i?.control?.discovered_via;await this._ensureEntityPicker();const o=document.createElement("div");o.id="sem-config-modal",o.style.cssText="position:fixed;inset:0;background:rgba(0,0,0,0.6);z-index:999;display:flex;align-items:center;justify-content:center";const l=i&&!["ev_charger","ev_charging","battery"].includes(i.deviceType);o.innerHTML=Vr({deviceName:t,values:a,t:e=>this._t(e),hasManual:n,behindRelay:l?!!i.behindRelay:null,ownName:l?i.goals&&i.goals.name||"":null,autoName:l&&(i.goals&&i.goals.automatic_name||i.name)||"",inUse:n||a.entity?"":i?.controlEntity||""}),this.renderRoot.appendChild(o);const c=o.querySelector("#cfg-type"),_=o.querySelector("#cfg-entity-group"),d=o.querySelector("#cfg-service-group"),p=o.querySelector("#cfg-entity"),h=o.querySelector(".cfg-error"),u=e=>{h.textContent=e,h.style.display="block"};let m=null;customElements.get("ha-entity-picker")&&(m=document.createElement("ha-entity-picker"),m.hass=this._hass,m.allowCustomEntity=!0,m.value=p.value,m.includeDomains=Ur[c.value],m.style.cssText="display:block;margin:6px 0 12px",m.addEventListener("value-changed",e=>{p.value=e.detail.value||""}),p.style.display="none",p.parentNode.insertBefore(m,p));c.addEventListener("change",()=>{const e="service"===c.value;_.style.display=e?"none":"block",d.style.display=e?"block":"none",m&&!e&&(m.includeDomains=Ur[c.value])});const g=o.querySelector("#cfg-relay");g&&i&&g.addEventListener("click",()=>{const e=!g.classList.contains("on");g.classList.toggle("on",e),g.setAttribute("aria-checked",String(e)),i.behindRelay=e,this._sendDeviceUpdate(i.id,"behind_operator_relay",String(e))});const f=o.querySelector("#cfg-name");f&&i&&f.addEventListener("change",()=>{const e=(t=f.value,String(null==t?"":t).replace(/\s+/g," ").trim().slice(0,60).trim());var t;if(f.value=e,i.goals=i.goals||{},(i.goals.name||"")===e)return;const s=i.goals.automatic_name||f.placeholder||i.name;i.goals.name=e,i.name=e||s,this._sendDeviceUpdate(i.id,"name",e),this.requestUpdate()}),o.addEventListener("click",e=>{e.target===o&&o.remove()}),o.querySelector("#cfg-cancel").addEventListener("click",()=>o.remove());const y=o.querySelector("#cfg-remove");y&&y.addEventListener("click",()=>{this._hass.callService("solar_energy_management","remove_device_control_mapping",{energy_sensor:r}).then(()=>{i&&(i.control=null,this.requestUpdate()),o.remove()}).catch(e=>u(e.message))}),o.querySelector("#cfg-save").addEventListener("click",()=>{const e=Yr(o);let t;m&&(e.entity=(m.value||"").trim());try{t=Hr(r,e)}catch(e){return void u(this._t(e.message))}this._hass.callService("solar_energy_management","set_device_control_mapping",t).then(()=>{i&&(i.control="service"===t.control_type?{type:"service",service:t.service,param:t.param,shed_value:t.shed_value,restore_value:t.restore_value,discovered_via:"manual_mapping"}:{type:t.control_type,entity:t.control_entity,discovered_via:"manual_mapping"},this.requestUpdate()),o.remove()}).catch(e=>u(e.message))})}_getPeakColor(){if(this.peakLimitUnlimited)return"#4caf50";if(null==this.currentPeak)return"#888";const e=this.targetPeakLimit>0?this.currentPeak/this.targetPeakLimit*100:0;return e>=100?"#f44336":e>=90?"#ff9800":e>=70?"#2196f3":"#4caf50"}_resolveDeviceIcon(e){for(const t of[e.switch_entity,e.power_entity,e.control?.entity])if(t){const e=this._hass?.states[t];if(e?.attributes?.icon)return e.attributes.icon}return{ev_charger:"mdi:ev-station",ev_charging:"mdi:ev-station",battery:"mdi:home-battery",heating:"mdi:radiator",heat_pump:"mdi:heat-pump",water_heater:"mdi:water-boiler",hot_water:"mdi:water-boiler",pool_pump:"mdi:pool",appliance:"mdi:washing-machine",climate:"mdi:thermostat",light:"mdi:lightbulb",printer:"mdi:printer",computer:"mdi:desktop-classic",network:"mdi:network",fan:"mdi:fan",dryer:"mdi:tumble-dryer",dishwasher:"mdi:dishwasher",freezer:"mdi:fridge",refrigerator:"mdi:fridge"}[e.device_type]||"mdi:power-plug"}getCardSize(){return Math.max(5,3+this.devices.length)}static getStubConfig(){return{entity_prefix:"sensor.sem_"}}},{type:"sem-load-priority-card",name:"SEM Load Priority Card",description:"Drag and drop interface for managing load shedding priorities",documentationURL:"https://github.com/traktore-org/sem-community/blob/develop/docs/DASHBOARD_GUIDE.md#sem-load-priority-card",preview:!0});const xc=[{id:"surplus",icon:"mdi:solar-power",color:"#ff9800",titleKey:"surplus_control",subtitleFn:e=>{const t=Te(e._valNum("surplus_active_devices"),0),s=Te(e._valNum("surplus_total_devices"),0),i=Te(e._valNum("surplus_allocated_w"),0);return`${t}/${s} ${e._t("devices")} · ${i}W`}},{id:"battery",icon:"mdi:battery-medium",color:"#4db6ac",titleKey:"battery_management",subtitleFn:e=>{const t=Oe(e._valNum("battery_soc",null)).label,s=e._val("battery_status")||"";return`SOC ${t} · ${s?e._t(s.toLowerCase()):"—"}`}},{id:"hotwater",icon:"mdi:water-thermometer",color:"#f06292",titleKey:"hot_water_title",subtitleFn:()=>""},{id:"heatpump",icon:"mdi:heat-pump",color:"#4db6ac",titleKey:"heat_pump_title",subtitleFn:e=>{if(!e._bin("heat_pump_registered"))return"";const t=e._val("heat_pump_sg_ready_state");return t?`${e._valLabel("heat_pump_mode")||"—"} · SG-Ready ${t}`:""}},{id:"tariff",icon:"mdi:cash-multiple",color:"#96CAEE",titleKey:"tariff_pricing",subtitleFn:e=>{const t=e._valLabel("tariff_provider")||"—",s=e._val("tariff_price_level")||"";return s?`${t} · ${e._t(Gt(s))}`:t}},{id:"peak",icon:"mdi:flash-alert",color:"#ff9800",titleKey:"peak_load_management",subtitleFn:e=>{const t=Te(e._valNum("consecutive_peak_15min"),1),s=Te(e._valNum("monthly_consecutive_peak"),1);return`${e._t("peak")} ${t} kW · ${e._t("monthly_peak")} ${s} kW`}}],$c=["sensor.sem_surplus_active_devices","sensor.sem_surplus_total_devices","sensor.sem_surplus_allocated_w","sensor.sem_surplus_total_w","sensor.sem_surplus_distributable_w","sensor.sem_surplus_unallocated_w","sensor.sem_battery_soc","sensor.sem_battery_status","sensor.sem_diag_battery_capacity","number.sem_legionella_target_temp","binary_sensor.sem_heat_pump_registered","sensor.sem_heat_pump_sg_ready_state","sensor.sem_heat_pump_mode","binary_sensor.sem_heat_pump_solar_boost","sensor.sem_tariff_provider","sensor.sem_tariff_price_level","sensor.sem_tariff_current_import_rate","sensor.sem_consecutive_peak_15min","sensor.sem_monthly_consecutive_peak","sensor.sem_current_vs_peak_percentage","sensor.sem_load_management_status","sensor.sem_load_management_recommendation","sensor.sem_peak_margin","sensor.sem_available_load_reduction","sensor.sem_controllable_devices_count","switch.sem_observer_mode","sensor.sem_target_peak_limit","sensor.sem_diag_grid_sign"],wc={"sensor.sem_target_peak_limit":["peak_limit_unlimited"],"sensor.sem_load_management_status":["shed_signal_active","shed_signal_cap_kw","shed_signal_since"]};ke("sem-control-card",class extends Re{static get watchedEntities(){return $c}static get watchedAttributes(){return wc}static get properties(){return{...super.properties}}constructor(){super(),this._collapsed={surplus:!0,battery:!1,hotwater:!0,heatpump:!0,tariff:!1,peak:!0}}setConfig(e){super.setConfig(e),this._prefix=e.entity_prefix||"sensor.sem_"}_val(e){const t=this._hass?.states[`${this._prefix}${e}`];return t&&"unavailable"!==t.state&&"unknown"!==t.state?t.state:""}_valNum(e,t=null){return Ae(this._hass?.states[`${this._prefix}${e}`])??t}_numVal(e,t=null){return Ae(this._hass?.states[`number.sem_${e}`])??t}_switchOn(e){return"on"===this._hass?.states[`switch.sem_${e}`]?.state}_bin(e){return"on"===this._hass?.states[`binary_sensor.sem_${e}`]?.state}_toggleSection(e){this._collapsed={...this._collapsed,[e]:!this._collapsed[e]},this.requestUpdate()}_renderStepper(e,t,s){const i=this._hass?.states[e];if(!i)return H;const r=this._frozenEntities[e],a=r?r.value:Ae(i),n=parseFloat(i.attributes.step)||1,o=i.attributes.unit_of_measurement||"",l=Te(a,n<1?1:0)+(o?" "+o:"");return j`
${this._t(t)} @@ -7936,7 +8647,7 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow
- `}_renderSelect(e,t,s){const i=this._hass?.states[e];if(!i)return G;const r=this._frozenEntities[e],a=r?r.value:i.state,o=i.attributes.options||[];return H` + `}_renderSelect(e,t,s){const i=this._hass?.states[e];if(!i)return H;const r=this._frozenEntities[e],a=r?r.value:i.state,n=i.attributes.options||[];return j`
${this._t(t)}
- `}_renderSurplusSection(e){const t=Ae(this._valNum("surplus_total_w"),0),s=Ae(this._valNum("surplus_distributable_w"),0),i=Ae(this._valNum("surplus_allocated_w"),0),r=Ae(this._valNum("surplus_unallocated_w"),0);return H` + `}_renderSurplusSection(e){const t=Te(this._valNum("surplus_total_w"),0),s=Te(this._valNum("surplus_distributable_w"),0),i=Te(this._valNum("surplus_allocated_w"),0),r=Te(this._valNum("surplus_unallocated_w"),0);return j`
@@ -7972,7 +8683,7 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow
- `}_renderBatterySection(e){const t=this._valNum("diag_battery_capacity"),s=t>0?t.toFixed(1)+" kWh":"—",i=this._valLabel("diag_grid_sign")||"—";return H` + `}_renderBatterySection(e){const t=this._valNum("diag_battery_capacity"),s=t>0?t.toFixed(1)+" kWh":"—",i=this._valLabel("diag_grid_sign")||"—";return j`
${this._t("capacity_kwh")} ${s} @@ -7981,9 +8692,9 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow ${this._t("grid_sign")} ${i}
- `}_renderHeatPumpSection(e){if(!this._bin("heat_pump_registered"))return H`
+ `}_renderHeatPumpSection(e){if(!this._bin("heat_pump_registered"))return j`
${this._t("heat_pump_not_configured")} -
`;const t=this._val("heat_pump_sg_ready_state"),s=this._valLabel("heat_pump_mode")||"—",i=this._switchOn("heat_pump_solar_boost")?"#ff9800":"#4db6ac";return H` +
`;const t=this._val("heat_pump_sg_ready_state"),s=this._valLabel("heat_pump_mode")||"—",i=this._switchOn("heat_pump_solar_boost")?"#ff9800":"#4db6ac";return j`
${this._t("heat_pump_mode")} @@ -7994,7 +8705,7 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow ${this._t("heat_pump_sg_ready_state")} ${t}
- `}_renderHotWaterSection(e){return t=this._hass?.states,t&&t["number.sem_legionella_target_temp"]?H` + `}_renderHotWaterSection(e){return t=this._hass?.states,t&&t["number.sem_legionella_target_temp"]?j` ${this._renderStepper("number.sem_legionella_target_temp","legionella_target",e)}
@@ -8005,17 +8716,17 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow
- `:H`
+ `:j`
${this._t("hot_water_not_configured")} -
`;var t}_renderTariffSection(e){const t=this._hass?.states["sensor.sem_tariff_current_import_rate"],s=t?t.state:"—",i=t?.attributes?.unit_of_measurement||"";return H` +
`;var t}_renderTariffSection(e){const t=this._hass?.states["sensor.sem_tariff_current_import_rate"],s=t?t.state:"—",i=t?.attributes?.unit_of_measurement||"";return j`
${this._t("current_electricity_price")} ${s} ${i}
- `}_renderPeakSection(e){const t=this._valNum("current_vs_peak_percentage"),s=t>90?"#f44336":t>70?"#ff9800":"#8DC892",i=Ae(this._valNum("controllable_devices_count"),0),r=this._valNum("available_load_reduction"),a=He(this._hass?.states[`${this._prefix}target_peak_limit`]);return H` + `}_renderPeakSection(e){const t=this._valNum("current_vs_peak_percentage"),s=t>90?"#f44336":t>70?"#ff9800":"#8DC892",i=Te(this._valNum("controllable_devices_count"),0),r=this._valNum("available_load_reduction"),a=je(this._hass?.states[`${this._prefix}target_peak_limit`]);return j`
- ${a?H`${this._t(et)}`:H`${Ae(t,0)}% + ${a?j`${this._t(st)}`:j`${Te(t,0)}% ${this._t("of_limit")}`}
@@ -8028,18 +8739,18 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow
${this._t("peak_margin")} - ${a?this._t(et):`${Ae(this._valNum("peak_margin"),1)} kW`} + ${a?this._t(st):`${Te(this._valNum("peak_margin"),1)} kW`}
${this._t("sheddable")} - ${Ae(r,1)} kW · ${i} ${this._t("devices")} + ${Te(r,1)} kW · ${i} ${this._t("devices")}
- `}_renderSectionHeader(e,t){const s=this._collapsed[e.id],i=s?"rotate(-90deg)":"rotate(0deg)";return H` + `}_renderSectionHeader(e,t){const s=this._collapsed[e.id],i=s?"rotate(-90deg)":"rotate(0deg)";return j`
- `}_renderSection(e,t,s){const i=this._collapsed[e.id];return H` + `}_renderSection(e,t,s){const i=this._collapsed[e.id];return j`
${this._renderSectionHeader(e,s)} @@ -8067,7 +8778,7 @@ const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow
- `}render(){if(!this._config)return G;const e=this._theme(),t=!1!==e.isDark,s=e.accent||"#42a5f5",i=this._switchOn("observer_mode"),r=this._hass?.states?.["sensor.sem_load_management_status"]?.attributes||{},a=!0===r.shed_signal_active,o=a?this._operatorBanner(r):"",n={surplus:e=>this._renderSurplusSection(e),battery:e=>this._renderBatterySection(e),hotwater:e=>this._renderHotWaterSection(e),heatpump:e=>this._renderHeatPumpSection(e),tariff:e=>this._renderTariffSection(e),peak:e=>this._renderPeakSection(e)};return H` + `}render(){if(!this._config)return H;const e=this._theme(),t=!1!==e.isDark,s=e.accent||"#42a5f5",i=this._switchOn("observer_mode"),r=this._hass?.states?.["sensor.sem_load_management_status"]?.attributes||{},a=!0===r.shed_signal_active,n=a?this._operatorBanner(r):"",o={surplus:e=>this._renderSurplusSection(e),battery:e=>this._renderBatterySection(e),hotwater:e=>this._renderHotWaterSection(e),heatpump:e=>this._renderHeatPumpSection(e),tariff:e=>this._renderTariffSection(e),peak:e=>this._renderPeakSection(e)};return j`