broken_source:
"""Failure Map reference implementation. Python standard library only."""
import json
import math
from fractions import Fraction
N = 1
observations = []
def solve(x):
    now = x['now']
    window = sum(mg for t, mg in x['history'] if now - t < 1440)
    remaining = x['max_mg'] - window
    if x['history']:
        last = max(t for t, mg in x['history'])
        if now - last < x['min_interval']:
            return {'allowed': False, 'reason': 'TOO_SOON', 'window_mg': window, 'remaining_mg': remaining}
    if window + x['request_mg'] > x['max_mg']:
        return {'allowed': False, 'reason': 'DAILY_LIMIT', 'window_mg': window, 'remaining_mg': remaining}
    return {'allowed': True, 'reason': 'OK', 'window_mg': window, 'remaining_mg': remaining}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('dose exactly 24 hours old has expired',
   {'history': [[0, 1000], [600, 1000], [1200, 1000]],
    'now': 1440,
    'request_mg': 1000,
    'max_mg': 3100,
    'min_interval': 240},
   {'allowed': True, 'reason': 'OK', 'window_mg': 2000, 'remaining_mg': 1100}),
  ('rolling window spans midnight',
   {'history': [[1300, 1000], [1401, 1000]],
    'now': 1700,
    'request_mg': 1000,
    'max_mg': 2500,
    'min_interval': 240},
   {'allowed': False, 'reason': 'DAILY_LIMIT', 'window_mg': 2000, 'remaining_mg': 500}),
  ('unsorted history most recent dose',
   {'history': [[500, 500], [100, 500], [300, 500]],
    'now': 610,
    'request_mg': 500,
    'max_mg': 4000,
    'min_interval': 240},
   {'allowed': False, 'reason': 'TOO_SOON', 'window_mg': 1500, 'remaining_mg': 2500}),
  ('interval exactly met',
   {'history': [[100, 650]], 'now': 340, 'request_mg': 650, 'max_mg': 3000, 'min_interval': 240},
   {'allowed': True, 'reason': 'OK', 'window_mg': 650, 'remaining_mg': 2350}),
  ('interval shortly before minimum',
   {'history': [[100, 650]], 'now': 335, 'request_mg': 650, 'max_mg': 3000, 'min_interval': 240},
   {'allowed': False, 'reason': 'TOO_SOON', 'window_mg': 650, 'remaining_mg': 2350}),
  ('request reaches maximum exactly',
   {'history': [[0, 1000], [300, 1000]], 'now': 600, 'request_mg': 1100, 'max_mg': 3100, 'min_interval': 240},
   {'allowed': True, 'reason': 'OK', 'window_mg': 2000, 'remaining_mg': 1100}),
  ('over limit remaining clamped',
   {'history': [[0, 2000], [400, 1500]], 'now': 900, 'request_mg': 500, 'max_mg': 3000, 'min_interval': 240},
   {'allowed': False, 'reason': 'DAILY_LIMIT', 'window_mg': 3500, 'remaining_mg': 0}),
  ('first dose control',
   {'history': [], 'now': 50, 'request_mg': 650, 'max_mg': 3000, 'min_interval': 240},
   {'allowed': True, 'reason': 'OK', 'window_mg': 0, 'remaining_mg': 3000})],
 [('dose exactly 24 hours old has expired',
   {'history': [[0, 1000], [600, 1000], [1200, 1000]],
    'now': 1440,
    'request_mg': 1000,
    'max_mg': 3200,
    'min_interval': 240},
   {'allowed': True, 'reason': 'OK', 'window_mg': 2000, 'remaining_mg': 1200}),
  ('rolling window spans midnight',
   {'history': [[1300, 1000], [1402, 1000]],
    'now': 1700,
    'request_mg': 1000,
    'max_mg': 2500,
    'min_interval': 240},
   {'allowed': False, 'reason': 'DAILY_LIMIT', 'window_mg': 2000, 'remaining_mg': 500}),
  ('unsorted history most recent dose',
   {'history': [[500, 500], [100, 500], [300, 500]],
    'now': 620,
    'request_mg': 500,
    'max_mg': 4000,
    'min_interval': 240},
   {'allowed': False, 'reason': 'TOO_SOON', 'window_mg': 1500, 'remaining_mg': 2500}),
  ('interval exactly met',
   {'history': [[100, 650]], 'now': 400, 'request_mg': 650, 'max_mg': 3000, 'min_interval': 300},
   {'allowed': True, 'reason': 'OK', 'window_mg': 650, 'remaining_mg': 2350}),
  ('interval shortly before minimum',
   {'history': [[100, 650]], 'now': 330, 'request_mg': 650, 'max_mg': 3000, 'min_interval': 240},
   {'allowed': False, 'reason': 'TOO_SOON', 'window_mg': 650, 'remaining_mg': 2350}),
  ('request reaches maximum exactly',
   {'history': [[0, 1000], [300, 1000]], 'now': 600, 'request_mg': 1200, 'max_mg': 3200, 'min_interval': 240},
   {'allowed': True, 'reason': 'OK', 'window_mg': 2000, 'remaining_mg': 1200}),
  ('over limit remaining clamped',
   {'history': [[0, 2000], [400, 1500]], 'now': 900, 'request_mg': 500, 'max_mg': 3050, 'min_interval': 240},
   {'allowed': False, 'reason': 'DAILY_LIMIT', 'window_mg': 3500, 'remaining_mg': 0}),
  ('first dose control',
   {'history': [], 'now': 100, 'request_mg': 650, 'max_mg': 3000, 'min_interval': 240},
   {'allowed': True, 'reason': 'OK', 'window_mg': 0, 'remaining_mg': 3000})],
 [('dose exactly 24 hours old has expired',
   {'history': [[0, 1000], [600, 1000], [1200, 1000]],
    'now': 1440,
    'request_mg': 1000,
    'max_mg': 3300,
    'min_interval': 240},
   {'allowed': True, 'reason': 'OK', 'window_mg': 2000, 'remaining_mg': 1300}),
  ('rolling window spans midnight',
   {'history': [[1300, 1000], [1403, 1000]],
    'now': 1700,
    'request_mg': 1000,
    'max_mg': 2500,
    'min_interval': 240},
   {'allowed': False, 'reason': 'DAILY_LIMIT', 'window_mg': 2000, 'remaining_mg': 500}),
  ('unsorted history most recent dose',
   {'history': [[500, 500], [100, 500], [300, 500]],
    'now': 630,
    'request_mg': 500,
    'max_mg': 4000,
    'min_interval': 240},
   {'allowed': False, 'reason': 'TOO_SOON', 'window_mg': 1500, 'remaining_mg': 2500}),
  ('interval exactly met',
   {'history': [[100, 650]], 'now': 460, 'request_mg': 650, 'max_mg': 3000, 'min_interval': 360},
   {'allowed': True, 'reason': 'OK', 'window_mg': 650, 'remaining_mg': 2350}),
  ('interval shortly before minimum',
   {'history': [[100, 650]], 'now': 325, 'request_mg': 650, 'max_mg': 3000, 'min_interval': 240},
   {'allowed': False, 'reason': 'TOO_SOON', 'window_mg': 650, 'remaining_mg': 2350}),
  ('request reaches maximum exactly',
   {'history': [[0, 1000], [300, 1000]], 'now': 600, 'request_mg': 1300, 'max_mg': 3300, 'min_interval': 240},
   {'allowed': True, 'reason': 'OK', 'window_mg': 2000, 'remaining_mg': 1300}),
  ('over limit remaining clamped',
   {'history': [[0, 2000], [400, 1500]], 'now': 900, 'request_mg': 500, 'max_mg': 3100, 'min_interval': 240},
   {'allowed': False, 'reason': 'DAILY_LIMIT', 'window_mg': 3500, 'remaining_mg': 0}),
  ('first dose control',
   {'history': [], 'now': 150, 'request_mg': 650, 'max_mg': 3000, 'min_interval': 240},
   {'allowed': True, 'reason': 'OK', 'window_mg': 0, 'remaining_mg': 3000})],
 [('dose exactly 24 hours old has expired',
   {'history': [[0, 1000], [600, 1000], [1200, 1000]],
    'now': 1440,
    'request_mg': 1000,
    'max_mg': 3400,
    'min_interval': 240},
   {'allowed': True, 'reason': 'OK', 'window_mg': 2000, 'remaining_mg': 1400}),
  ('rolling window spans midnight',
   {'history': [[1300, 1000], [1404, 1000]],
    'now': 1700,
    'request_mg': 1000,
    'max_mg': 2500,
    'min_interval': 240},
   {'allowed': False, 'reason': 'DAILY_LIMIT', 'window_mg': 2000, 'remaining_mg': 500}),
  ('unsorted history most recent dose',
   {'history': [[500, 500], [100, 500], [300, 500]],
    'now': 640,
    'request_mg': 500,
    'max_mg': 4000,
    'min_interval': 240},
   {'allowed': False, 'reason': 'TOO_SOON', 'window_mg': 1500, 'remaining_mg': 2500}),
  ('interval exactly met',
   {'history': [[100, 650]], 'now': 520, 'request_mg': 650, 'max_mg': 3000, 'min_interval': 420},
   {'allowed': True, 'reason': 'OK', 'window_mg': 650, 'remaining_mg': 2350}),
  ('interval shortly before minimum',
   {'history': [[100, 650]], 'now': 320, 'request_mg': 650, 'max_mg': 3000, 'min_interval': 240},
   {'allowed': False, 'reason': 'TOO_SOON', 'window_mg': 650, 'remaining_mg': 2350}),
  ('request reaches maximum exactly',
   {'history': [[0, 1000], [300, 1000]], 'now': 600, 'request_mg': 1400, 'max_mg': 3400, 'min_interval': 240},
   {'allowed': True, 'reason': 'OK', 'window_mg': 2000, 'remaining_mg': 1400}),
  ('over limit remaining clamped',
   {'history': [[0, 2000], [400, 1500]], 'now': 900, 'request_mg': 500, 'max_mg': 3150, 'min_interval': 240},
   {'allowed': False, 'reason': 'DAILY_LIMIT', 'window_mg': 3500, 'remaining_mg': 0}),
  ('first dose control',
   {'history': [], 'now': 200, 'request_mg': 650, 'max_mg': 3000, 'min_interval': 240},
   {'allowed': True, 'reason': 'OK', 'window_mg': 0, 'remaining_mg': 3000})],
 [('dose exactly 24 hours old has expired',
   {'history': [[0, 1000], [600, 1000], [1200, 1000]],
    'now': 1440,
    'request_mg': 1000,
    'max_mg': 3500,
    'min_interval': 240},
   {'allowed': True, 'reason': 'OK', 'window_mg': 2000, 'remaining_mg': 1500}),
  ('rolling window spans midnight',
   {'history': [[1300, 1000], [1405, 1000]],
    'now': 1700,
    'request_mg': 1000,
    'max_mg': 2500,
    'min_interval': 240},
   {'allowed': False, 'reason': 'DAILY_LIMIT', 'window_mg': 2000, 'remaining_mg': 500}),
  ('unsorted history most recent dose',
   {'history': [[500, 500], [100, 500], [300, 500]],
    'now': 650,
    'request_mg': 500,
    'max_mg': 4000,
    'min_interval': 240},
   {'allowed': False, 'reason': 'TOO_SOON', 'window_mg': 1500, 'remaining_mg': 2500}),
  ('interval exactly met',
   {'history': [[100, 650]], 'now': 580, 'request_mg': 650, 'max_mg': 3000, 'min_interval': 480},
   {'allowed': True, 'reason': 'OK', 'window_mg': 650, 'remaining_mg': 2350}),
  ('interval shortly before minimum',
   {'history': [[100, 650]], 'now': 315, 'request_mg': 650, 'max_mg': 3000, 'min_interval': 240},
   {'allowed': False, 'reason': 'TOO_SOON', 'window_mg': 650, 'remaining_mg': 2350}),
  ('request reaches maximum exactly',
   {'history': [[0, 1000], [300, 1000]], 'now': 600, 'request_mg': 1500, 'max_mg': 3500, 'min_interval': 240},
   {'allowed': True, 'reason': 'OK', 'window_mg': 2000, 'remaining_mg': 1500}),
  ('over limit remaining clamped',
   {'history': [[0, 2000], [400, 1500]], 'now': 900, 'request_mg': 500, 'max_mg': 3200, 'min_interval': 240},
   {'allowed': False, 'reason': 'DAILY_LIMIT', 'window_mg': 3500, 'remaining_mg': 0}),
  ('first dose control',
   {'history': [], 'now': 250, 'request_mg': 650, 'max_mg': 3000, 'min_interval': 240},
   {'allowed': True, 'reason': 'OK', 'window_mg': 0, 'remaining_mg': 3000})]]
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)

