FA-79896 / Typography line breaking / Open access
Total-fit paragraph demerits: badness tolerance · case 01
Very loose lines are chosen that should have been rejected as infeasible.
ROOT CAUSE
The tolerance check compares against the 10000 ceiling so nothing is ever rejected.
VERIFIED REPAIR
Reject candidate lines with badness above 1000.
Unsuccessful approach: Tightening the tolerance to 100 rejects acceptable lines.
Case contract
Input [word widths, [space, stretch, shrink] per gap, line width, line penalty]. A line i..j has gaps=j-i-1. The last line has zero badness when not overfull. Other lines use ratio shortfall/(stretch*gaps) or /(shrink*gaps); zero stretch with slack or compression past shrink is infeasible; badness=min(10000, floor(100|r|^3+1/2)) and must be <=1000. Fitness 0 tight (r<-1/2), 1 decent (r<=1/2), 2 loose (r<=1), 3 very loose; last line decent. Demerits (lp+b)^2 plus 3000 when adjacent fitness classes differ by more than 1, starting from decent. Return [min total demerits, break list] (ties: first found) or ["infeasible"].
Why this case matters
Line breaking decides where paragraphs wrap on screen and in print; a wrong decision point shifts every following line.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
import math
from fractions import Fraction
N = 1
observations = []
def solve(x):
words, glue, width, lp = x
sp, st, sh = glue
n = len(words)
def rate(i, j):
gaps = j - i - 1
nat = sum(words[i:j]) + sp * gaps
short = width - nat
if j == n and short >= 0:
return 0, 1
if short > 0:
if st * gaps == 0:
return None
r = Fraction(short, st * gaps)
elif short < 0:
if -short > sh * gaps:
return None
r = Fraction(short, sh * gaps)
else:
r = Fraction(0)
b = min(10000, math.floor(100 * abs(r) ** 3 + Fraction(1, 2)))
if b > 10000:
return None
fit = 0 if r < Fraction(-1, 2) else 1 if r <= Fraction(1, 2) else 2 if r <= 1 else 3
return b, fit
best = {(0, 1): (0, [])}
for j in range(1, n + 1):
for i in range(j):
rated = rate(i, j)
if rated is None:
continue
b, fit = rated
for (pos, pfit), (dem, brk) in sorted(best.items()):
if pos != i:
continue
d = dem + (lp + b) ** 2
if abs(fit - pfit) > 1:
d += 3000
key = (j, fit)
if key not in best or d < best[key][0]:
best[key] = (d, brk + [j])
finals = [v for (pos, f), v in best.items() if pos == n]
if not finals:
return ['infeasible']
d, brk = min(finals)
return [d, brk]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: badness tolerance', [[2, 7, 3, 5, 2, 5, 6], [2, 1, 0], 21, 50], ['infeasible']), ('regression: badness tolerance', [[5, 5, 6, 1], [3, 3, 0], 20, 50], ['infeasible']), ('partial-repair probe', [[7, 4, 5, 1, 7, 3], [2, 2, 0], 17, 1], [647603, [2, 5, 6]]), ('partial-repair probe', [[7, 5, 2, 3, 3], [3, 1, 0], 17, 10], [662200, [2, 5]]), ('tight then loose lines', [[3, 3, 3, 3, 3, 3, 3], [2, 2, 1], 11, 1], [20403, [3, 6, 7]]), ('two-line paragraph with loose first line', [[4, 5, 3, 6], [2, 2, 1], 14, 10], [12200, [3, 4]]), ('control layout', [[2, 3, 3], [3, 1, 2], 12, 10], [529, [3]]), ('control layout', [[2, 1, 7, 7], [2, 3, 0], 13, 10], ['infeasible'])], [('regression: badness tolerance', [[7, 4, 7, 2, 4, 2, 1], [2, 1, 0], 17, 10], ['infeasible']), ('regression: badness tolerance', [[1, 4, 7, 1, 7, 3, 2, 4], [2, 1, 0], 14, 10], ['infeasible']), ('partial-repair probe', [[7, 4, 7, 6], [2, 3, 1], 19, 10], [662200, [2, 4]]), ('partial-repair probe', [[4, 4, 6], [2, 2, 0], 14, 10], [662200, [2, 3]]), ('last line overfull', [[5, 5, 5], [1, 1, 0], 9, 10], ['infeasible']), ('tight then loose lines', [[3, 3, 3, 3, 3, 3, 3], [2, 2, 1], 11, 1], [20403, [3, 6, 7]]), ('control layout', [[2, 4, 4, 7, 4, 2, 5], [1, 3, 2], 16, 50], [8900, [4, 7]]), ('control layout', [[1, 2, 5, 4, 1, 6], [2, 1, 2], 18, 50], [5000, [4, 6]])], [('regression: badness tolerance', [[5, 4, 1, 2, 6, 1, 5], [3, 1, 1], 11, 50], ['infeasible']), ('regression: badness tolerance', [[6, 7, 6, 5], [3, 3, 0], 24, 50], ['infeasible']), ('partial-repair probe', [[6, 1, 2, 5, 6, 1, 3, 4], [3, 2, 0], 22, 10], [313316, [3, 6, 8]]), ('partial-repair probe', [[1, 3, 3], [3, 3, 0], 12, 50], [271669, [2, 3]]), ('single word paragraph', [[7], [2, 2, 1], 10, 10], [100, [1]]), ('last line overfull', [[5, 5, 5], [1, 1, 0], 9, 10], ['infeasible']), ('control layout', [[7, 6, 4, 1, 7, 1, 6], [1, 3, 2], 16, 1], [963, [2, 6, 7]]), ('control layout', [[7, 7, 7, 6], [2, 3, 1], 24, 10], [629, [3, 4]])], [('regression: badness tolerance', [[6, 7, 6, 5], [3, 3, 0], 24, 50], ['infeasible']), ('regression: badness tolerance', [[1, 2, 6, 2, 4, 4, 7, 1], [1, 2, 0], 9, 10], ['infeasible']), ('partial-repair probe', [[4, 5, 7, 1], [3, 2, 1], 15, 10], [127204, [2, 4]]), ('partial-repair probe', [[7, 4, 7, 6], [2, 3, 1], 19, 10], [662200, [2, 4]]), ('single word paragraph', [[7], [2, 2, 1], 10, 10], [100, [1]]), ('tight then loose lines', [[3, 3, 3, 3, 3, 3, 3], [2, 2, 1], 11, 1], [20403, [3, 6, 7]]), ('control layout', [[7, 7, 7, 1, 2], [3, 3, 2], 20, 10], [12200, [2, 5]]), ('control layout', [[7, 7, 4, 3, 1], [2, 2, 1], 21, 10], [629, [3, 5]])], [('regression: badness tolerance', [[4, 5, 3, 6, 2, 7], [1, 1, 0], 13, 1], ['infeasible']), ('regression: badness tolerance', [[3, 7, 1, 2, 7, 1], [2, 2, 0], 13, 10], ['infeasible']), ('partial-repair probe', [[5, 2, 6, 2, 2, 5, 5], [3, 1, 1], 12, 10], [1330400, [2, 4, 6, 7]]), ('partial-repair probe', [[5, 3, 5, 4, 7], [3, 1, 2], 13, 50], [750500, [2, 4, 5]]), ('two-line paragraph with loose first line', [[4, 5, 3, 6], [2, 2, 1], 14, 10], [12200, [3, 4]]), ('single word paragraph', [[7], [2, 2, 1], 10, 10], [100, [1]]), ('control layout', [[5, 7, 2], [2, 1, 2], 24, 50], [2500, [3]]), ('control layout', [[1, 1, 5, 3], [3, 2, 0], 14, 50], [5204, [3, 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 |
|---|---|---|---|
| regression: badness tolerance | [5212038, [3, 6, 7]] | ['infeasible'] | Failed |
| regression: badness tolerance | [1750900, [2, 4]] | ['infeasible'] | Failed |
| partial-repair probe | [647603, [2, 5, 6]] | [647603, [2, 5, 6]] | Passed |
| partial-repair probe | [662200, [2, 5]] | [662200, [2, 5]] | Passed |
| tight then loose lines | [20403, [3, 6, 7]] | [20403, [3, 6, 7]] | Passed |
| two-line paragraph with loose first line | [12200, [3, 4]] | [12200, [3, 4]] | Passed |
| control layout | [529, [3]] | [529, [3]] | Passed |
| control layout | ['infeasible'] | ['infeasible'] | Passed |
SHA-256 / 6bba8cefb329267adfedc8af41dfb26b471c9b8f19ee4af6040e5c12660827ae
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
import math
from fractions import Fraction
N = 1
observations = []
def solve(x):
words, glue, width, lp = x
sp, st, sh = glue
n = len(words)
def rate(i, j):
gaps = j - i - 1
nat = sum(words[i:j]) + sp * gaps
short = width - nat
if j == n and short >= 0:
return 0, 1
if short > 0:
if st * gaps == 0:
return None
r = Fraction(short, st * gaps)
elif short < 0:
if -short > sh * gaps:
return None
r = Fraction(short, sh * gaps)
else:
r = Fraction(0)
b = min(10000, math.floor(100 * abs(r) ** 3 + Fraction(1, 2)))
if b > 100:
return None
fit = 0 if r < Fraction(-1, 2) else 1 if r <= Fraction(1, 2) else 2 if r <= 1 else 3
return b, fit
best = {(0, 1): (0, [])}
for j in range(1, n + 1):
for i in range(j):
rated = rate(i, j)
if rated is None:
continue
b, fit = rated
for (pos, pfit), (dem, brk) in sorted(best.items()):
if pos != i:
continue
d = dem + (lp + b) ** 2
if abs(fit - pfit) > 1:
d += 3000
key = (j, fit)
if key not in best or d < best[key][0]:
best[key] = (d, brk + [j])
finals = [v for (pos, f), v in best.items() if pos == n]
if not finals:
return ['infeasible']
d, brk = min(finals)
return [d, brk]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: badness tolerance', [[2, 7, 3, 5, 2, 5, 6], [2, 1, 0], 21, 50], ['infeasible']), ('regression: badness tolerance', [[5, 5, 6, 1], [3, 3, 0], 20, 50], ['infeasible']), ('partial-repair probe', [[7, 4, 5, 1, 7, 3], [2, 2, 0], 17, 1], [647603, [2, 5, 6]]), ('partial-repair probe', [[7, 5, 2, 3, 3], [3, 1, 0], 17, 10], [662200, [2, 5]]), ('tight then loose lines', [[3, 3, 3, 3, 3, 3, 3], [2, 2, 1], 11, 1], [20403, [3, 6, 7]]), ('two-line paragraph with loose first line', [[4, 5, 3, 6], [2, 2, 1], 14, 10], [12200, [3, 4]]), ('control layout', [[2, 3, 3], [3, 1, 2], 12, 10], [529, [3]]), ('control layout', [[2, 1, 7, 7], [2, 3, 0], 13, 10], ['infeasible'])], [('regression: badness tolerance', [[7, 4, 7, 2, 4, 2, 1], [2, 1, 0], 17, 10], ['infeasible']), ('regression: badness tolerance', [[1, 4, 7, 1, 7, 3, 2, 4], [2, 1, 0], 14, 10], ['infeasible']), ('partial-repair probe', [[7, 4, 7, 6], [2, 3, 1], 19, 10], [662200, [2, 4]]), ('partial-repair probe', [[4, 4, 6], [2, 2, 0], 14, 10], [662200, [2, 3]]), ('last line overfull', [[5, 5, 5], [1, 1, 0], 9, 10], ['infeasible']), ('tight then loose lines', [[3, 3, 3, 3, 3, 3, 3], [2, 2, 1], 11, 1], [20403, [3, 6, 7]]), ('control layout', [[2, 4, 4, 7, 4, 2, 5], [1, 3, 2], 16, 50], [8900, [4, 7]]), ('control layout', [[1, 2, 5, 4, 1, 6], [2, 1, 2], 18, 50], [5000, [4, 6]])], [('regression: badness tolerance', [[5, 4, 1, 2, 6, 1, 5], [3, 1, 1], 11, 50], ['infeasible']), ('regression: badness tolerance', [[6, 7, 6, 5], [3, 3, 0], 24, 50], ['infeasible']), ('partial-repair probe', [[6, 1, 2, 5, 6, 1, 3, 4], [3, 2, 0], 22, 10], [313316, [3, 6, 8]]), ('partial-repair probe', [[1, 3, 3], [3, 3, 0], 12, 50], [271669, [2, 3]]), ('single word paragraph', [[7], [2, 2, 1], 10, 10], [100, [1]]), ('last line overfull', [[5, 5, 5], [1, 1, 0], 9, 10], ['infeasible']), ('control layout', [[7, 6, 4, 1, 7, 1, 6], [1, 3, 2], 16, 1], [963, [2, 6, 7]]), ('control layout', [[7, 7, 7, 6], [2, 3, 1], 24, 10], [629, [3, 4]])], [('regression: badness tolerance', [[6, 7, 6, 5], [3, 3, 0], 24, 50], ['infeasible']), ('regression: badness tolerance', [[1, 2, 6, 2, 4, 4, 7, 1], [1, 2, 0], 9, 10], ['infeasible']), ('partial-repair probe', [[4, 5, 7, 1], [3, 2, 1], 15, 10], [127204, [2, 4]]), ('partial-repair probe', [[7, 4, 7, 6], [2, 3, 1], 19, 10], [662200, [2, 4]]), ('single word paragraph', [[7], [2, 2, 1], 10, 10], [100, [1]]), ('tight then loose lines', [[3, 3, 3, 3, 3, 3, 3], [2, 2, 1], 11, 1], [20403, [3, 6, 7]]), ('control layout', [[7, 7, 7, 1, 2], [3, 3, 2], 20, 10], [12200, [2, 5]]), ('control layout', [[7, 7, 4, 3, 1], [2, 2, 1], 21, 10], [629, [3, 5]])], [('regression: badness tolerance', [[4, 5, 3, 6, 2, 7], [1, 1, 0], 13, 1], ['infeasible']), ('regression: badness tolerance', [[3, 7, 1, 2, 7, 1], [2, 2, 0], 13, 10], ['infeasible']), ('partial-repair probe', [[5, 2, 6, 2, 2, 5, 5], [3, 1, 1], 12, 10], [1330400, [2, 4, 6, 7]]), ('partial-repair probe', [[5, 3, 5, 4, 7], [3, 1, 2], 13, 50], [750500, [2, 4, 5]]), ('two-line paragraph with loose first line', [[4, 5, 3, 6], [2, 2, 1], 14, 10], [12200, [3, 4]]), ('single word paragraph', [[7], [2, 2, 1], 10, 10], [100, [1]]), ('control layout', [[5, 7, 2], [2, 1, 2], 24, 50], [2500, [3]]), ('control layout', [[1, 1, 5, 3], [3, 2, 0], 14, 50], [5204, [3, 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 |
|---|---|---|---|
| regression: badness tolerance | ['infeasible'] | ['infeasible'] | Passed |
| regression: badness tolerance | ['infeasible'] | ['infeasible'] | Passed |
| partial-repair probe | ['infeasible'] | [647603, [2, 5, 6]] | Failed |
| partial-repair probe | ['infeasible'] | [662200, [2, 5]] | Failed |
| tight then loose lines | [20403, [3, 6, 7]] | [20403, [3, 6, 7]] | Passed |
| two-line paragraph with loose first line | [12200, [3, 4]] | [12200, [3, 4]] | Passed |
| control layout | [529, [3]] | [529, [3]] | Passed |
| control layout | ['infeasible'] | ['infeasible'] | Passed |
SHA-256 / a6e56a0106a06bf3711987850adc57315706082a4d261298cb8f251d538c82b7
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
import math
from fractions import Fraction
N = 1
observations = []
def solve(x):
words, glue, width, lp = x
sp, st, sh = glue
n = len(words)
def rate(i, j):
gaps = j - i - 1
nat = sum(words[i:j]) + sp * gaps
short = width - nat
if j == n and short >= 0:
return 0, 1
if short > 0:
if st * gaps == 0:
return None
r = Fraction(short, st * gaps)
elif short < 0:
if -short > sh * gaps:
return None
r = Fraction(short, sh * gaps)
else:
r = Fraction(0)
b = min(10000, math.floor(100 * abs(r) ** 3 + Fraction(1, 2)))
if b > 1000:
return None
fit = 0 if r < Fraction(-1, 2) else 1 if r <= Fraction(1, 2) else 2 if r <= 1 else 3
return b, fit
best = {(0, 1): (0, [])}
for j in range(1, n + 1):
for i in range(j):
rated = rate(i, j)
if rated is None:
continue
b, fit = rated
for (pos, pfit), (dem, brk) in sorted(best.items()):
if pos != i:
continue
d = dem + (lp + b) ** 2
if abs(fit - pfit) > 1:
d += 3000
key = (j, fit)
if key not in best or d < best[key][0]:
best[key] = (d, brk + [j])
finals = [v for (pos, f), v in best.items() if pos == n]
if not finals:
return ['infeasible']
d, brk = min(finals)
return [d, brk]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: badness tolerance', [[2, 7, 3, 5, 2, 5, 6], [2, 1, 0], 21, 50], ['infeasible']), ('regression: badness tolerance', [[5, 5, 6, 1], [3, 3, 0], 20, 50], ['infeasible']), ('partial-repair probe', [[7, 4, 5, 1, 7, 3], [2, 2, 0], 17, 1], [647603, [2, 5, 6]]), ('partial-repair probe', [[7, 5, 2, 3, 3], [3, 1, 0], 17, 10], [662200, [2, 5]]), ('tight then loose lines', [[3, 3, 3, 3, 3, 3, 3], [2, 2, 1], 11, 1], [20403, [3, 6, 7]]), ('two-line paragraph with loose first line', [[4, 5, 3, 6], [2, 2, 1], 14, 10], [12200, [3, 4]]), ('control layout', [[2, 3, 3], [3, 1, 2], 12, 10], [529, [3]]), ('control layout', [[2, 1, 7, 7], [2, 3, 0], 13, 10], ['infeasible'])], [('regression: badness tolerance', [[7, 4, 7, 2, 4, 2, 1], [2, 1, 0], 17, 10], ['infeasible']), ('regression: badness tolerance', [[1, 4, 7, 1, 7, 3, 2, 4], [2, 1, 0], 14, 10], ['infeasible']), ('partial-repair probe', [[7, 4, 7, 6], [2, 3, 1], 19, 10], [662200, [2, 4]]), ('partial-repair probe', [[4, 4, 6], [2, 2, 0], 14, 10], [662200, [2, 3]]), ('last line overfull', [[5, 5, 5], [1, 1, 0], 9, 10], ['infeasible']), ('tight then loose lines', [[3, 3, 3, 3, 3, 3, 3], [2, 2, 1], 11, 1], [20403, [3, 6, 7]]), ('control layout', [[2, 4, 4, 7, 4, 2, 5], [1, 3, 2], 16, 50], [8900, [4, 7]]), ('control layout', [[1, 2, 5, 4, 1, 6], [2, 1, 2], 18, 50], [5000, [4, 6]])], [('regression: badness tolerance', [[5, 4, 1, 2, 6, 1, 5], [3, 1, 1], 11, 50], ['infeasible']), ('regression: badness tolerance', [[6, 7, 6, 5], [3, 3, 0], 24, 50], ['infeasible']), ('partial-repair probe', [[6, 1, 2, 5, 6, 1, 3, 4], [3, 2, 0], 22, 10], [313316, [3, 6, 8]]), ('partial-repair probe', [[1, 3, 3], [3, 3, 0], 12, 50], [271669, [2, 3]]), ('single word paragraph', [[7], [2, 2, 1], 10, 10], [100, [1]]), ('last line overfull', [[5, 5, 5], [1, 1, 0], 9, 10], ['infeasible']), ('control layout', [[7, 6, 4, 1, 7, 1, 6], [1, 3, 2], 16, 1], [963, [2, 6, 7]]), ('control layout', [[7, 7, 7, 6], [2, 3, 1], 24, 10], [629, [3, 4]])], [('regression: badness tolerance', [[6, 7, 6, 5], [3, 3, 0], 24, 50], ['infeasible']), ('regression: badness tolerance', [[1, 2, 6, 2, 4, 4, 7, 1], [1, 2, 0], 9, 10], ['infeasible']), ('partial-repair probe', [[4, 5, 7, 1], [3, 2, 1], 15, 10], [127204, [2, 4]]), ('partial-repair probe', [[7, 4, 7, 6], [2, 3, 1], 19, 10], [662200, [2, 4]]), ('single word paragraph', [[7], [2, 2, 1], 10, 10], [100, [1]]), ('tight then loose lines', [[3, 3, 3, 3, 3, 3, 3], [2, 2, 1], 11, 1], [20403, [3, 6, 7]]), ('control layout', [[7, 7, 7, 1, 2], [3, 3, 2], 20, 10], [12200, [2, 5]]), ('control layout', [[7, 7, 4, 3, 1], [2, 2, 1], 21, 10], [629, [3, 5]])], [('regression: badness tolerance', [[4, 5, 3, 6, 2, 7], [1, 1, 0], 13, 1], ['infeasible']), ('regression: badness tolerance', [[3, 7, 1, 2, 7, 1], [2, 2, 0], 13, 10], ['infeasible']), ('partial-repair probe', [[5, 2, 6, 2, 2, 5, 5], [3, 1, 1], 12, 10], [1330400, [2, 4, 6, 7]]), ('partial-repair probe', [[5, 3, 5, 4, 7], [3, 1, 2], 13, 50], [750500, [2, 4, 5]]), ('two-line paragraph with loose first line', [[4, 5, 3, 6], [2, 2, 1], 14, 10], [12200, [3, 4]]), ('single word paragraph', [[7], [2, 2, 1], 10, 10], [100, [1]]), ('control layout', [[5, 7, 2], [2, 1, 2], 24, 50], [2500, [3]]), ('control layout', [[1, 1, 5, 3], [3, 2, 0], 14, 50], [5204, [3, 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 |
|---|---|---|---|
| regression: badness tolerance | ['infeasible'] | ['infeasible'] | Passed |
| regression: badness tolerance | ['infeasible'] | ['infeasible'] | Passed |
| partial-repair probe | [647603, [2, 5, 6]] | [647603, [2, 5, 6]] | Passed |
| partial-repair probe | [662200, [2, 5]] | [662200, [2, 5]] | Passed |
| tight then loose lines | [20403, [3, 6, 7]] | [20403, [3, 6, 7]] | Passed |
| two-line paragraph with loose first line | [12200, [3, 4]] | [12200, [3, 4]] | Passed |
| control layout | [529, [3]] | [529, [3]] | Passed |
| control layout | ['infeasible'] | ['infeasible'] | Passed |
SHA-256 / d237a371027b259710d38b89dde6f11d222b2da1b0927d2c601179ae3759f4a0
Verification & scope
A deterministic toy typesetting model with integer widths and a stipulated rule set; it does not claim conformance to any 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:49:48.859306+00:00.
Case digest / 40a7cb60464f83b13fb276c81d2f0eb4567d35c92c3c791128caefe259d6b7e7