""" Usage logger for tracking SLM and automata usage Logs all requests to SQLite for pattern analysis and plasticity """ import logging import sqlite3 from datetime import datetime from typing import Dict, Any, Optional from pathlib import Path import json from app.config import settings logger = logging.getLogger(__name__) def get_logger(name: str) -> logging.Logger: """ Get a logger instance for the given module name. Args: name: Module name (usually __name__) Returns: Logger instance """ return logging.getLogger(name) class UsageLogger: """Logs all code processing requests for analysis""" def __init__(self, db_path: Optional[Path] = None): """ Initialize usage logger Args: db_path: Path to SQLite database """ self.db_path = db_path or settings.db_path self._ensure_db() def _ensure_db(self): """Create database and tables if they don't exist""" try: self.db_path.parent.mkdir(parents=True, exist_ok=True) conn = sqlite3.connect(self.db_path) cursor = conn.cursor() # Main usage log table cursor.execute(""" CREATE TABLE IF NOT EXISTS usage_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, task TEXT NOT NULL, language TEXT NOT NULL, code_hash TEXT, used_automata BOOLEAN, used_slm BOOLEAN, automata_components TEXT, slm_component TEXT, success BOOLEAN, duration_ms REAL, error TEXT ) """) # Pattern frequency table cursor.execute(""" CREATE TABLE IF NOT EXISTS pattern_frequency ( id INTEGER PRIMARY KEY AUTOINCREMENT, pattern_type TEXT NOT NULL, pattern_signature TEXT NOT NULL, frequency INTEGER DEFAULT 1, last_seen DATETIME DEFAULT CURRENT_TIMESTAMP, automatable BOOLEAN DEFAULT 0, UNIQUE(pattern_type, pattern_signature) ) """) # Automata conversion candidates cursor.execute(""" CREATE TABLE IF NOT EXISTS automata_candidates ( id INTEGER PRIMARY KEY AUTOINCREMENT, pattern_type TEXT NOT NULL, pattern_signature TEXT NOT NULL, frequency INTEGER, estimated_speedup REAL, priority_score REAL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, converted BOOLEAN DEFAULT 0 ) """) conn.commit() conn.close() logger.info(f"Database initialized at {self.db_path}") except Exception as e: logger.error(f"Failed to initialize database: {e}") raise def log_request( self, task: str, language: str, code_hash: str, used_automata: bool, used_slm: bool, pipeline: list, success: bool, duration_ms: float, error: Optional[str] = None ): """Log a code processing request""" try: conn = sqlite3.connect(self.db_path) cursor = conn.cursor() # Extract components from pipeline automata_components = [ step['component'] for step in pipeline if step['step_type'] == 'automata' ] slm_component = next( (step['component'] for step in pipeline if step['step_type'] == 'slm'), None ) cursor.execute(""" INSERT INTO usage_log ( task, language, code_hash, used_automata, used_slm, automata_components, slm_component, success, duration_ms, error ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( task, language, code_hash, used_automata, used_slm, json.dumps(automata_components) if automata_components else None, slm_component, success, duration_ms, error )) conn.commit() conn.close() logger.debug(f"Logged request: {task} ({language}) - automata:{used_automata} slm:{used_slm}") except Exception as e: logger.error(f"Failed to log request: {e}") def update_pattern_frequency( self, pattern_type: str, pattern_signature: str ): """Update frequency counter for a pattern""" try: conn = sqlite3.connect(self.db_path) cursor = conn.cursor() cursor.execute(""" INSERT INTO pattern_frequency (pattern_type, pattern_signature, frequency) VALUES (?, ?, 1) ON CONFLICT(pattern_type, pattern_signature) DO UPDATE SET frequency = frequency + 1, last_seen = CURRENT_TIMESTAMP """, (pattern_type, pattern_signature)) conn.commit() conn.close() except Exception as e: logger.error(f"Failed to update pattern frequency: {e}") def get_frequent_patterns(self, min_frequency: int = 10) -> list: """Get patterns that occur frequently""" try: conn = sqlite3.connect(self.db_path) conn.row_factory = sqlite3.Row cursor = conn.cursor() cursor.execute(""" SELECT * FROM pattern_frequency WHERE frequency >= ? AND automatable = 0 ORDER BY frequency DESC """, (min_frequency,)) patterns = [dict(row) for row in cursor.fetchall()] conn.close() return patterns except Exception as e: logger.error(f"Failed to get frequent patterns: {e}") return [] def get_stats(self) -> Dict[str, Any]: """Get usage statistics""" try: conn = sqlite3.connect(self.db_path) conn.row_factory = sqlite3.Row cursor = conn.cursor() # Total requests cursor.execute("SELECT COUNT(*) as total FROM usage_log") total = cursor.fetchone()['total'] # Automata vs SLM usage cursor.execute(""" SELECT SUM(CASE WHEN used_automata AND NOT used_slm THEN 1 ELSE 0 END) as automata_only, SUM(CASE WHEN used_slm AND NOT used_automata THEN 1 ELSE 0 END) as slm_only, SUM(CASE WHEN used_automata AND used_slm THEN 1 ELSE 0 END) as both FROM usage_log """) usage = dict(cursor.fetchone()) # Average duration cursor.execute(""" SELECT AVG(CASE WHEN used_automata AND NOT used_slm THEN duration_ms END) as avg_automata_ms, AVG(CASE WHEN used_slm THEN duration_ms END) as avg_slm_ms FROM usage_log WHERE success = 1 """) durations = dict(cursor.fetchone()) # Automation rate automation_rate = (usage['automata_only'] / total * 100) if total > 0 else 0 conn.close() return { "total_requests": total, "automation_rate": automation_rate, "automata_only": usage['automata_only'], "slm_only": usage['slm_only'], "both": usage['both'], "avg_automata_duration_ms": durations['avg_automata_ms'] or 0, "avg_slm_duration_ms": durations['avg_slm_ms'] or 0 } except Exception as e: logger.error(f"Failed to get stats: {e}") return {}