Bläddra i källkod

feat: ai search of categories

n2749 2 veckor sedan
förälder
incheckning
8b09a2bbd6

+ 0 - 0
bom_assistant/ai/__init__.py


+ 25 - 0
bom_assistant/ai/client.py

@@ -0,0 +1,25 @@
+from __future__ import annotations
+
+import os
+
+from dotenv import load_dotenv
+from openai import OpenAI
+
+load_dotenv()
+
+_DEFAULT_MODEL = "google/gemini-flash-1.5"
+
+
+def complete(prompt: str, max_tokens: int = 200) -> str:
+    """Call OpenRouter with prompt, return response text. Raises on failure."""
+    client = OpenAI(
+        base_url="https://openrouter.ai/api/v1",
+        api_key=os.environ["OPENROUTER_API_KEY"],
+    )
+    model = os.environ.get("OPENROUTER_MODEL", _DEFAULT_MODEL)
+    resp = client.chat.completions.create(
+        model=model,
+        max_tokens=max_tokens,
+        messages=[{"role": "user", "content": prompt}],
+    )
+    return resp.choices[0].message.content.strip()

+ 0 - 0
bom_assistant/suppliers/__init__.py


+ 30 - 0
bom_assistant/suppliers/base.py

@@ -0,0 +1,30 @@
+from __future__ import annotations
+
+from abc import ABC, abstractmethod
+from dataclasses import dataclass, field
+from typing import Any
+
+from bom_assistant.session.models import NormalizedParams
+
+
+@dataclass
+class SearchResult:
+    count: int
+    page: int
+    page_size: int
+    items: list[dict[str, Any]]
+    error: str | None = None
+
+
+class SupplierAdapter(ABC):
+    def __init__(self, api_token: str | None = None) -> None:
+        self.api_token = api_token
+
+    @abstractmethod
+    def search(
+        self,
+        category_id: int,
+        params: NormalizedParams,
+        filters: dict[str, Any] | None = None,
+        page: int = 1,
+    ) -> SearchResult: ...

+ 95 - 0
bom_assistant/suppliers/category_resolver.py

@@ -0,0 +1,95 @@
+from __future__ import annotations
+
+import json
+import re
+from dataclasses import dataclass
+from pathlib import Path
+
+from bom_assistant.ai.client import complete
+from bom_assistant.session.models import BomRow
+
+
+@dataclass
+class ResolvedCategory:
+    name: str
+    id: int
+
+
+def resolve_category(
+    row: BomRow,
+    subcats: list[dict],
+    supplier: str,
+    cache_path: Path,
+) -> ResolvedCategory | None:
+    """Map a BomRow to a supplier subcategory using LLM. Cache hit → instant; miss → LLM call → cached."""
+    cache = _load(cache_path)
+    key = _cache_key(row)
+    if key in cache:
+        e = cache[key]
+        return ResolvedCategory(e["name"], e["id"])
+    if not subcats:
+        return None
+    result = _ai_resolve(row, subcats, supplier)
+    if result:
+        cache[key] = {"name": result.name, "id": result.id}
+        _save(cache, cache_path)
+    return result
+
+
+def _cache_key(row: BomRow) -> str:
+    p = row.normalized_params
+    return f"{row.category.value}|{p.package or ''}|{p.value_str or row.raw_value}"
+
+
+def _ai_resolve(row: BomRow, subcats: list[dict], supplier: str) -> ResolvedCategory | None:
+    p = row.normalized_params
+    lines: list[str] = [f"  type: {row.category.value}"]
+    if p.value_str:
+        lines.append(f"  value: {p.value_str}")
+    if p.package:
+        lines.append(f"  package: {p.package}")
+    if p.voltage_rating is not None:
+        lines.append(f"  voltage_rating: {p.voltage_rating}V")
+    if p.current_rating is not None:
+        lines.append(f"  current_rating: {p.current_rating}A")
+    if p.part_number:
+        lines.append(f"  part_number: {p.part_number}")
+    if row.raw_value and row.raw_value != p.value_str:
+        lines.append(f"  raw_value: {row.raw_value!r}")
+
+    subcat_lines = "\n".join(f"  {s['name']}" for s in subcats)
+    prompt = (
+        f"You are an electronics sourcing expert. Pick the best {supplier.upper()} subcategory for this component.\n\n"
+        "Component:\n"
+        + "\n".join(lines)
+        + "\n\nAvailable subcategories:\n"
+        + subcat_lines
+        + '\n\nReply ONLY with JSON: {"subcategory": "<exact name from list above>"}'
+    )
+    try:
+        text = complete(prompt, max_tokens=100)
+        m = re.search(r'"subcategory"\s*:\s*"([^"]+)"', text)
+        if not m:
+            return None
+        return _lookup(m.group(1).strip(), subcats)
+    except Exception:
+        return None
+
+
+def _lookup(chosen: str, subcats: list[dict]) -> ResolvedCategory | None:
+    cl = chosen.lower()
+    for s in subcats:
+        if s["name"].lower() == cl:
+            return ResolvedCategory(s["name"], s["id"])
+    for s in subcats:
+        if cl in s["name"].lower() or s["name"].lower() in cl:
+            return ResolvedCategory(s["name"], s["id"])
+    return None
+
+
+def _load(path: Path) -> dict:
+    return json.loads(path.read_text(encoding="utf-8")) if path.exists() else {}
+
+
+def _save(cache: dict, path: Path) -> None:
+    path.write_text(json.dumps(cache, indent=2, ensure_ascii=False), encoding="utf-8")

+ 0 - 0
bom_assistant/suppliers/lcsc/__init__.py


+ 2507 - 0
bom_assistant/suppliers/lcsc/categories.json