source:
"""Failure Map reference implementation. Python standard library only."""
import json
import math
from fractions import Fraction
N = 1
observations = []
def solve(x):
    now = x['now']
    window = sum(mg for t, mg in x['history'] if now - t < 1440)
    remaining = abs(x['max_mg'] - window)
    if x['history']:
        last = max(t for t, mg in x['history'])
        if now - last < x['min_interval']:
            return {'allowed': False, 'reason': 'TOO_SOON', 'window_mg': window, 'remaining_mg': remaining}
    if window + x['request_mg'] > x['max_mg']:
        return {'allowed': False, 'reason': 'DAILY_LIMIT', 'window_mg': window, 'remaining_mg': remaining}
    return {'allowed': True, 'reason': 'OK', 'window_mg': window, 'remaining_mg': remaining}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('dose exactly 24 hours old has expired',
   {'history': [[0, 1000], [600, 1000], [1200, 1000]],
    'now': 1440,
    'request_mg': 1000,
    'max_mg': 3100,
    'min_interval': 240},
   {'allowed': True, 'reason': 'OK', 'window_mg': 2000, 'remaining_mg': 1100}),
  ('rolling window spans midnight',
   {'history': [[1300, 1000], [1401, 1000]],
    'now': 1700,
    'request_mg': 1000,
    'max_mg': 2500,
    'min_interval': 240},
   {'allowed': False, 'reason': 'DAILY_LIMIT', 'window_mg': 2000, 'remaining_mg': 500}),
  ('unsorted history most recent dose',
   {'history': [[500, 500], [100, 500], [300, 500]],
    'now': 610,
    'request_mg': 500,
    'max_mg': 4000,
    'min_interval': 240},
   {'allowed': False, 'reason': 'TOO_SOON', 'window_mg': 1500, 'remaining_mg': 2500}),
  ('interval exactly met',
   {'history': [[100, 650]], 'now': 340, 'request_mg': 650, 'max_mg': 3000, 'min_interval': 240},
   {'allowed': True, 'reason': 'OK', 'window_mg': 650, 'remaining_mg': 2350}),
  ('interval shortly before minimum',
   {'history': [[100, 650]], 'now': 335, 'request_mg': 650, 'max_mg': 3000, 'min_interval': 240},
   {'allowed': False, 'reason': 'TOO_SOON', 'window_mg': 650, 'remaining_mg': 2350}),
  ('request reaches maximum exactly',
   {'history': [[0, 1000], [300, 1000]], 'now': 600, 'request_mg': 1100, 'max_mg': 3100, 'min_interval': 240},
   {'allowed': True, 'reason': 'OK', 'window_mg': 2000, 'remaining_mg': 1100}),
  ('over limit remaining clamped',
   {'history': [[0, 2000], [400, 1500]], 'now': 900, 'request_mg': 500, 'max_mg': 3000, 'min_interval': 240},
   {'allowed': False, 'reason': 'DAILY_LIMIT', 'window_mg': 3500, 'remaining_mg': 0}),
  ('first dose control',
   {'history': [], 'now': 50, 'request_mg': 650, 'max_mg': 3000, 'min_interval': 240},
   {'allowed': True, 'reason': 'OK', 'window_mg': 0, 'remaining_mg': 3000})],
 [('dose exactly 24 hours old has expired',
   {'history': [[0, 1000], [600, 1000], [1200, 1000]],
    'now': 1440,
    'request_mg': 1000,
    'max_mg': 3200,
    'min_interval': 240},
   {'allowed': True, 'reason': 'OK', 'window_mg': 2000, 'remaining_mg': 1200}),
  ('rolling window spans midnight',
   {'history': [[1300, 1000], [1402, 1000]],
    'now': 1700,
    'request_mg': 1000,
    'max_mg': 2500,
    'min_interval': 240},
   {'allowed': False, 'reason': 'DAILY_LIMIT', 'window_mg': 2000, 'remaining_mg': 500}),
  ('unsorted history most recent dose',
   {'history': [[500, 500], [100, 500], [300, 500]],
    'now': 620,
    'request_mg': 500,
    'max_mg': 4000,
    'min_interval': 240},
   {'allowed': False, 'reason': 'TOO_SOON', 'window_mg': 1500, 'remaining_mg': 2500}),
  ('interval exactly met',
   {'history': [[100, 650]], 'now': 400, 'request_mg': 650, 'max_mg': 3000, 'min_interval': 300},
   {'allowed': True, 'reason': 'OK', 'window_mg': 650, 'remaining_mg': 2350}),
  ('interval shortly before minimum',
   {'history': [[100, 650]], 'now': 330, 'request_mg': 650, 'max_mg': 3000, 'min_interval': 240},
   {'allowed': False, 'reason': 'TOO_SOON', 'window_mg': 650, 'remaining_mg': 2350}),
  ('request reaches maximum exactly',
   {'history': [[0, 1000], [300, 1000]], 'now': 600, 'request_mg': 1200, 'max_mg': 3200, 'min_interval': 240},
   {'allowed': True, 'reason': 'OK', 'window_mg': 2000, 'remaining_mg': 1200}),
  ('over limit remaining clamped',
   {'history': [[0, 2000], [400, 1500]], 'now': 900, 'request_mg': 500, 'max_mg': 3050, 'min_interval': 240},
   {'allowed': False, 'reason': 'DAILY_LIMIT', 'window_mg': 3500, 'remaining_mg': 0}),
  ('first dose control',
   {'history': [], 'now': 100, 'request_mg': 650, 'max_mg': 3000, 'min_interval': 240},
   {'allowed': True, 'reason': 'OK', 'window_mg': 0, 'remaining_mg': 3000})],
 [('dose exactly 24 hours old has expired',
   {'history': [[0, 1000], [600, 1000], [1200, 1000]],
    'now': 1440,
    'request_mg': 1000,
    'max_mg': 3300,
    'min_interval': 240},
   {'allowed': True, 'reason': 'OK', 'window_mg': 2000, 'remaining_mg': 1300}),
  ('rolling window spans midnight',
   {'history': [[1300, 1000], [1403, 1000]],
    'now': 1700,
    'request_mg': 1000,
    'max_mg': 2500,
    'min_interval': 240},
   {'allowed': False, 'reason': 'DAILY_LIMIT', 'window_mg': 2000, 'remaining_mg': 500}),
  ('unsorted history most recent dose',
   {'history': [[500, 500], [100, 500], [300, 500]],
    'now': 630,
    'request_mg': 500,
    'max_mg': 4000,
    'min_interval': 240},
   {'allowed': False, 'reason': 'TOO_SOON', 'window_mg': 1500, 'remaining_mg': 2500}),
  ('interval exactly met',
   {'history': [[100, 650]], 'now': 460, 'request_mg': 650, 'max_mg': 3000, 'min_interval': 360},
   {'allowed': True, 'reason': 'OK', 'window_mg': 650, 'remaining_mg': 2350}),
  ('interval shortly before minimum',
   {'history': [[100, 650]], 'now': 325, 'request_mg': 650, 'max_mg': 3000, 'min_interval': 240},
   {'allowed': False, 'reason': 'TOO_SOON', 'window_mg': 650, 'remaining_mg': 2350}),
  ('request reaches maximum exactly',
   {'history': [[0, 1000], [300, 1000]], 'now': 600, 'request_mg': 1300, 'max_mg': 3300, 'min_interval': 240},
   {'allowed': True, 'reason': 'OK', 'window_mg': 2000, 'remaining_mg': 1300}),
  ('over limit remaining clamped',
   {'history': [[0, 2000], [400, 1500]], 'now': 900, 'request_mg': 500, 'max_mg': 3100, 'min_interval': 240},
   {'allowed': False, 'reason': 'DAILY_LIMIT', 'window_mg': 3500, 'remaining_mg': 0}),
  ('first dose control',
   {'history': [], 'now': 150, 'request_mg': 650, 'max_mg': 3000, 'min_interval': 240},
   {'allowed': True, 'reason': 'OK', 'window_mg': 0, 'remaining_mg': 3000})],
 [('dose exactly 24 hours old has expired',
   {'history': [[0, 1000], [600, 1000], [1200, 1000]],
    'now': 1440,
    'request_mg': 1000,
    'max_mg': 3400,
    'min_interval': 240},
   {'allowed': True, 'reason': 'OK', 'window_mg': 2000, 'remaining_mg': 1400}),
  ('rolling window spans midnight',
   {'history': [[1300, 1000], [1404, 1000]],
    'now': 1700,
    'request_mg': 1000,
    'max_mg': 2500,
    'min_interval': 240},
   {'allowed': False, 'reason': 'DAILY_LIMIT', 'window_mg': 2000, 'remaining_mg': 500}),
  ('unsorted history most recent dose',
   {'history': [[500, 500], [100, 500], [300, 500]],
    'now': 640,
    'request_mg': 500,
    'max_mg': 4000,
    'min_interval': 240},
   {'allowed': False, 'reason': 'TOO_SOON', 'window_mg': 1500, 'remaining_mg': 2500}),
  ('interval exactly met',
   {'history': [[100, 650]], 'now': 520, 'request_mg': 650, 'max_mg': 3000, 'min_interval': 420},
   {'allowed': True, 'reason': 'OK', 'window_mg': 650, 'remaining_mg': 2350}),
  ('interval shortly before minimum',
   {'history': [[100, 650]], 'now': 320, 'request_mg': 650, 'max_mg': 3000, 'min_interval': 240},
   {'allowed': False, 'reason': 'TOO_SOON', 'window_mg': 650, 'remaining_mg': 2350}),
  ('request reaches maximum exactly',
   {'history': [[0, 1000], [300, 1000]], 'now': 600, 'request_mg': 1400, 'max_mg': 3400, 'min_interval': 240},
   {'allowed': True, 'reason': 'OK', 'window_mg': 2000, 'remaining_mg': 1400}),
  ('over limit remaining clamped',
   {'history': [[0, 2000], [400, 1500]], 'now': 900, 'request_mg': 500, 'max_mg': 3150, 'min_interval': 240},
   {'allowed': False, 'reason': 'DAILY_LIMIT', 'window_mg': 3500, 'remaining_mg': 0}),
  ('first dose control',
   {'history': [], 'now': 200, 'request_mg': 650, 'max_mg': 3000, 'min_interval': 240},
   {'allowed': True, 'reason': 'OK', 'window_mg': 0, 'remaining_mg': 3000})],
 [('dose exactly 24 hours old has expired',
   {'history': [[0, 1000], [600, 1000], [1200, 1000]],
    'now': 1440,
    'request_mg': 1000,
    'max_mg': 3500,
    'min_interval': 240},
   {'allowed': True, 'reason': 'OK', 'window_mg': 2000, 'remaining_mg': 1500}),
  ('rolling window spans midnight',
   {'history': [[1300, 1000], [1405, 1000]],
    'now': 1700,
    'request_mg': 1000,
    'max_mg': 2500,
    'min_interval': 240},
   {'allowed': False, 'reason': 'DAILY_LIMIT', 'window_mg': 2000, 'remaining_mg': 500}),
  ('unsorted history most recent dose',
   {'history': [[500, 500], [100, 500], [300, 500]],
    'now': 650,
    'request_mg': 500,
    'max_mg': 4000,
    'min_interval': 240},
   {'allowed': False, 'reason': 'TOO_SOON', 'window_mg': 1500, 'remaining_mg': 2500}),
  ('interval exactly met',
   {'history': [[100, 650]], 'now': 580, 'request_mg': 650, 'max_mg': 3000, 'min_interval': 480},
   {'allowed': True, 'reason': 'OK', 'window_mg': 650, 'remaining_mg': 2350}),
  ('interval shortly before minimum',
   {'history': [[100, 650]], 'now': 315, 'request_mg': 650, 'max_mg': 3000, 'min_interval': 240},
   {'allowed': False, 'reason': 'TOO_SOON', 'window_mg': 650, 'remaining_mg': 2350}),
  ('request reaches maximum exactly',
   {'history': [[0, 1000], [300, 1000]], 'now': 600, 'request_mg': 1500, 'max_mg': 3500, 'min_interval': 240},
   {'allowed': True, 'reason': 'OK', 'window_mg': 2000, 'remaining_mg': 1500}),
  ('over limit remaining clamped',
   {'history': [[0, 2000], [400, 1500]], 'now': 900, 'request_mg': 500, 'max_mg': 3200, 'min_interval': 240},
   {'allowed': False, 'reason': 'DAILY_LIMIT', 'window_mg': 3500, 'remaining_mg': 0}),
  ('first dose control',
   {'history': [], 'now': 250, 'request_mg': 650, 'max_mg': 3000, 'min_interval': 240},
   {'allowed': True, 'reason': 'OK', 'window_mg': 0, 'remaining_mg': 3000})]]
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)

