/* Hearth — one fixture for the whole product.
   Every screen reads these arrays; no screen writes a second literal to match.
   The clock is frozen so the derived flags are reproducible: Tuesday 6 October
   2026, 19:10 at Blackthorn House. Dark since 18:20, the peak tariff window shut
   ten minutes ago, one child is still up, and the back door is unlocked.

   The log, the requests and the notifications are authored AGAINST these
   records: an entry that names a door, a filter or a quote agrees with that
   record's status and timeline at the moment it was written. */

const HEARTH_NOW = Date.parse("2026-10-06T19:10:00Z");
const minsAgo = (m) => HEARTH_NOW - m * MINUTE_MS;
const hoursAgo = (h) => HEARTH_NOW - h * HOUR_MS;
const hoursOn = (h) => HEARTH_NOW + h * HOUR_MS;
const daysAgo = (d) => HEARTH_NOW - d * DAY_MS;
const daysOn = (d) => HEARTH_NOW + d * DAY_MS;
const dayAt = (offset, hour, minute) => {
  const d = new Date(HEARTH_NOW + offset * DAY_MS);
  d.setHours(hour, minute || 0, 0, 0);
  return d.getTime();
};

const RESIDENCE = {
  name: "Blackthorn House",
  short: "Blackthorn",
  address: "Blackthorn House, Bovingdon Lane, Marlow SL7",
  built: 1908,
  bedrooms: 6,
  floorAreaSqm: 412,
  timezone: "Europe/London",
  epc: "D",
  responseNumber: "0800 118 4400",
  insurer: "Cranmore Private Clients",
  policyNumber: "CPC-8841-22",
};

const HEARTH_SIGNED_IN = "mem_01";

/* The alarm, held on the house rather than on a device: it is one state the
   whole product reads. */
const HOUSE_STATE = {
  alarm: "disarmed",
  alarmChangedAt: hoursAgo(9.5),
  alarmChangedById: "mem_01",
  lastArmedAwayAt: daysAgo(4),
  responseCalled: false,
};

/* ── The household ───────────────────────────────────────────────────────── */
const MEMBERS = [
  { id: "mem_01", name: "Sofia Brandt", role: "owner", status: "active", home: true, avatar: "../../assets/img/avatar-f-2.png", email: "sofia@blackthorn.house", phone: "07700 900 141", since: daysAgo(2190), lastSeenAt: minsAgo(4), where: "study",
    prefs: { comfortC: 20.5, bedtime: "23:00", wake: "06:30", lightsOnArrival: true, notifyAlarm: true, notifyDeliveries: true } },
  { id: "mem_02", name: "Adam Brandt", role: "adult", status: "active", home: false, avatar: "../../assets/img/avatar-m-13.jpg", email: "adam@blackthorn.house", phone: "07700 900 208", since: daysAgo(2190), lastSeenAt: hoursAgo(11), where: null,
    prefs: { comfortC: 19.5, bedtime: "23:30", wake: "06:00", lightsOnArrival: true, notifyAlarm: true, notifyDeliveries: false } },
  { id: "mem_03", name: "Milo Brandt", role: "child", status: "active", home: true, avatar: "../../assets/img/avatar-40-02.jpg", email: "milo@blackthorn.house", phone: null, since: daysAgo(1460), lastSeenAt: minsAgo(12), where: "milo_bed",
    prefs: { comfortC: 20, bedtime: "20:30", wake: "07:00", lightsOnArrival: false, notifyAlarm: false, notifyDeliveries: false } },
  { id: "mem_04", name: "Iris Brandt", role: "child", status: "active", home: true, avatar: "../../assets/img/avatar-40-03.jpg", email: null, phone: null, since: daysAgo(1460), lastSeenAt: minsAgo(25), where: "living",
    prefs: { comfortC: 21, bedtime: "19:30", wake: "06:45", lightsOnArrival: false, notifyAlarm: false, notifyDeliveries: false } },
  { id: "mem_05", name: "Renate Kohl", role: "staff", status: "active", home: true, avatar: "../../assets/img/avatar-f-9.jpg", email: "renate.kohl@example.com", phone: "07700 900 377", since: daysAgo(980), lastSeenAt: minsAgo(40), where: "kitchen",
    prefs: { comfortC: 19, bedtime: "22:30", wake: "06:00", lightsOnArrival: false, notifyAlarm: false, notifyDeliveries: true } },
  { id: "mem_06", name: "Dee Osman", role: "adult", status: "invited", home: false, avatar: "../../assets/img/avatar-80-06.jpg", email: "dee.osman@example.com", phone: "07700 900 512", since: daysAgo(2), lastSeenAt: null, where: null,
    prefs: { comfortC: 20, bedtime: "23:00", wake: "07:30", lightsOnArrival: true, notifyAlarm: false, notifyDeliveries: false } },
  { id: "mem_07", name: "Tomas Iversen", role: "guest", status: "suspended", home: false, avatar: "../../assets/img/avatar-m-18.jpg", email: "tomas.iversen@example.com", phone: null, since: daysAgo(620), lastSeenAt: daysAgo(96), where: null,
    prefs: { comfortC: 20, bedtime: "00:30", wake: "08:00", lightsOnArrival: false, notifyAlarm: false, notifyDeliveries: false },
    suspendedNote: "Access suspended when the au-pair arrangement ended in July. The record is kept so the log still reads." },
];

/* ── Heating zones ───────────────────────────────────────────────────────
   targetC is stored only where the zone has its own setpoint (hot water). For
   the living zones the target is DERIVED from who is home and awake — see
   metrics.comfortTarget — so raising somebody's comfort preference moves the
   house. */
const ZONES_STATE = [
  { id: "ground", status: "comfort", reportedC: 19.8, humidity: 48, radiators: 7, overrideUntil: null, targetC: null, floor: "ground" },
  { id: "first", status: "setback", reportedC: 18.1, humidity: 52, radiators: 8, overrideUntil: null, targetC: null, floor: "first" },
  { id: "study", status: "boost", reportedC: 20.4, humidity: 45, radiators: 2, overrideUntil: hoursOn(0.6), targetC: null, floor: "ground" },
  { id: "water", status: "comfort", reportedC: 47, humidity: null, radiators: 0, overrideUntil: null, targetC: 50, floor: null },
  { id: "annexe", status: "frost", reportedC: 9.4, humidity: 61, radiators: 3, overrideUntil: null, targetC: null, floor: "annexe" },
];

