Complete toxicity analysis implementation with manual review

- Fixed review submission bug (item_id now uses internal database ID)
- Added comprehensive logging to review API endpoint
- Updated analysis report for Jan 1 - Mar 30, 2026 period
- Report includes all 44 manually reviewed posts
- 4 confirmed toxic, 40 false positives (90.9% FP rate)
- Improved table layout: reduced column widths, smaller text
- Fixed horizontal scrolling with max-width override
- All flagged posts now successfully reviewed and stored

Key findings:
- 7,506 posts collected, 3,938 analyzed
- Only 0.10% confirmed toxic (4 of 3,938)
- High false positive rate shows challenge of automated detection
- Most FPs were legitimate political discourse about extremism

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Pieter 2026-03-31 17:50:23 +02:00
parent 9919d2fc04
commit 2faf6c660b
3 changed files with 35 additions and 19 deletions

View file

@ -232,7 +232,7 @@ def get_flagged_content(
items.append({
"id": r[0],
"status_id": r[1],
"item_id": r[1], # Template compatibility
"item_id": r[0], # Internal database ID for toxicity_scores FK
"content": r[2],
"text_content": r[3],
"text": r[3] or r[2], # Template compatibility

View file

@ -225,9 +225,13 @@
--tox-high: #e74c3c;
}
/* Override base template container max-width for this page */
main .container {
max-width: 1400px !important;
}
.flagged-container {
padding: 2rem;
max-width: 1400px;
margin: 0 auto;
}
@ -344,6 +348,7 @@
width: 100%;
border-collapse: collapse;
font-size: 0.9rem;
table-layout: fixed;
}
.flagged-table thead {
@ -352,11 +357,12 @@
}
.flagged-table th {
padding: 1rem;
padding: 0.75rem 0.5rem;
text-align: left;
font-weight: 600;
color: var(--dark-text);
white-space: nowrap;
font-size: 0.85rem;
}
/* Sort Links */
@ -379,7 +385,7 @@
}
.flagged-table td {
padding: 1rem;
padding: 0.75rem 0.5rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
color: var(--dark-text);
}
@ -390,42 +396,49 @@
/* Column Styles */
.col-type {
width: 90px;
width: 60px;
}
.col-author {
width: 200px;
width: 15%;
max-width: 200px;
word-break: break-word;
}
.col-text {
min-width: 300px;
width: 45%;
word-break: break-word;
}
.col-score {
width: 150px;
width: 90px;
}
.col-category {
width: 140px;
width: 90px;
}
.col-created {
width: 120px;
width: 80px;
}
.col-review {
width: 130px;
width: 110px;
}
/* Badges */
.badge {
display: inline-block;
padding: 0.35rem 0.75rem;
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
font-size: 0.8rem;
font-size: 0.65rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
letter-spacing: 0.3px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
}
.badge-post {
@ -517,13 +530,13 @@
z-index: 1;
font-weight: 600;
color: var(--dark-text);
font-size: 0.85rem;
font-size: 0.75rem;
}
/* Time Ago */
.time-ago {
color: rgba(255, 255, 255, 0.6);
font-size: 0.85rem;
font-size: 0.75rem;
cursor: help;
}
@ -731,8 +744,7 @@ document.addEventListener('DOMContentLoaded', function() {
'Content-Type': 'application/json',
},
body: JSON.stringify({
item_id: itemId,
source_type: sourceType,
status_id: itemId,
review_status: clickedStatus,
}),
});

View file

@ -547,18 +547,21 @@ def api_review_submit():
from sqlalchemy import text
data = request.get_json()
logger.info(f"Review submission received: {data}")
status_id = data.get("status_id")
review_status = data.get("review_status")
if not all([status_id, review_status]):
logger.error(f"Missing fields - status_id: {status_id}, review_status: {review_status}")
return jsonify({"error": "Missing required fields"}), 400
if review_status not in ["correct", "incorrect", "unsure"]:
logger.error(f"Invalid review_status: {review_status}")
return jsonify({"error": "Invalid review_status"}), 400
session = get_session()
try:
session.execute(text("""
result = session.execute(text("""
UPDATE toxicity_scores
SET human_reviewed = true,
review_status = :review_status,
@ -566,6 +569,7 @@ def api_review_submit():
WHERE status_id = :status_id
"""), {"review_status": review_status, "status_id": status_id})
session.commit()
logger.info(f"Review saved for status_id {status_id}: {review_status} (rows affected: {result.rowcount})")
return jsonify({"success": True, "message": "Review submitted"}), 200
except Exception as e: