"""Re-parse device_list child rows from stored ADE detail PDF attachments.""" from __future__ import annotations import os import frappe from frappe.utils.file_manager import get_file_path from sfda_parser.sfda_scraper.detail_pdf import parse_affected_device_list def _read_detail_pdf_bytes(file_url: str) -> bytes: file_name = file_url.split("/files/")[-1] path = get_file_path(file_name) if not path or not os.path.isfile(path): site_path = frappe.get_site_path("public", "files", file_name) if os.path.isfile(site_path): path = site_path else: raise FileNotFoundError(f"PDF not found for {file_url}") with open(path, "rb") as f: return f.read() def _normalize_ncmdr_ref(ncmdr_ref: str) -> str: return (ncmdr_ref or "").strip().upper() def _group_child_rows_by_ncmdr_ref(doc) -> dict[str, dict[str, str]]: groups: dict[str, dict[str, str]] = {} for row in doc.device_list or []: ref = (row.ncmdr_ref or "").strip() if not ref: continue key = _normalize_ncmdr_ref(ref) if key not in groups: groups[key] = { "ncmdr_ref": ref, "manufacturer": row.manufacturer or "", "detail_pdf": row.detail_pdf or "", "ade_detail_url": row.ade_detail_url or "", } return groups def reparse_sfda_entry_device_list(doc_name: str) -> dict: """Rebuild device_list child rows from stored detail_pdf on each NCMDR group.""" doc = frappe.get_doc("SFDA Entries", doc_name) groups = _group_child_rows_by_ncmdr_ref(doc) if not groups: return {"name": doc_name, "device_rows": 0, "skipped": "no_child_rows"} rebuilt_rows: list[dict] = [] total_device_rows = 0 for group in groups.values(): detail_pdf = group.get("detail_pdf") or "" if not detail_pdf: rebuilt_rows.append( { "doctype": "SFDA Device Entries", "ncmdr_ref": group["ncmdr_ref"], "manufacturer": group["manufacturer"], "ade_detail_url": group["ade_detail_url"], } ) continue pdf_bytes = _read_detail_pdf_bytes(detail_pdf) devices = parse_affected_device_list(pdf_bytes) if devices: for device in devices: rebuilt_rows.append( { "doctype": "SFDA Device Entries", "ncmdr_ref": group["ncmdr_ref"], "manufacturer": group["manufacturer"], "detail_pdf": detail_pdf, "ade_detail_url": group["ade_detail_url"], **device.as_dict(), } ) total_device_rows += len(devices) else: rebuilt_rows.append( { "doctype": "SFDA Device Entries", "ncmdr_ref": group["ncmdr_ref"], "manufacturer": group["manufacturer"], "detail_pdf": detail_pdf, "ade_detail_url": group["ade_detail_url"], } ) doc.set("device_list", []) for row in rebuilt_rows: doc.append("device_list", row) doc.save(ignore_permissions=True) frappe.db.commit() return { "name": doc_name, "ncmdr_groups": len(groups), "device_rows": total_device_rows or len(rebuilt_rows), } def reparse_all_stored_pdf_device_lists(sfda_entries_name: str | None = None) -> dict: """Re-parse device_list from child-row detail_pdf for one or all SFDA Entries.""" if sfda_entries_name: names = [sfda_entries_name] else: names = frappe.db.sql_list( """ select distinct parent from `tabSFDA Device Entries` where ifnull(detail_pdf, '') != '' """ ) summary = { "processed": 0, "updated": 0, "total_device_rows": 0, "errors": [], } for name in names: summary["processed"] += 1 try: result = reparse_sfda_entry_device_list(name) summary["updated"] += 1 summary["total_device_rows"] += result.get("device_rows", 0) except Exception as exc: frappe.db.rollback() summary["errors"].append(f"{name}: {exc}") return summary