/* ── Doors, windows and gates ───────────────────────────────────────────── */
const ENTRIES = [
  { id: "ent_01", label: "Front door", kind: "door", room: "hall", zone: "ground", floor: "ground", status: "locked", changedAt: hoursAgo(9.5), byId: "mem_01", deviceId: "dev_04" },
  { id: "ent_02", label: "Back door", kind: "door", room: "kitchen", zone: "ground", floor: "ground", status: "unlocked", changedAt: minsAgo(38), byId: "mem_05", deviceId: "dev_05" },
  { id: "ent_03", label: "French doors", kind: "door", room: "living", zone: "ground", floor: "ground", status: "locked", changedAt: hoursAgo(26), byId: "mem_02", deviceId: "dev_06" },
  { id: "ent_04", label: "Utility door", kind: "door", room: "utility", zone: "ground", floor: "ground", status: "locked", changedAt: hoursAgo(5), byId: "mem_05", deviceId: null },
  { id: "ent_05", label: "Kitchen window", kind: "window", room: "kitchen", zone: "ground", floor: "ground", status: "locked", changedAt: hoursAgo(3), byId: null, deviceId: "dev_18" },
  { id: "ent_06", label: "Bathroom window", kind: "window", room: "main_bed", zone: "first", floor: "first", status: "open", changedAt: hoursAgo(2.2), byId: null, deviceId: "dev_19" },
  { id: "ent_07", label: "Study window", kind: "window", room: "study", zone: "study", floor: "ground", status: "locked", changedAt: daysAgo(1), byId: null, deviceId: "dev_20" },
  { id: "ent_08", label: "Milo's window", kind: "window", room: "milo_bed", zone: "first", floor: "first", status: "unknown", changedAt: hoursAgo(31), byId: null, deviceId: "dev_21" },
  { id: "ent_09", label: "Side gate", kind: "gate", room: "garden", zone: null, floor: "ground", status: "locked", changedAt: hoursAgo(6), byId: "mem_05", deviceId: "dev_22" },
  { id: "ent_10", label: "Garage door", kind: "garage", room: "garage", zone: null, floor: "ground", status: "locked", changedAt: hoursAgo(11), byId: "mem_02", deviceId: "dev_23" },
];

/* ── Devices ─────────────────────────────────────────────────────────────
   watts is what the device is drawing now; battery is null on anything mains
   powered. runtimeHours exists on the two things whose consumables are counted
   in hours rather than days, and the upkeep screen reads it. */
const DEVICES = [
  { id: "dev_01", name: "Kitchen ceiling", kind: "light", room: "kitchen", status: "online", on: true, watts: 34, battery: null, lastSeenAt: minsAgo(1), addedAt: daysAgo(700) },
  { id: "dev_02", name: "Kitchen island", kind: "light", room: "kitchen", status: "online", on: true, watts: 22, battery: null, lastSeenAt: minsAgo(1), addedAt: daysAgo(700) },
  { id: "dev_03", name: "Hall lantern", kind: "light", room: "hall", status: "online", on: true, watts: 18, battery: null, lastSeenAt: minsAgo(2), addedAt: daysAgo(700) },
  { id: "dev_04", name: "Front door lock", kind: "lock", room: "hall", status: "online", on: true, watts: 0, battery: 74, lastSeenAt: minsAgo(3), addedAt: daysAgo(540) },
  { id: "dev_05", name: "Back door lock", kind: "lock", room: "kitchen", status: "online", on: false, watts: 0, battery: 61, lastSeenAt: minsAgo(2), addedAt: daysAgo(540) },
  { id: "dev_06", name: "French door lock", kind: "lock", room: "living", status: "online", on: true, watts: 0, battery: 18, lastSeenAt: minsAgo(4), addedAt: daysAgo(540) },
  { id: "dev_07", name: "Drawing room lamps", kind: "light", room: "living", status: "online", on: true, watts: 41, battery: null, lastSeenAt: minsAgo(2), addedAt: daysAgo(690) },
  { id: "dev_08", name: "Drawing room blinds", kind: "blind", room: "living", status: "online", on: true, watts: 0, battery: 88, lastSeenAt: hoursAgo(1), addedAt: daysAgo(400) },
  { id: "dev_09", name: "Study desk light", kind: "light", room: "study", status: "online", on: true, watts: 12, battery: null, lastSeenAt: minsAgo(1), addedAt: daysAgo(690) },
  { id: "dev_10", name: "Study heater", kind: "appliance", room: "study", status: "online", on: true, watts: 780, battery: null, lastSeenAt: minsAgo(1), addedAt: daysAgo(120) },
  { id: "dev_11", name: "Boiler controller", kind: "thermostat", room: "utility", status: "online", on: true, watts: 6, battery: null, lastSeenAt: minsAgo(1), addedAt: daysAgo(900), runtimeHours: 1310 },
  { id: "dev_12", name: "Ground floor thermostat", kind: "thermostat", room: "hall", status: "online", on: true, watts: 2, battery: 55, lastSeenAt: minsAgo(2), addedAt: daysAgo(900) },
  { id: "dev_13", name: "First floor thermostat", kind: "thermostat", room: "main_bed", status: "online", on: true, watts: 2, battery: 43, lastSeenAt: minsAgo(3), addedAt: daysAgo(900) },
  { id: "dev_14", name: "Ventilation unit", kind: "appliance", room: "utility", status: "online", on: true, watts: 62, battery: null, lastSeenAt: minsAgo(1), addedAt: daysAgo(430), runtimeHours: 3980 },
  { id: "dev_15", name: "Immersion heater", kind: "appliance", room: "utility", status: "online", on: false, watts: 0, battery: null, lastSeenAt: minsAgo(5), addedAt: daysAgo(900) },
  { id: "dev_16", name: "Washing machine", kind: "appliance", room: "utility", status: "online", on: true, watts: 410, battery: null, lastSeenAt: minsAgo(1), addedAt: daysAgo(260) },
  { id: "dev_17", name: "Oven", kind: "appliance", room: "kitchen", status: "online", on: false, watts: 0, battery: null, lastSeenAt: minsAgo(2), addedAt: daysAgo(1200) },
  { id: "dev_18", name: "Kitchen window sensor", kind: "sensor", room: "kitchen", status: "online", on: true, watts: 0, battery: 67, lastSeenAt: minsAgo(3), addedAt: daysAgo(500) },
  { id: "dev_19", name: "Bathroom window sensor", kind: "sensor", room: "main_bed", status: "online", on: true, watts: 0, battery: 51, lastSeenAt: minsAgo(2), addedAt: daysAgo(500) },
  { id: "dev_20", name: "Study window sensor", kind: "sensor", room: "study", status: "online", on: true, watts: 0, battery: 72, lastSeenAt: minsAgo(4), addedAt: daysAgo(500) },
  { id: "dev_21", name: "Milo's window sensor", kind: "sensor", room: "milo_bed", status: "offline", on: false, watts: 0, battery: 0, lastSeenAt: hoursAgo(31), addedAt: daysAgo(500) },
  { id: "dev_22", name: "Side gate lock", kind: "lock", room: "garden", status: "online", on: true, watts: 0, battery: 34, lastSeenAt: minsAgo(9), addedAt: daysAgo(310) },
  { id: "dev_23", name: "Garage door", kind: "appliance", room: "garage", status: "online", on: true, watts: 4, battery: null, lastSeenAt: hoursAgo(11), addedAt: daysAgo(820) },
  { id: "dev_24", name: "Drive camera", kind: "camera", room: "garden", status: "online", on: true, watts: 8, battery: null, lastSeenAt: minsAgo(1), addedAt: daysAgo(380) },
  { id: "dev_25", name: "Back garden camera", kind: "camera", room: "garden", status: "online", on: true, watts: 8, battery: null, lastSeenAt: minsAgo(1), addedAt: daysAgo(380) },
  { id: "dev_26", name: "Gate camera", kind: "camera", room: "garden", status: "setup_pending", on: false, watts: 0, battery: null, lastSeenAt: null, addedAt: daysAgo(3) },
  { id: "dev_27", name: "Porch camera", kind: "camera", room: "hall", status: "updating", on: true, watts: 7, battery: null, lastSeenAt: minsAgo(6), addedAt: daysAgo(380) },
  { id: "dev_28", name: "Electricity meter", kind: "meter", room: "utility", status: "online", on: true, watts: 0, battery: null, lastSeenAt: minsAgo(1), addedAt: daysAgo(1100) },
  { id: "dev_29", name: "Water meter", kind: "meter", room: "utility", status: "online", on: true, watts: 0, battery: null, lastSeenAt: minsAgo(2), addedAt: daysAgo(1100) },
  { id: "dev_30", name: "Kitchen speaker", kind: "speaker", room: "kitchen", status: "online", on: false, watts: 3, battery: null, lastSeenAt: minsAgo(14), addedAt: daysAgo(600) },
  { id: "dev_31", name: "Milo's lamp", kind: "light", room: "milo_bed", status: "online", on: true, watts: 9, battery: null, lastSeenAt: minsAgo(3), addedAt: daysAgo(300) },
  { id: "dev_32", name: "Iris's lamp", kind: "light", room: "iris_bed", status: "online", on: false, watts: 0, battery: null, lastSeenAt: minsAgo(30), addedAt: daysAgo(300) },
  { id: "dev_33", name: "Garden lights", kind: "light", room: "garden", status: "online", on: true, watts: 46, battery: null, lastSeenAt: minsAgo(50), addedAt: daysAgo(420) },
  { id: "dev_34", name: "Garage strip light", kind: "light", room: "garage", status: "retired", on: false, watts: 0, battery: null, lastSeenAt: daysAgo(9), addedAt: daysAgo(820) },
];

