FAILURE MAP
← Case archive

FA-92221 / PLC ladder logic scan cycles / Open access

Analog scaling divides by the 0-20 mA span · case 01

Readings are compressed and full scale is never reached at 20 mA.

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

ROOT CAUSE

The divisor uses 31208 counts (0-20 mA) instead of the 24966-count 4-20 mA span.

VERIFIED REPAIR

Divide by the 4-20 mA span of 24966 counts.

Unsuccessful approach: Changing only the rounding term leaves the wrong divisor.

Case contract

Scale a 4-20 mA analog channel read once per scan. Raw counts are proportional to 0-20 mA with 20 mA = 31208 counts, so 4 mA = 6242. Below 3.6 mA (raw*200 < 36*31208) the channel reports open wire and above 20.5 mA (raw*40 > 41*31208) over-range; in both cases it returns the last good value (None before any). Otherwise the engineering value in tenths is eng_lo*10 + (raw-6242)*(eng_hi-eng_lo)*10 / 24966 rounded half up, clamped to the configured range (reverse-acting ranges allowed). Return [status, tenths] per scan.

Why this case matters

Ladder programs are executed as repeated scans; each defect here changes what a rung, timer, counter or data-table instruction reports on a particular scan, which is how commissioning and field faults are actually observed.

1 / The failure

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(eng_lo, eng_hi, raws):
    last = None
    lo10, hi10 = sorted([eng_lo * 10, eng_hi * 10])
    out = []
    for raw in raws:
        if raw * 200 < 36 * 31208:
            out.append(['open', last])
            continue
        if raw * 40 > 41 * 31208:
            out.append(['over', last])
            continue
        num = (raw - 6242) * (eng_hi - eng_lo) * 10
        tenths = eng_lo * 10 + (2 * num + 31208) // (2 * 31208)
        tenths = max(min(tenths, hi10), lo10)
        last = tenths
        out.append(['ok', tenths])
    return out
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: span points and faults',
   [0, 100, [6242, 31208, 18725, 5000, 32000, 5800]],
   [['ok', 0], ['ok', 1000], ['ok', 500], ['open', 500], ['over', 500], ['ok', 0]]],
  ['regression: reverse acting valve',
   [100, 0, [6242, 12483, 31208, 31600, 5700]],
   [['ok', 1000], ['ok', 750], ['ok', 0], ['ok', 0], ['ok', 1000]]],
  ['regression: under-range tolerance',
   [-40, 120, [5700, 6100, 6242, 6243, 31900]],
   [['ok', -400], ['ok', -400], ['ok', -400], ['ok', -400], ['ok', 1200]]],
  ['regression: rounding half',
   [0, 1, [7490, 7491, 18725, 3000, 31950]],
   [['ok', 0], ['ok', 1], ['ok', 5], ['open', 5], ['ok', 10]]],
  ['regression: scenario 1',
   [100, 0, [31500, 8288, 22627, 22870, 5618, 13683, 31500, 18942]],
   [['ok', 0], ['ok', 918], ['ok', 344], ['ok', 334], ['ok', 1000], ['ok', 702], ['ok', 0], ['ok', 491]]],
  ['regression: scenario 2',
   [0, 250, [5617, 10253, 31208, 20785, 15236, 31208]],
   [['open', None], ['ok', 402], ['ok', 2500], ['ok', 1456], ['ok', 901], ['ok', 2500]]],
  ['regression: scenario 3',
   [-40, 120, [32767, 6000, 6242, 24089, 32767, 26567, 26190, 22883]],
   [['over', None],
    ['ok', -400],
    ['ok', -400],
    ['ok', 744],
    ['over', 744],
    ['ok', 903],
    ['ok', 878],
    ['ok', 666]]],
  ['regression: scenario 4',
   [-40, 120, [29499, 18725, 0, 11927, 7165, 31988, 5618, 28746, 31807]],
   [['ok', 1090],
    ['ok', 400],
    ['open', 400],
    ['ok', -36],
    ['ok', -341],
    ['ok', 1200],
    ['ok', -400],
    ['ok', 1042],
    ['ok', 1200]]]],
 [['regression: reverse acting valve',
   [100, 0, [6242, 12483, 31208, 31600, 5700]],
   [['ok', 1000], ['ok', 750], ['ok', 0], ['ok', 0], ['ok', 1000]]],
  ['regression: under-range tolerance',
   [-40, 120, [5700, 6100, 6242, 6243, 31900]],
   [['ok', -400], ['ok', -400], ['ok', -400], ['ok', -400], ['ok', 1200]]],
  ['regression: span points and faults',
   [0, 100, [6242, 31208, 18725, 5000, 32000, 5800]],
   [['ok', 0], ['ok', 1000], ['ok', 500], ['open', 500], ['over', 500], ['ok', 0]]],
  ['regression: rounding half',
   [0, 1, [7490, 7491, 18725, 3000, 31950]],
   [['ok', 0], ['ok', 1], ['ok', 5], ['open', 5], ['ok', 10]]],
  ['regression: scenario 1',
   [0, 100, [31500, 12465, 31988, 14808, 13503, 27085, 21673, 31500]],
   [['ok', 1000],
    ['ok', 249],
    ['ok', 1000],
    ['ok', 343],
    ['ok', 291],
    ['ok', 835],
    ['ok', 618],
    ['ok', 1000]]],
  ['regression: scenario 2',
   [4, 20, [10220, 9382, 7184, 5618, 28096, 3000, 31208, 6242, 5617, 6452]],
   [['ok', 65],
    ['ok', 60],
    ['ok', 46],
    ['ok', 40],
    ['ok', 180],
    ['open', 180],
    ['ok', 200],
    ['ok', 40],
    ['open', 40],
    ['ok', 41]]],
  ['regression: scenario 3',
   [100, 0, [6000, 25570, 31208, 14262, 10000]],
   [['ok', 1000], ['ok', 226], ['ok', 0], ['ok', 679], ['ok', 849]]],
  ['regression: scenario 4',
   [0, 1000, [25458, 32767, 3000, 26377, 18190, 14636, 32767, 10000]],
   [['ok', 7697],
    ['over', 7697],
    ['open', 7697],
    ['ok', 8065],
    ['ok', 4786],
    ['ok', 3362],
    ['over', 3362],
    ['ok', 1505]]]],
 [['regression: under-range tolerance',
   [-40, 120, [5700, 6100, 6242, 6243, 31900]],
   [['ok', -400], ['ok', -400], ['ok', -400], ['ok', -400], ['ok', 1200]]],
  ['regression: rounding half',
   [0, 1, [7490, 7491, 18725, 3000, 31950]],
   [['ok', 0], ['ok', 1], ['ok', 5], ['open', 5], ['ok', 10]]],
  ['regression: span points and faults',
   [0, 100, [6242, 31208, 18725, 5000, 32000, 5800]],
   [['ok', 0], ['ok', 1000], ['ok', 500], ['open', 500], ['over', 500], ['ok', 0]]],
  ['regression: reverse acting valve',
   [100, 0, [6242, 12483, 31208, 31600, 5700]],
   [['ok', 1000], ['ok', 750], ['ok', 0], ['ok', 0], ['ok', 1000]]],
  ['regression: scenario 1',
   [-40, 120, [3000, 16471, 17868, 31500, 31989, 18725, 31989, 10136]],
   [['open', None],
    ['ok', 256],
    ['ok', 345],
    ['ok', 1200],
    ['over', 1200],
    ['ok', 400],
    ['over', 400],
    ['ok', -150]]],
  ['regression: scenario 2',
   [4, 20, [27643, 11003, 3000, 32767, 3000]],
   [['ok', 177], ['ok', 71], ['open', 71], ['over', 71], ['open', 71]]],
  ['regression: scenario 3',
   [0, 100, [31989, 22472, 5618, 23809, 20211, 3000, 6242, 12935, 28379, 31989]],
   [['over', None],
    ['ok', 650],
    ['ok', 0],
    ['ok', 704],
    ['ok', 560],
    ['open', 560],
    ['ok', 0],
    ['ok', 268],
    ['ok', 887],
    ['over', 887]]],
  ['regression: scenario 4',
   [4, 20, [11825, 32767, 10576, 27128, 10066, 5618, 12778, 6000, 10000]],
   [['ok', 76],
    ['over', 76],
    ['ok', 68],
    ['ok', 174],
    ['ok', 65],
    ['ok', 40],
    ['ok', 82],
    ['ok', 40],
    ['ok', 64]]]],
 [['regression: rounding half',
   [0, 1, [7490, 7491, 18725, 3000, 31950]],
   [['ok', 0], ['ok', 1], ['ok', 5], ['open', 5], ['ok', 10]]],
  ['regression: scenario 1',
   [0, 250, [3000, 5618, 31500, 27302, 12019]],
   [['open', None], ['ok', 0], ['ok', 2500], ['ok', 2109], ['ok', 578]]],
  ['regression: span points and faults',
   [0, 100, [6242, 31208, 18725, 5000, 32000, 5800]],
   [['ok', 0], ['ok', 1000], ['ok', 500], ['open', 500], ['over', 500], ['ok', 0]]],
  ['regression: reverse acting valve',
   [100, 0, [6242, 12483, 31208, 31600, 5700]],
   [['ok', 1000], ['ok', 750], ['ok', 0], ['ok', 0], ['ok', 1000]]],
  ['regression: under-range tolerance',
   [-40, 120, [5700, 6100, 6242, 6243, 31900]],
   [['ok', -400], ['ok', -400], ['ok', -400], ['ok', -400], ['ok', 1200]]],
  ['regression: scenario 2',
   [0, 250, [30430, 16995, 0, 10000, 6000, 10979]],
   [['ok', 2422], ['ok', 1077], ['open', 1077], ['ok', 376], ['ok', 0], ['ok', 474]]],
  ['regression: scenario 3',
   [0, 250, [31456, 13455, 24788, 8289, 14213, 18164, 10000, 0, 20991]],
   [['ok', 2500],
    ['ok', 722],
    ['ok', 1857],
    ['ok', 205],
    ['ok', 798],
    ['ok', 1194],
    ['ok', 376],
    ['open', 376],
    ['ok', 1477]]],
  ['regression: scenario 4',
   [0, 100, [3000, 5633, 7891, 18725, 19352, 30730, 10000, 31208, 25959]],
   [['open', None],
    ['ok', 0],
    ['ok', 66],
    ['ok', 500],
    ['ok', 525],
    ['ok', 981],
    ['ok', 151],
    ['ok', 1000],
    ['ok', 790]]]],
 [['regression: scenario 1',
   [0, 250, [15441, 21456, 3000, 0, 6000, 5618]],
   [['ok', 921], ['ok', 1523], ['open', 1523], ['open', 1523], ['ok', 0], ['ok', 0]]],
  ['regression: scenario 2',
   [0, 100, [31078, 6242, 18725, 18727, 19134, 13243, 18725, 21879, 0, 19240]],
   [['ok', 995],
    ['ok', 0],
    ['ok', 500],
    ['ok', 500],
    ['ok', 516],
    ['ok', 280],
    ['ok', 500],
    ['ok', 626],
    ['open', 626],
    ['ok', 521]]],
  ['regression: span points and faults',
   [0, 100, [6242, 31208, 18725, 5000, 32000, 5800]],
   [['ok', 0], ['ok', 1000], ['ok', 500], ['open', 500], ['over', 500], ['ok', 0]]],
  ['regression: reverse acting valve',
   [100, 0, [6242, 12483, 31208, 31600, 5700]],
   [['ok', 1000], ['ok', 750], ['ok', 0], ['ok', 0], ['ok', 1000]]],
  ['regression: under-range tolerance',
   [-40, 120, [5700, 6100, 6242, 6243, 31900]],
   [['ok', -400], ['ok', -400], ['ok', -400], ['ok', -400], ['ok', 1200]]],
  ['regression: rounding half',
   [0, 1, [7490, 7491, 18725, 3000, 31950]],
   [['ok', 0], ['ok', 1], ['ok', 5], ['open', 5], ['ok', 10]]],
  ['regression: scenario 3',
   [4, 20, [6813, 26535, 0, 32767, 7893]],
   [['ok', 44], ['ok', 170], ['open', 170], ['over', 170], ['ok', 51]]],
  ['regression: scenario 4',
   [0, 250, [27893, 24340, 19137, 9936, 26279, 31500, 28004, 10000]],
   [['ok', 2168],
    ['ok', 1812],
    ['ok', 1291],
    ['ok', 370],
    ['ok', 2006],
    ['ok', 2500],
    ['ok', 2179],
    ['ok', 376]]]]]
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
regression: span points and faults[['ok', 0], ['ok', 800], ['ok', 400], ['open', 400], ['over', 400], ['ok', 0]][['ok', 0], ['ok', 1000], ['ok', 500], ['open', 500], ['over', 500], ['ok', 0]]Failed
regression: reverse acting valve[['ok', 1000], ['ok', 800], ['ok', 200], ['ok', 187], ['ok', 1000]][['ok', 1000], ['ok', 750], ['ok', 0], ['ok', 0], ['ok', 1000]]Failed
regression: under-range tolerance[['ok', -400], ['ok', -400], ['ok', -400], ['ok', -400], ['ok', 915]][['ok', -400], ['ok', -400], ['ok', -400], ['ok', -400], ['ok', 1200]]Failed
regression: rounding half[['ok', 0], ['ok', 0], ['ok', 4], ['open', 4], ['ok', 8]][['ok', 0], ['ok', 1], ['ok', 5], ['open', 5], ['ok', 10]]Failed
regression: scenario 1[['ok', 191], ['ok', 934], ['ok', 475], ['ok', 467], ['ok', 1000], ['ok', 762], ['ok', 191], ['ok', 593]][['ok', 0], ['ok', 918], ['ok', 344], ['ok', 334], ['ok', 1000], ['ok', 702], ['ok', 0], ['ok', 491]]Failed
regression: scenario 2[['open', None], ['ok', 321], ['ok', 2000], ['ok', 1165], ['ok', 720], ['ok', 2000]][['open', None], ['ok', 402], ['ok', 2500], ['ok', 1456], ['ok', 901], ['ok', 2500]]Failed
regression: scenario 3[['over', None], ['ok', -400], ['ok', -400], ['ok', 515], ['over', 515], ['ok', 642], ['ok', 623], ['ok', 453]][['over', None], ['ok', -400], ['ok', -400], ['ok', 744], ['over', 744], ['ok', 903], ['ok', 878], ['ok', 666]]Failed
regression: scenario 4[['ok', 792], ['ok', 240], ['open', 240], ['ok', -109], ['ok', -353], ['ok', 920], ['ok', -400], ['ok', 754], ['ok', 911]][['ok', 1090], ['ok', 400], ['open', 400], ['ok', -36], ['ok', -341], ['ok', 1200], ['ok', -400], ['ok', 1042], ['ok', 1200]]Failed

