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.
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| 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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| 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