FAILURE MAP
← Case archive

FA-71386 / Seismic magnitude estimation / Open access

Network magnitude from station magnitudes: tenth rounding · case 01

An exact x.x5 median such as 3.25 is published as 3.2.

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

ROOT CAUSE

Python round() applies banker rounding to the half tenth.

VERIFIED REPAIR

Round half up with floor((m + 5)/10).

Unsuccessful approach: Truncation drops every fractional tenth and fails for negative magnitudes.

Case contract

Station magnitudes are integers in hundredths (None = no reading; 0 and negatives are valid). Require min_stations readings; take the median (mean of the middle pair for even counts), keep stations within 50 hundredths of it inclusive, require min_stations again, take the median of the kept set and round half up to 0.1 magnitude. Return [magnitude, kept count] or None.

Why this case matters

The published event magnitude is a robust network average of station magnitudes.

1 / The failure

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(station_mags, min_stations):
    vals = sorted(m for m in station_mags if m is not None)
    if len(vals) < min_stations:
        return None
    def med(xs):
        n = len(xs)
        mid = n // 2
        return xs[mid] if n % 2 else (xs[mid - 1] + xs[mid]) / 2
    m0 = med(vals)
    kept = [v for v in vals if abs(v - m0) <= 50]
    if len(kept) < min_stations:
        return None
    m1 = med(kept)
    tenths = round(m1 / 10)
    return [tenths / 10, len(kept)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['event 0', [[274, 336, 318, 341, 328, None], 4], [3.3, 4]], ['event 1', [[381, 387, 376, 348, 397, 350, 333, 349, None], 3], [3.6, 8]], ['event 2', [[550, 532, 540, 504, 484, 528, 530, 570], 3], [5.3, 8]], ['event 3', [[-34, -47, 14, 21, -68], 3], [-0.4, 4]], ['event 4', [[101, 111, 104, 54, 138, None], 3], [1.0, 5]], ['event 5', [[168, 146, 176, 132, 162, 118, 174, 186], 4], [1.7, 8]], ['event 6', [[374, 385, 429, 376, 402, 398, 421, 340, None], 3], [4.0, 7]], ['event 7', [[469, 498, 493, 486, 467, 522], 4], [4.9, 6]]], [['event 5', [[168, 146, 176, 132, 162, 118, 174, 186], 4], [1.7, 8]], ['event 7', [[469, 498, 493, 486, 467, 522], 4], [4.9, 6]], ['event 8', [[152, 146, 148, 165, 191], 3], [1.5, 5]], ['event 9', [[-26, -9, -1, 4, 0, 64, None], 4], [0.0, 5]], ['event 10', [[498, 507, 494, 464], 3], [5.0, 4]], ['event 11', [[189, 235, 240, 199, 215], 4], [2.2, 5]], ['event 12', [[106, 62, 76, 99, 87, 82, 116, 114, 149], 4], [1.0, 9]], ['event 13', [[131, 138, 79, 101, 94, 83, 121, 119, 167], 4], [1.2, 9]]], [['event 5', [[168, 146, 176, 132, 162, 118, 174, 186], 4], [1.7, 8]], ['event 14', [[125, 133, 111, 191, 191, 148, 241], 3], [1.4, 6]], ['event 15', [[381, 371, 330, 376, 403, 335, 339, 348, None], 3], [3.6, 8]], ['event 16', [[-23, -62, -35, -143], 4], None], ['event 17', [[432, 385, 408, 473], 4], None], ['event 18', [[9, 11, -21, 37, -4, 11], 3], [0.1, 6]], ['event 19', [[160, 170, 168, 12, None], 3], [1.7, 3]], ['event 23', [[103, 63, 30, 93], 4], [0.8, 4]]], [['event 5', [[168, 146, 176, 132, 162, 118, 174, 186], 4], [1.7, 8]], ['event 21', [[208, 239, 263, 248, 235, 223], 3], [2.4, 6]], ['event 22', [[347, 369, 375, 379, None], 3], [3.7, 4]], ['event 23', [[103, 63, 30, 93], 4], [0.8, 4]], ['event 24', [[142, 93, 150, 136, 125, 91, 82, 115, None], 4], [1.2, 8]], ['event 25', [[545, 488, 482, 494, 603, None], 4], None], ['event 26', [[55, 21, 75, 67, 25, 28], 3], [0.4, 6]], ['even station count', [[310, 320, 330, 350], 3], [3.3, 4]]], [['event 5', [[168, 146, 176, 132, 162, 118, 174, 186], 4], [1.7, 8]], ['event 7', [[469, 498, 493, 486, 467, 522], 4], [4.9, 6]], ['event 28', [[483, 485, 434, 486, 504, 514, None], 3], [4.9, 5]], ['event 29', [[462, 439, 425, 447, 432, 317], 3], [4.4, 5]], ['event 30', [[34, 54, -15, -27], 4], [0.1, 4]], ['event 31', [[269, 318, 251, 298, 329], 3], [3.0, 5]], ['event 32', [[231, 238, 217, 239, 214, 186, 164, 154, None], 3], [2.2, 6]], ['event 33', [[523, 505, 529, 488, 514], 3], [5.1, 5]]]]
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
event 0[3.3, 4][3.3, 4]Passed
event 1[3.6, 8][3.6, 8]Passed
event 2[5.3, 8][5.3, 8]Passed
event 3[-0.4, 4][-0.4, 4]Passed
event 4[1.0, 5][1.0, 5]Passed
event 5[1.6, 8][1.7, 8]Failed
event 6[4.0, 7][4.0, 7]Passed
event 7[4.9, 6][4.9, 6]Passed