SHA-256 / 41b51ca302941b8e43508e2549e6e447ea5ffa274133cc3d866a73fd375ffd90

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(eng_lo, eng_hi, raws):
    last = None
    lo10, hi10 = sorted([eng_lo * 10, eng_hi * 10])
    out = []
    for raw in raws:
        if raw * 200 < 36 * 31208:
            out.append(['open', last])
            continue
        if raw * 40 > 41 * 31208:
            out.append(['over', last])
            continue
        num = (raw - 6242) * (eng_hi - eng_lo) * 10
        tenths = eng_lo * 10 + (2 * num + 24966) // (2 * 31208)
        tenths = max(min(tenths, hi10), lo10)
        last = tenths
        out.append(['ok', tenths])
    return out
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: span points and faults',
   [0, 100, [6242, 31208, 18725, 5000, 32000, 5800]],
   [['ok', 0], ['ok', 1000], ['ok', 500], ['open', 500], ['over', 500], ['ok', 0]]],
  ['regression: reverse acting valve',
   [100, 0, [6242, 12483, 31208, 31600, 5700]],
   [['ok', 1000], ['ok', 750], ['ok', 0], ['ok', 0], ['ok', 1000]]],
  ['regression: under-range tolerance',
   [-40, 120, [5700, 6100, 6242, 6243, 31900]],
   [['ok', -400], ['ok', -400], ['ok', -400], ['ok', -400], ['ok', 1200]]],
  ['regression: rounding half',
   [0, 1, [7490, 7491, 18725, 3000, 31950]],
   [['ok', 0], ['ok', 1], ['ok', 5], ['open', 5], ['ok', 10]]],
  ['regression: scenario 1',
   [100, 0, [31500, 8288, 22627, 22870, 5618, 13683, 31500, 18942]],
   [['ok', 0], ['ok', 918], ['ok', 344], ['ok', 334], ['ok', 1000], ['ok', 702], ['ok', 0], ['ok', 491]]],
  ['regression: scenario 2',
   [0, 250, [5617, 10253, 31208, 20785, 15236, 31208]],
   [['open', None], ['ok', 402], ['ok', 2500], ['ok', 1456], ['ok', 901], ['ok', 2500]]],
  ['regression: scenario 3',
   [-40, 120, [32767, 6000, 6242, 24089, 32767, 26567, 26190, 22883]],
   [['over', None],
    ['ok', -400],
    ['ok', -400],
    ['ok', 744],
    ['over', 744],
    ['ok', 903],
    ['ok', 878],
    ['ok', 666]]],
  ['regression: scenario 4',
   [-40, 120, [29499, 18725, 0, 11927, 7165, 31988, 5618, 28746, 31807]],
   [['ok', 1090],
    ['ok', 400],
    ['open', 400],
    ['ok', -36],
    ['ok', -341],
    ['ok', 1200],
    ['ok', -400],
    ['ok', 1042],
    ['ok', 1200]]]],
 [['regression: reverse acting valve',
   [100, 0, [6242, 12483, 31208, 31600, 5700]],
   [['ok', 1000], ['ok', 750], ['ok', 0], ['ok', 0], ['ok', 1000]]],
  ['regression: under-range tolerance',
   [-40, 120, [5700, 6100, 6242, 6243, 31900]],
   [['ok', -400], ['ok', -400], ['ok', -400], ['ok', -400], ['ok', 1200]]],
  ['regression: span points and faults',
   [0, 100, [6242, 31208, 18725, 5000, 32000, 5800]],
   [['ok', 0], ['ok', 1000], ['ok', 500], ['open', 500], ['over', 500], ['ok', 0]]],
  ['regression: rounding half',
   [0, 1, [7490, 7491, 18725, 3000, 31950]],
   [['ok', 0], ['ok', 1], ['ok', 5], ['open', 5], ['ok', 10]]],
  ['regression: scenario 1',
   [0, 100, [31500, 12465, 31988, 14808, 13503, 27085, 21673, 31500]],
   [['ok', 1000],
    ['ok', 249],
    ['ok', 1000],
    ['ok', 343],
    ['ok', 291],
    ['ok', 835],
    ['ok', 618],
    ['ok', 1000]]],
  ['regression: scenario 2',
   [4, 20, [10220, 9382, 7184, 5618, 28096, 3000, 31208, 6242, 5617, 6452]],
   [['ok', 65],
    ['ok', 60],
    ['ok', 46],
    ['ok', 40],
    ['ok', 180],
    ['open', 180],
    ['ok', 200],
    ['ok', 40],
    ['open', 40],
    ['ok', 41]]],
  ['regression: scenario 3',
   [100, 0, [6000, 25570, 31208, 14262, 10000]],
   [['ok', 1000], ['ok', 226], ['ok', 0], ['ok', 679], ['ok', 849]]],
  ['regression: scenario 4',
   [0, 1000, [25458, 32767, 3000, 26377, 18190, 14636, 32767, 10000]],
   [['ok', 7697],
    ['over', 7697],
    ['open', 7697],
    ['ok', 8065],
    ['ok', 4786],
    ['ok', 3362],
    ['over', 3362],
    ['ok', 1505]]]],
 [['regression: under-range tolerance',
   [-40, 120, [5700, 6100, 6242, 6243, 31900]],
   [['ok', -400], ['ok', -400], ['ok', -400], ['ok', -400], ['ok', 1200]]],
  ['regression: rounding half',
   [0, 1, [7490, 7491, 18725, 3000, 31950]],
   [['ok', 0], ['ok', 1], ['ok', 5], ['open', 5], ['ok', 10]]],
  ['regression: span points and faults',
   [0, 100, [6242, 31208, 18725, 5000, 32000, 5800]],
   [['ok', 0], ['ok', 1000], ['ok', 500], ['open', 500], ['over', 500], ['ok', 0]]],
  ['regression: reverse acting valve',
   [100, 0, [6242, 12483, 31208, 31600, 5700]],
   [['ok', 1000], ['ok', 750], ['ok', 0], ['ok', 0], ['ok', 1000]]],
  ['regression: scenario 1',
   [-40, 120, [3000, 16471, 17868, 31500, 31989, 18725, 31989, 10136]],
   [['open', None],
    ['ok', 256],
    ['ok', 345],
    ['ok', 1200],
    ['over', 1200],
    ['ok', 400],
    ['over', 400],
    ['ok', -150]]],
  ['regression: scenario 2',
   [4, 20, [27643, 11003, 3000, 32767, 3000]],
   [['ok', 177], ['ok', 71], ['open', 71], ['over', 71], ['open', 71]]],
  ['regression: scenario 3',
   [0, 100, [31989, 22472, 5618, 23809, 20211, 3000, 6242, 12935, 28379, 31989]],
   [['over', None],
    ['ok', 650],
    ['ok', 0],
    ['ok', 704],
    ['ok', 560],
    ['open', 560],
    ['ok', 0],
    ['ok', 268],
    ['ok', 887],
    ['over', 887]]],
  ['regression: scenario 4',
   [4, 20, [11825, 32767, 10576, 27128, 10066, 5618, 12778, 6000, 10000]],
   [['ok', 76],
    ['over', 76],
    ['ok', 68],
    ['ok', 174],
    ['ok', 65],
    ['ok', 40],
    ['ok', 82],
    ['ok', 40],
    ['ok', 64]]]],
 [['regression: rounding half',
   [0, 1, [7490, 7491, 18725, 3000, 31950]],
   [['ok', 0], ['ok', 1], ['ok', 5], ['open', 5], ['ok', 10]]],
  ['regression: scenario 1',
   [0, 250, [3000, 5618, 31500, 27302, 12019]],
   [['open', None], ['ok', 0], ['ok', 2500], ['ok', 2109], ['ok', 578]]],
  ['regression: span points and faults',
   [0, 100, [6242, 31208, 18725, 5000, 32000, 5800]],
   [['ok', 0], ['ok', 1000], ['ok', 500], ['open', 500], ['over', 500], ['ok', 0]]],
  ['regression: reverse acting valve',
   [100, 0, [6242, 12483, 31208, 31600, 5700]],
   [['ok', 1000], ['ok', 750], ['ok', 0], ['ok', 0], ['ok', 1000]]],
  ['regression: under-range tolerance',
   [-40, 120, [5700, 6100, 6242, 6243, 31900]],
   [['ok', -400], ['ok', -400], ['ok', -400], ['ok', -400], ['ok', 1200]]],
  ['regression: scenario 2',
   [0, 250, [30430, 16995, 0, 10000, 6000, 10979]],
   [['ok', 2422], ['ok', 1077], ['open', 1077], ['ok', 376], ['ok', 0], ['ok', 474]]],
  ['regression: scenario 3',
   [0, 250, [31456, 13455, 24788, 8289, 14213, 18164, 10000, 0, 20991]],
   [['ok', 2500],
    ['ok', 722],
    ['ok', 1857],
    ['ok', 205],
    ['ok', 798],
    ['ok', 1194],
    ['ok', 376],
    ['open', 376],
    ['ok', 1477]]],
  ['regression: scenario 4',
   [0, 100, [3000, 5633, 7891, 18725, 19352, 30730, 10000, 31208, 25959]],
   [['open', None],
    ['ok', 0],
    ['ok', 66],
    ['ok', 500],
    ['ok', 525],
    ['ok', 981],
    ['ok', 151],
    ['ok', 1000],
    ['ok', 790]]]],
 [['regression: scenario 1',
   [0, 250, [15441, 21456, 3000, 0, 6000, 5618]],
   [['ok', 921], ['ok', 1523], ['open', 1523], ['open', 1523], ['ok', 0], ['ok', 0]]],
  ['regression: scenario 2',
   [0, 100, [31078, 6242, 18725, 18727, 19134, 13243, 18725, 21879, 0, 19240]],
   [['ok', 995],
    ['ok', 0],
    ['ok', 500],
    ['ok', 500],
    ['ok', 516],
    ['ok', 280],
    ['ok', 500],
    ['ok', 626],
    ['open', 626],
    ['ok', 521]]],
  ['regression: span points and faults',
   [0, 100, [6242, 31208, 18725, 5000, 32000, 5800]],
   [['ok', 0], ['ok', 1000], ['ok', 500], ['open', 500], ['over', 500], ['ok', 0]]],
  ['regression: reverse acting valve',
   [100, 0, [6242, 12483, 31208, 31600, 5700]],
   [['ok', 1000], ['ok', 750], ['ok', 0], ['ok', 0], ['ok', 1000]]],
  ['regression: under-range tolerance',
   [-40, 120, [5700, 6100, 6242, 6243, 31900]],
   [['ok', -400], ['ok', -400], ['ok', -400], ['ok', -400], ['ok', 1200]]],
  ['regression: rounding half',
   [0, 1, [7490, 7491, 18725, 3000, 31950]],
   [['ok', 0], ['ok', 1], ['ok', 5], ['open', 5], ['ok', 10]]],
  ['regression: scenario 3',
   [4, 20, [6813, 26535, 0, 32767, 7893]],
   [['ok', 44], ['ok', 170], ['open', 170], ['over', 170], ['ok', 51]]],
  ['regression: scenario 4',
   [0, 250, [27893, 24340, 19137, 9936, 26279, 31500, 28004, 10000]],
   [['ok', 2168],
    ['ok', 1812],
    ['ok', 1291],
    ['ok', 370],
    ['ok', 2006],
    ['ok', 2500],
    ['ok', 2179],
    ['ok', 376]]]]]
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
regression: span points and faults[['ok', 0], ['ok', 800], ['ok', 400], ['open', 400], ['over', 400], ['ok', 0]][['ok', 0], ['ok', 1000], ['ok', 500], ['open', 500], ['over', 500], ['ok', 0]]Failed
regression: reverse acting valve[['ok', 1000], ['ok', 800], ['ok', 200], ['ok', 187], ['ok', 1000]][['ok', 1000], ['ok', 750], ['ok', 0], ['ok', 0], ['ok', 1000]]Failed
regression: under-range tolerance[['ok', -400], ['ok', -400], ['ok', -400], ['ok', -400], ['ok', 915]][['ok', -400], ['ok', -400], ['ok', -400], ['ok', -400], ['ok', 1200]]Failed
regression: rounding half[['ok', 0], ['ok', 0], ['ok', 4], ['open', 4], ['ok', 8]][['ok', 0], ['ok', 1], ['ok', 5], ['open', 5], ['ok', 10]]Failed
regression: scenario 1[['ok', 191], ['ok', 934], ['ok', 475], ['ok', 467], ['ok', 1000], ['ok', 761], ['ok', 191], ['ok', 593]][['ok', 0], ['ok', 918], ['ok', 344], ['ok', 334], ['ok', 1000], ['ok', 702], ['ok', 0], ['ok', 491]]Failed
regression: scenario 2[['open', None], ['ok', 321], ['ok', 2000], ['ok', 1165], ['ok', 720], ['ok', 2000]][['open', None], ['ok', 402], ['ok', 2500], ['ok', 1456], ['ok', 901], ['ok', 2500]]Failed
regression: scenario 3[['over', None], ['ok', -400], ['ok', -400], ['ok', 515], ['over', 515], ['ok', 642], ['ok', 623], ['ok', 453]][['over', None], ['ok', -400], ['ok', -400], ['ok', 744], ['over', 744], ['ok', 903], ['ok', 878], ['ok', 666]]Failed
regression: scenario 4[['ok', 792], ['ok', 240], ['open', 240], ['ok', -109], ['ok', -353], ['ok', 920], ['ok', -400], ['ok', 754], ['ok', 911]][['ok', 1090], ['ok', 400], ['open', 400], ['ok', -36], ['ok', -341], ['ok', 1200], ['ok', -400], ['ok', 1042], ['ok', 1200]]Failed

