Wire the guarded conversational RAG answer layer end-to-end

This commit is contained in:
2026-08-05 14:33:13 +07:00
parent 834d9e51b0
commit ef08b4929e
127 changed files with 37921 additions and 169 deletions
+77 -8
View File
@@ -1,6 +1,12 @@
from ingestion.extract.models import Span
from ingestion.segment import assemble
from ingestion.tables import SHAPE_GRID_2D, SHAPE_SIMPLE, TableRegion, index_by_page
from ingestion.tables import (
SHAPE_FORMULA_2D,
SHAPE_GRID_2D,
SHAPE_SIMPLE,
TableRegion,
index_by_page,
)
def _span(text, page, y0, *, bold=False, x0=50.0, block=0, line=0, column="left"):
@@ -54,6 +60,40 @@ def test_table_spans_are_lifted_out_of_section_prose():
assert block.quarantined is True
def test_section_named_table_cell_does_not_change_owning_section():
# Confirmed in WARFARIN p1485 and IOBITRIDOL p826: a table column named
# "Chỉ định" belongs to the dosing table; it is not a document
# section heading and must not move the block into chi_dinh.
spans = [
_span("WARFARIN", 1485, 60.0, bold=True),
_span("Tên chung quốc tế", 1485, 80.0, bold=True),
_span("Warfarinum.", 1485, 92.0),
_span("Liều lượng và cách dùng", 1485, 200.0, bold=True),
_span("Chỉ định", 1485, 400.0, bold=True, block=5),
_span("INR 2,0 - 3,0", 1485, 412.0, block=5),
]
region = TableRegion("p1485_t0", 1485, (40.0, 380.0, 400.0, 460.0), 2, 2, SHAPE_SIMPLE)
m = list(assemble(spans, table_index=index_by_page([region])))[0]
assert "chi_dinh" not in m.sections
assert len(m.tables) == 1
assert m.tables[0].section_key == "lieu_luong_va_cach_dung"
def test_wide_formula_band_does_not_swallow_the_opposite_column():
spans = _monograph_spans([
_span("Công thức:", 109, 360.0),
_span("Cl", 109, 400.0, x0=280.0, column="left", block=5),
_span("Xem thêm Liều lượng và cách dùng", 109, 400.0,
x0=310.0, column="right", block=6),
])
# Deliberately extends across the gutter, as verified formula bands do.
region = TableRegion("p109_f0", 109, (40.0, 380.0, 390.0, 430.0),
2, 1, SHAPE_FORMULA_2D)
m = list(assemble(spans, table_index=index_by_page([region])))[0]
assert m.tables[0].text == "Cl"
assert "Xem thêm Liều lượng và cách dùng" in m.sections["dang_thuoc_va_ham_luong"].text
def test_without_a_region_map_behaviour_is_unchanged():
spans = _monograph_spans([
_span("Thuốc dùng đường uống.", 109, 220.0),
@@ -85,24 +125,53 @@ def test_non_table_regions_are_never_lifted():
def test_table_block_ids_stay_unique_when_a_section_resumes():
# a region flushed twice (section closes, then resumes) must not emit two
# blocks with the same table_id — provenance ids have to be unique
# Confirmed on CAPECITABIN pp. 308-309 and IMATINIB p. 795: PDF block
# order can place a visually later heading between cells from one physical
# table. The complete region must stay atomic and owned by the section
# active where the table first appears.
spans = [
_span("CEFAMANDOL", 339, 60.0, bold=True),
_span("Tên chung quốc tế", 339, 80.0, bold=True),
_span("Cefamandolum.", 339, 92.0),
_span("Liều lượng và cách dùng", 339, 200.0, bold=True),
_span("80 - 50", 339, 400.0, block=5),
_span("Liều lượng và cách dùng", 339, 500.0, bold=True),
# Visually below the table, but emitted before its final cell by the
# PDF's internal block order.
_span("Tương tác thuốc", 339, 640.0, bold=True),
_span("< 25 - 10", 339, 600.0, block=9),
_span("Không phối hợp với thuốc X.", 339, 660.0, block=10),
]
region = TableRegion("p339_t0", 339, (40.0, 380.0, 400.0, 620.0), 5, 2, SHAPE_SIMPLE)
m = list(assemble(spans, table_index=index_by_page([region])))[0]
# table_id is deterministic per REGION, so two parts of one table share
# it on purpose; table_part_id is the unique key, derived from the first
# source span rather than a counter (a counter would renumber whenever
# anything upstream shifted, hiding rather than identifying a duplicate)
assert len(m.tables) == 1
assert m.tables[0].section_key == "lieu_luong_va_cach_dung"
assert "80 - 50" in m.tables[0].text
assert "< 25 - 10" in m.tables[0].text
assert "Không phối hợp với thuốc X." in m.sections["tuong_tac_thuoc"].text
assert len({t.table_part_id for t in m.tables}) == len(m.tables)
assert {t.continuation_group for t in m.tables} == {"p339_t0"}
assert all(t.table_part_id.startswith("p339_t0@") for t in m.tables)
assert all(t.quarantined for t in m.tables)
def test_explicit_dose_adjustment_caption_reassigns_late_appendix_table():
# CAPECITABIN p. 309: the PDF puts dose-adjustment tables after the trade
# names and does not repeat the ordinary dosage section heading. Internal
# block order can even emit a cell before the visually preceding caption.
spans = [
_span("CAPECITABIN", 309, 40.0, bold=True),
_span("Tên chung quốc tế", 309, 50.0, bold=True),
_span("Capecitabinum.", 309, 60.0),
_span("Tên thương mại", 309, 70.0, bold=True),
_span("Xeloda.", 309, 80.0),
_span("Mức độ theo NCIC", 309, 120.0, block=5),
_span("Bảng 3. Điều chỉnh liều do độc tính.", 309, 100.0),
_span("Ngừng thuốc cho đến khi về mức 0.", 309, 140.0, block=5),
]
region = TableRegion("p309_t0", 309, (40.0, 115.0, 400.0, 180.0),
3, 4, SHAPE_SIMPLE)
m = list(assemble(spans, table_index=index_by_page([region])))[0]
assert len(m.tables) == 1
assert m.tables[0].section_key == "lieu_luong_va_cach_dung"
dosage = m.sections["lieu_luong_va_cach_dung"].text
assert dosage.count("Bảng 3. Điều chỉnh liều do độc tính.") == 1