/* ── The radio link, one record per device ───────────────────────────────
   This is what a connection test reads: how loud the device is (dBm), how long
   it took to answer, the firmware it is running against the firmware available,
   and when the hub last asked. Nothing here is a judgement — weak, behind and
   stale are all derived in enums.js from these readings and the clock, so a
   device becomes a problem without anybody writing on it. */
const HUB = {
  name: "Hearth hub", room: "utility", channel: 11, firmware: "5.2.0", latest: "5.2.2",
  uptimeHours: 812, testedAt: hoursAgo(20), devices: 33,
};

const DEVICE_LINK = {
  dev_01: { dbm: -44, latencyMs: 12, firmware: "3.4.1", latest: "3.4.1", testedAt: hoursAgo(20), result: "pass" },
  dev_02: { dbm: -47, latencyMs: 14, firmware: "3.4.1", latest: "3.4.1", testedAt: hoursAgo(20), result: "pass" },
  dev_03: { dbm: -52, latencyMs: 18, firmware: "3.4.0", latest: "3.4.1", testedAt: hoursAgo(20), result: "pass" },
  dev_04: { dbm: -58, latencyMs: 26, firmware: "2.9.4", latest: "2.9.4", testedAt: hoursAgo(20), result: "pass" },
  dev_05: { dbm: -61, latencyMs: 31, firmware: "2.9.4", latest: "2.9.4", testedAt: hoursAgo(20), result: "pass" },
  dev_06: { dbm: -66, latencyMs: 44, firmware: "2.9.1", latest: "2.9.4", testedAt: hoursAgo(20), result: "slow" },
  dev_07: { dbm: -49, latencyMs: 15, firmware: "3.4.1", latest: "3.4.1", testedAt: hoursAgo(20), result: "pass" },
  dev_08: { dbm: -63, latencyMs: 38, firmware: "1.8.2", latest: "1.8.2", testedAt: hoursAgo(20), result: "pass" },
  dev_09: { dbm: -55, latencyMs: 17, firmware: "3.4.1", latest: "3.4.1", testedAt: hoursAgo(20), result: "pass" },
  dev_10: { dbm: -57, latencyMs: 22, firmware: "1.2.0", latest: "1.2.0", testedAt: hoursAgo(20), result: "pass" },
  dev_11: { dbm: -41, latencyMs: 9, firmware: "6.1.3", latest: "6.1.3", testedAt: hoursAgo(20), result: "pass" },
  dev_12: { dbm: -50, latencyMs: 16, firmware: "6.1.3", latest: "6.1.3", testedAt: hoursAgo(20), result: "pass" },
  dev_13: { dbm: -68, latencyMs: 52, firmware: "6.1.0", latest: "6.1.3", testedAt: hoursAgo(20), result: "slow" },
  dev_14: { dbm: -43, latencyMs: 11, firmware: "4.0.7", latest: "4.0.7", testedAt: hoursAgo(20), result: "pass" },
  dev_15: { dbm: -45, latencyMs: 13, firmware: "4.0.7", latest: "4.0.7", testedAt: hoursAgo(20), result: "pass" },
  dev_16: { dbm: -46, latencyMs: 19, firmware: "2.2.1", latest: "2.2.1", testedAt: hoursAgo(20), result: "pass" },
  dev_17: { dbm: -48, latencyMs: 21, firmware: "2.2.1", latest: "2.2.1", testedAt: hoursAgo(20), result: "pass" },
  dev_18: { dbm: -54, latencyMs: 24, firmware: "1.4.6", latest: "1.4.6", testedAt: hoursAgo(20), result: "pass" },
  dev_19: { dbm: -71, latencyMs: 68, firmware: "1.4.6", latest: "1.4.6", testedAt: hoursAgo(20), result: "slow" },
  dev_20: { dbm: -59, latencyMs: 28, firmware: "1.4.6", latest: "1.4.6", testedAt: hoursAgo(20), result: "pass" },
  dev_21: { dbm: null, latencyMs: null, firmware: "1.4.3", latest: "1.4.6", testedAt: hoursAgo(20), result: "fail" },
  dev_22: { dbm: -78, latencyMs: 96, firmware: "2.9.4", latest: "2.9.4", testedAt: hoursAgo(20), result: "slow" },
  dev_23: { dbm: -74, latencyMs: 81, firmware: "1.1.9", latest: "1.1.9", testedAt: hoursAgo(20), result: "slow" },
  dev_24: { dbm: -62, latencyMs: 34, firmware: "8.3.2", latest: "8.3.2", testedAt: hoursAgo(20), result: "pass" },
  dev_25: { dbm: -73, latencyMs: 77, firmware: "8.3.2", latest: "8.3.2", testedAt: hoursAgo(20), result: "slow" },
  dev_26: { dbm: null, latencyMs: null, firmware: null, latest: "8.3.2", testedAt: null, result: "untested" },
  dev_27: { dbm: -51, latencyMs: 20, firmware: "8.3.0", latest: "8.3.2", testedAt: hoursAgo(20), result: "pass" },
  dev_28: { dbm: -40, latencyMs: 8, firmware: "5.0.1", latest: "5.0.1", testedAt: hoursAgo(20), result: "pass" },
  dev_29: { dbm: -42, latencyMs: 10, firmware: "5.0.1", latest: "5.0.1", testedAt: hoursAgo(20), result: "pass" },
  dev_30: { dbm: -53, latencyMs: 23, firmware: "7.2.4", latest: "7.2.4", testedAt: hoursAgo(20), result: "pass" },
  dev_31: { dbm: -64, latencyMs: 41, firmware: "3.4.1", latest: "3.4.1", testedAt: hoursAgo(20), result: "pass" },
  dev_32: { dbm: -65, latencyMs: 43, firmware: "3.4.1", latest: "3.4.1", testedAt: hoursAgo(20), result: "pass" },
  dev_33: { dbm: -76, latencyMs: 88, firmware: "3.3.8", latest: "3.4.1", testedAt: hoursAgo(20), result: "slow" },
  dev_34: { dbm: null, latencyMs: null, firmware: "3.2.0", latest: "3.4.1", testedAt: daysAgo(9), result: "untested" },
};