@@ -0,0 +1,2507 @@
+[
+  {
+    "group": "Audio",
+    "category": "Audio Products",
+    "category_id": 3,
+    "subcategories": [
+      {
+        "name": "Alarms, Buzzers, and Sirens",
+        "id": 51
+      },
+      {
+        "name": "Audio Products Accessories",
+        "id": 50
+      },
+      {
+        "name": "Buzzer Elements, Piezo Benders",
+        "id": 1405
+      },
+      {
+        "name": "Microphones",
+        "id": 54
+      },
+      {
+        "name": "Speakers",
+        "id": 55
+      }
+    ]
+  },
+  {
+    "group": "Circuit Protection",
+    "category": "Circuit Protection",
+    "category_id": 8,
+    "subcategories": [
+      {
+        "name": "Circuit breaker accessories",
+        "id": 113
+      },
+      {
+        "name": "Circuit Breakers",
+        "id": 1411
+      },
+      {
+        "name": "Circuit Protection Accessories",
+        "id": 1406
+      },
+      {
+        "name": "Equipment circuit breaker",
+        "id": 121
+      },
+      {
+        "name": "Fuseholders",
+        "id": 123
+      },
+      {
+        "name": "Fuses",
+        "id": 124
+      },
+      {
+        "name": "Gas Discharge Tube Arresters (GDT)",
+        "id": 125
+      },
+      {
+        "name": "Ground Fault Circuit Interrupter (GFCI)",
+        "id": 122
+      },
+      {
+        "name": "Inrush Current Limiters (ICL)",
+        "id": 126
+      },
+      {
+        "name": "Miniature Circuit Breaker",
+        "id": 114
+      },
+      {
+        "name": "Miniature leakage protection circuit breaker",
+        "id": 119
+      },
+      {
+        "name": "Miniature Residual Current Circuit Breaker",
+        "id": 116
+      },
+      {
+        "name": "Molded case circuit breakers",
+        "id": 117
+      },
+      {
+        "name": "Motor Protective Circuit Breaker",
+        "id": 115
+      },
+      {
+        "name": "Power Distribution, Surge Protectors",
+        "id": 1407
+      },
+      {
+        "name": "PTC Resettable Fuses",
+        "id": 127
+      },
+      {
+        "name": "Residual Current Circuit Breaker",
+        "id": 118
+      },
+      {
+        "name": "Safety circuit breaker",
+        "id": 120
+      },
+      {
+        "name": "Surge Suppression ICs",
+        "id": 128
+      },
+      {
+        "name": "Thermal Cutoffs (Thermal Fuses)",
+        "id": 129
+      },
+      {
+        "name": "Transient Voltage Suppressors (TVS)",
+        "id": 130
+      },
+      {
+        "name": "Mixed Technology",
+        "id": 699
+      },
+      {
+        "name": "Surge Protection Devices (SPDs)",
+        "id": 700
+      },
+      {
+        "name": "Thyristors (TSS)",
+        "id": 701
+      },
+      {
+        "name": "TVS Diodes",
+        "id": 702
+      },
+      {
+        "name": "Varistors, MOVs",
+        "id": 131
+      }
+    ]
+  },
+  {
+    "group": "Connectors",
+    "category": "Connectors, Interconnects",
+    "category_id": 10,
+    "subcategories": [
+      {
+        "name": "AC Power Connectors",
+        "id": 150
+      },
+      {
+        "name": "Plugs and Receptacles",
+        "id": 703
+      },
+      {
+        "name": "Power Entry Connector Accessories",
+        "id": 704
+      },
+      {
+        "name": "Power Entry Modules (PEM)",
+        "id": 705
+      },
+      {
+        "name": "Travel adapters",
+        "id": 1305
+      },
+      {
+        "name": "Backplane Connectors",
+        "id": 151
+      },
+      {
+        "name": "ARINC",
+        "id": 706
+      },
+      {
+        "name": "ARINC Inserts",
+        "id": 707
+      },
+      {
+        "name": "Backplane Connector Accessories",
+        "id": 708
+      },
+      {
+        "name": "Backplane Connector Contacts",
+        "id": 709
+      },
+      {
+        "name": "Backplane Connector Housings",
+        "id": 710
+      },
+      {
+        "name": "DIN 41612",
+        "id": 711
+      },
+      {
+        "name": "Hard Metric, Standard",
+        "id": 712
+      },
+      {
+        "name": "Specialized Connectors",
+        "id": 713
+      },
+      {
+        "name": "Banana and Tip Connectors",
+        "id": 152
+      },
+      {
+        "name": "Banana and Tip Connector Accessories",
+        "id": 714
+      },
+      {
+        "name": "Banana and Tip Connector Adapters",
+        "id": 715
+      },
+      {
+        "name": "Binding Posts",
+        "id": 716
+      },
+      {
+        "name": "Jacks, Plugs",
+        "id": 717
+      },
+      {
+        "name": "Barrel Connectors",
+        "id": 153
+      },
+      {
+        "name": "Audio Connectors",
+        "id": 718
+      },
+      {
+        "name": "Barrel Connector Adapters",
+        "id": 720
+      },
+      {
+        "name": "Power Connectors",
+        "id": 721
+      },
+      {
+        "name": "Between Series Adapters",
+        "id": 154
+      },
+      {
+        "name": "Blade Type Power Connectors",
+        "id": 155
+      },
+      {
+        "name": "Blade Type Power Connector Accessories",
+        "id": 722
+      },
+      {
+        "name": "Blade Type Power Connector Assemblies",
+        "id": 723
+      },
+      {
+        "name": "Blade Type Power Connector Contacts",
+        "id": 724
+      },
+      {
+        "name": "Blade Type Power Connector Housings",
+        "id": 725
+      },
+      {
+        "name": "Card Edge Connectors",
+        "id": 156
+      },
+      {
+        "name": "Card Edge Connector Accessories",
+        "id": 726
+      },
+      {
+        "name": "Card Edge Connector Adapters",
+        "id": 727
+      },
+      {
+        "name": "Card Edge Connector Contacts",
+        "id": 728
+      },
+      {
+        "name": "Card Edge Connector Housings",
+        "id": 729
+      },
+      {
+        "name": "Edgeboard Connectors",
+        "id": 730
+      },
+      {
+        "name": "Circular Connectors",
+        "id": 157
+      },
+      {
+        "name": "Backshells and Cable Clamps",
+        "id": 731
+      },
+      {
+        "name": "Circular Connector Accessories",
+        "id": 732
+      },
+      {
+        "name": "Circular Connector Adapters",
+        "id": 733
+      },
+      {
+        "name": "Circular Connector Assemblies",
+        "id": 734
+      },
+      {
+        "name": "Circular Connector Contacts",
+        "id": 735
+      },
+      {
+        "name": "Circular Connector Housings",
+        "id": 736
+      },
+      {
+        "name": "Coaxial Connectors (RF)",
+        "id": 158
+      },
+      {
+        "name": "Coaxial Connector (RF) Accessories",
+        "id": 737
+      },
+      {
+        "name": "Coaxial Connector (RF) Adapters",
+        "id": 738
+      },
+      {
+        "name": "Coaxial Connector (RF) Assemblies",
+        "id": 739
+      },
+      {
+        "name": "Coaxial Connector (RF) Contacts",
+        "id": 740
+      },
+      {
+        "name": "Coaxial Connector (RF) Terminators",
+        "id": 741
+      },
+      {
+        "name": "Connector Adapter Kits",
+        "id": 1318
+      },
+      {
+        "name": "Contacts",
+        "id": 159
+      },
+      {
+        "name": "Contacts, Spring Loaded (Pogo Pins), and Pressure",
+        "id": 742
+      },
+      {
+        "name": "Leadframe",
+        "id": 743
+      },
+      {
+        "name": "Multi Purpose",
+        "id": 744
+      },
+      {
+        "name": "D-Sub, D-Shaped Connectors",
+        "id": 160
+      },
+      {
+        "name": "Centronics Connectors",
+        "id": 745
+      },
+      {
+        "name": "D-Sub Connector",
+        "id": 746
+      },
+      {
+        "name": "D-Sub, D-Shaped Connector Accessories",
+        "id": 747
+      },
+      {
+        "name": "D-Sub, D-Shaped Connector Adapters",
+        "id": 748
+      },
+      {
+        "name": "D-Sub, D-Shaped Connector Backshells, Hoods",
+        "id": 749
+      },
+      {
+        "name": "D-Sub, D-Shaped Connector Contacts",
+        "id": 750
+      },
+      {
+        "name": "D-Sub, D-Shaped Connector Housings",
+        "id": 751
+      },
+      {
+        "name": "D-Sub, D-Shaped Connector Jackscrews",
+        "id": 752
+      },
+      {
+        "name": "D-Sub, D-Shaped Connector Terminators",
+        "id": 753
+      },
+      {
+        "name": "FFC, FPC (Flat Flexible) Connectors",
+        "id": 161
+      },
+      {
+        "name": "FFC, FPC (Flat Flexible) Connector Accessories",
+        "id": 754
+      },
+      {
+        "name": "FFC, FPC (Flat Flexible) Connector Assemblies",
+        "id": 755
+      },
+      {
+        "name": "FFC, FPC (Flat Flexible) Connector Contacts",
+        "id": 756
+      },
+      {
+        "name": "FFC, FPC (Flat Flexible) Connector Housings",
+        "id": 757
+      },
+      {
+        "name": "Fiber Optic Connectors",
+        "id": 162
+      },
+      {
+        "name": "Fiber Optic Connector Accessories",
+        "id": 758
+      },
+      {
+        "name": "Fiber Optic Connector Adapters",
+        "id": 759
+      },
+      {
+        "name": "Fiber Optic Connector Assemblies",
+        "id": 760
+      },
+      {
+        "name": "Fiber Optic Connector Housings",
+        "id": 761
+      },
+      {
+        "name": "Heavy Duty Connectors",
+        "id": 163
+      },
+      {
+        "name": "Heavy Duty Connector Accessories",
+        "id": 762
+      },
+      {
+        "name": "Heavy Duty Connector Assemblies",
+        "id": 763
+      },
+      {
+        "name": "Heavy Duty Connector Contacts",
+        "id": 764
+      },
+      {
+        "name": "Heavy Duty Connector Frames",
+        "id": 765
+      },
+      {
+        "name": "Heavy Duty Connector Housings, Hoods, Bases",
+        "id": 766
+      },
+      {
+        "name": "Heavy Duty Connector Inserts, Modules",
+        "id": 767
+      },
+      {
+        "name": "Keystone Connectors",
+        "id": 164
+      },
+      {
+        "name": "Keystone Connector Accessories",
+        "id": 768
+      },
+      {
+        "name": "Keystone Faceplates, Frames",
+        "id": 769
+      },
+      {
+        "name": "Keystone Inserts",
+        "id": 770
+      },
+      {
+        "name": "LGH Connectors",
+        "id": 165
+      },
+      {
+        "name": "Memory Connectors",
+        "id": 166
+      },
+      {
+        "name": "Inline Module Sockets",
+        "id": 771
+      },
+      {
+        "name": "Memory Connector Accessories",
+        "id": 772
+      },
+      {
+        "name": "PC Card Sockets",
+        "id": 773
+      },
+      {
+        "name": "Modular Connectors",
+        "id": 167
+      },
+      {
+        "name": "Modular/Ethernet Connector Accessories",
+        "id": 775
+      },
+      {
+        "name": "Modular/Ethernet Connector Adapters",
+        "id": 776
+      },
+      {
+        "name": "Modular/Ethernet Connector Jacks",
+        "id": 777
+      },
+      {
+        "name": "Modular/Ethernet Connector Jacks With Magnetics",
+        "id": 778
+      },
+      {
+        "name": "Modular/Ethernet Connector Plug Housings",
+        "id": 779
+      },
+      {
+        "name": "Modular/Ethernet Connector Plugs",
+        "id": 780
+      },
+      {
+        "name": "Modular/Ethernet Connector Wiring Blocks",
+        "id": 781
+      },
+      {
+        "name": "Patchbay, Jack Panel Accessories",
+        "id": 1327
+      },
+      {
+        "name": "Photovoltaic (Solar Panel) Connectors",
+        "id": 168
+      },
+      {
+        "name": "Photovoltaic (Solar Panel) Connector Accessories",
+        "id": 783
+      },
+      {
+        "name": "Photovoltaic (Solar Panel) Connector Assemblies",
+        "id": 784
+      },
+      {
+        "name": "Pluggable Connectors",
+        "id": 169
+      },
+      {
+        "name": "Pluggable Connector Accessories",
+        "id": 786
+      },
+      {
+        "name": "Pluggable Connector Assemblies",
+        "id": 787
+      },
+      {
+        "name": "Rectangular Connectors",
+        "id": 170
+      },
+      {
+        "name": "Arrays, Edge Type, Mezzanine (Board to Board)",
+        "id": 788
+      },
+      {
+        "name": "Board In, Direct Wire to Board",
+        "id": 789
+      },
+      {
+        "name": "Board Spacers, Stackers (Board to Board)",
+        "id": 790
+      },
+      {
+        "name": "Free Hanging, Panel Mount",
+        "id": 791
+      },
+      {
+        "name": "Headers, Male Pins",
+        "id": 792
+      },
+      {
+        "name": "Headers, Receptacles, Female Sockets",
+        "id": 793
+      },
+      {
+        "name": "Headers, Specialty Pin",
+        "id": 794
+      },
+      {
+        "name": "Rectangular Connector Accessories",
+        "id": 795
+      },
+      {
+        "name": "Rectangular Connector Adapters",
+        "id": 796
+      },
+      {
+        "name": "Rectangular Connector Contacts",
+        "id": 797
+      },
+      {
+        "name": "Rectangular Connector Housings",
+        "id": 798
+      },
+      {
+        "name": "Spring Loaded",
+        "id": 799
+      },
+      {
+        "name": "Shunts, Jumpers",
+        "id": 171
+      },
+      {
+        "name": "Sockets for ICs, Transistors",
+        "id": 172
+      },
+      {
+        "name": "IC Sockets",
+        "id": 800
+      },
+      {
+        "name": "Socket Accessories",
+        "id": 801
+      },
+      {
+        "name": "Socket Adapters",
+        "id": 802
+      },
+      {
+        "name": "Solid State Lighting Connectors",
+        "id": 173
+      },
+      {
+        "name": "Solid State Lighting Connector Accessories",
+        "id": 803
+      },
+      {
+        "name": "Solid State Lighting Connector Assemblies",
+        "id": 804
+      },
+      {
+        "name": "Solid State Lighting Connector Contacts",
+        "id": 805
+      },
+      {
+        "name": "Terminal Blocks",
+        "id": 174
+      },
+      {
+        "name": "Barrier Blocks",
+        "id": 806
+      },
+      {
+        "name": "Din Rail, Channel",
+        "id": 807
+      },
+      {
+        "name": "Headers, Plugs and Sockets",
+        "id": 808
+      },
+      {
+        "name": "Interface Modules",
+        "id": 809
+      },
+      {
+        "name": "Panel Mount",
+        "id": 810
+      },
+      {
+        "name": "Power Distribution",
+        "id": 811
+      },
+      {
+        "name": "Specialized Terminal Blocks",
+        "id": 812
+      },
+      {
+        "name": "Terminal Block Accessories",
+        "id": 813
+      },
+      {
+        "name": "Terminal Block Adapters",
+        "id": 817
+      },
+      {
+        "name": "Terminal Block Contacts",
+        "id": 818
+      },
+      {
+        "name": "Wire to Board",
+        "id": 819
+      },
+      {
+        "name": "Terminal Junction Systems",
+        "id": 175
+      },
+      {
+        "name": "Terminal Strips and Turret Boards",
+        "id": 176
+      },
+      {
+        "name": "Terminals",
+        "id": 177
+      },
+      {
+        "name": "Barrel, Bullet Connectors",
+        "id": 820
+      },
+      {
+        "name": "Foil Connectors",
+        "id": 821
+      },
+      {
+        "name": "Housings, Boots",
+        "id": 822
+      },
+      {
+        "name": "Knife Connectors",
+        "id": 823
+      },
+      {
+        "name": "Lugs",
+        "id": 824
+      },
+      {
+        "name": "Magnetic Wire Connectors",
+        "id": 825
+      },
+      {
+        "name": "PC Pin Receptacles, Socket Connectors",
+        "id": 826
+      },
+      {
+        "name": "PC Pin, Single Post Connectors",
+        "id": 827
+      },
+      {
+        "name": "Quick Connects, Quick Disconnect Connectors",
+        "id": 828
+      },
+      {
+        "name": "Ring Connectors",
+        "id": 829
+      },
+      {
+        "name": "Screw Connectors",
+        "id": 830
+      },
+      {
+        "name": "Solder Lug Connectors",
+        "id": 831
+      },
+      {
+        "name": "Spade Connectors",
+        "id": 832
+      },
+      {
+        "name": "Specialized Connectors Terminals",
+        "id": 833
+      },
+      {
+        "name": "Terminal Accessories",
+        "id": 834
+      },
+      {
+        "name": "Terminal Adapters",
+        "id": 835
+      },
+      {
+        "name": "Turret Connectors",
+        "id": 836
+      },
+      {
+        "name": "Wire Pin Connectors",
+        "id": 837
+      },
+      {
+        "name": "Wire Splice Connectors",
+        "id": 838
+      },
+      {
+        "name": "Wire to Board Connectors",
+        "id": 839
+      },
+      {
+        "name": "USB, DVI, HDMI Connectors",
+        "id": 178
+      },
+      {
+        "name": "USB, DVI, HDMI Connector Accessories",
+        "id": 840
+      },
+      {
+        "name": "USB, DVI, HDMI Connector Adapters",
+        "id": 841
+      },
+      {
+        "name": "USB, DVI, HDMI Connector Assemblies",
+        "id": 842
+      }
+    ]
+  },
+  {
+    "group": "Discrete Semiconductors",
+    "category": "Discrete Semiconductors",
+    "category_id": 12,
+    "subcategories": [
+      {
+        "name": "Current Regulation - Diodes, Transistors",
+        "id": 184
+      },
+      {
+        "name": "Diodes",
+        "id": 185
+      },
+      {
+        "name": "Bridge Rectifiers",
+        "id": 856
+      },
+      {
+        "name": "Rectifiers",
+        "id": 1427
+      },
+      {
+        "name": "RF Diodes",
+        "id": 859
+      },
+      {
+        "name": "Variable Capacitance (Varicaps, Varactors)",
+        "id": 860
+      },
+      {
+        "name": "Zener",
+        "id": 1428
+      },
+      {
+        "name": "JFETs",
+        "id": 191
+      },
+      {
+        "name": "Power Driver Modules",
+        "id": 186
+      },
+      {
+        "name": "Programmable Unijunction Transistors",
+        "id": 192
+      },
+      {
+        "name": "Special Purpose Transistors",
+        "id": 193
+      },
+      {
+        "name": "Thyristors",
+        "id": 187
+      },
+      {
+        "name": "DIACs, SIDACs",
+        "id": 863
+      },
+      {
+        "name": "SCRs",
+        "id": 864
+      },
+      {
+        "name": "SCRs - Modules",
+        "id": 865
+      },
+      {
+        "name": "TRIACs",
+        "id": 866
+      },
+      {
+        "name": "Transistors",
+        "id": 1420
+      },
+      {
+        "name": "Bipolar (BJT)",
+        "id": 1421
+      },
+      {
+        "name": "FETs, MOSFETs",
+        "id": 1433
+      },
+      {
+        "name": "IGBTs",
+        "id": 1437
+      }
+    ]
+  },
+  {
+    "group": "Thermal",
+    "category": "Fans, Thermal Management, HVAC",
+    "category_id": 14,
+    "subcategories": [
+      {
+        "name": "Air Conditioner Accessories",
+        "id": 206
+      },
+      {
+        "name": "DC Brushless Fans (BLDC)",
+        "id": 198
+      },
+      {
+        "name": "Fan Accessories",
+        "id": 199
+      },
+      {
+        "name": "Finger Guards, Filters & Sleeves",
+        "id": 201
+      },
+      {
+        "name": "Heat Sinks",
+        "id": 202
+      },
+      {
+        "name": "Heater Accessories",
+        "id": 214
+      },
+      {
+        "name": "Heating Equipment",
+        "id": 212
+      },
+      {
+        "name": "Pads, Sheets",
+        "id": 204
+      },
+      {
+        "name": "Thermal Pad",
+        "id": 203
+      },
+      {
+        "name": "Thermoelectric, Peltier Modules",
+        "id": 1445
+      }
+    ]
+  },
+  {
+    "group": "ICs",
+    "category": "Integrated Circuits (ICs)",
+    "category_id": 17,
+    "subcategories": [
+      {
+        "name": "Audio Special Purpose",
+        "id": 255
+      },
+      {
+        "name": "Clock/Timing",
+        "id": 256
+      },
+      {
+        "name": "Application Specific Clock/Timing",
+        "id": 923
+      },
+      {
+        "name": "Clock Buffers, Drivers",
+        "id": 924
+      },
+      {
+        "name": "Clock Generators, PLLs, Frequency Synthesizers",
+        "id": 925
+      },
+      {
+        "name": "Programmable Timers and Oscillators",
+        "id": 928
+      },
+      {
+        "name": "Real Time Clocks",
+        "id": 929
+      },
+      {
+        "name": "Data Acquisition",
+        "id": 257
+      },
+      {
+        "name": "ADCs/DACs - Special Purpose",
+        "id": 930
+      },
+      {
+        "name": "Analog Front End (AFE)",
+        "id": 931
+      },
+      {
+        "name": "Analog to Digital Converters (ADC)",
+        "id": 932
+      },
+      {
+        "name": "Digital Potentiometers",
+        "id": 933
+      },
+      {
+        "name": "Digital to Analog Converters (DAC)",
+        "id": 934
+      },
+      {
+        "name": "Touch Screen Controllers",
+        "id": 935
+      },
+      {
+        "name": "Embedded",
+        "id": 258
+      },
+      {
+        "name": "Application Specific Microcontrollers",
+        "id": 936
+      },
+      {
+        "name": "CPLDs (Complex Programmable Logic Devices)",
+        "id": 937
+      },
+      {
+        "name": "DSP (Digital Signal Processors)",
+        "id": 938
+      },
+      {
+        "name": "FPGAs (Field Programmable Gate Array)",
+        "id": 939
+      },
+      {
+        "name": "FPGAs (Field Programmable Gate Array) with Microcontrollers",
+        "id": 940
+      },
+      {
+        "name": "Microcontrollers",
+        "id": 941
+      },
+      {
+        "name": "Microcontrollers, Microprocessor, FPGA Modules",
+        "id": 942
+      },
+      {
+        "name": "Microprocessors",
+        "id": 943
+      },
+      {
+        "name": "PLDs (Programmable Logic Device)",
+        "id": 944
+      },
+      {
+        "name": "System On Chip (SoC)",
+        "id": 945
+      },
+      {
+        "name": "Interface",
+        "id": 259
+      },
+      {
+        "name": "Analog Switches - Special Purpose",
+        "id": 946
+      },
+      {
+        "name": "Analog Switches, Multiplexers, Demultiplexers",
+        "id": 947
+      },
+      {
+        "name": "CODECS",
+        "id": 948
+      },
+      {
+        "name": "Direct Digital Synthesis (DDS)",
+        "id": 950
+      },
+      {
+        "name": "Drivers, Receivers, Transceivers",
+        "id": 951
+      },
+      {
+        "name": "Encoders, Decoders, Converters",
+        "id": 952
+      },
+      {
+        "name": "Filters - Active",
+        "id": 953
+      },
+      {
+        "name": "I/O Expanders",
+        "id": 954
+      },
+      {
+        "name": "Interface Controllers",
+        "id": 949
+      },
+      {
+        "name": "Modems - ICs and Modules",
+        "id": 955
+      },
+      {
+        "name": "Modules",
+        "id": 956
+      },
+      {
+        "name": "Sensor and Detector Interfaces",
+        "id": 957
+      },
+      {
+        "name": "Sensor, Capacitive Touch",
+        "id": 958
+      },
+      {
+        "name": "Serializers, Deserializers",
+        "id": 959
+      },
+      {
+        "name": "Signal Buffers, Repeaters, Splitters",
+        "id": 960
+      },
+      {
+        "name": "Signal Terminators",
+        "id": 961
+      },
+      {
+        "name": "Specialized",
+        "id": 962
+      },
+      {
+        "name": "Telecom",
+        "id": 963
+      },
+      {
+        "name": "UARTs (Universal Asynchronous Receiver Transmitter)",
+        "id": 964
+      },
+      {
+        "name": "Voice Record and Playback",
+        "id": 965
+      },
+      {
+        "name": "Linear",
+        "id": 260
+      },
+      {
+        "name": "Amplifiers",
+        "id": 966
+      },
+      {
+        "name": "Analog Multipliers, Dividers",
+        "id": 967
+      },
+      {
+        "name": "Linear Comparators",
+        "id": 968
+      },
+      {
+        "name": "Video Processing",
+        "id": 969
+      },
+      {
+        "name": "Logic",
+        "id": 261
+      },
+      {
+        "name": "Buffers, Drivers, Receivers, Transceivers",
+        "id": 970
+      },
+      {
+        "name": "Counters, Dividers",
+        "id": 972
+      },
+      {
+        "name": "FIFOs Memory",
+        "id": 973
+      },
+      {
+        "name": "Flip Flops",
+        "id": 974
+      },
+      {
+        "name": "Gates and Inverters",
+        "id": 975
+      },
+      {
+        "name": "Gates and Inverters - Multi-Function, Configurable",
+        "id": 976
+      },
+      {
+        "name": "Latches",
+        "id": 977
+      },
+      {
+        "name": "Logic Comparators",
+        "id": 971
+      },
+      {
+        "name": "Multivibrators",
+        "id": 978
+      },
+      {
+        "name": "Parity Generators and Checkers",
+        "id": 979
+      },
+      {
+        "name": "Shift Registers",
+        "id": 980
+      },
+      {
+        "name": "Signal Switches, Multiplexers, Decoders",
+        "id": 981
+      },
+      {
+        "name": "Specialty Logic",
+        "id": 982
+      },
+      {
+        "name": "Translators, Level Shifters",
+        "id": 983
+      },
+      {
+        "name": "Universal Bus Functions",
+        "id": 984
+      },
+      {
+        "name": "Memory",
+        "id": 262
+      },
+      {
+        "name": "Configuration PROMs for FPGAs",
+        "id": 990
+      },
+      {
+        "name": "Memory (ICs)",
+        "id": 1288
+      },
+      {
+        "name": "Memory Controllers",
+        "id": 992
+      },
+      {
+        "name": "Power Management (PMIC)",
+        "id": 263
+      },
+      {
+        "name": "AC DC Converters, Offline Switchers",
+        "id": 1003
+      },
+      {
+        "name": "Battery Chargers ICs",
+        "id": 1004
+      },
+      {
+        "name": "Battery Management",
+        "id": 1005
+      },
+      {
+        "name": "Current Regulation/Management",
+        "id": 1006
+      },
+      {
+        "name": "DC DC Switching Controllers",
+        "id": 1007
+      },
+      {
+        "name": "Display Drivers",
+        "id": 1008
+      },
+      {
+        "name": "Energy Metering",
+        "id": 1009
+      },
+      {
+        "name": "Full Half-Bridge (H Bridge) Drivers",
+        "id": 1010
+      },
+      {
+        "name": "Gate Drivers",
+        "id": 1011
+      },
+      {
+        "name": "Hot Swap Controllers",
+        "id": 1012
+      },
+      {
+        "name": "Laser Drivers",
+        "id": 1013
+      },
+      {
+        "name": "LED Drivers ICs",
+        "id": 1014
+      },
+      {
+        "name": "Lighting, Ballast Controllers",
+        "id": 1015
+      },
+      {
+        "name": "Motor Drivers, Controllers",
+        "id": 1016
+      },
+      {
+        "name": "OR Controllers, Ideal Diodes",
+        "id": 1017
+      },
+      {
+        "name": "PFC (Power Factor Correction)",
+        "id": 1018
+      },
+      {
+        "name": "Power Distribution Switches, Load Drivers",
+        "id": 1019
+      },
+      {
+        "name": "Power Management - Specialized",
+        "id": 1020
+      },
+      {
+        "name": "Power Over Ethernet (PoE) Controllers",
+        "id": 1021
+      },
+      {
+        "name": "Power Supply Controllers, Monitors",
+        "id": 1022
+      },
+      {
+        "name": "RMS to DC Converters",
+        "id": 1023
+      },
+      {
+        "name": "Special Purpose Regulators",
+        "id": 1024
+      },
+      {
+        "name": "Supervisors",
+        "id": 1025
+      },
+      {
+        "name": "Thermal Management",
+        "id": 1026
+      },
+      {
+        "name": "V/F and F/V Converters",
+        "id": 1027
+      },
+      {
+        "name": "Voltage Reference",
+        "id": 1028
+      },
+      {
+        "name": "Voltage Regulators - DC DC Switching Regulators",
+        "id": 1029
+      },
+      {
+        "name": "Voltage Regulators - Linear + Switching",
+        "id": 1030
+      },
+      {
+        "name": "Voltage Regulators - Linear Regulator Controllers",
+        "id": 1031
+      },
+      {
+        "name": "Voltage Regulators - Linear, Low Drop Out (LDO) Regulators",
+        "id": 1032
+      },
+      {
+        "name": "Specialized ICs",
+        "id": 264
+      }
+    ]
+  },
+  {
+    "group": "ICs",
+    "category": "Isolators",
+    "category_id": 18,
+    "subcategories": [
+      {
+        "name": "Digital Isolators",
+        "id": 265
+      },
+      {
+        "name": "Isolators - Gate Drivers",
+        "id": 266
+      },
+      {
+        "name": "Optocouplers, Optoisolators",
+        "id": 267
+      },
+      {
+        "name": "Logic Output Optoisolators",
+        "id": 1033
+      },
+      {
+        "name": "Transistor, Photovoltaic Output Optoisolators",
+        "id": 1034
+      },
+      {
+        "name": "Triac, SCR Output Optoisolators",
+        "id": 1035
+      },
+      {
+        "name": "Special Purpose Isolators",
+        "id": 268
+      }
+    ]
+  },
+  {
+    "group": "Optoelectronics",
+    "category": "Optoelectronics",
+    "category_id": 28,
+    "subcategories": [
+      {
+        "name": "Circuit Board Indicators, Arrays, Light Bars, Bar Graphs",
+        "id": 386
+      },
+      {
+        "name": "Display Backlights",
+        "id": 388
+      },
+      {
+        "name": "Display, Monitor - LCD Driver/Controller",
+        "id": 390
+      },
+      {
+        "name": "Fiber Optic Attenuators",
+        "id": 392
+      },
+      {
+        "name": "Fiber Optic Receivers",
+        "id": 393
+      },
+      {
+        "name": "Fiber Optic Switches, Multiplexers, Demultiplexers",
+        "id": 394
+      },
+      {
+        "name": "Fiber Optic Transceiver Modules",
+        "id": 395
+      },
+      {
+        "name": "Fiber Optic Transmitters - Discrete",
+        "id": 396
+      },
+      {
+        "name": "Fiber Optic Transmitters - Drive Circuitry Integrated",
+        "id": 397
+      },
+      {
+        "name": "Incandescent, Neon Lamps",
+        "id": 400
+      },
+      {
+        "name": "Laser Diodes, Modules",
+        "id": 403
+      },
+      {
+        "name": "LCD, OLED Character and Numeric",
+        "id": 404
+      },
+      {
+        "name": "LCD, OLED, Graphic",
+        "id": 405
+      },
+      {
+        "name": "LED Addressable, Specialty",
+        "id": 406
+      },
+      {
+        "name": "LED Character and Numeric",
+        "id": 407
+      },
+      {
+        "name": "LED COBs, Engines, Modules, Strips",
+        "id": 408
+      },
+      {
+        "name": "LED Color Lighting",
+        "id": 409
+      },
+      {
+        "name": "LED Dot Matrix and Cluster",
+        "id": 410
+      },
+      {
+        "name": "LED Emitters - Infrared, UV, Visible",
+        "id": 411
+      },
+      {
+        "name": "LED Indication - Discrete",
+        "id": 412
+      },
+      {
+        "name": "LED White Lighting",
+        "id": 415
+      },
+      {
+        "name": "Lenses",
+        "id": 416
+      },
+      {
+        "name": "Light Pipes",
+        "id": 417
+      },
+      {
+        "name": "Optic Spacers, Standoffs",
+        "id": 423
+      },
+      {
+        "name": "Optoelectronics Accessories",
+        "id": 419
+      },
+      {
+        "name": "Reflectors",
+        "id": 421
+      },
+      {
+        "name": "Vacuum Fluorescent (VFD)",
+        "id": 425
+      },
+      {
+        "name": "Xenon Lighting",
+        "id": 426
+      }
+    ]
+  },
+  {
+    "group": "Passives",
+    "category": "Passives",
+    "category_id": 30,
+    "subcategories": [
+      {
+        "name": "Capacitors",
+        "id": 495
+      },
+      {
+        "name": "Aluminum - Polymer Capacitors",
+        "id": 1139
+      },
+      {
+        "name": "Aluminum Electrolytic Capacitors",
+        "id": 1140
+      },
+      {
+        "name": "Capacitor Networks, Arrays",
+        "id": 1141
+      },
+      {
+        "name": "Ceramic Capacitors",
+        "id": 1142
+      },
+      {
+        "name": "Electric Double Layer Capacitors (EDLC), Supercapacitors",
+        "id": 1143
+      },
+      {
+        "name": "Film Capacitors",
+        "id": 1144
+      },
+      {
+        "name": "Mica and PTFE Capacitors",
+        "id": 1145
+      },
+      {
+        "name": "Motor Start, Motor Run Capacitors (AC)",
+        "id": 1146
+      },
+      {
+        "name": "Niobium Oxide Capacitors",
+        "id": 1147
+      },
+      {
+        "name": "Silicon Capacitors",
+        "id": 1148
+      },
+      {
+        "name": "Tantalum - Polymer Capacitors",
+        "id": 1149
+      },
+      {
+        "name": "Tantalum Capacitors",
+        "id": 1150
+      },
+      {
+        "name": "Thin Film Capacitors",
+        "id": 1151
+      },
+      {
+        "name": "Trimmers, Variable Capacitors",
+        "id": 1152
+      },
+      {
+        "name": "Crystals, Oscillators, Resonators",
+        "id": 496
+      },
+      {
+        "name": "Crystal, Oscillator, Resonator Accessories",
+        "id": 1154
+      },
+      {
+        "name": "Crystals",
+        "id": 1155
+      },
+      {
+        "name": "Oscillators",
+        "id": 1157
+      },
+      {
+        "name": "Pin Configurable/Selectable Oscillators",
+        "id": 1158
+      },
+      {
+        "name": "Programmable Oscillators",
+        "id": 1159
+      },
+      {
+        "name": "Resonators",
+        "id": 1160
+      },
+      {
+        "name": "Stand Alone Programmers",
+        "id": 1161
+      },
+      {
+        "name": "VCOs (Voltage Controlled Oscillators)",
+        "id": 1162
+      },
+      {
+        "name": "Filters",
+        "id": 497
+      },
+      {
+        "name": "Cable Ferrites",
+        "id": 1163
+      },
+      {
+        "name": "Ceramic Filters",
+        "id": 1164
+      },
+      {
+        "name": "Common Mode Chokes",
+        "id": 1165
+      },
+      {
+        "name": "EMI/RFI Filters (LC, RC Networks)",
+        "id": 1168
+      },
+      {
+        "name": "Feed Through Capacitors",
+        "id": 1169
+      },
+      {
+        "name": "Ferrite Beads and Chips",
+        "id": 1170
+      },
+      {
+        "name": "Filter Accessories",
+        "id": 1172
+      },
+      {
+        "name": "Monolithic Crystals",
+        "id": 1174
+      },
+      {
+        "name": "Power Line Filter Modules",
+        "id": 1175
+      },
+      {
+        "name": "RF Filters",
+        "id": 1176
+      },
+      {
+        "name": "SAW Filters",
+        "id": 1177
+      },
+      {
+        "name": "Inductors, Coils, Chokes",
+        "id": 498
+      },
+      {
+        "name": "Adjustable Inductors",
+        "id": 1178
+      },
+      {
+        "name": "Arrays, Signal Transformers",
+        "id": 1179
+      },
+      {
+        "name": "Delay Lines",
+        "id": 1180
+      },
+      {
+        "name": "Fixed Inductors",
+        "id": 1181
+      },
+      {
+        "name": "Wireless Charging Coils",
+        "id": 1182
+      },
+      {
+        "name": "Magnetics - Transformer, Inductor Components",
+        "id": 499
+      },
+      {
+        "name": "Bobbins (Coil Formers), Mounts, Hardware",
+        "id": 1184
+      },
+      {
+        "name": "Ferrite Cores",
+        "id": 1185
+      },
+      {
+        "name": "Magnet Wire",
+        "id": 1186
+      },
+      {
+        "name": "Potentiometers, Variable Resistors",
+        "id": 500
+      },
+      {
+        "name": "Adjustable Power Resistor",
+        "id": 1188
+      },
+      {
+        "name": "Rotary Potentiometers, Rheostats",
+        "id": 1191
+      },
+      {
+        "name": "Scale Dials",
+        "id": 1192
+      },
+      {
+        "name": "Slide Potentiometers",
+        "id": 1193
+      },
+      {
+        "name": "Thumbwheel Potentiometers",
+        "id": 1194
+      },
+      {
+        "name": "Trimmer Potentiometers",
+        "id": 1195
+      },
+      {
+        "name": "Value Display Potentiometers",
+        "id": 1196
+      },
+      {
+        "name": "Resistors",
+        "id": 501
+      },
+      {
+        "name": "Chassis Mount Resistors",
+        "id": 1198
+      },
+      {
+        "name": "Chip Resistor - Surface Mount",
+        "id": 1199
+      },
+      {
+        "name": "Current Sense Resistors",
+        "id": 1336
+      },
+      {
+        "name": "Resistor Kits",
+        "id": 1201
+      },
+      {
+        "name": "Resistor Networks, Arrays",
+        "id": 1200
+      },
+      {
+        "name": "Resistors Accessories",
+        "id": 1197
+      },
+      {
+        "name": "Specialized Resistors",
+        "id": 1202
+      },
+      {
+        "name": "Through Hole Resistors",
+        "id": 1203
+      }
+    ]
+  },
+  {
+    "group": "Power",
+    "category": "Power Supplies",
+    "category_id": 32,
+    "subcategories": [
+      {
+        "name": "Power Suppliers - Board Mount",
+        "id": 509
+      },
+      {
+        "name": "AC DC Converters",
+        "id": 1375
+      },
+      {
+        "name": "Board Mount Power Supply Accessorie",
+        "id": 1377
+      },
+      {
+        "name": "DC DC Converters",
+        "id": 1378
+      },
+      {
+        "name": "LED Drivers",
+        "id": 1379
+      },
+      {
+        "name": "Power Supplies - External/Internal (Off-Board)",
+        "id": 510
+      },
+      {
+        "name": "AC AC Wall Power Adapters",
+        "id": 1376
+      },
+      {
+        "name": "AC DC Configurable Power Supplies (Factory Assembled)",
+        "id": 1380
+      },
+      {
+        "name": "AC DC Configurable Power Supply Chassis",
+        "id": 1381
+      },
+      {
+        "name": "AC DC Configurable Power Supply Modules",
+        "id": 1382
+      },
+      {
+        "name": "AC DC Converters (Off-Board)",
+        "id": 1383
+      },
+      {
+        "name": "AC DC Desktop, Wall Power Adapters",
+        "id": 1384
+      },
+      {
+        "name": "Computer Power Supply Units",
+        "id": 1390
+      },
+      {
+        "name": "DC AC Inverters",
+        "id": 1391
+      },
+      {
+        "name": "DC DC Converters (Off-Board)",
+        "id": 1385
+      },
+      {
+        "name": "DC Power Supplies",
+        "id": 1392
+      },
+      {
+        "name": "External/Internal Power Supply Accessories",
+        "id": 1386
+      },
+      {
+        "name": "Industrial, DIN Rail Power Supplies",
+        "id": 1387
+      },
+      {
+        "name": "LED Drivers (Off-Board)",
+        "id": 1388
+      },
+      {
+        "name": "Power over Ethernet (PoE)",
+        "id": 1389
+      },
+      {
+        "name": "Uninterruptible Power Supply (UPS) Systems",
+        "id": 1393
+      }
+    ]
+  },
+  {
+    "group": "Relays",
+    "category": "Relays",
+    "category_id": 34,
+    "subcategories": [
+      {
+        "name": "Automotive Relays",
+        "id": 531
+      },
+      {
+        "name": "Contactors (Electromechanical)",
+        "id": 530
+      },
+      {
+        "name": "Contactors (Solid State)",
+        "id": 529
+      },
+      {
+        "name": "High Frequency (RF) Relays",
+        "id": 532
+      },
+      {
+        "name": "Industrial Relays",
+        "id": 528
+      },
+      {
+        "name": "Power Relays",
+        "id": 533
+      },
+      {
+        "name": "Reed Relays",
+        "id": 536
+      },
+      {
+        "name": "Relay Modules",
+        "id": 526
+      },
+      {
+        "name": "Relay Sockets",
+        "id": 537
+      },
+      {
+        "name": "Relay Switches",
+        "id": 527
+      },
+      {
+        "name": "Relays Accessories",
+        "id": 524
+      },
+      {
+        "name": "Safety Relays",
+        "id": 1308
+      },
+      {
+        "name": "Signal Relays",
+        "id": 534
+      },
+      {
+        "name": "Solid State Relays (SSR)",
+        "id": 538
+      }
+    ]
+  },
+  {
+    "group": "RF",
+    "category": "RF and Wireless",
+    "category_id": 35,
+    "subcategories": [
+      {
+        "name": "Attenuators",
+        "id": 539
+      },
+      {
+        "name": "Balun",
+        "id": 540
+      },
+      {
+        "name": "RF Accessories",
+        "id": 541
+      },
+      {
+        "name": "RF Amplifiers",
+        "id": 542
+      },
+      {
+        "name": "RF Antennas",
+        "id": 543
+      },
+      {
+        "name": "RF Circulators and Isolators",
+        "id": 544
+      },
+      {
+        "name": "RF Demodulators",
+        "id": 545
+      },
+      {
+        "name": "RF Detectors",
+        "id": 546
+      },
+      {
+        "name": "RF Directional Coupler",
+        "id": 547
+      },
+      {
+        "name": "RF Front End (LNA + PA)",
+        "id": 548
+      },
+      {
+        "name": "RF Misc ICs and Modules",
+        "id": 549
+      },
+      {
+        "name": "RF Mixers",
+        "id": 550
+      },
+      {
+        "name": "RF Modulators",
+        "id": 551
+      },
+      {
+        "name": "RF Multiplexers",
+        "id": 552
+      },
+      {
+        "name": "RF Power Controller ICs",
+        "id": 553
+      },
+      {
+        "name": "RF Power Dividers/Splitters",
+        "id": 554
+      },
+      {
+        "name": "RF Receiver, Transmitter, and Transceiver Finished Units",
+        "id": 555
+      },
+      {
+        "name": "RF Receivers",
+        "id": 556
+      },
+      {
+        "name": "RF Shields",
+        "id": 557
+      },
+      {
+        "name": "RF Switches",
+        "id": 558
+      },
+      {
+        "name": "RF Transceiver ICs",
+        "id": 559
+      },
+      {
+        "name": "RF Transceiver Modules and Modems",
+        "id": 560
+      },
+      {
+        "name": "RF Transmitters",
+        "id": 561
+      },
+      {
+        "name": "RFI and EMI - Contacts, Fingerstock and Gaskets",
+        "id": 562
+      },
+      {
+        "name": "RFI and EMI - Shielding and Absorbing Materials",
+        "id": 563
+      },
+      {
+        "name": "RFID Antennas",
+        "id": 565
+      },
+      {
+        "name": "RFID Reader Modules",
+        "id": 566
+      },
+      {
+        "name": "RFID Transponders, Tags",
+        "id": 567
+      },
+      {
+        "name": "RFID, RF Access, Monitoring ICs",
+        "id": 568
+      },
+      {
+        "name": "Subscriber Identification Module (SIM) Cards",
+        "id": 569
+      }
+    ]
+  },
+  {
+    "group": "Sensors",
+    "category": "Sensors, Transducers",
+    "category_id": 37,
+    "subcategories": [
+      {
+        "name": "Color Sensors",
+        "id": 599
+      },
+      {
+        "name": "Color Sensors (on-board)",
+        "id": 1238
+      },
+      {
+        "name": "Current Sensors",
+        "id": 600
+      },
+      {
+        "name": "Differential Pressure Transmitters",
+        "id": 1354
+      },
+      {
+        "name": "Encoders",
+        "id": 601
+      },
+      {
+        "name": "Float, Level Sensors",
+        "id": 602
+      },
+      {
+        "name": "Flow Sensors",
+        "id": 603
+      },
+      {
+        "name": "Force Sensors, Load Cells",
+        "id": 604
+      },
+      {
+        "name": "Gas Sensor",
+        "id": 1444
+      },
+      {
+        "name": "Humidity, Moisture Sensors",
+        "id": 605
+      },
+      {
+        "name": "IrDA Transceiver Modules",
+        "id": 606
+      },
+      {
+        "name": "LVDT Transducers (Linear Variable Differential Transformer)",
+        "id": 607
+      },
+      {
+        "name": "Magnetic Sensors",
+        "id": 608
+      },
+      {
+        "name": "Compass, Magnetic Field (Modules)",
+        "id": 1243
+      },
+      {
+        "name": "Linear, Compass (ICs)",
+        "id": 1244
+      },
+      {
+        "name": "Position, Proximity, Speed (Modules)",
+        "id": 1245
+      },
+      {
+        "name": "Switches (Solid State)",
+        "id": 1246
+      },
+      {
+        "name": "Magnets",
+        "id": 609
+      },
+      {
+        "name": "Multi Purpose Magnets",
+        "id": 1247
+      },
+      {
+        "name": "Motion Sensors",
+        "id": 610
+      },
+      {
+        "name": "Accelerometers",
+        "id": 1249
+      },
+      {
+        "name": "Gyroscopes",
+        "id": 1250
+      },
+      {
+        "name": "IMUs (Inertial Measurement Units)",
+        "id": 1251
+      },
+      {
+        "name": "Inclinometers (ICs)",
+        "id": 1252
+      },
+      {
+        "name": "Optical Motion Sensors",
+        "id": 1253
+      },
+      {
+        "name": "Tilt Switches",
+        "id": 1254
+      },
+      {
+        "name": "Vibration Sensors",
+        "id": 1255
+      },
+      {
+        "name": "Multifunction",
+        "id": 611
+      },
+      {
+        "name": "Optical Sensors",
+        "id": 612
+      },
+      {
+        "name": "Ambient Light, IR, UV Sensors",
+        "id": 1256
+      },
+      {
+        "name": "Camera Modules",
+        "id": 1257
+      },
+      {
+        "name": "Distance Measuring",
+        "id": 1258
+      },
+      {
+        "name": "Image Sensors, Camera",
+        "id": 1259
+      },
+      {
+        "name": "Photo Detectors - CdS Cells",
+        "id": 1260
+      },
+      {
+        "name": "Photo Detectors - Logic Output",
+        "id": 1261
+      },
+      {
+        "name": "Photo Detectors - Remote Receiver",
+        "id": 1262
+      },
+      {
+        "name": "Photodiodes",
+        "id": 1263
+      },
+      {
+        "name": "Photointerrupters - Slot Type - Logic Output",
+        "id": 1264
+      },
+      {
+        "name": "Photointerrupters - Slot Type - Transistor Output",
+        "id": 1265
+      },
+      {
+        "name": "Photonics - Counters, Detectors, SPCM (Single Photon Counting Module)",
+        "id": 1266
+      },
+      {
+        "name": "Phototransistors",
+        "id": 1267
+      },
+      {
+        "name": "Reflective - Analog Output",
+        "id": 1268
+      },
+      {
+        "name": "Reflective - Logic Output",
+        "id": 1269
+      },
+      {
+        "name": "Particle, Dust Sensors",
+        "id": 613
+      },
+      {
+        "name": "Particulate Matter (PM) Transmitters",
+        "id": 1353
+      },
+      {
+        "name": "Photoresistors",
+        "id": 624
+      },
+      {
+        "name": "Position Sensors",
+        "id": 614
+      },
+      {
+        "name": "Angle, Linear Position Measuring",
+        "id": 1270
+      },
+      {
+        "name": "Pressure Sensors, Transducers",
+        "id": 615
+      },
+      {
+        "name": "Pressure Transmitters",
+        "id": 1355
+      },
+      {
+        "name": "Proximity Sensors",
+        "id": 617
+      },
+      {
+        "name": "Proximity/Occupancy Sensors Finished Units",
+        "id": 616
+      },
+      {
+        "name": "Safety Light Curtains",
+        "id": 1352
+      },
+      {
+        "name": "Sensor Cable Accessories",
+        "id": 618
+      },
+      {
+        "name": "Sensor Cable Assemblies",
+        "id": 619
+      },
+      {
+        "name": "Sensor Interface Junction Blocks",
+        "id": 620
+      },
+      {
+        "name": "Sensor, Transducer Accessories",
+        "id": 621
+      },
+      {
+        "name": "Sensor, Transducer Amplifiers",
+        "id": 622
+      },
+      {
+        "name": "Shock Sensors",
+        "id": 623
+      },
+      {
+        "name": "Specialized Sensors",
+        "id": 625
+      },
+      {
+        "name": "Strain Gauges",
+        "id": 626
+      },
+      {
+        "name": "Temperature Sensors",
+        "id": 627
+      },
+      {
+        "name": "Analog and Digital Output",
+        "id": 1271
+      },
+      {
+        "name": "NTC Thermistors",
+        "id": 1272
+      },
+      {
+        "name": "PTC Thermistors",
+        "id": 1273
+      },
+      {
+        "name": "RTD (Resistance Temperature Detector)",
+        "id": 1274
+      },
+      {
+        "name": "Thermocouples, Temperature Probes",
+        "id": 1275
+      },
+      {
+        "name": "Thermostats - Mechanical",
+        "id": 1276
+      },
+      {
+        "name": "Thermostats - Solid State",
+        "id": 1277
+      },
+      {
+        "name": "Touch Sensors",
+        "id": 628
+      },
+      {
+        "name": "Ultrasonic Receivers, Transmitters",
+        "id": 629
+      }
+    ]
+  },
+  {
+    "group": "Switches",
+    "category": "Switches",
+    "category_id": 38,
+    "subcategories": [
+      {
+        "name": "Accessories",
+        "id": 630
+      },
+      {
+        "name": "Accessories - Boots, Seals",
+        "id": 1402
+      },
+      {
+        "name": "Accessories - Caps",
+        "id": 1403
+      },
+      {
+        "name": "Automatic Transfer Switches",
+        "id": 1330
+      },
+      {
+        "name": "Cable Pull Switches",
+        "id": 631
+      },
+      {
+        "name": "Configurable Switch Components",
+        "id": 632
+      },
+      {
+        "name": "Configurable Switch Bodies",
+        "id": 1278
+      },
+      {
+        "name": "Configurable Switch Contact Blocks",
+        "id": 1279
+      },
+      {
+        "name": "Configurable Switch Illumination Sources",
+        "id": 1280
+      },
+      {
+        "name": "Configurable Switch Lens",
+        "id": 1281
+      },
+      {
+        "name": "DIP Switches/SIP Switches",
+        "id": 633
+      },
+      {
+        "name": "Disconnect Switch Components",
+        "id": 634
+      },
+      {
+        "name": "Emergency Stop (E-Stop) Switches",
+        "id": 635
+      },
+      {
+        "name": "Foot switches",
+        "id": 1285
+      },
+      {
+        "name": "Interlock Switches",
+        "id": 636
+      },
+      {
+        "name": "Keylock Switches",
+        "id": 637
+      },
+      {
+        "name": "Limit Switches",
+        "id": 639
+      },
+      {
+        "name": "Magnetic, Reed Switches",
+        "id": 640
+      },
+      {
+        "name": "Metal Dome Pot",
+        "id": 642
+      },
+      {
+        "name": "Navigation Switches, Joystick",
+        "id": 643
+      },
+      {
+        "name": "Pushbutton Switches",
+        "id": 645
+      },
+      {
+        "name": "Pushbutton Switches - Hall Effect",
+        "id": 646
+      },
+      {
+        "name": "Rocker Switches",
+        "id": 647
+      },
+      {
+        "name": "Rotary Switches",
+        "id": 648
+      },
+      {
+        "name": "Selector Switches",
+        "id": 649
+      },
+      {
+        "name": "Slide Switches",
+        "id": 650
+      },
+      {
+        "name": "Tactile Switches",
+        "id": 651
+      },
+      {
+        "name": "Thumbwheel Switches",
+        "id": 652
+      },
+      {
+        "name": "Toggle Switches",
+        "id": 653
+      }
+    ]
+  },
+  {
+    "group": "Transformers",
+    "category": "Transformers",
+    "category_id": 40,
+    "subcategories": [
+      {
+        "name": "Audio Transformers",
+        "id": 686
+      },
+      {
+        "name": "Current Transformers",
+        "id": 687
+      },
+      {
+        "name": "Isolation Transformers and Autotransformers, Step Up, Step Down",
+        "id": 688
+      },
+      {
+        "name": "Potential Transformer",
+        "id": 689
+      },
+      {
+        "name": "Power Transformers",
+        "id": 690
+      },
+      {
+        "name": "Pulse Transformers",
+        "id": 691
+      },
+      {
+        "name": "Specialty Transformers",
+        "id": 692
+      },
+      {
+        "name": "Switching Converter, SMPS Transformers",
+        "id": 693
+      }
+    ]
+  }
+]

