2026-09-17 21:45:57 +02:00
2026-09-17 21:45:57 +02:00
2026-09-17 21:45:57 +02:00
2026-09-17 21:45:57 +02:00
2026-09-17 21:45:57 +02:00
2026-09-17 21:45:57 +02:00

Bookie — Football Stats Analyzer

A full-stack application over the PostgreSQL bookie schema:

  • BookieApi/ — ASP.NET Core (.NET 10) Web API, layered architecture, EF Core (Npgsql, database-first), JWT auth, Swagger.
  • BookieClient/ — Angular 22 (standalone components, signals) + Angular Material, Chart.js, jsPDF.

The headline feature is the Pre-Match Report, a faithful port + extension of the original report.py analyzer exposed as a service, an API endpoint, and a rich Angular page.


1. Architecture

Backend (BookieApi) — layered

src/
├─ Bookie.Domain          # POCO entities mirroring the bookie schema
├─ Bookie.Application     # DTOs, service interfaces (no persistence deps)
├─ Bookie.Infrastructure  # EF Core DbContext, EF configs, service impls, JWT
└─ Bookie.Api             # Controllers, Program.cs, Swagger, JWT wiring
  • DbContext: BookieDbContext maps every table/column explicitly to snake_case names and uses modelBuilder.HasDefaultSchema("bookie").
  • Repository/Service pattern: each entity has a service (ILeagueService, IMatchService, …) implemented in Infrastructure; the API depends only on the interfaces.
  • DTOs: read/create/update DTOs with DataAnnotations validation; server-side paging/sorting/filtering via PagedQuery / PagedResult<T>.

Frontend (BookieClient)

src/app/
├─ core/            # models, AuthService (signals), HTTP interceptor, guards, ApiService
├─ layout/          # ShellComponent (toolbar + sidenav)
├─ shared/          # confirm dialog
└─ features/
   ├─ auth/         # login page
   ├─ dashboard/    # summary widgets + Chart.js status doughnut
   ├─ pre-match/    # PRIORITY: Pre-Match Reports page (+ CSV/PDF export)
   └─ crud/         # metadata-driven CRUD page + form dialog (all 6 entities)

2. Prerequisites

  • .NET SDK 10.x
  • Node.js 20+ and npm
  • Access to the PostgreSQL database that contains the bookie schema

3. Configure & run the backend

Edit BookieApi/src/Bookie.Api/appsettings.json:

"ConnectionStrings": {
  // Same database the report.py script used (DB "modwad", schema "bookie").
  "BookieDb": "Host=YOUR_HOST;Port=5432;Database=modwad;Username=YOUR_USER;Password=YOUR_PASSWORD;Search Path=bookie"
},
"Jwt": {
  "Secret": "replace-with-a-long-random-secret-at-least-32-chars"
},
"OpenAI": {
  // Required only for the AI Match Prediction feature. Model must support structured outputs.
  "ApiKey": "sk-...your-key...",
  "Model": "gpt-5.6-luna"
}

Tip: keep secrets out of source control with user-secrets: dotnet user-secrets set "ConnectionStrings:BookieDb" "..." (run in the Bookie.Api project). The OpenAI key can be supplied the same way (dotnet user-secrets set "OpenAI:ApiKey" "sk-...") or via the OPENAI_API_KEY environment variable, which is used automatically when OpenAI:ApiKey is blank.

Run:

cd BookieApi
dotnet restore
dotnet run --project src/Bookie.Api --urls http://localhost:5210

Demo accounts (in-memory, configured in appsettings.jsonAuthUsers)

Username Password Role
admin admin123 admin
user user123 user

admin can create/update/delete; user has read-only access. (The bookie schema has no users table, so auth is a simple configurable in-memory list.)


4. Run the frontend

cd BookieClient
npm install
npm start        # ng serve on http://localhost:4200

The client points to http://localhost:5210/api (see src/environments/environment.ts). CORS on the API allows http://localhost:4200 by default (configurable via Cors:AllowedOrigins).


5. The Pre-Match Report feature

Endpoint: GET /api/reports/pre-match?date=YYYY-MM-DD (defaults to today).

