{
  "id": "P115",
  "slug": "thermostat",
  "key": "P115-thermostat",
  "title": "Thermostat",
  "summary": "Three rooms with their own settings over factory defaults that they read live, heated minute by minute through a day with a band around the target, hour by hour, and the band's trade-off between switching the heater and holding the temperature.",
  "entry": "main.eml",
  "ui": "terminal",
  "readme": "# P115 - Thermostat\n\nThree rooms heated through a day. Each room has its own settings over the\nfactory defaults, and a band around the target keeps the heater from\nswitching every minute.\n\n- `main.eml` - the menu, the rooms table, setting a room or the factory,\n  a day, one room hour by hour, and other bands\n- `heat.eml` - the factory defaults, the rooms, the target for a minute,\n  and the day minute by minute\n\nHow each part works:\n\n- The factory defaults are on a class: a day target of 20.0 degrees, a\n  night target of 17.0, a band of 0.5, and the day from 06:00 to 22:00. A\n  room keeps only its own settings. As in the corpus case\n  `thermostat-class-defaults`:\n  - reading a setting falls back to the factory at the moment of asking;\n  - changing the factory moves at once every room without its own value;\n  - setting one room's value moves no other room, and not the factory.\n- Temperatures are whole tenths of a degree, and time runs in minutes. A\n  heating room warms by 0.2 degrees a minute, and a room warmer than\n  outside (5.0) cools by 0.1. The heater comes on at target - band and goes\n  off at target + band.\n- The band's trade-off: within the day, from the moment the room first\n  comes within the band, the program records the lowest and highest\n  temperature. A band of 0 switches the heater on at least 9 times as\n  often as a band of 1.0, and holds the temperature closer. Bands less\n  than 0.3 apart can come out the other way by a little, since the heater\n  warms in steps of 0.2.\n\nWhat is checked: menu choices 1 to 7; one of the three rooms; one of the\nfive settings; a temperature from 5.0 to 30.0, a band from 0 to 3.0, a\ntime as hh:mm; a day that starts before it ends, for every room a\nfactory change would move. An empty answer cancels; factory as a value\nmakes a room follow the factory again.\n\nSessions: `sessions/basic.in` gives:\n- the rooms (the bedroom with its own night, the nursery with its own day\n  and band) and a day;\n- the living room hour by hour, and with bands 0, 0.2, 0.5 and 1.0: 463\n  switches down to 46;\n- the factory day lowered to 19.0, which the living room and the bedroom\n  follow and the nursery does not;\n- the living room's own night of 15.5, the nursery back to the factory\n  day, and the day again.\n\n`sessions/bad-input.in` gives:\n- menu choices 0 and x;\n- settings: a cancel, a kitchen, a cancel, an unknown setting, a cancel,\n  35, 21.55, a band of 4, the times 6:30 and 24:00, and a morning after the\n  evening;\n- the factory: a cancel, a cancel, abc, and an evening before the morning;\n- a cancel for hour by hour, and a kitchen for the bands.\n\nBuilt on the verified corpus case `thermostat-class-defaults` (a default\non the class, an override on the instance, and a live fallback between\nthem).\n",
  "modules": [
    {
      "name": "main.eml",
      "eml": "# P115 thermostat: rooms with their own settings over factory defaults, a\n# day minute by minute, one room hour by hour, and the band that keeps the\n# heater from switching every minute.\nimport heat\n\ndef plain(s):\n    0 => i\n    len(s) => j\n    while i < j and s[i] == \" \":\n        i + 1 => i\n    while j > i and s[j - 1] == \" \":\n        j - 1 => j\n    return s[i:j]\n\ndef pad(s, n):\n    while len(s) < n:\n        s + \" \" => s\n    return s\n\ndef lpad(s, n):\n    while len(s) < n:\n        \" \" + s => s\n    return s\n\ndef keys():\n    return [\"day\", \"night\", \"band\", \"morning\", \"evening\"]\n\ndef shown(key, v):\n    if key == \"morning\" or key == \"evening\":\n        return heat.clock(v)\n    return heat.tenths(v)\n\ndef parse_tenths(t):\n    # \"21.5\" or \"18\" -> 215 or 180, or -1\n    \"\" => whole\n    \"\" => frac\n    False => dot\n    for c in t:\n        if c == \".\" and not dot:\n            True => dot\n        elif c >= \"0\" and c <= \"9\":\n            if dot:\n                frac + c => frac\n            else:\n                whole + c => whole\n        else:\n            return 0 - 1\n    if whole == \"\" or len(whole) > 2 or len(frac) > 1 or (dot and frac == \"\"):\n        return 0 - 1\n    if frac == \"\":\n        \"0\" => frac\n    return int(whole) * 10 + int(frac)\n\ndef parse_clock(t):\n    # \"06:30\" -> 390, or -1\n    if len(t) != 5 or t[2] != \":\":\n        return 0 - 1\n    for i in [0, 1, 3, 4]:\n        if not (t[i] >= \"0\" and t[i] <= \"9\"):\n            return 0 - 1\n    int(t[0:2]) => h\n    int(t[3:5]) => m\n    if h > 23 or m > 59:\n        return 0 - 1\n    return h * 60 + m\n\ndef parse_setting(key, t):\n    if key == \"morning\" or key == \"evening\":\n        return parse_clock(t)\n    parse_tenths(t) => v\n    if key == \"band\":\n        if v > 30:\n            return 0 - 1\n        return v\n    if v < 50 or v > 300:\n        return 0 - 1\n    return v\n\ndef what(key):\n    if key == \"morning\" or key == \"evening\":\n        return \"a time such as 06:30\"\n    if key == \"band\":\n        return \"a band from 0 to 3.0 degrees\"\n    return \"a temperature from 5.0 to 30.0 degrees\"\n\ndef show_rooms(rooms):\n    \"  room       day           night         band          morning        evening\" ^0\n    for r in rooms:\n        \"  \" + pad(r.name, 11) => line\n        for k in keys():\n            shown(k, r.get(k)) + \" \" + r.source(k) => cell\n            if k == \"evening\":\n                line + cell => line\n            else:\n                line + pad(cell, 14) => line\n        line ^0\n    (\"Factory: day \" + heat.tenths(heat.Factory.day) + \", night \" + heat.tenths(heat.Factory.night) + \", band \" + heat.tenths(heat.Factory.band) + \", day from \" + heat.clock(heat.Factory.morning) + \" to \" + heat.clock(heat.Factory.evening) + \".\") ^0\n\ndef ask_room(rooms):\n    plain(input(\"room (living, bedroom or nursery)> \")) => n\n    if n == \"\":\n        return 0 - 1\n    for i in [0:len(rooms) - 1]:\n        if rooms[i].name == n:\n            return i\n    \"Type living, bedroom or nursery.\" ^0\n    return 0 - 2\n\ndef ask_key():\n    plain(input(\"setting (day, night, band, morning or evening)> \")) => k\n    if k == \"\":\n        return \"\"\n    for x in keys():\n        if x == k:\n            return k\n    \"Type day, night, band, morning or evening.\" ^0\n    return \"!\"\n\ndef day_fits(r, k, v):\n    # would the room's day still start before it ends with k set to v?\n    r.get(\"morning\") => m\n    r.get(\"evening\") => e\n    if k == \"morning\":\n        v => m\n    if k == \"evening\":\n        v => e\n    return m < e\n\ndef set_room(rooms):\n    ask_room(rooms) => i\n    if i == 0 - 1:\n        \"Cancelled.\" ^0\n        return\n    if i == 0 - 2:\n        return\n    ask_key() => k\n    if k == \"\":\n        \"Cancelled.\" ^0\n        return\n    if k == \"!\":\n        return\n    plain(input(\"value, or factory to follow the factory again> \")) => t\n    if t == \"\":\n        \"Cancelled.\" ^0\n        return\n    rooms[i] => r\n    if t == \"factory\":\n        if k in r.own:\n            {} => keep\n            for x in r.own:\n                if x != k:\n                    r.own[x] => keep[x]\n            keep => r.own\n        (r.name + \" \" + k + \" follows the factory again: \" + shown(k, r.get(k)) + \".\") ^0\n        return\n    parse_setting(k, t) => v\n    if v < 0:\n        (\"Type \" + what(k) + \".\") ^0\n        return\n    if not day_fits(r, k, v):\n        \"The day has to start before it ends.\" ^0\n        return\n    v => r.own[k]\n    (r.name + \" \" + k + \" is now its own: \" + shown(k, v) + \". The other rooms are as they were.\") ^0\n\ndef set_factory(rooms):\n    ask_key() => k\n    if k == \"\":\n        \"Cancelled.\" ^0\n        return\n    if k == \"!\":\n        return\n    plain(input(\"factory value> \")) => t\n    if t == \"\":\n        \"Cancelled.\" ^0\n        return\n    parse_setting(k, t) => v\n    if v < 0:\n        (\"Type \" + what(k) + \".\") ^0\n        return\n    for r in rooms:\n        if r.source(k) == \"factory\" and not day_fits(r, k, v):\n            (\"The day has to start before it ends, in \" + r.name + \" too.\") ^0\n            return\n    heat.set_factory(k, v)\n    [] => follow\n    [] => keep\n    for r in rooms:\n        if r.source(k) == \"factory\":\n            follow + [r.name] => follow\n        else:\n            keep + [r.name + \" (\" + shown(k, r.get(k)) + \")\"] => keep\n    (\"Factory \" + k + \" is now \" + shown(k, v) + \".\") ^0\n    (\"  following it at once: \" + listed(follow) + \"; keeping their own: \" + listed(keep) + \".\") ^0\n\ndef listed(xs):\n    if len(xs) == 0:\n        return \"none\"\n    \"\" => out\n    for x in xs:\n        if out != \"\":\n            out + \", \" => out\n        out + x => out\n    return out\n\ndef hm(minutes):\n    return str(int((minutes - minutes % 60) / 60)) + \"h \" + lpad(str(minutes % 60), 2) + \"m\"\n\ndef day(rooms):\n    \"A day from 00:00, each room starting at 18.0 degrees with 5.0 outside:\" ^0\n    \"  room       heating   switched on   lowest   highest\" ^0\n    for r in rooms:\n        heat.run_day(r, 180, 50, 0 - 1) => d\n        (\"  \" + pad(r.name, 11) + lpad(hm(d[0]), 7) + lpad(str(d[1]), 14) + lpad(heat.tenths(d[2]), 9) + lpad(heat.tenths(d[3]), 10)) ^0\n\ndef hours(rooms):\n    ask_room(rooms) => i\n    if i == 0 - 1:\n        \"Cancelled.\" ^0\n        return\n    if i == 0 - 2:\n        return\n    rooms[i] => r\n    heat.run_day(r, 180, 50, 0 - 1) => d\n    (r.name + \", hour by hour (the temperature on the hour, minutes heating in the hour):\") ^0\n    \"\" => line\n    0 => n\n    for h in [0:23]:\n        line + lpad(heat.clock(h * 60), 6) + lpad(heat.tenths(d[4][h][0]), 6) + lpad(str(d[4][h][1]), 4) => line\n        n + 1 => n\n        if n == 4:\n            line ^0\n            \"\" => line\n            0 => n\n\ndef bands(rooms):\n    ask_room(rooms) => i\n    if i == 0 - 1:\n        \"Cancelled.\" ^0\n        return\n    if i == 0 - 2:\n        return\n    rooms[i] => r\n    (r.name + \" over a day with other bands:\") ^0\n    \"  band   switched on   heating   lowest   highest\" ^0\n    for b in [0, 2, 5, 10]:\n        heat.run_day(r, 180, 50, b) => d\n        (\"  \" + lpad(heat.tenths(b), 4) + lpad(str(d[1]), 14) + lpad(hm(d[0]), 10) + lpad(heat.tenths(d[5]), 9) + lpad(heat.tenths(d[6]), 10)) ^0\n    \"The lowest and highest are in the day, from the moment the room first comes within the band. A narrow band holds the temperature close to the target and switches the heater far more often; a wide one switches less and lets it swing. Bands less than 0.3 degrees apart can come out the other way by a little, since the heater warms in steps of 0.2 degrees.\" ^0\n\n[heat.Room(\"living\"), heat.Room(\"bedroom\"), heat.Room(\"nursery\")] => rooms\n160 => rooms[1].own[\"night\"]\n220 => rooms[2].own[\"day\"]\n3 => rooms[2].own[\"band\"]\n\n\"== Thermostat ==\" ^0\n\"Three rooms over the factory settings, each with its own changes, heated through a day.\" ^0\nTrue => running\nwhile running:\n    \"\" ^0\n    \"1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit\" ^0\n    plain(input(\"choice> \")) => choice\n    if choice == \"1\":\n        show_rooms(rooms)\n    elif choice == \"2\":\n        set_room(rooms)\n    elif choice == \"3\":\n        set_factory(rooms)\n    elif choice == \"4\":\n        day(rooms)\n    elif choice == \"5\":\n        hours(rooms)\n    elif choice == \"6\":\n        bands(rooms)\n    elif choice == \"7\":\n        False => running\n    else:\n        \"Pick a number from 1 to 7.\" ^0\n\"Bye.\" ^0\n",
      "python": "import heat\n\ndef plain(s):\n    i = 0\n    j = len(s)\n    while i < j and s[i] == \" \":\n        i = i + 1\n    while j > i and s[j - 1] == \" \":\n        j = j - 1\n    return s[i:j]\n\ndef pad(s, n):\n    while len(s) < n:\n        s = s + \" \"\n    return s\n\ndef lpad(s, n):\n    while len(s) < n:\n        s = \" \" + s\n    return s\n\ndef keys():\n    return [\"day\", \"night\", \"band\", \"morning\", \"evening\"]\n\ndef shown(key, v):\n    if key == \"morning\" or key == \"evening\":\n        return heat.clock(v)\n    return heat.tenths(v)\n\ndef parse_tenths(t):\n    whole = \"\"\n    frac = \"\"\n    dot = False\n    for c in t:\n        if c == \".\" and not dot:\n            dot = True\n        elif c >= \"0\" and c <= \"9\":\n            if dot:\n                frac = frac + c\n            else:\n                whole = whole + c\n        else:\n            return 0 - 1\n    if whole == \"\" or len(whole) > 2 or len(frac) > 1 or dot and frac == \"\":\n        return 0 - 1\n    if frac == \"\":\n        frac = \"0\"\n    return int(whole) * 10 + int(frac)\n\ndef parse_clock(t):\n    if len(t) != 5 or t[2] != \":\":\n        return 0 - 1\n    for i in [0, 1, 3, 4]:\n        if not (t[i] >= \"0\" and t[i] <= \"9\"):\n            return 0 - 1\n    h = int(t[0:2])\n    m = int(t[3:5])\n    if h > 23 or m > 59:\n        return 0 - 1\n    return h * 60 + m\n\ndef parse_setting(key, t):\n    if key == \"morning\" or key == \"evening\":\n        return parse_clock(t)\n    v = parse_tenths(t)\n    if key == \"band\":\n        if v > 30:\n            return 0 - 1\n        return v\n    if v < 50 or v > 300:\n        return 0 - 1\n    return v\n\ndef what(key):\n    if key == \"morning\" or key == \"evening\":\n        return \"a time such as 06:30\"\n    if key == \"band\":\n        return \"a band from 0 to 3.0 degrees\"\n    return \"a temperature from 5.0 to 30.0 degrees\"\n\ndef show_rooms(rooms):\n    print(\"  room       day           night         band          morning        evening\")\n    for r in rooms:\n        line = \"  \" + pad(r.name, 11)\n        for k in keys():\n            cell = shown(k, r.get(k)) + \" \" + r.source(k)\n            if k == \"evening\":\n                line = line + cell\n            else:\n                line = line + pad(cell, 14)\n        print(line)\n    print(\"Factory: day \" + heat.tenths(heat.Factory.day) + \", night \" + heat.tenths(heat.Factory.night) + \", band \" + heat.tenths(heat.Factory.band) + \", day from \" + heat.clock(heat.Factory.morning) + \" to \" + heat.clock(heat.Factory.evening) + \".\")\n\ndef ask_room(rooms):\n    n = plain(input(\"room (living, bedroom or nursery)> \"))\n    if n == \"\":\n        return 0 - 1\n    for i in range(0, len(rooms)):\n        if rooms[i].name == n:\n            return i\n    print(\"Type living, bedroom or nursery.\")\n    return 0 - 2\n\ndef ask_key():\n    k = plain(input(\"setting (day, night, band, morning or evening)> \"))\n    if k == \"\":\n        return \"\"\n    for x in keys():\n        if x == k:\n            return k\n    print(\"Type day, night, band, morning or evening.\")\n    return \"!\"\n\ndef day_fits(r, k, v):\n    m = r.get(\"morning\")\n    e = r.get(\"evening\")\n    if k == \"morning\":\n        m = v\n    if k == \"evening\":\n        e = v\n    return m < e\n\ndef set_room(rooms):\n    i = ask_room(rooms)\n    if i == 0 - 1:\n        print(\"Cancelled.\")\n        return\n    if i == 0 - 2:\n        return\n    k = ask_key()\n    if k == \"\":\n        print(\"Cancelled.\")\n        return\n    if k == \"!\":\n        return\n    t = plain(input(\"value, or factory to follow the factory again> \"))\n    if t == \"\":\n        print(\"Cancelled.\")\n        return\n    r = rooms[i]\n    if t == \"factory\":\n        if k in r.own:\n            keep = {}\n            for x in r.own:\n                if x != k:\n                    keep[x] = r.own[x]\n            r.own = keep\n        print(r.name + \" \" + k + \" follows the factory again: \" + shown(k, r.get(k)) + \".\")\n        return\n    v = parse_setting(k, t)\n    if v < 0:\n        print(\"Type \" + what(k) + \".\")\n        return\n    if not day_fits(r, k, v):\n        print(\"The day has to start before it ends.\")\n        return\n    r.own[k] = v\n    print(r.name + \" \" + k + \" is now its own: \" + shown(k, v) + \". The other rooms are as they were.\")\n\ndef set_factory(rooms):\n    k = ask_key()\n    if k == \"\":\n        print(\"Cancelled.\")\n        return\n    if k == \"!\":\n        return\n    t = plain(input(\"factory value> \"))\n    if t == \"\":\n        print(\"Cancelled.\")\n        return\n    v = parse_setting(k, t)\n    if v < 0:\n        print(\"Type \" + what(k) + \".\")\n        return\n    for r in rooms:\n        if r.source(k) == \"factory\" and not day_fits(r, k, v):\n            print(\"The day has to start before it ends, in \" + r.name + \" too.\")\n            return\n    heat.set_factory(k, v)\n    follow = []\n    keep = []\n    for r in rooms:\n        if r.source(k) == \"factory\":\n            follow = follow + [r.name]\n        else:\n            keep = keep + [r.name + \" (\" + shown(k, r.get(k)) + \")\"]\n    print(\"Factory \" + k + \" is now \" + shown(k, v) + \".\")\n    print(\"  following it at once: \" + listed(follow) + \"; keeping their own: \" + listed(keep) + \".\")\n\ndef listed(xs):\n    if len(xs) == 0:\n        return \"none\"\n    out = \"\"\n    for x in xs:\n        if out != \"\":\n            out = out + \", \"\n        out = out + x\n    return out\n\ndef hm(minutes):\n    return str(int((minutes - minutes % 60) / 60)) + \"h \" + lpad(str(minutes % 60), 2) + \"m\"\n\ndef day(rooms):\n    print(\"A day from 00:00, each room starting at 18.0 degrees with 5.0 outside:\")\n    print(\"  room       heating   switched on   lowest   highest\")\n    for r in rooms:\n        d = heat.run_day(r, 180, 50, 0 - 1)\n        print(\"  \" + pad(r.name, 11) + lpad(hm(d[0]), 7) + lpad(str(d[1]), 14) + lpad(heat.tenths(d[2]), 9) + lpad(heat.tenths(d[3]), 10))\n\ndef hours(rooms):\n    i = ask_room(rooms)\n    if i == 0 - 1:\n        print(\"Cancelled.\")\n        return\n    if i == 0 - 2:\n        return\n    r = rooms[i]\n    d = heat.run_day(r, 180, 50, 0 - 1)\n    print(r.name + \", hour by hour (the temperature on the hour, minutes heating in the hour):\")\n    line = \"\"\n    n = 0\n    for h in range(0, 24):\n        line = line + lpad(heat.clock(h * 60), 6) + lpad(heat.tenths(d[4][h][0]), 6) + lpad(str(d[4][h][1]), 4)\n        n = n + 1\n        if n == 4:\n            print(line)\n            line = \"\"\n            n = 0\n\ndef bands(rooms):\n    i = ask_room(rooms)\n    if i == 0 - 1:\n        print(\"Cancelled.\")\n        return\n    if i == 0 - 2:\n        return\n    r = rooms[i]\n    print(r.name + \" over a day with other bands:\")\n    print(\"  band   switched on   heating   lowest   highest\")\n    for b in [0, 2, 5, 10]:\n        d = heat.run_day(r, 180, 50, b)\n        print(\"  \" + lpad(heat.tenths(b), 4) + lpad(str(d[1]), 14) + lpad(hm(d[0]), 10) + lpad(heat.tenths(d[5]), 9) + lpad(heat.tenths(d[6]), 10))\n    print(\"The lowest and highest are in the day, from the moment the room first comes within the band. A narrow band holds the temperature close to the target and switches the heater far more often; a wide one switches less and lets it swing. Bands less than 0.3 degrees apart can come out the other way by a little, since the heater warms in steps of 0.2 degrees.\")\n\nrooms = [heat.Room(\"living\"), heat.Room(\"bedroom\"), heat.Room(\"nursery\")]\nrooms[1].own[\"night\"] = 160\nrooms[2].own[\"day\"] = 220\nrooms[2].own[\"band\"] = 3\nprint(\"== Thermostat ==\")\nprint(\"Three rooms over the factory settings, each with its own changes, heated through a day.\")\nrunning = True\nwhile running:\n    print(\"\")\n    print(\"1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit\")\n    choice = plain(input(\"choice> \"))\n    if choice == \"1\":\n        show_rooms(rooms)\n    elif choice == \"2\":\n        set_room(rooms)\n    elif choice == \"3\":\n        set_factory(rooms)\n    elif choice == \"4\":\n        day(rooms)\n    elif choice == \"5\":\n        hours(rooms)\n    elif choice == \"6\":\n        bands(rooms)\n    elif choice == \"7\":\n        running = False\n    else:\n        print(\"Pick a number from 1 to 7.\")\nprint(\"Bye.\")\n"
    },
    {
      "name": "heat.eml",
      "eml": "# P115 thermostat - rooms that heat with a band around the target\n# (hysteresis): the heater comes on at target - band and goes off at\n# target + band, so it does not switch every minute. As in the corpus case\n# thermostat-class-defaults, the factory settings live on the class and a\n# room's own settings on the room: reading falls back to the class, live,\n# and writing a room's setting never touches the class or another room.\n#\n# Temperatures are whole tenths of a degree; times are minutes from\n# midnight. Each minute a heating room warms by 0.2 degrees, and a room\n# warmer than outside cools by 0.1.\n\nclass Factory:\n    200 => day\n    170 => night\n    5 => band\n    360 => morning\n    1320 => evening\n\nclass Room:\n    def __init__(self, name):\n        name => self.name\n        {} => self.own\n\n    def get(self, key):\n        # the room's own value, or the factory's at the moment of asking\n        if key in self.own:\n            return self.own[key]\n        if key == \"day\":\n            return Factory.day\n        if key == \"night\":\n            return Factory.night\n        if key == \"band\":\n            return Factory.band\n        if key == \"morning\":\n            return Factory.morning\n        return Factory.evening\n\n    def source(self, key):\n        if key in self.own:\n            return \"own\"\n        return \"factory\"\n\n    def target(self, minute):\n        if minute >= self.get(\"morning\") and minute < self.get(\"evening\"):\n            return self.get(\"day\")\n        return self.get(\"night\")\n\ndef set_factory(key, value):\n    if key == \"day\":\n        value => Factory.day\n    elif key == \"night\":\n        value => Factory.night\n    elif key == \"band\":\n        value => Factory.band\n    elif key == \"morning\":\n        value => Factory.morning\n    else:\n        value => Factory.evening\n\ndef run_day(room, start, outside, band_override):\n    # [minutes heating, times switched on, lowest, highest, hourly\n    # [temperature at the hour, minutes heating in the hour], lowest and\n    # highest in the day period once the room has first come within the\n    # band, target - band to target + band - the swing the band allows,\n    # without the change from the night's temperature]; a band override of\n    # -1 uses the room's band\n    start => t\n    False => on\n    0 => heating\n    0 => switches\n    t => low\n    t => high\n    0 - 1 => day_low\n    0 - 1 => day_high\n    False => settled\n    [] => hours\n    0 => this_hour\n    room.get(\"band\") => band\n    if band_override >= 0:\n        band_override => band\n    for m in [0:1439]:\n        if m % 60 == 0:\n            hours + [[t, 0]] => hours\n            0 => this_hour\n        room.target(m) => goal\n        if not on and t <= goal - band:\n            True => on\n            switches + 1 => switches\n        elif on and t >= goal + band:\n            False => on\n        if on:\n            t + 2 => t\n            heating + 1 => heating\n            hours[len(hours) - 1][1] + 1 => hours[len(hours) - 1][1]\n        elif t > outside:\n            t - 1 => t\n        if t < low:\n            t => low\n        if t > high:\n            t => high\n        if m >= room.get(\"morning\") and m < room.get(\"evening\"):\n            if t >= goal - band and t <= goal + band:\n                True => settled\n            if settled:\n                if day_low == 0 - 1 or t < day_low:\n                    t => day_low\n                if t > day_high:\n                    t => day_high\n        else:\n            False => settled\n    return [heating, switches, low, high, hours, day_low, day_high]\n\ndef tenths(t):\n    \"\" => sign\n    t => a\n    if t < 0:\n        \"-\" => sign\n        0 - t => a\n    return sign + str(int((a - a % 10) / 10)) + \".\" + str(a % 10)\n\ndef clock(m):\n    m % 60 => mm\n    int((m - mm) / 60) => h\n    str(h) => hs\n    if h < 10:\n        \"0\" + hs => hs\n    str(mm) => ms\n    if mm < 10:\n        \"0\" + ms => ms\n    return hs + \":\" + ms\n",
      "python": "class Factory:\n    day = 200\n    night = 170\n    band = 5\n    morning = 360\n    evening = 1320\n\nclass Room:\n    def __init__(self, name):\n        self.name = name\n        self.own = {}\n    def get(self, key):\n        if key in self.own:\n            return self.own[key]\n        if key == \"day\":\n            return Factory.day\n        if key == \"night\":\n            return Factory.night\n        if key == \"band\":\n            return Factory.band\n        if key == \"morning\":\n            return Factory.morning\n        return Factory.evening\n    def source(self, key):\n        if key in self.own:\n            return \"own\"\n        return \"factory\"\n    def target(self, minute):\n        if minute >= self.get(\"morning\") and minute < self.get(\"evening\"):\n            return self.get(\"day\")\n        return self.get(\"night\")\n\ndef set_factory(key, value):\n    if key == \"day\":\n        Factory.day = value\n    elif key == \"night\":\n        Factory.night = value\n    elif key == \"band\":\n        Factory.band = value\n    elif key == \"morning\":\n        Factory.morning = value\n    else:\n        Factory.evening = value\n\ndef run_day(room, start, outside, band_override):\n    t = start\n    on = False\n    heating = 0\n    switches = 0\n    low = t\n    high = t\n    day_low = 0 - 1\n    day_high = 0 - 1\n    settled = False\n    hours = []\n    this_hour = 0\n    band = room.get(\"band\")\n    if band_override >= 0:\n        band = band_override\n    for m in range(0, 1440):\n        if m % 60 == 0:\n            hours = hours + [[t, 0]]\n            this_hour = 0\n        goal = room.target(m)\n        if not on and t <= goal - band:\n            on = True\n            switches = switches + 1\n        elif on and t >= goal + band:\n            on = False\n        if on:\n            t = t + 2\n            heating = heating + 1\n            hours[len(hours) - 1][1] = hours[len(hours) - 1][1] + 1\n        elif t > outside:\n            t = t - 1\n        if t < low:\n            low = t\n        if t > high:\n            high = t\n        if m >= room.get(\"morning\") and m < room.get(\"evening\"):\n            if t >= goal - band and t <= goal + band:\n                settled = True\n            if settled:\n                if day_low == 0 - 1 or t < day_low:\n                    day_low = t\n                if t > day_high:\n                    day_high = t\n        else:\n            settled = False\n    return [heating, switches, low, high, hours, day_low, day_high]\n\ndef tenths(t):\n    sign = \"\"\n    a = t\n    if t < 0:\n        sign = \"-\"\n        a = 0 - t\n    return sign + str(int((a - a % 10) / 10)) + \".\" + str(a % 10)\n\ndef clock(m):\n    mm = m % 60\n    h = int((m - mm) / 60)\n    hs = str(h)\n    if h < 10:\n        hs = \"0\" + hs\n    ms = str(mm)\n    if mm < 10:\n        ms = \"0\" + ms\n    return hs + \":\" + ms\n"
    }
  ],
  "sessions": [
    {
      "name": "bad-input",
      "input": "0\nx\n2\n\n2\nkitchen\n2\nliving\n\n2\nliving\ncolour\n2\nliving\nday\n\n2\nliving\nday\n35\n2\nliving\nday\n21.55\n2\nliving\nband\n4\n2\nliving\nmorning\n6:30\n2\nliving\nmorning\n24:00\n2\nliving\nmorning\n22:30\n3\n\n3\nday\n\n3\nday\nabc\n3\nevening\n05:00\n5\n\n6\nkitchen\n7\n",
      "screen": "== Thermostat ==\nThree rooms over the factory settings, each with its own changes, heated through a day.\n\n1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit\nchoice> 0\nPick a number from 1 to 7.\n\n1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit\nchoice> x\nPick a number from 1 to 7.\n\n1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit\nchoice> 2\nroom (living, bedroom or nursery)> \nCancelled.\n\n1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit\nchoice> 2\nroom (living, bedroom or nursery)> kitchen\nType living, bedroom or nursery.\n\n1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit\nchoice> 2\nroom (living, bedroom or nursery)> living\nsetting (day, night, band, morning or evening)> \nCancelled.\n\n1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit\nchoice> 2\nroom (living, bedroom or nursery)> living\nsetting (day, night, band, morning or evening)> colour\nType day, night, band, morning or evening.\n\n1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit\nchoice> 2\nroom (living, bedroom or nursery)> living\nsetting (day, night, band, morning or evening)> day\nvalue, or factory to follow the factory again> \nCancelled.\n\n1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit\nchoice> 2\nroom (living, bedroom or nursery)> living\nsetting (day, night, band, morning or evening)> day\nvalue, or factory to follow the factory again> 35\nType a temperature from 5.0 to 30.0 degrees.\n\n1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit\nchoice> 2\nroom (living, bedroom or nursery)> living\nsetting (day, night, band, morning or evening)> day\nvalue, or factory to follow the factory again> 21.55\nType a temperature from 5.0 to 30.0 degrees.\n\n1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit\nchoice> 2\nroom (living, bedroom or nursery)> living\nsetting (day, night, band, morning or evening)> band\nvalue, or factory to follow the factory again> 4\nType a band from 0 to 3.0 degrees.\n\n1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit\nchoice> 2\nroom (living, bedroom or nursery)> living\nsetting (day, night, band, morning or evening)> morning\nvalue, or factory to follow the factory again> 6:30\nType a time such as 06:30.\n\n1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit\nchoice> 2\nroom (living, bedroom or nursery)> living\nsetting (day, night, band, morning or evening)> morning\nvalue, or factory to follow the factory again> 24:00\nType a time such as 06:30.\n\n1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit\nchoice> 2\nroom (living, bedroom or nursery)> living\nsetting (day, night, band, morning or evening)> morning\nvalue, or factory to follow the factory again> 22:30\nThe day has to start before it ends.\n\n1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit\nchoice> 3\nsetting (day, night, band, morning or evening)> \nCancelled.\n\n1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit\nchoice> 3\nsetting (day, night, band, morning or evening)> day\nfactory value> \nCancelled.\n\n1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit\nchoice> 3\nsetting (day, night, band, morning or evening)> day\nfactory value> abc\nType a temperature from 5.0 to 30.0 degrees.\n\n1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit\nchoice> 3\nsetting (day, night, band, morning or evening)> evening\nfactory value> 05:00\nThe day has to start before it ends, in living too.\n\n1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit\nchoice> 5\nroom (living, bedroom or nursery)> \nCancelled.\n\n1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit\nchoice> 6\nroom (living, bedroom or nursery)> kitchen\nType living, bedroom or nursery.\n\n1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit\nchoice> 7\nBye.\n",
      "interpreter": "equal"
    },
    {
      "name": "basic",
      "input": "1\n4\n5\nliving\n6\nliving\n3\nday\n19.0\n1\n2\nliving\nnight\n15.5\n2\nnursery\nday\nfactory\n1\n4\n7\n",
      "screen": "== Thermostat ==\nThree rooms over the factory settings, each with its own changes, heated through a day.\n\n1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit\nchoice> 1\n  room       day           night         band          morning        evening\n  living     20.0 factory  17.0 factory  0.5 factory   06:00 factory 22:00 factory\n  bedroom    20.0 factory  16.0 own      0.5 factory   06:00 factory 22:00 factory\n  nursery    22.0 own      17.0 factory  0.3 own       06:00 factory 22:00 factory\nFactory: day 20.0, night 17.0, band 0.5, day from 06:00 to 22:00.\n\n1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit\nchoice> 4\nA day from 00:00, each room starting at 18.0 degrees with 5.0 outside:\n  room       heating   switched on   lowest   highest\n  living      7h 55m            92     16.5      20.5\n  bedroom     7h 55m            91     15.5      20.5\n  nursery     7h 57m           152     16.7      22.3\n\n1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit\nchoice> 5\nroom (living, bedroom or nursery)> living\nliving, hour by hour (the temperature on the hour, minutes heating in the hour):\n 00:00  18.0  15 01:00  16.5  20 02:00  16.5  20 03:00  16.5  20\n 04:00  16.5  20 05:00  16.5  20 06:00  16.5  30 07:00  19.5  20\n 08:00  19.5  20 09:00  19.5  20 10:00  19.5  20 11:00  19.5  20\n 12:00  19.5  20 13:00  19.5  20 14:00  19.5  20 15:00  19.5  20\n 16:00  19.5  20 17:00  19.5  20 18:00  19.5  20 19:00  19.5  20\n 20:00  19.5  20 21:00  19.5  20 22:00  19.5  10 23:00  16.5  20\n\n1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit\nchoice> 6\nroom (living, bedroom or nursery)> living\nliving over a day with other bands:\n  band   switched on   heating   lowest   highest\n   0.0           463    7h 57m     20.0      20.2\n   0.2           231    7h 57m     19.8      20.2\n   0.5            92    7h 55m     19.5      20.5\n   1.0            46    7h 55m     19.0      21.0\nThe lowest and highest are in the day, from the moment the room first comes within the band. A narrow band holds the temperature close to the target and switches the heater far more often; a wide one switches less and lets it swing. Bands less than 0.3 degrees apart can come out the other way by a little, since the heater warms in steps of 0.2 degrees.\n\n1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit\nchoice> 3\nsetting (day, night, band, morning or evening)> day\nfactory value> 19.0\nFactory day is now 19.0.\n  following it at once: living, bedroom; keeping their own: nursery (22.0).\n\n1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit\nchoice> 1\n  room       day           night         band          morning        evening\n  living     19.0 factory  17.0 factory  0.5 factory   06:00 factory 22:00 factory\n  bedroom    19.0 factory  16.0 own      0.5 factory   06:00 factory 22:00 factory\n  nursery    22.0 own      17.0 factory  0.3 own       06:00 factory 22:00 factory\nFactory: day 19.0, night 17.0, band 0.5, day from 06:00 to 22:00.\n\n1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit\nchoice> 2\nroom (living, bedroom or nursery)> living\nsetting (day, night, band, morning or evening)> night\nvalue, or factory to follow the factory again> 15.5\nliving night is now its own: 15.5. The other rooms are as they were.\n\n1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit\nchoice> 2\nroom (living, bedroom or nursery)> nursery\nsetting (day, night, band, morning or evening)> day\nvalue, or factory to follow the factory again> factory\nnursery day follows the factory again: 19.0.\n\n1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit\nchoice> 1\n  room       day           night         band          morning        evening\n  living     19.0 factory  15.5 own      0.5 factory   06:00 factory 22:00 factory\n  bedroom    19.0 factory  16.0 own      0.5 factory   06:00 factory 22:00 factory\n  nursery    19.0 factory  17.0 factory  0.3 own       06:00 factory 22:00 factory\nFactory: day 19.0, night 17.0, band 0.5, day from 06:00 to 22:00.\n\n1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit\nchoice> 4\nA day from 00:00, each room starting at 18.0 degrees with 5.0 outside:\n  room       heating   switched on   lowest   highest\n  living      7h 53m            91     15.0      19.6\n  bedroom     7h 55m            92     15.5      19.5\n  nursery     7h 56m           156     16.7      19.3\n\n1) rooms  2) set a room  3) change the factory settings  4) a day  5) hour by hour  6) the band  7) quit\nchoice> 7\nBye.\n",
      "interpreter": "equal"
    }
  ],
  "builtOn": [
    {
      "slug": "thermostat-class-defaults",
      "caseId": "198-thermostat-class-defaults",
      "title": "Thermostats: a factory default shared until it is overridden"
    }
  ],
  "updated": "2026-10-16"
}
