"""The comparison under a figure has to be right, or it undoes the figure. A wrong comparator is worse than none: the number above it is measured, and a reader who catches the comparison being wrong has no reason to trust the measurement either. """ from __future__ import annotations import math import pytest from distinct_server.comparators import ( COMPARATORS, KETTLE_EFFICIENCY, KWH, WATER_SPECIFIC_HEAT, basis, choose, describe, ) def test_there_are_about_a_hundred_of_them() -> None: assert len(COMPARATORS) >= 100 def test_every_entry_states_its_working() -> None: for entry in COMPARATORS: assert entry.note.strip(), entry.label assert entry.joules > 0, entry.label assert math.isfinite(entry.joules), entry.label def test_labels_are_unique() -> None: labels = [entry.label for entry in COMPARATORS] assert len(labels) == len(set(labels)) def test_the_physics_is_the_physics() -> None: """Spot-check the derived entries against the formula in the docstring.""" by_label = {entry.label: entry.joules for entry in COMPARATORS} # 0.25 l from 20 C to 100 C, in a kettle at 80%. expected = 0.25 * WATER_SPECIFIC_HEAT * 80 / KETTLE_EFFICIENCY assert by_label["mug of tea boiled"] == pytest.approx(expected, rel=0.01) # 0.568 l (a pint) over the same rise. expected = 0.568 * WATER_SPECIFIC_HEAT * 80 / KETTLE_EFFICIENCY assert by_label["pint of water boiled"] == pytest.approx(expected, rel=0.01) # 1 kg of water, 1 degree. assert by_label["litre of water warmed by 1 C"] == pytest.approx(WATER_SPECIFIC_HEAT, rel=0.001) # A 100 g apple lifted a metre. assert by_label["apple lifted one metre"] == pytest.approx(0.1 * 9.81, rel=0.01) # Rated appliances are watts times seconds. assert by_label["minute in a microwave"] == pytest.approx(1000 * 60) assert by_label["hour of a laptop"] == pytest.approx(50 * 3600) assert by_label["load of washing"] == pytest.approx(0.7 * KWH, rel=0.001) assert by_label["day of a UK home"] == pytest.approx(7.5 * KWH, rel=0.001) def test_a_figure_always_finds_a_comparison_it_can_carry() -> None: """Across nine decades, the count in front of the label stays picturable.""" value = 1.0 while value <= 1e10: chosen = choose(value) assert chosen is not None, value _, count = chosen assert 0.05 <= count <= 20, (value, count) value *= 3.0 def test_the_chosen_comparison_is_the_closest_one() -> None: entry, count = choose(104_650.0) assert entry.label == "mug of tea boiled" assert count == pytest.approx(1.0, rel=0.01) def test_singular_only_when_it_reads_as_one() -> None: """"about 0.98 minute" reads as a mistake, so the rule follows the printed number rather than a tolerance around 1.0.""" entry = next(e for e in COMPARATORS if e.label == "mug of tea boiled") assert entry.name(1.0) == "mug of tea boiled" assert entry.name(1.04) == "mug of tea boiled" assert entry.name(0.98) == "mugs of tea boiled" assert entry.name(2.0) == "mugs of tea boiled" assert entry.name(0.4) == "mugs of tea boiled" assert describe(104_650.0) == "about 1 mug of tea boiled" def test_nothing_sensible_to_say_says_nothing() -> None: for bad in (0, -1, None, float("nan"), float("inf"), True, "12"): assert describe(bad) == "" # type: ignore[arg-type] assert choose(bad) is None # type: ignore[arg-type] def test_the_working_travels_with_the_comparison() -> None: assert "4186" in basis(4186.0) or "kg" in basis(4186.0) assert basis(104_650.0) def test_no_conversion_this_project_refuses_to_make() -> None: """No CO2, water, land or money: those need assumptions a joule does not.""" forbidden = ("co2", "carbon", "gco2", "water footprint", "litres of water used", "£", "$") for entry in COMPARATORS: blob = f"{entry.label} {entry.note}".lower() for word in forbidden: assert word not in blob, (entry.label, word)