HomePilot Deploy Bot
chore(hf): sync HomePilot to HF Space
921d377
Raw
History Blame Contribute Delete
6.59 kB
"""
Level / tier description per progression scheme.
``describe_level`` takes a scheme + progress dict and returns a
``LevelDescription`` with (current_level, xp, xp_next, human
label). The frontend reads this to render the progress meter.
"""
from __future__ import annotations
import math
from dataclasses import dataclass
from typing import Dict
@dataclass(frozen=True)
class LevelDescription:
"""Human-readable progress snapshot."""
scheme: str
level: int
current_value: float
next_threshold: float
label: str
display: str # pre-formatted '15 / 35 XP β†’ Level 2'
# ─────────────────────────────────────────────────────────────────
# xp_level β€” quadratic curve: level N requires 15 * N^1.5 XP total
# ─────────────────────────────────────────────────────────────────
def _xp_threshold(level: int) -> int:
"""XP needed to REACH the start of this level."""
if level <= 1:
return 0
return int(15 * math.pow(level - 1, 1.5))
def level_from_xp(xp: float) -> int:
lvl = 1
while xp >= _xp_threshold(lvl + 1):
lvl += 1
if lvl >= 50:
break
return lvl
def _describe_xp_level(progress: Dict[str, float]) -> LevelDescription:
xp = float(progress.get("xp", 0.0))
level = int(progress.get("level") or level_from_xp(xp))
next_threshold = _xp_threshold(level + 1)
display = f"Level {level} {int(xp)} / {next_threshold} XP β†’ Level {level + 1}"
return LevelDescription(
scheme="xp_level", level=level, current_value=xp,
next_threshold=next_threshold, label=f"Level {level}", display=display,
)
# ─────────────────────────────────────────────────────────────────
# mastery β€” percentage (0..1) toward a single topic or overall
# ─────────────────────────────────────────────────────────────────
def _describe_mastery(progress: Dict[str, float]) -> LevelDescription:
pct = float(progress.get("pct", 0.0))
pct = max(0.0, min(1.0, pct))
level = int(pct * 100)
display = f"{level}% mastery"
return LevelDescription(
scheme="mastery", level=level, current_value=pct,
next_threshold=1.0, label=f"{level}%", display=display,
)
# ─────────────────────────────────────────────────────────────────
# cefr β€” tier (A1..C2 numeric 1..6)
# ─────────────────────────────────────────────────────────────────
_CEFR_NAMES = ["A1", "A2", "B1", "B2", "C1", "C2"]
def _describe_cefr(progress: Dict[str, float]) -> LevelDescription:
tier = int(progress.get("tier", 1) or 1)
tier = max(1, min(6, tier))
name = _CEFR_NAMES[tier - 1]
next_name = _CEFR_NAMES[tier] if tier < 6 else "C2"
display = f"CEFR {name} β†’ {next_name}"
return LevelDescription(
scheme="cefr", level=tier, current_value=float(tier),
next_threshold=float(min(6, tier + 1)), label=name, display=display,
)
# ─────────────────────────────────────────────────────────────────
# affinity_tier β€” float 0..1 mapped to named tiers
# ─────────────────────────────────────────────────────────────────
_AFFINITY_TIERS = [
(0.0, "Stranger"),
(0.2, "Friendly"),
(0.5, "Close"),
(0.75, "Intimate"),
(0.9, "Devoted"),
]
def _describe_affinity(progress: Dict[str, float]) -> LevelDescription:
aff = float(progress.get("affinity", 0.5))
aff = max(0.0, min(1.0, aff))
label = "Stranger"
next_threshold = 1.0
tier_idx = 0
for i, (thresh, name) in enumerate(_AFFINITY_TIERS):
if aff >= thresh:
label = name
tier_idx = i + 1
next_threshold = _AFFINITY_TIERS[i + 1][0] if i + 1 < len(_AFFINITY_TIERS) else 1.0
display = f"{label} ({int(aff * 100)}%)"
return LevelDescription(
scheme="affinity_tier", level=tier_idx, current_value=aff,
next_threshold=next_threshold, label=label, display=display,
)
# ─────────────────────────────────────────────────────────────────
# certification β€” discrete states
# ─────────────────────────────────────────────────────────────────
def _describe_certification(progress: Dict[str, float]) -> LevelDescription:
stage = int(progress.get("stage", 0) or 0)
stages = ["Not started", "In progress", "Passed", "Certified"]
stage = max(0, min(len(stages) - 1, stage))
display = stages[stage]
return LevelDescription(
scheme="certification", level=stage, current_value=float(stage),
next_threshold=float(len(stages) - 1), label=stages[stage], display=display,
)
# ─────────────────────────────────────────────────────────────────
# Public dispatcher
# ─────────────────────────────────────────────────────────────────
_DISPATCH = {
"xp_level": _describe_xp_level,
"mastery": _describe_mastery,
"cefr": _describe_cefr,
"affinity_tier": _describe_affinity,
"certification": _describe_certification,
}
def describe_level(scheme: str, progress: Dict[str, float]) -> LevelDescription:
fn = _DISPATCH.get(scheme, _describe_xp_level)
return fn(progress)