prompt:
Over-limit patients are shown a negative remaining allowance.

Input {history [[t_min, mg], ...] (any order), now, request_mg, max_mg, min_interval}. Doses with now - t < 1440 minutes count toward the rolling window (a dose exactly 24 h old has expired). The request is refused with TOO_SOON when now minus the most recent dose time is below min_interval, otherwise with DAILY_LIMIT when window + request exceeds max_mg; else OK. Return {allowed, reason, window_mg, remaining_mg = max(0, max_mg - window)}.

split: open-access


broken_source:
"""Failure Map reference implementation. Python standard library only."""
import json
import math
from fractions import Fraction
N = 1
observations = []
def solve(x):
    dose = Fraction(str(x['start_mg']))
    t = Fraction(str(x['tablet_mg']))
    out = []
    for day in range(x['days']):
        if day % x['every_days'] == 0 and dose > 0:
            new = dose * (100 - x['step_pct']) / 100
            new = math.floor(new / t + Fraction(1, 2)) * t
            if new >= dose:
                new = dose - t
            dose = new if new >= Fraction(str(x['min_mg'])) else Fraction(0)
        out.append(float(dose))
    return out
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 11},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 4},
   [50.0, 50.0, 45.0, 45.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 2, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 10.0, 7.5, 7.5, 5.0, 5.0, 2.5, 2.5]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 6},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 10, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 0.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 8},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0])],
 [('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 12},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0, 25.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 5},
   [50.0, 50.0, 45.0, 45.0, 40.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 1, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 7.5, 5.0, 2.5, 0.0, 0.0, 0.0, 0.0]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 7},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 5, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 5.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 9},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0, 8.0])],
 [('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 13},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0, 25.0, 20.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 6},
   [50.0, 50.0, 45.0, 45.0, 40.0, 40.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 2, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 10.0, 7.5, 7.5, 5.0, 5.0, 2.5, 2.5]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 8},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 10, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 0.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 10},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0, 8.0, 8.0])],
 [('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 14},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0, 25.0, 20.0, 20.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 7},
   [50.0, 50.0, 45.0, 45.0, 40.0, 40.0, 35.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 1, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 7.5, 5.0, 2.5, 0.0, 0.0, 0.0, 0.0]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 9},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0, 0.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 5, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 5.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 11},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0, 8.0, 8.0, 6.0])],
 [('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 15},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0, 25.0, 20.0, 20.0, 20.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 8},
   [50.0, 50.0, 45.0, 45.0, 40.0, 40.0, 35.0, 35.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 2, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 10.0, 7.5, 7.5, 5.0, 5.0, 2.5, 2.5]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 10},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 10, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 0.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 12},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0, 8.0, 8.0, 6.0, 6.0])]]
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)

