feat: add heating support with heat pump modeling and cold risk detection

Model heating mode when rooms have net heat loss in cold weather (<10°C).
AC units with heat pump capability (canHeat) provide heating capacity,
with the same 20% headroom threshold used for cooling. Adds cold risk
detection, cold-weather actions, and full frontend support including
heating mode timeline colors, room budget heating display, and i18n.
This commit is contained in:
2026-02-11 00:00:43 +01:00
parent 94798631bc
commit 21154d5d7f
22 changed files with 1087 additions and 118 deletions

View File

@@ -614,7 +614,8 @@
const roomIds = assignments.filter(a => a.acId === u.id).map(a => a.roomId);
const roomNames = roomIds.map(id => roomMap[id] || `Room ${id}`).join(", ");
el.querySelector('[data-slot="name"]').textContent = u.name;
el.querySelector('[data-slot="details"]').textContent = `${u.capacityBtu} BTU \u00b7 ${u.acType}${roomNames ? ' \u00b7 ' + roomNames : ''}`;
const heatInfo = u.canHeat ? ` \u00b7 Heat ${u.heatingCapacityBtu || u.capacityBtu} BTU` : '';
el.querySelector('[data-slot="details"]').textContent = `${u.capacityBtu} BTU \u00b7 ${u.acType}${heatInfo}${roomNames ? ' \u00b7 ' + roomNames : ''}`;
el.firstElementChild.dataset.id = u.id;
list.appendChild(el);
}
@@ -630,6 +631,8 @@
form.querySelector('input[name="capacityBtu"]').value = u.capacityBtu || 0;
form.querySelector('input[name="efficiencyEer"]').value = u.efficiencyEer || 10;
form.querySelector('input[name="hasDehumidify"]').checked = !!u.hasDehumidify;
form.querySelector('input[name="canHeat"]').checked = !!u.canHeat;
form.querySelector('input[name="heatingCapacityBtu"]').value = u.heatingCapacityBtu || 0;
// Check assigned rooms
const assignments = await dbGetAll("ac_assignments");
const assignedRoomIds = new Set(assignments.filter(a => a.acId === id).map(a => a.roomId));
@@ -674,6 +677,8 @@
capacityBtu: numOrDefault(data.capacityBtu, 0),
efficiencyEer: numOrDefault(data.efficiencyEer, 10),
hasDehumidify: !!data.hasDehumidify,
canHeat: !!data.canHeat,
heatingCapacityBtu: numOrDefault(data.heatingCapacityBtu, 0),
};
let acId;
if (data.id) {