From 3be7d2fc2122f684ffd97cc7a60a3b639f44d9ec Mon Sep 17 00:00:00 2001 From: Michele Bigi Date: Fri, 28 Aug 2026 02:18:50 +0200 Subject: [PATCH] Copy library datasheets onto every graph IC before review. Review skipped U* when the PDF was stored under a catalog MPN or the part was only in the netlist. Lookup now uses spelling variants and aliases. Co-authored-by: Cursor --- backend/services/pipeline.py | 32 ++++++++++++++++++++++++++------ backend/services/validation.py | 17 +++++++++++++---- 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/backend/services/pipeline.py b/backend/services/pipeline.py index cc191d4..f3ab1c3 100644 --- a/backend/services/pipeline.py +++ b/backend/services/pipeline.py @@ -681,11 +681,19 @@ async def _ensure_local_datasheet( """ if pdf_path.is_file(): return True - lib_ds_key = proj_svc.library_has_datasheet(ctx.storage, mpn) - if lib_ds_key: - ctx.storage.download_to_local(lib_ds_key, pdf_path) - return True - from backend.services.datasheet_finder import find_datasheet + from backend.services.datasheet_finder import find_datasheet, mpn_query_variants + + for name in mpn_query_variants(mpn) or [mpn]: + alt = pdf_path.parent / f"{safe_mpn(name)}.pdf" + if alt.is_file(): + if alt.resolve() != pdf_path.resolve(): + pdf_path.parent.mkdir(parents=True, exist_ok=True) + pdf_path.write_bytes(alt.read_bytes()) + return True + lib_ds_key = proj_svc.library_has_datasheet(ctx.storage, name) + if lib_ds_key: + ctx.storage.download_to_local(lib_ds_key, pdf_path) + return True broker.publish( ctx.project_id, "step_update", @@ -1474,7 +1482,19 @@ async def _stage_validation(ctx: PipelineContext) -> None: # Ensure all IC datasheet PDFs are available locally for review. # Cached ICs skipped pintable extraction, so their PDFs may not # have been downloaded yet. Auto-fetch fills remaining gaps. - for mpn in ctx.ic_mpns: + # Ensure PDFs for BOM ICs and any extra U* the netlist classified as IC. + seen_mpn: set[str] = set() + mpns_to_place = list(ctx.ic_mpns) + for comp in ctx.graph.components.values(): + if comp.component_type != ComponentType.IC: + continue + extra = (comp.mpn or "").strip() + if extra: + mpns_to_place.append(extra) + for mpn in mpns_to_place: + if mpn in seen_mpn: + continue + seen_mpn.add(mpn) safe = safe_mpn(mpn) pdf_path = ds_dir / f"{safe}.pdf" if not pdf_path.is_file(): diff --git a/backend/services/validation.py b/backend/services/validation.py index 3c078ff..36ffb7d 100644 --- a/backend/services/validation.py +++ b/backend/services/validation.py @@ -571,15 +571,24 @@ def _find_pdf( """Find the datasheet PDF for an MPN. Checks local dir first, then tries to download from the library. """ - safe = safe_mpn(mpn) - local = pdf_dir / f"{safe}.pdf" - if local.is_file(): - return local + from backend.services.datasheet_finder import mpn_query_variants + from backend.pinscopex.utils import safe_mpn as _safe + + names = mpn_query_variants(mpn) or [mpn] + for name in names: + local = pdf_dir / f"{_safe(name)}.pdf" + if local.is_file(): + wanted = pdf_dir / f"{_safe(mpn)}.pdf" + if local != wanted and not wanted.is_file(): + wanted.write_bytes(local.read_bytes()) + return wanted + return local if storage: from backend.services import projects as proj_svc lib_key = proj_svc.library_has_datasheet(storage, mpn) if lib_key: + local = pdf_dir / f"{_safe(mpn)}.pdf" storage.download_to_local(lib_key, local) if local.is_file(): return local