meaning: Fixture-count band, not measured model difficulty

deps: stdlib

source:
"""Failure Map reference implementation. Python standard library only."""
import json
import math
from fractions import Fraction
N = 1
observations = []
def solve(x):
    dose = Fraction(str(x['start_mg']))
    t = Fraction(str(x['tablet_mg']))
    out = []
    for day in range(x['days']):
        if (day + 1) % x['every_days'] == 0 and dose > 0:
            new = dose * (100 - x['step_pct']) / 100
            new = math.floor(new / t + Fraction(1, 2)) * t
            if new >= dose:
                new = dose - t
            dose = new if new >= Fraction(str(x['min_mg'])) else Fraction(0)
        out.append(float(dose))
    return out
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 11},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 4},
   [50.0, 50.0, 45.0, 45.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 2, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 10.0, 7.5, 7.5, 5.0, 5.0, 2.5, 2.5]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 6},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 10, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 0.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 8},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0])],
 [('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 12},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0, 25.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 5},
   [50.0, 50.0, 45.0, 45.0, 40.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 1, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 7.5, 5.0, 2.5, 0.0, 0.0, 0.0, 0.0]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 7},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 5, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 5.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 9},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0, 8.0])],
 [('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 13},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0, 25.0, 20.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 6},
   [50.0, 50.0, 45.0, 45.0, 40.0, 40.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 2, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 10.0, 7.5, 7.5, 5.0, 5.0, 2.5, 2.5]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 8},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 10, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 0.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 10},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0, 8.0, 8.0])],
 [('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 14},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0, 25.0, 20.0, 20.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 7},
   [50.0, 50.0, 45.0, 45.0, 40.0, 40.0, 35.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 1, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 7.5, 5.0, 2.5, 0.0, 0.0, 0.0, 0.0]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 9},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0, 0.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 5, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 5.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 11},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0, 8.0, 8.0, 6.0])],
 [('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 15},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0, 25.0, 20.0, 20.0, 20.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 8},
   [50.0, 50.0, 45.0, 45.0, 40.0, 40.0, 35.0, 35.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 2, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 10.0, 7.5, 7.5, 5.0, 5.0, 2.5, 2.5]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 10},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 10, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 0.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 12},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0, 8.0, 8.0, 6.0, 6.0])]]
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)

