Major features: - Pixel-Perfect glassmorphism design (landing + POS + public catalog) - OEM/Local catalog toggle with Nexpart taxonomy (14 groups, 108 subgroups, 558 part types) - Marketplace B2B Phase 1 (bodegas, POs, status machine, WA+email notifications) - Peer-to-peer inventory (multi-instance, LAN discovery) - WhatsApp: photo→Vision AI, voice→Whisper, conversational quotations - Smart unified search (VIN/plate/part_number/keyword auto-detect) - Shop Supplies tab (vehicle-independent parts) - Chatbot AI fallback chain (5 models) + response cache - CSV inventory import tool + setup_instance.sh installer - Tablet-responsive CSS + sidebar toggle - Filters, export CSV, employee edit, business data save - Quotation system (WA→POS) with auto-print on confirmation - Live stats on landing page Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
746 lines
34 KiB
Python
746 lines
34 KiB
Python
"""
|
|
Nexpart Taxonomy — Universal parts classification used in Local catalog mode.
|
|
|
|
Source of truth: /home/Autopartes/CapturasWeb/nexpart_hierarchy.txt
|
|
Total: 14 Groups → 103 Subgroups → 558 Part Types
|
|
|
|
This module loads the Nexpart hierarchy from the .txt file and provides
|
|
helpers to:
|
|
1. List all groups / subgroups / part types
|
|
2. Map a TecDoc `parts.name_part` value to (group, subgroup, part_type)
|
|
3. Translate any node name to Spanish using the existing translations.py
|
|
|
|
Business decisions (locked in by user 2026-04-08):
|
|
1. AMBIGUITY: first match wins (the order in nexpart_hierarchy.txt is
|
|
Nexpart's own canonical order, so the first match is also Nexpart's
|
|
primary classification).
|
|
2. UNMAPPED: drop. Parts without a clean Nexpart match do NOT appear in
|
|
Local mode. Local mode is intentionally smaller and more consistent.
|
|
3. LANGUAGE: bilingual via translations.py — single source of truth.
|
|
The hierarchy is stored in English; the UI translates each node
|
|
on-the-fly using `translate_taxonomy_node()`.
|
|
"""
|
|
|
|
import os
|
|
import re
|
|
from typing import Optional
|
|
|
|
# ============================================================================
|
|
# CONSTANTS
|
|
# ============================================================================
|
|
|
|
UNMAPPED_STRATEGY = "drop"
|
|
LANGUAGE_STRATEGY = "bilingual_taxonomy"
|
|
|
|
# Path to the source-of-truth hierarchy text file
|
|
_HIERARCHY_PATH = os.path.join(
|
|
os.path.dirname(os.path.abspath(__file__)),
|
|
"..", "..", "CapturasWeb", "nexpart_hierarchy.txt"
|
|
)
|
|
|
|
|
|
# ============================================================================
|
|
# HIERARCHY PARSER
|
|
# ============================================================================
|
|
|
|
# The list of valid groups, in canonical order (matches Nexpart's own order
|
|
# from the screenshots). Used to disambiguate "is this line a group header?"
|
|
# from "is this line a subgroup name?" — both can be capitalized.
|
|
_KNOWN_GROUPS = (
|
|
"IGNITION & FILTERS",
|
|
"BELTS, HOSES, WATER PUMPS & COOLING SYSTEM PARTS",
|
|
"STARTING & CHARGING SYSTEM PARTS (ALTERNATORS, BATTERIES & CABLES)",
|
|
"BRAKE SYSTEM, WHEEL BEARINGS, STUDS, NUTS & HARDWARE",
|
|
"FUEL & EMISSIONS PARTS",
|
|
"HEATING & AIR CONDITIONING",
|
|
"ENGINE PARTS",
|
|
"DRIVETRAIN PARTS",
|
|
"STEERING & SUSPENSION PARTS",
|
|
"EXHAUST, CLUTCH & FLYWHEEL PARTS",
|
|
"WIPERS, LAMPS & FUSES",
|
|
"BODY PARTS, CABLES, CAPS, ELECTRICAL MOTORS, SWITCHES & OTHER MISCELLANEOUS PARTS",
|
|
"CHEMICALS, WAXES & LUBRICANTS",
|
|
"TIRES, WHEELS, TOOLS & ACCESSORY PARTS",
|
|
)
|
|
|
|
|
|
def _parse_hierarchy_file() -> dict:
|
|
"""Parse nexpart_hierarchy.txt into a nested dict.
|
|
|
|
Returns:
|
|
{
|
|
"Ignition & Filters": {
|
|
"Computers & Relays": ["Engine Control Module (ECM)", ...],
|
|
...
|
|
},
|
|
...
|
|
}
|
|
"""
|
|
taxonomy = {}
|
|
current_group = None
|
|
current_subgroup = None
|
|
|
|
if not os.path.exists(_HIERARCHY_PATH):
|
|
return taxonomy
|
|
|
|
with open(_HIERARCHY_PATH, "r", encoding="utf-8") as f:
|
|
for line in f:
|
|
line = line.rstrip("\n")
|
|
|
|
# Skip comments, blank lines, and decoration rules
|
|
if not line or line.startswith("#"):
|
|
continue
|
|
if set(line.strip()) <= {"═", " "}:
|
|
continue
|
|
if line.strip() == "SUMMARY":
|
|
break # End-of-file marker
|
|
|
|
# Group header: ALL CAPS line that matches a known group
|
|
if line.strip().upper() in _KNOWN_GROUPS:
|
|
# Convert to title case for display, preserving the original
|
|
# casing from the .txt file (which already mixes Title Case)
|
|
current_group = line.strip().title() \
|
|
.replace("Ac ", "AC ") \
|
|
.replace("Pcv", "PCV") \
|
|
.replace("Ecm", "ECM") \
|
|
.replace("Cv ", "CV ") \
|
|
.replace("Vvt", "VVT") \
|
|
.replace("Tpms", "TPMS") \
|
|
.replace("Hvac", "HVAC") \
|
|
.replace("Abs ", "ABS ") \
|
|
.replace("Egr", "EGR")
|
|
taxonomy.setdefault(current_group, {})
|
|
current_subgroup = None
|
|
continue
|
|
|
|
# Part type: lines with leading " - "
|
|
if line.lstrip().startswith("- "):
|
|
if current_group and current_subgroup:
|
|
pt = line.lstrip()[2:].strip()
|
|
taxonomy[current_group][current_subgroup].append(pt)
|
|
continue
|
|
|
|
# Subgroup: a non-empty line that's not a comment, not a header,
|
|
# not a part type, and starts with a non-space character.
|
|
if line[0] not in (" ", "\t"):
|
|
current_subgroup = line.strip()
|
|
if current_group:
|
|
taxonomy[current_group].setdefault(current_subgroup, [])
|
|
|
|
return taxonomy
|
|
|
|
|
|
# Load at import time
|
|
NEXPART_TAXONOMY = _parse_hierarchy_file()
|
|
|
|
|
|
# ============================================================================
|
|
# FLAT INDEX FOR FAST LOOKUP
|
|
# ============================================================================
|
|
# Building these once at import time means O(1) lookups during requests.
|
|
|
|
def _build_indexes():
|
|
"""Build flat lookup tables from the nested taxonomy."""
|
|
# part_type_lower → list of (group, subgroup, original_part_type)
|
|
# We use lowercase keys so the matcher is case-insensitive.
|
|
part_type_index = {}
|
|
all_part_types = [] # ordered list, in canonical Nexpart order
|
|
|
|
for group, subgroups in NEXPART_TAXONOMY.items():
|
|
for subgroup, part_types in subgroups.items():
|
|
for pt in part_types:
|
|
key = pt.strip().lower()
|
|
part_type_index.setdefault(key, []).append((group, subgroup, pt))
|
|
all_part_types.append((group, subgroup, pt))
|
|
return part_type_index, all_part_types
|
|
|
|
|
|
_PART_TYPE_INDEX, _ALL_PART_TYPES = _build_indexes()
|
|
|
|
|
|
# ============================================================================
|
|
# DECISION 1 — RESOLVE AMBIGUITY (first-match wins)
|
|
# ============================================================================
|
|
|
|
# Manual overrides for ambiguous part names. Key = lowercase TecDoc name
|
|
# (as fed to the matcher). Value = the subgroup WHERE the part should
|
|
# canonically live when a mechanic thinks about it.
|
|
#
|
|
# These beat the first-match rule. Add entries when you see that your users
|
|
# expect a part in a different subgroup than the one Nexpart's canonical
|
|
# order picks. Leave empty at start — grow incrementally from feedback.
|
|
#
|
|
# Example: a Mexican mechanic troubleshooting a failed emissions test will
|
|
# look for an O2 sensor under "Catalytic Converter" (system-level thinking),
|
|
# not "Emission Sensors, Relays, Solenoids & Switches" (component-level).
|
|
AMBIGUITY_OVERRIDES = {
|
|
# tecdoc name (lowercase) -> preferred subgroup name (exact string)
|
|
# (populated as real usage surfaces mismatches)
|
|
# 'oxygen sensor': 'Catalytic Converter',
|
|
}
|
|
|
|
|
|
def resolve_ambiguous_subgroup(tecdoc_name: str, candidates: list) -> tuple:
|
|
"""Pick the canonical (group, subgroup, part_type) for an ambiguous name.
|
|
|
|
Resolution order:
|
|
1. AMBIGUITY_OVERRIDES dict — manual curation wins over everything.
|
|
2. First-match in canonical Nexpart order (Decision 1 locked in).
|
|
|
|
Search by the user still finds the part from anywhere via the flat
|
|
index; the override only affects which subgroup the part "lives in"
|
|
during hierarchical navigation.
|
|
|
|
Args:
|
|
tecdoc_name: e.g. "Oxygen Sensor"
|
|
candidates: list of (group, subgroup, part_type) tuples
|
|
|
|
Returns:
|
|
A single (group, subgroup, part_type) tuple.
|
|
"""
|
|
# 1. Manual override wins
|
|
key = (tecdoc_name or '').strip().lower()
|
|
preferred_subgroup = AMBIGUITY_OVERRIDES.get(key)
|
|
if preferred_subgroup:
|
|
for cand in candidates:
|
|
if cand[1] == preferred_subgroup:
|
|
return cand
|
|
# Override pointed to a subgroup not in the candidate set —
|
|
# log and fall through to first-match.
|
|
# (Using print to stay import-free; swap for logger if available.)
|
|
print(f"[taxonomy] AMBIGUITY_OVERRIDES['{key}'] = '{preferred_subgroup}' "
|
|
f"not in candidates {[c[1] for c in candidates]}; falling back")
|
|
|
|
# 2. First-match in canonical order
|
|
return candidates[0]
|
|
|
|
|
|
# ============================================================================
|
|
# DECISION 2 — UNMAPPED HANDLING (drop)
|
|
# ============================================================================
|
|
# When a TecDoc name doesn't match any Nexpart Part Type, the matcher
|
|
# returns None and the caller filters it out of Local mode results.
|
|
|
|
|
|
# ============================================================================
|
|
# CORE MATCHER: tecdoc_to_nexpart()
|
|
# ============================================================================
|
|
|
|
def tecdoc_to_nexpart(tecdoc_name: str) -> Optional[tuple]:
|
|
"""Map a TecDoc part name to its Nexpart (group, subgroup, part_type).
|
|
|
|
Matching strategy (in order of preference):
|
|
1. Exact match (case-insensitive) on the full Part Type name.
|
|
2. Substring match — TecDoc name CONTAINS a known Part Type.
|
|
Example: "Front Brake Pad Set" contains "Brake Pad Set" → match.
|
|
3. Reverse substring — known Part Type contains the TecDoc name.
|
|
Example: TecDoc "Wiper" matches Nexpart "Wiper Arm". Less precise,
|
|
used as last resort.
|
|
|
|
Args:
|
|
tecdoc_name: value from `parts.name_part` (English)
|
|
|
|
Returns:
|
|
(group, subgroup, part_type) if matched, None otherwise.
|
|
Per Decision 2, callers should filter out None values.
|
|
"""
|
|
if not tecdoc_name:
|
|
return None
|
|
|
|
name_lower = tecdoc_name.strip().lower()
|
|
if not name_lower:
|
|
return None
|
|
|
|
# 1. Exact match
|
|
if name_lower in _PART_TYPE_INDEX:
|
|
candidates = _PART_TYPE_INDEX[name_lower]
|
|
return resolve_ambiguous_subgroup(tecdoc_name, candidates)
|
|
|
|
# 2. Substring match (TecDoc contains Nexpart Part Type)
|
|
# Prefer the LONGEST match — more specific wins on a tie of position.
|
|
best_match = None
|
|
best_len = 0
|
|
for pt_key, candidates in _PART_TYPE_INDEX.items():
|
|
if pt_key in name_lower and len(pt_key) > best_len:
|
|
best_match = candidates
|
|
best_len = len(pt_key)
|
|
if best_match:
|
|
return resolve_ambiguous_subgroup(tecdoc_name, best_match)
|
|
|
|
# 3. Reverse substring (Nexpart Part Type contains TecDoc) — last resort
|
|
for pt_key, candidates in _PART_TYPE_INDEX.items():
|
|
if name_lower in pt_key and len(name_lower) >= 4:
|
|
# Min length 4 to avoid false matches on short words like "Cap"
|
|
return resolve_ambiguous_subgroup(tecdoc_name, candidates)
|
|
|
|
return None
|
|
|
|
|
|
# ============================================================================
|
|
# DECISION 3 — BILINGUAL VIA translations.py
|
|
# ============================================================================
|
|
|
|
# Curated translations for the 14 top-level groups + common subgroups.
|
|
# These are full-string (not substring) so they always win over the partial
|
|
# matcher in translations.py and produce clean Spanish display.
|
|
TAXONOMY_OVERRIDES_ES = {
|
|
# ─── Top-level groups (14) ───
|
|
"Ignition & Filters": "Encendido y Filtros",
|
|
"Belts, Hoses, Water Pumps & Cooling System Parts": "Bandas, Mangueras, Bombas de Agua y Sistema de Enfriamiento",
|
|
"Starting & Charging System Parts (Alternators, Batteries & Cables)": "Sistema de Arranque y Carga (Alternadores, Baterías y Cables)",
|
|
"Brake System, Wheel Bearings, Studs, Nuts & Hardware": "Sistema de Frenos, Baleros, Birlos, Tuercas y Ferretería",
|
|
"Fuel & Emissions Parts": "Combustible y Emisiones",
|
|
"Heating & Air Conditioning": "Calefacción y Aire Acondicionado",
|
|
"Engine Parts": "Partes de Motor",
|
|
"Drivetrain Parts": "Tren Motriz",
|
|
"Steering & Suspension Parts": "Dirección y Suspensión",
|
|
"Exhaust, Clutch & Flywheel Parts": "Escape, Clutch y Volante",
|
|
"Wipers, Lamps & Fuses": "Limpiaparabrisas, Luces y Fusibles",
|
|
"Body Parts, Cables, Caps, Electrical Motors, Switches & Other Miscellaneous Parts": "Carrocería, Cables, Tapones, Motores Eléctricos, Switches y Misceláneos",
|
|
"Chemicals, Waxes & Lubricants": "Químicos, Ceras y Lubricantes",
|
|
"Tires, Wheels, Tools & Accessory Parts": "Llantas, Rines, Herramientas y Accesorios",
|
|
|
|
# ─── Common subgroups (the most-used ones; expand as needed) ───
|
|
"Filters & PCV": "Filtros y PCV",
|
|
"Spark Plugs & Glow Plugs": "Bujías",
|
|
"Tune-Up & Ignition Parts": "Afinación y Encendido",
|
|
"Belts, Tensioners & Pulleys": "Bandas, Tensores y Poleas",
|
|
"Radiators & Electric Fan Motors": "Radiadores y Motoventiladores",
|
|
"Thermostats, Housings & Radiator Caps": "Termostatos, Carcasas y Tapones de Radiador",
|
|
"Water Pumps, Fan Blades & Clutches": "Bombas de Agua, Aspas y Fan Clutches",
|
|
"Alternators & Voltage Regulators": "Alternadores y Reguladores de Voltaje",
|
|
"Batteries": "Baterías",
|
|
"Starters": "Marchas / Arrancadores",
|
|
"ABS Controls & Parts": "Controles y Partes de ABS",
|
|
"Front Friction, Drums & Rotors": "Frenos Delanteros: Pastillas, Tambores y Discos",
|
|
"Rear Friction, Drums & Rotors": "Frenos Traseros: Pastillas, Tambores y Discos",
|
|
"Front Wheel Bearings & Seals": "Baleros y Sellos de Rueda Delantera",
|
|
"Rear Wheel Bearings & Seals": "Baleros y Sellos de Rueda Trasera",
|
|
"Master Cylinders, Boosters & Switches": "Cilindros Maestros, Boosters y Switches",
|
|
"Fuel Pumps & Tanks": "Bombas y Tanques de Gasolina",
|
|
"Fuel Injection Parts, Mass Air Flow Sensors": "Inyección, Sensores MAF",
|
|
"Turbochargers & Superchargers": "Turbos y Compresores",
|
|
"AC Compressors, Kits & Parts": "Compresores de A/C y Kits",
|
|
"AC Condensers & Evaporators": "Condensadores y Evaporadores de A/C",
|
|
"Cams, Lifters & Timing Parts": "Árboles de Levas, Buzos y Distribución",
|
|
"Crankshafts & Bearings": "Cigüeñales y Metales",
|
|
"Pistons, Rings & Rods": "Pistones, Anillos y Bielas",
|
|
"Heads & Manifolds": "Cabezas y Múltiples",
|
|
"Engine Mounts & Other Miscellaneous Engine Parts": "Soportes de Motor y Otros",
|
|
"Driveshafts, U-Joints & CV (Constant Velocity) Parts": "Flechas, Crucetas y Juntas Homocinéticas",
|
|
"Automatic Transmission Seals": "Sellos de Transmisión Automática",
|
|
"Manual Transmission Seals": "Sellos de Transmisión Manual",
|
|
"Transmission & Parts": "Transmisión y Partes",
|
|
"Ball Joints & Control Arms": "Rótulas y Horquillas",
|
|
"Shock Absorbers & Struts": "Amortiguadores y Strut",
|
|
"Steering Linkages, Rods & Arms": "Direcciones, Bieletas y Brazos",
|
|
"Sway Bars, Stabilizer Bars, Strut Rods & Parts": "Barras Estabilizadoras y Tornillos",
|
|
"All Exhaust & Diagrams": "Sistema de Escape Completo",
|
|
"Catalytic Converter": "Convertidor Catalítico",
|
|
"Clutches & Clutch Kits": "Clutches y Kits",
|
|
"Manifolds & Headers": "Múltiples y Headers",
|
|
"Arms, Blades & Refills": "Brazos, Plumas y Repuestos",
|
|
"Headlamps & Flashers": "Faros y Direccionales",
|
|
"Exterior Lamps": "Luces Exteriores",
|
|
"Interior Lamps": "Luces Interiores",
|
|
"Wiper Motors & Washer Pumps": "Motores de Limpia y Bombas de Agua",
|
|
"Bumpers & License Plates": "Defensas y Placas",
|
|
"Door, Window & Tailgate Parts": "Puertas, Ventanas y Cajuela",
|
|
"Engine & Transmission Lubricants & Additives": "Aceites de Motor y Transmisión",
|
|
"Tires & Wheels": "Llantas y Rines",
|
|
"Tools, Jacks, Hardware & Manuals": "Herramientas, Gatos y Hardware",
|
|
|
|
# ─── Remaining subgroups (phase 2 translation coverage) ───
|
|
"Computers & Relays": "Computadoras y Relés",
|
|
"Ignition Wires": "Cables de Bujía",
|
|
"Miscellaneous Ignition Parts": "Conectores y Misceláneos de Encendido",
|
|
"Engine Coolant & Bypass Hoses": "Mangueras de Refrigerante y Bypass",
|
|
"Heater & Other Hoses": "Mangueras de Calefacción y Otras",
|
|
"Sensors, Switches & Relays": "Sensores, Switches y Relés",
|
|
"Starter Solenoids, Switches & Relays": "Solenoides de Marcha, Switches y Relés",
|
|
"Brake Cables, Studs, Nuts & Spindle Nuts": "Cables, Birlos y Tuercas de Freno",
|
|
"Front Brake Hardware & ABS Sensors": "Ferretería y Sensores ABS Delanteros",
|
|
"Front Calipers, Wheel Cylinders, Hoses": "Calipers, Cilindros y Mangueras Delanteras",
|
|
"Miscellaneous Disc Hardware": "Ferretería Misceláneo de Disco",
|
|
"Miscellaneous Drum Hardware": "Ferretería Misceláneo de Tambor",
|
|
"Miscellaneous Hydraulic Parts & Brake Specifications": "Hidráulica y Especificaciones de Freno",
|
|
"Rear Brake Hardware & ABS Sensors": "Ferretería y Sensores ABS Traseros",
|
|
"Rear Calipers, Wheel Cylinders, Hoses": "Calipers, Cilindros y Mangueras Traseras",
|
|
"Carburetors, Carburetor Kits & Components": "Carburadores, Kits y Componentes",
|
|
"EGR & Emissions Valves": "EGR y Válvulas de Emisiones",
|
|
"Emission Sensors, Relays, Solenoids & Switches": "Sensores de Emisiones, Relés, Solenoides y Switches",
|
|
"Fuel Injection Harnesses, Connectors & Miscellaneous Parts": "Arneses, Conectores e Inyección Misceláneos",
|
|
"Fuel Injection Sensors, Relays & Switches": "Sensores, Relés y Switches de Inyección",
|
|
"AC Accumulators, Receiver Driers & Valves": "Acumuladores, Secadores y Válvulas de A/C",
|
|
"AC Hose Assemblies & Fittings": "Mangueras y Conexiones de A/C",
|
|
"AC Relays & Switches": "Relés y Switches de A/C",
|
|
"AC, Heating & Ventilation Gaskets, O-Rings, Kits, Doors & Actuators": "Juntas, O-Rings, Puertas y Actuadores A/C",
|
|
"Blower Motors & Parts": "Motores de Ventilador y Partes",
|
|
"Heater Cores & Heater Control Valves": "Radiadores de Calefacción y Válvulas",
|
|
"Engine Block Parts": "Partes de Bloque de Motor",
|
|
"Engines & Kits": "Motores y Kits",
|
|
"Gasket Sets": "Juegos de Juntas",
|
|
"Individual Gaskets & Seals": "Juntas y Sellos Individuales",
|
|
"Intake & Exhaust Valves": "Válvulas de Admisión y Escape",
|
|
"Rockers & Push Rods": "Balancines y Varillas de Empuje",
|
|
"Vacuum & Oil Pumps": "Bombas de Vacío y Aceite",
|
|
"Axle & Differential Parts": "Partes de Eje y Diferencial",
|
|
"Electronics, Sensors, Relays & Miscellaneous Parts": "Electrónica, Sensores y Misceláneos",
|
|
"Manual Transmission Bearings": "Baleros de Transmisión Manual",
|
|
"Spindles & Hubs": "Husillos y Mazas",
|
|
"Transmission Kits & Gaskets": "Kits y Juntas de Transmisión",
|
|
"Alignment Kits & Tools": "Kits y Herramientas de Alineación",
|
|
"King Pins, Trailing Arms, Alignment & Other Chassis": "Pivotes, Brazos y Otros de Chasis",
|
|
"Power Steering Pumps, Hoses & Kits": "Bombas, Mangueras y Kits de Dirección Hidráulica",
|
|
"Rack & Pinion, Gear Box, Power Cylinder": "Cremallera, Caja de Dirección y Cilindro",
|
|
"Clutch Hydraulics": "Hidráulica de Clutch",
|
|
"Individual Exhaust Parts": "Partes de Escape Individuales",
|
|
"Miscellaneous Clutch Parts": "Partes Misceláneas de Clutch",
|
|
"Lighting Modules & Switches": "Módulos y Switches de Iluminación",
|
|
"Lighting Relays & Sensors": "Relés y Sensores de Luces",
|
|
"Caps": "Tapones",
|
|
"Cruise Control Parts": "Partes de Control de Crucero",
|
|
"Electrical Motors": "Motores Eléctricos",
|
|
"Glass": "Cristales",
|
|
"Hood & Tailgate Parts": "Partes de Cofre y Cajuela",
|
|
"Hoods Fenders & Body Parts": "Cofres, Salpicaderas y Carrocería",
|
|
"Lift Supports": "Amortiguadores de Cofre/Cajuela",
|
|
"Switches, Relays & Miscellaneous Parts": "Switches, Relés y Misceláneos",
|
|
"Wheel & Hardware": "Rines y Ferretería",
|
|
"Bumper & License Plate": "Defensas y Placas",
|
|
"Electronics Audio/Visual & Mirrors": "Electrónica, Audio y Espejos",
|
|
"Hood, Fender & Body Parts": "Cofre, Salpicaderas y Carrocería",
|
|
"Interior & Steering Wheel": "Interior y Volante",
|
|
|
|
# ─── High-value part types (most-searched in real use) ───
|
|
# Ignition & Filters
|
|
"Engine Control Module (ECM)": "Módulo de Control del Motor (ECM)",
|
|
"Ignition Relay": "Relé de Encendido",
|
|
"Transmission Control Module": "Módulo de Control de Transmisión",
|
|
"Engine Air Filter": "Filtro de Aire del Motor",
|
|
"Engine Oil Filter": "Filtro de Aceite del Motor",
|
|
"Engine Oil Filter Adapter": "Adaptador de Filtro de Aceite",
|
|
"Engine Oil Filter Housing": "Carcasa de Filtro de Aceite",
|
|
"Vapor Canister": "Canister de Vapor",
|
|
"Vapor Canister Purge Valve": "Válvula de Purga del Canister",
|
|
"Vapor Canister Purge Solenoid": "Solenoide de Purga del Canister",
|
|
"Spark Plug Set": "Juego de Bujías",
|
|
"Direct Ignition Coil": "Bobina de Encendido Directo",
|
|
"Ignition Coil": "Bobina de Encendido",
|
|
"Ignition Kit": "Kit de Encendido",
|
|
|
|
# Belts / Cooling
|
|
"Engine Timing Belt": "Banda de Distribución",
|
|
"Engine Timing Belt Component Kit": "Kit de Componentes de Distribución",
|
|
"Engine Timing Belt Kit with Water Pump": "Kit de Distribución con Bomba de Agua",
|
|
"Engine Timing Chain": "Cadena de Distribución",
|
|
"Engine Timing Chain Guide": "Guía de Cadena de Distribución",
|
|
"Engine Timing Chain Tensioner": "Tensor de Cadena de Distribución",
|
|
"Accessory Drive Belt Tensioner Assembly": "Tensor de Banda Accesoria",
|
|
"Accessory Drive Belt Tensioner Pulley": "Polea Tensora de Banda Accesoria",
|
|
"Serpentine Belt": "Banda Serpentina",
|
|
"Radiator": "Radiador",
|
|
"Radiator Coolant Hose": "Manguera de Refrigerante del Radiador",
|
|
"Engine Coolant Reservoir": "Depósito de Refrigerante",
|
|
"Engine Water Pump": "Bomba de Agua del Motor",
|
|
"Engine Water Pump Gasket": "Junta de Bomba de Agua",
|
|
"Engine Water Pump Pulley": "Polea de Bomba de Agua",
|
|
"Engine Coolant Thermostat": "Termostato de Refrigerante",
|
|
"Engine Coolant Thermostat Housing": "Carcasa de Termostato",
|
|
"Engine Coolant Temperature Sensor": "Sensor de Temperatura de Refrigerante",
|
|
"Engine Cooling Fan": "Ventilador de Enfriamiento",
|
|
"Engine Cooling Fan Assembly": "Conjunto de Ventilador de Enfriamiento",
|
|
"HVAC Heater Hose": "Manguera de Calefacción HVAC",
|
|
|
|
# Starting & Charging
|
|
"Alternator": "Alternador",
|
|
"Vehicle Battery": "Batería del Vehículo",
|
|
"Starter": "Marcha / Arrancador",
|
|
"Ignition Lock Cylinder": "Switch de Encendido (Cilindro)",
|
|
"Ignition Switch": "Switch de Encendido",
|
|
|
|
# Brake System
|
|
"ABS Wheel Speed Sensor": "Sensor de Velocidad de Rueda ABS",
|
|
"Front Disc Brake Pad Set": "Juego de Pastillas Delanteras",
|
|
"Rear Disc Brake Pad Set": "Juego de Pastillas Traseras",
|
|
"Front Disc Brake Rotor": "Disco de Freno Delantero",
|
|
"Rear Disc Brake Rotor": "Disco de Freno Trasero",
|
|
"Front Disc Brake Caliper": "Caliper de Freno Delantero",
|
|
"Rear Disc Brake Caliper": "Caliper de Freno Trasero",
|
|
"Front Brake Hydraulic Hose": "Manguera Hidráulica Delantera",
|
|
"Rear Brake Hydraulic Hose": "Manguera Hidráulica Trasera",
|
|
"Brake Master Cylinder": "Cilindro Maestro de Frenos",
|
|
"Power Brake Booster": "Booster de Frenos",
|
|
"Front Wheel Bearing": "Balero de Rueda Delantera",
|
|
"Rear Wheel Bearing": "Balero de Rueda Trasera",
|
|
"Front Wheel Bearing and Hub Assembly": "Balero y Maza Delantera",
|
|
"Rear Wheel Bearing and Hub Assembly": "Balero y Maza Trasera",
|
|
"Wheel Lug Nut": "Tuerca de Rueda (Birlo)",
|
|
"Wheel Lug Stud": "Birlo de Rueda",
|
|
|
|
# Fuel & Emissions
|
|
"Electric Fuel Pump": "Bomba Eléctrica de Gasolina",
|
|
"Fuel Pump Module Assembly": "Conjunto de Módulo de Bomba de Gasolina",
|
|
"Fuel Level Sensor": "Sensor de Nivel de Gasolina",
|
|
"Fuel Tank Cap": "Tapón de Tanque de Gasolina",
|
|
"Fuel Injector": "Inyector de Gasolina",
|
|
"Fuel Injector Set": "Juego de Inyectores",
|
|
"Fuel Injection Throttle Body": "Cuerpo de Aceleración",
|
|
"Mass Air Flow Sensor": "Sensor MAF (Flujo de Aire)",
|
|
"Oxygen Sensor": "Sensor de Oxígeno",
|
|
"Engine Camshaft Position Sensor": "Sensor de Posición de Árbol de Levas",
|
|
"Engine Crankshaft Position Sensor": "Sensor de Posición del Cigüeñal",
|
|
"Engine Knock Sensor": "Sensor de Detonación",
|
|
"Manifold Absolute Pressure Sensor": "Sensor MAP (Presión Absoluta)",
|
|
"Turbocharger": "Turbocargador",
|
|
|
|
# Heating & AC
|
|
"A/C Compressor": "Compresor de A/C",
|
|
"A/C Condenser": "Condensador de A/C",
|
|
"A/C Evaporator Core": "Evaporador de A/C",
|
|
"A/C Expansion Valve": "Válvula de Expansión de A/C",
|
|
"A/C Receiver Drier/Desiccant Element": "Filtro Deshidratador de A/C",
|
|
"A/C Hose Assembly": "Manguera de A/C",
|
|
"HVAC Blower Motor": "Motor de Ventilador HVAC",
|
|
"HVAC Blower Motor Resistor": "Resistencia de Ventilador HVAC",
|
|
"HVAC Heater Core": "Radiador de Calefacción",
|
|
"HVAC Blend Door Actuator": "Actuador de Puerta de Mezcla",
|
|
|
|
# Engine Parts
|
|
"Engine Camshaft": "Árbol de Levas",
|
|
"Engine Harmonic Balancer": "Damper / Polea del Cigüeñal",
|
|
"Engine Crankshaft Main Bearing Set": "Juego de Metales de Bancada",
|
|
"Engine Piston": "Pistón",
|
|
"Engine Piston Ring Set": "Juego de Anillos de Pistón",
|
|
"Engine Connecting Rod Bearing Set": "Juego de Metales de Biela",
|
|
"Engine Cylinder Head Gasket": "Junta de Cabeza de Cilindros",
|
|
"Engine Cylinder Head Bolt Set": "Juego de Tornillos de Cabeza",
|
|
"Engine Intake Manifold": "Múltiple de Admisión",
|
|
"Engine Intake Manifold Gasket": "Junta de Múltiple de Admisión",
|
|
"Engine Valve Cover": "Tapa de Válvulas",
|
|
"Engine Valve Cover Gasket": "Junta de Tapa de Válvulas",
|
|
"Engine Oil Pan": "Cárter de Aceite",
|
|
"Engine Oil Pan Gasket": "Junta de Cárter",
|
|
"Engine Oil Pump": "Bomba de Aceite",
|
|
"Engine Oil Pressure Sender": "Sensor de Presión de Aceite",
|
|
"Engine Oil Pressure Switch": "Switch de Presión de Aceite",
|
|
"Engine Mount": "Soporte de Motor",
|
|
"Engine Rocker Arm": "Balancín",
|
|
"Engine Exhaust Valve": "Válvula de Escape",
|
|
"Engine Intake Valve": "Válvula de Admisión",
|
|
"Engine Valve Spring": "Resorte de Válvula",
|
|
"Engine Valve Stem Oil Seal": "Sello de Válvula",
|
|
|
|
# Drivetrain
|
|
"CV Axle Assembly": "Flecha Homocinética Completa",
|
|
"CV Axle Shaft": "Flecha Homocinética",
|
|
"Automatic Transmission Mount": "Soporte de Transmisión Automática",
|
|
"Automatic Transmission Oil Cooler": "Enfriador de Aceite de Transmisión",
|
|
"Automatic Transmission Oil Pan": "Cárter de Transmisión Automática",
|
|
"Manual Transmission Mount": "Soporte de Transmisión Manual",
|
|
"Transmission Filter Kit": "Kit de Filtro de Transmisión",
|
|
"Transmission Oil Pan": "Cárter de Transmisión",
|
|
"Spindle Nut": "Tuerca de Husillo",
|
|
"Vehicle Speed Sensor": "Sensor de Velocidad del Vehículo",
|
|
|
|
# Steering & Suspension
|
|
"Suspension Ball Joint": "Rótula de Suspensión",
|
|
"Suspension Control Arm Bushing": "Buje de Horquilla",
|
|
"Suspension Control Arm and Ball Joint Assembly": "Horquilla con Rótula",
|
|
"Suspension Shock Absorber": "Amortiguador",
|
|
"Suspension Strut": "Strut de Suspensión",
|
|
"Suspension Strut Assembly": "Conjunto de Strut",
|
|
"Suspension Strut Mount": "Base de Strut",
|
|
"Suspension Stabilizer Bar Link": "Terminal de Barra Estabilizadora",
|
|
"Steering Tie Rod End": "Terminal de Dirección",
|
|
"Rack and Pinion Assembly": "Cremallera de Dirección",
|
|
"Steering Column": "Columna de Dirección",
|
|
|
|
# Exhaust/Clutch
|
|
"Catalytic Converter": "Convertidor Catalítico",
|
|
"Catalytic Converter Gasket": "Junta de Convertidor Catalítico",
|
|
"Exhaust Manifold": "Múltiple de Escape",
|
|
"Exhaust Manifold Gasket": "Junta de Múltiple de Escape",
|
|
"Exhaust Muffler": "Mofle",
|
|
"Exhaust Muffler Assembly": "Conjunto de Mofle",
|
|
"Exhaust Pipe": "Tubo de Escape",
|
|
"Exhaust Clamp": "Abrazadera de Escape",
|
|
"Clutch Slave Cylinder": "Cilindro Esclavo de Clutch",
|
|
"Transmission Clutch Kit": "Kit de Clutch",
|
|
|
|
# Wipers/Lamps
|
|
"Wiper Arm": "Brazo de Limpiaparabrisas",
|
|
"Wiper Blade": "Pluma Limpiaparabrisas",
|
|
"Wiper Motor": "Motor de Limpiaparabrisas",
|
|
"Wiper Switch": "Switch de Limpiaparabrisas",
|
|
"Headlight Bulb": "Foco de Faro",
|
|
"Tail Light Bulb": "Foco de Calavera",
|
|
"Brake Light Bulb": "Foco de Freno",
|
|
"Turn Signal Light Bulb": "Foco Direccional",
|
|
"Fog Light Bulb": "Foco Antiniebla",
|
|
"Back Up Light Bulb": "Foco de Reversa",
|
|
"License Plate Light Bulb": "Foco de Placa",
|
|
"Dome Light Bulb": "Foco de Domo",
|
|
"Washer Fluid Reservoir Cap": "Tapón de Depósito de Limpiaparabrisas",
|
|
"Headlight Switch": "Switch de Luces",
|
|
"Turn Signal Switch": "Switch de Direccionales",
|
|
"Multi-Function Switch": "Switch Multifunciones",
|
|
"Hazard Warning Switch": "Switch de Intermitentes",
|
|
|
|
# Body / Electrical / Misc
|
|
"Door Lock Actuator": "Actuador de Cerradura",
|
|
"Door Lock Actuator Motor": "Motor de Actuador de Cerradura",
|
|
"Window Motor": "Motor de Ventana",
|
|
"Window Regulator": "Elevador de Ventana",
|
|
"Window Motor and Regulator Assembly": "Motor y Elevador de Ventana",
|
|
"Sunroof Motor": "Motor de Quemacocos",
|
|
"Exterior Door Handle": "Manija Exterior de Puerta",
|
|
"Interior Door Handle": "Manija Interior de Puerta",
|
|
"Door Mirror Glass": "Cristal de Espejo",
|
|
"Horn Relay": "Relé de Claxon",
|
|
"Liftgate Lift Support": "Amortiguador de Cajuela",
|
|
"Cruise Control Switch": "Switch de Control de Crucero",
|
|
"Engine Coolant Reservoir Cap": "Tapón de Depósito de Refrigerante",
|
|
"Engine Oil Filler Cap": "Tapón de Llenado de Aceite",
|
|
"Radiator Cap": "Tapón de Radiador",
|
|
"TPMS Sensor": "Sensor TPMS",
|
|
"TPMS Programmable Sensor": "Sensor TPMS Programable",
|
|
|
|
# Chemicals / Tools
|
|
"Automatic Transmission Fluid": "Aceite de Transmisión Automática",
|
|
"Engine Oil": "Aceite de Motor",
|
|
}
|
|
|
|
|
|
def translate_taxonomy_node(english_name: str) -> str:
|
|
"""Translate a Nexpart group / subgroup / part type to Spanish.
|
|
|
|
STRICT lookup only — no partial substitution. The order:
|
|
1. TAXONOMY_OVERRIDES_ES — full-string curated translations.
|
|
2. PART_TRANSLATIONS exact match (from services.translations).
|
|
3. Fallback: return the English original UNCHANGED.
|
|
|
|
Why strict-only: partial substitution within a compound name produces
|
|
ugly hybrids ("Front Tambor de Freno", "Engine Filtro de Aceite").
|
|
For taxonomy display we'd rather show clean English than dirty Spanish.
|
|
Untranslated entries are visible reminders to extend the override dict.
|
|
|
|
Args:
|
|
english_name: the canonical English name (group, subgroup, or part type)
|
|
|
|
Returns:
|
|
Spanish display string, or the English original if no exact match.
|
|
"""
|
|
if not english_name:
|
|
return english_name
|
|
|
|
# 1. Curated overrides (highest priority)
|
|
if english_name in TAXONOMY_OVERRIDES_ES:
|
|
return TAXONOMY_OVERRIDES_ES[english_name]
|
|
|
|
# 2. Exact match in PART_TRANSLATIONS
|
|
try:
|
|
from services.translations import PART_TRANSLATIONS
|
|
if english_name in PART_TRANSLATIONS:
|
|
return PART_TRANSLATIONS[english_name]
|
|
except ImportError:
|
|
pass
|
|
|
|
# 3. Fallback — return English unchanged
|
|
return english_name
|
|
|
|
|
|
def list_untranslated_nodes() -> dict:
|
|
"""Diagnostic helper: list every taxonomy node missing a Spanish entry.
|
|
|
|
Useful for filling in TAXONOMY_OVERRIDES_ES incrementally — run this
|
|
in a one-off script to see exactly what still needs translation.
|
|
|
|
Returns:
|
|
{"groups": [...], "subgroups": [...], "part_types": [...]}
|
|
"""
|
|
try:
|
|
from services.translations import PART_TRANSLATIONS
|
|
known = set(PART_TRANSLATIONS.keys()) | set(TAXONOMY_OVERRIDES_ES.keys())
|
|
except ImportError:
|
|
known = set(TAXONOMY_OVERRIDES_ES.keys())
|
|
|
|
missing = {"groups": [], "subgroups": [], "part_types": []}
|
|
for group, subgroups in NEXPART_TAXONOMY.items():
|
|
if group not in known:
|
|
missing["groups"].append(group)
|
|
for subgroup, part_types in subgroups.items():
|
|
if subgroup not in known:
|
|
missing["subgroups"].append(subgroup)
|
|
for pt in part_types:
|
|
if pt not in known:
|
|
missing["part_types"].append(pt)
|
|
return missing
|
|
|
|
|
|
# ============================================================================
|
|
# PUBLIC API — used by catalog_service / blueprints
|
|
# ============================================================================
|
|
|
|
def get_groups() -> list:
|
|
"""Return the 14 top-level groups in canonical order.
|
|
|
|
Each item: {"name": english, "name_es": spanish, "subgroup_count": int}
|
|
"""
|
|
return [
|
|
{
|
|
"name": group,
|
|
"name_es": translate_taxonomy_node(group),
|
|
"subgroup_count": len(subgroups),
|
|
}
|
|
for group, subgroups in NEXPART_TAXONOMY.items()
|
|
]
|
|
|
|
|
|
def get_subgroups(group_name: str) -> list:
|
|
"""Return all subgroups for a given group.
|
|
|
|
Each item: {"name": english, "name_es": spanish, "part_type_count": int}
|
|
"""
|
|
subgroups = NEXPART_TAXONOMY.get(group_name, {})
|
|
return [
|
|
{
|
|
"name": subgroup,
|
|
"name_es": translate_taxonomy_node(subgroup),
|
|
"part_type_count": len(part_types),
|
|
}
|
|
for subgroup, part_types in subgroups.items()
|
|
]
|
|
|
|
|
|
def get_part_types(group_name: str, subgroup_name: str) -> list:
|
|
"""Return all part types within a group + subgroup.
|
|
|
|
Each item: {"name": english, "name_es": spanish}
|
|
"""
|
|
subgroups = NEXPART_TAXONOMY.get(group_name, {})
|
|
part_types = subgroups.get(subgroup_name, [])
|
|
return [
|
|
{
|
|
"name": pt,
|
|
"name_es": translate_taxonomy_node(pt),
|
|
}
|
|
for pt in part_types
|
|
]
|
|
|
|
|
|
def stats() -> dict:
|
|
"""Return totals — useful for healthcheck and debugging."""
|
|
total_subgroups = sum(len(sg) for sg in NEXPART_TAXONOMY.values())
|
|
total_part_types = sum(
|
|
len(pts)
|
|
for sg in NEXPART_TAXONOMY.values()
|
|
for pts in sg.values()
|
|
)
|
|
return {
|
|
"groups": len(NEXPART_TAXONOMY),
|
|
"subgroups": total_subgroups,
|
|
"part_types": total_part_types,
|
|
"indexed_keys": len(_PART_TYPE_INDEX),
|
|
}
|