FAILURE MAP
← Case archive

FA-81321 / Music interval and transposition theory / Open access

Triad and seventh inversion figures: first listed note taken as the bass · case 01

An unordered voicing such as [72,64,67] is reported in root position instead of first inversion.

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

ROOT CAUSE

The bass is taken from input order instead of the lowest pitch.

VERIFIED REPAIR

Restore the bass selection step so that it reads `bass = min(x)`.

Unsuccessful approach: Taking the lowest pitch class rather than the lowest pitch misreads inversions whose bass is not the smallest class.

Case contract

Input 3 or 4 MIDI integers in any order. The pitch-class set must match a template relative to some root (triads 047 037 036 048; sevenths 047t 037t 047e 036t 0369, t=10 e=11); roots are tried in ascending pitch-class order. The bass is the lowest MIDI note. Return [root_pc, figure] with figures "", "6", "64" for triads and "7", "65", "43", "42" for sevenths by the bass position in the template; otherwise None.

Why this case matters

Figured-bass realisation and harmonic analysis depend on identifying chord root and inversion.

1 / The failure

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

N = 1
observations = []
def solve(x):
    if not isinstance(x, list) or not 3 <= len(x) <= 4 or not all(isinstance(v, int) for v in x):
        return None
    bass = x[0]
    pcs = sorted(set(v % 12 for v in x))
    for r in pcs:
        rel = sorted((p - r) % 12 for p in pcs)
        if rel in ([0, 4, 7], [0, 3, 7], [0, 3, 6], [0, 4, 8], [0, 4, 7, 10], [0, 3, 7, 10], [0, 4, 7, 11], [0, 3, 6, 10], [0, 3, 6, 9]):
            pos = rel.index((bass - r) % 12)
            figs = ['', '6', '64'] if len(rel) == 3 else ['7', '65', '43', '42']
            return [r, figs[pos]]
    return None
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[([60, 64, 67], [0, '']), ([64, 67, 72], [0, '6']), ([72, 64, 67], [0, '6']), ([55, 59, 62, 65], [7, '7']), ([62, 65, 67, 71], [7, '43']), ([60, 64, 67, 72], [0, '']), ([48, 64, 67, 72], [0, '']), ([64, 60, 67], [0, ''])], [([67, 72, 76], [0, '64']), ([76, 60, 67], [0, '']), ([59, 62, 65, 67], [7, '65']), ([48, 64, 67, 72], [0, '']), ([60, 63, 66, 69], [0, '7']), ([62, 65, 68, 71], [2, '7']), ([60, 62, 67], None), ([71, 62, 65], [11, '6'])], [([72, 64, 67], [0, '6']), ([65, 67, 71, 74], [7, '42']), ([60, 62, 67], None), ([60, 64, 68], [0, '']), ([64, 60, 67], [0, '']), ([60, 61, 62], None), ([60, 64], None), ([63, 60, 66, 70], [0, '7'])], [([64, 67, 72], [0, '6']), ([67, 72, 76], [0, '64']), ([72, 64, 67], [0, '6']), ([55, 59, 62, 65], [7, '7']), ([59, 62, 65, 69], [11, '7']), ([71, 62, 65], [11, '6']), ([60, 64], None), ([60, 64, 67, 70, 74], None)], [([67, 72, 76], [0, '64']), ([72, 64, 67], [0, '6']), ([76, 60, 67], [0, '']), ([55, 59, 62, 65], [7, '7']), ([59, 62, 65, 67], [7, '65']), ([65, 67, 71, 74], [7, '42']), ([57, 60, 64], [9, '']), ([63, 60, 66, 70], [0, '7'])]]
for i, (args, expected) in enumerate(fixtures[N-1]):
    check("oracle %d" % i, 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
oracle 0[0, ''][0, '']Passed
oracle 1[0, '6'][0, '6']Passed
oracle 2[0, ''][0, '6']Failed
oracle 3[7, '7'][7, '7']Passed
oracle 4[7, '43'][7, '43']Passed
oracle 5[0, ''][0, '']Passed
oracle 6[0, ''][0, '']Passed
oracle 7[0, '6'][0, '']Failed

SHA-256 / 8677f4387253808f49e2a2dcadc4e1f919f3f9b9a9909c6c5afcd172611a94b0

2 / The unsuccessful fix

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

N = 1
observations = []
def solve(x):
    if not isinstance(x, list) or not 3 <= len(x) <= 4 or not all(isinstance(v, int) for v in x):
        return None
    bass = min(v % 12 for v in x)
    pcs = sorted(set(v % 12 for v in x))
    for r in pcs:
        rel = sorted((p - r) % 12 for p in pcs)
        if rel in ([0, 4, 7], [0, 3, 7], [0, 3, 6], [0, 4, 8], [0, 4, 7, 10], [0, 3, 7, 10], [0, 4, 7, 11], [0, 3, 6, 10], [0, 3, 6, 9]):
            pos = rel.index((bass - r) % 12)
            figs = ['', '6', '64'] if len(rel) == 3 else ['7', '65', '43', '42']
            return [r, figs[pos]]
    return None
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[([60, 64, 67], [0, '']), ([64, 67, 72], [0, '6']), ([72, 64, 67], [0, '6']), ([55, 59, 62, 65], [7, '7']), ([62, 65, 67, 71], [7, '43']), ([60, 64, 67, 72], [0, '']), ([48, 64, 67, 72], [0, '']), ([64, 60, 67], [0, ''])], [([67, 72, 76], [0, '64']), ([76, 60, 67], [0, '']), ([59, 62, 65, 67], [7, '65']), ([48, 64, 67, 72], [0, '']), ([60, 63, 66, 69], [0, '7']), ([62, 65, 68, 71], [2, '7']), ([60, 62, 67], None), ([71, 62, 65], [11, '6'])], [([72, 64, 67], [0, '6']), ([65, 67, 71, 74], [7, '42']), ([60, 62, 67], None), ([60, 64, 68], [0, '']), ([64, 60, 67], [0, '']), ([60, 61, 62], None), ([60, 64], None), ([63, 60, 66, 70], [0, '7'])], [([64, 67, 72], [0, '6']), ([67, 72, 76], [0, '64']), ([72, 64, 67], [0, '6']), ([55, 59, 62, 65], [7, '7']), ([59, 62, 65, 69], [11, '7']), ([71, 62, 65], [11, '6']), ([60, 64], None), ([60, 64, 67, 70, 74], None)], [([67, 72, 76], [0, '64']), ([72, 64, 67], [0, '6']), ([76, 60, 67], [0, '']), ([55, 59, 62, 65], [7, '7']), ([59, 62, 65, 67], [7, '65']), ([65, 67, 71, 74], [7, '42']), ([57, 60, 64], [9, '']), ([63, 60, 66, 70], [0, '7'])]]
for i, (args, expected) in enumerate(fixtures[N-1]):
    check("oracle %d" % i, 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
oracle 0[0, ''][0, '']Passed
oracle 1[0, ''][0, '6']Failed
oracle 2[0, ''][0, '6']Failed
oracle 3[7, '43'][7, '7']Failed
oracle 4[7, '43'][7, '43']Passed
oracle 5[0, ''][0, '']Passed
oracle 6[0, ''][0, '']Passed
oracle 7[0, ''][0, '']Passed

SHA-256 / d06f7f4b8a83169c675dfa6b46fcfb2b9432ff411a8651d5249ce41b2be48905

3 / The verified repair

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

N = 1
observations = []
def solve(x):
    if not isinstance(x, list) or not 3 <= len(x) <= 4 or not all(isinstance(v, int) for v in x):
        return None
    bass = min(x)
    pcs = sorted(set(v % 12 for v in x))
    for r in pcs:
        rel = sorted((p - r) % 12 for p in pcs)
        if rel in ([0, 4, 7], [0, 3, 7], [0, 3, 6], [0, 4, 8], [0, 4, 7, 10], [0, 3, 7, 10], [0, 4, 7, 11], [0, 3, 6, 10], [0, 3, 6, 9]):
            pos = rel.index((bass - r) % 12)
            figs = ['', '6', '64'] if len(rel) == 3 else ['7', '65', '43', '42']
            return [r, figs[pos]]
    return None
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[([60, 64, 67], [0, '']), ([64, 67, 72], [0, '6']), ([72, 64, 67], [0, '6']), ([55, 59, 62, 65], [7, '7']), ([62, 65, 67, 71], [7, '43']), ([60, 64, 67, 72], [0, '']), ([48, 64, 67, 72], [0, '']), ([64, 60, 67], [0, ''])], [([67, 72, 76], [0, '64']), ([76, 60, 67], [0, '']), ([59, 62, 65, 67], [7, '65']), ([48, 64, 67, 72], [0, '']), ([60, 63, 66, 69], [0, '7']), ([62, 65, 68, 71], [2, '7']), ([60, 62, 67], None), ([71, 62, 65], [11, '6'])], [([72, 64, 67], [0, '6']), ([65, 67, 71, 74], [7, '42']), ([60, 62, 67], None), ([60, 64, 68], [0, '']), ([64, 60, 67], [0, '']), ([60, 61, 62], None), ([60, 64], None), ([63, 60, 66, 70], [0, '7'])], [([64, 67, 72], [0, '6']), ([67, 72, 76], [0, '64']), ([72, 64, 67], [0, '6']), ([55, 59, 62, 65], [7, '7']), ([59, 62, 65, 69], [11, '7']), ([71, 62, 65], [11, '6']), ([60, 64], None), ([60, 64, 67, 70, 74], None)], [([67, 72, 76], [0, '64']), ([72, 64, 67], [0, '6']), ([76, 60, 67], [0, '']), ([55, 59, 62, 65], [7, '7']), ([59, 62, 65, 67], [7, '65']), ([65, 67, 71, 74], [7, '42']), ([57, 60, 64], [9, '']), ([63, 60, 66, 70], [0, '7'])]]
for i, (args, expected) in enumerate(fixtures[N-1]):
    check("oracle %d" % i, 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
oracle 0[0, ''][0, '']Passed
oracle 1[0, '6'][0, '6']Passed
oracle 2[0, '6'][0, '6']Passed
oracle 3[7, '7'][7, '7']Passed
oracle 4[7, '43'][7, '43']Passed
oracle 5[0, ''][0, '']Passed
oracle 6[0, ''][0, '']Passed
oracle 7[0, ''][0, '']Passed

SHA-256 / bd6a492af337cac2070fe551f74ff68f6bfcf5ce6c239ac1b3348168f51671b8

Verification & scope

A deterministic bounded teaching model with a stipulated toy contract; it is not a complete music notation or theory engine. 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:50:01.875054+00:00.

Case digest / e98385ee55d97fb71e3edb07b90658a79c85654b192dfe4910ff1e704d06125f