prompt:
The first reduction happens on day 0, so the starting dose is never given.

Input {start_mg, step_pct, every_days, min_mg, tablet_mg, days}. Day 0 uses start_mg. On each day d > 0 with d % every_days == 0 (while the dose is non-zero), the dose becomes current*(100-step_pct)/100 rounded half-up to a tablet_mg multiple; if that does not lower the dose, it is lowered by one tablet_mg instead; a result below min_mg stops the taper (0). Return the list of daily doses for days days.

split: open-access


broken_source:
"""Failure Map reference implementation. Python standard library only."""
import json
import math
from fractions import Fraction
N = 1
observations = []
def solve(x):
    dose = Fraction(str(x['start_mg']))
    t = Fraction(str(x['tablet_mg']))
    out = []
    for day in range(x['days']):
        if day > 0 and day % x['every_days'] == 0 and dose > 0:
            new = dose - Fraction(str(x['start_mg'])) * x['step_pct'] / 100
            new = math.floor(new / t + Fraction(1, 2)) * t
            if new >= dose:
                new = dose - t
            dose = new if new >= Fraction(str(x['min_mg'])) else Fraction(0)
        out.append(float(dose))
    return out
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 11},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 4},
   [50.0, 50.0, 45.0, 45.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 2, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 10.0, 7.5, 7.5, 5.0, 5.0, 2.5, 2.5]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 6},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 10, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 0.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 8},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0])],
 [('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 12},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0, 25.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 5},
   [50.0, 50.0, 45.0, 45.0, 40.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 1, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 7.5, 5.0, 2.5, 0.0, 0.0, 0.0, 0.0]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 7},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 5, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 5.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 9},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0, 8.0])],
 [('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 13},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0, 25.0, 20.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 6},
   [50.0, 50.0, 45.0, 45.0, 40.0, 40.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 2, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 10.0, 7.5, 7.5, 5.0, 5.0, 2.5, 2.5]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 8},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 10, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 0.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 10},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0, 8.0, 8.0])],
 [('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 14},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0, 25.0, 20.0, 20.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 7},
   [50.0, 50.0, 45.0, 45.0, 40.0, 40.0, 35.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 1, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 7.5, 5.0, 2.5, 0.0, 0.0, 0.0, 0.0]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 9},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0, 0.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 5, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 5.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 11},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0, 8.0, 8.0, 6.0])],
 [('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 15},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0, 25.0, 20.0, 20.0, 20.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 8},
   [50.0, 50.0, 45.0, 45.0, 40.0, 40.0, 35.0, 35.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 2, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 10.0, 7.5, 7.5, 5.0, 5.0, 2.5, 2.5]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 10},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 10, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 0.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 12},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0, 8.0, 8.0, 6.0, 6.0])]]
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)

