Add admin panel, enhanced search, Gonher import and expand API
- Add admin interface (admin.html, admin.js) for managing catalog data - Add enhanced search module with advanced filtering capabilities - Expand server.py with new API endpoints and admin functionality - Add Gonher catalog import scripts (import_gonher_catalog.py, import_gonher_complete.py) - Add demo data population script and sample CSV data - Update customer landing page and dashboard with UI improvements - Update database with enriched vehicle and parts data Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
342
vehicle_database/scripts/import_gonher_catalog.py
Normal file
342
vehicle_database/scripts/import_gonher_catalog.py
Normal file
@@ -0,0 +1,342 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Import Gonher Filter Catalog PDF into the autoparts database.
|
||||
Extracts filter part numbers and vehicle compatibility.
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
import re
|
||||
import pypdf
|
||||
from pathlib import Path
|
||||
|
||||
# Database path
|
||||
DB_PATH = Path(__file__).parent.parent / 'vehicle_database.db'
|
||||
PDF_PATH = '/tmp/filtros_catalog.pdf'
|
||||
|
||||
# Filter type mapping
|
||||
FILTER_TYPES = {
|
||||
'ACEITE': {'category': 'Engine', 'group': 'Oil Filters', 'name_prefix': 'Oil Filter'},
|
||||
'SINTÉTICO': {'category': 'Engine', 'group': 'Oil Filters', 'name_prefix': 'Synthetic Oil Filter'},
|
||||
'AIRE': {'category': 'Engine', 'group': 'Air Filters', 'name_prefix': 'Air Filter'},
|
||||
'COMB.': {'category': 'Fuel & Air', 'group': 'Fuel Filters', 'name_prefix': 'Fuel Filter'},
|
||||
'CABINA': {'category': 'Heat & Air Conditioning', 'group': 'Cabin Air Filters', 'name_prefix': 'Cabin Air Filter'}
|
||||
}
|
||||
|
||||
def get_db_connection():
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
conn.row_factory = sqlite3.Row
|
||||
return conn
|
||||
|
||||
def ensure_manufacturer(cursor, name='Gonher'):
|
||||
"""Ensure Gonher manufacturer exists"""
|
||||
cursor.execute("SELECT id FROM manufacturers WHERE name = ?", (name,))
|
||||
row = cursor.fetchone()
|
||||
if row:
|
||||
return row['id']
|
||||
|
||||
cursor.execute("""
|
||||
INSERT INTO manufacturers (name, type, quality_tier, country)
|
||||
VALUES (?, 'aftermarket', 'standard', 'Mexico')
|
||||
""", (name,))
|
||||
return cursor.lastrowid
|
||||
|
||||
def get_or_create_group(cursor, category_name, group_name):
|
||||
"""Get or create a part group"""
|
||||
# Find category
|
||||
cursor.execute("SELECT id FROM part_categories WHERE name = ?", (category_name,))
|
||||
cat_row = cursor.fetchone()
|
||||
if not cat_row:
|
||||
print(f" Warning: Category '{category_name}' not found")
|
||||
return None
|
||||
|
||||
category_id = cat_row['id']
|
||||
|
||||
# Find or create group
|
||||
cursor.execute("""
|
||||
SELECT id FROM part_groups
|
||||
WHERE category_id = ? AND name = ?
|
||||
""", (category_id, group_name))
|
||||
group_row = cursor.fetchone()
|
||||
|
||||
if group_row:
|
||||
return group_row['id']
|
||||
|
||||
# Create group
|
||||
cursor.execute("""
|
||||
INSERT INTO part_groups (category_id, name, name_es)
|
||||
VALUES (?, ?, ?)
|
||||
""", (category_id, group_name, group_name))
|
||||
return cursor.lastrowid
|
||||
|
||||
def parse_year_range(year_str):
|
||||
"""Parse year range like '2019 - 2016' or '2020- 2018' into list of years"""
|
||||
year_str = year_str.strip()
|
||||
|
||||
# Handle single year
|
||||
if re.match(r'^\d{4}$', year_str):
|
||||
return [int(year_str)]
|
||||
|
||||
# Handle range with various separators
|
||||
match = re.match(r'(\d{4})\s*[-–]\s*(\d{4})', year_str)
|
||||
if match:
|
||||
start, end = int(match.group(1)), int(match.group(2))
|
||||
if start > end:
|
||||
start, end = end, start
|
||||
return list(range(end, start + 1))
|
||||
|
||||
return []
|
||||
|
||||
def parse_catalog_page(text, current_brand=None):
|
||||
"""Parse a catalog page and extract vehicle-filter mappings"""
|
||||
entries = []
|
||||
lines = text.split('\n')
|
||||
|
||||
brand = current_brand
|
||||
model = None
|
||||
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
|
||||
# Skip header lines
|
||||
if 'AÑO' in line and 'MOTOR' in line:
|
||||
continue
|
||||
if 'ACEITE' in line and 'AIRE' in line:
|
||||
continue
|
||||
if 'Los filtros Gonher' in line:
|
||||
continue
|
||||
if 'Para vehículos anteriores' in line:
|
||||
continue
|
||||
if '(Continúa)' in line:
|
||||
# Extract brand/model from continuation
|
||||
match = re.match(r'^([A-Z\s]+)\s*\(Continúa\)', line)
|
||||
if match:
|
||||
potential = match.group(1).strip()
|
||||
if len(potential) > 2:
|
||||
if potential in ['ACURA', 'ALFA ROMEO', 'AUDI', 'BMW', 'BUICK', 'CADILLAC',
|
||||
'CHEVROLET', 'CHRYSLER', 'DODGE', 'FIAT', 'FORD', 'GMC',
|
||||
'HONDA', 'HYUNDAI', 'INFINITI', 'JAGUAR', 'JEEP', 'KIA',
|
||||
'LEXUS', 'LINCOLN', 'MAZDA', 'MERCEDES BENZ', 'MERCURY',
|
||||
'MINI', 'MITSUBISHI', 'NISSAN', 'PEUGEOT', 'PONTIAC',
|
||||
'PORSCHE', 'RAM', 'RENAULT', 'SEAT', 'SMART', 'SUBARU',
|
||||
'SUZUKI', 'TOYOTA', 'VOLKSWAGEN', 'VOLVO']:
|
||||
brand = potential
|
||||
else:
|
||||
model = potential
|
||||
continue
|
||||
|
||||
# Check if this is a brand line (all caps, single word or known brand)
|
||||
if re.match(r'^[A-Z][A-Z\s]+$', line) and len(line) > 2:
|
||||
potential_brand = line.strip()
|
||||
if potential_brand in ['ACURA', 'ALFA ROMEO', 'AUDI', 'BMW', 'BUICK', 'CADILLAC',
|
||||
'CHEVROLET', 'CHRYSLER', 'DODGE', 'FIAT', 'FORD', 'GMC',
|
||||
'HONDA', 'HYUNDAI', 'INFINITI', 'JAGUAR', 'JEEP', 'KIA',
|
||||
'LEXUS', 'LINCOLN', 'MAZDA', 'MERCEDES BENZ', 'MERCURY',
|
||||
'MINI', 'MITSUBISHI', 'NISSAN', 'PEUGEOT', 'PONTIAC',
|
||||
'PORSCHE', 'RAM', 'RENAULT', 'SEAT', 'SMART', 'SUBARU',
|
||||
'SUZUKI', 'TOYOTA', 'VOLKSWAGEN', 'VOLVO']:
|
||||
brand = potential_brand
|
||||
model = None
|
||||
continue
|
||||
|
||||
# Check if this is a model line (letters/numbers, no year pattern at start)
|
||||
if brand and re.match(r'^[A-Z][A-Z0-9\s\-]+$', line) and not re.match(r'^\d{4}', line):
|
||||
if not any(c.isdigit() for c in line[:4]): # Model names don't start with 4 digits
|
||||
potential_model = line.strip()
|
||||
if len(potential_model) >= 2 and potential_model not in ['ACEITE', 'AIRE', 'COMB', 'CABINA']:
|
||||
model = potential_model
|
||||
continue
|
||||
|
||||
# Try to parse data line (year, motor, filters)
|
||||
# Pattern: YEAR[-YEAR] MOTOR FILTER1 FILTER2 ...
|
||||
if brand and model:
|
||||
# Look for year at start
|
||||
match = re.match(r'^(\d{4}(?:\s*[-–]\s*\d{4})?)\s+(.+)$', line)
|
||||
if match:
|
||||
year_str = match.group(1)
|
||||
rest = match.group(2)
|
||||
|
||||
# Extract motor (usually like L4-2.0L or V6-3.5L)
|
||||
motor_match = re.match(r'^([LV]\d+[-][\d.]+L(?:\s+(?:Turbo|TURBOCHARGED|diésel|ELECTRIC))?)\s*(.*)$', rest, re.IGNORECASE)
|
||||
if motor_match:
|
||||
motor = motor_match.group(1).strip()
|
||||
filters_str = motor_match.group(2).strip()
|
||||
|
||||
# Parse filter part numbers
|
||||
# They follow pattern like: GP-149 GPS-149 GA-1113 GAC-92
|
||||
filter_parts = re.findall(r'[A-Z]+[-][\dA-Z]+(?:\(\d+\))?', filters_str)
|
||||
|
||||
years = parse_year_range(year_str)
|
||||
|
||||
if years and filter_parts:
|
||||
for year in years:
|
||||
entry = {
|
||||
'brand': brand,
|
||||
'model': model,
|
||||
'year': year,
|
||||
'motor': motor,
|
||||
'filters': {}
|
||||
}
|
||||
|
||||
# Categorize filters by prefix
|
||||
for fp in filter_parts:
|
||||
fp_clean = re.sub(r'\(\d+\)', '', fp) # Remove (1) markers
|
||||
if fp_clean.startswith('GP-') or fp_clean.startswith('G-'):
|
||||
entry['filters']['ACEITE'] = fp_clean
|
||||
elif fp_clean.startswith('GPS-'):
|
||||
entry['filters']['SINTÉTICO'] = fp_clean
|
||||
elif fp_clean.startswith('GA-') and not fp_clean.startswith('GAC-') and not fp_clean.startswith('GAVW-'):
|
||||
entry['filters']['AIRE'] = fp_clean
|
||||
elif fp_clean.startswith('GG-'):
|
||||
entry['filters']['COMB.'] = fp_clean
|
||||
elif fp_clean.startswith('GAC-'):
|
||||
entry['filters']['CABINA'] = fp_clean
|
||||
|
||||
if entry['filters']:
|
||||
entries.append(entry)
|
||||
|
||||
return entries, brand
|
||||
|
||||
def import_catalog():
|
||||
"""Main import function"""
|
||||
print("="*60)
|
||||
print("GONHER FILTER CATALOG IMPORT")
|
||||
print("="*60)
|
||||
|
||||
# Read PDF
|
||||
print(f"\nReading PDF: {PDF_PATH}")
|
||||
pdf = pypdf.PdfReader(PDF_PATH)
|
||||
total_pages = len(pdf.pages)
|
||||
print(f"Total pages: {total_pages}")
|
||||
|
||||
# Parse all pages
|
||||
all_entries = []
|
||||
current_brand = None
|
||||
|
||||
for i in range(total_pages):
|
||||
text = pdf.pages[i].extract_text()
|
||||
if text:
|
||||
entries, current_brand = parse_catalog_page(text, current_brand)
|
||||
all_entries.extend(entries)
|
||||
|
||||
if (i + 1) % 20 == 0:
|
||||
print(f" Processed {i + 1}/{total_pages} pages, {len(all_entries)} entries so far...")
|
||||
|
||||
print(f"\nTotal entries extracted: {len(all_entries)}")
|
||||
|
||||
# Get unique filters
|
||||
unique_filters = {}
|
||||
for entry in all_entries:
|
||||
for filter_type, part_num in entry['filters'].items():
|
||||
if part_num not in unique_filters:
|
||||
unique_filters[part_num] = filter_type
|
||||
|
||||
print(f"Unique filter part numbers: {len(unique_filters)}")
|
||||
|
||||
# Import to database
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Ensure Gonher manufacturer exists
|
||||
manufacturer_id = ensure_manufacturer(cursor, 'Gonher')
|
||||
print(f"\nManufacturer ID: {manufacturer_id}")
|
||||
|
||||
# Create parts for each unique filter
|
||||
print("\nCreating filter parts...")
|
||||
filter_part_ids = {}
|
||||
|
||||
for part_num, filter_type in unique_filters.items():
|
||||
config = FILTER_TYPES.get(filter_type)
|
||||
if not config:
|
||||
continue
|
||||
|
||||
group_id = get_or_create_group(cursor, config['category'], config['group'])
|
||||
if not group_id:
|
||||
continue
|
||||
|
||||
# Check if part exists
|
||||
cursor.execute("SELECT id FROM parts WHERE oem_part_number = ?", (part_num,))
|
||||
existing = cursor.fetchone()
|
||||
|
||||
if existing:
|
||||
filter_part_ids[part_num] = existing['id']
|
||||
else:
|
||||
# Create new part
|
||||
name = f"{config['name_prefix']} {part_num}"
|
||||
name_es = f"Filtro {part_num}"
|
||||
|
||||
cursor.execute("""
|
||||
INSERT INTO parts (oem_part_number, name, name_es, group_id, description)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
""", (part_num, name, name_es, group_id, f"Gonher {config['name_prefix']}"))
|
||||
|
||||
filter_part_ids[part_num] = cursor.lastrowid
|
||||
|
||||
print(f" Created/found {len(filter_part_ids)} filter parts")
|
||||
|
||||
# Create vehicle fitments
|
||||
print("\nCreating vehicle fitments...")
|
||||
fitments_created = 0
|
||||
fitments_skipped = 0
|
||||
|
||||
for entry in all_entries:
|
||||
# Find matching vehicle (model_year_engine)
|
||||
cursor.execute("""
|
||||
SELECT mye.id
|
||||
FROM model_year_engine mye
|
||||
JOIN models m ON mye.model_id = m.id
|
||||
JOIN brands b ON m.brand_id = b.id
|
||||
JOIN years y ON mye.year_id = y.id
|
||||
WHERE UPPER(b.name) = UPPER(?)
|
||||
AND UPPER(m.name) = UPPER(?)
|
||||
AND y.year = ?
|
||||
LIMIT 1
|
||||
""", (entry['brand'], entry['model'], entry['year']))
|
||||
|
||||
mye_row = cursor.fetchone()
|
||||
if not mye_row:
|
||||
fitments_skipped += 1
|
||||
continue
|
||||
|
||||
mye_id = mye_row['id']
|
||||
|
||||
# Create fitments for each filter
|
||||
for filter_type, part_num in entry['filters'].items():
|
||||
part_id = filter_part_ids.get(part_num)
|
||||
if not part_id:
|
||||
continue
|
||||
|
||||
# Check if fitment exists
|
||||
cursor.execute("""
|
||||
SELECT id FROM vehicle_parts
|
||||
WHERE model_year_engine_id = ? AND part_id = ?
|
||||
""", (mye_id, part_id))
|
||||
|
||||
if not cursor.fetchone():
|
||||
cursor.execute("""
|
||||
INSERT INTO vehicle_parts (model_year_engine_id, part_id, quantity_required, fitment_notes)
|
||||
VALUES (?, ?, 1, ?)
|
||||
""", (mye_id, part_id, f"Gonher catalog 2022 - {filter_type}"))
|
||||
fitments_created += 1
|
||||
|
||||
print(f" Fitments created: {fitments_created}")
|
||||
print(f" Entries skipped (vehicle not found): {fitments_skipped}")
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("IMPORT COMPLETE")
|
||||
print("="*60)
|
||||
|
||||
return {
|
||||
'total_entries': len(all_entries),
|
||||
'unique_filters': len(unique_filters),
|
||||
'parts_created': len(filter_part_ids),
|
||||
'fitments_created': fitments_created
|
||||
}
|
||||
|
||||
if __name__ == '__main__':
|
||||
results = import_catalog()
|
||||
print(f"\nSummary: {results}")
|
||||
511
vehicle_database/scripts/import_gonher_complete.py
Normal file
511
vehicle_database/scripts/import_gonher_complete.py
Normal file
@@ -0,0 +1,511 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
IMPORTADOR COMPLETO DEL CATÁLOGO GONHER 2022
|
||||
- Crea vehículos faltantes
|
||||
- Crea partes de filtros Gonher
|
||||
- Crea referencias cruzadas con otras marcas (AC Delco, Fram, etc.)
|
||||
- Crea fitments (vincula partes a vehículos)
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
import re
|
||||
import pypdf
|
||||
from pathlib import Path
|
||||
from collections import defaultdict
|
||||
|
||||
# Paths
|
||||
DB_PATH = Path(__file__).parent.parent / 'vehicle_database.db'
|
||||
PDF_PATH = '/tmp/filtros_catalog.pdf'
|
||||
|
||||
# Filter type configuration
|
||||
FILTER_TYPES = {
|
||||
'ACEITE': {'category': 'Engine', 'group': 'Oil Filters', 'prefix': 'Oil Filter', 'prefix_es': 'Filtro de Aceite'},
|
||||
'SINTÉTICO': {'category': 'Engine', 'group': 'Oil Filters', 'prefix': 'Synthetic Oil Filter', 'prefix_es': 'Filtro de Aceite Sintético'},
|
||||
'AIRE': {'category': 'Engine', 'group': 'Air Filters', 'prefix': 'Air Filter', 'prefix_es': 'Filtro de Aire'},
|
||||
'COMB.': {'category': 'Fuel & Air', 'group': 'Fuel Filters', 'prefix': 'Fuel Filter', 'prefix_es': 'Filtro de Combustible'},
|
||||
'CABINA': {'category': 'Heat & Air Conditioning', 'group': 'Cabin Air Filters', 'prefix': 'Cabin Air Filter', 'prefix_es': 'Filtro de Cabina'}
|
||||
}
|
||||
|
||||
# Known brands in catalog
|
||||
CATALOG_BRANDS = [
|
||||
'ACURA', 'ALFA ROMEO', 'AUDI', 'BMW', 'BUICK', 'CADILLAC',
|
||||
'CHEVROLET', 'CHRYSLER', 'DODGE', 'FIAT', 'FORD', 'GMC',
|
||||
'HONDA', 'HYUNDAI', 'INFINITI', 'JAGUAR', 'JEEP', 'KIA',
|
||||
'LEXUS', 'LINCOLN', 'MAZDA', 'MERCEDES BENZ', 'MERCURY',
|
||||
'MINI', 'MITSUBISHI', 'NISSAN', 'PEUGEOT', 'PONTIAC',
|
||||
'PORSCHE', 'RAM', 'RENAULT', 'SEAT', 'SMART', 'SUBARU',
|
||||
'SUZUKI', 'TOYOTA', 'VOLKSWAGEN', 'VOLVO'
|
||||
]
|
||||
|
||||
# Cross-reference brands
|
||||
XREF_BRANDS = ['AC DELCO', 'FRAM', 'INTERFIL', 'MANN', 'MOTORCRAFT']
|
||||
|
||||
def get_db():
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
conn.row_factory = sqlite3.Row
|
||||
return conn
|
||||
|
||||
def ensure_manufacturer(cursor, name, type_='aftermarket', quality='standard', country=None):
|
||||
"""Create manufacturer if not exists"""
|
||||
cursor.execute("SELECT id FROM manufacturers WHERE UPPER(name) = UPPER(?)", (name,))
|
||||
row = cursor.fetchone()
|
||||
if row:
|
||||
return row['id']
|
||||
|
||||
cursor.execute("""
|
||||
INSERT INTO manufacturers (name, type, quality_tier, country)
|
||||
VALUES (?, ?, ?, ?)
|
||||
""", (name, type_, quality, country))
|
||||
return cursor.lastrowid
|
||||
|
||||
def ensure_brand(cursor, name):
|
||||
"""Create brand if not exists"""
|
||||
cursor.execute("SELECT id FROM brands WHERE UPPER(name) = UPPER(?)", (name,))
|
||||
row = cursor.fetchone()
|
||||
if row:
|
||||
return row['id']
|
||||
|
||||
cursor.execute("INSERT INTO brands (name) VALUES (?)", (name,))
|
||||
return cursor.lastrowid
|
||||
|
||||
def ensure_model(cursor, brand_id, name):
|
||||
"""Create model if not exists"""
|
||||
cursor.execute("""
|
||||
SELECT id FROM models
|
||||
WHERE brand_id = ? AND UPPER(name) = UPPER(?)
|
||||
""", (brand_id, name))
|
||||
row = cursor.fetchone()
|
||||
if row:
|
||||
return row['id']
|
||||
|
||||
cursor.execute("INSERT INTO models (brand_id, name) VALUES (?, ?)", (brand_id, name))
|
||||
return cursor.lastrowid
|
||||
|
||||
def ensure_year(cursor, year):
|
||||
"""Create year if not exists"""
|
||||
cursor.execute("SELECT id FROM years WHERE year = ?", (year,))
|
||||
row = cursor.fetchone()
|
||||
if row:
|
||||
return row['id']
|
||||
|
||||
cursor.execute("INSERT INTO years (year) VALUES (?)", (year,))
|
||||
return cursor.lastrowid
|
||||
|
||||
def ensure_engine(cursor, name):
|
||||
"""Create engine if not exists"""
|
||||
cursor.execute("SELECT id FROM engines WHERE name = ?", (name,))
|
||||
row = cursor.fetchone()
|
||||
if row:
|
||||
return row['id']
|
||||
|
||||
# Parse engine details from name
|
||||
displacement = None
|
||||
cylinders = None
|
||||
fuel_type = 'gasoline' # lowercase to match DB constraint
|
||||
|
||||
# Parse displacement and cylinders from patterns like "L4-2.0L" or "V6-3.5L"
|
||||
match = re.match(r'([LV])(\d+)[-]?([\d.]+)L?', name)
|
||||
if match:
|
||||
engine_type = match.group(1) # L or V
|
||||
cylinders = int(match.group(2))
|
||||
displacement = int(float(match.group(3)) * 1000)
|
||||
|
||||
if 'DIESEL' in name.upper() or 'DIÉSEL' in name.upper():
|
||||
fuel_type = 'diesel'
|
||||
elif 'ELECTRIC' in name.upper():
|
||||
fuel_type = 'electric'
|
||||
elif 'HYBRID' in name.upper():
|
||||
fuel_type = 'hybrid'
|
||||
# Note: 'TURBO' is not a fuel type, it's a modifier - default to gasoline
|
||||
|
||||
cursor.execute("""
|
||||
INSERT INTO engines (name, displacement_cc, cylinders, fuel_type)
|
||||
VALUES (?, ?, ?, ?)
|
||||
""", (name, displacement, cylinders, fuel_type))
|
||||
return cursor.lastrowid
|
||||
|
||||
def ensure_mye(cursor, model_id, year_id, engine_id):
|
||||
"""Create model_year_engine if not exists"""
|
||||
cursor.execute("""
|
||||
SELECT id FROM model_year_engine
|
||||
WHERE model_id = ? AND year_id = ? AND engine_id = ?
|
||||
""", (model_id, year_id, engine_id))
|
||||
row = cursor.fetchone()
|
||||
if row:
|
||||
return row['id']
|
||||
|
||||
cursor.execute("""
|
||||
INSERT INTO model_year_engine (model_id, year_id, engine_id)
|
||||
VALUES (?, ?, ?)
|
||||
""", (model_id, year_id, engine_id))
|
||||
return cursor.lastrowid
|
||||
|
||||
def get_or_create_group(cursor, category_name, group_name):
|
||||
"""Get or create part group"""
|
||||
cursor.execute("SELECT id FROM part_categories WHERE name = ?", (category_name,))
|
||||
cat_row = cursor.fetchone()
|
||||
if not cat_row:
|
||||
return None
|
||||
|
||||
category_id = cat_row['id']
|
||||
|
||||
cursor.execute("""
|
||||
SELECT id FROM part_groups WHERE category_id = ? AND name = ?
|
||||
""", (category_id, group_name))
|
||||
group_row = cursor.fetchone()
|
||||
|
||||
if group_row:
|
||||
return group_row['id']
|
||||
|
||||
cursor.execute("""
|
||||
INSERT INTO part_groups (category_id, name, name_es)
|
||||
VALUES (?, ?, ?)
|
||||
""", (category_id, group_name, group_name))
|
||||
return cursor.lastrowid
|
||||
|
||||
def parse_year_range(year_str):
|
||||
"""Parse year range into list of years"""
|
||||
year_str = year_str.strip()
|
||||
|
||||
if re.match(r'^\d{4}$', year_str):
|
||||
return [int(year_str)]
|
||||
|
||||
match = re.match(r'(\d{4})\s*[-–]\s*(\d{4})', year_str)
|
||||
if match:
|
||||
start, end = int(match.group(1)), int(match.group(2))
|
||||
if start > end:
|
||||
start, end = end, start
|
||||
return list(range(end, start + 1))
|
||||
|
||||
return []
|
||||
|
||||
def classify_filter(part_number):
|
||||
"""Classify filter type by part number prefix"""
|
||||
part_number = part_number.upper()
|
||||
if part_number.startswith('GP-') or part_number.startswith('GPS-'):
|
||||
if part_number.startswith('GPS-'):
|
||||
return 'SINTÉTICO'
|
||||
return 'ACEITE'
|
||||
elif part_number.startswith('GA-') and not part_number.startswith('GAC-') and not part_number.startswith('GAVW-'):
|
||||
return 'AIRE'
|
||||
elif part_number.startswith('GG-'):
|
||||
return 'COMB.'
|
||||
elif part_number.startswith('GAC-'):
|
||||
return 'CABINA'
|
||||
elif part_number.startswith('G-'):
|
||||
return 'ACEITE' # Generic oil filter
|
||||
return None
|
||||
|
||||
def extract_vehicle_entries(pdf):
|
||||
"""Extract all vehicle entries from catalog"""
|
||||
entries = []
|
||||
current_brand = None
|
||||
current_model = None
|
||||
|
||||
for page in pdf.pages:
|
||||
text = page.extract_text()
|
||||
if not text:
|
||||
continue
|
||||
|
||||
for line in text.split('\n'):
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
|
||||
# Skip header/footer
|
||||
if 'AÑO' in line and 'MOTOR' in line:
|
||||
continue
|
||||
if 'Los filtros Gonher' in line:
|
||||
continue
|
||||
|
||||
# Brand detection
|
||||
if line in CATALOG_BRANDS:
|
||||
current_brand = line
|
||||
current_model = None
|
||||
continue
|
||||
|
||||
# Handle (Continúa) lines
|
||||
if '(Continúa)' in line:
|
||||
match = re.match(r'^([A-Z][A-Z0-9\s\-]+)\s*\(Continúa\)', line)
|
||||
if match:
|
||||
potential = match.group(1).strip()
|
||||
if potential in CATALOG_BRANDS:
|
||||
current_brand = potential
|
||||
elif current_brand:
|
||||
current_model = potential
|
||||
continue
|
||||
|
||||
# Model detection
|
||||
if current_brand:
|
||||
if re.match(r'^[A-Z][A-Z0-9\s\-/]+$', line) and not re.match(r'^\d{4}', line):
|
||||
if line not in ['ACEITE', 'AIRE', 'COMB', 'CABINA', 'SINTÉTICO', 'AÑO', 'MOTOR']:
|
||||
if not re.match(r'^G[APCS]?[-]?\d', line): # Not a part number
|
||||
current_model = line
|
||||
continue
|
||||
|
||||
# Data line with year
|
||||
if current_brand and current_model:
|
||||
match = re.match(r'^(\d{4}(?:\s*[-–]\s*\d{4})?)\s+(.+)$', line)
|
||||
if match:
|
||||
year_str = match.group(1)
|
||||
rest = match.group(2)
|
||||
|
||||
# Extract motor
|
||||
motor_match = re.match(r'^([LV]\d+[-][\d.]+L(?:\s+(?:Turbo|TURBOCHARGED|diésel|ELECTRIC|HYBRID))?)\s*(.*)$', rest, re.IGNORECASE)
|
||||
if motor_match:
|
||||
motor = motor_match.group(1).strip()
|
||||
filters_str = motor_match.group(2).strip()
|
||||
|
||||
# Parse filter part numbers
|
||||
filter_parts = re.findall(r'G[A-Z]*[-]?[\dA-Z]+(?:\(\d+\))?', filters_str)
|
||||
|
||||
years = parse_year_range(year_str)
|
||||
|
||||
if years:
|
||||
for year in years:
|
||||
entry = {
|
||||
'brand': current_brand,
|
||||
'model': current_model,
|
||||
'year': year,
|
||||
'motor': motor,
|
||||
'filters': {}
|
||||
}
|
||||
|
||||
for fp in filter_parts:
|
||||
fp_clean = re.sub(r'\(\d+\)', '', fp)
|
||||
filter_type = classify_filter(fp_clean)
|
||||
if filter_type:
|
||||
entry['filters'][filter_type] = fp_clean
|
||||
|
||||
if entry['filters']:
|
||||
entries.append(entry)
|
||||
|
||||
return entries
|
||||
|
||||
def extract_cross_references(pdf):
|
||||
"""Extract cross-reference data from catalog"""
|
||||
xrefs = []
|
||||
current_brand = None
|
||||
|
||||
# Cross-references are typically in pages 117+
|
||||
for i in range(117, len(pdf.pages)):
|
||||
text = pdf.pages[i].extract_text()
|
||||
if not text:
|
||||
continue
|
||||
|
||||
for line in text.split('\n'):
|
||||
line = line.strip()
|
||||
|
||||
# Brand header
|
||||
if line in XREF_BRANDS:
|
||||
current_brand = line
|
||||
continue
|
||||
|
||||
# Cross-reference line
|
||||
if current_brand:
|
||||
match = re.match(r'^([A-Z0-9\-/]+)\s+(G[A-Z]*[-]?\d+[A-Z]*)$', line)
|
||||
if match:
|
||||
xrefs.append({
|
||||
'brand': current_brand,
|
||||
'part_number': match.group(1),
|
||||
'gonher_part': match.group(2)
|
||||
})
|
||||
|
||||
return xrefs
|
||||
|
||||
def main():
|
||||
print("=" * 70)
|
||||
print("IMPORTADOR COMPLETO - CATÁLOGO GONHER 2022")
|
||||
print("=" * 70)
|
||||
|
||||
# Read PDF
|
||||
print(f"\n[1/7] Leyendo PDF: {PDF_PATH}")
|
||||
pdf = pypdf.PdfReader(PDF_PATH)
|
||||
print(f" Total páginas: {len(pdf.pages)}")
|
||||
|
||||
# Extract data
|
||||
print("\n[2/7] Extrayendo datos del catálogo...")
|
||||
vehicle_entries = extract_vehicle_entries(pdf)
|
||||
cross_refs = extract_cross_references(pdf)
|
||||
print(f" Entradas de vehículos: {len(vehicle_entries)}")
|
||||
print(f" Referencias cruzadas: {len(cross_refs)}")
|
||||
|
||||
# Get unique filters
|
||||
unique_filters = {}
|
||||
for entry in vehicle_entries:
|
||||
for filter_type, part_num in entry['filters'].items():
|
||||
if part_num not in unique_filters:
|
||||
unique_filters[part_num] = filter_type
|
||||
print(f" Filtros únicos: {len(unique_filters)}")
|
||||
|
||||
# Connect to database
|
||||
conn = get_db()
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Create manufacturers
|
||||
print("\n[3/7] Creando fabricantes...")
|
||||
manufacturers = {
|
||||
'Gonher': ensure_manufacturer(cursor, 'Gonher', 'aftermarket', 'standard', 'Mexico'),
|
||||
'AC Delco': ensure_manufacturer(cursor, 'AC Delco', 'oem', 'oem', 'USA'),
|
||||
'Fram': ensure_manufacturer(cursor, 'Fram', 'aftermarket', 'standard', 'USA'),
|
||||
'Interfil': ensure_manufacturer(cursor, 'Interfil', 'aftermarket', 'economy', 'Mexico'),
|
||||
'Mann': ensure_manufacturer(cursor, 'Mann', 'aftermarket', 'premium', 'Germany'),
|
||||
'Motorcraft': ensure_manufacturer(cursor, 'Motorcraft', 'oem', 'oem', 'USA'),
|
||||
}
|
||||
print(f" Fabricantes: {list(manufacturers.keys())}")
|
||||
|
||||
# Create vehicles
|
||||
print("\n[4/7] Creando vehículos faltantes...")
|
||||
vehicles_created = 0
|
||||
mye_cache = {}
|
||||
|
||||
for entry in vehicle_entries:
|
||||
cache_key = (entry['brand'], entry['model'], entry['year'], entry['motor'])
|
||||
if cache_key in mye_cache:
|
||||
continue
|
||||
|
||||
# Check if vehicle exists
|
||||
cursor.execute("""
|
||||
SELECT mye.id FROM model_year_engine mye
|
||||
JOIN models m ON mye.model_id = m.id
|
||||
JOIN brands b ON m.brand_id = b.id
|
||||
JOIN years y ON mye.year_id = y.id
|
||||
JOIN engines e ON mye.engine_id = e.id
|
||||
WHERE UPPER(b.name) = UPPER(?) AND UPPER(m.name) = UPPER(?)
|
||||
AND y.year = ? AND e.name = ?
|
||||
LIMIT 1
|
||||
""", (entry['brand'], entry['model'], entry['year'], entry['motor']))
|
||||
|
||||
existing = cursor.fetchone()
|
||||
if existing:
|
||||
mye_cache[cache_key] = existing['id']
|
||||
else:
|
||||
# Create vehicle
|
||||
brand_id = ensure_brand(cursor, entry['brand'])
|
||||
model_id = ensure_model(cursor, brand_id, entry['model'])
|
||||
year_id = ensure_year(cursor, entry['year'])
|
||||
engine_id = ensure_engine(cursor, entry['motor'])
|
||||
mye_id = ensure_mye(cursor, model_id, year_id, engine_id)
|
||||
mye_cache[cache_key] = mye_id
|
||||
vehicles_created += 1
|
||||
|
||||
print(f" Vehículos creados: {vehicles_created}")
|
||||
|
||||
# Create filter parts
|
||||
print("\n[5/7] Creando partes de filtros...")
|
||||
filter_parts = {}
|
||||
parts_created = 0
|
||||
|
||||
for part_num, filter_type in unique_filters.items():
|
||||
config = FILTER_TYPES.get(filter_type)
|
||||
if not config:
|
||||
continue
|
||||
|
||||
group_id = get_or_create_group(cursor, config['category'], config['group'])
|
||||
if not group_id:
|
||||
continue
|
||||
|
||||
# Check if part exists
|
||||
cursor.execute("SELECT id FROM parts WHERE oem_part_number = ?", (part_num,))
|
||||
existing = cursor.fetchone()
|
||||
|
||||
if existing:
|
||||
filter_parts[part_num] = existing['id']
|
||||
else:
|
||||
name = f"{config['prefix']} {part_num}"
|
||||
name_es = f"{config['prefix_es']} {part_num}"
|
||||
|
||||
cursor.execute("""
|
||||
INSERT INTO parts (oem_part_number, name, name_es, group_id, description)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
""", (part_num, name, name_es, group_id, f"Gonher {config['prefix']}"))
|
||||
|
||||
filter_parts[part_num] = cursor.lastrowid
|
||||
parts_created += 1
|
||||
|
||||
print(f" Partes creadas: {parts_created}")
|
||||
|
||||
# Create fitments
|
||||
print("\n[6/7] Creando fitments (vehículo-parte)...")
|
||||
fitments_created = 0
|
||||
|
||||
for entry in vehicle_entries:
|
||||
cache_key = (entry['brand'], entry['model'], entry['year'], entry['motor'])
|
||||
mye_id = mye_cache.get(cache_key)
|
||||
|
||||
if not mye_id:
|
||||
continue
|
||||
|
||||
for filter_type, part_num in entry['filters'].items():
|
||||
part_id = filter_parts.get(part_num)
|
||||
if not part_id:
|
||||
continue
|
||||
|
||||
# Check if fitment exists
|
||||
cursor.execute("""
|
||||
SELECT id FROM vehicle_parts
|
||||
WHERE model_year_engine_id = ? AND part_id = ?
|
||||
""", (mye_id, part_id))
|
||||
|
||||
if not cursor.fetchone():
|
||||
cursor.execute("""
|
||||
INSERT INTO vehicle_parts (model_year_engine_id, part_id, quantity_required, fitment_notes)
|
||||
VALUES (?, ?, 1, ?)
|
||||
""", (mye_id, part_id, f"Catálogo Gonher 2022 - {filter_type}"))
|
||||
fitments_created += 1
|
||||
|
||||
print(f" Fitments creados: {fitments_created}")
|
||||
|
||||
# Create cross-references
|
||||
print("\n[7/7] Creando referencias cruzadas...")
|
||||
xrefs_created = 0
|
||||
|
||||
for xref in cross_refs:
|
||||
gonher_part_id = filter_parts.get(xref['gonher_part'])
|
||||
if not gonher_part_id:
|
||||
# Part might not exist yet, try to find by OEM number
|
||||
cursor.execute("SELECT id FROM parts WHERE oem_part_number = ?", (xref['gonher_part'],))
|
||||
row = cursor.fetchone()
|
||||
if row:
|
||||
gonher_part_id = row['id']
|
||||
else:
|
||||
continue
|
||||
|
||||
# Check if cross-reference exists
|
||||
cursor.execute("""
|
||||
SELECT id FROM part_cross_references
|
||||
WHERE part_id = ? AND cross_reference_number = ?
|
||||
""", (gonher_part_id, xref['part_number']))
|
||||
|
||||
if not cursor.fetchone():
|
||||
# Map brand to reference type
|
||||
ref_type = 'interchange'
|
||||
if xref['brand'] in ['AC DELCO', 'MOTORCRAFT']:
|
||||
ref_type = 'oem_alternate'
|
||||
|
||||
cursor.execute("""
|
||||
INSERT INTO part_cross_references (part_id, cross_reference_number, reference_type)
|
||||
VALUES (?, ?, ?)
|
||||
""", (gonher_part_id, xref['part_number'], ref_type))
|
||||
xrefs_created += 1
|
||||
|
||||
print(f" Referencias cruzadas creadas: {xrefs_created}")
|
||||
|
||||
# Commit
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print("IMPORTACIÓN COMPLETADA")
|
||||
print("=" * 70)
|
||||
print(f"""
|
||||
RESUMEN:
|
||||
- Vehículos creados: {vehicles_created:,}
|
||||
- Partes creadas: {parts_created:,}
|
||||
- Fitments creados: {fitments_created:,}
|
||||
- Cross-refs creadas: {xrefs_created:,}
|
||||
- Fabricantes: {len(manufacturers)}
|
||||
""")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
315
vehicle_database/scripts/populate_demo_data.py
Normal file
315
vehicle_database/scripts/populate_demo_data.py
Normal file
@@ -0,0 +1,315 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Script para poblar la base de datos con datos de demo realistas.
|
||||
Incluye partes OEM, fabricantes aftermarket, partes alternativas y fitments.
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
from pathlib import Path
|
||||
|
||||
DB_PATH = Path(__file__).parent.parent / 'vehicle_database.db'
|
||||
|
||||
# Demo Parts Data - Partes realistas comunes
|
||||
DEMO_PARTS = [
|
||||
# Brake System (category 2, group varies)
|
||||
{'oem': '04465-33450', 'name': 'Front Brake Pad Set', 'name_es': 'Juego de Pastillas Delanteras', 'group': 'Brake Pads/Shoes', 'desc': 'Ceramic front brake pads', 'weight': 1.2},
|
||||
{'oem': '04466-33180', 'name': 'Rear Brake Pad Set', 'name_es': 'Juego de Pastillas Traseras', 'group': 'Brake Pads/Shoes', 'desc': 'Ceramic rear brake pads', 'weight': 0.9},
|
||||
{'oem': '43512-33130', 'name': 'Front Brake Rotor', 'name_es': 'Disco de Freno Delantero', 'group': 'Brake Rotors', 'desc': 'Vented front brake disc', 'weight': 8.5},
|
||||
{'oem': '42431-33130', 'name': 'Rear Brake Rotor', 'name_es': 'Disco de Freno Trasero', 'group': 'Brake Rotors', 'desc': 'Solid rear brake disc', 'weight': 5.2},
|
||||
{'oem': '47750-33210', 'name': 'Brake Caliper Front Right', 'name_es': 'Caliper Delantero Derecho', 'group': 'Brake Calipers', 'desc': 'Front right brake caliper assembly', 'weight': 4.5},
|
||||
{'oem': '47730-33210', 'name': 'Brake Caliper Front Left', 'name_es': 'Caliper Delantero Izquierdo', 'group': 'Brake Calipers', 'desc': 'Front left brake caliper assembly', 'weight': 4.5},
|
||||
|
||||
# Engine (category 6)
|
||||
{'oem': '90915-YZZD4', 'name': 'Oil Filter', 'name_es': 'Filtro de Aceite', 'group': 'Oil Filters', 'desc': 'Spin-on oil filter element', 'weight': 0.3},
|
||||
{'oem': '17801-0P010', 'name': 'Air Filter', 'name_es': 'Filtro de Aire', 'group': 'Air Filters', 'desc': 'Engine air filter element', 'weight': 0.25},
|
||||
{'oem': '23300-79525', 'name': 'Fuel Filter', 'name_es': 'Filtro de Combustible', 'group': 'Fuel Filters', 'desc': 'In-line fuel filter', 'weight': 0.4},
|
||||
{'oem': '90048-51003', 'name': 'Spark Plug Iridium', 'name_es': 'Bujia de Iridio', 'group': 'Spark Plugs', 'desc': 'Long-life iridium spark plug', 'weight': 0.05},
|
||||
{'oem': '13568-09130', 'name': 'Timing Belt', 'name_es': 'Banda de Tiempo', 'group': 'Timing Components', 'desc': 'Engine timing belt', 'weight': 0.35},
|
||||
{'oem': 'SU003-02574', 'name': 'Serpentine Belt', 'name_es': 'Banda Serpentina', 'group': 'Belts', 'desc': 'Multi-rib accessory drive belt', 'weight': 0.2},
|
||||
{'oem': '16100-09515', 'name': 'Water Pump', 'name_es': 'Bomba de Agua', 'group': 'Water Pumps', 'desc': 'Engine coolant water pump', 'weight': 2.1},
|
||||
{'oem': '16363-0P030', 'name': 'Thermostat', 'name_es': 'Termostato', 'group': 'Thermostats', 'desc': 'Engine thermostat with housing', 'weight': 0.3},
|
||||
|
||||
# Suspension (category 11)
|
||||
{'oem': '48157-42010', 'name': 'Front Strut Mount', 'name_es': 'Soporte de Amortiguador Delantero', 'group': 'Strut Mounts', 'desc': 'Front suspension strut mount', 'weight': 0.8},
|
||||
{'oem': '48609-48020', 'name': 'Front Strut Assembly', 'name_es': 'Amortiguador Delantero Completo', 'group': 'Struts/Shocks', 'desc': 'Complete front strut assembly', 'weight': 6.5},
|
||||
{'oem': '48530-09S00', 'name': 'Rear Shock Absorber', 'name_es': 'Amortiguador Trasero', 'group': 'Struts/Shocks', 'desc': 'Rear shock absorber', 'weight': 3.2},
|
||||
{'oem': '48068-33070', 'name': 'Front Lower Control Arm', 'name_es': 'Brazo de Control Inferior Delantero', 'group': 'Control Arms', 'desc': 'Front lower control arm with bushing', 'weight': 4.8},
|
||||
{'oem': '48725-33050', 'name': 'Rear Trailing Arm', 'name_es': 'Brazo Trasero', 'group': 'Control Arms', 'desc': 'Rear suspension trailing arm', 'weight': 3.5},
|
||||
{'oem': '48815-33070', 'name': 'Sway Bar Link Front', 'name_es': 'Enlace de Barra Estabilizadora', 'group': 'Sway Bar Links', 'desc': 'Front stabilizer bar end link', 'weight': 0.4},
|
||||
|
||||
# Steering (category 10)
|
||||
{'oem': '45046-39335', 'name': 'Outer Tie Rod End', 'name_es': 'Terminal Exterior de Direccion', 'group': 'Tie Rods', 'desc': 'Outer steering tie rod end', 'weight': 0.6},
|
||||
{'oem': '45503-39165', 'name': 'Inner Tie Rod', 'name_es': 'Terminal Interior de Direccion', 'group': 'Tie Rods', 'desc': 'Inner steering tie rod', 'weight': 0.5},
|
||||
{'oem': '44200-33480', 'name': 'Power Steering Pump', 'name_es': 'Bomba de Direccion', 'group': 'Power Steering', 'desc': 'Hydraulic power steering pump', 'weight': 3.8},
|
||||
{'oem': '45510-33310', 'name': 'Steering Rack', 'name_es': 'Cremallera de Direccion', 'group': 'Steering Racks', 'desc': 'Power steering rack and pinion', 'weight': 12.5},
|
||||
|
||||
# Electrical (category 5)
|
||||
{'oem': '28100-21030', 'name': 'Starter Motor', 'name_es': 'Motor de Arranque', 'group': 'Starters', 'desc': 'Engine starter motor', 'weight': 4.2},
|
||||
{'oem': '27060-37030', 'name': 'Alternator', 'name_es': 'Alternador', 'group': 'Alternators', 'desc': '100 amp alternator', 'weight': 5.8},
|
||||
{'oem': '90919-02240', 'name': 'Ignition Coil', 'name_es': 'Bobina de Ignicion', 'group': 'Ignition Coils', 'desc': 'Direct ignition coil', 'weight': 0.35},
|
||||
{'oem': '89467-33040', 'name': 'Oxygen Sensor', 'name_es': 'Sensor de Oxigeno', 'group': 'Sensors', 'desc': 'Upstream O2 sensor', 'weight': 0.15},
|
||||
|
||||
# Cooling System (category 3)
|
||||
{'oem': '16400-0W010', 'name': 'Radiator', 'name_es': 'Radiador', 'group': 'Radiators', 'desc': 'Engine cooling radiator', 'weight': 8.5},
|
||||
{'oem': '16711-31310', 'name': 'Radiator Fan', 'name_es': 'Ventilador de Radiador', 'group': 'Cooling Fans', 'desc': 'Electric radiator cooling fan', 'weight': 3.2},
|
||||
{'oem': '16361-0P030', 'name': 'Radiator Hose Upper', 'name_es': 'Manguera Superior de Radiador', 'group': 'Hoses', 'desc': 'Upper radiator coolant hose', 'weight': 0.4},
|
||||
|
||||
# Exhaust (category 7)
|
||||
{'oem': '17140-31620', 'name': 'Catalytic Converter', 'name_es': 'Convertidor Catalitico', 'group': 'Catalytic Converters', 'desc': 'Three-way catalytic converter', 'weight': 6.5},
|
||||
{'oem': '17430-31410', 'name': 'Muffler', 'name_es': 'Silenciador', 'group': 'Mufflers', 'desc': 'Rear exhaust muffler', 'weight': 8.2},
|
||||
|
||||
# Fuel System (category 8)
|
||||
{'oem': '23220-31100', 'name': 'Fuel Pump', 'name_es': 'Bomba de Combustible', 'group': 'Fuel Pumps', 'desc': 'Electric in-tank fuel pump', 'weight': 0.8},
|
||||
{'oem': '23250-31010', 'name': 'Fuel Injector', 'name_es': 'Inyector de Combustible', 'group': 'Fuel Injectors', 'desc': 'Multi-port fuel injector', 'weight': 0.1},
|
||||
|
||||
# Drivetrain (category 4)
|
||||
{'oem': '31470-52011', 'name': 'Clutch Kit', 'name_es': 'Kit de Embrague', 'group': 'Clutches', 'desc': 'Complete clutch replacement kit', 'weight': 7.5},
|
||||
{'oem': '43502-35210', 'name': 'CV Axle Front', 'name_es': 'Flecha Homocinética Delantera', 'group': 'CV Axles', 'desc': 'Front CV axle shaft assembly', 'weight': 5.2},
|
||||
|
||||
# HVAC (category 9)
|
||||
{'oem': '88310-33250', 'name': 'AC Compressor', 'name_es': 'Compresor de AC', 'group': 'AC Compressors', 'desc': 'Air conditioning compressor', 'weight': 6.8},
|
||||
{'oem': '87103-33110', 'name': 'Blower Motor', 'name_es': 'Motor de Ventilador', 'group': 'Blower Motors', 'desc': 'HVAC blower motor', 'weight': 1.5},
|
||||
{'oem': '87139-YZZ05', 'name': 'Cabin Air Filter', 'name_es': 'Filtro de Cabina', 'group': 'Cabin Filters', 'desc': 'Cabin air filter element', 'weight': 0.15},
|
||||
]
|
||||
|
||||
# Demo Manufacturers
|
||||
DEMO_MANUFACTURERS = [
|
||||
{'name': 'Brembo', 'type': 'aftermarket', 'tier': 'premium', 'country': 'Italy'},
|
||||
{'name': 'Bosch', 'type': 'aftermarket', 'tier': 'premium', 'country': 'Germany'},
|
||||
{'name': 'Denso', 'type': 'aftermarket', 'tier': 'premium', 'country': 'Japan'},
|
||||
{'name': 'ACDelco', 'type': 'aftermarket', 'tier': 'standard', 'country': 'USA'},
|
||||
{'name': 'Raybestos', 'type': 'aftermarket', 'tier': 'standard', 'country': 'USA'},
|
||||
{'name': 'Wagner', 'type': 'aftermarket', 'tier': 'standard', 'country': 'USA'},
|
||||
{'name': 'Monroe', 'type': 'aftermarket', 'tier': 'standard', 'country': 'USA'},
|
||||
{'name': 'KYB', 'type': 'aftermarket', 'tier': 'premium', 'country': 'Japan'},
|
||||
{'name': 'NGK', 'type': 'aftermarket', 'tier': 'premium', 'country': 'Japan'},
|
||||
{'name': 'Aisin', 'type': 'aftermarket', 'tier': 'premium', 'country': 'Japan'},
|
||||
{'name': 'Gates', 'type': 'aftermarket', 'tier': 'premium', 'country': 'USA'},
|
||||
{'name': 'Continental', 'type': 'aftermarket', 'tier': 'premium', 'country': 'Germany'},
|
||||
{'name': 'Moog', 'type': 'aftermarket', 'tier': 'standard', 'country': 'USA'},
|
||||
{'name': 'Dorman', 'type': 'aftermarket', 'tier': 'economy', 'country': 'USA'},
|
||||
{'name': 'Centric', 'type': 'aftermarket', 'tier': 'standard', 'country': 'USA'},
|
||||
{'name': 'Beck/Arnley', 'type': 'aftermarket', 'tier': 'standard', 'country': 'USA'},
|
||||
{'name': 'Cardone', 'type': 'remanufactured', 'tier': 'standard', 'country': 'USA'},
|
||||
{'name': 'Standard Motor', 'type': 'aftermarket', 'tier': 'standard', 'country': 'USA'},
|
||||
{'name': 'Spectra Premium', 'type': 'aftermarket', 'tier': 'economy', 'country': 'Canada'},
|
||||
{'name': 'TRW', 'type': 'aftermarket', 'tier': 'premium', 'country': 'Germany'},
|
||||
]
|
||||
|
||||
|
||||
def get_group_id(cursor, group_name):
|
||||
"""Get or create a group by name"""
|
||||
cursor.execute("SELECT id FROM part_groups WHERE name = ?", (group_name,))
|
||||
row = cursor.fetchone()
|
||||
if row:
|
||||
return row[0]
|
||||
return None
|
||||
|
||||
|
||||
def populate_parts(cursor):
|
||||
"""Populate OEM parts"""
|
||||
print("Adding OEM parts...")
|
||||
added = 0
|
||||
for part in DEMO_PARTS:
|
||||
group_id = get_group_id(cursor, part['group'])
|
||||
if not group_id:
|
||||
print(f" Warning: Group '{part['group']}' not found, skipping {part['oem']}")
|
||||
continue
|
||||
|
||||
# Check if part already exists
|
||||
cursor.execute("SELECT id FROM parts WHERE oem_part_number = ?", (part['oem'],))
|
||||
if cursor.fetchone():
|
||||
continue
|
||||
|
||||
cursor.execute("""
|
||||
INSERT INTO parts (oem_part_number, name, name_es, group_id, description, weight_kg)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
""", (part['oem'], part['name'], part['name_es'], group_id, part['desc'], part['weight']))
|
||||
added += 1
|
||||
|
||||
print(f" Added {added} OEM parts")
|
||||
return added
|
||||
|
||||
|
||||
def populate_manufacturers(cursor):
|
||||
"""Populate manufacturers"""
|
||||
print("Adding manufacturers...")
|
||||
added = 0
|
||||
for mfr in DEMO_MANUFACTURERS:
|
||||
cursor.execute("SELECT id FROM manufacturers WHERE name = ?", (mfr['name'],))
|
||||
if cursor.fetchone():
|
||||
continue
|
||||
|
||||
cursor.execute("""
|
||||
INSERT INTO manufacturers (name, type, quality_tier, country)
|
||||
VALUES (?, ?, ?, ?)
|
||||
""", (mfr['name'], mfr['type'], mfr['tier'], mfr['country']))
|
||||
added += 1
|
||||
|
||||
print(f" Added {added} manufacturers")
|
||||
return added
|
||||
|
||||
|
||||
def populate_aftermarket(cursor):
|
||||
"""Populate aftermarket parts linked to OEM"""
|
||||
print("Adding aftermarket alternatives...")
|
||||
|
||||
# Get all parts and manufacturers
|
||||
cursor.execute("SELECT id, oem_part_number, name FROM parts")
|
||||
parts = cursor.fetchall()
|
||||
|
||||
cursor.execute("SELECT id, name, quality_tier FROM manufacturers WHERE type != 'oem'")
|
||||
manufacturers = cursor.fetchall()
|
||||
|
||||
added = 0
|
||||
import random
|
||||
|
||||
for part in parts:
|
||||
part_id, oem_num, part_name = part
|
||||
|
||||
# Add 2-4 aftermarket alternatives per OEM part
|
||||
num_alternatives = random.randint(2, 4)
|
||||
selected_mfrs = random.sample(manufacturers, min(num_alternatives, len(manufacturers)))
|
||||
|
||||
for mfr in selected_mfrs:
|
||||
mfr_id, mfr_name, tier = mfr
|
||||
|
||||
# Check if already exists
|
||||
cursor.execute("""
|
||||
SELECT id FROM aftermarket_parts WHERE oem_part_id = ? AND manufacturer_id = ?
|
||||
""", (part_id, mfr_id))
|
||||
if cursor.fetchone():
|
||||
continue
|
||||
|
||||
# Generate part number
|
||||
prefix = mfr_name[:3].upper()
|
||||
part_num = f"{prefix}-{oem_num.replace('-', '')[:6]}"
|
||||
|
||||
# Set price based on tier
|
||||
base_price = random.uniform(20, 200)
|
||||
if tier == 'premium':
|
||||
price = base_price * 1.5
|
||||
warranty = 36
|
||||
elif tier == 'economy':
|
||||
price = base_price * 0.6
|
||||
warranty = 12
|
||||
else:
|
||||
price = base_price
|
||||
warranty = 24
|
||||
|
||||
cursor.execute("""
|
||||
INSERT INTO aftermarket_parts (oem_part_id, manufacturer_id, part_number, name, quality_tier, price_usd, warranty_months)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
""", (part_id, mfr_id, part_num, part_name, tier, round(price, 2), warranty))
|
||||
added += 1
|
||||
|
||||
print(f" Added {added} aftermarket parts")
|
||||
return added
|
||||
|
||||
|
||||
def populate_fitments(cursor):
|
||||
"""Create fitments linking parts to vehicles"""
|
||||
print("Adding fitments (linking parts to vehicles)...")
|
||||
|
||||
# Get some vehicle configurations (first 50)
|
||||
cursor.execute("""
|
||||
SELECT mye.id, b.name as brand, m.name as model, y.year
|
||||
FROM model_year_engine mye
|
||||
JOIN models m ON mye.model_id = m.id
|
||||
JOIN brands b ON m.brand_id = b.id
|
||||
JOIN years y ON mye.year_id = y.id
|
||||
WHERE b.name IN ('TOYOTA', 'HONDA', 'FORD', 'CHEVROLET', 'NISSAN')
|
||||
ORDER BY RANDOM()
|
||||
LIMIT 100
|
||||
""")
|
||||
vehicles = cursor.fetchall()
|
||||
|
||||
# Get all parts
|
||||
cursor.execute("SELECT id, name FROM parts")
|
||||
parts = cursor.fetchall()
|
||||
|
||||
added = 0
|
||||
import random
|
||||
|
||||
for vehicle in vehicles:
|
||||
mye_id = vehicle[0]
|
||||
|
||||
# Assign 10-20 random parts to each vehicle
|
||||
num_parts = random.randint(10, 20)
|
||||
selected_parts = random.sample(parts, min(num_parts, len(parts)))
|
||||
|
||||
for part in selected_parts:
|
||||
part_id = part[0]
|
||||
|
||||
# Check if fitment already exists
|
||||
cursor.execute("""
|
||||
SELECT id FROM vehicle_parts WHERE model_year_engine_id = ? AND part_id = ?
|
||||
""", (mye_id, part_id))
|
||||
if cursor.fetchone():
|
||||
continue
|
||||
|
||||
# Determine position based on part name
|
||||
part_name = part[1].lower()
|
||||
position = None
|
||||
quantity = 1
|
||||
|
||||
if 'front' in part_name:
|
||||
position = 'front'
|
||||
elif 'rear' in part_name:
|
||||
position = 'rear'
|
||||
elif 'left' in part_name:
|
||||
position = 'left'
|
||||
elif 'right' in part_name:
|
||||
position = 'right'
|
||||
|
||||
# Some parts need multiples
|
||||
if 'spark plug' in part_name or 'injector' in part_name:
|
||||
quantity = 4
|
||||
elif 'pad' in part_name or 'rotor' in part_name:
|
||||
quantity = 2 if position else 1
|
||||
|
||||
cursor.execute("""
|
||||
INSERT INTO vehicle_parts (model_year_engine_id, part_id, quantity_required, position)
|
||||
VALUES (?, ?, ?, ?)
|
||||
""", (mye_id, part_id, quantity, position))
|
||||
added += 1
|
||||
|
||||
print(f" Added {added} fitments")
|
||||
return added
|
||||
|
||||
|
||||
def main():
|
||||
print("=" * 50)
|
||||
print("Populating Demo Data")
|
||||
print("=" * 50)
|
||||
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
conn.row_factory = sqlite3.Row
|
||||
cursor = conn.cursor()
|
||||
|
||||
try:
|
||||
populate_parts(cursor)
|
||||
populate_manufacturers(cursor)
|
||||
populate_aftermarket(cursor)
|
||||
populate_fitments(cursor)
|
||||
|
||||
conn.commit()
|
||||
print("\nDemo data populated successfully!")
|
||||
|
||||
# Show final counts
|
||||
print("\n=== Final Counts ===")
|
||||
for table in ['parts', 'manufacturers', 'aftermarket_parts', 'vehicle_parts']:
|
||||
cursor.execute(f"SELECT COUNT(*) FROM {table}")
|
||||
count = cursor.fetchone()[0]
|
||||
print(f" {table}: {count}")
|
||||
|
||||
except Exception as e:
|
||||
conn.rollback()
|
||||
print(f"Error: {e}")
|
||||
raise
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user