/* Devices the hub can hear but the house has not taken in yet — what the pairing
   flow starts from, rather than a blank form. */
const DISCOVERED = [
  { id: "dsc_01", announced: "Dimmable bulb, E27", suggestKind: "light", suggestRoom: "main_bed", dbm: -57, foundAt: minsAgo(3) },
  { id: "dsc_02", announced: "Contact sensor", suggestKind: "sensor", suggestRoom: "iris_bed", dbm: -66, foundAt: minsAgo(9) },
  { id: "dsc_03", announced: "Outdoor camera, 2K", suggestKind: "camera", suggestRoom: "garden", dbm: -72, foundAt: hoursAgo(2) },
];

/* ── Cameras ─────────────────────────────────────────────────────────────
   One record per camera, holding only what a camera has and a light does not: a
   shutter, a floodlight, two-way audio, and whether it is set to record. Whether
   it is WATCHING is derived from these and the device's own status. */
const CAMERAS_STATE = [
  { deviceId: "dev_24", view: "The drive and the gates", privacy: false, recording: true, talk: true, floodlight: true, floodlightOn: false, nightVision: true, resolution: "2K", clipSeconds: 30 },
  { deviceId: "dev_27", view: "The porch and the front door", privacy: false, recording: true, talk: true, floodlight: false, floodlightOn: false, nightVision: true, resolution: "2K", clipSeconds: 20 },
  { deviceId: "dev_25", view: "The back lawn to the annexe", privacy: true, recording: true, talk: false, floodlight: true, floodlightOn: false, nightVision: true, resolution: "1080p", clipSeconds: 30 },
  { deviceId: "dev_26", view: "The side gate and the passage", privacy: false, recording: false, talk: false, floodlight: false, floodlightOn: false, nightVision: true, resolution: "2K", clipSeconds: 30 },
];

/* What the cameras caught, newest first, authored against the log: the car in
   the drive is the electrician leaving, and the porch clips are Milo and Renate. */
const CLIPS = [
  { id: "clp_01", deviceId: "dev_27", at: minsAgo(22), kind: "person", seconds: 14, note: "Milo at the front door, then went round the back" },
  { id: "clp_02", deviceId: "dev_24", at: minsAgo(41), kind: "vehicle", seconds: 26, note: "A van turned in the drive and reversed out" },
  { id: "clp_03", deviceId: "dev_27", at: hoursAgo(4.5), kind: "person", seconds: 11, note: "Milo let himself in on the front door code" },
  { id: "clp_04", deviceId: "dev_24", at: hoursAgo(6), kind: "vehicle", seconds: 34, note: "Marlow Electrical arrived for the gate motor" },
  { id: "clp_05", deviceId: "dev_27", at: hoursAgo(10.5), kind: "person", seconds: 9, note: "Renate arrived" },
  { id: "clp_06", deviceId: "dev_24", at: hoursAgo(13), kind: "animal", seconds: 6, note: "A fox across the gravel" },
  { id: "clp_07", deviceId: "dev_27", at: hoursAgo(28), kind: "parcel", seconds: 18, note: "A delivery left in the porch" },
];

/* ── Energy ──────────────────────────────────────────────────────────────
   One row per day, oldest first, today last and incomplete. offPeakShare is the
   share of that day's import taken inside a cheap window; cost is never stored —
   costOf applies the tariff, once, in metrics.js. */
const TARIFF = {
  name: "Octave Flux — three rate",
  supplier: "Octave Energy",
  standingChargePence: HOUSE_POLICY.standingChargePence,
  exportPencePerKwh: 15,
  windows: [
    { id: "off_peak", label: "Off-peak", from: "00:30", to: "05:30", pencePerKwh: 8.4 },
    { id: "peak", label: "Peak", from: "16:00", to: "19:00", pencePerKwh: 41.2 },
    { id: "standard", label: "Standard", from: null, to: null, pencePerKwh: 24.8 },
  ],
};

