""" Spare-parts classification module for AI-powered item extraction. This module provides functions to classify items as spare parts or consumables using fuzzy matching against a predefined whitelist. """ from typing import Optional from fuzzywuzzy import fuzz 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" ] # Regex patterns for matching common categories MEMORY_PATTERNS = ["DDR", "DRAM", "RAM", "SODIMM", "DIMM"] STORAGE_PATTERNS = ["SSD", "NVME", "SATA", "HDD", "M.2"] PROCESSOR_PATTERNS = ["CPU", "GPU", "PROCESSOR", "CORE", "APU"] PSU_PATTERNS = ["PSU", "POWER SUPPLY", "POWER UNIT"] def classify_as_spare_part(category: str) -> bool: """ Classify an item as a spare part or consumable based on category string. Uses a scoring algorithm combining exact matching, fuzzy matching, and exclusion patterns to classify items. Args: category: Item category string (e.g., "Kingston DDR4 RAM", "6ft SATA Cable") Returns: True if item is classified as a spare part, False if consumable. Examples: >>> classify_as_spare_part("Kingston DDR4 RAM") True >>> classify_as_spare_part("6ft SATA Cable") False >>> classify_as_spare_part("CPU Mounting Hardware Kit") False >>> classify_as_spare_part("Corsair RM850x 850W PSU") True """ if not category: return False # Normalize input normalized = category.upper().strip() # Check exact match in spare parts categories for spare_part in SPARE_PART_CATEGORIES: if spare_part == normalized: return True score = 0 # Check regex patterns for common categories for pattern in MEMORY_PATTERNS: if pattern in normalized: score += 50 break for pattern in STORAGE_PATTERNS: if pattern in normalized: score += 50 break for pattern in PROCESSOR_PATTERNS: if pattern in normalized: score += 50 break for pattern in PSU_PATTERNS: if pattern in normalized: score += 50 break # Check fuzzy match against spare parts categories best_fuzzy_score = 0 for spare_part in SPARE_PART_CATEGORIES: fuzzy_score = fuzz.token_set_ratio(normalized, spare_part) if fuzzy_score > best_fuzzy_score: best_fuzzy_score = fuzzy_score if best_fuzzy_score >= 80: score += 50 elif best_fuzzy_score >= 70: score += 30 # Check exclusion patterns (consumables) — override other scores for keyword in CONSUMABLE_KEYWORDS: if keyword in normalized: score -= 100 # Special case: power supply is spare part, but power cable is consumable if ("POWER SUPPLY" in normalized or "PSU" in normalized): for consumable_keyword in POWER_SUPPLY_CONSUMABLE_KEYWORDS: if consumable_keyword in normalized: return False # Final decision return score >= 40 def is_consumable(category: str) -> bool: """ Determine if an item is a consumable (inverse of classify_as_spare_part). Args: category: Item category string Returns: True if item is a consumable, False if a spare part. """ return not classify_as_spare_part(category) def get_spare_part_type(category: str) -> Optional[str]: """ Return the normalized spare-part type for a given category, or None if not a spare part. Used for building web search queries with the specific part type. Args: category: Item category string Returns: Normalized spare-part type (e.g., "RAM", "SSD", "CPU") or None. """ if not classify_as_spare_part(category): return None normalized = category.upper().strip() # Try to find best matching spare part type best_match = None best_score = 0 for spare_part in SPARE_PART_CATEGORIES: fuzzy_score = fuzz.token_set_ratio(normalized, spare_part) if fuzzy_score > best_score: best_score = fuzzy_score best_match = spare_part return best_match if best_match else None