Functional Weave
Code in Rust

energy.tariff-time-of-use@1.0.1

vectors.json

10,153 bytes · view raw

[
  {"name": "Economy 7: 00:00 is still day, 00:30 to 07:00 night, 07:30 day again", "args": [[{"start": "2026-01-15T00:00", "wattHours": 500}, {"start": "2026-01-15T00:30", "wattHours": 1000}, {"start": "2026-01-15T07:00", "wattHours": 800}, {"start": "2026-01-15T07:30", "wattHours": 400}, {"start": "2026-01-15T23:30", "wattHours": 300}], [{"name": "night", "start": "00:30", "end": "07:30", "rate": 12000}, {"name": "day", "start": "07:30", "end": "00:30", "rate": 30000}], "GBP", "half-up"], "expect": {"lines": [{"name": "night", "wattHours": 1800, "cost": {"minor": 22, "currency": "GBP"}}, {"name": "day", "wattHours": 1200, "cost": {"minor": 36, "currency": "GBP"}}], "wattHours": 3000, "total": {"minor": 58, "currency": "GBP"}}},
  {"name": "Agile prices summed exactly then rounded once: 34p, where rounding each half hour gives 33p", "args": [[{"start": "2026-03-01T16:00", "wattHours": 700}, {"start": "2026-03-01T13:00", "wattHours": 900}, {"start": "2026-03-01T13:30", "wattHours": 1200}], [{"name": "agile", "start": "2026-03-01T13:00", "end": "2026-03-01T13:30", "rate": -2150}, {"name": "agile", "start": "2026-03-01T13:30", "end": "2026-03-01T14:00", "rate": 15403}, {"name": "agile", "start": "2026-03-01T16:00", "end": "2026-03-01T16:30", "rate": 24999}], "GBP", "half-up"], "expect": {"lines": [{"name": "agile", "wattHours": 2800, "cost": {"minor": 34, "currency": "GBP"}}], "wattHours": 2800, "total": {"minor": 34, "currency": "GBP"}}},
  {"name": "a negative plunge price is a credit", "args": [[{"start": "2026-03-01T13:00", "wattHours": 2000}], [{"name": "agile", "start": "2026-03-01T13:00", "end": "2026-03-01T13:30", "rate": -2150}, {"name": "agile", "start": "2026-03-01T13:30", "end": "2026-03-01T14:00", "rate": 15403}, {"name": "agile", "start": "2026-03-01T16:00", "end": "2026-03-01T16:30", "rate": 24999}], "GBP", "half-up"], "expect": {"lines": [{"name": "agile", "wattHours": 2000, "cost": {"minor": -4, "currency": "GBP"}}], "wattHours": 2000, "total": {"minor": -4, "currency": "GBP"}}},
  {"name": "a window that wraps midnight, 22:00 to 05:00", "args": [[{"start": "2026-02-01T23:00", "wattHours": 1000}, {"start": "2026-02-02T04:30", "wattHours": 1000}, {"start": "2026-02-02T05:00", "wattHours": 1000}], [{"name": "off-peak", "start": "22:00", "end": "05:00", "rate": 9000}, {"name": "peak", "start": "05:00", "end": "22:00", "rate": 35000}], "GBP", "half-up"], "expect": {"lines": [{"name": "off-peak", "wattHours": 2000, "cost": {"minor": 18, "currency": "GBP"}}, {"name": "peak", "wattHours": 1000, "cost": {"minor": 35, "currency": "GBP"}}], "wattHours": 3000, "total": {"minor": 53, "currency": "GBP"}}},
  {"name": "start equal to end is a flat rate all day", "args": [[{"start": "2026-05-01T12:00", "wattHours": 1234}, {"start": "2026-05-01T00:00", "wattHours": 766}], [{"name": "standard", "start": "00:00", "end": "00:00", "rate": 24567}], "GBP", "half-up"], "expect": {"lines": [{"name": "standard", "wattHours": 2000, "cost": {"minor": 49, "currency": "GBP"}}], "wattHours": 2000, "total": {"minor": 49, "currency": "GBP"}}},
  {"name": "half a penny rounds up under half-up", "args": [[{"start": "2026-05-01T12:00", "wattHours": 500}], [{"name": "flat", "start": "00:00", "end": "00:00", "rate": 1000}], "GBP", "half-up"], "expect": {"lines": [{"name": "flat", "wattHours": 500, "cost": {"minor": 1, "currency": "GBP"}}], "wattHours": 500, "total": {"minor": 1, "currency": "GBP"}}},
  {"name": "half a penny rounds to the even 0 under half-even", "args": [[{"start": "2026-05-01T12:00", "wattHours": 500}], [{"name": "flat", "start": "00:00", "end": "00:00", "rate": 1000}], "GBP", "half-even"], "expect": {"lines": [{"name": "flat", "wattHours": 500, "cost": {"minor": 0, "currency": "GBP"}}], "wattHours": 500, "total": {"minor": 0, "currency": "GBP"}}},
  {"name": "a rate name with no usage still gets a zero line; no usage at all is zero", "args": [[], [{"name": "night", "start": "00:30", "end": "07:30", "rate": 12000}, {"name": "day", "start": "07:30", "end": "00:30", "rate": 30000}], "GBP", "half-up"], "expect": {"lines": [{"name": "night", "wattHours": 0, "cost": {"minor": 0, "currency": "GBP"}}, {"name": "day", "wattHours": 0, "cost": {"minor": 0, "currency": "GBP"}}], "wattHours": 0, "total": {"minor": 0, "currency": "GBP"}}},
  {"name": "a dated peak between two daily windows: the peak covers 17:00-19:00 on that day only", "args": [[{"start": "2026-06-01T10:00", "wattHours": 1000}, {"start": "2026-06-01T18:00", "wattHours": 1000}], [{"name": "day", "start": "00:00", "end": "17:00", "rate": 20000}, {"name": "peak", "start": "2026-06-01T17:00", "end": "2026-06-01T19:00", "rate": 40000}, {"name": "evening", "start": "19:00", "end": "00:00", "rate": 20000}], "GBP", "half-up"], "expect": {"lines": [{"name": "day", "wattHours": 1000, "cost": {"minor": 20, "currency": "GBP"}}, {"name": "peak", "wattHours": 1000, "cost": {"minor": 40, "currency": "GBP"}}, {"name": "evening", "wattHours": 0, "cost": {"minor": 0, "currency": "GBP"}}], "wattHours": 2000, "total": {"minor": 60, "currency": "GBP"}}},
  {"name": "daily and dated rates together without overlap", "args": [[{"start": "2026-06-01T10:00", "wattHours": 1000}, {"start": "2026-06-01T18:00", "wattHours": 1000}], [{"name": "day", "start": "00:00", "end": "17:00", "rate": 20000}, {"name": "peak", "start": "2026-06-01T17:00", "end": "2026-06-01T19:00", "rate": 40000}, {"name": "late", "start": "17:00", "end": "00:00", "rate": 20000}], "GBP", "half-up"], "expectError": "rates peak and late both cover the half hour starting 2026-06-01T18:00"},
  {"name": "line costs sum to the total even when the exact total would round differently", "args": [[{"start": "2026-01-15T01:00", "wattHours": 500}, {"start": "2026-01-15T12:00", "wattHours": 500}], [{"name": "night", "start": "00:30", "end": "07:30", "rate": 1000}, {"name": "day", "start": "07:30", "end": "00:30", "rate": 1000}], "GBP", "half-up"], "expect": {"lines": [{"name": "night", "wattHours": 500, "cost": {"minor": 1, "currency": "GBP"}}, {"name": "day", "wattHours": 500, "cost": {"minor": 1, "currency": "GBP"}}], "wattHours": 1000, "total": {"minor": 2, "currency": "GBP"}}},
  {"name": "a half hour no rate covers is an error, not free energy", "args": [[{"start": "2026-01-15T08:00", "wattHours": 100}], [{"name": "night", "start": "00:30", "end": "07:30", "rate": 12000}], "GBP", "half-up"], "expectError": "no rate covers the half hour starting 2026-01-15T08:00"},
  {"name": "overlapping windows are an error", "args": [[{"start": "2026-01-15T07:00", "wattHours": 100}], [{"name": "night", "start": "00:00", "end": "07:30", "rate": 12000}, {"name": "day", "start": "07:00", "end": "00:00", "rate": 30000}], "GBP", "half-up"], "expectError": "rates night and day both cover the half hour starting 2026-01-15T07:00"},
  {"name": "the same half hour twice is an error", "args": [[{"start": "2026-01-15T07:00", "wattHours": 100}, {"start": "2026-01-15T07:00", "wattHours": 100}], [{"name": "night", "start": "00:30", "end": "07:30", "rate": 12000}, {"name": "day", "start": "07:30", "end": "00:30", "rate": 30000}], "GBP", "half-up"], "expectError": "usage for the half hour starting 2026-01-15T07:00 appears twice"},
  {"name": "a usage start off the half hour is refused", "args": [[{"start": "2026-01-15T07:15", "wattHours": 100}], [{"name": "night", "start": "00:30", "end": "07:30", "rate": 12000}, {"name": "day", "start": "07:30", "end": "00:30", "rate": 30000}], "GBP", "half-up"], "expectError": "usage start must be YYYY-MM-DDTHH:MM on the hour or half hour"},
  {"name": "a rate window off the half hour is refused", "args": [[], [{"name": "night", "start": "00:15", "end": "07:15", "rate": 12000}], "GBP", "half-up"], "expectError": "rate night: start and end must both be HH:MM or both YYYY-MM-DDTHH:MM"},
  {"name": "a dated rate that ends before it starts is refused", "args": [[], [{"name": "agile", "start": "2026-03-01T14:00", "end": "2026-03-01T13:30", "rate": 100}], "GBP", "half-up"], "expectError": "rate agile: start must be before end"},
  {"name": "negative usage is refused", "args": [[{"start": "2026-01-15T07:00", "wattHours": -100}], [{"name": "night", "start": "00:30", "end": "07:30", "rate": 12000}, {"name": "day", "start": "07:30", "end": "00:30", "rate": 30000}], "GBP", "half-up"], "expectError": "usage wattHours must be a whole number of 0 or more"},
  {"name": "a fractional rate is refused", "args": [[], [{"name": "flat", "start": "00:00", "end": "00:00", "rate": 24.5}], "GBP", "half-up"], "expectError": "rate flat: rate must be a whole number of thousandths of a minor unit"},
  {"name": "a usage start with a trailing newline is refused", "args": [[{"start": "2026-01-15T07:00\n", "wattHours": 100}], [{"name": "night", "start": "00:30", "end": "07:30", "rate": 12000}, {"name": "day", "start": "07:30", "end": "00:30", "rate": 30000}], "GBP", "half-up"], "expectError": "usage start must be YYYY-MM-DDTHH:MM on the hour or half hour"},
  {"name": "a usage start in Arabic-Indic digits is refused", "args": [[{"start": "٢٠٢٦-٠١-١٥T07:00", "wattHours": 100}], [{"name": "night", "start": "00:30", "end": "07:30", "rate": 12000}, {"name": "day", "start": "07:30", "end": "00:30", "rate": 30000}], "GBP", "half-up"], "expectError": "usage start must be YYYY-MM-DDTHH:MM on the hour or half hour"},
  {"name": "a daily rate end with a trailing newline is refused", "args": [[], [{"name": "night", "start": "00:30", "end": "07:30\n", "rate": 12000}], "GBP", "half-up"], "expectError": "rate night: start and end must both be HH:MM or both YYYY-MM-DDTHH:MM"},
  {"name": "a daily rate start in Arabic-Indic digits is refused", "args": [[], [{"name": "night", "start": "0٠:30", "end": "07:30", "rate": 12000}], "GBP", "half-up"], "expectError": "rate night: start and end must both be HH:MM or both YYYY-MM-DDTHH:MM"},
  {"name": "a dated rate start with a trailing newline is refused", "args": [[], [{"name": "agile", "start": "2026-03-01T13:00\n", "end": "2026-03-01T14:00", "rate": 100}], "GBP", "half-up"], "expectError": "rate agile: start and end must both be HH:MM or both YYYY-MM-DDTHH:MM"}
]