const ENERGY_DAYS = [
  { at: daysAgo(13), kwh: 41.2, offPeakShare: 0.34, solarKwh: 9.1, exportKwh: 2.4 },
  { at: daysAgo(12), kwh: 38.6, offPeakShare: 0.31, solarKwh: 11.4, exportKwh: 3.8 },
  { at: daysAgo(11), kwh: 44.9, offPeakShare: 0.29, solarKwh: 6.2, exportKwh: 0.9 },
  { at: daysAgo(10), kwh: 52.4, offPeakShare: 0.41, solarKwh: 4.8, exportKwh: 0.4 },
  { at: daysAgo(9), kwh: 47.1, offPeakShare: 0.38, solarKwh: 7.7, exportKwh: 1.2 },
  { at: daysAgo(8), kwh: 39.8, offPeakShare: 0.36, solarKwh: 10.2, exportKwh: 3.1 },
  { at: daysAgo(7), kwh: 36.4, offPeakShare: 0.33, solarKwh: 12.6, exportKwh: 4.6 },
  { at: daysAgo(6), kwh: 48.8, offPeakShare: 0.27, solarKwh: 5.4, exportKwh: 0.6 },
  { at: daysAgo(5), kwh: 55.2, offPeakShare: 0.44, solarKwh: 3.1, exportKwh: 0 },
  { at: daysAgo(4), kwh: 51.6, offPeakShare: 0.42, solarKwh: 4.4, exportKwh: 0.2 },
  { at: daysAgo(3), kwh: 43.7, offPeakShare: 0.35, solarKwh: 8.8, exportKwh: 1.9 },
  { at: daysAgo(2), kwh: 46.9, offPeakShare: 0.3, solarKwh: 6.9, exportKwh: 1.1 },
  { at: daysAgo(1), kwh: 58.3, offPeakShare: 0.46, solarKwh: 2.8, exportKwh: 0 },
  { at: daysAgo(0), kwh: 44.1, offPeakShare: 0.39, solarKwh: 6.8, exportKwh: 0.7, partial: true },
];

const BATTERY = { capacityKwh: 9.6, chargePercent: 34, mode: "tariff", chargedTodayKwh: 6.2, dischargedTodayKwh: 4.1, cycles: 412 };
const SOLAR = { peakKwp: 4.2, todayKwh: 6.8, nowKw: 0, bestTodayKw: 2.4, bestAt: dayAt(0, 12, 40) };

/* ── Water — seven days, and the overnight figure a leak shows up in ────── */
const WATER_DAYS = [
  { at: daysAgo(6), litres: 612, overnightLitres: 4 },
  { at: daysAgo(5), litres: 703, overnightLitres: 6 },
  { at: daysAgo(4), litres: 588, overnightLitres: 5 },
  { at: daysAgo(3), litres: 641, overnightLitres: 38 },
  { at: daysAgo(2), litres: 664, overnightLitres: 44 },
  { at: daysAgo(1), litres: 719, overnightLitres: 51 },
  { at: daysAgo(0), litres: 508, overnightLitres: 49, partial: true },
];

/* ── Money ───────────────────────────────────────────────────────────────
   amountMinor is pence. forecastMinor is what the house expected to pay, so a
   bill above it is a derived finding rather than a number nobody checks. */
const BILLS = [
  { id: "bil_01", kind: "energy", supplier: "Octave Energy", period: "September", amountMinor: 41280, forecastMinor: 36500, status: "due", issuedAt: daysAgo(6), dueAt: daysOn(8), paidAt: null, note: "Includes the standing charge for 30 days." },
  { id: "bil_02", kind: "water", supplier: "Thames Valley Water", period: "Jul–Sep", amountMinor: 18640, forecastMinor: 15200, status: "due", issuedAt: daysAgo(11), dueAt: daysAgo(2), paidAt: null, note: "Measured, not estimated — the overnight flow is in this period." },
  { id: "bil_03", kind: "council", supplier: "Buckinghamshire Council", period: "October", amountMinor: 31700, forecastMinor: 31700, status: "due", issuedAt: daysAgo(4), dueAt: daysOn(3), paidAt: null, note: "Band G, tenth instalment of twelve." },
  { id: "bil_04", kind: "insurance", supplier: "Cranmore Private Clients", period: "Year to Aug 2027", amountMinor: 142000, forecastMinor: 138000, status: "paid", issuedAt: daysAgo(48), dueAt: daysAgo(20), paidAt: daysAgo(24), note: "Renewed with the same excess." },
  { id: "bil_05", kind: "service", supplier: "Wexler Heating", period: "Annual cover", amountMinor: 39600, forecastMinor: 39600, status: "paid", issuedAt: daysAgo(90), dueAt: daysAgo(76), paidAt: daysAgo(80), note: "Covers the boiler service and one call-out." },
  { id: "bil_06", kind: "broadband", supplier: "Verity Fibre", period: "October", amountMinor: 6900, forecastMinor: 6900, status: "paid", issuedAt: daysAgo(5), dueAt: daysOn(9), paidAt: daysAgo(5), note: "Paid by standing order." },
  { id: "bil_07", kind: "energy", supplier: "Octave Energy", period: "August", amountMinor: 33900, forecastMinor: 34800, status: "paid", issuedAt: daysAgo(36), dueAt: daysAgo(8), paidAt: daysAgo(30), note: null },
  { id: "bil_08", kind: "water", supplier: "Thames Valley Water", period: "Apr–Jun", amountMinor: 21400, forecastMinor: 15600, status: "disputed", issuedAt: daysAgo(80), dueAt: daysAgo(52), paidAt: null, note: "Queried in July: the reading jumped 40% with nobody in the house for a fortnight." },
];

const CONTRACTS = [
  { id: "con_01", trade: "heating", providerId: "prv_01", name: "Boiler and radiators, annual cover", monthlyMinor: 3300, status: "active", startedAt: daysAgo(730), endsAt: daysOn(275), note: "One service a year, call-outs included, parts at cost." },
  { id: "con_02", trade: "garden", providerId: "prv_03", name: "Grounds, fortnightly", monthlyMinor: 24000, status: "active", startedAt: daysAgo(1100), endsAt: daysOn(56), note: "Two people, half a day, March to November." },
  { id: "con_03", trade: "cleaning", providerId: "prv_04", name: "Housekeeping, three days a week", monthlyMinor: 96000, status: "active", startedAt: daysAgo(980), endsAt: null, note: "Renate's hours; reviewed each April." },
  { id: "con_04", trade: "security", providerId: "prv_05", name: "Alarm monitoring and response", monthlyMinor: 4200, status: "active", startedAt: daysAgo(400), endsAt: daysOn(12), note: "Renewal quote is due — the premium rises 14% unless the cameras are commissioned." },
];

