Add persian-tutor: Gradio-based GCSE Persian language learning app
Vocabulary study with FSRS spaced repetition, AI tutoring (Ollama/Claude), essay marking, idioms browser, Anki export, and dashboard. 918 vocabulary entries across 39 categories. 41 tests passing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
84
python/persian-tutor/modules/dashboard.py
Normal file
84
python/persian-tutor/modules/dashboard.py
Normal file
@@ -0,0 +1,84 @@
|
||||
"""Dashboard: progress stats, charts, and overview."""
|
||||
|
||||
import db
|
||||
from modules.vocab import load_vocab, get_categories
|
||||
|
||||
|
||||
def get_overview():
|
||||
"""Return overview stats: total words, seen, mastered, due today."""
|
||||
vocab = load_vocab()
|
||||
counts = db.get_word_counts(total_vocab_size=len(vocab))
|
||||
stats = db.get_stats()
|
||||
counts["streak"] = stats["streak"]
|
||||
counts["total_reviews"] = stats["total_reviews"]
|
||||
counts["total_quizzes"] = stats["total_quizzes"]
|
||||
return counts
|
||||
|
||||
|
||||
def get_category_breakdown():
|
||||
"""Return progress per category as list of dicts."""
|
||||
vocab = load_vocab()
|
||||
categories = get_categories()
|
||||
|
||||
breakdown = []
|
||||
for cat in categories:
|
||||
cat_words = [e for e in vocab if e["category"] == cat]
|
||||
cat_ids = {e["id"] for e in cat_words}
|
||||
total = len(cat_words)
|
||||
|
||||
seen = 0
|
||||
mastered = 0
|
||||
for wid in cat_ids:
|
||||
progress = db.get_word_progress(wid)
|
||||
if progress:
|
||||
seen += 1
|
||||
if progress["stability"] and progress["stability"] > 10:
|
||||
mastered += 1
|
||||
|
||||
breakdown.append({
|
||||
"Category": cat,
|
||||
"Total": total,
|
||||
"Seen": seen,
|
||||
"Mastered": mastered,
|
||||
"Progress": f"{seen}/{total}" if total > 0 else "0/0",
|
||||
})
|
||||
|
||||
return breakdown
|
||||
|
||||
|
||||
def get_recent_quizzes(limit=10):
|
||||
"""Return recent quiz results as list of dicts for display."""
|
||||
stats = db.get_stats()
|
||||
quizzes = stats["recent_quizzes"][:limit]
|
||||
result = []
|
||||
for q in quizzes:
|
||||
result.append({
|
||||
"Date": q["timestamp"],
|
||||
"Category": q["category"] or "All",
|
||||
"Score": f"{q['correct']}/{q['total_questions']}",
|
||||
"Duration": f"{q['duration_seconds'] or 0}s",
|
||||
})
|
||||
return result
|
||||
|
||||
|
||||
def format_overview_markdown():
|
||||
"""Format overview stats as a markdown string for display."""
|
||||
o = get_overview()
|
||||
pct = (o["seen"] / o["total"] * 100) if o["total"] > 0 else 0
|
||||
bar_filled = int(pct / 5)
|
||||
bar_empty = 20 - bar_filled
|
||||
progress_bar = "█" * bar_filled + "░" * bar_empty
|
||||
|
||||
lines = [
|
||||
"## Dashboard",
|
||||
"",
|
||||
f"**Words studied:** {o['seen']} / {o['total']} ({pct:.0f}%)",
|
||||
f"`{progress_bar}`",
|
||||
"",
|
||||
f"**Due today:** {o['due']}",
|
||||
f"**Mastered:** {o['mastered']}",
|
||||
f"**Daily streak:** {o['streak']} day{'s' if o['streak'] != 1 else ''}",
|
||||
f"**Total reviews:** {o['total_reviews']}",
|
||||
f"**Quiz sessions:** {o['total_quizzes']}",
|
||||
]
|
||||
return "\n".join(lines)
|
||||
Reference in New Issue
Block a user