FAILURE MAP
← Case archive

FA-66601 / Airline fare rule evaluation / Open access

Free tickets counted against total travellers · case 01

A group of 15 receives a free ticket although only 14 would then be paying.

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

ROOT CAUSE

The entitlement divides the whole party by free_per, counting the free traveller as paying.

THE FAILURE

The entitlement divides the whole party by free_per, counting the free traveller as paying.

Unsuccessful approach: Subtracting one traveller first still over-grants for larger groups.

Case contract

Input {'pax','min_group','free_per','fare','disc_pct'}. Below min_group (total travellers) no discount and no free tickets. Otherwise one free ticket per free_per paying travellers (free = pax // (free_per+1)); each paying fare = fare*(100-disc)/100 rounded to cents, then multiplied. Return [paying, free, total r2].

Why this case matters

Group fares apply a discount once a minimum size is reached and grant tour-conductor tickets that must not themselves count as paying passengers.

1 / The failure

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

N = 1
observations = []
def solve(x):
    n = x['pax']
    if n < x['min_group']:
        per = round(x['fare'], 2)
        return [n, 0, round(per * n, 2)]
    free = n // x['free_per']
    paying = n - free
    per = round(x['fare'] * (100 - x['disc_pct']) / 100, 2)
    return [paying, free, round(per * paying, 2)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['exactly minimum group', {'pax': 10, 'min_group': 10, 'free_per': 15, 'fare': 249.99, 'disc_pct': 13}, [10, 0, 2174.9]], ['sixteen travellers one free', {'pax': 16, 'min_group': 10, 'free_per': 15, 'fare': 199.95, 'disc_pct': 10}, [15, 1, 2699.4]], ['fifteen travellers none free', {'pax': 15, 'min_group': 10, 'free_per': 15, 'fare': 200.95, 'disc_pct': 10}, [15, 0, 2712.75]], ['small party', {'pax': 9, 'min_group': 10, 'free_per': 15, 'fare': 120.0, 'disc_pct': 15}, [9, 0, 1080.0]], ['large group two free', {'pax': 35, 'min_group': 10, 'free_per': 15, 'fare': 333.33, 'disc_pct': 7}, [33, 2, 10230.0]], ['generous free rule small party', {'pax': 12, 'min_group': 20, 'free_per': 5, 'fare': 101.0, 'disc_pct': 10}, [12, 0, 1212.0]], ['rounding per passenger', {'pax': 21, 'min_group': 20, 'free_per': 9, 'fare': 99.99, 'disc_pct': 15}, [19, 2, 1614.81]], ['sampled case 1', {'pax': 13, 'min_group': 20, 'free_per': 5, 'fare': 244.64, 'disc_pct': 12}, [13, 0, 3180.32]], ['sampled case 2', {'pax': 24, 'min_group': 20, 'free_per': 5, 'fare': 241.52, 'disc_pct': 20}, [20, 4, 3864.4]], ['sampled case 3', {'pax': 59, 'min_group': 20, 'free_per': 10, 'fare': 154.51, 'disc_pct': 15}, [54, 5, 7091.82]], ['regression: free ticket entitlement', {'pax': 31, 'min_group': 20, 'free_per': 15, 'fare': 530.51, 'disc_pct': 12}, [30, 1, 14005.5]]], [['exactly minimum group', {'pax': 10, 'min_group': 10, 'free_per': 15, 'fare': 249.99, 'disc_pct': 14}, [10, 0, 2149.9]], ['sixteen travellers one free', {'pax': 16, 'min_group': 10, 'free_per': 15, 'fare': 199.95, 'disc_pct': 10}, [15, 1, 2699.4]], ['fifteen travellers none free', {'pax': 15, 'min_group': 10, 'free_per': 15, 'fare': 201.95, 'disc_pct': 10}, [15, 0, 2726.25]], ['small party', {'pax': 8, 'min_group': 10, 'free_per': 15, 'fare': 120.0, 'disc_pct': 15}, [8, 0, 960.0]], ['large group two free', {'pax': 36, 'min_group': 10, 'free_per': 15, 'fare': 333.33, 'disc_pct': 7}, [34, 2, 10540.0]], ['generous free rule small party', {'pax': 12, 'min_group': 20, 'free_per': 5, 'fare': 102.0, 'disc_pct': 10}, [12, 0, 1224.0]], ['rounding per passenger', {'pax': 21, 'min_group': 20, 'free_per': 9, 'fare': 99.99, 'disc_pct': 15}, [19, 2, 1614.81]], ['sampled case 1', {'pax': 24, 'min_group': 15, 'free_per': 5, 'fare': 165.48, 'disc_pct': 15}, [20, 4, 2813.2]], ['sampled case 2', {'pax': 50, 'min_group': 20, 'free_per': 15, 'fare': 118.08, 'disc_pct': 10}, [47, 3, 4994.69]], ['sampled case 3', {'pax': 24, 'min_group': 20, 'free_per': 9, 'fare': 198.91, 'disc_pct': 7}, [22, 2, 4069.78]], ['regression: free ticket entitlement', {'pax': 47, 'min_group': 10, 'free_per': 15, 'fare': 513.47, 'disc_pct': 20}, [45, 2, 18485.1]]], [['exactly minimum group', {'pax': 10, 'min_group': 10, 'free_per': 15, 'fare': 249.99, 'disc_pct': 15}, [10, 0, 2124.9]], ['sixteen travellers one free', {'pax': 16, 'min_group': 10, 'free_per': 15, 'fare': 199.95, 'disc_pct': 10}, [15, 1, 2699.4]], ['fifteen travellers none free', {'pax': 15, 'min_group': 10, 'free_per': 15, 'fare': 202.95, 'disc_pct': 10}, [15, 0, 2739.9]], ['small party', {'pax': 9, 'min_group': 10, 'free_per': 15, 'fare': 120.0, 'disc_pct': 15}, [9, 0, 1080.0]], ['large group two free', {'pax': 37, 'min_group': 10, 'free_per': 15, 'fare': 333.33, 'disc_pct': 7}, [35, 2, 10850.0]], ['generous free rule small party', {'pax': 12, 'min_group': 20, 'free_per': 5, 'fare': 103.0, 'disc_pct': 10}, [12, 0, 1236.0]], ['rounding per passenger', {'pax': 21, 'min_group': 20, 'free_per': 9, 'fare': 99.99, 'disc_pct': 15}, [19, 2, 1614.81]], ['sampled case 1', {'pax': 28, 'min_group': 20, 'free_per': 9, 'fare': 148.43, 'disc_pct': 15}, [26, 2, 3280.42]], ['sampled case 2', {'pax': 19, 'min_group': 10, 'free_per': 10, 'fare': 244.1, 'disc_pct': 12}, [18, 1, 3866.58]], ['sampled case 3', {'pax': 37, 'min_group': 20, 'free_per': 15, 'fare': 79.12, 'disc_pct': 5}, [35, 2, 2630.6]]], [['exactly minimum group', {'pax': 10, 'min_group': 10, 'free_per': 15, 'fare': 249.99, 'disc_pct': 16}, [10, 0, 2099.9]], ['sixteen travellers one free', {'pax': 16, 'min_group': 10, 'free_per': 15, 'fare': 199.95, 'disc_pct': 10}, [15, 1, 2699.4]], ['fifteen travellers none free', {'pax': 15, 'min_group': 10, 'free_per': 15, 'fare': 203.95, 'disc_pct': 10}, [15, 0, 2753.4]], ['small party', {'pax': 8, 'min_group': 10, 'free_per': 15, 'fare': 120.0, 'disc_pct': 15}, [8, 0, 960.0]], ['large group two free', {'pax': 38, 'min_group': 10, 'free_per': 15, 'fare': 333.33, 'disc_pct': 7}, [36, 2, 11160.0]], ['generous free rule small party', {'pax': 12, 'min_group': 20, 'free_per': 5, 'fare': 104.0, 'disc_pct': 10}, [12, 0, 1248.0]], ['rounding per passenger', {'pax': 21, 'min_group': 20, 'free_per': 9, 'fare': 99.99, 'disc_pct': 15}, [19, 2, 1614.81]], ['sampled case 1', {'pax': 17, 'min_group': 20, 'free_per': 10, 'fare': 198.81, 'disc_pct': 7}, [17, 0, 3379.77]], ['sampled case 2', {'pax': 19, 'min_group': 15, 'free_per': 10, 'fare': 304.43, 'disc_pct': 5}, [18, 1, 5205.78]], ['sampled case 3', {'pax': 23, 'min_group': 15, 'free_per': 10, 'fare': 154.19, 'disc_pct': 7}, [21, 2, 3011.4]], ['regression: free ticket entitlement', {'pax': 38, 'min_group': 15, 'free_per': 5, 'fare': 383.79, 'disc_pct': 10}, [32, 6, 11053.12]]], [['exactly minimum group', {'pax': 10, 'min_group': 10, 'free_per': 15, 'fare': 249.99, 'disc_pct': 17}, [10, 0, 2074.9]], ['sixteen travellers one free', {'pax': 16, 'min_group': 10, 'free_per': 15, 'fare': 199.95, 'disc_pct': 10}, [15, 1, 2699.4]], ['fifteen travellers none free', {'pax': 15, 'min_group': 10, 'free_per': 15, 'fare': 204.95, 'disc_pct': 10}, [15, 0, 2766.9]], ['small party', {'pax': 9, 'min_group': 10, 'free_per': 15, 'fare': 120.0, 'disc_pct': 15}, [9, 0, 1080.0]], ['large group two free', {'pax': 39, 'min_group': 10, 'free_per': 15, 'fare': 333.33, 'disc_pct': 7}, [37, 2, 11470.0]], ['generous free rule small party', {'pax': 12, 'min_group': 20, 'free_per': 5, 'fare': 105.0, 'disc_pct': 10}, [12, 0, 1260.0]], ['rounding per passenger', {'pax': 21, 'min_group': 20, 'free_per': 9, 'fare': 99.99, 'disc_pct': 15}, [19, 2, 1614.81]], ['sampled case 1', {'pax': 30, 'min_group': 15, 'free_per': 15, 'fare': 348.05, 'disc_pct': 12}, [29, 1, 8882.12]], ['sampled case 2', {'pax': 31, 'min_group': 15, 'free_per': 9, 'fare': 312.49, 'disc_pct': 12}, [28, 3, 7699.72]], ['sampled case 3', {'pax': 59, 'min_group': 20, 'free_per': 9, 'fare': 68.44, 'disc_pct': 10}, [54, 5, 3326.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 fixtureActualExpectedOutcome
exactly minimum group[10, 0, 2174.9][10, 0, 2174.9]Passed
sixteen travellers one free[15, 1, 2699.4][15, 1, 2699.4]Passed
fifteen travellers none free[14, 1, 2531.9][15, 0, 2712.75]Failed
small party[9, 0, 1080.0][9, 0, 1080.0]Passed
large group two free[33, 2, 10230.0][33, 2, 10230.0]Passed
generous free rule small party[12, 0, 1212.0][12, 0, 1212.0]Passed
rounding per passenger[19, 2, 1614.81][19, 2, 1614.81]Passed
sampled case 1[13, 0, 3180.32][13, 0, 3180.32]Passed
sampled case 2[20, 4, 3864.4][20, 4, 3864.4]Passed
sampled case 3[54, 5, 7091.82][54, 5, 7091.82]Passed
regression: free ticket entitlement[29, 2, 13538.65][30, 1, 14005.5]Failed

SHA-256 / d3539ef5b491b586bcebc63218096fe5fce66a6d573501df086f8c95982a6e9d

2 / The unsuccessful fix

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

N = 1
observations = []
def solve(x):
    n = x['pax']
    if n < x['min_group']:
        per = round(x['fare'], 2)
        return [n, 0, round(per * n, 2)]
    free = (n - 1) // x['free_per']
    paying = n - free
    per = round(x['fare'] * (100 - x['disc_pct']) / 100, 2)
    return [paying, free, round(per * paying, 2)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['exactly minimum group', {'pax': 10, 'min_group': 10, 'free_per': 15, 'fare': 249.99, 'disc_pct': 13}, [10, 0, 2174.9]], ['sixteen travellers one free', {'pax': 16, 'min_group': 10, 'free_per': 15, 'fare': 199.95, 'disc_pct': 10}, [15, 1, 2699.4]], ['fifteen travellers none free', {'pax': 15, 'min_group': 10, 'free_per': 15, 'fare': 200.95, 'disc_pct': 10}, [15, 0, 2712.75]], ['small party', {'pax': 9, 'min_group': 10, 'free_per': 15, 'fare': 120.0, 'disc_pct': 15}, [9, 0, 1080.0]], ['large group two free', {'pax': 35, 'min_group': 10, 'free_per': 15, 'fare': 333.33, 'disc_pct': 7}, [33, 2, 10230.0]], ['generous free rule small party', {'pax': 12, 'min_group': 20, 'free_per': 5, 'fare': 101.0, 'disc_pct': 10}, [12, 0, 1212.0]], ['rounding per passenger', {'pax': 21, 'min_group': 20, 'free_per': 9, 'fare': 99.99, 'disc_pct': 15}, [19, 2, 1614.81]], ['sampled case 1', {'pax': 13, 'min_group': 20, 'free_per': 5, 'fare': 244.64, 'disc_pct': 12}, [13, 0, 3180.32]], ['sampled case 2', {'pax': 24, 'min_group': 20, 'free_per': 5, 'fare': 241.52, 'disc_pct': 20}, [20, 4, 3864.4]], ['sampled case 3', {'pax': 59, 'min_group': 20, 'free_per': 10, 'fare': 154.51, 'disc_pct': 15}, [54, 5, 7091.82]], ['regression: free ticket entitlement', {'pax': 31, 'min_group': 20, 'free_per': 15, 'fare': 530.51, 'disc_pct': 12}, [30, 1, 14005.5]]], [['exactly minimum group', {'pax': 10, 'min_group': 10, 'free_per': 15, 'fare': 249.99, 'disc_pct': 14}, [10, 0, 2149.9]], ['sixteen travellers one free', {'pax': 16, 'min_group': 10, 'free_per': 15, 'fare': 199.95, 'disc_pct': 10}, [15, 1, 2699.4]], ['fifteen travellers none free', {'pax': 15, 'min_group': 10, 'free_per': 15, 'fare': 201.95, 'disc_pct': 10}, [15, 0, 2726.25]], ['small party', {'pax': 8, 'min_group': 10, 'free_per': 15, 'fare': 120.0, 'disc_pct': 15}, [8, 0, 960.0]], ['large group two free', {'pax': 36, 'min_group': 10, 'free_per': 15, 'fare': 333.33, 'disc_pct': 7}, [34, 2, 10540.0]], ['generous free rule small party', {'pax': 12, 'min_group': 20, 'free_per': 5, 'fare': 102.0, 'disc_pct': 10}, [12, 0, 1224.0]], ['rounding per passenger', {'pax': 21, 'min_group': 20, 'free_per': 9, 'fare': 99.99, 'disc_pct': 15}, [19, 2, 1614.81]], ['sampled case 1', {'pax': 24, 'min_group': 15, 'free_per': 5, 'fare': 165.48, 'disc_pct': 15}, [20, 4, 2813.2]], ['sampled case 2', {'pax': 50, 'min_group': 20, 'free_per': 15, 'fare': 118.08, 'disc_pct': 10}, [47, 3, 4994.69]], ['sampled case 3', {'pax': 24, 'min_group': 20, 'free_per': 9, 'fare': 198.91, 'disc_pct': 7}, [22, 2, 4069.78]], ['regression: free ticket entitlement', {'pax': 47, 'min_group': 10, 'free_per': 15, 'fare': 513.47, 'disc_pct': 20}, [45, 2, 18485.1]]], [['exactly minimum group', {'pax': 10, 'min_group': 10, 'free_per': 15, 'fare': 249.99, 'disc_pct': 15}, [10, 0, 2124.9]], ['sixteen travellers one free', {'pax': 16, 'min_group': 10, 'free_per': 15, 'fare': 199.95, 'disc_pct': 10}, [15, 1, 2699.4]], ['fifteen travellers none free', {'pax': 15, 'min_group': 10, 'free_per': 15, 'fare': 202.95, 'disc_pct': 10}, [15, 0, 2739.9]], ['small party', {'pax': 9, 'min_group': 10, 'free_per': 15, 'fare': 120.0, 'disc_pct': 15}, [9, 0, 1080.0]], ['large group two free', {'pax': 37, 'min_group': 10, 'free_per': 15, 'fare': 333.33, 'disc_pct': 7}, [35, 2, 10850.0]], ['generous free rule small party', {'pax': 12, 'min_group': 20, 'free_per': 5, 'fare': 103.0, 'disc_pct': 10}, [12, 0, 1236.0]], ['rounding per passenger', {'pax': 21, 'min_group': 20, 'free_per': 9, 'fare': 99.99, 'disc_pct': 15}, [19, 2, 1614.81]], ['sampled case 1', {'pax': 28, 'min_group': 20, 'free_per': 9, 'fare': 148.43, 'disc_pct': 15}, [26, 2, 3280.42]], ['sampled case 2', {'pax': 19, 'min_group': 10, 'free_per': 10, 'fare': 244.1, 'disc_pct': 12}, [18, 1, 3866.58]], ['sampled case 3', {'pax': 37, 'min_group': 20, 'free_per': 15, 'fare': 79.12, 'disc_pct': 5}, [35, 2, 2630.6]]], [['exactly minimum group', {'pax': 10, 'min_group': 10, 'free_per': 15, 'fare': 249.99, 'disc_pct': 16}, [10, 0, 2099.9]], ['sixteen travellers one free', {'pax': 16, 'min_group': 10, 'free_per': 15, 'fare': 199.95, 'disc_pct': 10}, [15, 1, 2699.4]], ['fifteen travellers none free', {'pax': 15, 'min_group': 10, 'free_per': 15, 'fare': 203.95, 'disc_pct': 10}, [15, 0, 2753.4]], ['small party', {'pax': 8, 'min_group': 10, 'free_per': 15, 'fare': 120.0, 'disc_pct': 15}, [8, 0, 960.0]], ['large group two free', {'pax': 38, 'min_group': 10, 'free_per': 15, 'fare': 333.33, 'disc_pct': 7}, [36, 2, 11160.0]], ['generous free rule small party', {'pax': 12, 'min_group': 20, 'free_per': 5, 'fare': 104.0, 'disc_pct': 10}, [12, 0, 1248.0]], ['rounding per passenger', {'pax': 21, 'min_group': 20, 'free_per': 9, 'fare': 99.99, 'disc_pct': 15}, [19, 2, 1614.81]], ['sampled case 1', {'pax': 17, 'min_group': 20, 'free_per': 10, 'fare': 198.81, 'disc_pct': 7}, [17, 0, 3379.77]], ['sampled case 2', {'pax': 19, 'min_group': 15, 'free_per': 10, 'fare': 304.43, 'disc_pct': 5}, [18, 1, 5205.78]], ['sampled case 3', {'pax': 23, 'min_group': 15, 'free_per': 10, 'fare': 154.19, 'disc_pct': 7}, [21, 2, 3011.4]], ['regression: free ticket entitlement', {'pax': 38, 'min_group': 15, 'free_per': 5, 'fare': 383.79, 'disc_pct': 10}, [32, 6, 11053.12]]], [['exactly minimum group', {'pax': 10, 'min_group': 10, 'free_per': 15, 'fare': 249.99, 'disc_pct': 17}, [10, 0, 2074.9]], ['sixteen travellers one free', {'pax': 16, 'min_group': 10, 'free_per': 15, 'fare': 199.95, 'disc_pct': 10}, [15, 1, 2699.4]], ['fifteen travellers none free', {'pax': 15, 'min_group': 10, 'free_per': 15, 'fare': 204.95, 'disc_pct': 10}, [15, 0, 2766.9]], ['small party', {'pax': 9, 'min_group': 10, 'free_per': 15, 'fare': 120.0, 'disc_pct': 15}, [9, 0, 1080.0]], ['large group two free', {'pax': 39, 'min_group': 10, 'free_per': 15, 'fare': 333.33, 'disc_pct': 7}, [37, 2, 11470.0]], ['generous free rule small party', {'pax': 12, 'min_group': 20, 'free_per': 5, 'fare': 105.0, 'disc_pct': 10}, [12, 0, 1260.0]], ['rounding per passenger', {'pax': 21, 'min_group': 20, 'free_per': 9, 'fare': 99.99, 'disc_pct': 15}, [19, 2, 1614.81]], ['sampled case 1', {'pax': 30, 'min_group': 15, 'free_per': 15, 'fare': 348.05, 'disc_pct': 12}, [29, 1, 8882.12]], ['sampled case 2', {'pax': 31, 'min_group': 15, 'free_per': 9, 'fare': 312.49, 'disc_pct': 12}, [28, 3, 7699.72]], ['sampled case 3', {'pax': 59, 'min_group': 20, 'free_per': 9, 'fare': 68.44, 'disc_pct': 10}, [54, 5, 3326.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 fixtureActualExpectedOutcome
exactly minimum group[10, 0, 2174.9][10, 0, 2174.9]Passed
sixteen travellers one free[15, 1, 2699.4][15, 1, 2699.4]Passed
fifteen travellers none free[15, 0, 2712.75][15, 0, 2712.75]Passed
small party[9, 0, 1080.0][9, 0, 1080.0]Passed
large group two free[33, 2, 10230.0][33, 2, 10230.0]Passed
generous free rule small party[12, 0, 1212.0][12, 0, 1212.0]Passed
rounding per passenger[19, 2, 1614.81][19, 2, 1614.81]Passed
sampled case 1[13, 0, 3180.32][13, 0, 3180.32]Passed
sampled case 2[20, 4, 3864.4][20, 4, 3864.4]Passed
sampled case 3[54, 5, 7091.82][54, 5, 7091.82]Passed
regression: free ticket entitlement[29, 2, 13538.65][30, 1, 14005.5]Failed

SHA-256 / 8e26196b276cdaf107af0451d79f40786b0f19dcea17c40815bf2a97c03925ec

HELD IN THE MEMBER ARCHIVE

The verified repair and its recorded checks are member-only.

This mechanism has 11 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

A stipulated toy fare-rule contract with invented constants; it is not an ATPCO or carrier tariff implementation and makes no claim of industry-standard conformance. 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:47:45.300402+00:00.

Case digest / edfad7d4f632c18d35831590f9da459fa4155d87e7d86596f3aee51c801640f3