Skip to content

IBM-SkillsBuild-AI-Builders-Challenge/same-whistle

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Same Whistle

Did the referee call it the same for both teams? An explainable refereeing consistency audit.

Powered by IBM watsonx IBM Granite Docling Streamlit Python

Demo

Same Whistle demo

Watch the three minute walkthrough: https://www.youtube.com/watch?v=G7OMbz096bI

Overview

Same Whistle answers one question about any World Cup match: did the referee call it the same way for both teams across the whole match. It never makes the call itself. It reconstructs the pattern of fouls and cards from match data, grounds every verdict in the actual Laws of the Game, and hands the fan the evidence to trust or question a decision. It was built for the IBM SkillsBuild challenge, theme Soccer, AI, World Cup, in the Trust and Transparency area, which names refereeing consistency analyzers by name.

A booking looks unjust until you see the three identical situations that were also carded, or the identical one that was not. Broadcasters show the contested moment but never the pattern. Same Whistle shows the pattern. It does not say what the correct decision was. It surfaces whether the referee applied their own threshold evenly, and it always shows what the data cannot see.

The problem

Fans cannot tell whether a controversial call was a one off or consistent with how the whole match was officiated. Distrust grows in that gap. A single replay of one foul, shown without context, fuels the feeling that a team was robbed. The World Cup multiplies this across the fans of every nation, most of whom do not know the Laws of the Game in detail.

How it works

Same Whistle is explainable by construction. There is no black box. Every verdict is three things put together:

  1. A comparison set of real incidents from the same match, the situations that were genuinely comparable to the one in question.
  2. A citation to the governing Law 12 text, parsed from the official IFAB PDF.
  3. Explicit caveats that name what the data cannot see.

If a claim cannot be grounded in those three, the engine does not make it. When the comparable sample is too thin it says so and declines to judge.

Physical foul type is not in the data. The engine never infers trip versus push versus hold. Comparability is defined on context, not mechanism. For every Foul Committed and Bad Behaviour event the engine derives:

  • Zone. The pitch third relative to the fouling team own goal, and the channel across the width. StatsBomb records each foul from the fouling team attacking left to right, so the defensive third is always at x near 0. This was confirmed against the demo match files.
  • Attacking phase. Did the foul interrupt a promising attack by the opponent. Derived from possession and play pattern, reinforced by the 360 view when available.
  • Game state. The score band at that minute and the phase of the match.
  • Player foul count. The cumulative fouls by that player up to that minute. This is exact, which is why it carries the most defensible signal.

For a selected incident the engine builds its comparison set, the other incidents that share the same pitch zone, attacking phase status, and game state band. When that exact set is too thin to judge, it widens to pitch zone and attacking phase and says it did so. Then it computes the comparable count and the carded rate, a consistency verdict of consistent, harsher than comparable, more lenient than comparable, or insufficient data, and a persistent infringement check that uses only exact foul counts and never claims a card was given for persistent infringement, since the data never states a card reason.

There is also a match level overview with per team fouls, cards, and the fouls per card ratio, plus a card asymmetry read. Within matched situations, the fouls that share the same pitch zone, attacking phase, and game state band for both teams, it compares each team carded rate and states the relative likelihood. For example, in comparable situations one team fouls were carded some multiple as often as the other team fouls. This describes the outcome pattern only. It never measures intent or fairness, it is never a label of the referee, and it declines to speak when the matched sample is too small.

What this looks like on the hero match. In Netherlands 2-2 Argentina, the most carded match in World Cup history, the four cards that sat on a foul event with a pitch location all belong to one team, and each was shown where the comparable located fouls, including the other team fouls, were mostly not carded. This is the on-ball, location-attached picture only. The same match had twelve off-ball Bad Behaviour bookings, which Same Whistle keeps as a separate misconduct track, and the two teams finished level on cards, eight each. So this is not a claim that the referee only carded one side. It is a claim about the located foul cards specifically. Meanwhile one player committed six fouls without any card. Same Whistle surfaces all of this with the evidence attached, and still reminds you what the data cannot see.

The IBM stack