+ 141 - 0
bom_assistant/suppliers/lcsc/categories.py

@@ -0,0 +1,141 @@
+from __future__ import annotations
+
+import json
+from pathlib import Path
+
+from bom_assistant.session.models import ComponentCategory
+
+_CATEGORIES_PATH = Path(__file__).parent / "categories.json"
+
+# Explicit subcategory name lists for categories where the Nuxt flat list mixes
+# group headers (e.g. "Capacitors" id:495) with searchable leaves (id:1140+).
+# None = fall back to _TOP_LEVEL_CATS lookup (all subcategories from that section).
+_SUBCATEGORY_NAMES: dict[ComponentCategory, list[str] | None] = {
+    ComponentCategory.capacitor: [
+        "Aluminum - Polymer Capacitors",
+        "Aluminum Electrolytic Capacitors",
+        "Capacitor Networks, Arrays",
+        "Ceramic Capacitors",
+        "Electric Double Layer Capacitors (EDLC), Supercapacitors",
+        "Film Capacitors",
+        "Mica and PTFE Capacitors",
+        "Motor Start, Motor Run Capacitors (AC)",
+        "Niobium Oxide Capacitors",
+        "Silicon Capacitors",
+        "Tantalum - Polymer Capacitors",
+        "Tantalum Capacitors",
+        "Thin Film Capacitors",
+        "Trimmers, Variable Capacitors",
+    ],
+    ComponentCategory.resistor: [
+        "Chassis Mount Resistors",
+        "Chip Resistor - Surface Mount",
+        "Current Sense Resistors",
+        "Resistor Networks, Arrays",
+        "Specialized Resistors",
+        "Through Hole Resistors",
+        "Adjustable Power Resistor",
+        "Rotary Potentiometers, Rheostats",
+        "Slide Potentiometers",
+        "Trimmer Potentiometers",
+    ],
+    ComponentCategory.inductor: [
+        "Fixed Inductors",
+        "Adjustable Inductors",
+        "Arrays, Signal Transformers",
+        "Wireless Charging Coils",
+        "Ferrite Beads and Chips",
+        "Common Mode Chokes",
+    ],
+    ComponentCategory.crystal: [
+        "Crystals",
+        "Oscillators",
+        "Pin Configurable/Selectable Oscillators",
+        "Programmable Oscillators",
+        "Resonators",
+        "VCOs (Voltage Controlled Oscillators)",
+    ],
+    ComponentCategory.diode: [
+        "Diodes",
+        "Bridge Rectifiers",
+        "Rectifiers",
+        "RF Diodes",
+        "Variable Capacitance (Varicaps, Varactors)",
+        "Zener",
+        "Current Regulation - Diodes, Transistors",
+    ],
+    ComponentCategory.led: [
+        "LED Addressable, Specialty",
+        "LED Character and Numeric",
+        "LED COBs, Engines, Modules, Strips",
+        "LED Color Lighting",
+        "LED Emitters - Infrared, UV, Visible",
+        "LED Indication - Discrete",
+        "LED White Lighting",
+        "Circuit Board Indicators, Arrays, Light Bars, Bar Graphs",
+    ],
+    ComponentCategory.mosfet: [
+        "Transistors",
+        "FETs, MOSFETs",
+        "IGBTs",
+        "Bipolar (BJT)",
+        "Power Driver Modules",
+        "Special Purpose Transistors",
+    ],
+    # These have many subcategories — return all from the top-level section
+    ComponentCategory.ic: None,
+    ComponentCategory.connector: None,
+    ComponentCategory.sensor: None,
+    ComponentCategory.other: [],
+}
+
+# Top-level LCSC category names to scan when _SUBCATEGORY_NAMES entry is None
+_TOP_LEVEL_CATS: dict[ComponentCategory, list[str]] = {
+    ComponentCategory.ic: ["Integrated Circuits (ICs)", "Isolators"],
+    ComponentCategory.connector: ["Connectors, Interconnects"],
+    ComponentCategory.sensor: ["Sensors, Transducers"],
+}
+
+
+def load_categories() -> list[dict]:
+    if not _CATEGORIES_PATH.exists():
+        raise FileNotFoundError(
+            f"LCSC category file not found: {_CATEGORIES_PATH}\n"
+            "Run: python -m bom_assistant.suppliers.lcsc.fetch_categories"
+        )
+    with open(_CATEGORIES_PATH, encoding="utf-8") as f:
+        return json.load(f)
+
+
+def get_subcategories_for(internal_cat: ComponentCategory) -> list[dict]:
+    """Return [{name, id}] dicts relevant to internal_cat for the LLM prompt."""
+    names = _SUBCATEGORY_NAMES.get(internal_cat)
+
+    if names is not None:
+        if not names:
+            return []
+        name_set = set(names)
+        cats = load_categories()
+        result: list[dict] = []
+        seen: set[int] = set()
+        for cat in cats:
+            for sub in cat.get("subcategories", []):
+                if sub["name"] in name_set and sub["id"] not in seen:
+                    seen.add(sub["id"])
+                    result.append(sub)
+        return result
+
+    # Top-level category lookup
+    target_tops = _TOP_LEVEL_CATS.get(internal_cat, [])
+    if not target_tops:
+        return []
+    cats = load_categories()
+    result = []
+    seen = set()
+    for cat in cats:
+        if cat["category"] in target_tops:
+            for sub in cat.get("subcategories", []):
+                if sub["id"] not in seen:
+                    seen.add(sub["id"])
+                    result.append(sub)
+    return result

