Session 7c: Code audit - system manifest, env var documentation, 11 findings catalogued
This commit is contained in:
@@ -132,3 +132,17 @@
|
|||||||
{"ts":"2026-06-10T05:18:36.318Z","sport":"nba","player_espn_id":"3934719","player_name":"OG Anunoby","stat_type":"points","line":16.5,"direction":"over","actual_value":17,"result":"hit","margin":0.5,"grade":"A"}
|
{"ts":"2026-06-10T05:18:36.318Z","sport":"nba","player_espn_id":"3934719","player_name":"OG Anunoby","stat_type":"points","line":16.5,"direction":"over","actual_value":17,"result":"hit","margin":0.5,"grade":"A"}
|
||||||
{"ts":"2026-06-10T05:18:36.418Z","sport":"nba","player_espn_id":"99999","player_name":"Phantom","stat_type":"points","line":10.5,"direction":"over","actual_value":null,"result":"void","grade":"B"}
|
{"ts":"2026-06-10T05:18:36.418Z","sport":"nba","player_espn_id":"99999","player_name":"Phantom","stat_type":"points","line":10.5,"direction":"over","actual_value":null,"result":"void","grade":"B"}
|
||||||
{"ts":"2026-06-10T05:18:36.836Z","sport":"nba","player_espn_id":"3934719","player_name":"OG Anunoby","stat_type":"points","line":16.5,"direction":"over","actual_value":17,"result":"hit","margin":0.5,"grade":"A"}
|
{"ts":"2026-06-10T05:18:36.836Z","sport":"nba","player_espn_id":"3934719","player_name":"OG Anunoby","stat_type":"points","line":16.5,"direction":"over","actual_value":17,"result":"hit","margin":0.5,"grade":"A"}
|
||||||
|
{"ts":"2026-06-10T06:06:48.782Z","sport":"nba","player_espn_id":"3934719","player_name":"OG Anunoby","stat_type":"points","line":16.5,"direction":"over","actual_value":17,"result":"hit","margin":0.5,"grade":"A"}
|
||||||
|
{"ts":"2026-06-10T06:06:48.825Z","sport":"nba","player_espn_id":"3934719","player_name":"OG Anunoby","stat_type":"points","line":16.5,"direction":"over","actual_value":17,"result":"hit","margin":0.5,"grade":"A-"}
|
||||||
|
{"ts":"2026-06-10T06:06:48.825Z","sport":"nba","player_espn_id":"3136195","player_name":"Karl-Anthony Towns","stat_type":"rebounds","line":13.5,"direction":"under","actual_value":13,"result":"hit","margin":-0.5,"grade":"B+"}
|
||||||
|
{"ts":"2026-06-10T06:06:48.825Z","sport":"nba","player_espn_id":"3062679","player_name":"Josh Hart","stat_type":"pts_reb_ast","line":10.5,"direction":"over","actual_value":10,"result":"miss","margin":-0.5,"grade":"C"}
|
||||||
|
{"ts":"2026-06-10T06:06:48.871Z","sport":"nba","player_espn_id":"99999","player_name":"Phantom","stat_type":"points","line":10.5,"direction":"over","actual_value":null,"result":"void","grade":"B"}
|
||||||
|
{"ts":"2026-06-10T06:06:48.876Z","sport":"nba","player_espn_id":"999999999","player_name":"Phantom Player","stat_type":"points","line":10.5,"direction":"over","actual_value":null,"result":"void","grade":"C"}
|
||||||
|
{"ts":"2026-06-10T06:06:49.592Z","sport":"nba","player_espn_id":"3934719","player_name":"OG Anunoby","stat_type":"points","line":16.5,"direction":"over","actual_value":17,"result":"hit","margin":0.5,"grade":"A"}
|
||||||
|
{"ts":"2026-06-10T06:13:25.197Z","sport":"nba","player_espn_id":"3934719","player_name":"OG Anunoby","stat_type":"points","line":16.5,"direction":"over","actual_value":17,"result":"hit","margin":0.5,"grade":"A"}
|
||||||
|
{"ts":"2026-06-10T06:13:25.292Z","sport":"nba","player_espn_id":"99999","player_name":"Phantom","stat_type":"points","line":10.5,"direction":"over","actual_value":null,"result":"void","grade":"B"}
|
||||||
|
{"ts":"2026-06-10T06:13:25.356Z","sport":"nba","player_espn_id":"3934719","player_name":"OG Anunoby","stat_type":"points","line":16.5,"direction":"over","actual_value":17,"result":"hit","margin":0.5,"grade":"A-"}
|
||||||
|
{"ts":"2026-06-10T06:13:25.356Z","sport":"nba","player_espn_id":"3136195","player_name":"Karl-Anthony Towns","stat_type":"rebounds","line":13.5,"direction":"under","actual_value":13,"result":"hit","margin":-0.5,"grade":"B+"}
|
||||||
|
{"ts":"2026-06-10T06:13:25.356Z","sport":"nba","player_espn_id":"3062679","player_name":"Josh Hart","stat_type":"pts_reb_ast","line":10.5,"direction":"over","actual_value":10,"result":"miss","margin":-0.5,"grade":"C"}
|
||||||
|
{"ts":"2026-06-10T06:13:25.409Z","sport":"nba","player_espn_id":"999999999","player_name":"Phantom Player","stat_type":"points","line":10.5,"direction":"over","actual_value":null,"result":"void","grade":"C"}
|
||||||
|
{"ts":"2026-06-10T06:13:25.717Z","sport":"nba","player_espn_id":"3934719","player_name":"OG Anunoby","stat_type":"points","line":16.5,"direction":"over","actual_value":17,"result":"hit","margin":0.5,"grade":"A"}
|
||||||
|
|||||||
@@ -0,0 +1,529 @@
|
|||||||
|
# VYNDR — System Manifest
|
||||||
|
|
||||||
|
Source of truth for every interface in the codebase as of Session 7c
|
||||||
|
(958 tests across 73 suites, zero regressions). When this drifts from
|
||||||
|
reality, update this file FIRST.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Architecture overview
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────┐
|
||||||
|
│ Next.js (3000) │ ─── public web app + app/api/* proxies
|
||||||
|
└────────┬─────────┘
|
||||||
|
│ BACKEND_URL
|
||||||
|
▼
|
||||||
|
n8n cron ── POST ─► ┌──────────────────┐ ─► Supabase (RLS-enforced reads,
|
||||||
|
PM2 pollers ──────► │ Express (3001) │ service-role writes from API)
|
||||||
|
Coolify healthcheck │ │ ─► Redis (cache + locks)
|
||||||
|
└────────┬─────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌───────────────────────────────────┐
|
||||||
|
│ External adapters (configured()- │
|
||||||
|
│ gated, rate-limited, breakered) │
|
||||||
|
│ SharpAPI · OddsPapi · PropOdds · │
|
||||||
|
│ ParlayAPI · CFBD · OpenRouter · │
|
||||||
|
│ ESPN · MLB Stats API · Pinnacle │
|
||||||
|
└───────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
Two parallel grading paths coexist (audited in Session 7c):
|
||||||
|
|
||||||
|
- **Legacy path** (still wired): `routes/scan|analyze|bets|alerts` →
|
||||||
|
`parlayScanService` → `propAnalyzer` → `grader.js` → `UnifiedOddsProvider`
|
||||||
|
→ 9 book adapters (ESPN, Pinnacle, DraftKings, FanDuel, BetMGM, Caesars,
|
||||||
|
PrizePicks, Covers, Rotowire) → `processing/{LineShoppingEngine,
|
||||||
|
MiddlesDetector, EVCalculator}`.
|
||||||
|
- **New path** (Sessions 6a-6c): `routes/grading/pipeline` →
|
||||||
|
`gradingOrchestrator` → `featureCache` + `trapDetection` +
|
||||||
|
`consistencyScore` + `probabilityEstimator` → `engine1` →
|
||||||
|
(A/B-tier only) `engine2` via OpenRouter.
|
||||||
|
|
||||||
|
The two paths share `grade_history`, `Redis`, `Supabase`. They do not
|
||||||
|
share the grading function itself. **This duplication is documented as
|
||||||
|
finding #ARCH-1 below.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Express API endpoints
|
||||||
|
|
||||||
|
Mounted in `src/app.js`. Auth column meanings:
|
||||||
|
|
||||||
|
- **public** — no auth middleware
|
||||||
|
- **user** — `requireAuth` (Supabase JWT bearer)
|
||||||
|
- **internal** — `requireInternal` (X-VYNDR-Internal-Key + loopback IP)
|
||||||
|
- **pipeline** — `requirePipelineSecret` (X-Pipeline-Secret)
|
||||||
|
- **stripe-sig** — `stripe.webhooks.constructEvent` signature
|
||||||
|
|
||||||
|
| Method | Path | Auth | Body limit | Handler |
|
||||||
|
| ------ | ------------------------------------- | ----------- | ---------- | ------------------------------------ |
|
||||||
|
| GET | /api/health | public | n/a | `app.js` (inline) |
|
||||||
|
| GET | /api/odds/nba | public | 10mb | `routes/odds.js` |
|
||||||
|
| GET | /api/odds/ncaab | public | 10mb | `routes/odds.js` |
|
||||||
|
| POST | /api/analyze/prop | public | 10mb | `routes/analyze.js` (demo scan) |
|
||||||
|
| POST | /api/analyze/batch | public | 10mb | `routes/analyze.js` ⚠ no rate limit |
|
||||||
|
| POST | /api/scan/parlay | user | 10mb | `routes/scan.js` |
|
||||||
|
| GET | /api/movements | public | 10mb | `routes/movements.js` |
|
||||||
|
| GET | /api/alerts | user | 10mb | `routes/alerts.js` |
|
||||||
|
| PATCH | /api/alerts/:id/read | user | 10mb | `routes/alerts.js` |
|
||||||
|
| POST | /api/bets/quickslip | user | 10mb | `routes/bets.js` |
|
||||||
|
| POST | /api/bets/screenshot | user | 10mb | `routes/bets.js` |
|
||||||
|
| POST | /api/bets/screenshot/confirm | user | 10mb | `routes/bets.js` |
|
||||||
|
| POST | /api/bets/sync | user | 10mb | `routes/bets.js` |
|
||||||
|
| PATCH | /api/bets/:id/settle | user | 10mb | `routes/bets.js` |
|
||||||
|
| GET | /api/bets | user | 10mb | `routes/bets.js` |
|
||||||
|
| GET | /api/bets/performance | user | 10mb | `routes/bets.js` |
|
||||||
|
| POST | /api/stripe/checkout | user | 10mb | `routes/stripe.js` |
|
||||||
|
| POST | /api/stripe/webhook | stripe-sig | raw bytes | `routes/stripe.js` |
|
||||||
|
| POST | /api/stripe/portal | user | 10mb | `routes/stripe.js` |
|
||||||
|
| GET | /api/stripe/status | user | 10mb | `routes/stripe.js` |
|
||||||
|
| GET | /api/stats/parlays-graded | public | 10mb | `routes/stats.js` |
|
||||||
|
| GET | /api/stats/public | public | 10mb | `routes/stats.js` |
|
||||||
|
| GET | /api/stats/live | public | 10mb | `routes/stats.js` |
|
||||||
|
| GET | /api/props/joint-history | user | 10mb | `routes/props.js` |
|
||||||
|
| POST | /api/waitlist | public | 10mb | `routes/waitlist.js` |
|
||||||
|
| POST | /api/pipeline/refresh | pipeline | 10mb | `routes/pipeline.js` |
|
||||||
|
| GET | /api/pipeline/status | public | 10mb | `routes/pipeline.js` |
|
||||||
|
| POST | /api/share-card | public | 10mb | `routes/shareCard.js` |
|
||||||
|
| POST | /api/push/subscribe | user | 10mb | `routes/push.js` |
|
||||||
|
| DELETE | /api/push/unsubscribe | user | 10mb | `routes/push.js` |
|
||||||
|
| POST | /api/grading/resolve | internal | **10mb** | `routes/grading.js` |
|
||||||
|
| POST | /api/grading/pipeline | internal | 10mb | `routes/grading.js` |
|
||||||
|
| POST | /api/grading/correct | internal | 256kb | `routes/corrections.js` |
|
||||||
|
| GET | /api/widget | public | 10mb | `routes/widget.js` |
|
||||||
|
| OPTIONS| /api/widget | public | n/a | `routes/widget.js` |
|
||||||
|
|
||||||
|
### Next.js API routes (frontend `app/api/*`)
|
||||||
|
|
||||||
|
These are proxies or thin wrappers; they hit Express via `BACKEND_URL`
|
||||||
|
or the Python service via `NEXT_PUBLIC_NBA_SERVICE_URL`.
|
||||||
|
|
||||||
|
- `/api/checkout` (POST/GET) — NexaPay checkout
|
||||||
|
- `/api/games/[id]` and `/api/games/tonight` — list / detail
|
||||||
|
- `/api/games/[id]/props` — props for a game
|
||||||
|
- `/api/intelligence/feed` — homepage live signals
|
||||||
|
- `/api/ledger`, `/api/ledger/accuracy` — Ledger feed
|
||||||
|
- `/api/parlay/add-leg`, `/api/parlay/grade` — proxy to `/api/scan/parlay`
|
||||||
|
- `/api/players/search` — proxy to Python `/players/search`
|
||||||
|
- `/api/props/live`, `/api/props/most-parlayed`, `/api/props/top-graded`
|
||||||
|
- `/api/scan` — bare scan endpoint
|
||||||
|
- `/api/stats/parlays-graded`, `/api/stats/public` — proxy
|
||||||
|
- `/api/user/profile`, `/api/user/scans`, `/api/user/recent-scans`
|
||||||
|
- `/api/waitlist` — proxy
|
||||||
|
- `/api/webhook/nexapay` — NexaPay webhook
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Environment variables
|
||||||
|
|
||||||
|
Source: `grep -rn 'process\.env\.' src/ poller/ scripts/ web/src/`.
|
||||||
|
✓ = documented in `.env.example`. ⚠ = used but optional (default falls
|
||||||
|
back). Updated this session in Section 1 of Session 7c.
|
||||||
|
|
||||||
|
### Core
|
||||||
|
| Var | Required | Default | Used By | Doc? |
|
||||||
|
| ------------------------- | -------- | -------------- | ------------------------------------ | ---- |
|
||||||
|
| `NODE_ENV` | no | (none) | various | ✓ |
|
||||||
|
| `PORT` | no | `3001` | `src/server.js` | implicit |
|
||||||
|
| `BASE_URL` | yes | `http://localhost:3001` | `stripeService`, `widget` | ✓ |
|
||||||
|
| `FRONTEND_ORIGINS` | yes | (none) | `app.js` (CORS) | ✓ |
|
||||||
|
| `VYNDR_INTERNAL_KEY` | yes | (none) | `routes/grading`, `corrections` | ✓ |
|
||||||
|
|
||||||
|
### Supabase
|
||||||
|
| Var | Required | Used By | Doc? |
|
||||||
|
| -------------------------------- | -------- | -------------------------------- | ---- |
|
||||||
|
| `SUPABASE_URL` | yes | both clients | ✓ |
|
||||||
|
| `SUPABASE_ANON_KEY` | yes | `getSupabaseClient` | ✓ |
|
||||||
|
| `SUPABASE_SERVICE_ROLE_KEY` | yes | `getSupabaseServiceClient` | ✓ |
|
||||||
|
| `SUPABASE_SERVICE_KEY` (legacy) | no | fallback only (7b) | ✓ commented |
|
||||||
|
| `SUPABASE_DB_URL` | yes | `scripts/backup.sh`, `migrations`| ✓ |
|
||||||
|
| `SUPABASE_DB_PASSWORD` | no | legacy backup form | ✓ commented |
|
||||||
|
| `NEXT_PUBLIC_SUPABASE_URL` | yes | Next.js client | ✓ |
|
||||||
|
| `NEXT_PUBLIC_SUPABASE_ANON_KEY` | yes | Next.js client | ✓ |
|
||||||
|
| `NEXT_PUBLIC_SITE_URL` | yes | Next.js metadata | ✓ |
|
||||||
|
|
||||||
|
### Web ↔ Backend
|
||||||
|
| Var | Required | Default | Used By | Doc? |
|
||||||
|
| ---------------------------- | -------- | ------------------------ | -------------------------------- | ---- |
|
||||||
|
| `NEXT_PUBLIC_API_URL` | yes | `http://localhost:3001` | `web/src/lib/api.ts` | ✓ (added 7c) |
|
||||||
|
| `NEXT_PUBLIC_NBA_SERVICE_URL`| yes | `http://localhost:8000` | `web/api/players/search` | ✓ (added 7c) |
|
||||||
|
| `BACKEND_URL` | yes | `http://localhost:3001` | `web/services/odds-cache.ts` | ✓ (added 7c) |
|
||||||
|
|
||||||
|
### Stripe + Payments
|
||||||
|
| Var | Doc? |
|
||||||
|
| ---------------------------- | ---- |
|
||||||
|
| `STRIPE_SECRET_KEY` | ✓ |
|
||||||
|
| `STRIPE_WEBHOOK_SECRET` | ✓ |
|
||||||
|
| `STRIPE_PRICE_ANALYST` | ✓ |
|
||||||
|
| `STRIPE_PRICE_ANALYST_FOUNDER` | ✓ |
|
||||||
|
| `STRIPE_PRICE_DESK` | ✓ |
|
||||||
|
| `STRIPE_PRICE_DESK_FOUNDER` | ✓ |
|
||||||
|
| `FOUNDER_CODES` | ✓ |
|
||||||
|
| `FOUNDER_CODE_EXPIRY` | ✓ |
|
||||||
|
| `NEXAPAY_API_URL` | ✓ (added 7c) |
|
||||||
|
| `NEXAPAY_API_KEY` | ✓ (added 7c) |
|
||||||
|
| `NEXAPAY_WEBHOOK_SECRET` | ✓ (added 7c) |
|
||||||
|
|
||||||
|
### Push
|
||||||
|
| Var | Doc? |
|
||||||
|
| ---------------------------- | ---- |
|
||||||
|
| `VAPID_PUBLIC_KEY` | ✓ |
|
||||||
|
| `VAPID_PRIVATE_KEY` | ✓ |
|
||||||
|
| `VAPID_SUBJECT` | ✓ |
|
||||||
|
| `NEXT_PUBLIC_VAPID_PUBLIC_KEY`| ✓ |
|
||||||
|
|
||||||
|
### Odds adapters
|
||||||
|
| Var | Doc? |
|
||||||
|
| ------------------------- | ---- |
|
||||||
|
| `SHARPAPI_KEY` | ✓ |
|
||||||
|
| `SHARPAPI_BASE_URL` | ✓ |
|
||||||
|
| `ODDSPAPI_KEY` | ✓ |
|
||||||
|
| `ODDSPAPI_BASE_URL` | ✓ |
|
||||||
|
| `PARLAYAPI_KEY` | ✓ |
|
||||||
|
| `PARLAYAPI_BASE_URL` | ✓ |
|
||||||
|
| `PARLAYAPI_PULL_RATE_MS` | ✓ |
|
||||||
|
| `PROPODDS_KEY` | ✓ |
|
||||||
|
| `PROPODDS_BASE_URL` | ✓ |
|
||||||
|
| `CFBD_KEY` | ✓ |
|
||||||
|
| `CFBD_BASE_URL` | ✓ |
|
||||||
|
| `PINNACLE_API_KEY` | ✓ commented (legacy) |
|
||||||
|
| `PINNACLE_API_BASE` | ✓ commented (legacy) |
|
||||||
|
| `ODDS_API_KEY` | ✓ commented (legacy) |
|
||||||
|
|
||||||
|
### Engine 2
|
||||||
|
| Var | Doc? |
|
||||||
|
| ---------------------------- | ---- |
|
||||||
|
| `OPENROUTER_API_KEY` | ✓ |
|
||||||
|
| `OPENROUTER_PRIMARY_MODEL` | ✓ |
|
||||||
|
| `OPENROUTER_FALLBACK_MODEL` | ✓ |
|
||||||
|
| `OPENROUTER_BASE_URL` | ✓ |
|
||||||
|
| `OPENROUTER_REFERER` | ✓ |
|
||||||
|
| `OPENROUTER_TITLE` | ✓ |
|
||||||
|
| `ENGINE2_BATCH_SIZE` | ✓ |
|
||||||
|
| `ENGINE2_ENABLED` | ✓ |
|
||||||
|
|
||||||
|
### Redis + Python
|
||||||
|
| Var | Doc? |
|
||||||
|
| ------------------------- | ---- |
|
||||||
|
| `REDIS_URL` | ✓ |
|
||||||
|
| `PYTHON_SERVICE_URL` | ✓ |
|
||||||
|
| `NBA_SERVICE_URL` | ✓ commented (legacy alias) |
|
||||||
|
| `NBA_STATS_URL` | ✓ commented (legacy alias) |
|
||||||
|
| `EVOLUTION_SERVICE_URL` | ✓ commented (legacy) |
|
||||||
|
| `VYNDR_PYTHON` | ✓ commented (legacy alias) |
|
||||||
|
|
||||||
|
### Distribution + Analytics
|
||||||
|
| Var | Doc? |
|
||||||
|
| ------------------------- | ---- |
|
||||||
|
| `TELEGRAM_BOT_TOKEN` | ✓ |
|
||||||
|
| `TELEGRAM_CHANNEL_ID` | ✓ |
|
||||||
|
| `DISCORD_WEBHOOK_DAILY` | ✓ |
|
||||||
|
| `DISCORD_WEBHOOK_RESULTS` | ✓ |
|
||||||
|
| `DISCORD_WEBHOOK_ALERTS` | ✓ |
|
||||||
|
| `DISCORD_WEBHOOK_RARE` | ✓ |
|
||||||
|
| `RESEND_API_KEY` | ✓ (added 7c) |
|
||||||
|
| `RESEND_FROM_EMAIL` | ✓ (added 7c) |
|
||||||
|
| `NEXT_PUBLIC_POSTHOG_KEY` | ✓ (added 7c) |
|
||||||
|
| `NEXT_PUBLIC_POSTHOG_HOST`| ✓ (added 7c) |
|
||||||
|
|
||||||
|
### Poller-set (per-process via PM2)
|
||||||
|
| Var | Default | Doc? |
|
||||||
|
| ----------------- | ------- | ---- |
|
||||||
|
| `SPORT` | (none) | ✓ commented |
|
||||||
|
| `POLL_INTERVAL` | `60000` | ✓ commented |
|
||||||
|
| `BUFFER_MS` | `30000` | ✓ commented |
|
||||||
|
| `VYNDR_API_URL` | `http://localhost:3001` | ✓ commented |
|
||||||
|
| `OFF_HOURS_POLL_MS` | hardcoded 5min | not env |
|
||||||
|
|
||||||
|
### Backup + Ops
|
||||||
|
| Var | Doc? |
|
||||||
|
| ------------------------- | ---- |
|
||||||
|
| `NTFY_PORT` | ✓ |
|
||||||
|
| `NTFY_TOPIC` | ✓ |
|
||||||
|
| `BACKUP_RETENTION_DAYS` | ✓ |
|
||||||
|
| `PIPELINE_SECRET` | ✓ commented |
|
||||||
|
| `PUBLIC_SHARE_CARD_BASE` | ✓ commented |
|
||||||
|
| `API_BASE_URL` | ✓ commented (alias of BASE_URL) |
|
||||||
|
| `ENGINE` | ✓ commented (legacy) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Supabase tables
|
||||||
|
|
||||||
|
Source: `grep -rn "\.from('[a-z_]*')" src/ poller/`.
|
||||||
|
|
||||||
|
### Tables created by VYNDR migrations
|
||||||
|
| Table | Migration | Read by | Written by |
|
||||||
|
| ---------------------- | --------- | ------------------------------------------------------ | --------------------------------------- |
|
||||||
|
| `user_profiles` | 011, 015 | AuthContext, stripeService mirror, accuracyTracker | handle_new_user, stripeService |
|
||||||
|
| `parlay_leg_frequency` | 011 | parlay analytics | scan flow |
|
||||||
|
| `scan_history` | 011 | user scans listing | scanRoutes |
|
||||||
|
| `odds_cache` | 014 | oddsService | oddsService, n8n pipeline |
|
||||||
|
| `line_history` | 014 | lineMovementService | lineMovementService |
|
||||||
|
| `cascade_alerts` | 014 | cascadeService, intelligence feed | cascadeService |
|
||||||
|
| `player_stats_cache` | 014 | propAnalyzer | propAnalyzer |
|
||||||
|
| `grade_history` | 014/16/18 | Ledger, resolution route, weight adjuster, accuracy | orchestrator, resolution, engine2, corrections |
|
||||||
|
| `prop_correlations` | 014 | correlationEngine | correlationEngine |
|
||||||
|
| `player_id_map` | 014→016 | populate-player-ids, oddsPapiAdapter | populate-player-ids |
|
||||||
|
| `push_subscriptions` | 015 | webPush | push routes, webPush prune |
|
||||||
|
| `closing_lines` | 016 | clvTracker, oddsPapiAdapter | oddsPapiAdapter.batchCapture |
|
||||||
|
| `resolution_results` | 016 | clvTracker, weightAdjuster, corrections | resolution route |
|
||||||
|
| `historical_props` | 017 | trap historical signal (future) | pull-parlayapi-history |
|
||||||
|
| `line_snapshots` | 017 | lineMovement intel | sharpApiAdapter (fire-and-forget) |
|
||||||
|
| `ref_profiles` | 017 | refSignals | scrape-sports-reference |
|
||||||
|
| `coach_profiles` | 017 | coachSignals | scrape-sports-reference, seed file |
|
||||||
|
| `game_ref_assignments` | 017 | refSignals | ops manual entry |
|
||||||
|
| `engine1_weights` | 018 | weightAdjuster | weightAdjuster |
|
||||||
|
| `accuracy_tracking` | 018 | accuracyTracker, Ledger accuracy page | accuracyTracker |
|
||||||
|
| `user_notifications` | 013 | notifications route | notification senders |
|
||||||
|
|
||||||
|
### Tables present in DB but NOT in VYNDR migrations (BetonBLK era)
|
||||||
|
`bets`, `daily_scan`, `discrepancy_reliability_scores`, `joint_outcomes`,
|
||||||
|
`lineup_role_profiles`, `model_predictions_extended`, `outcomes`,
|
||||||
|
`performance`, `picks`, `player_role_activations`,
|
||||||
|
`player_role_profiles`, `prediction_registry`, `scan_sessions`.
|
||||||
|
|
||||||
|
VYNDR code reads/writes only the subset still in use:
|
||||||
|
- `bets` — `routes/bets.js`, `betService.js`
|
||||||
|
- `joint_outcomes` — `routes/props.js` (joint-history)
|
||||||
|
- `model_predictions_extended` — `propAnalyzer`
|
||||||
|
- `performance` — `performanceService`
|
||||||
|
- `picks` — `parlayScanService`
|
||||||
|
- `scan_sessions` — `routes/scan.js`
|
||||||
|
|
||||||
|
### Shared tables (BetonBLK + VYNDR)
|
||||||
|
- `users` — both eras
|
||||||
|
- `waitlist` / `waitlist_signups` — created in 012 (latter is canonical)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Redis keys
|
||||||
|
|
||||||
|
Source: `grep -rn "cacheSet\|cacheGet\|redis\.set"`.
|
||||||
|
|
||||||
|
| Key pattern | TTL | Set by | Read by |
|
||||||
|
| ---------------------------------------- | ----------- | --------------------------------- | ----------------------------- |
|
||||||
|
| `odds:{sport}:{gameId}:player_props` | 60s (300s stale) | sharpApiAdapter | sharpApiAdapter, orchestrator |
|
||||||
|
| `odds:{sport}:{gameId}:game` | 60s | sharpApiAdapter | sharpApiAdapter |
|
||||||
|
| `parlayapi:hist:{sport}:{p}:{stat}:{n}` | 24h | parlayApiAdapter | trapDetection (future) |
|
||||||
|
| `parlayapi:close:{sport}:{date}` | 24h | parlayApiAdapter | pull script |
|
||||||
|
| `parlayapi:checkpoint:{sport}:{season}` | 30d | pull-parlayapi-history | pull-parlayapi-history |
|
||||||
|
| `propodds:{sport}:{game}:{player}:{stat}`| 90s (300s stale) | propOddsAdapter | propOddsAdapter |
|
||||||
|
| `cfbd:teamstats:{team}:{year}` | 6h | cfbdAdapter | cfbdAdapter |
|
||||||
|
| `cfbd:usage:{player}:{team}:{year}` | 6h | cfbdAdapter | cfbdAdapter |
|
||||||
|
| `cfbd:talent:{team}:{year}` | 6h | cfbdAdapter | cfbdAdapter |
|
||||||
|
| `cfbd:lines:{team}:{year}` | 6h | cfbdAdapter | cfbdAdapter |
|
||||||
|
| `gamelogs:{sport}:{player}:{n}` | 4h | gameLogService | featureCache, consistency |
|
||||||
|
| `team_stats:{sport}:{teamAbbr}` | 24h | teamStatsCache | featureCache, engine1 |
|
||||||
|
| `features:{sport}:{playerId}:{stat}:{game}` | 2m | featureCache | orchestrator |
|
||||||
|
| `injuries:{sport}:{teamId}` | 2h | injuryParser | featureCache |
|
||||||
|
| `game:{gameId}:status` | 36h | poller | poller |
|
||||||
|
| `game:{gameId}:resolution_lock` | 5min (NX) | poller | poller (dedupe) |
|
||||||
|
| `game:{gameId}:live_status` | 1h | poller | frontend badges (future) |
|
||||||
|
| `poller:{sport}:heartbeat` | 3min | poller | ops monitoring |
|
||||||
|
| `vyndr:line_baseline:{...}` | 24h | lineMovementService | lineMovementService |
|
||||||
|
| (cascadeService player-set key) | 24h | cascadeService | cascadeService |
|
||||||
|
| (oddsService legacy game cache) | 5min | oddsService | routes/odds |
|
||||||
|
| (schemeClassifier cache) | varies | schemeClassifier | schemeClassifier |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. External API dependencies
|
||||||
|
|
||||||
|
| Service | Adapter | Env vars | Rate budget | Used by |
|
||||||
|
| -------------- | ------------------------------------ | ------------------------------------- | ----------------- | ------------------------ |
|
||||||
|
| SharpAPI | `sharpApiAdapter.js` | `SHARPAPI_KEY`, `SHARPAPI_BASE_URL` | 10/min | orchestrator |
|
||||||
|
| OddsPapi | `oddsPapiAdapter.js` | `ODDSPAPI_KEY`, `ODDSPAPI_BASE_URL` | 5/min | poller (tip-off CLV) |
|
||||||
|
| ParlayAPI | `parlayApiAdapter.js` | `PARLAYAPI_KEY` | 5/min | pull script |
|
||||||
|
| PropOdds | `propOddsAdapter.js` | `PROPODDS_KEY` | 3/min | trap consensus |
|
||||||
|
| CFBD | `cfbdAdapter.js` | `CFBD_KEY` | 10/min | college features |
|
||||||
|
| OpenRouter | `openRouterAdapter.js` | `OPENROUTER_API_KEY` + model env | 20/min, 1000/day | engine2 |
|
||||||
|
| ESPN scoreboard| `poller`, `teamStatsCache`, `injuryParser` | none | 2/min (limiter) | poller, refresh job |
|
||||||
|
| MLB Stats API | `poller` (gamePk feed) | none | 2/min | poller (mlb final) |
|
||||||
|
| Pinnacle (legacy) | `PinnacleAdapter.js` | `PINNACLE_API_KEY`, `PINNACLE_API_BASE` | tunable | UnifiedOddsProvider |
|
||||||
|
| DraftKings (legacy) | `DraftKingsAdapter.js` | none | tunable | UnifiedOddsProvider |
|
||||||
|
| FanDuel/BetMGM/Caesars/PrizePicks/Covers/Rotowire (legacy) | each `*Adapter.js` | none | tunable | UnifiedOddsProvider |
|
||||||
|
| Sports-Reference HTML | `scripts/scrape-sports-reference.js` | `REF_HTML_FILE`, `COACH_HTML_FILE` (optional) | 1 req / 5s | scraper |
|
||||||
|
| Resend (email) | `web/src/services/email.ts` | `RESEND_API_KEY`, `RESEND_FROM_EMAIL` | n/a | transactional email |
|
||||||
|
| NexaPay | `web/src/services/nexapay.ts` | `NEXAPAY_*` | n/a | checkout fallback |
|
||||||
|
| PostHog | `web/src/lib/analytics.ts` | `NEXT_PUBLIC_POSTHOG_KEY/HOST` | n/a | browser analytics |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. File dependency graph (entry points)
|
||||||
|
|
||||||
|
### Express entry: `src/server.js`
|
||||||
|
```
|
||||||
|
server.js
|
||||||
|
└─ app.js
|
||||||
|
├─ routes/{odds,analyze,scan,movements,alerts,bets,stripe,stats,
|
||||||
|
│ props,waitlist,pipeline,shareCard,push,grading,corrections,widget}
|
||||||
|
└─ middleware/{auth, mission}
|
||||||
|
|
||||||
|
routes/grading.js (POST /pipeline)
|
||||||
|
└─ services/intelligence/gradingOrchestrator
|
||||||
|
├─ adapters/sharpApiAdapter
|
||||||
|
├─ services/intelligence/featureCache
|
||||||
|
│ ├─ teamStatsCache · refSignals · coachSignals
|
||||||
|
│ ├─ injuryParser · lineMovement · gameLogService
|
||||||
|
│ └─ lineupSignals
|
||||||
|
├─ services/intelligence/trapDetection
|
||||||
|
│ └─ lineMovement
|
||||||
|
├─ services/intelligence/consistencyScore
|
||||||
|
├─ services/intelligence/probabilityEstimator
|
||||||
|
├─ services/intelligence/engine1
|
||||||
|
└─ services/intelligence/engine2
|
||||||
|
└─ adapters/openRouterAdapter
|
||||||
|
|
||||||
|
routes/grading.js (POST /resolve)
|
||||||
|
└─ services/training/jsonlLogger
|
||||||
|
└─ services/distribution/{webPush, telegram, discord}
|
||||||
|
└─ services/intelligence/{clvTracker, accuracyTracker, weightAdjuster}
|
||||||
|
|
||||||
|
routes/scan.js (POST /parlay — legacy path, still wired)
|
||||||
|
└─ services/parlayScanService
|
||||||
|
└─ services/propAnalyzer
|
||||||
|
└─ services/grader ← legacy grading (see ARCH-1)
|
||||||
|
└─ services/UnifiedOddsProvider
|
||||||
|
└─ adapters/{ESPN,Pinnacle,DraftKings,FanDuel,BetMGM,
|
||||||
|
Caesars,PrizePicks,Covers,Rotowire}
|
||||||
|
└─ services/processing/{LineShoppingEngine,
|
||||||
|
MiddlesDetector, EVCalculator}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Poller entry: `poller/poller.js`
|
||||||
|
```
|
||||||
|
poller.js
|
||||||
|
├─ src/config/sports
|
||||||
|
├─ src/utils/redis (cacheGet/cacheSet/lock)
|
||||||
|
├─ src/utils/rateLimiter (espnLimiter, mlbLimiter)
|
||||||
|
└─ src/services/adapters/oddsPapiAdapter (tip-off CLV capture)
|
||||||
|
```
|
||||||
|
|
||||||
|
No circular imports detected.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Findings
|
||||||
|
|
||||||
|
### ARCH — Architecture
|
||||||
|
|
||||||
|
- **[ARCH-1] Dual grading paths.** Severity: Medium. The legacy
|
||||||
|
`parlayScanService → propAnalyzer → grader → UnifiedOddsProvider`
|
||||||
|
chain still serves `/api/scan/parlay`, `/api/analyze/*`, and
|
||||||
|
`/api/bets/*`. The new `gradingOrchestrator → engine1 → engine2`
|
||||||
|
chain serves `/api/grading/pipeline`. Both write to `grade_history`
|
||||||
|
with different column sets and factor models. **Scope to fix:**
|
||||||
|
~1 session — choose one as canonical, migrate the routes, retire
|
||||||
|
the other adapter set.
|
||||||
|
|
||||||
|
- **[ARCH-2] Two circuit-breaker / rate-limiter modules.** Severity:
|
||||||
|
Low (documented in 6a). `src/services/{circuitBreaker.js,
|
||||||
|
rateLimiter.js}` (keyed registry, legacy) coexist with
|
||||||
|
`src/utils/rateLimiter.js` (factory, new). Consolidation is purely
|
||||||
|
cosmetic — both work. Scope: half-session.
|
||||||
|
|
||||||
|
### SEC — Security
|
||||||
|
|
||||||
|
- **[SEC-1] `/api/analyze/batch` has no auth or rate limit.** Severity:
|
||||||
|
High. A malicious caller can blast through prop-analyzer credits.
|
||||||
|
**Recommended fix:** require auth OR add an IP-keyed rate limiter
|
||||||
|
(10 req/min). Scope: ~30 min.
|
||||||
|
|
||||||
|
- **[SEC-2] `routes/shareCard.js` leaks `err.message` via `detail:`.**
|
||||||
|
Severity: Medium. Lines 185 and 206 expose upstream error messages
|
||||||
|
to public callers. **Recommended fix:** drop `detail` from public
|
||||||
|
responses; log to stderr only. Scope: ~10 min.
|
||||||
|
|
||||||
|
- **[SEC-3] Several `console.log` calls in production code.** Severity:
|
||||||
|
Low. All have `[VYNDR]` / `[redis]` / `[poller-X]` prefixes and
|
||||||
|
represent OPERATIONAL status (server starting, poller tick summary),
|
||||||
|
not debug. Per project convention `console.log` is acceptable for
|
||||||
|
operational events. Documenting so future audits don't re-flag.
|
||||||
|
|
||||||
|
### DEAD — Dead code
|
||||||
|
|
||||||
|
- **[DEAD-1] No truly orphaned files.** Every adapter in
|
||||||
|
`src/services/adapters/` has at least one consumer (legacy ones via
|
||||||
|
`UnifiedOddsProvider`, new ones via orchestrator/poller). Removing
|
||||||
|
any would break either `/api/scan` or `/api/grading/pipeline`.
|
||||||
|
|
||||||
|
- **[DEAD-2] `grader.js` is LIVE.** Imported by `propAnalyzer.js` →
|
||||||
|
`parlayScanService.js` → `routes/scan.js`. Kept until ARCH-1 is
|
||||||
|
resolved.
|
||||||
|
|
||||||
|
### DUP — Duplicates
|
||||||
|
|
||||||
|
- **[DUP-1] `normalizeName()` exists twice.** Severity: Low.
|
||||||
|
- `src/services/intelligence/trapDetection.js`
|
||||||
|
- `scripts/populate-player-ids.js`
|
||||||
|
Implementations are near-identical (NFD strip, lowercase, suffix
|
||||||
|
removal). **Recommended fix:** extract to `src/utils/normalize.js`;
|
||||||
|
the script can `require('../src/utils/normalize')`. Skipped this
|
||||||
|
session because the script is intentionally self-contained for
|
||||||
|
remote execution.
|
||||||
|
|
||||||
|
- **[DUP-2] `oddsToImplied` etc. only live in `src/utils/odds.js`.**
|
||||||
|
Confirmed not duplicated despite the `oddsNormalizer.js` neighbor —
|
||||||
|
the two files serve different purposes (math primitives vs book-level
|
||||||
|
prop shape).
|
||||||
|
|
||||||
|
### LEGACY — Documentation / naming
|
||||||
|
|
||||||
|
- **[LEG-1] BetonBLK references.** Severity: Informational. Confined to
|
||||||
|
`BUILD-STATE.md` historical notes, the `FOUNDER_CODES` allowlist
|
||||||
|
(intentional grandfathering — documented in stripeService comment),
|
||||||
|
and tests that assert the grandfathered code still validates. No
|
||||||
|
action.
|
||||||
|
|
||||||
|
### TODO
|
||||||
|
|
||||||
|
- **[TODO-1] PrizePicks / DraftKings / Pinnacle adapter TODOs.**
|
||||||
|
Severity: Low. Tracked in `specs/data-pipeline-books.md`. Each has a
|
||||||
|
comment line referencing the spec. Keep until the corresponding
|
||||||
|
feature ships.
|
||||||
|
|
||||||
|
### PERF — Performance
|
||||||
|
|
||||||
|
- **[PERF-1] `/api/analyze/*` lacks Redis cache.** Severity: Medium.
|
||||||
|
Each call re-runs `analyzeProp()` end-to-end. **Recommended fix:**
|
||||||
|
cache the result by `(player, stat, line, direction, sport)` for
|
||||||
|
60s. Scope: ~30 min.
|
||||||
|
|
||||||
|
- **[PERF-2] `routes/scan.js` resolves parlays one prop at a time.**
|
||||||
|
Sequential `await analyzeProp()` inside a for-loop. For 6-leg
|
||||||
|
parlays this is ~6x latency vs `Promise.allSettled`. **Recommended
|
||||||
|
fix:** parallelize independent props.
|
||||||
|
|
||||||
|
- **[PERF-3] Bundle analysis not run this session.** The
|
||||||
|
`ANALYZE=true` flag would require `@next/bundle-analyzer` which is
|
||||||
|
not installed. **Recommended fix:** install bundle-analyzer dev dep
|
||||||
|
+ run once + decide whether to ship a hidden audit/page route.
|
||||||
|
|
||||||
|
### Frontend ↔ Backend contract
|
||||||
|
|
||||||
|
All frontend API paths discovered are either:
|
||||||
|
- Direct Express paths confirmed in `src/app.js` (e.g. `/api/scan/parlay`,
|
||||||
|
`/api/waitlist`), or
|
||||||
|
- Next.js `app/api/*/route.ts` proxies that internally fetch
|
||||||
|
`${BACKEND_URL}/...`.
|
||||||
|
|
||||||
|
**No broken contracts found** — every fetched path maps to an existing
|
||||||
|
handler. Spot-checked: `/api/players/search` (Next → Python),
|
||||||
|
`/api/scan` (Next → Express), `/api/intelligence/feed` (Next direct DB).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. How to update this manifest
|
||||||
|
|
||||||
|
When you add a route, table, env var, or external API:
|
||||||
|
|
||||||
|
1. Add the row to the appropriate section above.
|
||||||
|
2. Re-run the discovery greps from this session's prompt to confirm
|
||||||
|
nothing else changed implicitly.
|
||||||
|
3. If you remove a table or env var, mark it `[REMOVED Session NX]`
|
||||||
|
instead of deleting the row outright — keeps audit trail.
|
||||||
|
|
||||||
|
Last updated: Session 7c.
|
||||||
+1
-1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user