|
|
@@ -100,29 +100,29 @@ Example: `category=capacitor, package=6.3x5.8, voltage=25V` → LLM picks `"Alum
|
|
|
- Verified live: `categories.json`/`category_resolver.py`'s subcategory ids (e.g. `1142` = "Ceramic Capacitors") are directly usable as `catalogIdList` values against both endpoints — no id remapping layer required.
|
|
|
- Token mode: if `api_token` provided, use official LCSC API (`https://ips.lcsc.com/rest/wmsc2agent/`) with HMAC-signed requests — not yet implemented (`NotImplementedError`).
|
|
|
|
|
|
-### 5. Scouting Loop *(not yet built)*
|
|
|
-Drives each `BomRow` from `pending` to `confirmed` through an iterative user-guided process.
|
|
|
+### 5. Scouting Loop
|
|
|
+Drives each `BomRow` from `pending` to `confirmed` through an iterative user-guided process (`bom_assistant/scouting/orchestrator.py`).
|
|
|
|
|
|
```
|
|
|
pending
|
|
|
- → scouting (LLM infers LCSC subcategory, user confirms or picks different)
|
|
|
- → filtering (search within confirmed subcategory + param filters, user browses results)
|
|
|
- → confirmed (user picks a specific part)
|
|
|
- → flagged (DNP / non-electronic / user skips)
|
|
|
+ → scouting (LLM infers LCSC subcategory, user confirms or picks different) — built
|
|
|
+ → filtering (facet query + search within confirmed subcategory, user narrows/browses) — built (Package + Manufacturer filters only)
|
|
|
+ → confirmed (user picks a specific part) — built
|
|
|
+ → flagged (DNP / non-electronic / user skips) — built (ingestion-time only)
|
|
|
```
|
|
|
|
|
|
-Retry path: user rejects all results → back to scouting with modified parameters.
|
|
|
+Not yet built: per-component-param filters (Capacitance/Tolerance/Voltage Rating/... — `paramNameValueMap` request shape unverified live), pagination past page 1, removing/toggling off an applied filter, and the retry path (user rejects all results → back to scouting with modified parameters).
|
|
|
|
|
|
-### 6. API Layer *(not yet built)*
|
|
|
-FastAPI HTTP interface.
|
|
|
+### 6. API Layer
|
|
|
+FastAPI HTTP interface (`bom_assistant/api/routes.py`).
|
|
|
|
|
|
-- `POST /upload` — accept BOM file, run ingestion, return `BomSession`
|
|
|
-- `GET /session/{id}` — current session state
|
|
|
-- `GET /session/{id}/row/{row_id}/scout` — trigger LLM category inference, return `{category, alternatives[]}`
|
|
|
-- `POST /session/{id}/row/{row_id}/confirm-category` — user accepts/overrides category → state: filtering
|
|
|
-- `POST /session/{id}/row/{row_id}/search` — search within confirmed category + params, return page 1
|
|
|
-- `POST /session/{id}/row/{row_id}/confirm` — lock part pick, advance state to confirmed
|
|
|
-- `GET /session/{id}/export` — collate confirmed picks → CSV/XLSX download
|
|
|
+- `POST /upload` — accept BOM file, run ingestion, return `BomSession` (built)
|
|
|
+- `GET /session/{id}/row/{row_id}/scout` — trigger LLM category inference, sets `row.scout_candidates` (built)
|
|
|
+- `POST /session/{id}/row/{row_id}/confirm-category` — user accepts/overrides category → state: filtering, populates facets + initial search results (built)
|
|
|
+- `POST /session/{id}/row/{row_id}/apply-filter` — apply a Package/Manufacturer facet value, re-run facets + search (built)
|
|
|
+- `POST /session/{id}/row/{row_id}/confirm` — lock part pick, advance state to confirmed (built)
|
|
|
+- `GET /session/{id}` — current session state (not yet built)
|
|
|
+- `GET /session/{id}/export` — collate confirmed picks → CSV/XLSX download (not yet built)
|
|
|
|
|
|
### 7. Frontend *(not yet built)*
|
|
|
React SPA.
|
|
|
@@ -171,19 +171,27 @@ POST /upload
|
|
|
|
|
|
GET /session/{id}/row/{row_id}/scout
|
|
|
lcsc_category_resolver.resolve(row) ← cache hit or Haiku call
|
|
|
- → {subcategory: "Alum. Electrolytic...", id: 1140, alternatives: [...]}
|
|
|
+ → [LcscCategory("Alum. Electrolytic...", 1140), ...] (best-first, up to 3)
|
|
|
+ row.scout_candidates = [{name, id}, ...]
|
|
|
row.state → scouting
|
|
|
|
|
|
-POST /session/{id}/row/{row_id}/confirm-category {subcategory_id: 1140}
|
|
|
- row.applied_filters["lcsc_category_id"] = 1140
|
|
|
+POST /session/{id}/row/{row_id}/confirm-category {category_id: 1140, category_name: "..."}
|
|
|
+ row.resolved_category_id = 1140
|
|
|
+ row.resolved_category_name = "..."
|
|
|
+ row.applied_filters = {} ← reset; kept strictly as an LCSC filter payload fragment
|
|
|
row.state → filtering
|
|
|
-
|
|
|
-POST /session/{id}/row/{row_id}/search {filters: {voltage_min: 25, ...}, page: 1}
|
|
|
- lcsc.search(category_id=1140, params=row.normalized_params, filters=..., page=1)
|
|
|
- → {count: N, results: [...]}
|
|
|
-
|
|
|
-POST /session/{id}/row/{row_id}/confirm {pick: {...}}
|
|
|
- row.confirmed_pick = pick
|
|
|
+ _refresh_filtering(row):
|
|
|
+ row.facet_groups = lcsc.query_facets(1140, row.applied_filters)
|
|
|
+ result = lcsc.search(1140, row.normalized_params, row.applied_filters, page=1)
|
|
|
+ row.search_results = result.items
|
|
|
+ row.search_meta = {count, page, page_size, error}
|
|
|
+
|
|
|
+POST /session/{id}/row/{row_id}/apply-filter {group: "Package"|"Manufacturer", value: ...}
|
|
|
+ row.applied_filters[<encapValueList|brandIdList>].append(value)
|
|
|
+ _refresh_filtering(row) ← same as above, narrows facets + results
|
|
|
+
|
|
|
+POST /session/{id}/row/{row_id}/confirm {product_code: "C602037"}
|
|
|
+ row.confirmed_pick = <item from row.search_results matching product_code>
|
|
|
row.state → confirmed
|
|
|
|
|
|
GET /session/{id}/export
|
|
|
@@ -219,16 +227,25 @@ instant-bom/
|
|
|
│ │ ├── categories.py — load + filter category tree
|
|
|
│ │ ├── category_resolver.py — LLM category inference + file-backed cache
|
|
|
│ │ ├── category_cache.json — auto-updated by resolver
|
|
|
-│ │ └── lcsc.py — search adapter (scraper + token mode)
|
|
|
+│ │ └── lcsc.py — search adapter (live wmsc.lcsc.com API + token mode stub)
|
|
|
+│ │
|
|
|
+│ ├── scouting/
|
|
|
+│ │ ├── ingest.py — bytes → BomSession (parser → column_mapper → normalizer)
|
|
|
+│ │ ├── orchestrator.py — scout_row, confirm_category, apply_filter, confirm_pick
|
|
|
+│ │ └── errors.py — SessionNotFoundError, RowNotFoundError, ScoutingValidationError
|
|
|
│ │
|
|
|
-│ └── api/ (not yet built)
|
|
|
-│ └── main.py — FastAPI app + routes
|
|
|
+│ └── api/
|
|
|
+│ ├── main.py — FastAPI app + exception handlers
|
|
|
+│ ├── routes.py — HTTP routes (HTMX-rendered partials)
|
|
|
+│ ├── store.py — in-memory SessionStore
|
|
|
+│ └── templates/ — Jinja2 partials for the HTMX test page
|
|
|
│
|
|
|
-├── frontend/ (not yet built)
|
|
|
+├── frontend/ (not yet built — React SPA; HTMX test page above covers interim needs)
|
|
|
│ └── src/
|
|
|
│
|
|
|
├── examples/
|
|
|
-│ └── bom/ — 12 real BOM test files (CSV, XLSX, XLS)
|
|
|
+│ ├── full/ — real BOM test files (CSV, XLSX, XLS)
|
|
|
+│ └── reduced/ — same files, trimmed
|
|
|
│
|
|
|
└── requirements.txt
|
|
|
```
|