141 lines
4.4 KiB
Python
141 lines
4.4 KiB
Python
"""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
|