classifier.py 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. from __future__ import annotations
  2. import re
  3. from bom_assistant.session.models import ComponentCategory
  4. _PREFIX_MAP: dict[str, ComponentCategory] = {
  5. "C": ComponentCategory.capacitor,
  6. "R": ComponentCategory.resistor,
  7. "RV": ComponentCategory.resistor,
  8. "L": ComponentCategory.inductor,
  9. "D": ComponentCategory.diode,
  10. "U": ComponentCategory.ic,
  11. "IC": ComponentCategory.ic,
  12. "J": ComponentCategory.connector,
  13. "P": ComponentCategory.connector,
  14. "CON": ComponentCategory.connector,
  15. "USB": ComponentCategory.connector,
  16. "Q": ComponentCategory.mosfet,
  17. "T": ComponentCategory.mosfet,
  18. "X": ComponentCategory.crystal,
  19. "Y": ComponentCategory.crystal,
  20. "NTC": ComponentCategory.sensor,
  21. "RT": ComponentCategory.sensor,
  22. }
  23. _LED_KEYWORDS = {"red", "grn", "blu", "green", "blue", "led", "yellow", "white", "amber"}
  24. _HW_RE = re.compile(
  25. r"^(PCB|PCBA|screw|washer|nut|lock\s*nut|heatsink|standoff|spacer|heat\s*sink)",
  26. re.IGNORECASE,
  27. )
  28. # Value-string heuristics (used when no designators)
  29. _CAP_RE = re.compile(r"\d+\.?\d*\s*[pnuµ][fF]?$", re.IGNORECASE)
  30. _RES_RE = re.compile(r"(\d+[kKMR]|\d+\.?\d*\s*[kKMΩR]$|\d+[kK]\d+)", re.IGNORECASE)
  31. _PART_NUM_RE = re.compile(r"^[A-Z]{2,}[\dA-Z\-]{3,}$")
  32. def classify(designators: list[str], raw_value: str) -> ComponentCategory:
  33. prefix = _extract_prefix(designators[0]) if designators else ""
  34. category = _PREFIX_MAP.get(prefix.upper())
  35. if category == ComponentCategory.diode:
  36. low = raw_value.lower()
  37. if any(kw in low for kw in _LED_KEYWORDS):
  38. return ComponentCategory.led
  39. return ComponentCategory.diode
  40. if category is not None:
  41. return category
  42. # no designator match — use value heuristics
  43. return _classify_by_value(raw_value)
  44. def is_non_electronic(raw_value: str) -> bool:
  45. return bool(_HW_RE.match(raw_value.strip()))
  46. def _extract_prefix(designator: str) -> str:
  47. m = re.match(r"^([A-Za-z]+)", designator.strip())
  48. return m.group(1) if m else ""
  49. def _classify_by_value(raw_value: str) -> ComponentCategory:
  50. v = raw_value.strip()
  51. if _CAP_RE.match(v):
  52. return ComponentCategory.capacitor
  53. if _RES_RE.match(v):
  54. return ComponentCategory.resistor
  55. # looks like a part number: ≥5 chars, mixed alpha+digit, no unit suffix
  56. if _PART_NUM_RE.match(v) and len(v) >= 5:
  57. return ComponentCategory.ic
  58. return ComponentCategory.other