File size: 4,568 Bytes
7554bb0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
from __future__ import annotations

from dataclasses import dataclass, field
from typing import List

"""Module for tracking user progress such as XP, badges, completed tasks, etc.

This module is intentionally lightweight so it can be reused by tutors and the UI
without additional dependencies.  All rendering is done via simple HTML snippets
that are embedded in the Gradio interface.

Future improvements (e.g., persistent storage) can be implemented in this class
without touching the rest of the codebase.
"""


@dataclass(slots=True)
class BadgeDefinition:
    """Configuration for a badge unlock.

    Attributes
    ----------
    name : str
        Display name of the badge.
    description : str
        Short description shown in the dashboard.
    threshold : int
        XP threshold required to unlock the badge.
    """

    name: str
    description: str
    threshold: int


@dataclass
class ProgressTracker:
    """Simple XP, badge and task tracker for the user."""

    def __init__(self):
        self.xp: int = 0
        self.tasks_completed: int = 0
        self.skills: dict[str, int] = {"grammar": 0, "vocabulary": 0, "pronunciation": 0}

        # Badge definitions - XP thresholds must be > 0 for XP-based badges
        self.BADGES: List[BadgeDefinition] = [
            BadgeDefinition(name="First Steps", description="Earn 50 XP", threshold=50),
            BadgeDefinition(name="Getting Warmer", description="Earn 200 XP", threshold=200),
            BadgeDefinition(name="Rising Star", description="Earn 500 XP", threshold=500),
            BadgeDefinition(name="Master", description="Earn 1000 XP", threshold=1000),
            # Task-based badge (threshold=0 means it's not XP-based)
            BadgeDefinition(name="Wordsmith", description="Complete 10 writing tasks", threshold=0),
        ]
        self.badges: List[str] = []

    def add_xp(self, amount: int) -> None:
        """Add XP and check for new badges."""
        if amount <= 0:
            return
        self.xp += amount
        self._check_badges()

    def increment_tasks(self, count: int = 1) -> None:
        """Increment completed tasks counter and check task-based badges."""
        if count <= 0:
            return
        self.tasks_completed += count
        self._check_badges()

    def update_skill(self, skill: str, points: int):
        """Update skill level and check for skill badges"""
        if skill in self.skills:
            self.skills[skill] += points
            if skill == "grammar" and self.skills[skill] >= 50:
                self._award_badge("Grammar Guru")

    # ---------------------------------------------------------------------
    # Rendering helpers
    # ---------------------------------------------------------------------
    def html_dashboard(self) -> str:
        """Generate HTML snippet showing user progress."""
        badges_html = ""
        if self.badges:
            badges_html = "".join(f"<li class='badge'>{badge}</li>" for badge in self.badges)
        else:
            badges_html = "<li>No badges yet</li>"

        skills_html = "".join(f"<li>{skill}: {level}</li>" for skill, level in self.skills.items())

        return f"""
        <div class='progress-dashboard'>
            <h2>User Progress</h2>
            <p>Total XP: {self.xp}</p>
            <div class='progress-bar-outer'>
                <div class='progress-bar-inner' style='width:{min(100, self.xp/10)}%'></div>
            </div>
            <h3>Skills</h3>
            <ul>{skills_html}</ul>
            <h3>Badges</h3>
            <ul class='badge-list'>{badges_html}</ul>
        </div>
        """

    # ------------------------------------------------------------------
    # Internal utilities
    # ------------------------------------------------------------------
    def _check_badges(self) -> None:
        """Check if user qualifies for any new badges."""
        for badge_def in self.BADGES:
            if badge_def.name not in self.badges:
                # XP-based badges (threshold > 0)
                if badge_def.threshold > 0 and self.xp >= badge_def.threshold:
                    self._award_badge(badge_def.name)
                # Special case for task-based badges (threshold=0)
                elif badge_def.name == "Wordsmith" and self.tasks_completed >= 10:
                    self._award_badge(badge_def.name)

    def _award_badge(self, badge_name: str) -> None:
        """Award a badge to the user."""
        if badge_name not in self.badges:
            self.badges.append(badge_name)