Stop the slugifier from deleting the letter D-stroke

This commit is contained in:
2026-08-19 13:07:13 +07:00
parent 24c55d1627
commit 4490a1abf0
14 changed files with 739 additions and 198 deletions
+14 -2
View File
@@ -92,9 +92,21 @@ _Event = Union[Heading, _SectionEvent, _TextEvent] # Heading == a title event
def _slugify(text: str) -> str:
normalized = unicodedata.normalize("NFKD", text)
# `đ`/`Đ` (U+0111/U+0110) are standalone Vietnamese letters, not a base
# letter plus a combining mark, so NFKD leaves them whole and the ASCII
# encode below then discards them silently -- turning `ĐIỆN GIẢI` into
# `ien_giai`. Exactly three monographs contain `Đ` and all three carried a
# damaged drug_id because of this: GIẢI ĐỘC TỐ UỐN VÁN, KHÁNG ĐỘC TỐ BẠCH
# HẦU, and THUỐC UỐNG BÙ NƯỚC VÀ ĐIỆN GIẢI. Found via the adversarial eval
# suite, where both Oresol cases failed to resolve their monograph.
#
# Casefold before replacing so one pass covers both cases: `Đ` casefolds
# to `đ`. This matches what `entities/catalog.py::normalize_name` already
# does -- that function got it right and this one did not.
folded = text.casefold().replace("đ", "d")
normalized = unicodedata.normalize("NFKD", folded)
ascii_text = normalized.encode("ascii", "ignore").decode("ascii")
return re.sub(r"[^a-z0-9]+", "_", ascii_text.lower()).strip("_")
return re.sub(r"[^a-z0-9]+", "_", ascii_text).strip("_")
def _starts_its_visual_line(span: Span, previous: Span | None) -> bool:
+35 -1
View File
@@ -3,7 +3,7 @@ from dataclasses import replace
import pytest
from ingestion.extract.models import Span
from ingestion.segment.assembler import DuplicateDrugIdError, assemble
from ingestion.segment.assembler import DuplicateDrugIdError, _slugify, assemble
def _span(text, page, y0, bold=True, size=9.5, printed=None, column="left"):
@@ -449,3 +449,37 @@ def test_a_section_name_opening_its_own_line_is_still_a_heading():
]
monograph = list(assemble(spans))[0]
assert monograph.sections["chong_chi_dinh"].text == "Suy tủy nặng."
@pytest.mark.parametrize(
("name", "expected"),
[
("THUỐC UỐNG BÙ NƯỚC VÀ ĐIỆN GIẢI", "thuoc_uong_bu_nuoc_va_dien_giai"),
("KHÁNG ĐỘC TỐ BẠCH HẦU", "khang_doc_to_bach_hau"),
("Điện giải", "dien_giai"),
("đường huyết", "duong_huyet"),
],
)
def test_slugify_keeps_d_with_stroke(name, expected):
"""`đ`/`Đ` must become `d`, not disappear.
They are standalone letters (U+0111/U+0110), not a base letter plus a
combining mark, so NFKD leaves them whole and a following ASCII encode
drops them outright. That silently produced `ien_giai` from `ĐIỆN GIẢI`,
and all three monographs in the formulary whose names contain `Đ` carried
a damaged drug_id as a result.
"""
assert _slugify(name) == expected
def test_slugify_unchanged_for_names_without_d_stroke():
"""The fix must not move any id that was already correct -- 681 of the
684 monographs were fine, and changing one of those would orphan its
chunks in the vector store."""
for name, expected in [
("PARACETAMOL (Acetaminophen)", "paracetamol_acetaminophen"),
("Acid ioxaglic", "acid_ioxaglic"),
("METFORMIN", "metformin"),
("Vắc xin uốn ván hấp phụ", "vac_xin_uon_van_hap_phu"),
]:
assert _slugify(name) == expected