diff --git a/backend/scripts/mdm_pass.py b/backend/scripts/mdm_pass.py index 47cc380..6a8abfb 100644 --- a/backend/scripts/mdm_pass.py +++ b/backend/scripts/mdm_pass.py @@ -44,6 +44,44 @@ REQUIRED = MDM_SECTIONS[:3] MARKER = re.compile(r"\[\[(\d+)\|") NUMBER = re.compile(r"\d+(?:\.\d+)?") +#: The "3." that opens an ordered-list item. It is a position in a list, not a +#: fact, and it necessarily changes when a list is split across sections — so +#: it is stripped before the numbers are compared. Requiring it to survive made +#: the first pass keep the original numbering, which left sections opening on +#: "2." and "4." because the list they came from had been divided. +LIST_MARKER = re.compile(r"^(\s*)\d+([.)])(\s)", re.M) + + +def facts_only(text: str) -> str: + """The text with ordered-list numbering removed, for comparing numbers.""" + return LIST_MARKER.sub(r"\1\2\3", text) + + +def renumber(text: str) -> str: + """Number every ordered-list item in this section from 1. + + A list split across two sections leaves the second starting wherever the + first stopped — "2. Blood cultures" opening the Diagnosis section. Counting + is per indentation level, so a nested list is numbered independently of its + parent, and nothing else resets it: a blank line between items is a loose + list, not a new one, and prose in between does not restart the count. + """ + counters: dict[int, int] = {} + out = [] + for line in text.split("\n"): + match = re.match(r"^(\s*)(\d+)([.)])(\s.*)$", line) + if not match: + out.append(line) + continue + indent, _was, dot, rest = match.groups() + depth = len(indent) + for deeper in [level for level in counters if level > depth]: + del counters[deeper] + counters[depth] = counters.get(depth, 0) + 1 + out.append(f"{indent}{counters[depth]}{dot}{rest}") + return "\n".join(out) + + MIN_RATIO, MAX_RATIO = 0.75, 1.35 @@ -94,7 +132,8 @@ def validate(before: list[dict], after: list[dict]) -> list[str]: if want != got: problems.append(f"cross-references changed (lost {sorted(set(want) - set(got)) or '-'}, " f"added {sorted(set(got) - set(want)) or '-'})") - want_n, got_n = sorted(NUMBER.findall(old_text)), sorted(NUMBER.findall(new_text)) + want_n = sorted(NUMBER.findall(facts_only(old_text))) + got_n = sorted(NUMBER.findall(facts_only(new_text))) if want_n != got_n: problems.append(f"numbers changed (lost {sorted(set(want_n) - set(got_n)) or '-'}, " f"added {sorted(set(got_n) - set(want_n)) or '-'})") @@ -172,7 +211,9 @@ def cmd_import(args) -> int: "id": uuid.uuid4().hex, "slug": slugify(section["title"]), "title": section["title"], - "content": section["content"], + # Each section's lists start at 1, whatever they started at + # in the block they were carved out of. + "content": renumber(section["content"]), "parent_id": None, "variant": "clinical", } for section in after]