|
|
import os
|
|
|
import sqlite3
|
|
|
from contextlib import contextmanager
|
|
|
from typing import Optional, Dict, Any, List, Tuple
|
|
|
from datetime import datetime
|
|
|
|
|
|
DEFAULT_DB_PATH = None
|
|
|
|
|
|
def set_db_path(db_path: str):
|
|
|
global DEFAULT_DB_PATH
|
|
|
DEFAULT_DB_PATH = db_path
|
|
|
os.makedirs(os.path.dirname(db_path), exist_ok=True)
|
|
|
|
|
|
def get_connection():
|
|
|
if not DEFAULT_DB_PATH:
|
|
|
raise ValueError("Database path not set. Call set_db_path(path) first.")
|
|
|
return sqlite3.connect(DEFAULT_DB_PATH)
|
|
|
|
|
|
@contextmanager
|
|
|
def get_conn(db_path: Optional[str] = None):
|
|
|
path = db_path or DEFAULT_DB_PATH
|
|
|
conn = sqlite3.connect(path, check_same_thread=False)
|
|
|
conn.row_factory = sqlite3.Row
|
|
|
try:
|
|
|
yield conn
|
|
|
conn.commit()
|
|
|
finally:
|
|
|
conn.close()
|
|
|
|
|
|
def init_schema():
|
|
|
with get_conn() as conn:
|
|
|
c = conn.cursor()
|
|
|
|
|
|
c.execute("""
|
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
username TEXT UNIQUE NOT NULL,
|
|
|
password_hash TEXT,
|
|
|
role TEXT NOT NULL,
|
|
|
created_at TEXT NOT NULL
|
|
|
);
|
|
|
""")
|
|
|
|
|
|
try:
|
|
|
c.execute("PRAGMA table_info(users)")
|
|
|
cols = {row[1] for row in c.fetchall()}
|
|
|
if "password_hash" not in cols:
|
|
|
c.execute("ALTER TABLE users ADD COLUMN password_hash TEXT")
|
|
|
if "role" not in cols:
|
|
|
c.execute("ALTER TABLE users ADD COLUMN role TEXT NOT NULL DEFAULT 'verd'")
|
|
|
if "created_at" not in cols:
|
|
|
c.execute("ALTER TABLE users ADD COLUMN created_at TEXT NOT NULL DEFAULT ''")
|
|
|
except sqlite3.OperationalError:
|
|
|
pass
|
|
|
|
|
|
try:
|
|
|
c.execute("ALTER TABLE users DROP COLUMN pw_hash;")
|
|
|
except sqlite3.OperationalError:
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
c.execute("""
|
|
|
CREATE TABLE IF NOT EXISTS feedback_ad (
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
video_name TEXT NOT NULL, -- nombre de carpeta dentro de videos/completed
|
|
|
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
|
transcripcio INTEGER NOT NULL, -- 1..10
|
|
|
identificacio INTEGER NOT NULL, -- 1..10
|
|
|
localitzacions INTEGER NOT NULL, -- 1..10
|
|
|
activitats INTEGER NOT NULL, -- 1..10
|
|
|
narracions INTEGER NOT NULL, -- 1..10
|
|
|
expressivitat INTEGER NOT NULL, -- 1..10
|
|
|
comments TEXT,
|
|
|
created_at TEXT NOT NULL
|
|
|
);
|
|
|
""")
|
|
|
|
|
|
try:
|
|
|
c.execute("ALTER TABLE feedback_ad ADD COLUMN expressivitat INTEGER NOT NULL DEFAULT 7;")
|
|
|
except sqlite3.OperationalError:
|
|
|
pass
|
|
|
|
|
|
def add_feedback_ad(video_name: str, user_id: int,
|
|
|
transcripcio: int, identificacio: int, localitzacions: int,
|
|
|
activitats: int, narracions: int, expressivitat: int, comments: str | None):
|
|
|
with get_conn() as conn:
|
|
|
conn.execute(
|
|
|
"""INSERT INTO feedback_ad
|
|
|
(video_name, user_id, transcripcio, identificacio, localitzacions, activitats, narracions, expressivitat, comments, created_at)
|
|
|
VALUES (?,?,?,?,?,?,?,?,?,?)""",
|
|
|
(video_name, user_id, transcripcio, identificacio, localitzacions, activitats, narracions, expressivitat, comments, now_str())
|
|
|
)
|
|
|
|
|
|
def get_feedback_ad_for_video(video_name: str):
|
|
|
with get_conn() as conn:
|
|
|
cur = conn.execute(
|
|
|
"""SELECT * FROM feedback_ad WHERE video_name=? ORDER BY created_at DESC""",
|
|
|
(video_name,)
|
|
|
)
|
|
|
return cur.fetchall()
|
|
|
|
|
|
def get_feedback_ad_stats():
|
|
|
|
|
|
with get_conn() as conn:
|
|
|
cur = conn.execute("""
|
|
|
SELECT
|
|
|
video_name,
|
|
|
COUNT(*) AS n,
|
|
|
AVG(transcripcio) AS avg_transcripcio,
|
|
|
AVG(identificacio) AS avg_identificacio,
|
|
|
AVG(localitzacions) AS avg_localitzacions,
|
|
|
AVG(activitats) AS avg_activitats,
|
|
|
AVG(narracions) AS avg_narracions,
|
|
|
AVG(expressivitat) AS avg_expressivitat,
|
|
|
(AVG(transcripcio)+AVG(identificacio)+AVG(localitzacions)+AVG(activitats)+AVG(narracions)+AVG(expressivitat))/6.0 AS avg_global
|
|
|
FROM feedback_ad
|
|
|
GROUP BY video_name
|
|
|
ORDER BY avg_global DESC, n DESC;
|
|
|
""")
|
|
|
return cur.fetchall()
|
|
|
|
|
|
def now_str():
|
|
|
return datetime.utcnow().isoformat(timespec="seconds") + "Z"
|
|
|
|
|
|
|
|
|
def create_user(username: str, password_hash: str, role: str):
|
|
|
with get_conn() as conn:
|
|
|
conn.execute(
|
|
|
"INSERT INTO users(username, password_hash, role, created_at) VALUES (?,?,?,?)",
|
|
|
(username, password_hash, role, now_str()),
|
|
|
)
|
|
|
|
|
|
def get_user(username: str):
|
|
|
with get_conn() as conn:
|
|
|
cur = conn.execute("SELECT * FROM users WHERE username=?", (username,))
|
|
|
return cur.fetchone()
|
|
|
|
|
|
def get_all_users() -> List[Dict[str, Any]]:
|
|
|
with get_conn() as conn:
|
|
|
cur = conn.execute("SELECT id, username, role FROM users ORDER BY username")
|
|
|
return cur.fetchall()
|
|
|
|
|
|
def update_user_password(username: str, password_hash: str):
|
|
|
with get_conn() as conn:
|
|
|
conn.execute(
|
|
|
"UPDATE users SET password_hash = ? WHERE username = ?",
|
|
|
(password_hash, username)
|
|
|
)
|
|
|
|
|
|
|
|
|
def create_video(filename: str, original_name: str, uploader: str, status: str) -> int:
|
|
|
with get_conn() as conn:
|
|
|
cur = conn.execute(
|
|
|
"INSERT INTO videos(filename, original_name, uploader, status, created_at) VALUES (?,?,?,?,?)",
|
|
|
(filename, original_name, uploader, status, now_str()),
|
|
|
)
|
|
|
return cur.lastrowid
|
|
|
|
|
|
def update_video_status(video_id: int, status: str):
|
|
|
with get_conn() as conn:
|
|
|
conn.execute("UPDATE videos SET status=? WHERE id=?", (status, video_id))
|
|
|
|
|
|
def list_videos(limit: int = 200):
|
|
|
with get_conn() as conn:
|
|
|
cur = conn.execute(
|
|
|
"SELECT * FROM videos ORDER BY id DESC LIMIT ?", (limit,)
|
|
|
)
|
|
|
return cur.fetchall()
|
|
|
|
|
|
def get_video(video_id: int):
|
|
|
with get_conn() as conn:
|
|
|
cur = conn.execute("SELECT * FROM videos WHERE id=?", (video_id,))
|
|
|
return cur.fetchone()
|
|
|
|
|
|
|
|
|
def upsert_result(video_id: int, mode: str, text_path: str = None, srt_path: str = None,
|
|
|
mp3_path: str = None, characters_json: str = None, metrics_json: str = None):
|
|
|
with get_conn() as conn:
|
|
|
cur = conn.execute(
|
|
|
"SELECT id FROM results WHERE video_id=? AND mode=?", (video_id, mode)
|
|
|
)
|
|
|
row = cur.fetchone()
|
|
|
if row:
|
|
|
conn.execute(
|
|
|
"UPDATE results SET text_path=?, srt_path=?, mp3_path=?, characters_json=?, metrics_json=? WHERE id=?",
|
|
|
(text_path, srt_path, mp3_path, characters_json, metrics_json, row["id"])
|
|
|
)
|
|
|
else:
|
|
|
conn.execute(
|
|
|
"INSERT INTO results(video_id, mode, text_path, srt_path, mp3_path, characters_json, metrics_json, created_at) VALUES (?,?,?,?,?,?,?,?)",
|
|
|
(video_id, mode, text_path, srt_path, mp3_path, characters_json, metrics_json, now_str())
|
|
|
)
|
|
|
|
|
|
def get_results(video_id: int):
|
|
|
with get_conn() as conn:
|
|
|
cur = conn.execute("SELECT * FROM results WHERE video_id=?", (video_id,))
|
|
|
return cur.fetchall()
|
|
|
|
|
|
|
|
|
def add_feedback(video_id: int, user_id: int, clarity: int, sync: int, relevance: int,
|
|
|
naturalness: int, overall: int, comments: str):
|
|
|
with get_conn() as conn:
|
|
|
conn.execute(
|
|
|
"INSERT INTO feedback(video_id, user_id, clarity, sync, relevance, naturalness, overall, comments, created_at) VALUES (?,?,?,?,?,?,?,?,?)",
|
|
|
(video_id, user_id, clarity, sync, relevance, naturalness, overall, comments, now_str())
|
|
|
)
|
|
|
|
|
|
def get_feedback_for_video(video_id: int):
|
|
|
with get_conn() as conn:
|
|
|
cur = conn.execute("SELECT * FROM feedback WHERE video_id=?", (video_id,))
|
|
|
return cur.fetchall()
|
|
|
|
|
|
def get_feedback_stats():
|
|
|
with get_conn() as conn:
|
|
|
cur = conn.execute("""
|
|
|
SELECT
|
|
|
v.id as video_id,
|
|
|
v.original_name as name,
|
|
|
COUNT(f.id) as n,
|
|
|
AVG(f.overall) as overall_avg,
|
|
|
AVG(f.clarity) as clarity_avg,
|
|
|
AVG(f.sync) as sync_avg,
|
|
|
AVG(f.relevance) as relevance_avg,
|
|
|
AVG(f.naturalness) as naturalness_avg
|
|
|
FROM videos v
|
|
|
LEFT JOIN feedback f ON f.video_id = v.id
|
|
|
GROUP BY v.id
|
|
|
ORDER BY overall_avg DESC NULLS LAST, n DESC, v.id DESC
|
|
|
""")
|
|
|
return cur.fetchall()
|
|
|
|
|
|
|
|
|
def get_feedback_with_users():
|
|
|
with get_conn() as conn:
|
|
|
cur = conn.execute("""
|
|
|
SELECT f.*, u.role as user_role
|
|
|
FROM feedback f
|
|
|
JOIN users u ON u.id = f.user_id
|
|
|
""")
|
|
|
return cur.fetchall()
|
|
|
|
|
|
def get_feedback_stats_weighted(role_weights: Dict[str, float] = None):
|
|
|
role_weights = role_weights or {'verd':1.0,'groc':1.0,'taronja':0.0,'blau':1.5}
|
|
|
|
|
|
with get_conn() as conn:
|
|
|
|
|
|
cur = conn.execute("""
|
|
|
SELECT v.id as video_id, v.original_name as name FROM videos v
|
|
|
""")
|
|
|
videos = cur.fetchall()
|
|
|
|
|
|
|
|
|
cur = conn.execute("""
|
|
|
SELECT f.video_id, f.clarity, f.sync, f.relevance, f.naturalness, f.overall, u.role as user_role
|
|
|
FROM feedback f
|
|
|
JOIN users u ON u.id = f.user_id
|
|
|
""")
|
|
|
fb = cur.fetchall()
|
|
|
|
|
|
by_vid = {v["video_id"]: {"name": v["name"], "n":0, "w_sum":0.0,
|
|
|
"overall":0.0,"clarity":0.0,"sync":0.0,"relevance":0.0,"naturalness":0.0}
|
|
|
for v in videos}
|
|
|
for r in fb:
|
|
|
w = role_weights.get(r["user_role"], 0.0)
|
|
|
if r["video_id"] not in by_vid:
|
|
|
continue
|
|
|
by_vid[r["video_id"]]["n"] += 1
|
|
|
by_vid[r["video_id"]]["w_sum"] += w
|
|
|
by_vid[r["video_id"]]["overall"] += w * r["overall"]
|
|
|
by_vid[r["video_id"]]["clarity"] += w * r["clarity"]
|
|
|
by_vid[r["video_id"]]["sync"] += w * r["sync"]
|
|
|
by_vid[r["video_id"]]["relevance"] += w * r["relevance"]
|
|
|
by_vid[r["video_id"]]["naturalness"] += w * r["naturalness"]
|
|
|
|
|
|
|
|
|
rows = []
|
|
|
for vid, agg in by_vid.items():
|
|
|
w = agg["w_sum"] if agg["w_sum"]>0 else None
|
|
|
def avg(val):
|
|
|
return (val / w) if w else None
|
|
|
rows.append({
|
|
|
"video_id": vid,
|
|
|
"name": agg["name"],
|
|
|
"n_ratings": agg["n"],
|
|
|
"w_count": agg["w_sum"],
|
|
|
"overall_avg_w": avg(agg["overall"]),
|
|
|
"clarity_avg_w": avg(agg["clarity"]),
|
|
|
"sync_avg_w": avg(agg["sync"]),
|
|
|
"relevance_avg_w": avg(agg["relevance"]),
|
|
|
"naturalness_avg_w": avg(agg["naturalness"]),
|
|
|
})
|
|
|
|
|
|
rows.sort(key=lambda r: (r["overall_avg_w"] is None, -(r["overall_avg_w"] or 0), -r["n_ratings"], -r["w_count"]))
|
|
|
return rows
|
|
|
|