FA-71111 / Weather index computation / Open access
Beaufort force and marine warning: knot conversion · case 01
Warnings arrive a force late because winds read low.
ROOT CAUSE
Knots are converted with the mph-to-m/s factor.
VERIFIED REPAIR
Multiply knots by 0.514444.
Unsuccessful approach: Dividing by 0.514444 converts in the opposite direction.
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.44704, 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 0.9 kt gust +8', [0.9, 8.9], [2, 'none']], ['sustained 0.9 kt gust +16', [0.9, 16.9], [2, 'none']]], [['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 3 kt gust +2', [3, 5], [1, 'none']], ['sustained 3 kt gust +8', [3, 11], [2, 'none']]], [['sustained 1 kt gust +8', [1, 9], [2, 'none']], ['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 10.7 kt gust +0', [10.7, 10.7], [4, 'none']]], [['sustained 6.6 kt gust +2', [6.6, 8.6], [3, 'none']], ['sustained 6.6 kt gust +16', [6.6, 22.6], [4, 'none']], ['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 10.7 kt gust +2', [10.7, 12.7], [4, 'none']], ['sustained 16 kt gust +0', [16, 16], [5, 'none']], ['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']]]]
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 | [0, 'none'] | [1, 'none'] | Failed |
| sustained 0.9 kt gust +2 | [0, 'none'] | [1, 'none'] | Failed |
| sustained 0.9 kt gust +8 | [1, 'none'] | [2, 'none'] | Failed |
| sustained 0.9 kt gust +16 | [1, 'none'] | [2, 'none'] | Failed |
SHA-256 / cf3ee1d6c03e01e5571289bc932c34012c9d79098dd8080baa3a5a92b72a71e7
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 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 0.9 kt gust +8', [0.9, 8.9], [2, 'none']], ['sustained 0.9 kt gust +16', [0.9, 16.9], [2, 'none']]], [['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 3 kt gust +2', [3, 5], [1, 'none']], ['sustained 3 kt gust +8', [3, 11], [2, 'none']]], [['sustained 1 kt gust +8', [1, 9], [2, 'none']], ['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 10.7 kt gust +0', [10.7, 10.7], [4, 'none']]], [['sustained 6.6 kt gust +2', [6.6, 8.6], [3, 'none']], ['sustained 6.6 kt gust +16', [6.6, 22.6], [4, 'none']], ['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 10.7 kt gust +2', [10.7, 12.7], [4, 'none']], ['sustained 16 kt gust +0', [16, 16], [5, 'none']], ['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']]]]
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 | [1, 'none'] | [0, 'none'] | Failed |
| 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 | [2, 'none'] | [1, 'none'] | Failed |
| sustained 0.9 kt gust +2 | [3, 'none'] | [1, 'none'] | Failed |
| sustained 0.9 kt gust +8 | [3, 'none'] | [2, 'none'] | Failed |
| sustained 0.9 kt gust +16 | [3, 'none'] | [2, 'none'] | Failed |
SHA-256 / 5b78105a937902dd1d3c4c7c3cf3da1fd576cb5dae5c8fb338f0a1394a0b23ae
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 0.9 kt gust +8', [0.9, 8.9], [2, 'none']], ['sustained 0.9 kt gust +16', [0.9, 16.9], [2, 'none']]], [['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 3 kt gust +2', [3, 5], [1, 'none']], ['sustained 3 kt gust +8', [3, 11], [2, 'none']]], [['sustained 1 kt gust +8', [1, 9], [2, 'none']], ['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 10.7 kt gust +0', [10.7, 10.7], [4, 'none']]], [['sustained 6.6 kt gust +2', [6.6, 8.6], [3, 'none']], ['sustained 6.6 kt gust +16', [6.6, 22.6], [4, 'none']], ['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 10.7 kt gust +2', [10.7, 12.7], [4, 'none']], ['sustained 16 kt gust +0', [16, 16], [5, 'none']], ['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']]]]
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 0.9 kt gust +8 | [2, 'none'] | [2, 'none'] | Passed |
| sustained 0.9 kt gust +16 | [2, 'none'] | [2, 'none'] | Passed |
SHA-256 / 134871623daa495d5b52823255902991cfe40a030e8849ec24313ff7160e8345
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.764143+00:00.
Case digest / a907ce9382454ecce22e384e4bf4eb92e657c4b305cf94ae72d17e24702dd016