source:
"""Failure Map reference implementation. Python standard library only."""
import json
import math
from fractions import Fraction
N = 1
observations = []
def solve(x):
    dose = Fraction(str(x['start_mg']))
    t = Fraction(str(x['tablet_mg']))
    out = []
    for day in range(x['days']):
        if day > 0 and day % x['every_days'] == 0 and dose > 0:
            new = dose * (100 - x['step_pct']) // 100
            new = math.floor(new / t + Fraction(1, 2)) * t
            if new >= dose:
                new = dose - t
            dose = new if new >= Fraction(str(x['min_mg'])) else Fraction(0)
        out.append(float(dose))
    return out
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 11},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 4},
   [50.0, 50.0, 45.0, 45.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 2, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 10.0, 7.5, 7.5, 5.0, 5.0, 2.5, 2.5]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 6},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 10, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 0.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 8},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0])],
 [('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 12},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0, 25.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 5},
   [50.0, 50.0, 45.0, 45.0, 40.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 1, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 7.5, 5.0, 2.5, 0.0, 0.0, 0.0, 0.0]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 7},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 5, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 5.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 9},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0, 8.0])],
 [('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 13},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0, 25.0, 20.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 6},
   [50.0, 50.0, 45.0, 45.0, 40.0, 40.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 2, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 10.0, 7.5, 7.5, 5.0, 5.0, 2.5, 2.5]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 8},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 10, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 0.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 10},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0, 8.0, 8.0])],
 [('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 14},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0, 25.0, 20.0, 20.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 7},
   [50.0, 50.0, 45.0, 45.0, 40.0, 40.0, 35.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 1, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 7.5, 5.0, 2.5, 0.0, 0.0, 0.0, 0.0]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 9},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0, 0.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 5, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 5.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 11},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0, 8.0, 8.0, 6.0])],
 [('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 15},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0, 25.0, 20.0, 20.0, 20.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 8},
   [50.0, 50.0, 45.0, 45.0, 40.0, 40.0, 35.0, 35.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 2, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 10.0, 7.5, 7.5, 5.0, 5.0, 2.5, 2.5]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 10},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 10, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 0.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 12},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0, 8.0, 8.0, 6.0, 6.0])]]
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)

prompt:
Later taper steps drop by the same number of milligrams as the first step.

Input {start_mg, step_pct, every_days, min_mg, tablet_mg, days}. Day 0 uses start_mg. On each day d > 0 with d % every_days == 0 (while the dose is non-zero), the dose becomes current*(100-step_pct)/100 rounded half-up to a tablet_mg multiple; if that does not lower the dose, it is lowered by one tablet_mg instead; a result below min_mg stops the taper (0). Return the list of daily doses for days days.

split: open-access


broken_source:
"""Failure Map reference implementation. Python standard library only."""
import json
import math
from fractions import Fraction
N = 1
observations = []
def solve(x):
    dose = Fraction(str(x['start_mg']))
    t = Fraction(str(x['tablet_mg']))
    out = []
    for day in range(x['days']):
        if day > 0 and day % x['every_days'] == 0 and dose > 0:
            new = dose * (100 - x['step_pct']) / 100
            new = math.floor(new / t) * t
            if new >= dose:
                new = dose - t
            dose = new if new >= Fraction(str(x['min_mg'])) else Fraction(0)
        out.append(float(dose))
    return out
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 11},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 4},
   [50.0, 50.0, 45.0, 45.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 2, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 10.0, 7.5, 7.5, 5.0, 5.0, 2.5, 2.5]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 6},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 10, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 0.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 8},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0])],
 [('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 12},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0, 25.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 5},
   [50.0, 50.0, 45.0, 45.0, 40.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 1, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 7.5, 5.0, 2.5, 0.0, 0.0, 0.0, 0.0]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 7},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 5, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 5.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 9},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0, 8.0])],
 [('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 13},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0, 25.0, 20.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 6},
   [50.0, 50.0, 45.0, 45.0, 40.0, 40.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 2, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 10.0, 7.5, 7.5, 5.0, 5.0, 2.5, 2.5]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 8},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 10, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 0.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 10},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0, 8.0, 8.0])],
 [('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 14},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0, 25.0, 20.0, 20.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 7},
   [50.0, 50.0, 45.0, 45.0, 40.0, 40.0, 35.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 1, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 7.5, 5.0, 2.5, 0.0, 0.0, 0.0, 0.0]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 9},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0, 0.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 5, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 5.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 11},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0, 8.0, 8.0, 6.0])],
 [('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 15},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0, 25.0, 20.0, 20.0, 20.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 8},
   [50.0, 50.0, 45.0, 45.0, 40.0, 40.0, 35.0, 35.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 2, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 10.0, 7.5, 7.5, 5.0, 5.0, 2.5, 2.5]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 10},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 10, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 0.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 12},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0, 8.0, 8.0, 6.0, 6.0])]]
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)