Tool What it does here
IBM Granite on watsonx.ai Writes the plain language verdicts. See src/reason.py.
Docling Parses the IFAB Laws of the Game PDF into Law 12 sections. See src/laws.py.
Granite Embedding Retrieves the governing Law 12 section, model free at runtime. See src/laws.py.
LangChain Orchestrates the pipeline as a chain that mirrors the code stages. See src/pipeline.py.

The data is StatsBomb open event and 360 data. An optional LangFlow graph at flows/same_whistle.langflow.json mirrors the pipeline for the demo video. A local ollama path can stand in for watsonx during offline development.

Same Whistle is powered by IBM watsonx and built with these tools. The badges and names credit the technology used. They do not imply that IBM endorses this project.

Why it matters for the World Cup

This turns the referee robbed us into something a fan can actually inspect. It replaces a feeling with a comparison set, a Law citation, and an honest list of caveats. It does that in plain language and in more than one language, so the trust it builds can scale to a global audience. It respects the human at the center. It audits consistency, it does not replace the official.

What it is not

  • It does not predict outcomes.
  • It does not replace or grade the referee. It audits whether the referee own threshold was applied evenly.
  • It does not classify a foul type the data cannot see. It never infers the mechanism of contact.

Limitations and what the data cannot see

  • Intent is not in the data, so motive is never assumed.
  • Contact severity and body part are not in the data.
  • There is no replay angle. The engine sees positions and outcomes, not video.
  • Comparison sets in a single match are often small. The engine scales its confidence to the comparable count and declines to judge when the sample is too thin.
  • The 360 view is partial. Counts come from the visible area only, so it is used as an enhancement and never as a dependency.
  • A difference in how two teams were carded can come from a different threshold, or from genuinely different fouls. The data cannot separate the two. The engine presents the gap as a prompt to look closer, not as a judgment.

Same Whistle is a fan facing trust audit. The prompt that drives Granite forbids it from stating the correct decision or grading the referee authority. The same rule runs through the engine and this document. The goal is to make refereeing legible to fans, not to officiate.

Run it locally

Recommended runtime is Python 3.11 or 3.12, where every wheel installs cleanly. The core engine and its tests run on Python 3.10 or newer.

pip install -r requirements.txt

# 1. Download and cache the configured matches. This also audits the extraction
#    against the validated foul and card counts.
python -m scripts.download_data

# 2. Optional but recommended for full law grounding. Download the IFAB Laws of
#    the Game PDF from theifab.com, place it here, then build the law index. This
#    embeds the sections and the pattern queries once and commits the vectors, so
#    the app loads no embedding model at runtime.
#    data/laws/laws_of_the_game.pdf
python -m src.laws build

# 3. Optional. Configure Granite for live narratives. Without this the pipeline
#    still runs and uses a deterministic offline narrative.
cp .env.example .env   # then fill in watsonx or set GRANITE_PROVIDER=ollama

# 4. Run the app.
python3 -m streamlit run app/streamlit_app.py

# Or emit a Verdict as JSON from the command line.
python -m src.pipeline 3869321

# Run the full submission verification at any time.
python -m scripts.verify_submission

Everything runs offline after step 1. The law citation is marked unavailable until the PDF is parsed, and narratives use the offline fallback until Granite is configured. The comparability analysis, the persistent infringement check, and the match overview need none of that and work immediately.

Configuration

All configuration lives in .env. See .env.example for the full list.

  • GRANITE_PROVIDER selects watsonx or ollama.
  • watsonx needs WATSONX_API_KEY, WATSONX_PROJECT_ID, WATSONX_URL, and GRANITE_MODEL_ID. The model id is env driven so no version is hardcoded.
  • ollama needs only GRANITE_MODEL_ID, for example granite3.3:8b.
  • GRANITE_EMBEDDING_MODEL selects the Granite Embedding model. The default is multilingual so the Spanish, French, Arabic, and Portuguese outputs work.
  • OUTPUT_LANGUAGE sets the default verdict language.

The matches

