from ingestion.segment.atc import extract_atc_codes, is_stated_absent, normalize_atc_candidate def test_stray_whitespace_split_j04a_c01_recovered(): assert normalize_atc_candidate("J04A C01") == "J04AC01" def test_stray_whitespace_split_n05b_a06_recovered(): assert normalize_atc_candidate("N05B A06") == "N05BA06" def test_stray_whitespace_split_l01x_x02_recovered(): assert normalize_atc_candidate("L01X X02") == "L01XX02" def test_digit_letter_confusion_no3ax12_recovered(): assert normalize_atc_candidate("NO3AX12") == "N03AX12" def test_digit_letter_confusion_jo1dc07_recovered(): assert normalize_atc_candidate("JO1DC07") == "J01DC07" def test_clean_code_passes_through(): assert normalize_atc_candidate("N03AX12") == "N03AX12" def test_garbage_not_recovered(): assert normalize_atc_candidate("NOT AN ATC CODE") is None assert normalize_atc_candidate("") is None def test_stated_absent_chua_co(): assert is_stated_absent("Mã ATC: Chưa có.") is True def test_stated_absent_khong_co(): assert is_stated_absent("Không có.") is True def test_stated_present_not_flagged_absent(): assert is_stated_absent("N03AX12") is False def test_extract_single_code(): result = extract_atc_codes("N03AX12") assert result.codes == ["N03AX12"] assert result.stated_absent is False def test_extract_multi_code_insulin_style(): result = extract_atc_codes("A10AB01, A10AC01, A10AD01") assert result.codes == ["A10AB01", "A10AC01", "A10AD01"] def test_extract_multi_code_with_noise_mixed_in(): # one clean code, one noisy code recovered, matching the real corpus # pattern where a monograph has some clean and some noisy ATC entries result = extract_atc_codes("N03AX12, J04A C01") assert result.codes == ["N03AX12", "J04AC01"] def test_extract_stated_absent_returns_no_codes(): result = extract_atc_codes("Mã ATC: Chưa có.") assert result.codes == [] assert result.stated_absent is True def test_trailing_period_recovered_confirmed_real_abacavir_case(): # real field text is "J05AF06." — a sentence-ending period, not part of # the code; an earlier version silently produced zero codes here. assert normalize_atc_candidate("J05AF06.") == "J05AF06" result = extract_atc_codes("J05AF06.") assert result.codes == ["J05AF06"] def test_species_annotation_stripped_confirmed_real_insulin_case(): # annotation-stripping is extract_atc_codes's job (must run before the # comma/semicolon split, see below) — normalize_atc_candidate itself # only normalizes an already-isolated code token. result = extract_atc_codes("A10AB01 (người); A10AB02 (bò)") assert result.codes == ["A10AB01", "A10AB02"] def test_leading_colon_from_value_span_stripped_confirmed_real_alcuronium_case(): # real field text for ALCURONIUM CLORID (physical page 152): the bold # label span is "Mã ATC" with no colon, and the plain value span is # ": M03AA01." — the colon belongs to the value side here, not the # label side (Abacavir's equivalent has it on the label side instead: # "Mã ATC: " + "J05AF06."). See atc.py module docstring, defect 5. assert normalize_atc_candidate(": M03AA01.") == "M03AA01" result = extract_atc_codes(": M03AA01.") assert result.codes == ["M03AA01"] def test_name_prefixed_code_stripped_confirmed_real_arginin_case(): # real field text for ARGININ (physical page 204): two salt forms, each # its own "Name: CODE" line, not a bare code — see atc.py module # docstring, defect 6. assert normalize_atc_candidate("Arginin glutamat: A05BA01") == "A05BA01" result = extract_atc_codes("Arginin glutamat: A05BA01\nArginin hydroclorid: B05XB01") assert result.codes == ["A05BA01", "B05XB01"] def test_plain_code_with_no_colon_still_normalizes(): assert normalize_atc_candidate("N03AX12") == "N03AX12" def test_reversed_code_first_shape_confirmed_real_hmg_coa_case(): # real field text for CÁC CHẤT ỨC CHẾ HMG-CoA REDUCTASE (physical page # 284): each statin is "CODE: Name", the opposite order from the # "Name: CODE" shape above — see atc.py module docstring, defect 7. # "C10A A01" also has the already-fixed stray-whitespace split. assert normalize_atc_candidate("C10A A01: Simvastatin") == "C10AA01" result = extract_atc_codes("C10A A01: Simvastatin\nC10A A02: Lovastatin") assert result.codes == ["C10AA01", "C10AA02"] def test_annotation_containing_a_comma_does_not_break_the_split_confirmed_vaccine_case(): # real field text for VẮC XIN SỞI (physical page 1437): the English # annotation "(Measles, live attenuated)" contains its own comma. An # earlier version split on "," *before* stripping the annotation, # breaking "J07BD01 (Measles, live attenuated)." into two unrecoverable # fragments and silently returning zero codes — see atc.py module # docstring, defect 4. result = extract_atc_codes("J07BD01 (Measles, live attenuated).") assert result.codes == ["J07BD01"] def test_extract_all_20_insulin_codes_from_real_field_text(): # exact real field text for INSULIN (physical page 809) — see atc.py # module docstring; confirms the fix recovers all 20, not just 2. field_text = ( "A10AB01 (người); A10AB02 (bò); A10AB03 (lợn);\n" "A10AB04 (lispro); A10AB05 (aspart); A10AB06 (glulisin);\n" "A10AC01 (người); A10AC02 (bò); A10AC03 (lợn); A10AC04\n" "(lispro); A10AD01 (người), A10AD02 (bò), A10AD03 (lợn),\n" "A10AD04 (lispro), A10AE01 (người); A10AE02 (bò); A10AE03\n" "(lợn); A10AE04 (glargin); A10AE05 (detemir), A10AF01 (người)." ) result = extract_atc_codes(field_text) assert len(result.codes) == 20 assert "A10AB01" in result.codes assert "A10AF01" in result.codes