221 lines
6.5 KiB
Python
221 lines
6.5 KiB
Python
|
|
"""
|
||
|
|
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()
|