FAILURE MAP
← Case archive

FA-64121 / Medication dosing calculations / Open access

Gravity drip rate and infusion end time: start time parsing · case 01

End times are wildly wrong for afternoon starts.

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

ROOT CAUSE

The HH:MM start is treated as an HHMM number instead of converting hours to minutes.

THE FAILURE

The HH:MM start is treated as an HHMM number instead of converting hours to minutes.

Unsuccessful approach: Reducing hours modulo 12 drops the afternoon for starts at or after 12:00.

Case contract

Input {volume_ml, duration_min, drop_factor (gtt/mL), start "HH:MM" 24-hour}. drops/min = volume*drop_factor/duration rounded half-up to a whole drop; mL/h = volume*60/duration rounded half-up to 0.1; end = start + duration on a 24-hour clock with day_offset = number of midnights crossed (ending exactly at 00:00 counts as the next day). Return {gtt_min, ml_h, end, day_offset}.

Why this case matters

Manual gravity infusions depend on drop-factor arithmetic and clock arithmetic across midnight, each with its own failure modes.

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):
    gtt = Fraction(x['volume_ml'] * x['drop_factor'], x['duration_min'])
    gtt_i = math.floor(gtt + Fraction(1, 2))
    mlh = Fraction(x['volume_ml'] * 60, x['duration_min'])
    mlh_t = math.floor(mlh * 10 + Fraction(1, 2))
    h, m = map(int, x['start'].split(':'))
    total = h * 100 + m + x['duration_min']
    day = total // 1440
    total = total % 1440
    return {'gtt_min': gtt_i, 'ml_h': mlh_t / 10, 'end': '%02d:%02d' % (total // 60, total % 60), 'day_offset': day}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('macrodrip set afternoon start',
   {'volume_ml': 1000, 'duration_min': 480, 'drop_factor': 15, 'start': '14:05'},
   {'gtt_min': 31, 'ml_h': 125.0, 'end': '22:05', 'day_offset': 0}),
  ('drop rate rounds half up',
   {'volume_ml': 250, 'duration_min': 100, 'drop_factor': 15, 'start': '08:00'},
   {'gtt_min': 38, 'ml_h': 150.0, 'end': '09:40', 'day_offset': 0}),
  ('fractional drop rate rounds down',
   {'volume_ml': 103, 'duration_min': 60, 'drop_factor': 20, 'start': '09:30'},
   {'gtt_min': 34, 'ml_h': 103.0, 'end': '10:30', 'day_offset': 0}),
  ('ends exactly at midnight',
   {'volume_ml': 500, 'duration_min': 150, 'drop_factor': 20, 'start': '22:00'},
   {'gtt_min': 67, 'ml_h': 200.0, 'end': '00:30', 'day_offset': 1}),
  ('multi-day infusion',
   {'volume_ml': 2000, 'duration_min': 2940, 'drop_factor': 60, 'start': '13:15'},
   {'gtt_min': 41, 'ml_h': 40.8, 'end': '14:15', 'day_offset': 2}),
  ('short microdrip before noon',
   {'volume_ml': 50, 'duration_min': 31, 'drop_factor': 60, 'start': '10:45'},
   {'gtt_min': 97, 'ml_h': 96.8, 'end': '11:16', 'day_offset': 0})],
 [('macrodrip set afternoon start',
   {'volume_ml': 1000, 'duration_min': 480, 'drop_factor': 20, 'start': '14:10'},
   {'gtt_min': 42, 'ml_h': 125.0, 'end': '22:10', 'day_offset': 0}),
  ('drop rate rounds half up',
   {'volume_ml': 250, 'duration_min': 100, 'drop_factor': 15, 'start': '08:00'},
   {'gtt_min': 38, 'ml_h': 150.0, 'end': '09:40', 'day_offset': 0}),
  ('fractional drop rate rounds down',
   {'volume_ml': 106, 'duration_min': 60, 'drop_factor': 20, 'start': '09:30'},
   {'gtt_min': 35, 'ml_h': 106.0, 'end': '10:30', 'day_offset': 0}),
  ('ends exactly at midnight',
   {'volume_ml': 500, 'duration_min': 180, 'drop_factor': 20, 'start': '21:30'},
   {'gtt_min': 56, 'ml_h': 166.7, 'end': '00:30', 'day_offset': 1}),
  ('multi-day infusion',
   {'volume_ml': 2000, 'duration_min': 3000, 'drop_factor': 60, 'start': '13:15'},
   {'gtt_min': 40, 'ml_h': 40.0, 'end': '15:15', 'day_offset': 2}),
  ('short microdrip before noon',
   {'volume_ml': 50, 'duration_min': 32, 'drop_factor': 60, 'start': '10:45'},
   {'gtt_min': 94, 'ml_h': 93.8, 'end': '11:17', 'day_offset': 0})],
 [('macrodrip set afternoon start',
   {'volume_ml': 1000, 'duration_min': 480, 'drop_factor': 10, 'start': '14:15'},
   {'gtt_min': 21, 'ml_h': 125.0, 'end': '22:15', 'day_offset': 0}),
  ('drop rate rounds half up',
   {'volume_ml': 250, 'duration_min': 100, 'drop_factor': 10, 'start': '08:00'},
   {'gtt_min': 25, 'ml_h': 150.0, 'end': '09:40', 'day_offset': 0}),
  ('fractional drop rate rounds down',
   {'volume_ml': 109, 'duration_min': 60, 'drop_factor': 20, 'start': '09:30'},
   {'gtt_min': 36, 'ml_h': 109.0, 'end': '10:30', 'day_offset': 0}),
  ('ends exactly at midnight',
   {'volume_ml': 500, 'duration_min': 210, 'drop_factor': 20, 'start': '21:00'},
   {'gtt_min': 48, 'ml_h': 142.9, 'end': '00:30', 'day_offset': 1}),
  ('multi-day infusion',
   {'volume_ml': 2000, 'duration_min': 3060, 'drop_factor': 60, 'start': '13:15'},
   {'gtt_min': 39, 'ml_h': 39.2, 'end': '16:15', 'day_offset': 2}),
  ('short microdrip before noon',
   {'volume_ml': 50, 'duration_min': 33, 'drop_factor': 60, 'start': '10:45'},
   {'gtt_min': 91, 'ml_h': 90.9, 'end': '11:18', 'day_offset': 0})],
 [('macrodrip set afternoon start',
   {'volume_ml': 1000, 'duration_min': 480, 'drop_factor': 15, 'start': '14:20'},
   {'gtt_min': 31, 'ml_h': 125.0, 'end': '22:20', 'day_offset': 0}),
  ('drop rate rounds half up',
   {'volume_ml': 250, 'duration_min': 100, 'drop_factor': 20, 'start': '08:00'},
   {'gtt_min': 50, 'ml_h': 150.0, 'end': '09:40', 'day_offset': 0}),
  ('fractional drop rate rounds down',
   {'volume_ml': 112, 'duration_min': 60, 'drop_factor': 20, 'start': '09:30'},
   {'gtt_min': 37, 'ml_h': 112.0, 'end': '10:30', 'day_offset': 0}),
  ('ends exactly at midnight',
   {'volume_ml': 500, 'duration_min': 240, 'drop_factor': 20, 'start': '20:30'},
   {'gtt_min': 42, 'ml_h': 125.0, 'end': '00:30', 'day_offset': 1}),
  ('multi-day infusion',
   {'volume_ml': 2000, 'duration_min': 3120, 'drop_factor': 60, 'start': '13:15'},
   {'gtt_min': 38, 'ml_h': 38.5, 'end': '17:15', 'day_offset': 2}),
  ('short microdrip before noon',
   {'volume_ml': 50, 'duration_min': 34, 'drop_factor': 60, 'start': '10:45'},
   {'gtt_min': 88, 'ml_h': 88.2, 'end': '11:19', 'day_offset': 0})],
 [('macrodrip set afternoon start',
   {'volume_ml': 1000, 'duration_min': 480, 'drop_factor': 20, 'start': '14:25'},
   {'gtt_min': 42, 'ml_h': 125.0, 'end': '22:25', 'day_offset': 0}),
  ('drop rate rounds half up',
   {'volume_ml': 250, 'duration_min': 100, 'drop_factor': 15, 'start': '08:00'},
   {'gtt_min': 38, 'ml_h': 150.0, 'end': '09:40', 'day_offset': 0}),
  ('fractional drop rate rounds down',
   {'volume_ml': 115, 'duration_min': 60, 'drop_factor': 20, 'start': '09:30'},
   {'gtt_min': 38, 'ml_h': 115.0, 'end': '10:30', 'day_offset': 0}),
  ('ends exactly at midnight',
   {'volume_ml': 500, 'duration_min': 270, 'drop_factor': 20, 'start': '20:00'},
   {'gtt_min': 37, 'ml_h': 111.1, 'end': '00:30', 'day_offset': 1}),
  ('multi-day infusion',
   {'volume_ml': 2000, 'duration_min': 3180, 'drop_factor': 60, 'start': '13:15'},
   {'gtt_min': 38, 'ml_h': 37.7, 'end': '18:15', 'day_offset': 2}),
  ('short microdrip before noon',
   {'volume_ml': 50, 'duration_min': 35, 'drop_factor': 60, 'start': '10:45'},
   {'gtt_min': 86, 'ml_h': 85.7, 'end': '11:20', 'day_offset': 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)
Boundary fixtureActualExpectedOutcome
macrodrip set afternoon start{'day_offset': 1, 'end': '07:25', 'gtt_min': 31, 'ml_h': 125.0}{'day_offset': 0, 'end': '22:05', 'gtt_min': 31, 'ml_h': 125.0}Failed
drop rate rounds half up{'day_offset': 0, 'end': '15:00', 'gtt_min': 38, 'ml_h': 150.0}{'day_offset': 0, 'end': '09:40', 'gtt_min': 38, 'ml_h': 150.0}Failed
fractional drop rate rounds down{'day_offset': 0, 'end': '16:30', 'gtt_min': 34, 'ml_h': 103.0}{'day_offset': 0, 'end': '10:30', 'gtt_min': 34, 'ml_h': 103.0}Failed
ends exactly at midnight{'day_offset': 1, 'end': '15:10', 'gtt_min': 67, 'ml_h': 200.0}{'day_offset': 1, 'end': '00:30', 'gtt_min': 67, 'ml_h': 200.0}Failed
multi-day infusion{'day_offset': 2, 'end': '22:55', 'gtt_min': 41, 'ml_h': 40.8}{'day_offset': 2, 'end': '14:15', 'gtt_min': 41, 'ml_h': 40.8}Failed
short microdrip before noon{'day_offset': 0, 'end': '17:56', 'gtt_min': 97, 'ml_h': 96.8}{'day_offset': 0, 'end': '11:16', 'gtt_min': 97, 'ml_h': 96.8}Failed

SHA-256 / 541b888ef47074e5aaad3f8997350e43453e203b962b5e913bc7b98444bcc466

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):
    gtt = Fraction(x['volume_ml'] * x['drop_factor'], x['duration_min'])
    gtt_i = math.floor(gtt + Fraction(1, 2))
    mlh = Fraction(x['volume_ml'] * 60, x['duration_min'])
    mlh_t = math.floor(mlh * 10 + Fraction(1, 2))
    h, m = map(int, x['start'].split(':'))
    total = (h % 12) * 60 + m + x['duration_min']
    day = total // 1440
    total = total % 1440
    return {'gtt_min': gtt_i, 'ml_h': mlh_t / 10, 'end': '%02d:%02d' % (total // 60, total % 60), 'day_offset': day}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('macrodrip set afternoon start',
   {'volume_ml': 1000, 'duration_min': 480, 'drop_factor': 15, 'start': '14:05'},
   {'gtt_min': 31, 'ml_h': 125.0, 'end': '22:05', 'day_offset': 0}),
  ('drop rate rounds half up',
   {'volume_ml': 250, 'duration_min': 100, 'drop_factor': 15, 'start': '08:00'},
   {'gtt_min': 38, 'ml_h': 150.0, 'end': '09:40', 'day_offset': 0}),
  ('fractional drop rate rounds down',
   {'volume_ml': 103, 'duration_min': 60, 'drop_factor': 20, 'start': '09:30'},
   {'gtt_min': 34, 'ml_h': 103.0, 'end': '10:30', 'day_offset': 0}),
  ('ends exactly at midnight',
   {'volume_ml': 500, 'duration_min': 150, 'drop_factor': 20, 'start': '22:00'},
   {'gtt_min': 67, 'ml_h': 200.0, 'end': '00:30', 'day_offset': 1}),
  ('multi-day infusion',
   {'volume_ml': 2000, 'duration_min': 2940, 'drop_factor': 60, 'start': '13:15'},
   {'gtt_min': 41, 'ml_h': 40.8, 'end': '14:15', 'day_offset': 2}),
  ('short microdrip before noon',
   {'volume_ml': 50, 'duration_min': 31, 'drop_factor': 60, 'start': '10:45'},
   {'gtt_min': 97, 'ml_h': 96.8, 'end': '11:16', 'day_offset': 0})],
 [('macrodrip set afternoon start',
   {'volume_ml': 1000, 'duration_min': 480, 'drop_factor': 20, 'start': '14:10'},
   {'gtt_min': 42, 'ml_h': 125.0, 'end': '22:10', 'day_offset': 0}),
  ('drop rate rounds half up',
   {'volume_ml': 250, 'duration_min': 100, 'drop_factor': 15, 'start': '08:00'},
   {'gtt_min': 38, 'ml_h': 150.0, 'end': '09:40', 'day_offset': 0}),
  ('fractional drop rate rounds down',
   {'volume_ml': 106, 'duration_min': 60, 'drop_factor': 20, 'start': '09:30'},
   {'gtt_min': 35, 'ml_h': 106.0, 'end': '10:30', 'day_offset': 0}),
  ('ends exactly at midnight',
   {'volume_ml': 500, 'duration_min': 180, 'drop_factor': 20, 'start': '21:30'},
   {'gtt_min': 56, 'ml_h': 166.7, 'end': '00:30', 'day_offset': 1}),
  ('multi-day infusion',
   {'volume_ml': 2000, 'duration_min': 3000, 'drop_factor': 60, 'start': '13:15'},
   {'gtt_min': 40, 'ml_h': 40.0, 'end': '15:15', 'day_offset': 2}),
  ('short microdrip before noon',
   {'volume_ml': 50, 'duration_min': 32, 'drop_factor': 60, 'start': '10:45'},
   {'gtt_min': 94, 'ml_h': 93.8, 'end': '11:17', 'day_offset': 0})],
 [('macrodrip set afternoon start',
   {'volume_ml': 1000, 'duration_min': 480, 'drop_factor': 10, 'start': '14:15'},
   {'gtt_min': 21, 'ml_h': 125.0, 'end': '22:15', 'day_offset': 0}),
  ('drop rate rounds half up',
   {'volume_ml': 250, 'duration_min': 100, 'drop_factor': 10, 'start': '08:00'},
   {'gtt_min': 25, 'ml_h': 150.0, 'end': '09:40', 'day_offset': 0}),
  ('fractional drop rate rounds down',
   {'volume_ml': 109, 'duration_min': 60, 'drop_factor': 20, 'start': '09:30'},
   {'gtt_min': 36, 'ml_h': 109.0, 'end': '10:30', 'day_offset': 0}),
  ('ends exactly at midnight',
   {'volume_ml': 500, 'duration_min': 210, 'drop_factor': 20, 'start': '21:00'},
   {'gtt_min': 48, 'ml_h': 142.9, 'end': '00:30', 'day_offset': 1}),
  ('multi-day infusion',
   {'volume_ml': 2000, 'duration_min': 3060, 'drop_factor': 60, 'start': '13:15'},
   {'gtt_min': 39, 'ml_h': 39.2, 'end': '16:15', 'day_offset': 2}),
  ('short microdrip before noon',
   {'volume_ml': 50, 'duration_min': 33, 'drop_factor': 60, 'start': '10:45'},
   {'gtt_min': 91, 'ml_h': 90.9, 'end': '11:18', 'day_offset': 0})],
 [('macrodrip set afternoon start',
   {'volume_ml': 1000, 'duration_min': 480, 'drop_factor': 15, 'start': '14:20'},
   {'gtt_min': 31, 'ml_h': 125.0, 'end': '22:20', 'day_offset': 0}),
  ('drop rate rounds half up',
   {'volume_ml': 250, 'duration_min': 100, 'drop_factor': 20, 'start': '08:00'},
   {'gtt_min': 50, 'ml_h': 150.0, 'end': '09:40', 'day_offset': 0}),
  ('fractional drop rate rounds down',
   {'volume_ml': 112, 'duration_min': 60, 'drop_factor': 20, 'start': '09:30'},
   {'gtt_min': 37, 'ml_h': 112.0, 'end': '10:30', 'day_offset': 0}),
  ('ends exactly at midnight',
   {'volume_ml': 500, 'duration_min': 240, 'drop_factor': 20, 'start': '20:30'},
   {'gtt_min': 42, 'ml_h': 125.0, 'end': '00:30', 'day_offset': 1}),
  ('multi-day infusion',
   {'volume_ml': 2000, 'duration_min': 3120, 'drop_factor': 60, 'start': '13:15'},
   {'gtt_min': 38, 'ml_h': 38.5, 'end': '17:15', 'day_offset': 2}),
  ('short microdrip before noon',
   {'volume_ml': 50, 'duration_min': 34, 'drop_factor': 60, 'start': '10:45'},
   {'gtt_min': 88, 'ml_h': 88.2, 'end': '11:19', 'day_offset': 0})],
 [('macrodrip set afternoon start',
   {'volume_ml': 1000, 'duration_min': 480, 'drop_factor': 20, 'start': '14:25'},
   {'gtt_min': 42, 'ml_h': 125.0, 'end': '22:25', 'day_offset': 0}),
  ('drop rate rounds half up',
   {'volume_ml': 250, 'duration_min': 100, 'drop_factor': 15, 'start': '08:00'},
   {'gtt_min': 38, 'ml_h': 150.0, 'end': '09:40', 'day_offset': 0}),
  ('fractional drop rate rounds down',
   {'volume_ml': 115, 'duration_min': 60, 'drop_factor': 20, 'start': '09:30'},
   {'gtt_min': 38, 'ml_h': 115.0, 'end': '10:30', 'day_offset': 0}),
  ('ends exactly at midnight',
   {'volume_ml': 500, 'duration_min': 270, 'drop_factor': 20, 'start': '20:00'},
   {'gtt_min': 37, 'ml_h': 111.1, 'end': '00:30', 'day_offset': 1}),
  ('multi-day infusion',
   {'volume_ml': 2000, 'duration_min': 3180, 'drop_factor': 60, 'start': '13:15'},
   {'gtt_min': 38, 'ml_h': 37.7, 'end': '18:15', 'day_offset': 2}),
  ('short microdrip before noon',
   {'volume_ml': 50, 'duration_min': 35, 'drop_factor': 60, 'start': '10:45'},
   {'gtt_min': 86, 'ml_h': 85.7, 'end': '11:20', 'day_offset': 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)
Boundary fixtureActualExpectedOutcome
macrodrip set afternoon start{'day_offset': 0, 'end': '10:05', 'gtt_min': 31, 'ml_h': 125.0}{'day_offset': 0, 'end': '22:05', 'gtt_min': 31, 'ml_h': 125.0}Failed
drop rate rounds half up{'day_offset': 0, 'end': '09:40', 'gtt_min': 38, 'ml_h': 150.0}{'day_offset': 0, 'end': '09:40', 'gtt_min': 38, 'ml_h': 150.0}Passed
fractional drop rate rounds down{'day_offset': 0, 'end': '10:30', 'gtt_min': 34, 'ml_h': 103.0}{'day_offset': 0, 'end': '10:30', 'gtt_min': 34, 'ml_h': 103.0}Passed
ends exactly at midnight{'day_offset': 0, 'end': '12:30', 'gtt_min': 67, 'ml_h': 200.0}{'day_offset': 1, 'end': '00:30', 'gtt_min': 67, 'ml_h': 200.0}Failed
multi-day infusion{'day_offset': 2, 'end': '02:15', 'gtt_min': 41, 'ml_h': 40.8}{'day_offset': 2, 'end': '14:15', 'gtt_min': 41, 'ml_h': 40.8}Failed
short microdrip before noon{'day_offset': 0, 'end': '11:16', 'gtt_min': 97, 'ml_h': 96.8}{'day_offset': 0, 'end': '11:16', 'gtt_min': 97, 'ml_h': 96.8}Passed

SHA-256 / 977ad2d2d04c223e07dd9b7ff1edb670e6646fc1a849dbef12f692eb3fed2a8e

HELD IN THE MEMBER ARCHIVE

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

This mechanism has 6 recorded checks per implementation. The open-access tier publishes the failure and the unsuccessful fix; the repaired source that passes every check, and the observations that prove it, are available to members.

Every case sharing this mechanism uses the same contract and the same repair, so this one record is held back for all of them.

Member access is invitation-based. Sign in with your invited account to inspect the repair.

Sign in to the archive ↗

Verification & scope

A deterministic toy software model with explicitly stipulated thresholds and formulas; it is not clinical guidance, not a validated medical calculator, and makes no claim of conformance to any published protocol. Numbered variants vary patient and order inputs. This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.

Observations recorded using Python 3.12.14 at 2026-09-29T14:47:21.202713+00:00.

Case digest / 7214cff4dfb42885379a9dc1168ad3fb5b6a05339f0978af4478a627c2653c13