FA-71126 / Weather index computation / Open access
Beaufort force and marine warning: small craft upper edge · case 01
Force 8 winds only raise a small-craft advisory instead of a gale warning.
ROOT CAUSE
The small-craft band includes force 8.
VERIFIED REPAIR
Issue small-craft warnings for forces 6 and 7.
Unsuccessful approach: Banding on the gust force instead of the (possibly upgraded) sustained force mislabels gusty force 6-7 winds as gale.
Case contract
Convert knots to m/s (x0.514444) and round to 0.1 m/s. Beaufort force n is the first n with speed < bounds[n], bounds=[0.5,1.6,3.4,5.5,8.0,10.8,13.9,17.2,20.8,24.5,28.5,32.7], else 12. If the gust force is at least sustained force + 2 (and force < 12) upgrade by one. Warning: <6 none, <8 small craft, <10 gale, <12 storm, 12 hurricane force. Return [force, warning].
Why this case matters
Coastal marine forecasts issue small-craft, gale and storm warnings from Beaufort force.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(sustained_kt, gust_kt):
bounds = [0.5, 1.6, 3.4, 5.5, 8.0, 10.8, 13.9, 17.2, 20.8, 24.5, 28.5, 32.7]
def force(kt):
ms = round(kt * 0.514444, 1)
for n, upper in enumerate(bounds):
if ms < upper:
return n
return 12
b = force(sustained_kt)
g = force(gust_kt)
if g >= b + 2 and b < 12:
b += 1
if b < 6:
warn = "none"
elif b <= 8:
warn = "small craft"
elif b < 10:
warn = "gale"
elif b < 12:
warn = "storm"
else:
warn = "hurricane force"
return [b, warn]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['sustained 0 kt gust +0', [0, 0], [0, 'none']], ['sustained 0 kt gust +2', [0, 2], [0, 'none']], ['sustained 0 kt gust +8', [0, 8], [1, 'none']], ['sustained 0 kt gust +16', [0, 16], [1, 'none']], ['sustained 0.9 kt gust +0', [0.9, 0.9], [1, 'none']], ['sustained 0.9 kt gust +2', [0.9, 2.9], [1, 'none']], ['sustained 27 kt gust +8', [27, 35], [7, 'small craft']], ['sustained 27 kt gust +16', [27, 43], [8, 'gale']]], [['sustained 0.9 kt gust +16', [0.9, 16.9], [2, 'none']], ['sustained 1 kt gust +0', [1, 1], [1, 'none']], ['sustained 1 kt gust +2', [1, 3], [1, 'none']], ['sustained 1 kt gust +8', [1, 9], [2, 'none']], ['sustained 1 kt gust +16', [1, 17], [2, 'none']], ['sustained 3 kt gust +0', [3, 3], [1, 'none']], ['sustained 21 kt gust +16', [21, 37], [7, 'small craft']], ['sustained 33.4 kt gust +8', [33.4, 41.4], [8, 'gale']]], [['sustained 3 kt gust +8', [3, 11], [2, 'none']], ['sustained 3 kt gust +16', [3, 19], [2, 'none']], ['sustained 6.6 kt gust +0', [6.6, 6.6], [3, 'none']], ['sustained 6.6 kt gust +2', [6.6, 8.6], [3, 'none']], ['sustained 6.6 kt gust +8', [6.6, 14.6], [3, 'none']], ['sustained 6.6 kt gust +16', [6.6, 22.6], [4, 'none']], ['sustained 27 kt gust +8', [27, 35], [7, 'small craft']], ['sustained 34 kt gust +8', [34, 42], [8, 'gale']]], [['sustained 10.7 kt gust +2', [10.7, 12.7], [4, 'none']], ['sustained 10.7 kt gust +8', [10.7, 18.7], [4, 'none']], ['sustained 10.7 kt gust +16', [10.7, 26.7], [5, 'none']], ['sustained 16 kt gust +0', [16, 16], [5, 'none']], ['sustained 16 kt gust +2', [16, 18], [5, 'none']], ['sustained 16 kt gust +8', [16, 24], [5, 'none']], ['sustained 21 kt gust +16', [21, 37], [7, 'small craft']], ['sustained 33.4 kt gust +2', [33.4, 35.4], [8, 'gale']]], [['sustained 21 kt gust +0', [21, 21], [6, 'small craft']], ['sustained 21 kt gust +2', [21, 23], [6, 'small craft']], ['sustained 21 kt gust +8', [21, 29], [6, 'small craft']], ['sustained 21 kt gust +16', [21, 37], [7, 'small craft']], ['sustained 27 kt gust +0', [27, 27], [7, 'small craft']], ['sustained 27 kt gust +2', [27, 29], [7, 'small craft']], ['sustained 27 kt gust +8', [27, 35], [7, 'small craft']], ['sustained 34 kt gust +2', [34, 36], [8, 'gale']]]]
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 |
|---|---|---|---|
| sustained 0 kt gust +0 | [0, 'none'] | [0, 'none'] | Passed |
| sustained 0 kt gust +2 | [0, 'none'] | [0, 'none'] | Passed |
| sustained 0 kt gust +8 | [1, 'none'] | [1, 'none'] | Passed |
| sustained 0 kt gust +16 | [1, 'none'] | [1, 'none'] | Passed |
| sustained 0.9 kt gust +0 | [1, 'none'] | [1, 'none'] | Passed |
| sustained 0.9 kt gust +2 | [1, 'none'] | [1, 'none'] | Passed |
| sustained 27 kt gust +8 | [7, 'small craft'] | [7, 'small craft'] | Passed |
| sustained 27 kt gust +16 | [8, 'small craft'] | [8, 'gale'] | Failed |
SHA-256 / d5f66a9e124272b35d06fb23f8f7ae84cd7ff2bf103878d56ff60c1060fd3095
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(sustained_kt, gust_kt):
bounds = [0.5, 1.6, 3.4, 5.5, 8.0, 10.8, 13.9, 17.2, 20.8, 24.5, 28.5, 32.7]
def force(kt):
ms = round(kt * 0.514444, 1)
for n, upper in enumerate(bounds):
if ms < upper:
return n
return 12
b = force(sustained_kt)
g = force(gust_kt)
if g >= b + 2 and b < 12:
b += 1
if b < 6:
warn = "none"
elif g < 8:
warn = "small craft"
elif b < 10:
warn = "gale"
elif b < 12:
warn = "storm"
else:
warn = "hurricane force"
return [b, warn]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['sustained 0 kt gust +0', [0, 0], [0, 'none']], ['sustained 0 kt gust +2', [0, 2], [0, 'none']], ['sustained 0 kt gust +8', [0, 8], [1, 'none']], ['sustained 0 kt gust +16', [0, 16], [1, 'none']], ['sustained 0.9 kt gust +0', [0.9, 0.9], [1, 'none']], ['sustained 0.9 kt gust +2', [0.9, 2.9], [1, 'none']], ['sustained 27 kt gust +8', [27, 35], [7, 'small craft']], ['sustained 27 kt gust +16', [27, 43], [8, 'gale']]], [['sustained 0.9 kt gust +16', [0.9, 16.9], [2, 'none']], ['sustained 1 kt gust +0', [1, 1], [1, 'none']], ['sustained 1 kt gust +2', [1, 3], [1, 'none']], ['sustained 1 kt gust +8', [1, 9], [2, 'none']], ['sustained 1 kt gust +16', [1, 17], [2, 'none']], ['sustained 3 kt gust +0', [3, 3], [1, 'none']], ['sustained 21 kt gust +16', [21, 37], [7, 'small craft']], ['sustained 33.4 kt gust +8', [33.4, 41.4], [8, 'gale']]], [['sustained 3 kt gust +8', [3, 11], [2, 'none']], ['sustained 3 kt gust +16', [3, 19], [2, 'none']], ['sustained 6.6 kt gust +0', [6.6, 6.6], [3, 'none']], ['sustained 6.6 kt gust +2', [6.6, 8.6], [3, 'none']], ['sustained 6.6 kt gust +8', [6.6, 14.6], [3, 'none']], ['sustained 6.6 kt gust +16', [6.6, 22.6], [4, 'none']], ['sustained 27 kt gust +8', [27, 35], [7, 'small craft']], ['sustained 34 kt gust +8', [34, 42], [8, 'gale']]], [['sustained 10.7 kt gust +2', [10.7, 12.7], [4, 'none']], ['sustained 10.7 kt gust +8', [10.7, 18.7], [4, 'none']], ['sustained 10.7 kt gust +16', [10.7, 26.7], [5, 'none']], ['sustained 16 kt gust +0', [16, 16], [5, 'none']], ['sustained 16 kt gust +2', [16, 18], [5, 'none']], ['sustained 16 kt gust +8', [16, 24], [5, 'none']], ['sustained 21 kt gust +16', [21, 37], [7, 'small craft']], ['sustained 33.4 kt gust +2', [33.4, 35.4], [8, 'gale']]], [['sustained 21 kt gust +0', [21, 21], [6, 'small craft']], ['sustained 21 kt gust +2', [21, 23], [6, 'small craft']], ['sustained 21 kt gust +8', [21, 29], [6, 'small craft']], ['sustained 21 kt gust +16', [21, 37], [7, 'small craft']], ['sustained 27 kt gust +0', [27, 27], [7, 'small craft']], ['sustained 27 kt gust +2', [27, 29], [7, 'small craft']], ['sustained 27 kt gust +8', [27, 35], [7, 'small craft']], ['sustained 34 kt gust +2', [34, 36], [8, 'gale']]]]
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 |
|---|---|---|---|
| sustained 0 kt gust +0 | [0, 'none'] | [0, 'none'] | Passed |
| sustained 0 kt gust +2 | [0, 'none'] | [0, 'none'] | Passed |
| sustained 0 kt gust +8 | [1, 'none'] | [1, 'none'] | Passed |
| sustained 0 kt gust +16 | [1, 'none'] | [1, 'none'] | Passed |
| sustained 0.9 kt gust +0 | [1, 'none'] | [1, 'none'] | Passed |
| sustained 0.9 kt gust +2 | [1, 'none'] | [1, 'none'] | Passed |
| sustained 27 kt gust +8 | [7, 'gale'] | [7, 'small craft'] | Failed |
| sustained 27 kt gust +16 | [8, 'gale'] | [8, 'gale'] | Passed |
SHA-256 / bd0c52b9e5ca092b42f8ceb7d5410bad13dbf1efbeda94f986c5bf1bb2456de4
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(sustained_kt, gust_kt):
bounds = [0.5, 1.6, 3.4, 5.5, 8.0, 10.8, 13.9, 17.2, 20.8, 24.5, 28.5, 32.7]
def force(kt):
ms = round(kt * 0.514444, 1)
for n, upper in enumerate(bounds):
if ms < upper:
return n
return 12
b = force(sustained_kt)
g = force(gust_kt)
if g >= b + 2 and b < 12:
b += 1
if b < 6:
warn = "none"
elif b < 8:
warn = "small craft"
elif b < 10:
warn = "gale"
elif b < 12:
warn = "storm"
else:
warn = "hurricane force"
return [b, warn]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['sustained 0 kt gust +0', [0, 0], [0, 'none']], ['sustained 0 kt gust +2', [0, 2], [0, 'none']], ['sustained 0 kt gust +8', [0, 8], [1, 'none']], ['sustained 0 kt gust +16', [0, 16], [1, 'none']], ['sustained 0.9 kt gust +0', [0.9, 0.9], [1, 'none']], ['sustained 0.9 kt gust +2', [0.9, 2.9], [1, 'none']], ['sustained 27 kt gust +8', [27, 35], [7, 'small craft']], ['sustained 27 kt gust +16', [27, 43], [8, 'gale']]], [['sustained 0.9 kt gust +16', [0.9, 16.9], [2, 'none']], ['sustained 1 kt gust +0', [1, 1], [1, 'none']], ['sustained 1 kt gust +2', [1, 3], [1, 'none']], ['sustained 1 kt gust +8', [1, 9], [2, 'none']], ['sustained 1 kt gust +16', [1, 17], [2, 'none']], ['sustained 3 kt gust +0', [3, 3], [1, 'none']], ['sustained 21 kt gust +16', [21, 37], [7, 'small craft']], ['sustained 33.4 kt gust +8', [33.4, 41.4], [8, 'gale']]], [['sustained 3 kt gust +8', [3, 11], [2, 'none']], ['sustained 3 kt gust +16', [3, 19], [2, 'none']], ['sustained 6.6 kt gust +0', [6.6, 6.6], [3, 'none']], ['sustained 6.6 kt gust +2', [6.6, 8.6], [3, 'none']], ['sustained 6.6 kt gust +8', [6.6, 14.6], [3, 'none']], ['sustained 6.6 kt gust +16', [6.6, 22.6], [4, 'none']], ['sustained 27 kt gust +8', [27, 35], [7, 'small craft']], ['sustained 34 kt gust +8', [34, 42], [8, 'gale']]], [['sustained 10.7 kt gust +2', [10.7, 12.7], [4, 'none']], ['sustained 10.7 kt gust +8', [10.7, 18.7], [4, 'none']], ['sustained 10.7 kt gust +16', [10.7, 26.7], [5, 'none']], ['sustained 16 kt gust +0', [16, 16], [5, 'none']], ['sustained 16 kt gust +2', [16, 18], [5, 'none']], ['sustained 16 kt gust +8', [16, 24], [5, 'none']], ['sustained 21 kt gust +16', [21, 37], [7, 'small craft']], ['sustained 33.4 kt gust +2', [33.4, 35.4], [8, 'gale']]], [['sustained 21 kt gust +0', [21, 21], [6, 'small craft']], ['sustained 21 kt gust +2', [21, 23], [6, 'small craft']], ['sustained 21 kt gust +8', [21, 29], [6, 'small craft']], ['sustained 21 kt gust +16', [21, 37], [7, 'small craft']], ['sustained 27 kt gust +0', [27, 27], [7, 'small craft']], ['sustained 27 kt gust +2', [27, 29], [7, 'small craft']], ['sustained 27 kt gust +8', [27, 35], [7, 'small craft']], ['sustained 34 kt gust +2', [34, 36], [8, 'gale']]]]
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 |
|---|---|---|---|
| sustained 0 kt gust +0 | [0, 'none'] | [0, 'none'] | Passed |
| sustained 0 kt gust +2 | [0, 'none'] | [0, 'none'] | Passed |
| sustained 0 kt gust +8 | [1, 'none'] | [1, 'none'] | Passed |
| sustained 0 kt gust +16 | [1, 'none'] | [1, 'none'] | Passed |
| sustained 0.9 kt gust +0 | [1, 'none'] | [1, 'none'] | Passed |
| sustained 0.9 kt gust +2 | [1, 'none'] | [1, 'none'] | Passed |
| sustained 27 kt gust +8 | [7, 'small craft'] | [7, 'small craft'] | Passed |
| sustained 27 kt gust +16 | [8, 'gale'] | [8, 'gale'] | Passed |
SHA-256 / b3c516cb37df8058aa876d78a882692a3589c2701a4fda178b23988d4bfff986
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:26.852479+00:00.
Case digest / b5ffba9361b1eb5cf3634b4b61ea8da8ae64f8c6a044233e82c9947168e907c4