Returns all matches on the date (any status) grouped by league / country / season, ordered by country, league name, then kickoff. For each match, and for both teams, it computes stats using only matches strictly before that match's kickoff_at, within the same season, excluding the match itself — mirroring the Python exclude_match_id + kickoff_at < before_date filters exactly:

  • Season averages (from match_team_stats): possession, shots, shots on target, corners, fouls, offsides (1 dp), yellow/red cards (2 dp).
  • Goals for / against per match — from finished matches only (2 dp).
  • Form — last 5 finished matches before kickoff, oldest → newest, as W / D / L. (The Python script uses W/R/P; the API normalizes to W/D/L per the DTO contract.)
  • Top 5 scorers — goals + assists for the team, counted from matches before this one.
  • Graceful empty state: when a team has no prior matches, hasHistory=false and the sub-objects report hasData=false (no errors).

Efficiency: rather than 4 queries per team per match (N+1), the service loads all season-scoped matches/stats/goals that could be relevant in a handful of set-based EF Core queries, then applies the exact per-match before/exclude filter in memory.

Angular page (/pre-match): date picker (default today), collapsible league/season sections, per-match cards (kickoff, teams, status badge, score, stadium/referee), two side-by-side team panels (goals, season averages grid, form as colored chips, top-5 scorers table), loading skeletons, empty/error states, and CSV + PDF export.

Extensions:

  • History depth selector?seasonsBack=N widens the window to include the current season plus up to N previous seasons of the same league; defaults to 0 (current season only). Each match reports the seasonsIncluded names.
  • Head-to-headGET /api/reports/head-to-head?teamAId=&teamBId=&beforeMatchId= returns previous finished meetings between the two teams (with a W/D/W summary and goal aggregate). A Head-to-head button on each card opens a dialog; each meeting drills into full match stats.
  • Clickable form chips — hovering a W/D/L chip shows the fixture and score; clicking opens that match's full stats.

6. AI Match Prediction

Endpoint: POST /api/predictions/match/{matchId}.

The PredictionService gathers structured historical data for the fixture — season overall averages, home/away venue splits, last-5 recent form with underlying stats, and cross-season head-to-head — then sends it to an OpenAI chat model with a dedicated "Football Match Statistics Predictor" system prompt. It uses structured outputs (JSON schema) so the response is a typed projection: per-team expected goals, shots on target, corners, fouls, yellow and red cards (each a point estimate plus low/high range), a reasoning paragraph, a High/Medium/Low confidence label, and data-quality flags.

Configure OpenAI:ApiKey (or the OPENAI_API_KEY env var) and optionally OpenAI:Model (default gpt-5.6-luna). In the UI, a Predict button on each match card opens the projection dialog.

These are statistical estimates for analytical purposes only — not guaranteed outcomes, and the feature offers no betting guidance.


7. Standard features

  • CRUD (list with paging/sorting/filtering + create/edit/delete dialogs) for: leagues, seasons, teams, players, matches, player contracts. Writes require the admin role.
  • Dashboard: counts of leagues/seasons/teams/players/matches, upcoming matches in the next 7 days, a matches-by-status doughnut chart, and next fixtures.
  • JWT auth with admin / user roles, enforced on the API and reflected in the UI (action buttons only for admins).

8. Assumptions

  • Database-first without live scaffolding: entities were written by hand to exactly match schema_bookie.sql (no DB connection was available at build time). To re-scaffold instead, run: dotnet ef dbcontext scaffold "<conn>" Npgsql.EntityFrameworkCore.PostgreSQL --schema bookie --output-dir Entities from the Infrastructure project.
  • Target framework: .NET 10 (the current LTS as of this build).
  • Auth users live in configuration because the schema has no users/roles tables.
  • updated_at / timestamps: the match timestamps are timestamp without time zone; the API writes them with Unspecified kind to satisfy Npgsql.
  • The API does not manage matchday as a full CRUD resource (not requested); it is exposed as a lookup so matches can be created/edited.
Description
No description provided
Readme 4.4 MiB
Languages
C# 51.7%
TypeScript 30%
HTML 12.4%
SCSS 5.9%