Apiarist / db.py
Apiarist Dev
feat: inspection dashboard (verdict + stat tiles), colony trend charts, polished A/B kill-shot cards
be206e5
Raw
History Blame Contribute Delete
5.27 kB
"""
SQLite layer for hive registry + inspection history.
Storage is ephemeral on HF Spaces' free tier (container restart wipes
the filesystem), that's fine for a hackathon demo. For real-world
deployment we'd back this with HF Hub persistence or a real DB.
"""
from __future__ import annotations
import json
import sqlite3
import time
from contextlib import contextmanager
from pathlib import Path
from typing import Iterator
DB_PATH = Path(__file__).parent / "apiarist.sqlite"
SCHEMA = """
CREATE TABLE IF NOT EXISTS hives (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
location TEXT,
queen_marker TEXT,
notes TEXT,
created_at REAL NOT NULL
);
CREATE TABLE IF NOT EXISTS inspections (
id INTEGER PRIMARY KEY AUTOINCREMENT,
hive_id INTEGER NOT NULL,
queen_detected INTEGER,
varroa_mites_visible INTEGER,
swarm_cells_detected INTEGER,
brood_pattern TEXT,
frame_health TEXT,
notes TEXT,
raw_response TEXT,
structured_json TEXT,
created_at REAL NOT NULL,
FOREIGN KEY (hive_id) REFERENCES hives(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_inspections_hive ON inspections(hive_id, created_at);
"""
@contextmanager
def conn() -> Iterator[sqlite3.Connection]:
c = sqlite3.connect(DB_PATH)
c.row_factory = sqlite3.Row
c.execute("PRAGMA foreign_keys = ON")
try:
yield c
c.commit()
finally:
c.close()
def init_db() -> None:
with conn() as c:
c.executescript(SCHEMA)
def add_hive(name: str, location: str = "", queen_marker: str = "", notes: str = "") -> int:
with conn() as c:
cur = c.execute(
"INSERT INTO hives (name, location, queen_marker, notes, created_at) "
"VALUES (?, ?, ?, ?, ?)",
(name, location, queen_marker, notes, time.time()),
)
return cur.lastrowid or 0
def get_or_create_hive(name: str) -> int:
"""Look up a hive by name; create with defaults if missing."""
name = name.strip()
if not name:
name = "Unnamed Hive"
with conn() as c:
row = c.execute("SELECT id FROM hives WHERE name = ?", (name,)).fetchone()
if row:
return row["id"]
cur = c.execute(
"INSERT INTO hives (name, created_at) VALUES (?, ?)",
(name, time.time()),
)
return cur.lastrowid or 0
def list_hives() -> list[dict]:
with conn() as c:
rows = c.execute(
"""
SELECT h.id, h.name, h.location, h.queen_marker, h.notes, h.created_at,
COUNT(i.id) AS inspection_count,
MAX(i.created_at) AS last_inspected
FROM hives h
LEFT JOIN inspections i ON i.hive_id = h.id
GROUP BY h.id
ORDER BY h.name
"""
).fetchall()
return [dict(r) for r in rows]
def delete_hive(hive_id: int) -> None:
with conn() as c:
c.execute("DELETE FROM hives WHERE id = ?", (hive_id,))
def add_inspection(hive_id: int, results: dict, raw_response: str = "") -> int:
with conn() as c:
cur = c.execute(
"""
INSERT INTO inspections (
hive_id, queen_detected, varroa_mites_visible,
swarm_cells_detected, brood_pattern, frame_health,
notes, raw_response, structured_json, created_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
hive_id,
int(bool(results.get("queen_detected"))),
int(results.get("varroa_mites_visible", 0) or 0),
int(bool(results.get("swarm_cells_detected"))),
results.get("brood_pattern", ""),
results.get("frame_health", ""),
results.get("notes", ""),
raw_response,
json.dumps(results),
time.time(),
),
)
return cur.lastrowid or 0
def get_inspections_for_hive(hive_id: int, limit: int = 50) -> list[dict]:
with conn() as c:
rows = c.execute(
"""
SELECT id, queen_detected, varroa_mites_visible, swarm_cells_detected,
brood_pattern, frame_health, notes, created_at, structured_json
FROM inspections
WHERE hive_id = ?
ORDER BY created_at DESC
LIMIT ?
""",
(hive_id, limit),
).fetchall()
return [dict(r) for r in rows]
def hive_stats() -> dict:
"""High-level apiary summary for the dashboard."""
with conn() as c:
total_hives = c.execute("SELECT COUNT(*) AS n FROM hives").fetchone()["n"]
total_inspections = c.execute(
"SELECT COUNT(*) AS n FROM inspections"
).fetchone()["n"]
recent = c.execute(
"""
SELECT h.name, i.frame_health, i.varroa_mites_visible, i.created_at
FROM inspections i
JOIN hives h ON h.id = i.hive_id
ORDER BY i.created_at DESC
LIMIT 5
"""
).fetchall()
return {
"total_hives": total_hives,
"total_inspections": total_inspections,
"recent": [dict(r) for r in recent],
}