/* ── Upkeep ──────────────────────────────────────────────────────────────
   A consumable's real due date is the hours it has run, which is read off the
   device, so a filter becomes overdue without anybody writing on the job. */
const JOBS = [
  { id: "job_01", title: "Ventilation filters", kind: "consumable", providerId: null, status: "planned", dueAt: daysAgo(11), scheduledAt: null, costMinor: 8400, quoteMinor: null, deviceId: "dev_14", lifeHours: 3600, ranHours: 3980, room: "utility", note: "Two G4 panels and one F7. The unit has run 380 hours past the change interval." },
  { id: "job_02", title: "Boiler service", kind: "service", providerId: "prv_01", status: "scheduled", dueAt: daysOn(9), scheduledAt: dayAt(3, 8, 30), costMinor: 0, quoteMinor: null, deviceId: "dev_11", lifeHours: null, ranHours: null, room: "utility", note: "Included in the annual cover. Renate will let them in." },
  { id: "job_03", title: "Electrical installation report", kind: "inspection", providerId: "prv_02", status: "planned", dueAt: daysAgo(34), scheduledAt: null, costMinor: 0, quoteMinor: 42000, deviceId: null, lifeHours: null, ranHours: null, room: null, note: "The last EICR expired in September. Insurance asks for one every five years." },
  { id: "job_04", title: "Gutters and downpipes", kind: "repair", providerId: "prv_06", status: "quoted", dueAt: daysOn(21), scheduledAt: null, costMinor: 0, quoteMinor: 68000, deviceId: null, lifeHours: null, ranHours: null, room: null, note: "Quote holds for 30 days. The rear downpipe is loose at the second bracket." },
  { id: "job_05", title: "Side gate motor", kind: "repair", providerId: "prv_02", status: "in_progress", dueAt: daysAgo(1), scheduledAt: hoursAgo(6), costMinor: 21000, quoteMinor: 21000, deviceId: "dev_22", lifeHours: null, ranHours: null, room: "garden", note: "Stripped and cleaned; waiting on a limit switch, back on Thursday." },
  { id: "job_06", title: "Chimney sweep, drawing room flue", kind: "service", providerId: "prv_06", status: "planned", dueAt: daysOn(38), scheduledAt: null, costMinor: 0, quoteMinor: null, deviceId: null, lifeHours: null, ranHours: null, room: "living", note: "Before the fire is lit for the winter." },
  { id: "job_07", title: "Water softener salt", kind: "consumable", providerId: null, status: "planned", dueAt: daysOn(6), scheduledAt: null, costMinor: 2600, quoteMinor: null, deviceId: null, lifeHours: null, ranHours: null, room: "utility", note: "Four blocks. The last delivery was eight weeks ago." },
  { id: "job_08", title: "Commission the gate camera", kind: "service", providerId: "prv_05", status: "planned", dueAt: daysOn(11), scheduledAt: null, costMinor: 0, quoteMinor: null, deviceId: "dev_26", lifeHours: null, ranHours: null, room: "garden", note: "The monitoring renewal is priced on the cameras being live." },
  { id: "job_09", title: "Lawn autumn treatment", kind: "garden", providerId: "prv_03", status: "done", dueAt: daysAgo(9), scheduledAt: daysAgo(9), costMinor: 14000, quoteMinor: 14000, deviceId: null, lifeHours: null, ranHours: null, room: "garden", note: "Scarified and fed. Next treatment in March." },
  { id: "job_10", title: "Replace bathroom window sensor", kind: "repair", providerId: null, status: "cancelled", dueAt: daysAgo(20), scheduledAt: null, costMinor: 0, quoteMinor: null, deviceId: "dev_19", lifeHours: null, ranHours: null, room: "main_bed", note: "Cancelled — the contacts were only dirty, and it has reported cleanly since." },
];

const PROVIDERS = [
  { id: "prv_01", name: "Wexler Heating", trade: "heating", contact: "Jan Wexler", phone: "01628 900 141", email: "jan@wexlerheating.example", since: daysAgo(1460), hourlyMinor: 8500, keyHolder: false, note: "Fits our boiler; holds the service history back to 2019." },
  { id: "prv_02", name: "Marlow Electrical", trade: "electrical", contact: "Ade Salako", phone: "01628 900 288", email: "office@marlowelectrical.example", since: daysAgo(900), hourlyMinor: 9200, keyHolder: false, note: "Did the annexe rewire. Holds the gate motor parts." },
  { id: "prv_03", name: "Hale and Sons", trade: "garden", contact: "Peter Hale", phone: "07700 900 640", email: "peter@haleandsons.example", since: daysAgo(1100), hourlyMinor: 4800, keyHolder: true, note: "Two people, fortnightly. Has the side gate code." },
  { id: "prv_04", name: "Renate Kohl", trade: "cleaning", contact: "Renate Kohl", phone: "07700 900 377", email: "renate.kohl@example.com", since: daysAgo(980), hourlyMinor: 3200, keyHolder: true, note: "Also a member of the household in Hearth, so her code is on the access list." },
  { id: "prv_05", name: "Sentinel Response", trade: "security", contact: "Control room", phone: "0800 118 4400", email: "control@sentinelresponse.example", since: daysAgo(400), hourlyMinor: 0, keyHolder: false, note: "Monitors the alarm. Calls Sofia, then Adam, then the police." },
  { id: "prv_06", name: "Bovingdon Property Care", trade: "chimney", contact: "Lorna Pike", phone: "01628 900 512", email: "hello@bovingdoncare.example", since: daysAgo(320), hourlyMinor: 6400, keyHolder: false, note: "Roof, gutters and flues. Scaffold-free up to two storeys." },
];

/* ── Ways in that are not a person ──────────────────────────────────────── */
const GRANTS = [
  { id: "grn_01", label: "Housekeeping", kind: "code", holder: "Renate Kohl", providerId: "prv_04", entryIds: ["ent_02", "ent_04"], status: "active", startsAt: daysAgo(980), endsAt: null, lastUsedAt: hoursAgo(10.5), uses: 412, note: "Weekdays, 07:00–15:00." },
  { id: "grn_02", label: "Grounds team", kind: "code", holder: "Hale and Sons", providerId: "prv_03", entryIds: ["ent_09"], status: "active", startsAt: daysAgo(1100), endsAt: daysOn(4), lastUsedAt: daysAgo(4), uses: 96, note: "Side gate only. Expires with the growing season." },
  { id: "grn_03", label: "Milo's code", kind: "code", holder: "Milo Brandt", providerId: null, entryIds: ["ent_01"], status: "active", startsAt: daysAgo(400), endsAt: null, lastUsedAt: hoursAgo(4.5), uses: 188, note: "Front door only, and it does not disarm the alarm." },
  { id: "grn_04", label: "Dog walker", kind: "key", holder: "Bea Ndiaye", providerId: null, entryIds: ["ent_04"], status: "active", startsAt: daysAgo(30), endsAt: null, lastUsedAt: null, uses: 0, note: "One key, utility door. Handed over but never used — the arrangement never started." },
  { id: "grn_05", label: "Gate motor repair", kind: "window", holder: "Marlow Electrical", providerId: "prv_02", entryIds: ["ent_09", "ent_10"], status: "active", startsAt: daysAgo(1), endsAt: hoursOn(3), lastUsedAt: hoursAgo(6), uses: 3, note: "Opened for job 05; closes tonight." },
  { id: "grn_06", label: "Grocery delivery", kind: "code", holder: "Fensham Farm", providerId: null, entryIds: ["ent_09"], status: "scheduled", startsAt: dayAt(1, 7, 0), endsAt: dayAt(1, 11, 0), lastUsedAt: null, uses: 0, note: "Wednesday morning drop into the side passage." },
  { id: "grn_07", label: "Former au pair", kind: "code", holder: "Tomas Iversen", providerId: null, entryIds: ["ent_01", "ent_02"], status: "suspended", startsAt: daysAgo(620), endsAt: null, lastUsedAt: daysAgo(96), uses: 640, note: "Suspended in July rather than revoked, so the log still reads." },
  { id: "grn_08", label: "Kitchen fitters", kind: "window", holder: "Ellis Joinery", providerId: null, entryIds: ["ent_02"], status: "revoked", startsAt: daysAgo(210), endsAt: daysAgo(180), lastUsedAt: daysAgo(182), uses: 44, note: "Revoked when the kitchen was signed off." },
];

/* ── Routines ────────────────────────────────────────────────────────────
   A routine stores the person or the policy, never the time, so moving Milo's
   bedtime moves "Milo's bedtime" with it. */
const ROUTINES = [
  { id: "rou_01", name: "Evening lights", trigger: "sunset", status: "on", deviceIds: ["dev_03", "dev_07", "dev_33"], memberId: null, offsetMinutes: -20, lastRunAt: minsAgo(70), runs: 1240, note: "Hall, drawing room and the garden path, twenty minutes before sunset." },
  { id: "rou_02", name: "Milo's bedtime", trigger: "time", status: "on", deviceIds: ["dev_31"], memberId: "mem_03", offsetMinutes: 0, lastRunAt: daysAgo(1), runs: 396, note: "Reads Milo's own bedtime preference, so changing it moves this." },
  { id: "rou_03", name: "Iris's bedtime", trigger: "time", status: "on", deviceIds: ["dev_32"], memberId: "mem_04", offsetMinutes: 0, lastRunAt: daysAgo(1), runs: 396, note: "Reads Iris's own bedtime preference." },
  { id: "rou_04", name: "Nobody home", trigger: "presence", status: "on", deviceIds: ["dev_12", "dev_13"], memberId: null, offsetMinutes: 15, lastRunAt: hoursAgo(11), runs: 820, note: "Drops both floors to the away setback a quarter of an hour after the last person leaves." },
  { id: "rou_05", name: "Somebody home", trigger: "presence", status: "on", deviceIds: ["dev_03", "dev_12"], memberId: null, offsetMinutes: 0, lastRunAt: hoursAgo(4.5), runs: 814, note: "Hall light and comfort heating when the first person arrives, if they asked for lights." },
  { id: "rou_06", name: "Charge the battery off-peak", trigger: "time", status: "on", deviceIds: ["dev_28"], memberId: null, offsetMinutes: 0, lastRunAt: hoursAgo(14), runs: 190, note: "Runs at the start of the off-peak window, from the tariff rather than a stored time." },
  { id: "rou_07", name: "Gate camera clip", trigger: "sensor", status: "draft", deviceIds: ["dev_26", "dev_22"], memberId: null, offsetMinutes: 0, lastRunAt: null, runs: 0, note: "Waiting on the gate camera being commissioned; no clip length chosen yet." },
  { id: "rou_08", name: "Garage light on entry", trigger: "sensor", status: "on", deviceIds: ["dev_34", "dev_23"], memberId: null, offsetMinutes: 0, lastRunAt: daysAgo(9), runs: 210, note: "Has not run since the strip light was switched out of the house." },
];

const REQUESTS = [
  { id: "req_01", memberId: "mem_03", askedAt: minsAgo(22), kind: "unlock", subject: "ent_02", body: "Can I unlock the back door? My bike is in the passage and it is raining.", status: "pending", answeredAt: null, answeredById: null, reason: null },
  { id: "req_02", memberId: "mem_04", askedAt: minsAgo(50), kind: "lights", subject: "dev_32", body: "Please can my lamp stay on until eight tonight.", status: "pending", answeredAt: null, answeredById: null, reason: null },
  { id: "req_03", memberId: "mem_03", askedAt: hoursAgo(27), kind: "climate", subject: "first", body: "My room was cold this morning — can the upstairs come on earlier?", status: "granted", answeredAt: hoursAgo(25), answeredById: "mem_01", reason: "Weekday warm-up moved to 06:20." },
];

