The Final Invitation That Never Came — Python Bug Hunt

Modelled on the NHS England breast screening failure announced in May 2018: the Health Secretary told Parliament that a computer algorithm failure dating…

  • Language: Python
  • Layer: Backend
  • Difficulty: Medium
  • Concepts: Time, Validation
  • Modelled on: NHS England · 2018
  • Visible tests: a routine run picks the due women in the band; a 70-year-old gets her final invitation
  • Reward: 50 XP for a complete fix

Briefing

Modelled on the NHS England breast screening failure announced in May 2018: the Health Secretary told Parliament that a computer algorithm failure dating back to 2009 meant an estimated 450,000 women aged 68 to 71 had not been invited to their final breast screening. (A later independent review found the causes were more tangled than a single coding error.)

This project is a reconstruction of the simplest form of that failure — an age-band boundary. screening.py decides who is in the programme with an exclusive upper bound on completed years of age, so the oldest women in the programme silently drop out of the invitation run.

Fix in_programme so the band matches the policy exactly.

Bug report

BUG-NHSBSP-0518 · Priority: Critical · Reported by: screening programme

in_programme(dob, today) — both datetime.date — is True from the day of a woman's 50th birthday up to AND INCLUDING the day of her 71st birthday, and False outside that. Birthdays come from dates.add_years(dob, n) (29 February falls on 1 March in non-leap years).

select_for_invite(women, today_iso) returns the sorted ids of women who are in_programme on that day AND are due: never screened (last is None), or today >= add_years(last screening date, 3).

Observed: women aged 70, and women on their 71st birthday, are never selected.

Logs

[bso-batch] run 2018-05-01 selected=18,204 eligible_by_age=50..69
[audit] 0 invitations issued to women aged 70 since 2009

The code as shipped

src/screening/screening.py (editable)

dates = bug_require("./dates.py")
policy = bug_require("./policy.py")


def in_programme(dob, today):
    age = dates.age_years(dob, today)
    return policy.FIRST_AGE <= age < policy.LAST_AGE


def select_for_invite(women, today_iso):
    today = dates.parse(today_iso)
    chosen = []
    for w in women:
        if not in_programme(dates.parse(w["dob"]), today):
            continue
        last = w.get("last")
        if last is not None and today < dates.add_years(dates.parse(last), policy.INTERVAL_YEARS):
            continue
        chosen.append(w["id"])
    return sorted(chosen)

Read-only context: src/screening/dates.py, src/screening/policy.py.

Open the hunt to edit the files, run the visible tests and submit against the hidden ones. More Python bug hunts.