+ 26 - 0
bom_assistant/suppliers/lcsc/category_cache.json

@@ -0,0 +1,26 @@
+{
+  "capacitor||10nF": {
+    "name": "Ceramic Capacitors",
+    "id": 1142
+  },
+  "capacitor||100nF": {
+    "name": "Ceramic Capacitors",
+    "id": 1142
+  },
+  "capacitor||10uF": {
+    "name": "Aluminum Electrolytic Capacitors",
+    "id": 1140
+  },
+  "capacitor||100uF": {
+    "name": "Aluminum Electrolytic Capacitors",
+    "id": 1140
+  },
+  "connector||3.5mm audio jack TRS": {
+    "name": "Audio Connectors",
+    "id": 718
+  },
+  "resistor||1.2k": {
+    "name": "Through Hole Resistors",
+    "id": 1203
+  }
+}

+ 30 - 0
bom_assistant/suppliers/lcsc/category_resolver.py

@@ -0,0 +1,30 @@
+from __future__ import annotations
+
+from dataclasses import dataclass
+from pathlib import Path
+
+from bom_assistant.session.models import BomRow
+from bom_assistant.suppliers.category_resolver import resolve_category
+from bom_assistant.suppliers.lcsc.categories import get_subcategories_for
+
+_CACHE_PATH = Path(__file__).parent / "category_cache.json"
+
+
+@dataclass
+class LcscCategory:
+    name: str
+    id: int
+
+    def __repr__(self) -> str:
+        return f"LcscCategory(name={self.name!r}, id={self.id})"
+
+
+def resolve(row: BomRow) -> LcscCategory | None:
+    """Map a BomRow to an LCSC subcategory via shared LLM resolver."""
+    result = resolve_category(
+        row,
+        get_subcategories_for(row.category),
+        supplier="lcsc",
+        cache_path=_CACHE_PATH,
+    )
+    return LcscCategory(result.name, result.id) if result else None

