# I built my girlfriend a local Gemma football analyst that won't make up the stats

> Source: <https://dev.to/darrkkens/i-built-my-girlfriend-a-local-gemma-football-analyst-that-wont-make-up-the-stats-4mb7>
> Published: 2026-10-03 02:02:04+00:00

*This is a submission for the [Hacktoberfest Weekend Challenge: Build for a Friend](https://dev.to/challenges/hacktoberfest-weekend-2026-10-01).*

**MatchMind** is a local AI analyst for Brazilian football, built for **Bruna Boaventura, my girlfriend**.

Bruna had a hard time finding Brazilian clubs' results and historical data. So I built her one place to look things up — and a way to just ask.

MatchMind puts the numbers together and lets her ask questions in Portuguese to an AI that runs **on her own computer**. You pick a club and get:

The rule behind everything: **the numbers come from data or from deterministic code, never from the model.** If something is missing, MatchMind says "Indisponível" instead of guessing.

A real answer generated locally by Gemma 3 4B (CPU only):

Line-ups for a match, loaded on demand:

Série A history:

*"Interessante, pois traz dados históricos de 2003 até hoje, contendo gols e estatísticas."*

("Interesting, because it brings historical data from 2003 until today, with goals and statistics.")

That was exactly the gap she had: the history is what made it useful for her. (To be precise about coverage: the historical dataset runs from 2003 to 2024, and the current 2026 season comes from a separate live source.)

**Your local AI analyst for Brazilian football.** · *Seu analista de futebol brasileiro com IA local.*

MatchMind is a Brasileirão dashboard with a Vue 3 interface (in Brazilian Portuguese), a Go REST API and an **open-weight model (Gemma) running locally through Ollama**. Pick a club and see the league table, recent form, the last matches with statistics, scorers, cards and lineups, 20+ seasons of Série A history, and ask an AI analyst questions that are answered only from that data.

`FATO` from `INTERPRETAÇÃO` and must say when data is missing.`pgx` for an optional PostgreSQL cache).`/api/chat` with structured JSON output.`go vet`, `go test -race` and the frontend build, plus contributing guide and issue templates.
Quick start: `ollama pull gemma3:4b`, then `go run ./cmd/server` in `backend/` and `npm run dev` in `frontend/`. No account and no API key are needed.

```
OpenFootball (results, CC0)  ──┐
Série A history CSVs (GPL-2.0) ┼─► Go API ─► deterministic stats, table, form
Optional stats API + cache  ───┘      │
                                      ▼
                     compact JSON context for this question
                                      ▼
                        Gemma 3 4B via Ollama (local)
                                      ▼
            Go validates the JSON and adds FATO / INTERPRETAÇÃO
```

**Data I can trust.** Results come from [OpenFootball](https://github.com/openfootball/football.json) (CC0). The 2003–2024 history comes from Adão Duque's [Brasileirão dataset](https://github.com/adaoduque/Brasileirao_Dataset), downloaded at runtime. I computed every season's final table from the results, and the champions match the official list for all 22 seasons; the scorers file matches the final score in 100% of matches since 2015. Where the source has gaps (its 2016 and 2024 statistics are mostly zeros), MatchMind drops those averages instead of showing fake numbers.

**Linking sources without guessing.** Per-match statistics, scorers and line-ups come from an optional API. A match only receives that data when round, opponent, home/away side and final score all agree with OpenFootball. Line-ups abbreviate names ("G. Gómez") while player stats use full names ("Gustavo Gómez"), so I wrote a matcher that understands initials. It links 95% of starters; the rest (nickname vs. legal name) are shown without numbers rather than guessed. Every API response is cached — in PostgreSQL if you run `docker compose up` — so a finished match costs one request, ever.

**Grounding the model.** The browser never sends context. For each question, Go rebuilds the club snapshot and sends Gemma a JSON envelope that keeps the data separate from the question and is explicitly treated as untrusted. Gemma returns `facts`, `interpretation` and `sources_used`; Go validates the structure and the allowed source names and adds the labels itself.

**What real testing taught me.** Mocked tests passed, but running the real model exposed two problems:

`serie_a_titles_2003_2024_only`. The next answer said "4 Série A titles between 2003 and 2024" — the answer you see in the screenshot above. When a rule really matters (like flagging sample data from an API test key), Go appends the warning itself instead of trusting the model.`ollama pull gemma3:4b`, there's no per-question fee, no AI account and no key to protect. The football data sources used by default are open too.`gemma3:12b` on a stronger machine with one environment variable.
I built MatchMind with the help of an AI coding assistant (Claude Code), which I directed, reviewed and tested throughout. All data, screenshots and the model answer shown above come from running the real application.
