fix: my own gate was forcing sections to open on "4."

The clinical view of an article was often one numbered list running from
the presenting complaint to discharge. Splitting it into Clinical paths
/ Diagnosis / Management necessarily divides that list — and because the
validator required every numeric token to survive, the restructure kept
the original numbering. Sections opened on "2." and "4.".

The "3." of a list item is a position, not a fact. It is stripped before
numbers are compared, so a restructure is free to renumber; and import
renumbers every section's lists from 1 regardless, per indentation
level, so a nested list counts independently of its parent. A blank line
between items is a loose list, not a new one, and does not reset it.

25 sections across 16 of the 54 already applied were repaired in place.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TqXevQJhxFrM7jJg82cgZN
This commit is contained in:
Daniel 2026-09-11 13:33:27 +02:00
parent 3b45eaf3a6
commit a9969d8d90

View file

@ -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]