# VYNDR — Claude Code Project Context

## What This Is
Sports betting intelligence SaaS. Real software product.
Three tiers: Free (5 scans), Analyst ($19.99 / $14.99 founder), Desk ($49.99 / $34.99 founder).

## Tech Stack
- Backend: Node.js / Express
- Database: Supabase (PostgreSQL)
- Frontend: React Native (built in Cursor)
- Data: The Odds API ($30/mo), nba_api (free, Python wrapper)
- Caching: Redis — 15min for odds, 24hr for season averages, 1hr for recent games
- Payments: Stripe

## Critical Rules — Non-Negotiable

### 1. NO CODE WITHOUT A SPEC
Every feature requires a spec file in `specs/` before any code is written.
Spec must include: endpoints, data shapes, acceptance criteria, test plan.
Get approval before building.

### 2. WSL2 HEREDOC RULE
WSL2 corrupts heredoc for files over 10 lines.
ALWAYS use Python file-writing: `python3` with triple-quoted strings.
This applies to every file creation operation.

### 3. 5 QUALITY GATES (all must pass before any feature is marked complete)
1. Unit tests pass
2. Integration tests pass
3. Acceptance criteria met (from spec)
4. PR description written
5. CLAUDE.md updated if anything new learned

### 4. BUILD-STATE.md
Update after every session. What shipped, what's next, any blockers.

### 5. BLOCKERS.md
If you hit something you cannot resolve: log it. Don't guess. Don't skip.

## Folder Structure
```
vyndr/
├── src/
│   ├── routes/        # Express route handlers
│   ├── models/        # Supabase data models
│   ├── services/      # Business logic (prop analysis, odds normalization)
│   ├── middleware/     # Auth, rate limiting, scan counting
│   └── utils/         # Helpers, formatters, validators
├── tests/             # Unit + integration tests
├── docs/              # API docs, architecture notes
├── specs/             # Feature specs (write BEFORE code)
├── build-briefs/      # Session summaries
├── CLAUDE.md          # This file
├── ROADMAP.md         # Feature roadmap with phases
├── BUILD-STATE.md     # Current build status
├── BLOCKERS.md        # Unresolved blockers
└── DECISIONS.md       # Architecture decisions log
```

## All-Day Intelligence Layer (Session 23)
Free/cheap content that keeps the platform alive when odds-api props are
empty. NONE of these spend odds-api credits:
- `/api/schedule/:sport` — cache-aside ESPN scoreboard (`scheduleService`),
  self-heals on cache miss. Per-game `hasOdds`/`hasGameLines` flags peek at
  other caches without fetching.
- `/api/gamelines/:sport` — Tank01 book-by-book lines (RAPID_API_KEY quota).
- `/api/streaks/:sport` + `/api/hotlist/:sport` — PURE engines
  (`streaksService`, `hotListService`) computed from cached game logs. NO
  API calls. Logs loaded by `rosterLogs.js` (prefetch blob, else Redis SCAN
  over `gamelogs:{sport}:{player}:{count}`). Empty roster = valid empty state.
- `?stat=` filters narrow streaks/hotlist; categories in `config/statFilters.js`
  (mirror `web/src/config/statFilters.ts`). Discovery: `/api/stats/filters/:sport`.
- Dead providers: set `status: 'dead'` in `config/providers.js` to drop a
  provider from fallback chains + configured list (ParlayAPI host is dead).

## Provider Strategy (Session 30)
Player props now have abundance, not rationing.
- **Player props** — PRIMARY: PropLine (`proplineAdapter`, 3 keys
  `PROPLINE_API_KEY_1/2/3`, 3,000 req/day FREE, rotates per-key; registry
  `propline` priority 1). BACKUP: The Odds API (`ODDS_API_KEY`, 500/month,
  priority 2, conserve). `getOdds()` tries PropLine first when keys present,
  falls back to odds-api; the response + cache carry a `provider` field.
  PropLine is The-Odds-API-compatible → reuses `utils/oddsNormalizer`.
  MLB market keys (`batter_hits`, `pitcher_strikeouts`, …) were added to
  `MARKET_MAP` — without them MLB props normalize to zero.
- **MLB stats** — `mlbStatsAdapter` → statsapi.mlb.com. FREE, no auth,
  unlimited. Game logs, season averages, BvP, probable pitchers. Does NOT
  use the gateway (no quota). Registry `mlb-stats` (`noAuth: true`).
- **Game enrichment** — `scheduleService.getGameSummary(sport, eventId)` →
  ESPN summary (injuries, ESPN Bet odds, ATS, leaders, box score). Free.
- **Game-level odds** — Tank01 (unchanged). Tank01 PLAYER PROPS = empty,
  do not wire.

## Frontend ↔ Backend Wiring (Session 25 — non-obvious)
A new Express route under `/api/*` is NOT reachable from the browser until
a matching **Next.js proxy route** exists at `web/src/app/api/.../route.ts`
that forwards to `${BACKEND_URL}/api/...`. The browser hits the Next origin,
not Express directly. This bit us: schedule/gamelines/streaks/hotlist
endpoints worked on Express but 404'd in the UI for two sessions. When
adding a backend endpoint the frontend calls, ALWAYS add the proxy too
(pattern: `web/src/app/api/odds/nba/route.ts`).

Tank01 betting-odds real shape: sportsbooks are TOP-LEVEL keys on each
game object (`{ awayTeam, homeTeam, bet365:{...} }`), not a `sportsBooks`
array. Filter `NON_BOOK_KEYS` to extract books (see `gameLines.js`).

## Active Skills
- vyndr-voice (all user-facing output)
- prop-analysis (grading methodology)
- monetization-system (scan-5 pitch, tier conversion)
