dddc6734d0
Splits the single main.py into config/database/models/routes/vision/recognition/ ollama_client/cleanup modules, adds a web UI, and introduces clip-service for CLIP (ViT-L-14) embeddings. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
110 lines
3.7 KiB
Python
110 lines
3.7 KiB
Python
"""SQLite database schema and connection management for frigate-notify v2."""
|
|
|
|
import logging
|
|
import sqlite3
|
|
import threading
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Thread-local storage for database connections
|
|
_local = threading.local()
|
|
|
|
_db_path: str = "/data/frigate-notify.db"
|
|
|
|
|
|
def set_db_path(path: str) -> None:
|
|
"""Set the database path. Closes any existing connection first."""
|
|
global _db_path
|
|
close_connection()
|
|
_db_path = path
|
|
|
|
|
|
def get_connection() -> sqlite3.Connection:
|
|
"""Get a thread-local database connection with WAL mode and foreign keys enabled."""
|
|
if not hasattr(_local, "connection") or _local.connection is None:
|
|
conn = sqlite3.connect(_db_path)
|
|
conn.execute("PRAGMA journal_mode=WAL")
|
|
conn.execute("PRAGMA foreign_keys=ON")
|
|
conn.row_factory = sqlite3.Row
|
|
_local.connection = conn
|
|
return _local.connection
|
|
|
|
|
|
def close_connection() -> None:
|
|
"""Close the thread-local database connection if one exists."""
|
|
if hasattr(_local, "connection") and _local.connection is not None:
|
|
_local.connection.close()
|
|
_local.connection = None
|
|
|
|
|
|
def init_db() -> None:
|
|
"""Create all tables and default settings."""
|
|
conn = get_connection()
|
|
cursor = conn.cursor()
|
|
|
|
cursor.executescript(
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS known_objects (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
name TEXT NOT NULL,
|
|
type TEXT NOT NULL CHECK(type IN ('person', 'vehicle', 'pet')),
|
|
action TEXT NOT NULL DEFAULT 'quiet' CHECK(action IN ('quiet', 'suppress', 'alert')),
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS embeddings (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
known_object_id INTEGER NOT NULL,
|
|
embedding BLOB NOT NULL,
|
|
snapshot_url TEXT,
|
|
thumbnail BLOB,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (known_object_id) REFERENCES known_objects(id) ON DELETE CASCADE
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS events (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
frigate_event_id TEXT UNIQUE NOT NULL,
|
|
camera TEXT NOT NULL,
|
|
label TEXT NOT NULL,
|
|
score REAL NOT NULL,
|
|
snapshot BLOB,
|
|
embedding BLOB,
|
|
match_status TEXT NOT NULL DEFAULT 'unreviewed'
|
|
CHECK(match_status IN ('known', 'unknown', 'ambiguous', 'unreviewed')),
|
|
matched_object_id INTEGER,
|
|
match_confidence REAL,
|
|
ai_description TEXT,
|
|
notified INTEGER NOT NULL DEFAULT 0,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (matched_object_id) REFERENCES known_objects(id) ON DELETE SET NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS settings (
|
|
key TEXT PRIMARY KEY,
|
|
value TEXT NOT NULL
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_events_created_at ON events(created_at);
|
|
CREATE INDEX IF NOT EXISTS idx_events_match_status ON events(match_status);
|
|
CREATE INDEX IF NOT EXISTS idx_events_camera ON events(camera);
|
|
CREATE INDEX IF NOT EXISTS idx_embeddings_known_object_id ON embeddings(known_object_id);
|
|
"""
|
|
)
|
|
|
|
# Insert default settings if they don't exist
|
|
defaults = {
|
|
"vision_backend": "haiku",
|
|
"recognition_enabled": "false",
|
|
"event_retention_days": "90",
|
|
}
|
|
for key, value in defaults.items():
|
|
cursor.execute(
|
|
"INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)",
|
|
(key, value),
|
|
)
|
|
|
|
conn.commit()
|
|
logger.info("Database initialized at %s", _db_path)
|