> ## Documentation Index
> Fetch the complete documentation index at: https://lifters.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# VIP Intelligence

> How Atlas defines who is a VIP — the weighted-by-modality definition, per-brand configuration, and the definition history & audit trail.

VIP Intelligence identifies each brand's most valuable players and powers the
`/dashboard/vip-intelligence` screens — KPIs, client list, opportunities and
comparisons. **Who counts as a VIP is defined per brand** on
`/dashboard/settings/vip-tiers`, and every query in the module respects the
active definition.

## The weighted-by-modality definition

Atlas uses a single definition model: **weighted by modality**
(`weighted_by_vertical`). Casino and Sports are evaluated independently — a
player who qualifies in **either** modality is a VIP (OR semantics), and the
combined tier is the highest tier reached across both.

### Anchored window

Metrics are computed over a **per-player rolling window** (30, 60, 90 or 180
days) anchored on the player's **last valid activity** (any bet or approved
deposit). A player whose anchor is older than 180 days leaves the VIP base
entirely (staleness gate) — inactive players never linger in VIP lists.

### Weighted score per tier

Each modality defines tiers (e.g. Legend, Elite, Exclusive, Black) with a
minimum band per metric, and a weight per metric (weights sum to 100%;
weight 0 disables the metric):

| Modality | Metrics                                                          |
| -------- | ---------------------------------------------------------------- |
| Casino   | GGR (window), Net Cash (wallet deposits − withdrawals)           |
| Sports   | Stake volume, GGR, active days (bet date), W/D adjustment factor |

For a tier `T`, the score is `Σ weight_i × (metric_i / band_i(T))` — no cap,
so an extreme metric can qualify on its own. The player lands on the **highest
tier whose score reaches the qualifying cut**. Sub-tiers reuse the same
weighted score against the sub-tier bands.

### W/D adjustment (Sports second filter)

The W/D factor (wins ÷ window deposits; wins include returned stakes)
rebalances Sports tiers after the base score: operators define contiguous W/D
ranges, each contributing a score — a bad W/D demotes the tier, a good one
sustains it. Players with no deposit in the window fall in the worst range.
With W/D weight 0 the factor is inert (ranges stay editable in the UI).

## Definition history & audit

Every effective change to a brand's VIP definition is recorded as an immutable
**version** — visible in the *Histórico e auditoria* section of the VIP tiers
screen, with the full before/after report and one-click restore.

* Versions are numbered **per brand** (v1, v2, …); v1 is the definition in
  effect when auditing was enabled.
* A save that does not change the definition does **not** create a version.
* The recorded author is the operator's session e-mail.
* **Restore is append-only**: restoring v2 creates a new version with v2's
  content, flagged `restored_from_version: 2` — the trail is never rewritten.

### `GET /v1/vips/config/versions`

Returns the organization-wide history (all brands), newest first, capped at
the 100 most recent entries.

```json theme={null}
{
  "versions": [
    {
      "brand_id": "17451dfb-…",
      "brand_name": "vaidebet-ngx",
      "version": 3,
      "changed_by": "ops@operator.com",
      "restored_from_version": 1,
      "is_current": true,
      "created_at": "2026-07-20T18:42:11Z",
      "definition": { "mode": "weighted_by_vertical", "casino": { "…": "…" } },
      "prev_definition": { "…": "…" }
    }
  ]
}
```

`is_current` marks the version in effect for that brand. `prev_definition` is
absent on the initial seed version.

### `POST /v1/brands/{brand_id}/vips/config/versions/{version}/restore`

Restores the brand's definition to that version's content, **as a new
version**, and returns the updated config (same shape as
`PATCH /v1/brands/{brand_id}/vips/config`).

| Status                           | Meaning                                                                                          |
| -------------------------------- | ------------------------------------------------------------------------------------------------ |
| `200`                            | Restored — response carries the updated config                                                   |
| `404 vip_version_not_found`      | No such version for this brand                                                                   |
| `409 vip_config_conflict`        | Config changed concurrently — reload and retry                                                   |
| `422 vip_version_not_restorable` | The stored definition no longer passes current validation rules (e.g. a removed definition mode) |

Both endpoints require a dashboard session (API tokens are not accepted —
audit entries always carry a human author).

## Simulation

Preview how a **candidate** (unsaved) definition would partition the real player
base — powers the *Simulação* collapse in each modality of the VIP tiers screen.
It runs against live windowed data without persisting anything.

### `POST /v1/brands/{brand_id}/vips/simulate`

Body: a full `VIPDefinition` (same shape as `POST .../vips/count`), typically the
values currently on the screen. Only `weighted_by_vertical` is supported
(`400 vip_simulate_unsupported_mode` otherwise); the definition is validated
before running (`400 invalid_config`).

Returns the base partitioned per tier, per modality:

```json theme={null}
{
  "total": 707994,        // players in the window (universe)
  "vip_total": 3048,      // VIP overall — casino OR sport
  "casino": {
    "base": 3041,         // players landing in any casino tier
    "tiers": [ { "tier": "Legend", "count": 90 }, { "tier": "Black", "count": 2399 } ]
  },
  "sport": {
    "base": 109,
    "tiers": [ /* … */ ],
    "wd_base_count": 140,  // pass the 1st sport filter (before W/D)
    "wd_bands": [ { "band": "Faixa 1", "count": 41 } ],
    "wd_inactive": false   // true when the W/D weight is 0 (decomposition skipped)
  }
}
```

Tiers are mutually exclusive (each player lands in the highest tier met), so per
modality they sum to `base`. `vip_total` is the union (a player VIP in both
modalities is counted once) — it is **not** `casino.base + sport.base`. No cache:
the result reflects the exact definition posted.

## Where the data comes from

* Windowed metrics: `atlas.vw_vip_player_window` (ClickHouse Gold, refreshed
  daily) — one row per player and anchor, with columns per window preset.
* Lifetime universe and screens: `atlas.vw_vip_player_current`.
* Definition and version history: PostgreSQL (`vip_score_configs`,
  `vip_definition_versions`) — the backend is the single write path.