Five 2022 World Cup matches ship configured. The Argentina set spans four rounds under four referees, which is a deliberate angle for the demo.

  • 3869321 Netherlands 2-2 Argentina, quarter-final, referee Antonio Mateu Lahoz. The most carded match in World Cup history. This is the centerpiece.
  • 3869685 Argentina 3-3 France, final, referee Szymon Marciniak. A cleaner contrast with fewer cards.
  • 3869354 England 1-2 France, quarter-final, referee Wilton Sampaio.
  • 3869519 Argentina 3-0 Croatia, semi-final, referee Daniele Orsato.
  • 3869151 Argentina 2-1 Australia, round of 16, referee Szymon Marciniak.

Repository structure

same-whistle/
  src/
    ingest.py            fetch and cache StatsBomb data
    features.py          zone, phase, game state, foul counts
    comparability.py     comparison sets, consistency, persistent infringement, card asymmetry
    laws.py              Docling parse and Granite Embedding, model free at runtime
    reason.py            Granite provider abstraction and the structured verdict
    pipeline.py          LangChain orchestration
    schema.py            pydantic models, the output contract
    teams.py             team flags, colors, and marquee names
    config.py            paths, source urls, match registry
  app/
    streamlit_app.py     the UI, a broadcast styled dashboard
    pitch.py             mplsoccer rendering
  flows/
    same_whistle.langflow.json   optional, for the video
  scripts/
    download_data.py
    verify_submission.py
  tests/                 features, comparability, laws, pipeline, teams
  data/
    laws/                user provided IFAB PDF goes here
    cache/               StatsBomb json, plus the committed redistribution safe law index

Tests

python -m pytest

The tests cover the pure feature helpers, the full feature build against the cached hero match, every branch of the consistency verdict, the persistent infringement check, the card asymmetry metric, the law parsing and retrieval math, the team identity data, and the end to end pipeline.

Deploy

The app is sized for a 1 GB host. At runtime it loads no embedding model. The law index, the section vectors, and the pattern query vectors are built once with python -m src.laws build and committed, so a cold start needs no PDF and no sentence-transformers download. Retrieval at runtime is a cached cosine lookup.

For a hosted deploy:

  • Provide the watsonx key through the host secret store, never in the repo. The app reads .env only for local runs and never overrides a real environment variable, so host injected secrets win.
  • Commit the redistribution safe law index, data/cache/law12_index.json and data/cache/law12_query_vectors.json. These carry section titles, the embeddings, and one short snippet per section, not the full Laws text.
  • The StatsBomb cache, the IFAB PDF, and the full Law text file data/cache/law12_fulltext.json stay gitignored.

python -m scripts.verify_submission reports the live and static readiness as a PASS, FAIL, SKIPPED punch list.

Data and law attribution

Source data is StatsBomb open data, fetched as raw JSON and cached to data/cache. The extraction is checked on download. The validated counts that the audit confirms include 3869321 with 59 Foul Committed, 57 Foul Won, 12 Bad Behaviour, 15 Yellow plus 1 Second Yellow, and 3869685 with 48 Foul Committed, 44 Foul Won, 2 Bad Behaviour, 8 Yellow. Cards are read from foul_committed.card on Foul Committed events and from bad_behaviour.card on Bad Behaviour events. Bad Behaviour events carry no location, so they are kept as a separate off-ball misconduct track rather than forced into a zone comparison.

The Laws of the Game are the property of The International Football Association Board. IFAB does not permit reproducing the Laws in whole or in part without permission. Same Whistle respects that.

  • The full Law 12 text is never committed and is not redistributed here. It is parsed locally from a PDF you provide and kept in a gitignored file.
  • The committable index ships only section titles, the section embeddings, and a single short quoted snippet per section capped at 25 words, with the credit "IFAB Laws of the Game, Law 12" and the official source link.
  • To get the full Laws, download them from IFAB at https://www.theifab.com/laws-of-the-game-documents/ and place the PDF at data/laws/laws_of_the_game.pdf.

Powered by IBM watsonx

Data: StatsBomb open data. Laws: Laws of the Game, The International Football Association Board (IFAB).

About

an explainable refereeing consistency audit, powered by IBM watsonx

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages