Files
Autoparts-DB/vehicle_database/scripts/populate_fase4.py
consultoria-as d4d1c9b7ba Implement complete autoparts catalog system (5 phases)
FASE 1: Parts Database
- Added part_categories, part_groups, parts, vehicle_parts tables
- 12 categories, 190 groups with Spanish translations
- API endpoints for categories, groups, parts CRUD

FASE 2: Cross-References & Aftermarket
- Added manufacturers, aftermarket_parts, part_cross_references tables
- 24 manufacturers, quality tier system (economy/standard/premium/oem)
- Part number search across OEM and aftermarket

FASE 3: Exploded Diagrams
- Added diagrams, vehicle_diagrams, diagram_hotspots tables
- SVG viewer with zoom controls and interactive hotspots
- 3 sample diagrams (brake, oil filter, suspension)

FASE 4: Search & VIN Decoder
- SQLite FTS5 full-text search with auto-sync triggers
- NHTSA VIN decoder API integration with 30-day cache
- Unified search endpoint

FASE 5: Optimization & UX
- API pagination (page/per_page, max 100 items)
- Dark mode with localStorage persistence
- Keyboard shortcuts (/, Ctrl+K, Escape, Backspace, Ctrl+D)
- Breadcrumb navigation
- ARIA accessibility (labels, roles, focus management)
- Skip link for keyboard users

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 07:13:46 +00:00

313 lines
11 KiB
Python
Executable File