+ 223 - 0
bom_assistant/suppliers/lcsc/fetch_categories.py

@@ -0,0 +1,223 @@
+#!/usr/bin/env python3
+"""
+One-time CLI: download LCSC category tree from homepage Nuxt state → categories.json
+
+The LCSC homepage embeds the full navigation tree (classifyPOS) in a server-side
+rendered __NUXT__ script block. We parse it to extract top-level categories and
+their subcategories (name + id), then write categories.json.
+
+Usage:
+    python -m bom_assistant.suppliers.lcsc.fetch_categories
+"""
+from __future__ import annotations
+
+import json
+import random
+import re
+import sys
+import time
+import urllib.request
+from html.parser import HTMLParser
+from pathlib import Path
+
+_OUT_PATH = Path(__file__).parent / "categories.json"
+_UA = "Mozilla/5.0 (X11; Linux x86_64; rv:120.0) Gecko/20100101 Firefox/120.0"
+_GRACE = 1.5    # seconds between requests
+_JITTER = 0.5   # ± random jitter added to grace period
+_RETRY_WAIT = 5.0
+
+# Group labels for each top-level LCSC category name (for the output JSON)
+_GROUP_MAP: dict[str, str] = {
+    "Audio Products": "Audio",
+    "Circuit Protection": "Circuit Protection",
+    "Connectors, Interconnects": "Connectors",
+    "Discrete Semiconductors": "Discrete Semiconductors",
+    "Development Boards, Kits, Programmers": "Development",
+    "Embedded Computers": "Embedded",
+    "Fans, Thermal Management, HVAC": "Thermal",
+    "Hardware, Fasteners, Accessories": "Hardware",
+    "Industrial Automation and Controls": "Industrial",
+    "Integrated Circuits (ICs)": "ICs",
+    "Isolators": "ICs",
+    "Optoelectronics": "Optoelectronics",
+    "Passives": "Passives",
+    "Power Supplies": "Power",
+    "Relays": "Relays",
+    "RF and Wireless": "RF",
+    "Sensors, Transducers": "Sensors",
+    "Switches": "Switches",
+    "Transformers": "Transformers",
+}
+
+
+class _AnchorParser(HTMLParser):
+    def __init__(self) -> None:
+        super().__init__()
+        self.links: list[tuple[str, str]] = []
+        self._href: str | None = None
+        self._buf: list[str] = []
+
+    def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None:
+        if tag == "a":
+            self._href = dict(attrs).get("href") or ""
+            self._buf = []
+
+    def handle_data(self, data: str) -> None:
+        if self._href is not None:
+            self._buf.append(data)
+
+    def handle_endtag(self, tag: str) -> None:
+        if tag == "a" and self._href is not None:
+            text = "".join(self._buf).strip()
+            if text:
+                self.links.append((self._href, text))
+            self._href = None
+            self._buf = []
+
+
+def _fetch(url: str) -> str | None:
+    req = urllib.request.Request(
+        url,
+        headers={"User-Agent": _UA, "Accept-Language": "en-US,en;q=0.9"},
+    )
+    for attempt in range(2):
+        try:
+            with urllib.request.urlopen(req, timeout=15) as resp:
+                return resp.read().decode("utf-8", errors="replace")
+        except Exception as exc:
+            if attempt == 0:
+                print(f"  warn: {exc} — retrying in {_RETRY_WAIT}s")
+                time.sleep(_RETRY_WAIT)
+            else:
+                print(f"  fail: skipping {url}")
+    return None
+
+
+def _sleep() -> None:
+    time.sleep(max(0.5, _GRACE + random.uniform(-_JITTER, _JITTER)))
+
+
+def _unescape(s: str) -> str:
+    """Decode unicode escapes like \\u002F → /."""
+    try:
+        return s.encode("utf-8").decode("unicode_escape")
+    except Exception:
+        return s
+
+
+def _split_objects(s: str) -> list[str]:
+    """Split a string into top-level {} objects by brace depth."""
+    objs: list[str] = []
+    depth = 0
+    start = None
+    for i, ch in enumerate(s):
+        if ch == "{":
+            if depth == 0:
+                start = i
+            depth += 1
+        elif ch == "}":
+            depth -= 1
+            if depth == 0 and start is not None:
+                objs.append(s[start : i + 1])
+    return objs
+
+
+_NAME_RE = re.compile(r'categoryNameEn:"((?:[^"\\]|\\.)*)"')
+_URL_RE = re.compile(r'url:"((?:[^"\\]|\\.)*)"')
+
+
+def _parse_nuxt_categories(html: str) -> list[dict] | None:
+    """Extract classifyPOS tree from the __NUXT__ script block."""
+    # Find the script containing classifyPOS
+    scripts = re.findall(r"<script[^>]*>(.*?)</script>", html, re.DOTALL)
+    nuxt = next((s for s in scripts if "classifyPOS" in s), None)
+    if not nuxt:
+        return None
+
+    # Extract the classifyPOS array by bracket depth
+    idx = nuxt.find("classifyPOS:[")
+    if idx == -1:
+        return None
+    start = idx + len("classifyPOS:")
+    depth = 0
+    end = start
+    for i, ch in enumerate(nuxt[start:], start):
+        if ch == "[":
+            depth += 1
+        elif ch == "]":
+            depth -= 1
+            if depth == 0:
+                end = i + 1
+                break
+    raw = nuxt[start:end]
+
+    results: list[dict] = []
+    for top_obj in _split_objects(raw):
+        nm = _NAME_RE.search(top_obj)
+        ur = _URL_RE.search(top_obj)
+        if not (nm and ur):
+            continue
+
+        parent_name = _unescape(nm.group(1))
+        url_str = _unescape(ur.group(1))
+        cid_m = re.search(r"/category/(\d+)", url_str)
+        parent_id = int(cid_m.group(1)) if cid_m else 0
+
+        # Extract childCategoryList
+        cl_idx = top_obj.find("childCategoryList:")
+        children: list[dict] = []
+        if cl_idx != -1:
+            cl_start = cl_idx + len("childCategoryList:")
+            seen: set[int] = set()
+            for child_obj in _split_objects(top_obj[cl_start:]):
+                cnm = _NAME_RE.search(child_obj)
+                cur = _URL_RE.search(child_obj)
+                if not (cnm and cur):
+                    continue
+                child_name = _unescape(cnm.group(1))
+                child_url = _unescape(cur.group(1))
+                ccid_m = re.search(r"/category/(\d+)", child_url)
+                if ccid_m:
+                    child_id = int(ccid_m.group(1))
+                    if child_id not in seen:
+                        seen.add(child_id)
+                        children.append({"name": child_name, "id": child_id})
+
+        group = _GROUP_MAP.get(parent_name, "Other")
+        results.append({
+            "group": group,
+            "category": parent_name,
+            "category_id": parent_id,
+            "subcategories": children,
+        })
+
+    return results or None
+
+
+def main() -> None:
+    print("Fetching LCSC homepage …")
+    _sleep()
+    html = _fetch("https://www.lcsc.com/")
+    if not html:
+        print("ERROR: could not reach lcsc.com")
+        sys.exit(1)
+
+    results = _parse_nuxt_categories(html)
+    if not results:
+        print("ERROR: classifyPOS not found in homepage — LCSC page structure may have changed")
+        sys.exit(1)
+
+    # Filter out non-component categories
+    skip = {"Temporary", "Hardware, Fasteners, Accessories", "Industrial Automation and Controls"}
+    results = [r for r in results if r["category"] not in skip]
+
+    _OUT_PATH.write_text(json.dumps(results, indent=2, ensure_ascii=False), encoding="utf-8")
+    total = sum(len(r["subcategories"]) for r in results)
+    print(f"Wrote {_OUT_PATH}")
+    print(f"  {len(results)} top-level categories, {total} total subcategories")
+    for r in results:
+        print(f"  [{r['category_id']:>4}] {r['category']}  ({len(r['subcategories'])} subcategories)")
+
+
+if __name__ == "__main__":
+    main()

