FA-81191 / Music interval and transposition theory / Open access
Roman numeral label for a chord in a key: leading-tone chord in minor given a sharp prefix · case 01
In A minor the G# diminished chord is labelled #vii° instead of vii°.
ROOT CAUSE
The minor-key special case for the raised leading tone is missing, so it falls into the chromatic sharp branch.
VERIFIED REPAIR
Restore the minor leading tone step so that it reads `elif mode == 'minor' and rel == 11: deg = 6`.
Unsuccessful approach: Treating the raised sixth as the leading tone too labels F# minor chords in A minor as vii instead of #vi.
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 == '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'), ([9, 'minor', 8, 'dim'], 'vii°'), ([9, 'minor', 6, 'min'], '#vi'), ([0, 'minor', 9, 'min'], '#vi'), ([5, 'minor', 4, 'dim'], 'vii°')], [([0, 'major', 8, 'maj'], 'bVI'), ([0, 'major', 10, 'maj'], 'bVII'), ([0, 'major', 1, 'maj'], 'bII'), ([0, 'major', 3, 'maj'], 'bIII'), ([9, 'minor', 8, 'dim'], 'vii°'), ([9, 'minor', 6, 'min'], '#vi'), ([0, 'minor', 11, 'dim'], 'vii°'), ([0, 'minor', 9, 'min'], '#vi')], [([0, 'major', 3, 'maj'], 'bIII'), ([0, 'major', 6, 'dim'], 'bv°'), ([9, 'minor', 4, 'maj'], 'V'), ([9, 'minor', 0, 'maj'], 'III'), ([9, 'minor', 6, 'min'], '#vi'), ([0, 'minor', 11, 'dim'], 'vii°'), ([0, 'minor', 9, 'min'], '#vi'), ([5, 'minor', 4, 'dim'], 'vii°')], [([9, 'minor', 8, 'dim'], 'vii°'), ([9, 'minor', 0, 'maj'], 'III'), ([9, 'minor', 7, 'maj'], 'VII'), ([9, 'minor', 6, 'min'], '#vi'), ([9, 'minor', 1, 'maj'], '#III'), ([9, 'minor', 3, 'dim'], '#iv°'), ([0, 'minor', 9, 'min'], '#vi'), ([5, 'minor', 4, 'dim'], 'vii°')], [([9, 'minor', 8, 'dim'], 'vii°'), ([9, 'minor', 6, 'min'], '#vi'), ([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+'), ([7, 'major', 2, 'maj'], 'V')]]
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| oracle 0 | V | V | Passed |
| oracle 1 | ii | ii | Passed |
| oracle 2 | vii° | vii° | Passed |
| oracle 3 | bVI | bVI | Passed |
| oracle 4 | #vii° | vii° | Failed |
| oracle 5 | #vi | #vi | Passed |
| oracle 6 | #vi | #vi | Passed |
| oracle 7 | #vii° | vii° | Failed |
SHA-256 / 504c08b7ea4084ef65b837b470deff0e4069c6b4f72c59adc787525ed1338f6b
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 in (11, 9):
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'), ([9, 'minor', 8, 'dim'], 'vii°'), ([9, 'minor', 6, 'min'], '#vi'), ([0, 'minor', 9, 'min'], '#vi'), ([5, 'minor', 4, 'dim'], 'vii°')], [([0, 'major', 8, 'maj'], 'bVI'), ([0, 'major', 10, 'maj'], 'bVII'), ([0, 'major', 1, 'maj'], 'bII'), ([0, 'major', 3, 'maj'], 'bIII'), ([9, 'minor', 8, 'dim'], 'vii°'), ([9, 'minor', 6, 'min'], '#vi'), ([0, 'minor', 11, 'dim'], 'vii°'), ([0, 'minor', 9, 'min'], '#vi')], [([0, 'major', 3, 'maj'], 'bIII'), ([0, 'major', 6, 'dim'], 'bv°'), ([9, 'minor', 4, 'maj'], 'V'), ([9, 'minor', 0, 'maj'], 'III'), ([9, 'minor', 6, 'min'], '#vi'), ([0, 'minor', 11, 'dim'], 'vii°'), ([0, 'minor', 9, 'min'], '#vi'), ([5, 'minor', 4, 'dim'], 'vii°')], [([9, 'minor', 8, 'dim'], 'vii°'), ([9, 'minor', 0, 'maj'], 'III'), ([9, 'minor', 7, 'maj'], 'VII'), ([9, 'minor', 6, 'min'], '#vi'), ([9, 'minor', 1, 'maj'], '#III'), ([9, 'minor', 3, 'dim'], '#iv°'), ([0, 'minor', 9, 'min'], '#vi'), ([5, 'minor', 4, 'dim'], 'vii°')], [([9, 'minor', 8, 'dim'], 'vii°'), ([9, 'minor', 6, 'min'], '#vi'), ([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+'), ([7, 'major', 2, 'maj'], 'V')]]
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| oracle 0 | V | V | Passed |
| oracle 1 | ii | ii | Passed |
| oracle 2 | vii° | vii° | Passed |
| oracle 3 | bVI | bVI | Passed |
| oracle 4 | vii° | vii° | Passed |
| oracle 5 | vii | #vi | Failed |
| oracle 6 | vii | #vi | Failed |
| oracle 7 | vii° | vii° | Passed |
SHA-256 / 2b40cd1eaf1f415265df5eb3337335108f4d1f826f5dc63c4a226b3f08aa4372
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'), ([9, 'minor', 8, 'dim'], 'vii°'), ([9, 'minor', 6, 'min'], '#vi'), ([0, 'minor', 9, 'min'], '#vi'), ([5, 'minor', 4, 'dim'], 'vii°')], [([0, 'major', 8, 'maj'], 'bVI'), ([0, 'major', 10, 'maj'], 'bVII'), ([0, 'major', 1, 'maj'], 'bII'), ([0, 'major', 3, 'maj'], 'bIII'), ([9, 'minor', 8, 'dim'], 'vii°'), ([9, 'minor', 6, 'min'], '#vi'), ([0, 'minor', 11, 'dim'], 'vii°'), ([0, 'minor', 9, 'min'], '#vi')], [([0, 'major', 3, 'maj'], 'bIII'), ([0, 'major', 6, 'dim'], 'bv°'), ([9, 'minor', 4, 'maj'], 'V'), ([9, 'minor', 0, 'maj'], 'III'), ([9, 'minor', 6, 'min'], '#vi'), ([0, 'minor', 11, 'dim'], 'vii°'), ([0, 'minor', 9, 'min'], '#vi'), ([5, 'minor', 4, 'dim'], 'vii°')], [([9, 'minor', 8, 'dim'], 'vii°'), ([9, 'minor', 0, 'maj'], 'III'), ([9, 'minor', 7, 'maj'], 'VII'), ([9, 'minor', 6, 'min'], '#vi'), ([9, 'minor', 1, 'maj'], '#III'), ([9, 'minor', 3, 'dim'], '#iv°'), ([0, 'minor', 9, 'min'], '#vi'), ([5, 'minor', 4, 'dim'], 'vii°')], [([9, 'minor', 8, 'dim'], 'vii°'), ([9, 'minor', 6, 'min'], '#vi'), ([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+'), ([7, 'major', 2, 'maj'], 'V')]]
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| oracle 0 | V | V | Passed |
| oracle 1 | ii | ii | Passed |
| oracle 2 | vii° | vii° | Passed |
| oracle 3 | bVI | bVI | Passed |
| oracle 4 | vii° | vii° | Passed |
| oracle 5 | #vi | #vi | Passed |
| oracle 6 | #vi | #vi | Passed |
| oracle 7 | vii° | vii° | Passed |
SHA-256 / e5c149697338caaa406f66b6d7fcf662b528ce139391323bd306e9de910ba275
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.640063+00:00.
Case digest / 52aa75981fa474fe3571718c5a62b0d0d3c5b5951fd3e499c816bda8fb3b6c37