FAILURE MAP
← Case archive

FA-71121 / Weather index computation / Open access

Beaufort force and marine warning: gust upgrade threshold · case 01

Ordinary gustiness upgrades nearly every warning.

Verified by executionVariant 1 · 8 checks per implementationDownload source bundle ↓JSON ↗

ROOT CAUSE

A gust only one force above the sustained wind triggers the upgrade.

THE FAILURE

A gust only one force above the sustained wind triggers the upgrade.

Unsuccessful approach: Requiring more than two forces misses the exact two-force case.

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 + 1 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 1 kt gust +8', [1, 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 10.7 kt gust +8', [10.7, 18.7], [4, 'none']], ['sustained 33.4 kt gust +16', [33.4, 49.4], [9, '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 48 kt gust +16', [48, 64], [11, 'storm']]], [['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 16 kt gust +16', [16, 32], [6, 'small craft']], ['sustained 40.4 kt gust +8', [40.4, 48.4], [9, '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 40.4 kt gust +16', [40.4, 56.4], [10, 'storm']], ['sustained 48 kt gust +8', [48, 56], [10, 'storm']]]]
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 fixtureActualExpectedOutcome
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[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 1 kt gust +8[2, 'none'][2, 'none']Passed

SHA-256 / 8587306bfd7ffda6a76e66be590ceeeef23c89b32390aed3b788497d88cbaed7

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 1 kt gust +8', [1, 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 10.7 kt gust +8', [10.7, 18.7], [4, 'none']], ['sustained 33.4 kt gust +16', [33.4, 49.4], [9, '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 48 kt gust +16', [48, 64], [11, 'storm']]], [['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 16 kt gust +16', [16, 32], [6, 'small craft']], ['sustained 40.4 kt gust +8', [40.4, 48.4], [9, '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 40.4 kt gust +16', [40.4, 56.4], [10, 'storm']], ['sustained 48 kt gust +8', [48, 56], [10, 'storm']]]]
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 fixtureActualExpectedOutcome
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[1, 'none'][2, 'none']Failed
sustained 1 kt gust +8[1, 'none'][2, 'none']Failed

SHA-256 / 504298182abd2062466f7636db68e2a8ebb1a892f446f20603bdc1aa01daf99f

HELD IN THE MEMBER ARCHIVE

The verified repair and its recorded checks are member-only.

This mechanism has 8 recorded checks per implementation. The open-access tier publishes the failure and the unsuccessful fix; the repaired source that passes every check, and the observations that prove it, are available to members.

Every case sharing this mechanism uses the same contract and the same repair, so this one record is held back for all of them.

Member access is invitation-based. Sign in with your invited account to inspect the repair.

Sign in to the archive ↗

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.852492+00:00.

Case digest / 941cad11629fbd1fdfcc7f1c3d1f303f5df579517a057b69a4140f1bc4a27074