Functional Weave
Code in Python

energy.degree-days

Heating and cooling degree days from daily mean temperatures and a base temperature, exact to 0.1.

1.0.0 · published 2026-10-03 by charlie · Anterra

Pinned by 14 tests, run in TypeScript, Python and Rust.

What it does

Heating degree days (HDD) and cooling degree days (CDD) for a run of days, from each day's mean temperature and a base temperature:

HDD = sum over days of max(0, base - mean)
CDD = sum over days of max(0, mean - base)

For example

  • degree_days(100, 155, 200, 155) → heating 55, cooling 45, days 3 one cold, one at the base, one warm day at 15.5
  • degree_days(21, -13, 0, 35, 48, 62, 17, 155) → heating 915, cooling 0, days 7 a cold week in January
  • degree_days(221, 254, 198, 155) → heating 0, cooling 208, days 3 a heatwave counts only cooling

The function

The same function in TypeScript, Python and Rust, pinned by the same tests. Pick your language; the choice follows you around the registry.

def degree_days(daily_mean_temperatures: Sequence[int], base_temperature: int) -> DegreeDays
daily_mean_temperaturesint[]one mean per day, in tenths of a degree Celsius: 12.3 °C is 123
base_temperatureintin tenths of a degree: the UK convention is 15.5 °C, 155
returnsDegreeDays

The type it declares, generated into your project

@dataclass(frozen=True)
class DegreeDays:
    """Heating and cooling degree days over the same run of days."""

    #: sum of how far each day's mean fell below the base, in tenths of a degree day
    heating: int
    #: sum of how far each day's mean rose above the base, in tenths of a degree day
    cooling: int
    #: how many daily means were counted
    days: int

Your code names it in one line, in the file that uses it

from fune.energy.degree_days import degree_days  # energy.degree-days@^1
impl/python.py · 26 lines · open · raw
from typing import Sequence

from .energy_degree_days_types import DegreeDays

LIMIT = 1000  # 100.0 C in tenths


def _check_temperature(what: str, value: object) -> None:
    if not isinstance(value, int) or isinstance(value, bool) or value < -LIMIT or value > LIMIT:
        raise ValueError("%s must be whole tenths of a degree from -1000 to 1000, received %s" % (what, value))


def degree_days(daily_mean_temperatures: Sequence[int], base_temperature: int) -> DegreeDays:
    """Heating and cooling degree days in tenths: each day adds how far its
    mean fell below (heating) or rose above (cooling) the base.
    """
    _check_temperature("base temperature", base_temperature)
    heating = 0
    cooling = 0
    for mean in daily_mean_temperatures:
        _check_temperature("daily mean temperature", mean)
        if mean < base_temperature:
            heating += base_temperature - mean
        elif mean > base_temperature:
            cooling += mean - base_temperature
    return DegreeDays(heating=heating, cooling=cooling, days=len(daily_mean_temperatures))

Install

fune build

With that line in your source, in a Python project (language python in fune.project), fune build resolves it and nothing else, pins them in fune.lock, downloads only the Python package of each, and builds the code above into your project’s .fune/build, one readable file per capability with a header linking back here. Or pin a range in fune.project and build in one step:

fune add energy.degree-days
Download for Python energy.degree-days-1.0.0-python.fune · 7,196 bytes sha256 239b5cfa5a11ee50dbcd96ce022d91969de4b53075be63c088fbb7729a460e98

The manifest, vectors and README with only the Python implementation. Install it without the registry with fune add ./energy.degree-days-1.0.0-python.fune, or fetch it from a terminal with fune pull energy.degree-days@1.0.0:python.

The whole function, every language, is one file too: energy.degree-days-1.0.0.fune, 10,167 bytes, sha256 dadb9334811da41846ae4e0017ec98006bd554a04830a1c1dd1ed49719b483e0. It installs into a project of any language.

Customise it in your app

