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