diff --git a/skills/bom/references/ordering-and-fabrication.md b/skills/bom/references/ordering-and-fabrication.md index e150aec..b6b502a 100644 --- a/skills/bom/references/ordering-and-fabrication.md +++ b/skills/bom/references/ordering-and-fabrication.md @@ -16,12 +16,21 @@ Paste into the bulk-add box at [digikey.com/ordering/shoppingcart](https://www.d Format: `quantity, DigiKey_PN, customer_reference` — comma or tab delimited, one line per part. +Separate multiple reference designators with `/`, as shown above — the field itself is +comma-delimited. Files generated by `bom_manager.py order` already use this form +(`_write_digikey_order` rewrites commas to `/`). + **FastAdd URL** — programmatic cart building: ``` -https://www.digikey.com/classic/ordering/fastadd.aspx?part1=490-10698-1-ND&qty1=3&cref1=C1/C2/C5&part2=...&newcart=true +https://www.digikey.com/classic/ordering/fastadd.aspx?part1=490-10698-1-ND&qty1=3&cref1=C1/C2/C5&part2=... ``` GET supports ~1700 chars; POST supports 400+ parts. +**Do not add `&newcart=true` unless you mean it.** The default adds to the current cart; +`newcart=true` **starts a new empty cart**, and the shopping-cart page warns that creating +a new cart deletes the items currently in it. Omit it to append. +([DigiKey FastAdd guide](https://forum.digikey.com/t/digikey-fastadd-bulk-add-parts-into-a-digikey-cart-via-third-party-tooling-and-urls/61356)) + **BOM Manager** — [digikey.com/en/resources/bom-manager](https://www.digikey.com/en/resources/bom-manager). Upload CSV/XLS/XLSX, map columns interactively. Accepts both DigiKey PNs and MPNs. ### Mouser — Part List Import diff --git a/skills/kicad/scripts/analyze_thermal.py b/skills/kicad/scripts/analyze_thermal.py index 51e3a71..fc31b9e 100644 --- a/skills/kicad/scripts/analyze_thermal.py +++ b/skills/kicad/scripts/analyze_thermal.py @@ -442,7 +442,10 @@ def _compute_junction_temps(power_comps: list, pcb: dict, "category": "thermal", "severity": "info", "confidence": "heuristic" if rtheta_source == "default" else "deterministic", - "evidence_source": "datasheet" if rtheta_source == "package_table" else "heuristic_rule", + # rtheta_source is only ever "package_table" (footprint regex matched the + # generic PACKAGE_THERMAL_RESISTANCE average) or "default" — neither is + # per-MPN datasheet data, so neither may claim datasheet provenance. + "evidence_source": "heuristic_rule", "summary": f"Thermal: {ref} Tj={round(tj, 1)}C (margin {round(margin, 1)}C)", "description": f"Component {ref} in {pkg_name} package: Tj={round(tj, 1)}C, margin {round(margin, 1)}C to Tj_max ({tj_max}C).", "components": [ref], @@ -483,8 +486,9 @@ def _generate_findings(assessments: list) -> list: pdiss = a["pdiss_w"] pkg = a["package"] confidence = _thermal_confidence(a) - ev_source = ("datasheet" if a.get("rtheta_ja_source") == "package_table" - else "heuristic_rule") + # Both rtheta_ja_source values ("package_table", "default") are generic + # package averages, not per-MPN datasheet data. See analyze_thermal:394. + ev_source = "heuristic_rule" label = f"{ref} ({val})" if val else ref diff --git a/skills/kicad/scripts/lifecycle_audit.py b/skills/kicad/scripts/lifecycle_audit.py index b73290b..0df7d21 100644 --- a/skills/kicad/scripts/lifecycle_audit.py +++ b/skills/kicad/scripts/lifecycle_audit.py @@ -107,6 +107,22 @@ def _normalize_status(raw: str | None) -> str: # Temperature parsing # --------------------------------------------------------------------------- +def _temp_evidence_source(source: str | None) -> str: + """Map a temperature-range source tag to a finding evidence_source. + + "extraction_cache" is a per-MPN datasheet extraction and is genuinely + datasheet-backed; "api:" is a catalogue lookup. Anything else + (or nothing) is not evidence of either. + """ + if not source: + return 'heuristic_rule' + if source == 'extraction_cache': + return 'datasheet' + if source.startswith('api:'): + return 'api_lookup' + return 'heuristic_rule' + + def _parse_temp_range(text: str) -> tuple[float, float] | None: """Parse temperature range from distributor attribute string. @@ -759,7 +775,7 @@ def audit_bom(analysis_json: dict, project_dir: str | None = None, 'category': 'lifecycle', 'severity': 'info', 'confidence': 'deterministic', - 'evidence_source': 'datasheet', + 'evidence_source': 'api_lookup', 'summary': f'{mpn}: single source ({active_sources[0]})', 'description': f'Component {mpn} ({len(refs)} ref(s)) is only available from {active_sources[0]} out of {total_queried} sources checked.', 'components': sorted(refs), @@ -800,7 +816,7 @@ def audit_bom(analysis_json: dict, project_dir: str | None = None, 'category': 'lifecycle', 'severity': severity, 'confidence': 'deterministic', - 'evidence_source': 'datasheet', + 'evidence_source': 'api_lookup', 'summary': f'{mpn}: {max_lead_weeks} week lead time', 'description': f'Component {mpn} has {max_lead_weeks} week lead time (from {lead_source}).', 'components': sorted(refs), @@ -845,7 +861,9 @@ def audit_bom(analysis_json: dict, project_dir: str | None = None, "rule_id": "LT-001", "category": "temperature", "confidence": "deterministic", - "evidence_source": "api_lookup" if temp.get("source") else "heuristic_rule", + # "extraction_cache" is a real per-MPN datasheet extraction and must + # not be downgraded to api_lookup; "api:" is a lookup. + "evidence_source": _temp_evidence_source(temp.get("source")), "summary": f"{mpn}: rated {comp_grade} ({comp_min}C to {comp_max}C), design needs {design_min}C to {design_max}C", "components": sorted(refs), "nets": [],