From 344721757afc3397603d2e1d73fb864434aafaeb Mon Sep 17 00:00:00 2001 From: Emi Griffith Date: Sun, 19 Jul 2026 12:28:58 -0700 Subject: [PATCH] Report rain in whole millimetres; lift tier spans out of the bars (#192) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two fixes to how measurements read. Rain goes back to millimetres, rendered as whole numbers — that is how rainfall is reported, and a tenth of a millimetre is below what the source resolves. Inches keep two decimals, since a whole inch of rain is a lot to round to. The distribution strip's low-high span moves from inside each bar to just under the average, above it. A column is ~38px on a phone, so the old "Max 62° / Min 17°" pill clipped — it had already been shrunk to 8px to cope, and on a high-rainfall city with longer values it lost both ends of the label, because the text is centred and overflow-hidden. Above the bar it has the full column width and needs no scrim to stay legible over nine tier colours. The span is now bare numbers ("17-62"), since the average directly above it carries the unit; repeating " g/m³" on both ends is what made it too wide in the first place. It uses the same precision as that average, or the two lines disagree ("52mm" over "12.7-136.91"). Sub-1 inch values drop their leading zero so precip's ten columns still fit a phone. With nothing inside the bars, the height floor drops from 30px to 14px: it only has to keep a non-empty bucket visible now, so the heights encode frequency more honestly. Category names get 2px of side padding, which stops long neighbours ("Light-Mod" beside "Moderate") reading as one word. Verified at 390/412/1920 in both themes across all four calendar metrics and the compare strips, in imperial and metric: nothing clips. Claude-Session: https://claude.ai/code/session_013dRZmX9D3JEntfMKWMTWZ8 --- content.py | 12 +++++++----- tests/test_content.py | 20 ++++++++++---------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/content.py b/content.py index 5a4c37c..bcaa6a6 100644 --- a/content.py +++ b/content.py @@ -69,9 +69,9 @@ _TEMP_METRICS = {"tmax", "tmin", "feels"} F_COUNTRIES = frozenset({"US", "PR", "GU", "VI", "AS", "MP", "UM", "BS", "BZ", "KY", "PW", "FM", "MH", "LR"}) -# One flag drives every measure: a °C page also gets cm and km/h. Mirrors the same +# One flag drives every measure: a °C page also gets mm and km/h. Mirrors the same # decision in frontend/units.js. -CM_PER_IN = 2.54 +MM_PER_IN = 25.4 KMH_PER_MPH = 1.609344 # The unit the current page renders temperatures in. None = °F, and also means @@ -171,11 +171,13 @@ def _precip(v): def _precip_text(v) -> str: - """Two decimals either way — cm and inches are only 2.54x apart, so cm needs - the same resolution, and the source is hundredths of an inch.""" + """Whole millimetres — that is how rainfall is reported, and a tenth of a + millimetre is below what the source resolves. Inches keep two decimals, since + a whole inch of rain is a lot to round to.""" if v is None: return "—" - return f"{v * CM_PER_IN:.2f} cm" if _UNIT.get() == "C" else f"{v:.2f} in" + return (f"{_round_half_up(v * MM_PER_IN)} mm" if _UNIT.get() == "C" + else f"{v:.2f} in") def _wind(v): diff --git a/tests/test_content.py b/tests/test_content.py index 5fe77c4..e8421de 100644 --- a/tests/test_content.py +++ b/tests/test_content.py @@ -304,11 +304,11 @@ def test_units_default_to_the_citys_country(client): assert 'data-unit-default="F"' in us -_PRECIP_SPAN = re.compile(r'([\d.]+) (in|cm)') +_PRECIP_SPAN = re.compile(r'([\d.]+) (in|mm)') def test_precip_and_wind_follow_the_citys_unit(client): - # One toggle drives every measure: a °C page shows cm and km/h, not °C mixed + # One toggle drives every measure: a °C page shows mm and km/h, not °C mixed # with inches and mph. The data-* attribute stays imperial in both, so # climate.js converts from the same source of truth it does for temperature. gb = client.get(f"{B}/climate/{SLUG}/records").text # London, GB @@ -317,12 +317,12 @@ def test_precip_and_wind_follow_the_citys_unit(client): gb_p, us_p = _PRECIP_SPAN.findall(gb), _PRECIP_SPAN.findall(us) assert gb_p and us_p - assert {u for _raw, _shown, u in gb_p} == {"cm"} + assert {u for _raw, _shown, u in gb_p} == {"mm"} assert {u for _raw, _shown, u in us_p} == {"in"} # The conversion is real, not just a relabel. for raw, shown, _u in gb_p: - assert abs(float(shown) - float(raw) * 2.54) < 0.005, (raw, shown) + assert abs(float(shown) - float(raw) * 25.4) < 0.51, (raw, shown) # On an imperial page the attribute and the text agree. for raw, shown, _u in us_p: assert abs(float(shown) - float(raw)) < 0.005, (raw, shown) @@ -348,14 +348,14 @@ def test_wind_and_humidity_render_per_unit_system(): def test_precip_precision_differs_by_unit(): - # cm and inches are only 2.54x apart, so cm keeps two decimals — at one - # decimal every light-rain day would collapse to 0.0 or 0.1 cm. + # Rainfall is reported in whole millimetres; a tenth of a mm is below what + # the source resolves. Inches keep two decimals. import content with content._unit_scope("F"): assert content._precip_text(0.04) == "0.04 in" with content._unit_scope("C"): - assert content._precip_text(0.04) == "0.10 cm" - assert content._precip_text(1.81) == "4.60 cm" + assert content._precip_text(0.04) == "1 mm" + assert content._precip_text(1.81) == "46 mm" def test_measure_conversion_constants_match_the_frontend(): @@ -366,9 +366,9 @@ def test_measure_conversion_constants_match_the_frontend(): os.pardir, "frontend", "units.js") with open(units_js, encoding="utf-8") as f: src = f.read() - cm = float(re.search(r"CM_PER_IN\s*=\s*([\d.]+)", src).group(1)) + mm = float(re.search(r"MM_PER_IN\s*=\s*([\d.]+)", src).group(1)) kmh = float(re.search(r"KMH_PER_MPH\s*=\s*([\d.]+)", src).group(1)) - assert cm == content.CM_PER_IN + assert mm == content.MM_PER_IN assert kmh == content.KMH_PER_MPH