comment-moderator / backend /database.py
SrinivasaPrasad's picture
Prepare for Render deployment
241ad11
Raw
History Blame Contribute Delete
6.87 kB
import sqlite3
def get_db():
conn = sqlite3.connect("moderation.db")
conn.row_factory = sqlite3.Row # Returns dicts instead of tuples
return conn
def init_db():
conn = get_db()
conn.execute("""
CREATE TABLE IF NOT EXISTS messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT,
message TEXT,
toxicity_score REAL,
primary_label TEXT,
flagged INTEGER,
reviewed INTEGER DEFAULT 0,
timestamp REAL
)
""")
conn.commit()
def save_message(msg_obj):
conn = get_db()
conn.execute("""
INSERT INTO messages (username, message, toxicity_score, primary_label, flagged, timestamp)
VALUES (?, ?, ?, ?, ?, ?)
""", (
msg_obj["username"],
msg_obj["message"],
msg_obj["result"]["toxicity_score"],
msg_obj["result"]["primary_label"],
1 if msg_obj["result"]["flagged"] else 0,
msg_obj["timestamp"]
))
conn.commit()
def get_flagged_messages():
conn = get_db()
return conn.execute(
"SELECT * FROM messages WHERE flagged=1 ORDER BY timestamp DESC"
).fetchall()
def mark_reviewed(message_id, action):
"""
Mark a message as reviewed.
action='approve': The comment is actually safe, unflag it.
action='reject': The comment is truly toxic, keep it flagged (and marked as reviewed).
"""
conn = get_db()
if action == "approve":
conn.execute("UPDATE messages SET reviewed=1, flagged=0 WHERE id=?", (message_id,))
elif action == "reject":
conn.execute("UPDATE messages SET reviewed=1, flagged=1 WHERE id=?", (message_id,))
conn.commit()
def get_stats():
"""
Return aggregate statistics for the analytics dashboard:
total messages, flagged count, average toxicity score,
and the flagged percentage.
"""
conn = get_db()
row = conn.execute("""
SELECT
COUNT(*) as total,
COALESCE(SUM(CASE WHEN flagged = 1 THEN 1 ELSE 0 END), 0) as flagged_count,
COALESCE(AVG(toxicity_score), 0) as avg_toxicity
FROM messages
""").fetchone()
total = row["total"]
flagged_count = row["flagged_count"]
avg_toxicity = row["avg_toxicity"]
flagged_pct = (flagged_count / total * 100) if total > 0 else 0
return {
"total": total,
"flagged_count": flagged_count,
"avg_toxicity": round(avg_toxicity, 4),
"flagged_pct": round(flagged_pct, 1)
}
def get_recent_scores(limit=50):
"""
Return the last `limit` messages with their toxicity scores and timestamps,
ordered from oldest to newest (for the line chart).
"""
conn = get_db()
rows = conn.execute("""
SELECT id, toxicity_score, primary_label, flagged, timestamp
FROM messages
ORDER BY id DESC
LIMIT ?
""", (limit,)).fetchall()
# Reverse so oldest is first (for chronological chart)
return [dict(r) for r in reversed(rows)]
def get_category_counts():
"""
Return counts of flagged messages grouped by primary_label.
Only includes labels from flagged messages (not 'clean').
"""
conn = get_db()
rows = conn.execute("""
SELECT primary_label, COUNT(*) as count
FROM messages
WHERE flagged = 1 AND primary_label != 'clean'
GROUP BY primary_label
ORDER BY count DESC
""").fetchall()
return {row["primary_label"]: row["count"] for row in rows}
def get_approved_comments():
"""
Return approved (non-flagged) messages for the public chat feed,
ordered newest first. Maps DB column names to the keys the frontend expects.
"""
conn = get_db()
rows = conn.execute("""
SELECT id, username, message, toxicity_score, primary_label, timestamp
FROM messages
WHERE flagged = 0
ORDER BY timestamp DESC
""").fetchall()
return [{
"id": r["id"],
"author": r["username"],
"content": r["message"],
"toxicity_score": r["toxicity_score"],
"primary_label": r["primary_label"],
"timestamp": r["timestamp"] * 1000 # JS expects milliseconds
} for r in rows]
def get_all_messages():
"""
Return all messages with a computed 'status' field for the moderator queue.
Status logic:
- reviewed=1, flagged=0 → 'approved'
- reviewed=1, flagged=1 → 'rejected'
- reviewed=0, flagged=1 → 'flagged'
- reviewed=0, flagged=0 → 'approved' (auto-approved by ML)
"""
conn = get_db()
rows = conn.execute("""
SELECT id, username, message, toxicity_score, primary_label, flagged, reviewed, timestamp
FROM messages
ORDER BY timestamp DESC
""").fetchall()
result = []
for r in rows:
if r["reviewed"] == 1 and r["flagged"] == 0:
status = "approved"
elif r["reviewed"] == 1 and r["flagged"] == 1:
status = "rejected"
elif r["flagged"] == 1:
status = "flagged"
else:
status = "approved"
result.append({
"id": r["id"],
"author": r["username"],
"content": r["message"],
"toxicity_score": r["toxicity_score"],
"flagged_reason": r["primary_label"] if r["primary_label"] != "clean" else None,
"status": status,
"timestamp": r["timestamp"] * 1000 # JS expects milliseconds
})
return result
def get_dashboard_stats():
"""
Return aggregate stats matching what dashboard.js expects:
total, approved, flagged (pending), rejected, avg_toxicity.
"""
conn = get_db()
row = conn.execute("""
SELECT
COUNT(*) as total,
COALESCE(SUM(CASE WHEN flagged = 0 THEN 1 ELSE 0 END), 0) as approved,
COALESCE(SUM(CASE WHEN flagged = 1 AND reviewed = 0 THEN 1 ELSE 0 END), 0) as flagged,
COALESCE(SUM(CASE WHEN flagged = 1 AND reviewed = 1 THEN 1 ELSE 0 END), 0) as rejected,
COALESCE(AVG(toxicity_score), 0) as avg_toxicity
FROM messages
""").fetchone()
return {
"total": row["total"],
"approved": row["approved"],
"flagged": row["flagged"],
"rejected": row["rejected"],
"avg_toxicity": row["avg_toxicity"]
}
def moderate_message(message_id, new_status):
"""
Update a message's moderation status.
'approved' → unflag and mark reviewed.
'rejected' → keep flagged and mark reviewed.
"""
conn = get_db()
if new_status == "approved":
conn.execute("UPDATE messages SET reviewed=1, flagged=0 WHERE id=?", (message_id,))
elif new_status == "rejected":
conn.execute("UPDATE messages SET reviewed=1, flagged=1 WHERE id=?", (message_id,))
conn.commit()