FAILURE MAP
← Case archive

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

Roman numeral label for a chord in a key: flat prefix attached to the degree below · case 01

The borrowed flat-six chord in C major is labelled bV instead of bVI.

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

ROOT CAUSE

Non-diatonic major-key roots are referenced to the degree a semitone below while still using the flat prefix.

VERIFIED REPAIR

Restore the major chromatic reference step so that it reads `prefix = 'b' deg = scale.index((rel + 1) % 12)`.

Unsuccessful approach: Switching to a sharp prefix with the lower degree labels bVI as #V, contradicting the major-key flat convention.

Case contract

Input [tonic_pc, mode, root_pc, quality] with mode major or minor (natural minor scale 0 2 3 5 7 8 10) and quality maj, min, dim or aug. Diatonic roots use their degree; in minor the leading tone (11 semitones) is degree VII with no prefix. Other roots in major are written b + the degree a semitone above; in minor # + the degree a semitone below. Numerals are uppercase for maj and aug, lowercase for min and dim, with suffix ° for dim and + for aug. Invalid input returns None.

Why this case matters

Harmonic analysis and ear-training tools label chords relative to the key.

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 len(x) != 4:
        return None
    tonic, mode, root, quality = x
    scales = {'major': [0, 2, 4, 5, 7, 9, 11], 'minor': [0, 2, 3, 5, 7, 8, 10]}
    if mode not in scales or quality not in ('maj', 'min', 'dim', 'aug'):
        return None
    if not all(isinstance(v, int) for v in (tonic, root)):
        return None
    rel = (root - tonic) % 12
    scale = scales[mode]
    names = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII']
    prefix = ''
    if rel in scale:
        deg = scale.index(rel)
    elif mode == 'minor' and rel == 11:
        deg = 6
    elif mode == 'major':
        prefix = 'b'
        deg = scale.index((rel - 1) % 12)
    else:
        prefix = '#'
        deg = scale.index((rel - 1) % 12)
    numeral = names[deg] if quality in ('maj', 'aug') else names[deg].lower()
    suffix = {'maj': '', 'min': '', 'dim': '°', 'aug': '+'}[quality]
    return prefix + numeral + suffix
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[([0, 'major', 7, 'maj'], 'V'), ([0, 'major', 2, 'min'], 'ii'), ([0, 'major', 11, 'dim'], 'vii°'), ([0, 'major', 8, 'maj'], 'bVI'), ([0, 'major', 1, 'maj'], 'bII'), ([0, 'major', 3, 'maj'], 'bIII'), ([9, 'minor', 4, 'maj'], 'V'), ([9, 'minor', 8, 'dim'], 'vii°')], [([0, 'major', 10, 'maj'], 'bVII'), ([0, 'major', 3, 'maj'], 'bIII'), ([0, 'major', 6, 'dim'], 'bv°'), ([9, 'minor', 4, 'maj'], 'V'), ([9, 'minor', 8, 'dim'], 'vii°'), ([9, 'minor', 0, 'maj'], 'III'), ([9, 'minor', 7, 'maj'], 'VII'), ([9, 'minor', 6, 'min'], '#vi')], [([0, 'major', 8, 'maj'], 'bVI'), ([0, 'major', 1, 'maj'], 'bII'), ([0, 'major', 6, 'dim'], 'bv°'), ([9, 'minor', 7, 'maj'], 'VII'), ([9, 'minor', 6, 'min'], '#vi'), ([9, 'minor', 1, 'maj'], '#III'), ([9, 'minor', 3, 'dim'], '#iv°'), ([0, 'minor', 11, 'dim'], 'vii°')], [([0, 'major', 8, 'maj'], 'bVI'), ([0, 'major', 10, 'maj'], 'bVII'), ([0, 'major', 3, 'maj'], 'bIII'), ([9, 'minor', 3, 'dim'], '#iv°'), ([0, 'minor', 11, 'dim'], 'vii°'), ([0, 'minor', 9, 'min'], '#vi'), ([0, 'minor', 4, 'maj'], '#III'), ([0, 'minor', 3, 'aug'], 'III+')], [([0, 'major', 10, 'maj'], 'bVII'), ([0, 'major', 1, 'maj'], 'bII'), ([0, 'major', 6, 'dim'], 'bv°'), ([0, 'minor', 4, 'maj'], '#III'), ([0, 'minor', 3, 'aug'], 'III+'), ([7, 'major', 2, 'maj'], 'V'), ([7, 'major', 0, 'maj'], 'IV'), ([2, 'major', 1, 'dim'], 'vii°')]]
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 0VVPassed
oracle 1iiiiPassed
oracle 2vii°vii°Passed
oracle 3bVbVIFailed
oracle 4bIbIIFailed
oracle 5bIIbIIIFailed
oracle 6VVPassed
oracle 7vii°vii°Passed