source:
"""Failure Map reference implementation. Python standard library only."""
import json
import math
from fractions import Fraction
N = 1
observations = []
def solve(x):
    dose = Fraction(str(x['start_mg']))
    t = Fraction(str(x['tablet_mg']))
    out = []
    for day in range(x['days']):
        if day > 0 and day % x['every_days'] == 0 and dose > 0:
            new = dose * (100 - x['step_pct']) / 100
            new = round(new / t) * t
            if new >= dose:
                new = dose - t
            dose = new if new >= Fraction(str(x['min_mg'])) else Fraction(0)
        out.append(float(dose))
    return out
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 11},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 4},
   [50.0, 50.0, 45.0, 45.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 2, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 10.0, 7.5, 7.5, 5.0, 5.0, 2.5, 2.5]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 6},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 10, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 0.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 8},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0])],
 [('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 12},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0, 25.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 5},
   [50.0, 50.0, 45.0, 45.0, 40.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 1, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 7.5, 5.0, 2.5, 0.0, 0.0, 0.0, 0.0]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 7},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 5, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 5.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 9},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0, 8.0])],
 [('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 13},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0, 25.0, 20.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 6},
   [50.0, 50.0, 45.0, 45.0, 40.0, 40.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 2, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 10.0, 7.5, 7.5, 5.0, 5.0, 2.5, 2.5]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 8},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 10, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 0.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 10},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0, 8.0, 8.0])],
 [('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 14},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0, 25.0, 20.0, 20.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 7},
   [50.0, 50.0, 45.0, 45.0, 40.0, 40.0, 35.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 1, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 7.5, 5.0, 2.5, 0.0, 0.0, 0.0, 0.0]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 9},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0, 0.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 5, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 5.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 11},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0, 8.0, 8.0, 6.0])],
 [('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 15},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0, 25.0, 20.0, 20.0, 20.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 8},
   [50.0, 50.0, 45.0, 45.0, 40.0, 40.0, 35.0, 35.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 2, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 10.0, 7.5, 7.5, 5.0, 5.0, 2.5, 2.5]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 10},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 10, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 0.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 12},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0, 8.0, 8.0, 6.0, 6.0])]]
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)

prompt:
A reduction to 42.5 mg with 5 mg tablets is given as 40 mg.

Input {start_mg, step_pct, every_days, min_mg, tablet_mg, days}. Day 0 uses start_mg. On each day d > 0 with d % every_days == 0 (while the dose is non-zero), the dose becomes current*(100-step_pct)/100 rounded half-up to a tablet_mg multiple; if that does not lower the dose, it is lowered by one tablet_mg instead; a result below min_mg stops the taper (0). Return the list of daily doses for days days.

split: open-access


broken_source:
"""Failure Map reference implementation. Python standard library only."""
import json
import math
from fractions import Fraction
N = 1
observations = []
def solve(x):
    dose = Fraction(str(x['start_mg']))
    t = Fraction(str(x['tablet_mg']))
    out = []
    for day in range(x['days']):
        if day > 0 and day % x['every_days'] == 0 and dose > 0:
            new = dose * (100 - x['step_pct']) / 100
            new = math.floor(new / t + Fraction(1, 2)) * t
            if new > dose:
                new = dose - t
            dose = new if new >= Fraction(str(x['min_mg'])) else Fraction(0)
        out.append(float(dose))
    return out
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 11},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 4},
   [50.0, 50.0, 45.0, 45.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 2, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 10.0, 7.5, 7.5, 5.0, 5.0, 2.5, 2.5]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 6},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 10, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 0.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 8},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0])],
 [('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 12},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0, 25.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 5},
   [50.0, 50.0, 45.0, 45.0, 40.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 1, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 7.5, 5.0, 2.5, 0.0, 0.0, 0.0, 0.0]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 7},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 5, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 5.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 9},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0, 8.0])],
 [('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 13},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0, 25.0, 20.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 6},
   [50.0, 50.0, 45.0, 45.0, 40.0, 40.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 2, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 10.0, 7.5, 7.5, 5.0, 5.0, 2.5, 2.5]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 8},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 10, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 0.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 10},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0, 8.0, 8.0])],
 [('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 14},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0, 25.0, 20.0, 20.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 7},
   [50.0, 50.0, 45.0, 45.0, 40.0, 40.0, 35.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 1, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 7.5, 5.0, 2.5, 0.0, 0.0, 0.0, 0.0]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 9},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0, 0.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 5, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 5.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 11},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0, 8.0, 8.0, 6.0])],
 [('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 15},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0, 25.0, 20.0, 20.0, 20.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 8},
   [50.0, 50.0, 45.0, 45.0, 40.0, 40.0, 35.0, 35.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 2, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 10.0, 7.5, 7.5, 5.0, 5.0, 2.5, 2.5]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 10},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 10, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 0.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 12},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0, 8.0, 8.0, 6.0, 6.0])]]
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)

source:
"""Failure Map reference implementation. Python standard library only."""
import json
import math
from fractions import Fraction
N = 1
observations = []
def solve(x):
    dose = Fraction(str(x['start_mg']))
    t = Fraction(str(x['tablet_mg']))
    out = []
    for day in range(x['days']):
        if day > 0 and day % x['every_days'] == 0 and dose > 0:
            new = dose * (100 - x['step_pct']) / 100
            new = math.floor(new / t + Fraction(1, 2)) * t
            if new >= dose:
                new = dose - 1
            dose = new if new >= Fraction(str(x['min_mg'])) else Fraction(0)
        out.append(float(dose))
    return out
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 11},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 4},
   [50.0, 50.0, 45.0, 45.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 2, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 10.0, 7.5, 7.5, 5.0, 5.0, 2.5, 2.5]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 6},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 10, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 0.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 8},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0])],
 [('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 12},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0, 25.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 5},
   [50.0, 50.0, 45.0, 45.0, 40.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 1, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 7.5, 5.0, 2.5, 0.0, 0.0, 0.0, 0.0]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 7},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 5, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 5.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 9},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0, 8.0])],
 [('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 13},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0, 25.0, 20.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 6},
   [50.0, 50.0, 45.0, 45.0, 40.0, 40.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 2, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 10.0, 7.5, 7.5, 5.0, 5.0, 2.5, 2.5]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 8},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 10, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 0.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 10},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0, 8.0, 8.0])],
 [('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 14},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0, 25.0, 20.0, 20.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 7},
   [50.0, 50.0, 45.0, 45.0, 40.0, 40.0, 35.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 1, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 7.5, 5.0, 2.5, 0.0, 0.0, 0.0, 0.0]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 9},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0, 0.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 5, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 5.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 11},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0, 8.0, 8.0, 6.0])],
 [('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 15},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0, 25.0, 20.0, 20.0, 20.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 8},
   [50.0, 50.0, 45.0, 45.0, 40.0, 40.0, 35.0, 35.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 2, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 10.0, 7.5, 7.5, 5.0, 5.0, 2.5, 2.5]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 10},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 10, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 0.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 12},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0, 8.0, 8.0, 6.0, 6.0])]]
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)

prompt:
The taper stalls at 10 mg because 10% rounds back to the same dose.