SHA-256 / d3a537e019f6671e38c44047b183c85109bbafe2cc3b7b87657c1a53b744ea8a

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(station_mags, min_stations):
    vals = sorted(m for m in station_mags if m is not None)
    if len(vals) < min_stations:
        return None
    def med(xs):
        n = len(xs)
        mid = n // 2
        return xs[mid] if n % 2 else (xs[mid - 1] + xs[mid]) / 2
    m0 = med(vals)
    kept = [v for v in vals if abs(v - m0) <= 50]
    if len(kept) < min_stations:
        return None
    m1 = med(kept)
    tenths = int(m1 / 10)
    return [tenths / 10, len(kept)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['event 0', [[274, 336, 318, 341, 328, None], 4], [3.3, 4]], ['event 1', [[381, 387, 376, 348, 397, 350, 333, 349, None], 3], [3.6, 8]], ['event 2', [[550, 532, 540, 504, 484, 528, 530, 570], 3], [5.3, 8]], ['event 3', [[-34, -47, 14, 21, -68], 3], [-0.4, 4]], ['event 4', [[101, 111, 104, 54, 138, None], 3], [1.0, 5]], ['event 5', [[168, 146, 176, 132, 162, 118, 174, 186], 4], [1.7, 8]], ['event 6', [[374, 385, 429, 376, 402, 398, 421, 340, None], 3], [4.0, 7]], ['event 7', [[469, 498, 493, 486, 467, 522], 4], [4.9, 6]]], [['event 5', [[168, 146, 176, 132, 162, 118, 174, 186], 4], [1.7, 8]], ['event 7', [[469, 498, 493, 486, 467, 522], 4], [4.9, 6]], ['event 8', [[152, 146, 148, 165, 191], 3], [1.5, 5]], ['event 9', [[-26, -9, -1, 4, 0, 64, None], 4], [0.0, 5]], ['event 10', [[498, 507, 494, 464], 3], [5.0, 4]], ['event 11', [[189, 235, 240, 199, 215], 4], [2.2, 5]], ['event 12', [[106, 62, 76, 99, 87, 82, 116, 114, 149], 4], [1.0, 9]], ['event 13', [[131, 138, 79, 101, 94, 83, 121, 119, 167], 4], [1.2, 9]]], [['event 5', [[168, 146, 176, 132, 162, 118, 174, 186], 4], [1.7, 8]], ['event 14', [[125, 133, 111, 191, 191, 148, 241], 3], [1.4, 6]], ['event 15', [[381, 371, 330, 376, 403, 335, 339, 348, None], 3], [3.6, 8]], ['event 16', [[-23, -62, -35, -143], 4], None], ['event 17', [[432, 385, 408, 473], 4], None], ['event 18', [[9, 11, -21, 37, -4, 11], 3], [0.1, 6]], ['event 19', [[160, 170, 168, 12, None], 3], [1.7, 3]], ['event 23', [[103, 63, 30, 93], 4], [0.8, 4]]], [['event 5', [[168, 146, 176, 132, 162, 118, 174, 186], 4], [1.7, 8]], ['event 21', [[208, 239, 263, 248, 235, 223], 3], [2.4, 6]], ['event 22', [[347, 369, 375, 379, None], 3], [3.7, 4]], ['event 23', [[103, 63, 30, 93], 4], [0.8, 4]], ['event 24', [[142, 93, 150, 136, 125, 91, 82, 115, None], 4], [1.2, 8]], ['event 25', [[545, 488, 482, 494, 603, None], 4], None], ['event 26', [[55, 21, 75, 67, 25, 28], 3], [0.4, 6]], ['even station count', [[310, 320, 330, 350], 3], [3.3, 4]]], [['event 5', [[168, 146, 176, 132, 162, 118, 174, 186], 4], [1.7, 8]], ['event 7', [[469, 498, 493, 486, 467, 522], 4], [4.9, 6]], ['event 28', [[483, 485, 434, 486, 504, 514, None], 3], [4.9, 5]], ['event 29', [[462, 439, 425, 447, 432, 317], 3], [4.4, 5]], ['event 30', [[34, 54, -15, -27], 4], [0.1, 4]], ['event 31', [[269, 318, 251, 298, 329], 3], [3.0, 5]], ['event 32', [[231, 238, 217, 239, 214, 186, 164, 154, None], 3], [2.2, 6]], ['event 33', [[523, 505, 529, 488, 514], 3], [5.1, 5]]]]
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
event 0[3.3, 4][3.3, 4]Passed
event 1[3.6, 8][3.6, 8]Passed
event 2[5.3, 8][5.3, 8]Passed
event 3[-0.4, 4][-0.4, 4]Passed
event 4[1.0, 5][1.0, 5]Passed
event 5[1.6, 8][1.7, 8]Failed
event 6[3.9, 7][4.0, 7]Failed
event 7[4.8, 6][4.9, 6]Failed

