File size: 1,889 Bytes
d896946
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""The XP economy. Pure data + functions: easy to read, tune and test.

Design goals
  * Consistency beats bursts (daily cap, active-days requirement for prizes).
  * Referrals only pay in full when the invited student becomes a REAL active user.
  * Nothing here can be farmed with a script: caps, idempotent events, delayed rewards.
"""
from typing import Optional

DAILY_CAP = 25  # max XP/day from the automatic sources below
CAPPED_KINDS = frozenset({"checkin", "download", "referral_join"})
PER_KIND_DAILY = {"download": 3, "referral_join": 5}  # max events per day

REFERRAL_JOIN_XP = 5  # invitee joined the channel + passed the gate
REFERRAL_QUALIFIED_XP = 30  # invitee later proved active (see QUALIFY_* below)
REFERRAL_QUALIFIED_PER_WEEK = 5  # paid referrals counted per week
QUALIFY_MIN_ACTIVE_DAYS = 3
QUALIFY_WINDOW_DAYS = 14
VELOCITY_LIMIT_24H = 8  # more invitees than this in 24h from one inviter = suspicious

MIN_TRUST = 70
STREAK_MILESTONES = {7: 10, 14: 25, 30: 60, 60: 120}
LEVEL_THRESHOLDS = (0, 25, 75, 150, 275, 450, 700, 1050, 1550, 2300)  # level n needs >= [n-1]
MAX_LEVEL = len(LEVEL_THRESHOLDS)


def checkin_xp(streak: int) -> int:
    """3 XP on day 1, +1 per extra streak day, up to 7."""
    return 3 + min(max(streak - 1, 0), 4)


def level_for(points: int) -> tuple[int, Optional[int]]:
    """-> (level, XP still needed for the next level, or None at max level)."""
    level = 1
    for i, threshold in enumerate(LEVEL_THRESHOLDS, start=1):
        if points >= threshold:
            level = i
    if level >= MAX_LEVEL:
        return level, None
    return level, LEVEL_THRESHOLDS[level] - points


def next_streak(last_active_iso: Optional[str], today_iso: str, current: int, yesterday_iso: str) -> int:
    if last_active_iso == today_iso:
        return current
    if last_active_iso == yesterday_iso:
        return current + 1
    return 1