SHA-256 / 7054939575c4affdab33bbab113a80d1aa0194c178fc0a4d235e748f2376f4a6

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 len(x) != 4:
        return None
    tonic, mode, root, quality = x
    scales = {'major': [0, 2, 4, 5, 7, 9, 11], 'minor': [0, 2, 3, 5, 7, 8, 10]}
    if mode not in scales or quality not in ('maj', 'min', 'dim', 'aug'):
        return None
    if not all(isinstance(v, int) for v in (tonic, root)):
        return None
    rel = (root - tonic) % 12
    scale = scales[mode]
    names = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII']
    prefix = ''
    if rel in scale:
        deg = scale.index(rel)
    elif mode == 'minor' and rel == 11:
        deg = 6
    elif mode == 'major':
        prefix = '#'
        deg = scale.index((rel - 1) % 12)
    else:
        prefix = '#'
        deg = scale.index((rel - 1) % 12)
    numeral = names[deg] if quality in ('maj', 'aug') else names[deg].lower()
    suffix = {'maj': '', 'min': '', 'dim': '°', 'aug': '+'}[quality]
    return prefix + numeral + suffix
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[([0, 'major', 7, 'maj'], 'V'), ([0, 'major', 2, 'min'], 'ii'), ([0, 'major', 11, 'dim'], 'vii°'), ([0, 'major', 8, 'maj'], 'bVI'), ([0, 'major', 1, 'maj'], 'bII'), ([0, 'major', 3, 'maj'], 'bIII'), ([9, 'minor', 4, 'maj'], 'V'), ([9, 'minor', 8, 'dim'], 'vii°')], [([0, 'major', 10, 'maj'], 'bVII'), ([0, 'major', 3, 'maj'], 'bIII'), ([0, 'major', 6, 'dim'], 'bv°'), ([9, 'minor', 4, 'maj'], 'V'), ([9, 'minor', 8, 'dim'], 'vii°'), ([9, 'minor', 0, 'maj'], 'III'), ([9, 'minor', 7, 'maj'], 'VII'), ([9, 'minor', 6, 'min'], '#vi')], [([0, 'major', 8, 'maj'], 'bVI'), ([0, 'major', 1, 'maj'], 'bII'), ([0, 'major', 6, 'dim'], 'bv°'), ([9, 'minor', 7, 'maj'], 'VII'), ([9, 'minor', 6, 'min'], '#vi'), ([9, 'minor', 1, 'maj'], '#III'), ([9, 'minor', 3, 'dim'], '#iv°'), ([0, 'minor', 11, 'dim'], 'vii°')], [([0, 'major', 8, 'maj'], 'bVI'), ([0, 'major', 10, 'maj'], 'bVII'), ([0, 'major', 3, 'maj'], 'bIII'), ([9, 'minor', 3, 'dim'], '#iv°'), ([0, 'minor', 11, 'dim'], 'vii°'), ([0, 'minor', 9, 'min'], '#vi'), ([0, 'minor', 4, 'maj'], '#III'), ([0, 'minor', 3, 'aug'], 'III+')], [([0, 'major', 10, 'maj'], 'bVII'), ([0, 'major', 1, 'maj'], 'bII'), ([0, 'major', 6, 'dim'], 'bv°'), ([0, 'minor', 4, 'maj'], '#III'), ([0, 'minor', 3, 'aug'], 'III+'), ([7, 'major', 2, 'maj'], 'V'), ([7, 'major', 0, 'maj'], 'IV'), ([2, 'major', 1, 'dim'], 'vii°')]]
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 0VVPassed
oracle 1iiiiPassed
oracle 2vii°vii°Passed
oracle 3#VbVIFailed
oracle 4#IbIIFailed
oracle 5#IIbIIIFailed
oracle 6VVPassed
oracle 7vii°vii°Passed