#!/usr/bin/env python3
"""
FASE 4: Full-Text Search and VIN Decoder
This script creates FASE 4 tables (FTS5, triggers, vin_cache) and populates
the parts_fts table with existing parts data.
"""
import sqlite3
import os
import json
from typing import Optional
from datetime import datetime, timedelta
# Database path configuration
DB_PATH = os.path.join(os.path.dirname(__file__), '..', 'vehicle_database.db')
SCHEMA_PATH = os.path.join(os.path.dirname(__file__), '..', 'sql', 'schema.sql')
class Fase4Manager:
"""Manager for FASE 4 tables: parts_fts, vin_cache, and related triggers"""
def __init__(self, db_path: str = DB_PATH):
self.db_path = db_path
self.connection = None
def connect(self):
"""Connect to the SQLite database"""
self.connection = sqlite3.connect(self.db_path)
self.connection.row_factory = sqlite3.Row
print(f"Connected to database: {self.db_path}")
def disconnect(self):
"""Close the database connection"""
if self.connection:
self.connection.close()
print("Disconnected from database")
def create_fase4_tables(self):
"""Create FASE 4 tables from schema file"""
if not os.path.exists(SCHEMA_PATH):
raise FileNotFoundError(f"Schema file not found: {SCHEMA_PATH}")
with open(SCHEMA_PATH, 'r') as f:
schema = f.read()
if self.connection:
cursor = self.connection.cursor()
cursor.executescript(schema)
self.connection.commit()
print("FASE 4 tables created successfully")
def check_fts_table_exists(self) -> bool:
"""Check if the parts_fts FTS5 table exists"""
cursor = self.connection.cursor()
cursor.execute(
"SELECT name FROM sqlite_master WHERE type='table' AND name='parts_fts'"
)
return cursor.fetchone() is not None
def check_fts_populated(self) -> bool:
"""Check if the FTS table has any data"""
cursor = self.connection.cursor()
try:
cursor.execute("SELECT COUNT(*) FROM parts_fts")
count = cursor.fetchone()[0]
return count > 0
except sqlite3.OperationalError:
return False
def populate_fts_from_parts(self):
"""Populate the parts_fts table with existing parts data"""
if not self.check_fts_table_exists():
print("FTS table does not exist, creating tables first...")
self.create_fase4_tables()
# Check if already populated
if self.check_fts_populated():
print("parts_fts table already has data, skipping population")
return
cursor = self.connection.cursor()
# Get count of parts
cursor.execute("SELECT COUNT(*) FROM parts")
parts_count = cursor.fetchone()[0]
if parts_count == 0:
print("No parts found in parts table, nothing to populate")
return
# Populate FTS table from parts
cursor.execute("""
INSERT INTO parts_fts(rowid, oem_part_number, name, name_es, description, description_es)
SELECT id, oem_part_number, name, name_es, description, description_es FROM parts
""")
self.connection.commit()
# Rebuild FTS index for proper search functionality
cursor.execute("INSERT INTO parts_fts(parts_fts) VALUES('rebuild')")
self.connection.commit()
# Verify population
cursor.execute("SELECT COUNT(*) FROM parts_fts")
fts_count = cursor.fetchone()[0]
print(f"Populated parts_fts with {fts_count} entries from {parts_count} parts")
def get_vin_by_vin(self, vin: str) -> Optional[int]:
"""Get VIN cache entry ID by VIN, returns None if not found"""
cursor = self.connection.cursor()
cursor.execute("SELECT id FROM vin_cache WHERE vin = ?", (vin,))
result = cursor.fetchone()
return result[0] if result else None
def insert_vin_cache(self, vin: str, decoded_data: dict, make: str, model: str,
year: int, engine_info: str = None, body_class: str = None,
drive_type: str = None, model_year_engine_id: int = None,
expires_days: int = 30) -> int:
"""Insert a VIN cache entry if it doesn't exist, return its ID"""
existing_id = self.get_vin_by_vin(vin)
if existing_id:
print(f" VIN '{vin}' already exists in cache (ID: {existing_id})")
return existing_id
cursor = self.connection.cursor()
expires_at = datetime.now() + timedelta(days=expires_days)
cursor.execute(
"""INSERT INTO vin_cache
(vin, decoded_data, make, model, year, engine_info, body_class,
drive_type, model_year_engine_id, expires_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
(vin, json.dumps(decoded_data), make, model, year, engine_info,
body_class, drive_type, model_year_engine_id, expires_at.isoformat())
)
self.connection.commit()
vin_id = cursor.lastrowid
print(f" Inserted VIN cache: {vin} -> {make} {model} {year} (ID: {vin_id})")
return vin_id
def populate_sample_vins(self):
"""Populate sample VIN cache entries for testing"""
print("\nPopulating sample VIN cache entries...")
sample_vins = [
{
'vin': '4T1BF1FK5CU123456',
'decoded_data': {
'Make': 'TOYOTA',
'Model': 'Camry',
'ModelYear': '2023',
'EngineModel': '2.5L I4',
'BodyClass': 'Sedan/Saloon',
'DriveType': 'FWD',
'PlantCountry': 'UNITED STATES (USA)',
'VehicleType': 'PASSENGER CAR'
},
'make': 'Toyota',
'model': 'Camry',
'year': 2023,
'engine_info': '2.5L I4 DOHC 16V',
'body_class': 'Sedan',
'drive_type': 'FWD'
},
{
'vin': '1HGBH41JXMN109186',
'decoded_data': {
'Make': 'HONDA',
'Model': 'Civic',
'ModelYear': '2023',
'EngineModel': '2.0L I4',
'BodyClass': 'Sedan/Saloon',
'DriveType': 'FWD',
'PlantCountry': 'UNITED STATES (USA)',
'VehicleType': 'PASSENGER CAR'
},
'make': 'Honda',
'model': 'Civic',
'year': 2023,
'engine_info': '2.0L I4 DOHC 16V',
'body_class': 'Sedan',
'drive_type': 'FWD'
},
{
'vin': '1FA6P8CF5L5123456',
'decoded_data': {
'Make': 'FORD',
'Model': 'Mustang',
'ModelYear': '2020',
'EngineModel': '5.0L V8',
'BodyClass': 'Coupe',
'DriveType': 'RWD',
'PlantCountry': 'UNITED STATES (USA)',
'VehicleType': 'PASSENGER CAR'
},
'make': 'Ford',
'model': 'Mustang',
'year': 2020,
'engine_info': '5.0L V8 Coyote',
'body_class': 'Coupe',
'drive_type': 'RWD'
}
]
for vin_data in sample_vins:
self.insert_vin_cache(
vin=vin_data['vin'],
decoded_data=vin_data['decoded_data'],
make=vin_data['make'],
model=vin_data['model'],
year=vin_data['year'],
engine_info=vin_data['engine_info'],
body_class=vin_data['body_class'],
drive_type=vin_data['drive_type']
)
def verify_installation(self):
"""Verify FASE 4 installation"""
cursor = self.connection.cursor()
print("\n" + "=" * 50)
print("FASE 4 Installation Verification")
print("=" * 50)
# Check FTS table
cursor.execute(
"SELECT name FROM sqlite_master WHERE type='table' AND name='parts_fts'"
)
fts_exists = cursor.fetchone() is not None
print(f"parts_fts table: {'OK' if fts_exists else 'MISSING'}")
if fts_exists:
cursor.execute("SELECT COUNT(*) FROM parts_fts")
fts_count = cursor.fetchone()[0]
print(f" - FTS entries: {fts_count}")
# Check triggers
triggers = ['parts_fts_insert', 'parts_fts_delete', 'parts_fts_update']
for trigger_name in triggers:
cursor.execute(
"SELECT name FROM sqlite_master WHERE type='trigger' AND name=?",
(trigger_name,)
)
trigger_exists = cursor.fetchone() is not None
print(f"Trigger {trigger_name}: {'OK' if trigger_exists else 'MISSING'}")
# Check vin_cache table
cursor.execute(
"SELECT name FROM sqlite_master WHERE type='table' AND name='vin_cache'"
)
vin_exists = cursor.fetchone() is not None
print(f"vin_cache table: {'OK' if vin_exists else 'MISSING'}")
if vin_exists:
cursor.execute("SELECT COUNT(*) FROM vin_cache")
vin_count = cursor.fetchone()[0]
print(f" - VIN cache entries: {vin_count}")
cursor.execute("SELECT vin, make, model, year FROM vin_cache")
for row in cursor.fetchall():
print(f" - {row['vin']}: {row['make']} {row['model']} {row['year']}")
# Check indexes
indexes = ['idx_vin_cache_vin', 'idx_vin_cache_make_model']
for index_name in indexes:
cursor.execute(
"SELECT name FROM sqlite_master WHERE type='index' AND name=?",
(index_name,)
)
index_exists = cursor.fetchone() is not None
print(f"Index {index_name}: {'OK' if index_exists else 'MISSING'}")
print("=" * 50)
def main():
"""Main function to populate FASE 4 tables"""
print("=" * 60)
print("FASE 4: Full-Text Search and VIN Decoder Population")
print("=" * 60)
manager = Fase4Manager()
try:
manager.connect()
# Step 1: Create FASE 4 tables (FTS5, triggers, vin_cache)
print("\n[1/4] Creating FASE 4 tables...")
manager.create_fase4_tables()
# Step 2: Populate FTS table with existing parts
print("\n[2/4] Populating Full-Text Search index...")
manager.populate_fts_from_parts()
# Step 3: Add sample VIN cache entries
print("\n[3/4] Adding sample VIN cache entries...")
manager.populate_sample_vins()
# Step 4: Verify installation
print("\n[4/4] Verifying FASE 4 installation...")
manager.verify_installation()
print("\nFASE 4 population completed successfully!")
except Exception as e:
print(f"\nError during FASE 4 population: {e}")
raise
finally:
manager.disconnect()
if __name__ == '__main__':
main()