Weather-Crop-Damage/tests/test_derived.py
2026-10-27 14:13:32 +01:00

60 lines
2.8 KiB
Python

import numpy as np
import pandas as pd
import pytest
from damage_engine.sources import derived
# real hours of station 566 (2026-10-19): hourly mean T (°C), mean RH (%), 3A's TBulbMed (°C)
STATION_566 = [(13.3, 99, 13.2), (13.8, 90, 12.8), (15.6, 82, 13.8),
(15.8, 78, 13.6), (15.9, 76, 13.4), (15.2, 78, 13.0)]
@pytest.mark.parametrize("t, rh, tbulb_3a", STATION_566)
def test_formula_reproduces_3a_tbulbmed(t, rh, tbulb_3a):
# same formula as 3A (document section 4): equal to 3A's value at its 0.1 °C rounding
assert abs(float(derived.wet_bulb_c(t, rh)) - tbulb_3a) <= 0.05
def test_saturated_air_wet_bulb_equals_air_temperature():
for t in (-5.0, 0.0, 10.0):
assert abs(float(derived.wet_bulb_c(t, 100)) - t) < 1e-6
def test_wet_bulb_below_air_when_dry_and_ice_branch_below_zero():
assert float(derived.wet_bulb_c(20, 50)) < 20
assert float(derived.wet_bulb_c(-3, 80)) < -3
# T <= 0 uses the ice constants: the result is continuous enough across 0 °C
assert abs(float(derived.wet_bulb_c(0.0, 90)) - float(derived.wet_bulb_c(0.01, 90))) < 0.2
def test_dew_point_at_saturation_is_air_temperature():
assert abs(float(derived.dew_point_c(12.0, 100)) - 12.0) < 1e-6
def test_add_wet_bulb_pairs_tmin_rhmax_and_tmax_rhmin():
df = pd.DataFrame({derived.T_MIN: [14.6], derived.T_MEAN: [15.6], derived.T_MAX: [17.5],
derived.RH_MIN: [70.0], derived.RH_MEAN: [82.0], derived.RH_MAX: [90.0]})
out = derived.add_wet_bulb(df)
assert out[derived.WET_BULB_MIN][0] == round(float(derived.wet_bulb_c(14.6, 90)), 2)
assert out[derived.WET_BULB_MAX][0] == round(float(derived.wet_bulb_c(17.5, 70)), 2)
assert out[derived.WET_BULB_MIN][0] <= out[derived.WET_BULB_MAX][0]
assert derived.WET_BULB_MIN not in df # input not changed
def test_add_wet_bulb_falls_back_to_means_and_never_fails():
df = pd.DataFrame({derived.T_MEAN: [5.0, None], derived.RH_MEAN: [90.0, 80.0]})
out = derived.add_wet_bulb(df) # no min/max columns at all
expected = round(float(derived.wet_bulb_c(5.0, 90)), 2)
assert out[derived.WET_BULB_MIN][0] == expected == out[derived.WET_BULB_MAX][0]
assert np.isnan(out[derived.WET_BULB_MIN][1]) # no temperature -> NaN
assert derived.add_wet_bulb(pd.DataFrame({"x": [1]}))[derived.WET_BULB_MIN].isna().all()
def test_frost_point_below_zero_uses_ice_constants():
assert abs(float(derived.dew_point_c(0, 100))) < 1e-9
assert float(derived.dew_point_c(-3, 40)) < -10
# T <= 0 °C: e = RH * 6.1 * 10^(9.5 T / (T + 265.5)), inverted with the same constants
e = 0.95 * 6.1 * 10 ** (9.5 * -3 / (-3 + 265.5))
x = np.log10(e / 6.1)
assert abs(float(derived.dew_point_c(-3, 95)) - 265.5 * x / (9.5 - x)) < 1e-9