"""Exclusive cohorts; convert legacy DB-local registration timestamps to Europe/Berlin.""" from datetime import date, datetime, time, timedelta import os from zoneinfo import ZoneInfo COHORTS = ('alpha_tester', 'beta_tester', 'early_bird') def boundaries(): alpha = date.fromisoformat(os.environ.get('ALPHA_TESTER_UNTIL', '2026-10-31')) beta = date.fromisoformat(os.environ.get('BETA_TESTER_UNTIL', '2026-12-31')) if beta <= alpha: raise ValueError('BETA_TESTER_UNTIL must be after ALPHA_TESTER_UNTIL') return (datetime.combine(alpha + timedelta(days=1), time()), datetime.combine(beta + timedelta(days=1), time())) def cohort(registered_at): if registered_at.tzinfo: registered_at = registered_at.astimezone(ZoneInfo('Europe/Berlin')).replace(tzinfo=None) alpha_end, beta_end = boundaries() return 'alpha_tester' if registered_at < alpha_end else 'beta_tester' if registered_at < beta_end else 'early_bird' def reconcile(cursor, user_id=None): """Also upgrades existing beta members and reclassifies when configured dates change.""" alpha_end, beta_end = boundaries() cursor.execute(''' DELETE FROM user_badges b USING users u WHERE b.user_id=u.id AND (%s::integer IS NULL OR u.id=%s) AND b.badge_code=ANY(%s) AND b.badge_code <> CASE WHEN u.created_at AT TIME ZONE current_setting('TimeZone') AT TIME ZONE 'Europe/Berlin' < %s THEN 'alpha_tester' WHEN u.created_at AT TIME ZONE current_setting('TimeZone') AT TIME ZONE 'Europe/Berlin' < %s THEN 'beta_tester' ELSE 'early_bird' END ''', (user_id, user_id, list(COHORTS), alpha_end, beta_end)) cursor.execute(''' INSERT INTO user_badges(user_id,badge_code,awarded_at) SELECT id, CASE WHEN created_at AT TIME ZONE current_setting('TimeZone') AT TIME ZONE 'Europe/Berlin' < %s THEN 'alpha_tester' WHEN created_at AT TIME ZONE current_setting('TimeZone') AT TIME ZONE 'Europe/Berlin' < %s THEN 'beta_tester' ELSE 'early_bird' END, created_at FROM users WHERE (%s::integer IS NULL OR id=%s) ON CONFLICT(user_id,badge_code) DO NOTHING ''', (alpha_end, beta_end, user_id, user_id))