SHA-256 / d6bd42d8d56e280175cec526d4dccd3421e42d16bea59827f7744aa10cfcd3a5

3 / The verified repair

Exit 0
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(eng_lo, eng_hi, raws):
    last = None
    lo10, hi10 = sorted([eng_lo * 10, eng_hi * 10])
    out = []
    for raw in raws:
        if raw * 200 < 36 * 31208:
            out.append(['open', last])
            continue
        if raw * 40 > 41 * 31208:
            out.append(['over', last])
            continue
        num = (raw - 6242) * (eng_hi - eng_lo) * 10
        tenths = eng_lo * 10 + (2 * num + 24966) // (2 * 24966)
        tenths = max(min(tenths, hi10), lo10)
        last = tenths
        out.append(['ok', tenths])
    return out
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: span points and faults',
   [0, 100, [6242, 31208, 18725, 5000, 32000, 5800]],
   [['ok', 0], ['ok', 1000], ['ok', 500], ['open', 500], ['over', 500], ['ok', 0]]],
  ['regression: reverse acting valve',
   [100, 0, [6242, 12483, 31208, 31600, 5700]],
   [['ok', 1000], ['ok', 750], ['ok', 0], ['ok', 0], ['ok', 1000]]],
  ['regression: under-range tolerance',
   [-40, 120, [5700, 6100, 6242, 6243, 31900]],
   [['ok', -400], ['ok', -400], ['ok', -400], ['ok', -400], ['ok', 1200]]],
  ['regression: rounding half',
   [0, 1, [7490, 7491, 18725, 3000, 31950]],
   [['ok', 0], ['ok', 1], ['ok', 5], ['open', 5], ['ok', 10]]],
  ['regression: scenario 1',
   [100, 0, [31500, 8288, 22627, 22870, 5618, 13683, 31500, 18942]],
   [['ok', 0], ['ok', 918], ['ok', 344], ['ok', 334], ['ok', 1000], ['ok', 702], ['ok', 0], ['ok', 491]]],
  ['regression: scenario 2',
   [0, 250, [5617, 10253, 31208, 20785, 15236, 31208]],
   [['open', None], ['ok', 402], ['ok', 2500], ['ok', 1456], ['ok', 901], ['ok', 2500]]],
  ['regression: scenario 3',
   [-40, 120, [32767, 6000, 6242, 24089, 32767, 26567, 26190, 22883]],
   [['over', None],
    ['ok', -400],
    ['ok', -400],
    ['ok', 744],
    ['over', 744],
    ['ok', 903],
    ['ok', 878],
    ['ok', 666]]],
  ['regression: scenario 4',
   [-40, 120, [29499, 18725, 0, 11927, 7165, 31988, 5618, 28746, 31807]],
   [['ok', 1090],
    ['ok', 400],
    ['open', 400],
    ['ok', -36],
    ['ok', -341],
    ['ok', 1200],
    ['ok', -400],
    ['ok', 1042],
    ['ok', 1200]]]],
 [['regression: reverse acting valve',
   [100, 0, [6242, 12483, 31208, 31600, 5700]],
   [['ok', 1000], ['ok', 750], ['ok', 0], ['ok', 0], ['ok', 1000]]],
  ['regression: under-range tolerance',
   [-40, 120, [5700, 6100, 6242, 6243, 31900]],
   [['ok', -400], ['ok', -400], ['ok', -400], ['ok', -400], ['ok', 1200]]],
  ['regression: span points and faults',
   [0, 100, [6242, 31208, 18725, 5000, 32000, 5800]],
   [['ok', 0], ['ok', 1000], ['ok', 500], ['open', 500], ['over', 500], ['ok', 0]]],
  ['regression: rounding half',
   [0, 1, [7490, 7491, 18725, 3000, 31950]],
   [['ok', 0], ['ok', 1], ['ok', 5], ['open', 5], ['ok', 10]]],
  ['regression: scenario 1',
   [0, 100, [31500, 12465, 31988, 14808, 13503, 27085, 21673, 31500]],
   [['ok', 1000],
    ['ok', 249],
    ['ok', 1000],
    ['ok', 343],
    ['ok', 291],
    ['ok', 835],
    ['ok', 618],
    ['ok', 1000]]],
  ['regression: scenario 2',
   [4, 20, [10220, 9382, 7184, 5618, 28096, 3000, 31208, 6242, 5617, 6452]],
   [['ok', 65],
    ['ok', 60],
    ['ok', 46],
    ['ok', 40],
    ['ok', 180],
    ['open', 180],
    ['ok', 200],
    ['ok', 40],
    ['open', 40],
    ['ok', 41]]],
  ['regression: scenario 3',
   [100, 0, [6000, 25570, 31208, 14262, 10000]],
   [['ok', 1000], ['ok', 226], ['ok', 0], ['ok', 679], ['ok', 849]]],
  ['regression: scenario 4',
   [0, 1000, [25458, 32767, 3000, 26377, 18190, 14636, 32767, 10000]],
   [['ok', 7697],
    ['over', 7697],
    ['open', 7697],
    ['ok', 8065],
    ['ok', 4786],
    ['ok', 3362],
    ['over', 3362],
    ['ok', 1505]]]],
 [['regression: under-range tolerance',
   [-40, 120, [5700, 6100, 6242, 6243, 31900]],
   [['ok', -400], ['ok', -400], ['ok', -400], ['ok', -400], ['ok', 1200]]],
  ['regression: rounding half',
   [0, 1, [7490, 7491, 18725, 3000, 31950]],
   [['ok', 0], ['ok', 1], ['ok', 5], ['open', 5], ['ok', 10]]],
  ['regression: span points and faults',
   [0, 100, [6242, 31208, 18725, 5000, 32000, 5800]],
   [['ok', 0], ['ok', 1000], ['ok', 500], ['open', 500], ['over', 500], ['ok', 0]]],
  ['regression: reverse acting valve',
   [100, 0, [6242, 12483, 31208, 31600, 5700]],
   [['ok', 1000], ['ok', 750], ['ok', 0], ['ok', 0], ['ok', 1000]]],
  ['regression: scenario 1',
   [-40, 120, [3000, 16471, 17868, 31500, 31989, 18725, 31989, 10136]],
   [['open', None],
    ['ok', 256],
    ['ok', 345],
    ['ok', 1200],
    ['over', 1200],
    ['ok', 400],
    ['over', 400],
    ['ok', -150]]],
  ['regression: scenario 2',
   [4, 20, [27643, 11003, 3000, 32767, 3000]],
   [['ok', 177], ['ok', 71], ['open', 71], ['over', 71], ['open', 71]]],
  ['regression: scenario 3',
   [0, 100, [31989, 22472, 5618, 23809, 20211, 3000, 6242, 12935, 28379, 31989]],
   [['over', None],
    ['ok', 650],
    ['ok', 0],
    ['ok', 704],
    ['ok', 560],
    ['open', 560],
    ['ok', 0],
    ['ok', 268],
    ['ok', 887],
    ['over', 887]]],
  ['regression: scenario 4',
   [4, 20, [11825, 32767, 10576, 27128, 10066, 5618, 12778, 6000, 10000]],
   [['ok', 76],
    ['over', 76],
    ['ok', 68],
    ['ok', 174],
    ['ok', 65],
    ['ok', 40],
    ['ok', 82],
    ['ok', 40],
    ['ok', 64]]]],
 [['regression: rounding half',
   [0, 1, [7490, 7491, 18725, 3000, 31950]],
   [['ok', 0], ['ok', 1], ['ok', 5], ['open', 5], ['ok', 10]]],
  ['regression: scenario 1',
   [0, 250, [3000, 5618, 31500, 27302, 12019]],
   [['open', None], ['ok', 0], ['ok', 2500], ['ok', 2109], ['ok', 578]]],
  ['regression: span points and faults',
   [0, 100, [6242, 31208, 18725, 5000, 32000, 5800]],
   [['ok', 0], ['ok', 1000], ['ok', 500], ['open', 500], ['over', 500], ['ok', 0]]],
  ['regression: reverse acting valve',
   [100, 0, [6242, 12483, 31208, 31600, 5700]],
   [['ok', 1000], ['ok', 750], ['ok', 0], ['ok', 0], ['ok', 1000]]],
  ['regression: under-range tolerance',
   [-40, 120, [5700, 6100, 6242, 6243, 31900]],
   [['ok', -400], ['ok', -400], ['ok', -400], ['ok', -400], ['ok', 1200]]],
  ['regression: scenario 2',
   [0, 250, [30430, 16995, 0, 10000, 6000, 10979]],
   [['ok', 2422], ['ok', 1077], ['open', 1077], ['ok', 376], ['ok', 0], ['ok', 474]]],
  ['regression: scenario 3',
   [0, 250, [31456, 13455, 24788, 8289, 14213, 18164, 10000, 0, 20991]],
   [['ok', 2500],
    ['ok', 722],
    ['ok', 1857],
    ['ok', 205],
    ['ok', 798],
    ['ok', 1194],
    ['ok', 376],
    ['open', 376],
    ['ok', 1477]]],
  ['regression: scenario 4',
   [0, 100, [3000, 5633, 7891, 18725, 19352, 30730, 10000, 31208, 25959]],
   [['open', None],
    ['ok', 0],
    ['ok', 66],
    ['ok', 500],
    ['ok', 525],
    ['ok', 981],
    ['ok', 151],
    ['ok', 1000],
    ['ok', 790]]]],
 [['regression: scenario 1',
   [0, 250, [15441, 21456, 3000, 0, 6000, 5618]],
   [['ok', 921], ['ok', 1523], ['open', 1523], ['open', 1523], ['ok', 0], ['ok', 0]]],
  ['regression: scenario 2',
   [0, 100, [31078, 6242, 18725, 18727, 19134, 13243, 18725, 21879, 0, 19240]],
   [['ok', 995],
    ['ok', 0],
    ['ok', 500],
    ['ok', 500],
    ['ok', 516],
    ['ok', 280],
    ['ok', 500],
    ['ok', 626],
    ['open', 626],
    ['ok', 521]]],
  ['regression: span points and faults',
   [0, 100, [6242, 31208, 18725, 5000, 32000, 5800]],
   [['ok', 0], ['ok', 1000], ['ok', 500], ['open', 500], ['over', 500], ['ok', 0]]],
  ['regression: reverse acting valve',
   [100, 0, [6242, 12483, 31208, 31600, 5700]],
   [['ok', 1000], ['ok', 750], ['ok', 0], ['ok', 0], ['ok', 1000]]],
  ['regression: under-range tolerance',
   [-40, 120, [5700, 6100, 6242, 6243, 31900]],
   [['ok', -400], ['ok', -400], ['ok', -400], ['ok', -400], ['ok', 1200]]],
  ['regression: rounding half',
   [0, 1, [7490, 7491, 18725, 3000, 31950]],
   [['ok', 0], ['ok', 1], ['ok', 5], ['open', 5], ['ok', 10]]],
  ['regression: scenario 3',
   [4, 20, [6813, 26535, 0, 32767, 7893]],
   [['ok', 44], ['ok', 170], ['open', 170], ['over', 170], ['ok', 51]]],
  ['regression: scenario 4',
   [0, 250, [27893, 24340, 19137, 9936, 26279, 31500, 28004, 10000]],
   [['ok', 2168],
    ['ok', 1812],
    ['ok', 1291],
    ['ok', 370],
    ['ok', 2006],
    ['ok', 2500],
    ['ok', 2179],
    ['ok', 376]]]]]
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
regression: span points and faults[['ok', 0], ['ok', 1000], ['ok', 500], ['open', 500], ['over', 500], ['ok', 0]][['ok', 0], ['ok', 1000], ['ok', 500], ['open', 500], ['over', 500], ['ok', 0]]Passed
regression: reverse acting valve[['ok', 1000], ['ok', 750], ['ok', 0], ['ok', 0], ['ok', 1000]][['ok', 1000], ['ok', 750], ['ok', 0], ['ok', 0], ['ok', 1000]]Passed
regression: under-range tolerance[['ok', -400], ['ok', -400], ['ok', -400], ['ok', -400], ['ok', 1200]][['ok', -400], ['ok', -400], ['ok', -400], ['ok', -400], ['ok', 1200]]Passed
regression: rounding half[['ok', 0], ['ok', 1], ['ok', 5], ['open', 5], ['ok', 10]][['ok', 0], ['ok', 1], ['ok', 5], ['open', 5], ['ok', 10]]Passed
regression: scenario 1[['ok', 0], ['ok', 918], ['ok', 344], ['ok', 334], ['ok', 1000], ['ok', 702], ['ok', 0], ['ok', 491]][['ok', 0], ['ok', 918], ['ok', 344], ['ok', 334], ['ok', 1000], ['ok', 702], ['ok', 0], ['ok', 491]]Passed
regression: scenario 2[['open', None], ['ok', 402], ['ok', 2500], ['ok', 1456], ['ok', 901], ['ok', 2500]][['open', None], ['ok', 402], ['ok', 2500], ['ok', 1456], ['ok', 901], ['ok', 2500]]Passed
regression: scenario 3[['over', None], ['ok', -400], ['ok', -400], ['ok', 744], ['over', 744], ['ok', 903], ['ok', 878], ['ok', 666]][['over', None], ['ok', -400], ['ok', -400], ['ok', 744], ['over', 744], ['ok', 903], ['ok', 878], ['ok', 666]]Passed
regression: scenario 4[['ok', 1090], ['ok', 400], ['open', 400], ['ok', -36], ['ok', -341], ['ok', 1200], ['ok', -400], ['ok', 1042], ['ok', 1200]][['ok', 1090], ['ok', 400], ['open', 400], ['ok', -36], ['ok', -341], ['ok', 1200], ['ok', -400], ['ok', 1042], ['ok', 1200]]Passed

SHA-256 / b3014654f6d03337005a57446bbb89b4dcd921ffa725d79ca20bc95ed14ae156

Verification & scope

A deterministic bounded teaching model of one PLC instruction or rung pattern evaluated scan by scan. The stated contract is a stipulated toy convention, not a claim of conformance to any vendor controller or IEC 61131-3 runtime. 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:51:43.687049+00:00.

Case digest / 95c4892dbe3287868d6ce774ed5bc16519ab27e67d74cc5f9cc7dd1b07f248f8