SHA-256 / 4f25fe921161d159b3c2390f0e0982a47339788eeee67e64e127920087926e95

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 len(x) != 4:
        return None
    tonic, mode, root, quality = x
    scales = {'major': [0, 2, 4, 5, 7, 9, 11], 'minor': [0, 2, 3, 5, 7, 8, 10]}
    if mode not in scales or quality not in ('maj', 'min', 'dim', 'aug'):
        return None
    if not all(isinstance(v, int) for v in (tonic, root)):
        return None
    rel = (root - tonic) % 12
    scale = scales[mode]
    names = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII']
    prefix = ''
    if rel in scale:
        deg = scale.index(rel)
    elif mode == 'minor' and rel == 11:
        deg = 6
    elif mode == 'major':
        prefix = 'b'
        deg = scale.index((rel + 1) % 12)
    else:
        prefix = '#'
        deg = scale.index((rel - 1) % 12)
    numeral = names[deg] if quality in ('maj', 'aug') else names[deg].lower()
    suffix = {'maj': '', 'min': '', 'dim': '°', 'aug': '+'}[quality]
    return prefix + numeral + suffix
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[([0, 'major', 7, 'maj'], 'V'), ([0, 'major', 2, 'min'], 'ii'), ([0, 'major', 11, 'dim'], 'vii°'), ([0, 'major', 8, 'maj'], 'bVI'), ([0, 'major', 1, 'maj'], 'bII'), ([0, 'major', 3, 'maj'], 'bIII'), ([9, 'minor', 4, 'maj'], 'V'), ([9, 'minor', 8, 'dim'], 'vii°')], [([0, 'major', 10, 'maj'], 'bVII'), ([0, 'major', 3, 'maj'], 'bIII'), ([0, 'major', 6, 'dim'], 'bv°'), ([9, 'minor', 4, 'maj'], 'V'), ([9, 'minor', 8, 'dim'], 'vii°'), ([9, 'minor', 0, 'maj'], 'III'), ([9, 'minor', 7, 'maj'], 'VII'), ([9, 'minor', 6, 'min'], '#vi')], [([0, 'major', 8, 'maj'], 'bVI'), ([0, 'major', 1, 'maj'], 'bII'), ([0, 'major', 6, 'dim'], 'bv°'), ([9, 'minor', 7, 'maj'], 'VII'), ([9, 'minor', 6, 'min'], '#vi'), ([9, 'minor', 1, 'maj'], '#III'), ([9, 'minor', 3, 'dim'], '#iv°'), ([0, 'minor', 11, 'dim'], 'vii°')], [([0, 'major', 8, 'maj'], 'bVI'), ([0, 'major', 10, 'maj'], 'bVII'), ([0, 'major', 3, 'maj'], 'bIII'), ([9, 'minor', 3, 'dim'], '#iv°'), ([0, 'minor', 11, 'dim'], 'vii°'), ([0, 'minor', 9, 'min'], '#vi'), ([0, 'minor', 4, 'maj'], '#III'), ([0, 'minor', 3, 'aug'], 'III+')], [([0, 'major', 10, 'maj'], 'bVII'), ([0, 'major', 1, 'maj'], 'bII'), ([0, 'major', 6, 'dim'], 'bv°'), ([0, 'minor', 4, 'maj'], '#III'), ([0, 'minor', 3, 'aug'], 'III+'), ([7, 'major', 2, 'maj'], 'V'), ([7, 'major', 0, 'maj'], 'IV'), ([2, 'major', 1, 'dim'], 'vii°')]]
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 0VVPassed
oracle 1iiiiPassed
oracle 2vii°vii°Passed
oracle 3bVIbVIPassed
oracle 4bIIbIIPassed
oracle 5bIIIbIIIPassed
oracle 6VVPassed
oracle 7vii°vii°Passed

SHA-256 / 25b85bc1194b73563087d9ea5e74d993c2e87458a0eb9dc9135b4c533ab85db8

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

Case digest / c3f4fd9d9617ace8fbd24abdccc22a95c9b3681b41dae69bf5afe459d6ea09ce