Spaces:
Running on Zero
Running on Zero
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
|