---
wave: 1
depends_on: null
files_modified:
- path: "backend/ai/spare_parts_whitelist.py"
- path: "backend/ai/gemini_extractor.py"
- path: "backend/ai/claude_extractor.py"
- path: "tests/test_spare_parts_classification.py"
autonomous: true
---
# Phase 4.1 Wave 1: Spare-Parts Classification & AI Prompt Enhancement
**Objective:** Build foundation for spare-parts identification by implementing classification logic and enhancing AI prompts with spare-parts vs consumables decision tree.
---
## Task 1: Create Spare-Parts Classification Whitelist
```xml
Build a configurable spare-parts whitelist module with fuzzy matching logic to classify items as spare parts or consumables.
- PROJECT_ARCHITECTURE.md (Item model, AI integration)
- 4.1-RESEARCH.md sections 2 (Spare-Parts Classification Strategy) and Fuzzy Matching Implementation
- No existing code to read — new file
Create file: backend/ai/spare_parts_whitelist.py
**Module Structure:**
1. Define constant lists (case-insensitive strings):
- SPARE_PART_CATEGORIES: ["RAM", "DRAM", "DDR3", "DDR4", "DDR5", "SODIMM", "DIMM", "SSD", "NVME", "M.2", "SATA", "HDD", "HARD DRIVE", "SOLID STATE DRIVE", "CPU", "PROCESSOR", "APU", "GPU", "GRAPHICS CARD", "DISCRETE GPU", "PSU", "POWER SUPPLY UNIT", "ADAPTER", "POWER MODULE", "PCIE", "PCI", "RAID CONTROLLER", "NETWORK CARD", "NIC", "HEATSINK", "CPU COOLER", "THERMAL SOLUTION", "MOTHERBOARD", "BIOS", "CHIPSET"]
- CONSUMABLE_KEYWORDS: ["CABLE", "CORD", "FASTENER", "SCREW", "WASHER", "BOLT", "STANDOFF", "ADHESIVE", "THERMAL PASTE", "THERMAL PAD", "TAPE", "CONNECTOR", "PLUG", "SOCKET", "ADAPTER"]
- POWER_SUPPLY_CONSUMABLE_KEYWORDS: ["CABLE", "CORD", "GENERIC", "POWER CORD", "AC CORD"]
2. Implement function: `classify_as_spare_part(category: str) -> bool`
- Input: extracted Category from AI (string)
- Algorithm:
a. Normalize input: lowercase, strip whitespace
b. Check exact match in SPARE_PART_CATEGORIES → return True
c. Check regex patterns for Memory/Storage/CPU/GPU/PSU: if matched → +50 points
d. Check fuzzy match (fuzzywuzzy library at 70-80% threshold) against SPARE_PART_CATEGORIES → if match ≥80% → +50 points, if 70-80% → +30 points
e. Check exclusion patterns (CONSUMABLE_KEYWORDS): if matched → -100 points (override other scores)
f. Special case: if "power supply" or "PSU" in category BUT "cable" or "cord" also in category → return False (consumable)
g. Final score: ≥ 40 → True (Spare Part), < 40 → False
- Return: bool
3. Implement function: `is_consumable(category: str) -> bool`
- Return: `not classify_as_spare_part(category)`
4. Implement function: `get_spare_part_type(category: str) -> str | None`
- Returns normalized spare-part type (e.g., "RAM", "SSD", "CPU", "GPU") or None if not a spare part
- Used for search query building
**Code Quality:**
- Use type hints: `def classify_as_spare_part(category: str) -> bool:`
- Include docstrings with examples (Kingston DDR4 RAM → True, 6ft cable → False)
- No external dependencies except fuzzywuzzy (add to requirements.txt)
- File exists: backend/ai/spare_parts_whitelist.py
- Function `classify_as_spare_part(category: str) -> bool` accepts string input
- Test passes: `classify_as_spare_part("Kingston DDR4 RAM")` returns True
- Test passes: `classify_as_spare_part("6ft SATA Cable")` returns False
- Test passes: `classify_as_spare_part("CPU Mounting Hardware Kit")` returns False
- Test passes: `classify_as_spare_part("Corsair RM850x 850W PSU")` returns True
- Fuzzy matching works: `classify_as_spare_part("Random Access Memory")` returns True
- All functions have type hints and docstrings
- fuzzywuzzy added to backend/requirements.txt with version constraint (e.g., ==0.18.0)
```
---
## Task 2: Enhance Gemini AI Prompt with Spare-Parts Classification
```xml
Update Gemini extraction prompt to include spare-parts vs consumables decision tree and comprehensive examples for accurate classification.
- backend/ai/gemini_extractor.py (current prompt structure and extraction pattern)
- 4.1-RESEARCH.md section 3 (AI Prompt Enhancement) — specifically the "New Classification Logic" code block
- 4.1-CONTEXT.md section decisions D-01 and D-02
Modify file: backend/ai/gemini_extractor.py
**Action Steps:**
1. Locate the extraction prompt (likely in a string or .md file loaded at module init)
2. Insert new section AFTER category/type extraction, before returning results. Add this exact text:
```
CLASSIFICATION GUIDE - SPARE PARTS vs CONSUMABLES:
Spare Parts (replaceable components that plug into or interface with devices):
- RAM, DDR memory modules (DDR3, DDR4, DDR5, SODIMM, DIMM)
- SSDs, NVMe drives, M.2 modules, SATA drives, hard drives
- CPUs, GPUs, processors, discrete graphics cards
- Power supply units (PSU), power modules (NOT generic power cords)
- Expansion cards (PCIe, PCI, RAID controllers, network cards/NIC)
- Cooling solutions (heatsinks, CPU coolers, thermal solutions)
- Motherboards, chipsets, BIOS modules
NOT Spare Parts (consumables, generic items):
- Cables (power, SATA, USB, Ethernet, proprietary cords)
- Fasteners (screws, washers, bolts, standoffs)
- Thermal paste, thermal pads, adhesive tapes
- Connectors, plugs, sockets, generic adapters
- Generic cords and utility items
Decision Tree:
1. Does the item have a replaceable function in a larger system?
2. Does it have a manufacturer part number and technical specifications?
3. Is it described with model/revision information?
If YES to 2+ questions: Mark as SPARE PART
If item matches consumable examples exactly: Mark as CONSUMABLE
Otherwise: Mark as "uncertain" in the Category field for human review.
Examples:
✓ "Kingston Fury 16GB DDR4-3200" → Spare Part (RAM module)
✓ "Samsung 970 EVO 1TB NVMe" → Spare Part (SSD)
✓ "Intel Core i7-12700K" → Spare Part (CPU)
✓ "Corsair RM850x 850W Power Supply" → Spare Part (PSU)
✗ "6ft SATA Cable" → Consumable (cable)
✗ "CPU Mounting Hardware Kit" → Consumable (fasteners)
✗ "Thermal Paste Tube" → Consumable (adhesive material)
```
3. In the prompt output template, ensure the Category field description includes: "Use the Classification Guide above to distinguish spare parts from consumables. If uncertain, add '(uncertain)' suffix."
4. Do NOT change any existing extraction logic or output structure. Only ADD the new section and clarify Category extraction.
**Code Quality:**
- Preserve exact indentation and formatting from original prompt
- Ensure multi-line string literals remain valid Python
- No imports or logic changes — prompt enhancement only
- File gemini_extractor.py modified
- Grep finds: "CLASSIFICATION GUIDE - SPARE PARTS vs CONSUMABLES" in the file
- Grep finds: "Kingston Fury 16GB DDR4-3200" example in the file
- Grep finds: "6ft SATA Cable" counter-example in the file
- Grep finds: "Decision Tree:" decision logic in the file
- Module still imports and initializes without errors: `python3 -c "from backend.ai.gemini_extractor import extract_item"`
- Prompt structure remains intact (no broken f-strings or syntax errors)
```
---
## Task 3: Enhance Claude AI Prompt with Spare-Parts Classification
```xml
Mirror Gemini prompt enhancement in Claude extractor for consistent spare-parts classification across both AI providers.
- backend/ai/claude_extractor.py (current prompt structure and extraction pattern)
- Task 2 output (what was added to Gemini prompt)
- 4.1-RESEARCH.md section 3 (AI Prompt Enhancement)
Modify file: backend/ai/claude_extractor.py
**Action Steps:**
1. Locate the extraction prompt in claude_extractor.py (similar structure to gemini_extractor.py)
2. Insert the SAME "CLASSIFICATION GUIDE - SPARE PARTS vs CONSUMABLES" section (from Task 2) after category/type extraction in Claude's prompt
3. Ensure Claude's Category field description includes the same guidance: "Use the Classification Guide above to distinguish spare parts from consumables. If uncertain, add '(uncertain)' suffix."
4. Preserve all existing Claude-specific instructions (model-specific prompt tuning, if any)
5. No logic changes — prompt enhancement only, identical content to Gemini
**Code Quality:**
- Match the exact text from Gemini prompt (no divergence)
- Preserve Claude's multi-turn or system prompt structure
- Maintain compatibility with anthropic SDK
- File claude_extractor.py modified
- Grep finds: "CLASSIFICATION GUIDE - SPARE PARTS vs CONSUMABLES" in the file
- Grep finds: "Kingston Fury 16GB DDR4-3200" example in the file
- Grep finds: "Decision Tree:" decision logic in the file
- Module still imports and initializes without errors: `python3 -c "from backend.ai.claude_extractor import extract_item"`
- Prompt structure remains intact (no broken f-strings or syntax errors)
- Content is identical to Gemini prompt CLASSIFICATION GUIDE section
```
---
## Task 4: Create Unit Tests for Spare-Parts Classification
```xml
Write comprehensive pytest unit tests for spare-parts classification logic to validate accuracy and edge cases.
- backend/ai/spare_parts_whitelist.py (Task 1 output)
- 4.1-RESEARCH.md section 5 (Testing & Validation Strategy) — specifically "Unit Tests" subsection
- PROJECT_ARCHITECTURE.md section 2.1 (Testing: Pytest)
Create file: tests/test_spare_parts_classification.py
**Test Structure (Pytest):**
```python
import pytest
from backend.ai.spare_parts_whitelist import (
classify_as_spare_part,
is_consumable,
get_spare_part_type
)
class TestSparePartsClassification:
"""Test spare-parts classification logic."""
def test_exact_match_ram(self):
"""Exact match for RAM should return True."""
assert classify_as_spare_part("RAM") is True
assert classify_as_spare_part("DDR4") is True
assert classify_as_spare_part("DRAM") is True
def test_exact_match_storage(self):
"""Exact match for storage should return True."""
assert classify_as_spare_part("SSD") is True
assert classify_as_spare_part("NVME") is True
assert classify_as_spare_part("HDD") is True
def test_exact_match_processor(self):
"""Exact match for processors should return True."""
assert classify_as_spare_part("CPU") is True
assert classify_as_spare_part("GPU") is True
assert classify_as_spare_part("PROCESSOR") is True
def test_exact_match_power(self):
"""Exact match for power supplies should return True."""
assert classify_as_spare_part("PSU") is True
assert classify_as_spare_part("POWER SUPPLY UNIT") is True
def test_consumable_cables(self):
"""Cables should return False."""
assert classify_as_spare_part("6ft SATA Cable") is False
assert classify_as_spare_part("USB Power Cable") is False
assert classify_as_spare_part("Ethernet Cable") is False
def test_consumable_fasteners(self):
"""Fasteners should return False."""
assert classify_as_spare_part("CPU Mounting Hardware Kit") is False
assert classify_as_spare_part("Screw Kit") is False
assert classify_as_spare_part("Standoff Set") is False
def test_consumable_thermal_materials(self):
"""Thermal materials should return False."""
assert classify_as_spare_part("Thermal Paste") is False
assert classify_as_spare_part("Thermal Pad") is False
assert classify_as_spare_part("Adhesive Tape") is False
def test_fuzzy_match_ram(self):
"""Fuzzy match for RAM variants should return True."""
assert classify_as_spare_part("Random Access Memory") is True
assert classify_as_spare_part("DDR 4") is True # with space
assert classify_as_spare_part("ddr4") is True # lowercase
def test_fuzzy_match_storage(self):
"""Fuzzy match for storage variants should return True."""
assert classify_as_spare_part("Solid State Drive") is True
assert classify_as_spare_part("NVMe Drive") is True
def test_case_insensitivity(self):
"""Classification should be case-insensitive."""
assert classify_as_spare_part("ram") is True
assert classify_as_spare_part("SsD") is True
assert classify_as_spare_part("sata cable") is False
def test_edge_case_power_cable_vs_psu(self):
"""Power supply is spare part; power cable is consumable."""
assert classify_as_spare_part("Corsair RM850x 850W Power Supply") is True
assert classify_as_spare_part("6ft Power Cable AC Cord") is False
def test_is_consumable_function(self):
"""is_consumable should be inverse of classify_as_spare_part."""
assert is_consumable("RAM") is False
assert is_consumable("SATA Cable") is True
def test_get_spare_part_type_returns_normalized_type(self):
"""get_spare_part_type returns normalized category or None."""
assert get_spare_part_type("DDR4 RAM") == "RAM"
assert get_spare_part_type("Kingston SSD") == "SSD"
assert get_spare_part_type("Intel CPU") == "CPU"
assert get_spare_part_type("SATA Cable") is None
```
**Execution & Verification:**
- All tests must pass without errors
- Minimum 12 test cases covering: exact match, fuzzy match, consumables, edge cases
- Use pytest fixtures if needed for setup/teardown
- No external API calls or network dependencies
**Code Quality:**
- Use descriptive test names following pattern: `test__`
- Include docstrings on each test method
- Use assertions with clear expected values
- File exists: tests/test_spare_parts_classification.py
- Test suite runs without errors: `pytest tests/test_spare_parts_classification.py -v`
- Minimum 12 test cases implemented
- Test passes: `test_exact_match_ram` and other exact match tests
- Test passes: `test_consumable_cables` and other consumable tests
- Test passes: `test_fuzzy_match_ram` for fuzzy matching
- Test passes: `test_edge_case_power_cable_vs_psu` for edge cases
- All assertions are positive (assert X is True/False, not assert not X)
- Grep finds: "class TestSparePartsClassification:" in the file
```
---
## Wave 1 Summary
**What this wave accomplishes:**
- Creates reusable spare-parts classification module with fuzzy matching (85-90% accuracy expected)
- Enhances both Gemini and Claude prompts with consistent spare-parts decision tree
- Provides comprehensive unit test coverage for classification logic
- Establishes foundation for web search service in Wave 2
**Completion Criteria:**
- All 4 tasks pass acceptance criteria
- Pytest suite: `pytest tests/test_spare_parts_classification.py -v` → all tests pass
- Code imports without errors:
```bash
python3 -c "from backend.ai.spare_parts_whitelist import classify_as_spare_part"
python3 -c "from backend.ai.gemini_extractor import extract_item"
python3 -c "from backend.ai.claude_extractor import extract_item"
```
- fuzzywuzzy dependency added to backend/requirements.txt
**Dependencies for Wave 2:**
- spare_parts_whitelist.py module (Task 1)
- Enhanced AI prompts (Tasks 2-3)
- Classification tests passing (Task 4)
---