"""Parse Safety Alerts table from the NCMDR weekly update PDF.""" from __future__ import annotations import io import re from dataclasses import dataclass import pdfplumber NCMDR_REF_PATTERN = re.compile(r"^SA-\d", re.IGNORECASE) @dataclass class SafetyAlertRow: ncmdr_ref: str detail_url: str def _collect_refs_from_page(page) -> list[str]: refs: list[str] = [] for table in page.extract_tables() or []: for row in table or []: if not row: continue for cell in row: if not cell: continue text = str(cell).strip() if NCMDR_REF_PATTERN.match(text): refs.append(text) return refs def _collect_links_from_page(page) -> list[str]: hyperlinks = [ h for h in (page.hyperlinks or []) if h.get("uri") and "PublishDetails" in h["uri"] ] hyperlinks.sort(key=lambda h: h.get("top", 0)) return [h["uri"] for h in hyperlinks] def parse_safety_alerts_from_pdf(pdf_bytes: bytes) -> list[SafetyAlertRow]: alerts: list[SafetyAlertRow] = [] with pdfplumber.open(io.BytesIO(pdf_bytes)) as pdf: for page in pdf.pages: refs = _collect_refs_from_page(page) links = _collect_links_from_page(page) if not refs: continue if len(refs) != len(links): # Fallback: zip min length and log mismatch in importer pair_count = min(len(refs), len(links)) refs = refs[:pair_count] links = links[:pair_count] for ncmdr_ref, detail_url in zip(refs, links): alerts.append(SafetyAlertRow(ncmdr_ref=ncmdr_ref, detail_url=detail_url)) # Deduplicate by NCMDR ref while preserving order seen: set[str] = set() unique: list[SafetyAlertRow] = [] for alert in alerts: key = alert.ncmdr_ref.strip().upper() if key in seen: continue seen.add(key) unique.append(alert) return unique