""" VIMS Core — Database Manager Hanterar databas per instans/ämne. """ import sqlite3 import json from pathlib import Path from typing import List, Dict, Optional from datetime import datetime class VIMSDatabase: """ Databas-hanterare för VIMS-instanser. Usage: db = VIMSDatabase("atm-monitoring") db.create_schema() db.add_detection(atm_id="ATM-001", anomaly_type="skimming", confidence=0.94) """ def __init__(self, topic: str, db_dir: str = "data"): self.topic = topic self.table_prefix = topic.replace("-", "_") self.db_path = Path(db_dir) / f"{topic}.db" self.db_path.parent.mkdir(parents=True, exist_ok=True) def _connect(self): """Skapa databas-anslutning.""" conn = sqlite3.connect(str(self.db_path)) conn.row_factory = sqlite3.Row return conn def create_schema(self, anomaly_classes: List[str]): """ Skapa databas-schema för ämne. Args: anomaly_classes: Lista med anomaliklasser """ conn = self._connect() cursor = conn.cursor() # Huvudtabell för detektioner cursor.execute(f""" CREATE TABLE IF NOT EXISTS {self.table_prefix}_detections ( id INTEGER PRIMARY KEY AUTOINCREMENT, asset_id TEXT NOT NULL, image_path TEXT, anomaly_type TEXT NOT NULL, confidence REAL, severity INTEGER, bbox TEXT, detected_at DATETIME DEFAULT CURRENT_TIMESTAMP, verified BOOLEAN DEFAULT FALSE, status TEXT DEFAULT 'open', metadata TEXT ) """) # Assets-tabell cursor.execute(f""" CREATE TABLE IF NOT EXISTS {self.table_prefix}_assets ( id TEXT PRIMARY KEY, name TEXT, location TEXT, status TEXT DEFAULT 'active', last_check DATETIME, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) """) # Alerts-tabell cursor.execute(f""" CREATE TABLE IF NOT EXISTS {self.table_prefix}_alerts ( id INTEGER PRIMARY KEY AUTOINCREMENT, detection_id INTEGER, severity INTEGER, message TEXT, sent_at DATETIME, status TEXT DEFAULT 'pending', FOREIGN KEY (detection_id) REFERENCES {self.table_prefix}_detections(id) ) """) # Index cursor.execute(f""" CREATE INDEX IF NOT EXISTS idx_{self.table_prefix}_detections_asset ON {self.table_prefix}_detections(asset_id) """) cursor.execute(f""" CREATE INDEX IF NOT EXISTS idx_{self.table_prefix}_detections_type ON {self.table_prefix}_detections(anomaly_type) """) cursor.execute(f""" CREATE INDEX IF NOT EXISTS idx_{self.table_prefix}_detections_status ON {self.table_prefix}_detections(status) """) conn.commit() conn.close() print(f"Schema created for {self.topic}") def add_detection( self, asset_id: str, anomaly_type: str, confidence: float, severity: int, image_path: Optional[str] = None, bbox: Optional[List[float]] = None, metadata: Optional[Dict] = None ) -> int: """ Lägg till ny detektion. Returns: ID för insatt rad """ conn = self._connect() cursor = conn.cursor() cursor.execute(f""" INSERT INTO {self.table_prefix}_detections (asset_id, image_path, anomaly_type, confidence, severity, bbox, metadata) VALUES (?, ?, ?, ?, ?, ?, ?) """, ( asset_id, image_path, anomaly_type, confidence, severity, json.dumps(bbox) if bbox else None, json.dumps(metadata) if metadata else None )) detection_id = cursor.lastrowid # Skapa alert om severity >= 4 if severity >= 4: cursor.execute(f""" INSERT INTO {self.table_prefix}_alerts (detection_id, severity, message) VALUES (?, ?, ?) """, ( detection_id, severity, f"Critical {anomaly_type} detected on {asset_id}" )) conn.commit() conn.close() return detection_id def get_detections( self, asset_id: Optional[str] = None, status: Optional[str] = None, limit: int = 100 ) -> List[Dict]: """Hämta detektioner med filter.""" conn = self._connect() cursor = conn.cursor() query = f"SELECT * FROM {self.table_prefix}_detections WHERE 1=1" params = [] if asset_id: query += " AND asset_id = ?" params.append(asset_id) if status: query += " AND status = ?" params.append(status) query += " ORDER BY detected_at DESC LIMIT ?" params.append(limit) cursor.execute(query, params) rows = cursor.fetchall() conn.close() return [dict(row) for row in rows] def get_stats(self) -> Dict: """Hämta statistik för ämne.""" conn = self._connect() cursor = conn.cursor() cursor.execute(f""" SELECT COUNT(*) as total_detections, COUNT(DISTINCT asset_id) as assets_monitored, COUNT(CASE WHEN status = 'open' THEN 1 END) as open_anomalies, MAX(severity) as max_severity FROM {self.table_prefix}_detections """) row = cursor.fetchone() conn.close() return dict(row) if row else {} def verify_detection(self, detection_id: int, verdict: str, notes: str = ""): """Verifiera detektion manuellt.""" conn = self._connect() cursor = conn.cursor() cursor.execute(f""" UPDATE {self.table_prefix}_detections SET verified = TRUE, status = ? WHERE id = ? """, (verdict, detection_id)) conn.commit() conn.close()