You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
TypeScript Development-track ratings engine ported and integrated
✅ Done
Phase 4
Marathon Match ratings engine with relative-scoring support
✅ Done
Phase 5
autopilot-v6 calls /stats/refresh and /stats/rerate at challenge end
✅ Done
Phase 6
reports-api-v6 SQL migrated to unified tables
✅ Done
Phase 7
Parity validation and consumer cutover (this phase)
🔄 In progress
New M2M Scopes
Scope
Route
Purpose
refresh:member_stats
POST /members/:handle/stats/refresh
Recompute aggregate stats from challenge results
rerate:member_stats
POST /members/:handle/stats/rerate
Re-run ratings from a given challenge forward
Configuration Flag
STATS_READ_SOURCE controls which stats tables back the read path.
unified is the default and reads from memberStats plus memberStatsHistory.
legacy falls back to the pre-migration table set during staged rollout or rollback validation.
Recommended rollout:
Backfill unified tables.
Run parity checks while reads stay on legacy.
Switch reads to unified after parity is clean and downstream consumers are verified.
Remove the flag only after the rollback window closes and legacy tables are no longer needed operationally.
Operational Runbook
1. Pre-flight checklist
Confirm all three database env vars are set: DATABASE_URL, CHALLENGES_DB_URL or CHALLENGE_DB_URL, REVIEW_DB_URL
Confirm REVIEW_DB_URL points to the review-api database that contains challengeResult; this is separate from the member Prisma schema deployment
Confirm the challenge-api-v6 migration that adds hidden legacy challenge types has been deployed so legacy subtracks such as ARCHITECTURE, ASSEMBLY_COMPETITION, and SRM can be resolved during stats backfill
Confirm STATS_READ_SOURCE=legacy is set so reads stay on legacy tables during backfill
Confirm member-api-v6 is running and /members/health returns 200
2. Phase A - Full backfill (run once, idempotent)
Step
Command
Notes
A1
node src/scripts/recalculateMemberStats.js
Full backfill of all users; writes memberStats + memberStatsHistory; starts aggregate rows from legacy stats tables when present, supplements them with newer review-api challengeResult rows, falls back to review-api challengeResult or ChallengeWinner when legacy rows do not exist, and also supplements memberStatsHistory with completed review/winner-backed challenges that were never written to the legacy history tables. Preserves existing rating/rank fields, processes users with bounded parallelism (--concurrency, default 4), and checkpoints processed user IDs to ./recalculateMemberStats.processedUserIds.json after each completed batch
Recommended for the initial full load when total runtime matters; still backfills legacy rating/rank fields but skips the expensive Development rerate replay so rerates can be run separately in Phase D
Re-run aggregate stats only if history is already seeded; full-user runs replace stale public unified-only rows for migrated members while leaving legacy-backed parents intact
If the script exits before processing users with REVIEW_DB_URL does not expose challengeResult, the configured review database is missing the review-api table. Deploy review-api-v6 migrations or update REVIEW_DB_URL to the correct database before retrying.
For large environments, prefer --skip-rerate during the bulk backfill and run the Development rerate pass separately afterward. The rerate replay is usually the longest phase by a wide margin because it replays rated challenge history one challenge at a time.
The script logs per-batch timing breakdowns for preload queries, aggregate generation, stats/history writes, rerates, and checkpoint writes, plus slow-user samples for the aggregate/history/rerate phases.
3. Phase B - Parity validation (run before switching read source)
Use the earliest Marathon Match challengeId for full history, or a later one for a partial rerate
D4
node src/scripts/rerateMarathonMatches.js --dry-run then node src/scripts/rerateMarathonMatches.js --concurrency 5
n/a
Bulk rerates native Marathon Match ratings for every discovered competitor from the beginning. Challenge discovery uses only the Marathon Match ChallengeType id, so Data Science and Development-track MM imports are replayed together into DATA_SCIENCE / MARATHON_MATCH; migrated numeric rows are de-duplicated against canonical UUID rows by MM round number during replay; no MM_DB_URL or marathon-match-api schema is required
D5
node src/scripts/dedupeMarathonMatchHistory.js --dry-run then node src/scripts/dedupeMarathonMatchHistory.js --apply
n/a
Physically removes migrated numeric DATA_SCIENCE / MARATHON_MATCHmemberStatsHistory rows that duplicate complete official Marathon Match UUID history rows for the same user and MM round. This requires the members and challenges schemas to be accessible from the same Postgres database and leaves non-MM helper Challenge API records intact
Auth: Bearer token with rerate:member_stats scope, or admin JWT
challengeId is required for rerates; use the earliest applicable challenge when you need a full-history rerate.
trackId and typeId are API enum names, not DB UUIDs.
6. Phase E - Stats refresh after a challenge
Step
Endpoint
Body
Notes
E1
POST /members/:handle/stats/refresh
{ "challengeId": "<uuid>" }
Recomputes aggregate stats; callable by autopilot-v6 via M2M
Auth: Bearer token with refresh:member_stats scope, or admin JWT