Phase 8: history

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jānis Kacēns
2026-05-11 16:50:55 +03:00
parent 715c1e4fe5
commit 2477130dd9
4 changed files with 219 additions and 0 deletions
+75
View File
@@ -445,6 +445,81 @@ func (r *Repo) GetStatsForUser(userID int64, questionIDs []string) (map[string]*
return result, rows.Err()
}
// ── History ──────────────────────────────────────────────────────────────────
// GetCorrectCountsForUser returns a map of test_id → correct-answer count for
// all completed tests belonging to userID.
func (r *Repo) GetCorrectCountsForUser(userID int64) (map[int64]int, error) {
rows, err := r.db.Query(`
SELECT ta.test_id, SUM(CASE WHEN ta.is_correct = 1 THEN 1 ELSE 0 END)
FROM test_answers ta
JOIN tests t ON ta.test_id = t.id
WHERE t.user_id = ? AND t.completed_at IS NOT NULL
GROUP BY ta.test_id`, userID)
if err != nil {
return nil, err
}
defer rows.Close()
result := make(map[int64]int)
for rows.Next() {
var testID int64
var correct int
if err := rows.Scan(&testID, &correct); err != nil {
return nil, err
}
result[testID] = correct
}
return result, rows.Err()
}
// GetAggregateStats returns total correct and total answered across all
// completed tests for userID.
func (r *Repo) GetAggregateStats(userID int64) (totalCorrect, totalAnswered int, err error) {
err = r.db.QueryRow(`
SELECT
COALESCE(SUM(CASE WHEN ta.is_correct = 1 THEN 1 ELSE 0 END), 0),
COALESCE(COUNT(ta.question_id), 0)
FROM test_answers ta
JOIN tests t ON ta.test_id = t.id
WHERE t.user_id = ? AND t.completed_at IS NOT NULL`, userID,
).Scan(&totalCorrect, &totalAnswered)
return
}
// WeakSpot is a question the user has answered incorrectly more than once.
type WeakSpot struct {
QuestionID string
QuestionText string
TimesWrong int
TimesSeen int
}
// GetWeakSpots returns up to 10 questions the user has gotten wrong more than
// once, ordered by wrong-answer count descending.
func (r *Repo) GetWeakSpots(userID int64) ([]*WeakSpot, error) {
rows, err := r.db.Query(`
SELECT uqs.question_id, q.text, uqs.times_seen,
(uqs.times_seen - uqs.times_correct) AS times_wrong
FROM user_question_stats uqs
JOIN questions q ON uqs.question_id = q.id
WHERE uqs.user_id = ? AND (uqs.times_seen - uqs.times_correct) > 1
ORDER BY times_wrong DESC, uqs.times_seen DESC
LIMIT 10`, userID)
if err != nil {
return nil, err
}
defer rows.Close()
var spots []*WeakSpot
for rows.Next() {
s := &WeakSpot{}
if err := rows.Scan(&s.QuestionID, &s.QuestionText, &s.TimesSeen, &s.TimesWrong); err != nil {
return nil, err
}
spots = append(spots, s)
}
return spots, rows.Err()
}
// ── Draft (import review) ────────────────────────────────────────────────────
func newDraftID() string {