from __future__ import annotations from bom_assistant.scouting.errors import RowNotFoundError, ScoutingValidationError from bom_assistant.session.models import BomRow, BomSession, RowState from bom_assistant.suppliers.lcsc.category_resolver import resolve from bom_assistant.suppliers.lcsc.lcsc import LcscAdapter _GROUP_FILTER_KEYS = {"Package": "encapValueList", "Manufacturer": "brandIdList"} def get_row(session: BomSession, row_id: str) -> BomRow: for row in session.rows: if row.row_id == row_id: return row raise RowNotFoundError(session.session_id, row_id) def scout_row(session: BomSession, row_id: str) -> BomRow: row = get_row(session, row_id) candidates = resolve(row) row.scout_candidates = [{"name": c.name, "id": c.id} for c in candidates] row.state = RowState.scouting return row def _refresh_filtering(row: BomRow) -> None: adapter = LcscAdapter() row.facet_groups = adapter.query_facets(row.resolved_category_id, row.applied_filters) result = adapter.search(row.resolved_category_id, row.normalized_params, row.applied_filters, page=1) row.search_results = result.items row.search_meta = { "count": result.count, "page": result.page, "page_size": result.page_size, "error": result.error, } def _preseed_package_filter(session: BomSession, row_id: str) -> None: row = get_row(session, row_id) package = row.normalized_params.package if not package: return options = [opt.get("name") for opt in row.facet_groups.get("Package", [])] match = next((opt for opt in options if opt and opt.lower() == package.lower()), None) if match: apply_filter(session, row_id, "Package", match) def confirm_category(session: BomSession, row_id: str, category_id: int, category_name: str) -> BomRow: row = get_row(session, row_id) row.resolved_category_id = category_id row.resolved_category_name = category_name row.applied_filters = {} row.confirmed_pick = None row.state = RowState.filtering _refresh_filtering(row) _preseed_package_filter(session, row_id) return row def apply_filter(session: BomSession, row_id: str, group: str, value: str) -> BomRow: row = get_row(session, row_id) if row.resolved_category_id is None: raise ScoutingValidationError("row has no confirmed category yet") key = _GROUP_FILTER_KEYS.get(group) if key is None: raise ScoutingValidationError(f"unsupported filter group: {group}") row.applied_filters.setdefault(key, []) if value not in row.applied_filters[key]: row.applied_filters[key].append(value) _refresh_filtering(row) return row def confirm_pick(session: BomSession, row_id: str, product_code: str) -> BomRow: row = get_row(session, row_id) pick = next((item for item in row.search_results if item.get("productCode") == product_code), None) if pick is None: raise ScoutingValidationError(f"product not found in current results: {product_code}") row.confirmed_pick = pick row.state = RowState.confirmed return row