- Personal project
- Solo project: full stack, evolved over several years
- 2021 - 2026
Bollette Calcio
A virtual football betting app that grew for five years, from an Express backend to NestJS, poker and an AI assistant.

- React
- TypeScript
- Vite
- Redux Toolkit
- NestJS
- Express
- MySQL
- Redis
- XState
- Flask
- LangChain
- TensorFlow
- Docker
The problem
A personal project started in January 2021: a web app for betting on football matches with virtual money, with a ranking of users, and later slot machines and multiplayer poker. It stayed alive for about five years and is split across six repositories, so it is mostly a story of how the architecture changed.
What I built
v1 (2021-2022): an Express, MySQL and Redis API with a React UI. Bets were settled by a Python scraper spawned as a child process, with tokens stored in Redis.
v2 (2023 onwards): the scraper became a separate Flask microservice and the backend was rewritten in NestJS with TypeORM, JWT and Swagger. Poker runs over WebSockets, with the table modelled as an XState state machine, bots and an event log.
Odds model: a neural network with team embeddings that generates Serie A odds, so the app does not depend on a paid odds API.
Agent (2025 onwards): an assistant that fills the betting slip from sentences like "checkout 20". The UI moved to Vite, React 19 and Tailwind 4.
Interesting decisions
The agent went through two designs. The first, built with LangChain and OpenAI, was a pipeline of specialised steps: intent detection, team extraction with a structured parser, fuzzy match of the fixture with embeddings and a similarity threshold, then outcome and amount. The second, from October 2026, replaces prompts and embeddings with typed questions (choices and yes/no) asked in parallel to a decision model. It returns probabilities rather than text, so replies are templates. It uses confidence thresholds and falls back to the first extractor when unsure.
The betting slip is shown in the UI and the checkout stays an explicit action by the user, so the human confirms before money moves, even virtual.
What I would do differently today
The first version coupled the backend to a scraper spawned as a child process, which is why I extracted it into its own service. Scraping third-party sites remains fragile by nature.
The agent keeps the betting slip in memory, so it is lost on restart. I documented this but did not fix it.
There is no systematic evaluation of the agent, and the confidence thresholds of the new version are an initial guess, not tuned. Moving from prompts to typed decisions was motivated by reliability, cost and ease of debugging, but I have not measured the difference.
The odds model only uses the two teams as input, with no form or injury features, and I have no accuracy metrics for it.
Need something similar?
Tell me about your project: I usually reply within one working day.
Get in touch