/* ── The log — authored against the records above ────────────────────────── */
const EVENTS = [
  { id: "evt_01", at: minsAgo(4), kind: "person", actorId: "mem_01", subject: { type: "member", id: "mem_01" }, line: "Sofia is in the study", note: null },
  { id: "evt_02", at: minsAgo(22), kind: "person", actorId: "mem_03", subject: { type: "request", id: "req_01" }, line: "Milo asked to unlock the back door", note: "Waiting on an answer" },
  { id: "evt_03", at: minsAgo(38), kind: "security", actorId: "mem_05", subject: { type: "entry", id: "ent_02" }, line: "Back door unlocked", note: "Renate took the recycling out" },
  { id: "evt_04", at: minsAgo(50), kind: "person", actorId: "mem_04", subject: { type: "request", id: "req_02" }, line: "Iris asked for her lamp to stay on", note: null },
  { id: "evt_05", at: minsAgo(70), kind: "routine", actorId: null, subject: { type: "routine", id: "rou_01" }, line: "Evening lights ran", note: "Hall, drawing room, garden path" },
  { id: "evt_06", at: hoursAgo(2.2), kind: "security", actorId: null, subject: { type: "entry", id: "ent_06" }, line: "Bathroom window opened", note: "Still open" },
  { id: "evt_07", at: hoursAgo(3), kind: "climate", actorId: "mem_01", subject: { type: "zone", id: "study" }, line: "Study boosted for an hour", note: "Extended once" },
  { id: "evt_08", at: hoursAgo(4.5), kind: "person", actorId: "mem_03", subject: { type: "entry", id: "ent_01" }, line: "Milo let himself in", note: "Front door code" },
  { id: "evt_09", at: hoursAgo(6), kind: "upkeep", actorId: null, subject: { type: "job", id: "job_05" }, line: "Marlow Electrical arrived for the gate motor", note: "Timed access opened at the side gate" },
  { id: "evt_10", at: hoursAgo(9.5), kind: "security", actorId: "mem_01", subject: { type: "house", id: "alarm" }, line: "Alarm disarmed", note: "Sofia, front door" },
  { id: "evt_11", at: hoursAgo(10.5), kind: "person", actorId: "mem_05", subject: { type: "grant", id: "grn_01" }, line: "Renate arrived", note: "Housekeeping code, back door" },
  { id: "evt_12", at: hoursAgo(11), kind: "routine", actorId: null, subject: { type: "routine", id: "rou_04" }, line: "Nobody home ran", note: "Both floors dropped to 15.5°" },
  { id: "evt_13", at: hoursAgo(14), kind: "routine", actorId: null, subject: { type: "routine", id: "rou_06" }, line: "Battery charged off-peak", note: "6.2 kWh at 8.4p" },
  { id: "evt_14", at: hoursAgo(31), kind: "device", actorId: null, subject: { type: "device", id: "dev_21" }, line: "Milo's window sensor stopped reporting", note: "Battery flat" },
  { id: "evt_15", at: daysAgo(1), kind: "money", actorId: null, subject: { type: "bill", id: "bil_01" }, line: "Electricity bill for September arrived", note: "£412.80, above the forecast" },
  { id: "evt_16", at: daysAgo(3), kind: "device", actorId: "mem_01", subject: { type: "device", id: "dev_26" }, line: "Gate camera added", note: "Not set up yet" },
  { id: "evt_17", at: daysAgo(9), kind: "upkeep", actorId: null, subject: { type: "job", id: "job_09" }, line: "Lawn autumn treatment done", note: "Hale and Sons, £140" },
  { id: "evt_18", at: daysAgo(9), kind: "device", actorId: "mem_02", subject: { type: "device", id: "dev_34" }, line: "Garage strip light switched out of the house", note: "Failed ballast" },
];

const NOTIFICATIONS = [
  { id: "ntf_01", title: "Back door has been unlocked 38 minutes", body: "It is dark, and the alarm is disarmed.", at: minsAgo(38), route: "/security" },
  { id: "ntf_02", title: "Milo is waiting on an answer", body: "He asked to unlock the back door.", at: minsAgo(22), route: "/people" },
  { id: "ntf_03", title: "Ventilation filters are 380 hours past due", body: "The unit has run 3,980 hours of a 3,600-hour interval.", at: hoursAgo(20), route: "/upkeep" },
  { id: "ntf_04", title: "Water bill is two days past its due date", body: "£186.40 to Thames Valley Water, and the overnight flow is still running.", at: daysAgo(2), route: "/spend" },
];

const NOTIFICATION_PREFS = {
  quietFrom: HOUSE_POLICY.quietFrom,
  quietTo: HOUSE_POLICY.quietTo,
  rows: [
    { id: "alarm", label: "The alarm is triggered, or faults", push: true, sms: true, email: false },
    { id: "insecure", label: "A door or window is insecure after dark", push: true, sms: false, email: false },
    { id: "request", label: "Somebody in the house asks for something", push: true, sms: false, email: false },
    { id: "upkeep", label: "A job or a consumable falls overdue", push: false, sms: false, email: true },
    { id: "money", label: "A bill arrives above its forecast", push: false, sms: false, email: true },
    { id: "arrival", label: "Somebody arrives or leaves", push: false, sms: false, email: false },
  ],
};

/* Who the monitoring company rings, in order, and what the house is allowed to
   remember. Both are edited in Settings and read by the screens that act on
   them. */
const RESPONSE = {
  order: ["mem_01", "mem_02", "mem_05"],
  autoArm: true,
  autoArmAt: "23:45",
  notifyPolice: true,
};

const DATA_PREFS = {
  keepLogDays: 90,
  keepClipsDays: 14,
  shareWithProviders: true,
  presenceHistory: true,
};

const HEARTH_RESET_LINK = { email: "sofia@blackthorn.house", issuedAt: minsAgo(25), expiresInMinutes: 60 };

const PASSWORD_RULES = [
  { id: "length", label: "At least 12 characters", test: (v) => (v || "").length >= 12 },
  { id: "case", label: "An upper and a lower case letter", test: (v) => /[a-z]/.test(v || "") && /[A-Z]/.test(v || "") },
  { id: "number", label: "A number", test: (v) => /\d/.test(v || "") },
  { id: "name", label: "Not your name or the house's name", test: (v) => !!v && !/sofia|brandt|blackthorn/i.test(v) },
];

Object.assign(window, {
  HEARTH_NOW, RESIDENCE, HEARTH_SIGNED_IN, HOUSE_STATE, MEMBERS, ZONES_STATE, ENTRIES, DEVICES,
  HUB, DEVICE_LINK, DISCOVERED, CAMERAS_STATE, CLIPS,
  TARIFF, ENERGY_DAYS, BATTERY, SOLAR, WATER_DAYS, BILLS, CONTRACTS, JOBS, PROVIDERS, GRANTS,
  ROUTINES, REQUESTS, EVENTS, NOTIFICATIONS, NOTIFICATION_PREFS, RESPONSE, DATA_PREFS, HEARTH_RESET_LINK, PASSWORD_RULES,
  minsAgo, hoursAgo, hoursOn, daysAgo, daysOn, dayAt,
});
