Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions dashboard/backend/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- **Market Consensus & Deviations — Phase 2**: Expanded market consensus analytics with richer consensus and dispersion metrics plus explicit best-value fields for bookmaker outlier workflows.
- `prisma/schema.prisma`: Enhanced `MarketConsensus` model with `consensusPrice`, `medianLine`, `meanLine`, `modeLine`, `range`, `interquartileRange`, `bestValueSide`, `bestValueBookmaker`, `bestValueLine`, `sharpBookWeight`, and a `marketType` index.
- `prisma/migrations/20260514000001_enhance_market_consensus_phase2/migration.sql`: Adds/backfills new `market_consensus` columns and creates an index on `market_type`.
- `src/services/market-consensus.service.ts`: `calculateConsensus()` now computes additional phase-2 market-truth metrics and persists them; added `identifyOutliers(gameId)` for per-market outlier discovery.
- `tests/market-consensus.service.test.ts`: New unit tests covering phase-2 spreads consensus calculations and outlier identification behavior.

---

## [0.3.10] - 2026-05-14
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
-- AlterTable
ALTER TABLE "market_consensus"
ADD COLUMN "consensus_price" INTEGER NOT NULL DEFAULT 0,
ADD COLUMN "median_line" DECIMAL(8,2) NOT NULL DEFAULT 0,
ADD COLUMN "mean_line" DECIMAL(8,2) NOT NULL DEFAULT 0,
ADD COLUMN "mode_line" DECIMAL(8,2),
ADD COLUMN "range" DECIMAL(8,2) NOT NULL DEFAULT 0,
ADD COLUMN "interquartile_range" DECIMAL(8,2) NOT NULL DEFAULT 0,
ADD COLUMN "best_value_side" VARCHAR(20) NOT NULL DEFAULT 'home',
ADD COLUMN "best_value_bookmaker" VARCHAR(50) NOT NULL DEFAULT '',
ADD COLUMN "best_value_line" DECIMAL(8,2) NOT NULL DEFAULT 0,
ADD COLUMN "sharp_book_weight" DECIMAL(5,2) NOT NULL DEFAULT 0;

-- Backfill new columns from existing values where possible
UPDATE "market_consensus"
SET
"consensus_price" = CASE
WHEN "market_type" = 'h2h' THEN ROUND("consensus_line")::INTEGER
ELSE 0
END,
"median_line" = "consensus_line",
"mean_line" = "consensus_line",
"range" = 0,
"interquartile_range" = 0,
"best_value_side" = COALESCE("best_value"->>'side', 'home'),
"best_value_bookmaker" = COALESCE("best_value"->>'bookmaker', ''),
"best_value_line" = COALESCE(NULLIF("best_value"->>'line', '')::DECIMAL, "consensus_line"),
"sharp_book_weight" = 0;

-- CreateIndex
CREATE INDEX "market_consensus_market_type_idx" ON "market_consensus"("market_type");
15 changes: 12 additions & 3 deletions dashboard/backend/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -178,17 +178,26 @@ model MarketConsensus {

// Consensus metrics
consensusLine Decimal @map("consensus_line") @db.Decimal(8, 2)
consensusPrice Int @map("consensus_price")
medianLine Decimal @map("median_line") @db.Decimal(8, 2)
meanLine Decimal @map("mean_line") @db.Decimal(8, 2)
modeLine Decimal? @map("mode_line") @db.Decimal(8, 2)
standardDeviation Decimal @map("standard_deviation") @db.Decimal(8, 2)
range Decimal @db.Decimal(8, 2)
interquartileRange Decimal @map("interquartile_range") @db.Decimal(8, 2)
outlierBookmakers Json @map("outlier_bookmakers") // [{bookmaker, line, deviation}]
bestValueSide String @map("best_value_side") @db.VarChar(20)
bestValueBookmaker String @map("best_value_bookmaker") @db.VarChar(50)
bestValueLine Decimal @map("best_value_line") @db.Decimal(8, 2)
bookmakerCount Int @map("bookmaker_count")

// Value indicators
sharpBookWeight Decimal @map("sharp_book_weight") @db.Decimal(5, 2)
disagreementScore Int @map("disagreement_score") // 1-100
bestValue Json @map("best_value") // {side, bookmaker, line, impliedProb}
bestValue Json @map("best_value") // compatibility payload

game Game @relation(fields: [gameId], references: [id], onDelete: Cascade)

@@index([gameId])
@@index([marketType])
@@index([disagreementScore])
@@index([calculatedAt])
@@map("market_consensus")
Expand Down
Loading
Loading