# Analytics, Earning History, and Leaderboards

## Authority boundary

Phase 17 does not create another balance source. `ledger_transactions`, `ledger_entries`, and `wallet_accounts` remain the financial authority. Daily/user/task statistics, Admin reports, and leaderboard snapshots are read models that can be rebuilt from authoritative events.

All money stays in integer minor units and is serialized as a decimal string. Analytics code never updates wallet accounts, ledger entries, completions, referrals, claims, or withdrawal state.

## UTC rollup contract

The scheduler processes the previous closed UTC calendar date. A distributed MySQL lease permits one application scheduler cycle at a time, while `analytics_rollup_runs(statistic_date, currency_id)` provides database-level idempotency.

One successful transaction:

1. Locks/checks the date and settlement-currency run key.
2. Captures authoritative source high-water marks.
3. Upserts daily, task, and user projections.
4. Builds all 12 metric/period snapshot combinations.
5. Publishes each completed snapshot.
6. Appends one immutable rollup evidence row.

If any step fails, the transaction rolls back. A retry sees either no completed run or the already-completed immutable run; it never publishes a partial date.

Supported leaderboard metrics are `verified_earnings`, `completed_tasks`, and `active_referrals`. Supported periods are `daily`, `weekly`, `monthly`, and `all_time`. Admins can choose the public metric and period through the existing versioned settings API using `settings.update`; arbitrary SQL fields or expressions are never accepted.

## Public privacy contract

`GET /api/v1/leaderboard` requires an authenticated Telegram session and reads a published snapshot. It never returns Telegram IDs, internal user IDs, email addresses, device data, or live profile JSON.

Users can set `leaderboard_visible` through `PATCH /api/v1/profile`. A value of `false` causes future snapshots to store only `Anonymous`, a null username, and a neutral initial. Telegram profile synchronization merges its owned fields into `profile_data`, so it cannot erase this privacy choice. Existing published snapshots are immutable and age out when the next snapshot for that period is published.

## Earning APIs

| Endpoint                                | Contract                                                                                                     |
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `GET /api/v1/earnings/history`          | Posted/reversed task, referral, and free-box reward movements for the authenticated user's available account |
| `GET /api/v1/earnings/summary?period=7d | 30d                                                                                                          | 90d` | Closed-date daily projections and integer totals |
| `GET /api/v1/leaderboard?limit=1..100`  | Current configured published snapshot plus the current user's rank when present                              |

History supports `category=all|task|referral|mystery`. Pagination uses an HMAC-signed, endpoint-scoped keyset cursor containing only the server position. Invalid or modified cursors fail closed.

## Admin reports and permissions

All routes also require the Admin IP policy and verified Admin authentication.

| Endpoint                              | Permission               | Data                                                                          |
| ------------------------------------- | ------------------------ | ----------------------------------------------------------------------------- |
| `GET /api/v1/admin/reports/overview`  | `reports.view`           | Non-monetary totals                                                           |
| `GET /api/v1/admin/reports/daily`     | `reports.view`           | Non-monetary daily series                                                     |
| `GET /api/v1/admin/reports/tasks`     | `reports.view`           | Task funnel counts with bounded pagination                                    |
| `GET /api/v1/admin/reports/rollups`   | `reports.view`           | Rollup timing, row counts, and source watermarks                              |
| `GET /api/v1/admin/reports/financial` | `reports.financial.view` | Reward, reversal, requested-withdrawal, and paid-withdrawal minor-unit totals |

Every report requires an inclusive `from` and `to` UTC date, with a maximum 366-day range. Report reads set Admin audit context. Operational permissions cannot reveal monetary totals.

## Migration and operations

1. Back up and test-restore MySQL.
2. Review existing statistic rows. Before adding the breakdown equality constraint, any pre-Phase-17 `user_statistics.earned_minor` values must agree with their task/referral/mystery components.
3. Review legacy leaderboard entries: score and rank must be positive, and display snapshots must be JSON objects.
4. Apply `022_harden_analytics_and_leaderboards.sql` in staging and run `npm run check`.
5. Start exactly the intended scheduler replicas and confirm one immutable rollup run for the previous UTC date.
6. Keep the public leaderboard feature flag disabled until the first snapshots and privacy behavior are inspected.
7. Enable the flag and choose the metric/period only after approval.

No Phase 17 build step connects to a live database or deployment automatically.

## Monitoring

Alert on scheduler errors, a missing closed-date rollup, stale `calculated_at`, unexpected row-count changes, duplicate-key failures outside normal retry behavior, or a published snapshot with an implausible entry count. Compare source watermarks to current authoritative table IDs to identify projection lag. Reconciliation must repair projections only; it must never manufacture ledger movements.
