orchestrator.py 3.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. from __future__ import annotations
  2. from bom_assistant.scouting.errors import RowNotFoundError, ScoutingValidationError
  3. from bom_assistant.session.models import BomRow, BomSession, RowState
  4. from bom_assistant.suppliers.lcsc.category_resolver import resolve
  5. from bom_assistant.suppliers.lcsc.lcsc import LcscAdapter
  6. _GROUP_FILTER_KEYS = {"Package": "encapValueList", "Manufacturer": "brandIdList"}
  7. def get_row(session: BomSession, row_id: str) -> BomRow:
  8. for row in session.rows:
  9. if row.row_id == row_id:
  10. return row
  11. raise RowNotFoundError(session.session_id, row_id)
  12. def scout_row(session: BomSession, row_id: str) -> BomRow:
  13. row = get_row(session, row_id)
  14. candidates = resolve(row)
  15. row.scout_candidates = [{"name": c.name, "id": c.id} for c in candidates]
  16. row.state = RowState.scouting
  17. return row
  18. def _refresh_filtering(row: BomRow) -> None:
  19. adapter = LcscAdapter()
  20. row.facet_groups = adapter.query_facets(row.resolved_category_id, row.applied_filters)
  21. result = adapter.search(row.resolved_category_id, row.normalized_params, row.applied_filters, page=1)
  22. row.search_results = result.items
  23. row.search_meta = {
  24. "count": result.count,
  25. "page": result.page,
  26. "page_size": result.page_size,
  27. "error": result.error,
  28. }
  29. def _preseed_package_filter(session: BomSession, row_id: str) -> None:
  30. row = get_row(session, row_id)
  31. package = row.normalized_params.package
  32. if not package:
  33. return
  34. options = [opt.get("name") for opt in row.facet_groups.get("Package", [])]
  35. match = next((opt for opt in options if opt and opt.lower() == package.lower()), None)
  36. if match:
  37. apply_filter(session, row_id, "Package", match)
  38. def confirm_category(session: BomSession, row_id: str, category_id: int, category_name: str) -> BomRow:
  39. row = get_row(session, row_id)
  40. row.resolved_category_id = category_id
  41. row.resolved_category_name = category_name
  42. row.applied_filters = {}
  43. row.confirmed_pick = None
  44. row.state = RowState.filtering
  45. _refresh_filtering(row)
  46. _preseed_package_filter(session, row_id)
  47. return row
  48. def apply_filter(session: BomSession, row_id: str, group: str, value: str) -> BomRow:
  49. row = get_row(session, row_id)
  50. if row.resolved_category_id is None:
  51. raise ScoutingValidationError("row has no confirmed category yet")
  52. key = _GROUP_FILTER_KEYS.get(group)
  53. if key is None:
  54. raise ScoutingValidationError(f"unsupported filter group: {group}")
  55. row.applied_filters.setdefault(key, [])
  56. if value not in row.applied_filters[key]:
  57. row.applied_filters[key].append(value)
  58. _refresh_filtering(row)
  59. return row
  60. def confirm_pick(session: BomSession, row_id: str, product_code: str) -> BomRow:
  61. row = get_row(session, row_id)
  62. pick = next((item for item in row.search_results if item.get("productCode") == product_code), None)
  63. if pick is None:
  64. raise ScoutingValidationError(f"product not found in current results: {product_code}")
  65. row.confirmed_pick = pick
  66. row.state = RowState.confirmed
  67. return row