FA-71366 / Seismic magnitude estimation / Open access
Network magnitude from station magnitudes: even-count median · case 01
With an even number of stations the magnitude is biased upward.
ROOT CAUSE
The median takes the upper middle value for even counts.
THE FAILURE
The median takes the upper middle value for even counts.
Unsuccessful approach: Taking the lower middle value biases the other way.
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]
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 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 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 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 22', [[347, 369, 375, 379, None], 3], [3.7, 4]], ['even station count', [[310, 320, 330, 350], 3], [3.3, 4]]], [['event 10', [[498, 507, 494, 464], 3], [5.0, 4]], ['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]], ['event 27', [[26, 11, -14, -12, -3, 32, -4, 100, None], 4], [0.0, 7]]], [['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]], ['even station count', [[310, 320, 330, 350], 3], [3.3, 4]], ['zero magnitude station', [[0, 10, -10, 5], 3], [0.0, 4]]]]
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 |
|---|---|---|---|
| event 0 | [3.4, 4] | [3.3, 4] | Failed |
| event 1 | [3.8, 8] | [3.6, 8] | Failed |
| event 2 | [5.3, 8] | [5.3, 8] | Passed |
| event 3 | [-0.3, 4] | [-0.4, 4] | Failed |
| 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 / b5303bbf38c54769a0657ca3485cfd1ef68fefc94574b82f8f9c60ed5406f79e
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 - 1] if n % 2 == 0 else xs[mid]
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 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 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 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 22', [[347, 369, 375, 379, None], 3], [3.7, 4]], ['even station count', [[310, 320, 330, 350], 3], [3.3, 4]]], [['event 10', [[498, 507, 494, 464], 3], [5.0, 4]], ['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]], ['event 27', [[26, 11, -14, -12, -3, 32, -4, 100, None], 4], [0.0, 7]]], [['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]], ['even station count', [[310, 320, 330, 350], 3], [3.3, 4]], ['zero magnitude station', [[0, 10, -10, 5], 3], [0.0, 4]]]]
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 |
|---|---|---|---|
| event 0 | [3.3, 4] | [3.3, 4] | Passed |
| event 1 | [3.5, 8] | [3.6, 8] | Failed |
| event 2 | [5.3, 8] | [5.3, 8] | Passed |
| event 3 | [-0.5, 4] | [-0.4, 4] | Failed |
| event 4 | [1.0, 5] | [1.0, 5] | Passed |
| event 5 | [1.6, 8] | [1.7, 8] | Failed |
| event 6 | [3.9, 8] | [4.0, 7] | Failed |
| event 7 | [4.9, 6] | [4.9, 6] | Passed |
SHA-256 / 7ab6d03ee0a3751f57bae3f054b916c36288559bcc411521934291759c772f95
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 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.052327+00:00.
Case digest / 63d424f2bd3d49169e3f4021bd3143ecdf9e5802c57ba75c9388fc6bc26ea7d0