> ## Documentation Index
> Fetch the complete documentation index at: https://docs.d-sports.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Fan Rep architecture

> Models and service boundaries for Fan, Club, Player, and Direct Rep.

## Primary backend components

* `app/api/v1/engage/rep/breakdown/route.ts`
* `app/api/v1/engage/rep/ledger/route.ts`
* `app/api/v1/engage/rep/earn/route.ts`
* `app/api/admin/users/[userId]/rep/route.ts`
* `server/rep-breakdown.ts`
* `server/rep-ledger.ts` (`recordRepEarn`, `recordDirectRepGrant`)
* `RepLedgerEntry` in `prisma/schema.prisma` (optional `reason`)

## Core model

`RepLedgerEntry` is the source of truth. Breakdown sums `amount` by `repType`:

| `repType` | Client source label |
| --------- | ------------------- |
| `Fan`     | `fanAmp`            |
| `Club`    | `club`              |
| `Player`  | `playerAmp`         |
| `Direct`  | `dsportsDirect`     |

`playerChallenge` writes map to `playerAmp` on the ledger read path. Direct grants map to `dsportsDirect`.

Direct earn kinds:

| Kind            | Writer today                         |
| --------------- | ------------------------------------ |
| `adminGrant`    | `POST /api/admin/users/{userId}/rep` |
| `referralBonus` | Reserved — no writer yet             |
| `eventReward`   | Reserved — no writer yet             |

Club rows may store a `targetId`. Only target IDs that resolve to a `Team` appear in `clubRepByTeam`. Daily-quest Club rows that target a quest id still count in `clubRep` but are dropped from the per-team list.

## High-level flow

```mermaid theme={null}
flowchart LR
  clientAction[ClientSafeAction] --> earnRoute[POST /api/v1/engage/rep/earn]
  dedicatedRoute[ArenaCheckInOrDailyQuest] --> dedicatedWrite[DedicatedRepWrite]
  adminGrant[AdminDirectGrant] --> directWrite[recordDirectRepGrant]
  earnRoute --> ledger[RepLedgerEntry]
  dedicatedWrite --> ledger
  directWrite --> ledger
  ledger --> breakdown[GET /api/v1/engage/rep/breakdown]
  ledger --> history[GET /api/v1/engage/rep/ledger]
```

## Architectural notes

* Amount and `repType` are never client-authoritative.
* Fan Rep is a separate ledger from `PointsHistory`.
* Direct grants use a server-generated idempotency key. Each admin POST is a new grant.
* Optional `reason` is stored on the ledger row and preferred on ledger reads.