+ 120 - 0
bom_assistant/suppliers/lcsc/lcsc.py

@@ -0,0 +1,120 @@
+from __future__ import annotations
+
+import json
+import re
+import urllib.request
+from typing import Any
+
+from bom_assistant.session.models import NormalizedParams
+from bom_assistant.suppliers.base import SearchResult, SupplierAdapter
+
+_SEARCH_URL = "https://lcsc.com/api/products/search"
+_SESSION_SEED_URL = "https://lcsc.com/products/Capacitors_11.html"
+_UA = "Mozilla/5.0 (X11; Linux x86_64; rv:120.0) Gecko/20100101 Firefox/120.0"
+
+
+class LcscAdapter(SupplierAdapter):
+    """
+    LCSC supplier adapter.
+
+    Modes:
+      api_token=None (default) — scraper mode: acquires CSRF session from a
+        category page, then POSTs to lcsc.com/api/products/search.
+      api_token=<key>          — official API mode (not yet implemented, raises
+        NotImplementedError until LCSC credentials are available).
+    """
+
+    def __init__(self, api_token: str | None = None) -> None:
+        super().__init__(api_token)
+        self._cookies: str | None = None
+        self._csrf: str | None = None
+
+    def search(
+        self,
+        category_id: int,
+        params: NormalizedParams,
+        filters: dict[str, Any] | None = None,
+        page: int = 1,
+    ) -> SearchResult:
+        if self.api_token:
+            return self._search_official(category_id, params, filters, page)
+        return self._search_scraper(category_id, params, filters, page)
+
+    # ------------------------------------------------------------------
+    # Scraper mode
+    # ------------------------------------------------------------------
+
+    def _ensure_session(self) -> None:
+        if self._csrf is not None:
+            return
+        req = urllib.request.Request(_SESSION_SEED_URL, headers={"User-Agent": _UA})
+        with urllib.request.urlopen(req, timeout=15) as resp:
+            self._cookies = resp.headers.get("Set-Cookie", "")
+            html = resp.read().decode("utf-8", errors="replace")
+        m = re.search(r'csrfToken["\s:=]+(["\'])([A-Za-z0-9_\-]+)\1', html)
+        self._csrf = m.group(2) if m else ""
+
+    def _search_scraper(
+        self,
+        category_id: int,
+        params: NormalizedParams,
+        filters: dict[str, Any] | None,
+        page: int,
+    ) -> SearchResult:
+        try:
+            self._ensure_session()
+        except Exception as exc:
+            return SearchResult(count=0, page=page, page_size=25, items=[], error=f"session: {exc}")
+
+        payload: dict[str, Any] = {
+            "current_page": page,
+            "page_size": 25,
+            "catalog_id": category_id,
+            "in_stock": False,
+            "is_RoHS": False,
+            "show_icon": False,
+        }
+        if filters:
+            payload.update(filters)
+
+        data = json.dumps(payload).encode()
+        headers: dict[str, str] = {
+            "User-Agent": _UA,
+            "Content-Type": "application/json",
+            "X-CSRF-Token": self._csrf or "",
+            "Referer": f"https://lcsc.com/category/{category_id}.html",
+        }
+        if self._cookies:
+            headers["Cookie"] = self._cookies
+
+        req = urllib.request.Request(_SEARCH_URL, data=data, headers=headers, method="POST")
+        try:
+            with urllib.request.urlopen(req, timeout=15) as resp:
+                body = json.loads(resp.read().decode())
+        except Exception as exc:
+            return SearchResult(count=0, page=page, page_size=25, items=[], error=str(exc))
+
+        vo = body.get("data", {}).get("productSearchResultVO", {})
+        return SearchResult(
+            count=vo.get("totalCount", 0),
+            page=vo.get("currentPage", page),
+            page_size=vo.get("pageSize", 25),
+            items=vo.get("productList", []),
+        )
+
+    # ------------------------------------------------------------------
+    # Official API mode (placeholder — needs LCSC API key + HMAC impl)
+    # ------------------------------------------------------------------
+
+    def _search_official(
+        self,
+        category_id: int,
+        params: NormalizedParams,
+        filters: dict[str, Any] | None,
+        page: int,
+    ) -> SearchResult:
+        # TODO: HMAC-sign request → POST https://ips.lcsc.com/rest/wmsc2agent/category/product/{category_id}
+        raise NotImplementedError(
+            "Official LCSC API not yet implemented. "
+            "Use LcscAdapter() without api_token to use scraper mode."
+        )

+ 2 - 1
requirements.txt

@@ -4,4 +4,5 @@ pydantic>=2
 python-multipart
 openpyxl
 xlrd>=2.0
-anthropic
+openai
+python-dotenv

+ 5 - 2
test.sh

@@ -6,7 +6,8 @@ python -c "
 from bom_assistant.ingestion.parser import parse_bom
 from bom_assistant.ingestion.column_mapper import map_columns
 from bom_assistant.ingestion.normalizer import normalize
-import pathlib, json
+from bom_assistant.suppliers.lcsc.category_resolver import resolve
+import pathlib
 
 f = pathlib.Path('$path')
 raw = parse_bom(f.read_bytes(), f.name)
@@ -15,5 +16,7 @@ rows = normalize(mapped)
 
 for r in rows:
     p = r.normalized_params
-    print(f'{r.state.value:8} [{r.category.value:10}] {str(r.designators):30} val={p.value_str!r:10} pkg={p.package!r} V={p.voltage_rating}')
+    cat = resolve(r)
+    lcsc = cat.name if cat else '???'
+    print(f'{r.state.value:8} [{r.category.value:10}] {str(r.designators):30} val={p.value_str!r:10} pkg={p.package!r:15} → {lcsc}')
 "