"""Explicit UTC activity and reported-sales metric definitions.""" import statistics def engagement(conn,game_id,days,player_ids=None): daily=conn.execute(""" WITH calendar AS (SELECT generate_series(current_date-(%(days)s-1),current_date,interval '1 day')::date AS day), selected_players AS (SELECT id,first_seen FROM players WHERE game_id=%(gid)s AND (%(player_ids)s::bigint[] IS NULL OR id=ANY(%(player_ids)s))), active AS (SELECT player_id,active_day FROM player_active_days WHERE game_id=%(gid)s AND player_id IN(SELECT id FROM selected_players) AND active_day>=current_date-(%(days)s+29)), first_sessions AS (SELECT DISTINCT ON(player_id) player_id,started_at,ended_at FROM sessions WHERE game_id=%(gid)s AND player_id IN(SELECT id FROM selected_players) ORDER BY player_id,started_at,id), visits AS (SELECT a.active_day AS day,count(*) dau,count(*) FILTER(WHERE (p.first_seen AT TIME ZONE 'UTC')::date=a.active_day) new_users FROM active a JOIN selected_players p ON p.id=a.player_id GROUP BY a.active_day), times AS (SELECT active_day AS day,sum(playtime) total,count(*) observed,count(playtime) known FROM session_day_playtime WHERE game_id=%(gid)s AND player_id IN(SELECT id FROM selected_players) AND active_day>=current_date-(%(days)s-1) GROUP BY active_day), starts AS (SELECT (s.started_at AT TIME ZONE 'UTC')::date AS day,count(*) sessions, avg(extract(epoch FROM ended_at-started_at))::double precision avg_session, sum(extract(epoch FROM ended_at-started_at)) FILTER(WHERE (s.started_at AT TIME ZONE 'UTC')::date=(p.first_seen AT TIME ZONE 'UTC')::date)::double precision new_time, count(s.ended_at) FILTER(WHERE (s.started_at AT TIME ZONE 'UTC')::date=(p.first_seen AT TIME ZONE 'UTC')::date) new_count, sum(extract(epoch FROM ended_at-started_at)) FILTER(WHERE (s.started_at AT TIME ZONE 'UTC')::date>(p.first_seen AT TIME ZONE 'UTC')::date)::double precision returning_time, count(s.ended_at) FILTER(WHERE (s.started_at AT TIME ZONE 'UTC')::date>(p.first_seen AT TIME ZONE 'UTC')::date) returning_count FROM sessions s JOIN selected_players p ON p.id=s.player_id WHERE s.game_id=%(gid)s AND s.started_at>=current_date-(%(days)s-1) GROUP BY day), first5 AS (SELECT (started_at AT TIME ZONE 'UTC')::date AS day,count(ended_at) known, count(*) FILTER(WHERE ended_at-started_at>=interval '5 minutes') stayed5 FROM first_sessions GROUP BY day) SELECT c.day,coalesce(v.dau,0)::int dau,coalesce(v.new_users,0)::int new_users, (coalesce(v.dau,0)-coalesce(v.new_users,0))::int returning_users, (SELECT count(DISTINCT player_id)::int FROM active a WHERE a.active_day BETWEEN c.day-29 AND c.day) mau, coalesce(s.sessions,0)::int sessions,s.avg_session, CASE WHEN t.known>0 THEN t.total WHEN t.observed>0 THEN NULL ELSE 0 END total_playtime, s.new_time,s.new_count,s.returning_time,s.returning_count,f.known first_known,f.stayed5 first_stayed5 FROM calendar c LEFT JOIN visits v ON v.day=c.day LEFT JOIN times t ON t.day=c.day LEFT JOIN starts s ON s.day=c.day LEFT JOIN first5 f ON f.day=c.day ORDER BY c.day """,{"gid":game_id,"days":days,"player_ids":player_ids}).fetchall() for r in daily: r['average_playtime']=r['total_playtime']/r['dau'] if r['total_playtime'] is not None and r['dau'] else None r['new_session_time']=r['new_time']/r['new_count'] if r['new_count'] else None r['returning_session_time']=r['returning_time']/r['returning_count'] if r['returning_count'] else None r['first_session_5min']=100*r['first_stayed5']/r['first_known'] if r['first_known'] else None latest=daily[-1] new_count=sum(r['new_count'] or 0 for r in daily) returning_count=sum(r['returning_count'] or 0 for r in daily) first_known=sum(r['first_known'] or 0 for r in daily) known_time=[r['total_playtime'] for r in daily if r['total_playtime'] is not None] active_users=conn.execute("SELECT count(DISTINCT player_id) total FROM player_active_days WHERE game_id=%s AND active_day>=current_date-(%s-1) AND (%s::bigint[] IS NULL OR player_id=ANY(%s))",(game_id,days,player_ids,player_ids)).fetchone()['total'] total=sum(known_time) if known_time else None metrics={'dau':latest['dau'],'mau':latest['mau'],'newUsersToday':latest['new_users'],'returningUsersToday':latest['returning_users'], 'totalPlaytime':total,'averagePlaytime':total/active_users if total is not None and active_users else None, 'newSessionTime':sum(r['new_time'] or 0 for r in daily)/new_count if new_count else None, 'returningSessionTime':sum(r['returning_time'] or 0 for r in daily)/returning_count if returning_count else None, 'firstSession5min':100*sum(r['first_stayed5'] or 0 for r in daily)/first_known if first_known else None, 'activeUsers':active_users} return {'metrics':metrics,'daily':daily} def monetization(conn,game_id,days): totals=conn.execute("""SELECT coalesce(sum(unit_price_robux*quantity),0)::double precision revenue, count(DISTINCT player_id) FILTER(WHERE unit_price_robux>0)::int paying_users, coalesce(sum(quantity) FILTER(WHERE unit_price_robux>0),0)::int paid_products, count(*) FILTER(WHERE player_id IS NULL AND unit_price_robux>0)::int unattributed_sales, count(*)::int transactions FROM product_sales WHERE game_id=%s AND purchased_at>=current_date-(%s-1)""",(game_id,days)).fetchone() active=conn.execute("""SELECT count(DISTINCT player_id)::int users,count(*)::int user_days FROM player_active_days WHERE game_id=%s AND active_day>=current_date-(%s-1)""",(game_id,days)).fetchone() valid=totals['unattributed_sales']==0 metrics={'revenue':totals['revenue'],'payingUsers':totals['paying_users'] if valid else None, 'payerConversion':100*totals['paying_users']/active['users'] if active['users'] and valid else None, 'arppu':totals['revenue']/totals['paying_users'] if totals['paying_users'] and valid else None, 'productsPerPayer':totals['paid_products']/totals['paying_users'] if totals['paying_users'] and valid else None, 'arpdau':totals['revenue']/active['user_days'] if active['user_days'] and valid else None, 'transactions':totals['transactions'],'unattributedSales':totals['unattributed_sales']} daily=conn.execute("""WITH calendar AS(SELECT generate_series(current_date-(%(days)s-1),current_date,interval '1 day')::date AS day), sales AS(SELECT (purchased_at AT TIME ZONE 'UTC')::date AS day,sum(unit_price_robux*quantity)::double precision robux, count(DISTINCT player_id) FILTER(WHERE unit_price_robux>0)::int payers FROM product_sales WHERE game_id=%(gid)s AND purchased_at>=current_date-(%(days)s-1) GROUP BY day), active AS(SELECT active_day AS day,count(*)::int dau FROM player_active_days WHERE game_id=%(gid)s AND active_day>=current_date-(%(days)s-1) GROUP BY day) SELECT c.day,coalesce(s.robux,0) robux,coalesce(s.payers,0) payers,coalesce(a.dau,0) dau FROM calendar c LEFT JOIN sales s ON s.day=c.day LEFT JOIN active a ON a.day=c.day ORDER BY c.day""",{'gid':game_id,'days':days}).fetchall() for r in daily: r['arpdau']=r['robux']/r['dau'] if r['dau'] and valid else None r['conversion']=100*r['payers']/r['dau'] if r['dau'] and valid else None if not valid: r['payers']=None hourly=conn.execute("""WITH hours AS(SELECT generate_series(date_trunc('day',now())-(%(days)s-1)*interval '1 day',date_trunc('hour',now()),interval '1 hour') AS hour), sales AS(SELECT date_trunc('hour',purchased_at) AS hour,sum(unit_price_robux*quantity)::double precision robux FROM product_sales WHERE game_id=%(gid)s AND purchased_at>=current_date-(%(days)s-1) GROUP BY hour) SELECT h.hour,coalesce(s.robux,0) robux FROM hours h LEFT JOIN sales s ON s.hour=h.hour ORDER BY h.hour""",{'gid':game_id,'days':days}).fetchall() products=conn.execute("""SELECT p.id,p.roblox_product_id,p.name,p.current_price_robux, coalesce(sum(s.unit_price_robux*s.quantity),0)::double precision robux, coalesce(sum(s.quantity) FILTER(WHERE s.unit_price_robux>0),0)::int quantity, count(DISTINCT s.player_id) FILTER(WHERE s.unit_price_robux>0)::int buyers FROM products p LEFT JOIN product_sales s ON s.game_id=p.game_id AND s.product_id=p.id AND s.purchased_at>=current_date-(%s-1) WHERE p.game_id=%s GROUP BY p.id ORDER BY robux DESC,p.id""",(days,game_id)).fetchall() for p in products: p['perPayer']=p['quantity']/totals['paying_users'] if totals['paying_users'] and valid else None return {'metrics':metrics,'daily':daily,'hourly':hourly,'products':products} def gamepasses(conn,game_id): totals=conn.execute("""SELECT count(*)::int players,count(*) FILTER(WHERE EXISTS( SELECT 1 FROM product_sales s WHERE s.game_id=p.game_id AND s.player_id=p.id AND s.unit_price_robux>0))::int payers FROM players p WHERE p.game_id=%s""",(game_id,)).fetchone() passes=conn.execute("""WITH payers AS(SELECT DISTINCT player_id FROM product_sales WHERE game_id=%s AND unit_price_robux>0 AND player_id IS NOT NULL) SELECT gp.id,gp.name,gp.roblox_gamepass_id, count(o.player_id)::int checked,count(o.player_id) FILTER(WHERE o.is_owned)::int owners, count(o.player_id) FILTER(WHERE p.player_id IS NOT NULL)::int checked_payers, count(o.player_id) FILTER(WHERE o.is_owned AND p.player_id IS NOT NULL)::int paying_owners, min(o.checked_at) oldest_check,max(o.checked_at) newest_check FROM gamepasses gp LEFT JOIN gamepass_ownership o ON o.game_id=gp.game_id AND o.gamepass_id=gp.id LEFT JOIN payers p ON p.player_id=o.player_id WHERE gp.game_id=%s GROUP BY gp.id ORDER BY gp.id""",(game_id,game_id)).fetchall() for gp in passes: gp['playerPercent']=100*gp['owners']/totals['players'] if totals['players'] and gp['checked']==totals['players'] else None gp['payerPercent']=100*gp['paying_owners']/totals['payers'] if totals['payers'] and gp['checked_payers']==totals['payers'] else None gp['coverage']=100*gp['checked']/totals['players'] if totals['players'] else None return {'metrics':{'gamepasses':len(passes),'trackedPlayers':totals['players'],'lifetimePayers':totals['payers']}, 'gamepasses':passes,'denominators':totals, 'definition':'Current reported ownership across all tracked players and lifetime positive-price paying users. Unknown ownership is not treated as not owned.'}