scouting_loop.md 4.2 KB

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: the full pending → scouting → filtering → confirmed loop is implemented: GET .../scout (category inference), POST .../confirm-category (locks category, populates facets + initial results), POST .../apply-filter (Package/Manufacturer only — narrows facets + results), POST .../confirm (locks a pick). Not yet built: per-component param filters (Capacitance/Tolerance/...), pagination, removing an applied filter, retry/re-scout, and export (see docs/backlog.md).

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

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

Walkthrough

  1. Upload (built) — user uploads a BOM file. POST /upload runs parse_bommap_columnsnormalize, 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 (built)POST .../confirm-category — user accepts the top candidate or picks an alternative; row locks in resolved_category_id/resolved_category_name, resets applied_filters, and moves to filtering.
  4. Facet query + Search (built — Package/Manufacturer only) — confirming a category (and every apply-filter call after) eagerly calls LcscAdapter.query_facets(category_id, applied_filters) and LcscAdapter.search(category_id, params, applied_filters, page=1), storing results on row.facet_groups/row.search_results. POST .../apply-filter lets the user narrow by a Package or Manufacturer facet value; per-component param filters (Capacitance/Tolerance/...) are not wired — the request shape LCSC expects to select a param value was never captured live, so it's deferred rather than guessed. applied_filters is kept strictly as the raw LCSC filter payload fragment (no app-internal keys mixed in) so it passes straight through with no translation layer.
  5. Confirm pick (built)POST .../confirm — user selects a specific part by productCode from row.search_results; 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 6–7 and per-param filtering within step 4 are the remaining gaps — see docs/backlog.md → Todo → Scouting Loop / API.

See also

  • docs/architecture.md — full intended module design
  • docs/backlog.md — current Done/Todo status per component