SHA-256 / 418ebb650e3377601c12443190a7ea1cc7a53a8f738f2af1898652025668bfe4

3 / The verified repair

Exit 0
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(station_mags, min_stations):
    vals = sorted(m for m in station_mags if m is not None)
    if len(vals) < min_stations:
        return None
    def med(xs):
        n = len(xs)
        mid = n // 2
        return xs[mid] if n % 2 else (xs[mid - 1] + xs[mid]) / 2
    m0 = med(vals)
    kept = [v for v in vals if abs(v - m0) <= 50]
    if len(kept) < min_stations:
        return None
    m1 = med(kept)
    tenths = int(math.floor((m1 + 5) / 10))
    return [tenths / 10, len(kept)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['event 0', [[274, 336, 318, 341, 328, None], 4], [3.3, 4]], ['event 1', [[381, 387, 376, 348, 397, 350, 333, 349, None], 3], [3.6, 8]], ['event 2', [[550, 532, 540, 504, 484, 528, 530, 570], 3], [5.3, 8]], ['event 3', [[-34, -47, 14, 21, -68], 3], [-0.4, 4]], ['event 4', [[101, 111, 104, 54, 138, None], 3], [1.0, 5]], ['event 5', [[168, 146, 176, 132, 162, 118, 174, 186], 4], [1.7, 8]], ['event 6', [[374, 385, 429, 376, 402, 398, 421, 340, None], 3], [4.0, 7]], ['event 7', [[469, 498, 493, 486, 467, 522], 4], [4.9, 6]]], [['event 5', [[168, 146, 176, 132, 162, 118, 174, 186], 4], [1.7, 8]], ['event 7', [[469, 498, 493, 486, 467, 522], 4], [4.9, 6]], ['event 8', [[152, 146, 148, 165, 191], 3], [1.5, 5]], ['event 9', [[-26, -9, -1, 4, 0, 64, None], 4], [0.0, 5]], ['event 10', [[498, 507, 494, 464], 3], [5.0, 4]], ['event 11', [[189, 235, 240, 199, 215], 4], [2.2, 5]], ['event 12', [[106, 62, 76, 99, 87, 82, 116, 114, 149], 4], [1.0, 9]], ['event 13', [[131, 138, 79, 101, 94, 83, 121, 119, 167], 4], [1.2, 9]]], [['event 5', [[168, 146, 176, 132, 162, 118, 174, 186], 4], [1.7, 8]], ['event 14', [[125, 133, 111, 191, 191, 148, 241], 3], [1.4, 6]], ['event 15', [[381, 371, 330, 376, 403, 335, 339, 348, None], 3], [3.6, 8]], ['event 16', [[-23, -62, -35, -143], 4], None], ['event 17', [[432, 385, 408, 473], 4], None], ['event 18', [[9, 11, -21, 37, -4, 11], 3], [0.1, 6]], ['event 19', [[160, 170, 168, 12, None], 3], [1.7, 3]], ['event 23', [[103, 63, 30, 93], 4], [0.8, 4]]], [['event 5', [[168, 146, 176, 132, 162, 118, 174, 186], 4], [1.7, 8]], ['event 21', [[208, 239, 263, 248, 235, 223], 3], [2.4, 6]], ['event 22', [[347, 369, 375, 379, None], 3], [3.7, 4]], ['event 23', [[103, 63, 30, 93], 4], [0.8, 4]], ['event 24', [[142, 93, 150, 136, 125, 91, 82, 115, None], 4], [1.2, 8]], ['event 25', [[545, 488, 482, 494, 603, None], 4], None], ['event 26', [[55, 21, 75, 67, 25, 28], 3], [0.4, 6]], ['even station count', [[310, 320, 330, 350], 3], [3.3, 4]]], [['event 5', [[168, 146, 176, 132, 162, 118, 174, 186], 4], [1.7, 8]], ['event 7', [[469, 498, 493, 486, 467, 522], 4], [4.9, 6]], ['event 28', [[483, 485, 434, 486, 504, 514, None], 3], [4.9, 5]], ['event 29', [[462, 439, 425, 447, 432, 317], 3], [4.4, 5]], ['event 30', [[34, 54, -15, -27], 4], [0.1, 4]], ['event 31', [[269, 318, 251, 298, 329], 3], [3.0, 5]], ['event 32', [[231, 238, 217, 239, 214, 186, 164, 154, None], 3], [2.2, 6]], ['event 33', [[523, 505, 529, 488, 514], 3], [5.1, 5]]]]
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
event 0[3.3, 4][3.3, 4]Passed
event 1[3.6, 8][3.6, 8]Passed
event 2[5.3, 8][5.3, 8]Passed
event 3[-0.4, 4][-0.4, 4]Passed
event 4[1.0, 5][1.0, 5]Passed
event 5[1.7, 8][1.7, 8]Passed
event 6[4.0, 7][4.0, 7]Passed
event 7[4.9, 6][4.9, 6]Passed

SHA-256 / bf42f5cbeeb9610a038d80afb436424b20420a5efd5922bb4a7e3e6d17a81865

Verification & scope

Stipulated deterministic teaching model of a seismological magnitude procedure; constants and tables are fixed by the contract and no claim of agency or 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:29.171778+00:00.

Case digest / 5194df310477316c983f70534eb03cb0714fc9fa70f26265f49bdb6a59d6a548