A single-user matched betting tracker built with Next.js, Drizzle, and the Vercel AI SDK.
This project focuses on making matched betting workflows fast, transparent, and auditable: capture bets quickly, reconcile outcomes reliably, and track profitability over time.
From specs/product.md, the current in-scope capabilities are:
- Matched sets (back + lay) with promo metadata
- AI screenshot intake: upload -> parse -> review -> save
- Reconciliation workflow
- Reporting and exposure tracking
- CSV import/export
Core domain entities are documented in specs/data-model.md (Accounts, Bets, MatchedSets, Promos, FreeBets, DepositBonuses, Transactions, Screenshots).
These routes are part of the current screenshot intake flow:
app/(chat)/api/bets/screenshots/route.ts- upload screenshotsapp/(chat)/api/bets/autoparse/route.ts- AI parse screenshotsapp/(chat)/api/bets/create-matched/route.ts- save matched bet
- Next.js App Router + React 19
- Vercel AI SDK / AI Gateway
- Drizzle ORM + Postgres
- Auth.js (Google + GitHub OAuth)
- Tailwind CSS + shadcn/ui
- Playwright + Vitest
-
Install dependencies:
pnpm install
-
Create local env file:
cp .env.example .env.local
-
Fill required values in
.env.local(see.env.example):POSTGRES_URL= BLOB_READ_WRITE_TOKEN= AI_GATEWAY_API_KEY= FXRATES_API_KEY= FOOTBALL_DATA_API_TOKEN= ODDS_API_API_KEY= MATCH_PROVIDER= ODDS_API_LEAGUES= UNLINKED_SETTLEMENT_SEARCH_MODEL= UNLINKED_SETTLEMENT_SEARCH_MODE= UNLINKED_SETTLEMENT_SEARCH_FALLBACK_MODELS= AZURE_DOCUMENT_INTELLIGENCE_ENDPOINT= AZURE_DOCUMENT_INTELLIGENCE_KEY= AUTH_SECRET= GOOGLE_CLIENT_ID= GOOGLE_CLIENT_SECRET= GITHUB_CLIENT_ID= GITHUB_CLIENT_SECRET=
The match data source is pluggable (see
lib/matches). SetODDS_API_API_KEYto use odds-api.io (395+ football leagues plus other sports); otherwise the sync falls back to football-data.org (FOOTBALL_DATA_API_TOKEN). Force a source withMATCH_PROVIDER(odds-api|football-data), and optionally restrict the odds-api target leagues withODDS_API_LEAGUES(comma-separated slugs, e.g.norway-eliteserien,usa-mls). odds-api leagues are discovered dynamically, so competitions that are out of season are skipped until fixtures publish.AI_GATEWAY_API_KEYenables auto-settlement fallback for matched sets that are not linked to a synced football match. The cron uses AI Gateway (openai/gpt-5.4-miniby default) to look up final scores from the manually entered market/selection. SetUNLINKED_SETTLEMENT_SEARCH_MODE=disabledto skip this lookup.UNLINKED_SETTLEMENT_SEARCH_FALLBACK_MODELSis an optional comma-separated list of backup AI Gateway models tried in order when the primary model fails or is rate-limited (defaults to a fewopenai/models so the OpenAIweb_searchtool stays valid). Transient failures (rate limits, 5xx, network errors) leave the betmatchedso the cron retries it on the next run instead of flagging it for manual review.Optional settled-bet edit allowlists:
SETTLED_BET_EDIT_USER_IDS= SETTLED_BET_EDIT_USER_EMAILS=
-
Apply database migrations:
pnpm db:migrate
-
Start the app:
pnpm dev
Then open http://localhost:3000.
pnpm dev- run local Next.js dev serverpnpm build- run DB migration then production buildpnpm start- start production serverpnpm lint- run Ultracite checkspnpm format- auto-fix formattingpnpm db:generate- generate Drizzle SQLpnpm db:migrate- apply latest DB migrationspnpm db:studio- open Drizzle Studiopnpm test- run Playwright testspnpm test:unit- run unit testspnpm test:integration- run integration tests withREAL_AI=true
- Manual migration job:
.github/workflows/db-migrate.yml- Run from GitHub Actions with
workflow_dispatch - Uses environment-scoped secret
POSTGRES_URL_NON_POOLING - Supports
productionorpreviewtarget environment
- Run from GitHub Actions with
- Migration integrity gate:
.github/workflows/migration-integrity.yml- Runs on PRs and pushes to
main - Executes
pnpm db:generateand fails if generated files underlib/db/migrationsdiffer from committed artifacts
- Runs on PRs and pushes to
To enforce this as a hard release gate, set Migration Integrity / Verify drizzle migration artifacts are committed as a required status check in branch protection for main.
See specs/README.md for the full spec index, including:
product.mddata-model.mdai-autoparse.mdlifecycle.mdreporting.mdimport-export.mdui-ux.md