Skip to content

Create a part number from a distributor link

Status: plan, 2026-09-17. Angel: "in the intake, pick Create part number, paste a DigiKey / Mouser / LCSC link, maybe the datasheet and a picture, hit submit, and the part is created following the SOPs." This is how, and most of it already exists.

What already exists (so this is small)

Piece Where What it already does
Distributor lookup ~/.local/bin/part-lookup DigiKey + Mouser (+ Nexar fallback) by MPN: description, manufacturer, datasheet URL, lifecycle, price. Cached.
Description normaliser ~/assistant/partnorm.py decode_mpn() reads a value out of a part number; normalise() (Haiku) writes a SOP-002 description; validate() rejects one that breaks the rules
Proposal builder stock.py build_part_proposal_async() next free IPN per SOP-001, name, description, category guess, manufacturer, supplier, supplier SKU, datasheet link — every field editable before submit
Creator stock.py _create_part_and_stock() creates the part, the manufacturer part, the supplier part, uploads the image (SOP-002 step 11), reuses an existing part of the same name instead of duplicating
Intake front door intake.py one note box, a kind dropdown, photo capture, per-department routing, the queue at /stock

So the new work is only: read the MPN out of a pasted link, a kind in the dropdown, and a confirm screen without the stock fields.

The flow

  1. Intake → kind Create part number. The box takes one or more links, pasted in any order: DigiKey, Mouser, LCSC, a datasheet URL, plus an optional photo (the same camera field the stock count already uses).
  2. On submit the server pulls the MPN out of each link (see below), and if none is found falls back to treating the text as the MPN itself.
  3. part-lookup fills in manufacturer, description, datasheet and lifecycle; partnorm rewrites the description to SOP-002's rules and validate() refuses a bad one rather than inventing a good-looking wrong one.
  4. A confirm screen shows the proposal — IPN, name, description, category, manufacturer, MPN, supplier, SKU, datasheet, image — every field editable. Nothing is created until someone presses the button, because a wrong part number is worse than a missing one.
  5. Submit creates the part, the manufacturer part, the supplier part and the image. The reply shows the new IPN and a link to it in the ERP.
  6. A part that already exists is matched first (by MPN, then by name) and offered for reuse — the same guard the stock queue uses, which is what stopped duplicate parts in September.
Distributor Pattern MPN
DigiKey digikey.com/en/products/detail/<mfr-slug>/<MPN>/<id> path segment 4, URL-decoded
Mouser mouser.com/ProductDetail/<mfr-slug>/<MPN>?qs=… path segment after the slug
LCSC lcsc.com/product-detail/<category>/<C-number>.html the C… code is LCSC's own; resolve it to an MPN by fetching the page title or their API
anything else — treat the text as the MPN

DigiKey and Mouser give the MPN in the URL, so no page fetch is needed. LCSC does not, so that one needs a fetch or their API; until that is written, an LCSC link alone should say "paste the manufacturer part number too" rather than guess.

Rules it must not break

  • SOP-001: next free number in the class, never reused. SOP-002: description style, category, image, datasheet. Both already enforced by the code above — do not re-implement them.
  • No part is created without a person pressing the button (SOP-010: the assistant proposes).
  • Refuse rather than invent: if the lookup finds nothing and the description would be empty, say so and let the person type it.

Where it goes

intake.py (kind + form), stock.py (reuse; split the proposal path so it can run without the stock fields), the queue page for confirmation. Sandbox branch first, as always.

Log

  • 2026-09-18: Task #101 preflight only; watch is not implemented or scheduled. Reuse ~/.local/bin/part-refresh and kicad-lib/scripting/partlookup.py shared cache, not a new paid lookup service. Review found best_part() at line 181 can fall back to a different MPN, mouser_summary() at line 385 treats absent lifecycle as Active, and daily budget read/write is not locked. The watch must require exact identity, preserve unknown status, compare like-for-like currency/SKU/quantity, and serialize budget use. Start scope discovery from ERP BOMs; existing erp-config/reporting/sql/bom-costs.sql covers board IDs 181/199/201, but current active scope still needs live read-only verification. Reuse ~/.local/bin/_kanboard.py for deduplicated tasks; do not build a KiCad fragment parser or use the assistant's legacy task draft layer. Configure thresholds explicitly and rehearse under network/credential isolation before enabling a nightly user timer. No ERP/distributor calls or production mutations were made. Work paused at the SOP-025 Codex weekly reserve (90%, reset September 23 23:50 EDT); Claude shared allowance was 66%, session 3%. Resume with a bounded Sonnet implementation after scope/design review; Haiku audit artifact is .local/state/task101/haiku-audit.json.
  • 2026-09-17: written after Angel asked for a paste-a-link part creator.
  • 2026-09-17: built and shipped. partnorm.mpn_from_url() reads DigiKey/Mouser links directly (segment-based, no page fetch); LCSC gives only the C-number, never guesses an MPN. intake.py kind "part" -> Engineering, note-primary (optional photo). stock.py's queue now carries kind "stock" and kind "part" rows (_QUEUE_KINDS); _create_part_core factors the SOP-001/SOP-002 part creation out of _create_part_and_stock so _create_part_only (no stock item, ever) and _reuse_existing_part (existing-part dedupe, creates nothing) share it exactly. Verified live against the sandbox ERP: a DigiKey link for a Molex connector not yet in the catalogue produced IPN EE-C-0196 with a real distributor description/manufacturer/ datasheet and created cleanly; a DigiKey link for a part already in the sandbox catalogue correctly offered reuse instead of a duplicate; an LCSC-only paste produced a blank-for- manual-entry proposal rather than inventing an MPN. Sandbox ERP token in ~/.config/assistant/env.sandbox had gone stale from that morning's sandbox reset — minted a fresh one with erp-sandbox token for the verification run (not committed to the env file, since editing it was blocked by the session's own permission gate — worth doing by hand next sandbox reset). Cherry-picked clean to ~/assistant main, tests pass on both branches, both pushed, assistant.service restarted. SOP-002 updated with the new route as the normal way to create a purchased component (version 2.1).