FA-70846 / Weather index computation / Open access
Heat index regression: dry adjustment direction · case 01
Very dry desert readings come out hotter than the regression instead of cooler.
ROOT CAUSE
The low-humidity correction is added to the regression although dry air lowers apparent heat.
VERIFIED REPAIR
Subtract ((13 - RH)/4)*sqrt((17 - |T - 95|)/17) inside the dry window.
Unsuccessful approach: Dropping the absolute value around T - 95 inflates the correction for temperatures below 95 degF.
Case contract
Input dry-bulb temperature in degF and relative humidity in percent. Compute the simple Steadman estimate 0.5*(T+61+1.2*(T-68)+0.094*RH); if the mean of that estimate and T is below 80 return it, otherwise use the Rothfusz regression with the dry adjustment (RH<13, 80<=T<=112) or the humid adjustment (RH>85, 80<=T<=87). Round to 0.1 degF.
Why this case matters
Heat advisories are issued from this value; a wrong branch changes public warnings.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(t_f, rh):
simple = 0.5 * (t_f + 61.0 + (t_f - 68.0) * 1.2 + rh * 0.094)
if (simple + t_f) / 2 < 80:
return round(simple, 1)
hi = (-42.379 + 2.04901523 * t_f + 10.14333127 * rh - 0.22475541 * t_f * rh
- 0.00683783 * t_f * t_f - 0.05481717 * rh * rh + 0.00122874 * t_f * t_f * rh
+ 0.00085282 * t_f * rh * rh - 0.00000199 * t_f * t_f * rh * rh)
if rh < 13 and 80 <= t_f <= 112:
hi += ((13 - rh) / 4) * math.sqrt((17 - abs(t_f - 95)) / 17)
elif rh > 85 and 80 <= t_f <= 87:
hi += ((rh - 85) / 10) * ((87 - t_f) / 5)
return round(hi, 1)
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['70F at 0% RH', [70, 0], 66.7], ['70F at 5% RH', [70, 5], 66.9], ['70F at 10% RH', [70, 10], 67.2], ['70F at 12% RH', [70, 12], 67.3], ['70F at 13% RH', [70, 13], 67.3], ['70F at 40% RH', [70, 40], 68.6], ['81.5F at 0% RH', [81.5, 0], 77.7], ['81.5F at 5% RH', [81.5, 5], 78.3]], [['70F at 85% RH', [70, 85], 70.7], ['70F at 86% RH', [70, 86], 70.7], ['70F at 90% RH', [70, 90], 70.9], ['70F at 95% RH', [70, 95], 71.2], ['70F at 100% RH', [70, 100], 71.4], ['76F at 0% RH', [76, 0], 73.3], ['81.5F at 12% RH', [81.5, 12], 79.1], ['82F at 10% RH', [82, 10], 79.2]], [['76F at 10% RH', [76, 10], 73.8], ['76F at 12% RH', [76, 12], 73.9], ['76F at 13% RH', [76, 13], 73.9], ['76F at 40% RH', [76, 40], 75.2], ['76F at 60% RH', [76, 60], 76.1], ['76F at 85% RH', [76, 85], 77.3], ['82F at 10% RH', [82, 10], 79.2], ['84F at 12% RH', [84, 12], 80.9]], [['76F at 90% RH', [76, 90], 77.5], ['76F at 95% RH', [76, 95], 77.8], ['76F at 100% RH', [76, 100], 78.0], ['79F at 0% RH', [79, 0], 76.6], ['79F at 5% RH', [79, 5], 76.8], ['79F at 10% RH', [79, 10], 77.1], ['84F at 5% RH', [84, 5], 80.0], ['86F at 0% RH', [86, 0], 81.0]], [['79F at 13% RH', [79, 13], 77.2], ['79F at 40% RH', [79, 40], 78.5], ['79F at 60% RH', [79, 60], 79.4], ['79F at 85% RH', [79, 85], 80.6], ['79F at 86% RH', [79, 86], 80.6], ['79F at 90% RH', [79, 90], 80.8], ['85F at 0% RH', [85, 0], 80.3], ['87F at 5% RH', [87, 5], 82.2]]]
for label, args, expected in fixtures[N - 1]:
check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| 70F at 0% RH | 66.7 | 66.7 | Passed |
| 70F at 5% RH | 66.9 | 66.9 | Passed |
| 70F at 10% RH | 67.2 | 67.2 | Passed |
| 70F at 12% RH | 67.3 | 67.3 | Passed |
| 70F at 13% RH | 67.3 | 67.3 | Passed |
| 70F at 40% RH | 68.6 | 68.6 | Passed |
| 81.5F at 0% RH | 80.7 | 77.7 | Failed |
| 81.5F at 5% RH | 80.1 | 78.3 | Failed |
SHA-256 / a6ac3289cd777bad0afff24528578157151517a43c219d8b5fbeedcb23a5e3e1
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(t_f, rh):
simple = 0.5 * (t_f + 61.0 + (t_f - 68.0) * 1.2 + rh * 0.094)
if (simple + t_f) / 2 < 80:
return round(simple, 1)
hi = (-42.379 + 2.04901523 * t_f + 10.14333127 * rh - 0.22475541 * t_f * rh
- 0.00683783 * t_f * t_f - 0.05481717 * rh * rh + 0.00122874 * t_f * t_f * rh
+ 0.00085282 * t_f * rh * rh - 0.00000199 * t_f * t_f * rh * rh)
if rh < 13 and 80 <= t_f <= 112:
hi -= ((13 - rh) / 4) * math.sqrt((17 - (t_f - 95)) / 17)
elif rh > 85 and 80 <= t_f <= 87:
hi += ((rh - 85) / 10) * ((87 - t_f) / 5)
return round(hi, 1)
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['70F at 0% RH', [70, 0], 66.7], ['70F at 5% RH', [70, 5], 66.9], ['70F at 10% RH', [70, 10], 67.2], ['70F at 12% RH', [70, 12], 67.3], ['70F at 13% RH', [70, 13], 67.3], ['70F at 40% RH', [70, 40], 68.6], ['81.5F at 0% RH', [81.5, 0], 77.7], ['81.5F at 5% RH', [81.5, 5], 78.3]], [['70F at 85% RH', [70, 85], 70.7], ['70F at 86% RH', [70, 86], 70.7], ['70F at 90% RH', [70, 90], 70.9], ['70F at 95% RH', [70, 95], 71.2], ['70F at 100% RH', [70, 100], 71.4], ['76F at 0% RH', [76, 0], 73.3], ['81.5F at 12% RH', [81.5, 12], 79.1], ['82F at 10% RH', [82, 10], 79.2]], [['76F at 10% RH', [76, 10], 73.8], ['76F at 12% RH', [76, 12], 73.9], ['76F at 13% RH', [76, 13], 73.9], ['76F at 40% RH', [76, 40], 75.2], ['76F at 60% RH', [76, 60], 76.1], ['76F at 85% RH', [76, 85], 77.3], ['82F at 10% RH', [82, 10], 79.2], ['84F at 12% RH', [84, 12], 80.9]], [['76F at 90% RH', [76, 90], 77.5], ['76F at 95% RH', [76, 95], 77.8], ['76F at 100% RH', [76, 100], 78.0], ['79F at 0% RH', [79, 0], 76.6], ['79F at 5% RH', [79, 5], 76.8], ['79F at 10% RH', [79, 10], 77.1], ['84F at 5% RH', [84, 5], 80.0], ['86F at 0% RH', [86, 0], 81.0]], [['79F at 13% RH', [79, 13], 77.2], ['79F at 40% RH', [79, 40], 78.5], ['79F at 60% RH', [79, 60], 79.4], ['79F at 85% RH', [79, 85], 80.6], ['79F at 86% RH', [79, 86], 80.6], ['79F at 90% RH', [79, 90], 80.8], ['85F at 0% RH', [85, 0], 80.3], ['87F at 5% RH', [87, 5], 82.2]]]
for label, args, expected in fixtures[N - 1]:
check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| 70F at 0% RH | 66.7 | 66.7 | Passed |
| 70F at 5% RH | 66.9 | 66.9 | Passed |
| 70F at 10% RH | 67.2 | 67.2 | Passed |
| 70F at 12% RH | 67.3 | 67.3 | Passed |
| 70F at 13% RH | 67.3 | 67.3 | Passed |
| 70F at 40% RH | 68.6 | 68.6 | Passed |
| 81.5F at 0% RH | 74.8 | 77.7 | Failed |
| 81.5F at 5% RH | 76.5 | 78.3 | Failed |
SHA-256 / 0db28dfdecf88b7b70160d2228d30320b0a05512b34bfc81fd15bbd0c268e0ce
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(t_f, rh):
simple = 0.5 * (t_f + 61.0 + (t_f - 68.0) * 1.2 + rh * 0.094)
if (simple + t_f) / 2 < 80:
return round(simple, 1)
hi = (-42.379 + 2.04901523 * t_f + 10.14333127 * rh - 0.22475541 * t_f * rh
- 0.00683783 * t_f * t_f - 0.05481717 * rh * rh + 0.00122874 * t_f * t_f * rh
+ 0.00085282 * t_f * rh * rh - 0.00000199 * t_f * t_f * rh * rh)
if rh < 13 and 80 <= t_f <= 112:
hi -= ((13 - rh) / 4) * math.sqrt((17 - abs(t_f - 95)) / 17)
elif rh > 85 and 80 <= t_f <= 87:
hi += ((rh - 85) / 10) * ((87 - t_f) / 5)
return round(hi, 1)
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['70F at 0% RH', [70, 0], 66.7], ['70F at 5% RH', [70, 5], 66.9], ['70F at 10% RH', [70, 10], 67.2], ['70F at 12% RH', [70, 12], 67.3], ['70F at 13% RH', [70, 13], 67.3], ['70F at 40% RH', [70, 40], 68.6], ['81.5F at 0% RH', [81.5, 0], 77.7], ['81.5F at 5% RH', [81.5, 5], 78.3]], [['70F at 85% RH', [70, 85], 70.7], ['70F at 86% RH', [70, 86], 70.7], ['70F at 90% RH', [70, 90], 70.9], ['70F at 95% RH', [70, 95], 71.2], ['70F at 100% RH', [70, 100], 71.4], ['76F at 0% RH', [76, 0], 73.3], ['81.5F at 12% RH', [81.5, 12], 79.1], ['82F at 10% RH', [82, 10], 79.2]], [['76F at 10% RH', [76, 10], 73.8], ['76F at 12% RH', [76, 12], 73.9], ['76F at 13% RH', [76, 13], 73.9], ['76F at 40% RH', [76, 40], 75.2], ['76F at 60% RH', [76, 60], 76.1], ['76F at 85% RH', [76, 85], 77.3], ['82F at 10% RH', [82, 10], 79.2], ['84F at 12% RH', [84, 12], 80.9]], [['76F at 90% RH', [76, 90], 77.5], ['76F at 95% RH', [76, 95], 77.8], ['76F at 100% RH', [76, 100], 78.0], ['79F at 0% RH', [79, 0], 76.6], ['79F at 5% RH', [79, 5], 76.8], ['79F at 10% RH', [79, 10], 77.1], ['84F at 5% RH', [84, 5], 80.0], ['86F at 0% RH', [86, 0], 81.0]], [['79F at 13% RH', [79, 13], 77.2], ['79F at 40% RH', [79, 40], 78.5], ['79F at 60% RH', [79, 60], 79.4], ['79F at 85% RH', [79, 85], 80.6], ['79F at 86% RH', [79, 86], 80.6], ['79F at 90% RH', [79, 90], 80.8], ['85F at 0% RH', [85, 0], 80.3], ['87F at 5% RH', [87, 5], 82.2]]]
for label, args, expected in fixtures[N - 1]:
check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| 70F at 0% RH | 66.7 | 66.7 | Passed |
| 70F at 5% RH | 66.9 | 66.9 | Passed |
| 70F at 10% RH | 67.2 | 67.2 | Passed |
| 70F at 12% RH | 67.3 | 67.3 | Passed |
| 70F at 13% RH | 67.3 | 67.3 | Passed |
| 70F at 40% RH | 68.6 | 68.6 | Passed |
| 81.5F at 0% RH | 77.7 | 77.7 | Passed |
| 81.5F at 5% RH | 78.3 | 78.3 | Passed |
SHA-256 / 0a7fe6c780788d8c6e05d9bd6ab1d5490775f6c165b7a8a072b2f6a63d4ca2dc
Verification & scope
Stipulated deterministic teaching model of an operational weather index; coefficients and thresholds are fixed by the contract and no claim of standards conformance is made. This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.
Observations recorded using Python 3.12.14 at 2026-09-29T14:48:24.409474+00:00.
Case digest / 30dccbbe3c44d892d6ba2e81289377fefebc1a16628dbfa5d17425e3e629549c