|
|
@@ -0,0 +1,74 @@
|
|
|
+# Scouting Loop
|
|
|
+
|
|
|
+The scouting loop drives each `BomRow` from `pending` to `confirmed` through a
|
|
|
+user-in-the-loop cycle: AI infers an LCSC subcategory → user confirms or
|
|
|
+overrides it → parametric search within that subcategory → user picks a part.
|
|
|
+Design source of truth: `docs/architecture.md` (§5 "Scouting Loop", "User
|
|
|
+Flow", "System Flow"). Build status source of truth: `docs/backlog.md`.
|
|
|
+
|
|
|
+**Current reality:** only the first hop (`pending → scouting`, i.e. category
|
|
|
+inference) is implemented, via `GET /session/{id}/row/{row_id}/scout`. The
|
|
|
+rest of the loop (`confirm-category`, `search`, `confirm`, `export`) is
|
|
|
+designed but intentionally deferred until inference itself is trusted through
|
|
|
+interactive testing (see `docs/backlog.md` → API section, "deferred" notes).
|
|
|
+
|
|
|
+## State diagram
|
|
|
+
|
|
|
+`RowState` per `bom_assistant/session/models.py`. Solid edges are built,
|
|
|
+dashed edges are designed but not yet implemented.
|
|
|
+
|
|
|
+Source: [`diagrams/scouting_loop_state.puml`](diagrams/scouting_loop_state.puml)
|
|
|
+
|
|
|
+Note: today's `orchestrator.scout_row` always transitions to `scouting`, even
|
|
|
+when `resolve()` returns zero candidates (it does not auto-flag the row) —
|
|
|
+the empty-result case is left as `scout_candidates: []` for the UI to show
|
|
|
+"no candidates found," rather than kicking the row to `flagged`. Auto-flagging
|
|
|
+on empty results, and a `flag_reason` field to distinguish that from
|
|
|
+normalizer-level flagging, was scoped out as part of the deferred loop work.
|
|
|
+
|
|
|
+## Sequence diagram
|
|
|
+
|
|
|
+Actors: User, API (`bom_assistant/api/routes.py`), Orchestrator
|
|
|
+(`bom_assistant/scouting/orchestrator.py`), LcscCategoryResolver
|
|
|
+(`bom_assistant/suppliers/lcsc/category_resolver.py`), LcscAdapter
|
|
|
+(`bom_assistant/suppliers/lcsc/lcsc.py`). Dashed arrows = not yet built.
|
|
|
+
|
|
|
+Source: [`diagrams/scouting_loop_sequence.puml`](diagrams/scouting_loop_sequence.puml)
|
|
|
+
|
|
|
+## Walkthrough
|
|
|
+
|
|
|
+1. **Upload** *(built)* — user uploads a BOM file. `POST /upload` runs
|
|
|
+ `parse_bom` → `map_columns` → `normalize`, returns a `BomSession` with all
|
|
|
+ rows `pending` (or `flagged` for DNP/non-electronic rows).
|
|
|
+2. **Scout** *(built)* — for a `pending` row, `GET .../scout` calls
|
|
|
+ `LcscCategoryResolver.resolve(row)` (file-cached, falls back to a Claude
|
|
|
+ Haiku call) and returns up to 3 ranked LCSC subcategory candidates. Row
|
|
|
+ moves to `scouting`. Can be re-triggered freely.
|
|
|
+3. **Confirm category** *(deferred)* — user accepts the top candidate or
|
|
|
+ picks an alternative; row locks in `resolved_category_id` and moves to
|
|
|
+ `filtering`.
|
|
|
+4. **Facet query + Search** *(data layer built, not yet wired to an
|
|
|
+ endpoint)* — `LcscAdapter.query_facets(category_id, filters)` calls LCSC's
|
|
|
+ real facet API (`wmsc.lcsc.com/ftps/wm/product/query/param/group`) to get
|
|
|
+ manufacturer/package/param filter groups scoped to the category and
|
|
|
+ whatever's already selected; `LcscAdapter.search(category_id, params,
|
|
|
+ filters, page)` calls the matching product-list endpoint. Both were
|
|
|
+ reverse-engineered from live traffic and verified end-to-end against real
|
|
|
+ category-resolver output — see `docs/backlog.md`. No `GET .../facets` or
|
|
|
+ `POST .../search` route exists yet.
|
|
|
+5. **Confirm pick** *(deferred)* — user selects a specific part; row locks
|
|
|
+ `confirmed_pick` and moves to `confirmed`.
|
|
|
+6. **Retry** *(deferred)* — if the user rejects all search results, the row
|
|
|
+ can go back to `scouting` with adjusted parameters rather than being a
|
|
|
+ dead end.
|
|
|
+7. **Export** *(deferred)* — once rows are `confirmed` (or explicitly
|
|
|
+ skipped), `GET .../export` collates them into an order-ready BOM.
|
|
|
+
|
|
|
+Steps 3–7 are why the API currently exposes only `/upload` and `/scout`: the
|
|
|
+loop is built incrementally, and the next slice only gets built once category
|
|
|
+inference (step 2) has been exercised enough through the HTMX test page to
|
|
|
+trust building on top of it.
|
|
|
+
|
|
|
+## See also
|
|
|
+- `docs/architecture.md` — full intended module design
|
|
|
+- `docs/backlog.md` — current Done/Todo status per component
|