The seams this capability offers. Put a marker directly above a function of your own and fune build wires it into the built code; the package on the registry is not changed, the built file’s header lists it under CUSTOMISED, and fune hooks lists every hook in the project. How hooks work.

before — your function gets the arguments and returns them, changed or not, or throws to refuse the call.

# fune: before energy.degree-days

after — your function gets the result and the arguments, and returns the final result.

# fune: after energy.degree-days

replace — it requires no other capability, so there is no dependency to replace.

step — your function runs at a numbered point inside the function’s body, receives the in-scope values it names as parameters, and may return replacements. List the points with fune show energy.degree-days --steps.

# fune: step energy.degree-days after <n|label>

Tests

A version published now needs at least 8 tests for every function, and one that expects the error for each function that throws; the registry refuses it otherwise. fune verify --all runs each case in TypeScript, Python and Rust, and a project runs them again with fune verify. This page lists the cases; it does not run them. The exact JSON is vectors.json.

CaseArgumentsExpected
one cold, one at the base, one warm day at 15.5 100, 155, 200, 155 → heating 55, cooling 45, days 3
a cold week in January 21, -13, 0, 35, 48, 62, 17, 155 → heating 915, cooling 0, days 7
a heatwave counts only cooling 221, 254, 198, 155 → heating 0, cooling 208, days 3
warm and cold days do not cancel out 55, 255, 155 → heating 100, cooling 100, days 2
a day exactly at the base counts for neither 155, 155 → heating 0, cooling 0, days 1
tenths below the base are kept, not rounded away 154, 154, 154, 155 → heating 3, cooling 0, days 3
below freezing adds the full distance to the base -25, 155 → heating 180, cooling 0, days 1
a lower base for a well-insulated building 100, 120, 130, 120 → heating 20, cooling 10, days 3
a cooling base of 22 °C 230, 215, 260, 220 → heating 5, cooling 50, days 3
no days is no degree days , 155 → heating 0, cooling 0, days 0
Show the other 4 tests
CaseArgumentsExpected
the extremes allowed -1,000, 1,000, 0 → heating 1,000, cooling 1,000, days 2
a temperature in hundredths is out of range 1,550, 155 → error: daily mean temperature must be whole tenths of a degree
a fractional tenth is refused 123.5, 155 → error: daily mean temperature must be whole tenths of a degree
a base in Fahrenheit tenths is out of range 100, 1,500 → error: base temperature must be whole tenths of a degree

More from the author

They are the standard way to normalise heating (or cooling) energy for the weather: kWh per degree day is comparable between a mild and a cold winter where raw kWh is not.

## Units

Temperatures and results are integers in **tenths of a degree**: 12.3 °C is `123`, and 45 degree days is `450`. Temperatures are recorded to a tenth of a degree, and with integer tenths every sum is exact, so the answer is exact to 0.1 of a degree day in all three languages rather than approximately so.

## Decisions

- **The base is an argument.** 15.5 °C (`155`) is the long-standing UK convention for heating degree days (CIBSE TM41, and the base of the published UK regional series); buildings with better insulation or higher internal gains are often analysed at a lower base, and cooling is often counted above 15.5 °C or 18 °C. The same base is used for both sums. - **Mean-temperature method only.** Each day contributes `base - mean` when positive. The Met Office / UKMO method that estimates degree days from a day's maximum and minimum (for days where the base falls between them) is a different formula and is not what this does; pass the true daily mean. - **A day at exactly the base counts for neither.** - An empty list gives zeros. A mean outside -100.0 to 100.0 °C is refused as a unit mistake (Fahrenheit, or whole degrees times 100).

## Source

CIBSE TM41, "Degree-days: theory and application" (Chartered Institution of Building Services Engineers), for the definition and the 15.5 °C UK base.

Files

PathBytes
README.md1,773
impl/python.py1,078
impl/rust.rs1,819
impl/typescript.ts1,015
vectors.json1,969