#!/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()