Input {start_mg, step_pct, every_days, min_mg, tablet_mg, days}. Day 0 uses start_mg. On each day d > 0 with d % every_days == 0 (while the dose is non-zero), the dose becomes current*(100-step_pct)/100 rounded half-up to a tablet_mg multiple; if that does not lower the dose, it is lowered by one tablet_mg instead; a result below min_mg stops the taper (0). Return the list of daily doses for days days.

split: open-access


broken_source:
"""Failure Map reference implementation. Python standard library only."""
import json
import math
from fractions import Fraction
N = 1
observations = []
def solve(x):
    dose = Fraction(str(x['start_mg']))
    t = Fraction(str(x['tablet_mg']))
    out = []
    for day in range(x['days']):
        if day > 0 and day % x['every_days'] == 0 and dose > 0:
            new = dose * (100 - x['step_pct']) / 100
            new = math.floor(new / t + Fraction(1, 2)) * t
            if new >= dose:
                new = dose - t
            dose = new if new > Fraction(str(x['min_mg'])) else Fraction(0)
        out.append(float(dose))
    return out
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 11},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 4},
   [50.0, 50.0, 45.0, 45.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 2, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 10.0, 7.5, 7.5, 5.0, 5.0, 2.5, 2.5]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 6},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 10, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 0.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 8},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0])],
 [('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 12},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0, 25.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 5},
   [50.0, 50.0, 45.0, 45.0, 40.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 1, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 7.5, 5.0, 2.5, 0.0, 0.0, 0.0, 0.0]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 7},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 5, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 5.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 9},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0, 8.0])],
 [('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 13},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0, 25.0, 20.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 6},
   [50.0, 50.0, 45.0, 45.0, 40.0, 40.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 2, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 10.0, 7.5, 7.5, 5.0, 5.0, 2.5, 2.5]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 8},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 10, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 0.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 10},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0, 8.0, 8.0])],
 [('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 14},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0, 25.0, 20.0, 20.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 7},
   [50.0, 50.0, 45.0, 45.0, 40.0, 40.0, 35.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 1, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 7.5, 5.0, 2.5, 0.0, 0.0, 0.0, 0.0]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 9},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0, 0.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 5, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 5.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 11},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0, 8.0, 8.0, 6.0])],
 [('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 15},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0, 25.0, 20.0, 20.0, 20.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 8},
   [50.0, 50.0, 45.0, 45.0, 40.0, 40.0, 35.0, 35.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 2, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 10.0, 7.5, 7.5, 5.0, 5.0, 2.5, 2.5]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 10},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 10, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 0.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 12},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0, 8.0, 8.0, 6.0, 6.0])]]
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)

source:
"""Failure Map reference implementation. Python standard library only."""
import json
import math
from fractions import Fraction
N = 1
observations = []
def solve(x):
    dose = Fraction(str(x['start_mg']))
    t = Fraction(str(x['tablet_mg']))
    out = []
    for day in range(x['days']):
        if day > 0 and day % x['every_days'] == 0 and dose > 0:
            new = dose * (100 - x['step_pct']) / 100
            new = math.floor(new / t + Fraction(1, 2)) * t
            if new >= dose:
                new = dose - t
            dose = new if new >= t else Fraction(0)
        out.append(float(dose))
    return out
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 11},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 4},
   [50.0, 50.0, 45.0, 45.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 2, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 10.0, 7.5, 7.5, 5.0, 5.0, 2.5, 2.5]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 6},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 10, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 0.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 8},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0])],
 [('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 12},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0, 25.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 5},
   [50.0, 50.0, 45.0, 45.0, 40.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 1, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 7.5, 5.0, 2.5, 0.0, 0.0, 0.0, 0.0]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 7},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 5, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 5.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 9},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0, 8.0])],
 [('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 13},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0, 25.0, 20.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 6},
   [50.0, 50.0, 45.0, 45.0, 40.0, 40.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 2, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 10.0, 7.5, 7.5, 5.0, 5.0, 2.5, 2.5]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 8},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 10, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 0.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 10},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0, 8.0, 8.0])],
 [('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 14},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0, 25.0, 20.0, 20.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 7},
   [50.0, 50.0, 45.0, 45.0, 40.0, 40.0, 35.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 1, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 7.5, 5.0, 2.5, 0.0, 0.0, 0.0, 0.0]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 9},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0, 0.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 5, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 5.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 11},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0, 8.0, 8.0, 6.0])],
 [('weekly 25 percent taper',
   {'start_mg': 60, 'step_pct': 25, 'every_days': 3, 'min_mg': 5, 'tablet_mg': 5, 'days': 15},
   [60.0, 60.0, 60.0, 45.0, 45.0, 45.0, 35.0, 35.0, 35.0, 25.0, 25.0, 25.0, 20.0, 20.0, 20.0]),
  ('half-tablet boundary rounds up',
   {'start_mg': 50, 'step_pct': 15, 'every_days': 2, 'min_mg': 5, 'tablet_mg': 5, 'days': 8},
   [50.0, 50.0, 45.0, 45.0, 40.0, 40.0, 35.0, 35.0]),
  ('rounding stall forces one tablet step',
   {'start_mg': 10, 'step_pct': 10, 'every_days': 2, 'min_mg': '2.5', 'tablet_mg': '2.5', 'days': 8},
   [10.0, 10.0, 7.5, 7.5, 5.0, 5.0, 2.5, 2.5]),
  ('stops below minimum',
   {'start_mg': 20, 'step_pct': 50, 'every_days': 2, 'min_mg': 10, 'tablet_mg': 5, 'days': 10},
   [20.0, 20.0, 10.0, 10.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]),
  ('dose equal to minimum continues',
   {'start_mg': 40, 'step_pct': 50, 'every_days': 1, 'min_mg': 10, 'tablet_mg': 5, 'days': 5},
   [40.0, 20.0, 10.0, 0.0, 0.0]),
  ('fractional tablet percentages',
   {'start_mg': 30, 'step_pct': 30, 'every_days': 2, 'min_mg': 1, 'tablet_mg': 1, 'days': 12},
   [30.0, 30.0, 21.0, 21.0, 15.0, 15.0, 11.0, 11.0, 8.0, 8.0, 6.0, 6.0])]]
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)

prompt:
The taper stops one step early when the dose reaches exactly the minimum.

Input {start_mg, step_pct, every_days, min_mg, tablet_mg, days}. Day 0 uses start_mg. On each day d > 0 with d % every_days == 0 (while the dose is non-zero), the dose becomes current*(100-step_pct)/100 rounded half-up to a tablet_mg multiple; if that does not lower the dose, it is lowered by one tablet_mg instead; a result below min_mg stops the taper (0). Return the list of daily doses for days days.

split: open-access