Seera-Unified-SFDA/sfda_parser/sfda_scraper/test_detail_pdf_sanitize.py

177 lines
6.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Tests for material/serial sanitization in ADE detail PDF parsing."""
from __future__ import annotations
import unittest
from sfda_parser.sfda_scraper.detail_pdf import (
DeviceRow,
_clean_material_value,
_clean_serial_value,
_is_likely_date_value,
_is_likely_prose,
_paired_rows_from_summary,
_parse_summary_kv,
_sanitize_device_row,
)
class TestMaterialCleaning(unittest.TestCase):
def test_strips_product_concerned_prefix(self):
self.assertEqual(
_clean_material_value("of the product concerned: TU2000 TRYTABLE"),
"TU2000 TRYTABLE",
)
def test_strips_trade_name_product_concerned_prefix(self):
self.assertEqual(
_clean_material_value("Trade name of the product concerned: TU2000 TRYTABLE"),
"TU2000 TRYTABLE",
)
def test_parse_summary_trade_name_line(self):
lines = ["Trade name of the product concerned: TU2000 TRYTABLE"]
summary = _parse_summary_kv(lines)
self.assertEqual(summary.get("material"), "TU2000 TRYTABLE")
class TestSerialDateRejection(unittest.TestCase):
def test_detects_day_month_year_dates(self):
self.assertTrue(_is_likely_date_value("21-APR-2025"))
self.assertTrue(_is_likely_date_value("21/APR/2025"))
def test_allows_real_serial_numbers(self):
self.assertFalse(_is_likely_date_value("24B0729"))
self.assertFalse(_is_likely_date_value("TSMICS1"))
def test_clean_serial_rejects_dates(self):
self.assertEqual(_clean_serial_value("21-APR-2025"), "")
self.assertEqual(_clean_serial_value("ABC12345"), "ABC12345")
def test_sanitize_device_row_clears_date_serial(self):
row = DeviceRow(
material="Pump",
material_description="",
catalog_number="",
udi="",
serial_no="21-APR-2025",
gstn="",
batch="",
)
cleaned = _sanitize_device_row(row)
self.assertEqual(cleaned.serial_no, "")
class TestPairedUdiSn(unittest.TestCase):
def test_pairs_udi_and_sn_by_position(self):
udi_values = (
"04020012, 04020014, 04020017, 04020018, 04020026, 04020021, "
"04020009, 04020010, 04020011, 04020001, 04020025, 04020013, "
"04020003, 04020005, 04020016, 04020023, 04020028, 04020008, "
"04020020, 04020019, 04020015, 04020024, 04020022, 04020006, "
"04020027, 04020029, 04020030, 04020031"
)
sn_values = (
"32090012, 32090014, 32090017, 32090018, 32090026, 32090021, "
"32090009, 32090010, 32090011, TR05617, 32090025, 32090013, "
"32090003, 302090005, 32090016, 32090023, 32090028, 32090008, "
"32090020, 32090019, 32090015, 32090024, 32090022, 302090006, "
"32090027, 32090029, 32090030, 32090031"
)
summary = {
"material": "TU2000 TRYTABLE",
"udi": udi_values,
"serial_no": sn_values,
}
rows = _paired_rows_from_summary(summary)
self.assertEqual(len(rows), 28)
self.assertEqual(rows[0].udi, "04020012")
self.assertEqual(rows[0].serial_no, "32090012")
self.assertEqual(rows[1].udi, "04020014")
self.assertEqual(rows[1].serial_no, "32090014")
self.assertEqual(rows[9].udi, "04020001")
self.assertEqual(rows[9].serial_no, "TR05617")
self.assertEqual(rows[27].udi, "04020031")
self.assertEqual(rows[27].serial_no, "32090031")
self.assertEqual(rows[0].material, "TU2000 TRYTABLE")
def test_parse_summary_kv_stops_before_narrative(self):
lines = [
"Trade name of the product concerned: TU2000 TRYTABLE",
"UDI-DI: 04020012, 04020014",
"SN: 32090012, 32090014",
"Dear Customer",
"The manufacturer has identified an error in the user manual.",
]
summary = _parse_summary_kv(lines)
self.assertEqual(summary.get("udi"), "04020012, 04020014")
self.assertEqual(summary.get("serial_no"), "32090012, 32090014")
self.assertNotIn("Dear Customer", summary.get("serial_no", ""))
def test_parse_summary_kv_multiline_udi_sn_lists(self):
lines = [
"Trade name of the product concerned: TU2000 TRYTABLE",
"UDI-DI: 04020012, 04020014, 04020017, 04020018, 04020026, 04020021, 04020009,",
"04020010, 04020011, 04020001, 04020025, 04020013, 04020003, 04020005, 04020016,",
"04020023, 04020028, 04020008, 04020020, 04020019, 04020015, 04020024, 04020022,",
"04020006, 04020027, 04020029, 04020030, 04020031",
"SN: 32090012, 32090014, 32090017, 32090018, 32090026, 32090021, 32090009, 32090010,",
"32090011, TR05617, 32090025, 32090013, 32090003, 302090005, 32090016, 32090023,",
"32090028, 32090008, 32090020, 32090019, 32090015, 32090024, 32090022, 302090006,",
"32090027, 32090029, 32090030, 32090031",
"Manufactured: 18/07/2024 17/03/2026",
"Dear Customer,",
]
summary = _parse_summary_kv(lines)
rows = _paired_rows_from_summary(summary)
self.assertEqual(len(rows), 28)
self.assertEqual(rows[9].udi, "04020001")
self.assertEqual(rows[9].serial_no, "TR05617")
class TestProseRejection(unittest.TestCase):
def test_detects_narrative_fragments(self):
self.assertTrue(_is_likely_prose("Dear Customer"))
self.assertTrue(
_is_likely_prose(
"The manufacturer has identified an error in the user manual."
)
)
self.assertTrue(_is_likely_prose("Manufactured: 18/07/2024 17/03/2026"))
def test_clean_serial_rejects_prose(self):
self.assertEqual(_clean_serial_value("Dear Customer"), "")
self.assertEqual(_clean_serial_value("32090012"), "32090012")
def test_allows_long_comma_separated_identifier_lists(self):
line = (
"0012508285, 0012517045, 0012526102, 0012526103, 0012614786, 0012614787, "
"0012623740, 0012623741, 0012642959, 0012659419"
)
self.assertFalse(_is_likely_prose(line))
def test_clean_serial_keeps_comma_separated_lot_list(self):
value = "24B0729, 25B0770, 25H0171, 26B0542"
self.assertEqual(_clean_serial_value(value), value)
class TestSerialLabelParsing(unittest.TestCase):
def test_parse_sn_label_from_text(self):
lines = [
"UDI-DI: 01234567890123",
"SN: ABC12345",
"Date: 21-APR-2025",
]
summary = _parse_summary_kv(lines)
self.assertEqual(summary.get("udi"), "01234567890123")
self.assertEqual(summary.get("serial_no"), "ABC12345")
def test_sn_label_ignores_date_values(self):
lines = ["SN: 21-APR-2025"]
summary = _parse_summary_kv(lines)
self.assertNotIn("serial_no", summary)
if __name__ == "__main__":
unittest.main()