108 lines
4.5 KiB
Python
108 lines
4.5 KiB
Python
from ingestion.segment.models import Monograph
|
|
from ingestion.validation.back_index import GroundTruthEntry
|
|
from ingestion.validation.metrics import compute_recall_precision
|
|
|
|
|
|
def _mono(drug_id, drug_name, start_physical):
|
|
return Monograph(drug_id=drug_id, drug_name=drug_name, source_page_range=[start_physical, start_physical + 1])
|
|
|
|
|
|
def test_perfect_match_recall_and_precision_are_one():
|
|
monographs = [_mono("abacavir", "ABACAVIR", 100)]
|
|
ground_truth = [GroundTruthEntry(name="Abacavir", printed_page=101)]
|
|
result = compute_recall_precision(monographs, ground_truth)
|
|
assert result.recall == 1.0
|
|
assert result.precision == 1.0
|
|
assert result.matched_count == 1
|
|
|
|
|
|
def test_missed_ground_truth_entry_lowers_recall_not_precision():
|
|
monographs = [_mono("abacavir", "ABACAVIR", 100)]
|
|
ground_truth = [
|
|
GroundTruthEntry(name="Abacavir", printed_page=101),
|
|
GroundTruthEntry(name="Acarbose", printed_page=103),
|
|
]
|
|
result = compute_recall_precision(monographs, ground_truth)
|
|
assert result.recall == 0.5
|
|
assert result.precision == 1.0
|
|
assert len(result.unmatched_ground_truth) == 1
|
|
assert result.unmatched_ground_truth[0].name == "Acarbose"
|
|
|
|
|
|
def test_spurious_detected_monograph_lowers_precision_not_recall():
|
|
monographs = [
|
|
_mono("abacavir", "ABACAVIR", 100),
|
|
_mono("cac_chuyen_luan_thuoc", "CÁC CHUYÊN LUẬN THUỐC", 98),
|
|
]
|
|
ground_truth = [GroundTruthEntry(name="Abacavir", printed_page=101)]
|
|
result = compute_recall_precision(monographs, ground_truth)
|
|
assert result.recall == 1.0
|
|
assert result.precision == 0.5
|
|
assert len(result.unmatched_detected) == 1
|
|
|
|
|
|
def test_page_tolerance_allows_small_offset():
|
|
monographs = [_mono("abacavir", "ABACAVIR", 100)]
|
|
ground_truth = [GroundTruthEntry(name="Abacavir", printed_page=103)] # +2 tolerance
|
|
result = compute_recall_precision(monographs, ground_truth)
|
|
assert result.recall == 1.0
|
|
|
|
|
|
def test_page_beyond_tolerance_does_not_match():
|
|
monographs = [_mono("abacavir", "ABACAVIR", 100)]
|
|
ground_truth = [GroundTruthEntry(name="Abacavir", printed_page=110)]
|
|
result = compute_recall_precision(monographs, ground_truth)
|
|
assert result.recall == 0.0
|
|
|
|
|
|
def test_qualifier_suffixed_name_still_matches_base_ground_truth_name():
|
|
# SALBUTAMOL (Dùng trong hô hấp) should still match a ground-truth
|
|
# entry that just says "Salbutamol"
|
|
monographs = [_mono("salbutamol_dung_trong_ho_hap", "SALBUTAMOL (Dùng trong hô hấp)", 1261)]
|
|
ground_truth = [GroundTruthEntry(name="Salbutamol", printed_page=1262)]
|
|
result = compute_recall_precision(monographs, ground_truth)
|
|
assert result.recall == 1.0
|
|
|
|
|
|
def test_empty_ground_truth_gives_zero_recall_not_error():
|
|
result = compute_recall_precision([_mono("a", "A", 1)], [])
|
|
assert result.recall == 0.0
|
|
|
|
|
|
def test_empty_monographs_gives_zero_precision_not_error():
|
|
result = compute_recall_precision([], [GroundTruthEntry(name="A", printed_page=1)])
|
|
assert result.precision == 0.0
|
|
assert result.recall == 0.0
|
|
|
|
|
|
def test_exact_match_preferred_over_substring_steal_confirmed_real_case():
|
|
# Confirmed real case from a whole-book `cli validate` run: "ISOSORBID"
|
|
# and "ISOSORBID DINITRAT" are two distinct, correctly-segmented
|
|
# monographs a page apart. A pure substring match lets the shorter name
|
|
# "steal" both ground-truth entries (it's a substring of the longer one
|
|
# too) via `next()`'s order-dependent first match, leaving the real
|
|
# "ISOSORBID DINITRAT" monograph spuriously unmatched even though an
|
|
# exact match for it exists.
|
|
monographs = [
|
|
_mono("isosorbid", "ISOSORBID", 844),
|
|
_mono("isosorbid_dinitrat", "ISOSORBID DINITRAT", 845),
|
|
]
|
|
ground_truth = [
|
|
GroundTruthEntry(name="Isosorbid", printed_page=845),
|
|
GroundTruthEntry(name="Isosorbid dinitrat", printed_page=846),
|
|
]
|
|
result = compute_recall_precision(monographs, ground_truth)
|
|
assert result.recall == 1.0
|
|
assert result.precision == 1.0
|
|
assert len(result.unmatched_detected) == 0
|
|
|
|
|
|
def test_double_space_in_detected_name_still_matches_confirmed_real_case():
|
|
# confirmed real case from a whole-book `cli validate` run: "ALVERIN
|
|
# CITRAT" (double space) failed to match ground truth's single-spaced
|
|
# "Alverin citrat" under plain strip+upper comparison.
|
|
monographs = [_mono("alverin_citrat", "ALVERIN CITRAT", 171)]
|
|
ground_truth = [GroundTruthEntry(name="Alverin citrat", printed_page=172)]
|
|
result = compute_recall_precision(monographs, ground_truth)
|
|
assert result.recall == 1.0
|