DEV Community

一郎 支
一郎 支

Posted on

I Built an Offline Match Analyst That Turns Event Data Into Evidence-Cited Tactics

A football match produces about 2,000 events in 90 minutes. A pundit watches it once, forms an impression, and talks for an hour. Ask them why their team lost and you get narrative: "they wanted it more", "the ref changed it", "we were unlucky".

Last month I watched a match that finished 3-1 and thought the loser was battered. Then I pulled the event data: the losing team had more expected goals (xG 1.54 vs 1.81 - close), created the better chances, and only collapsed after a 63rd-minute red card. The scoreline told one story. The data told a completely different one - and nobody watching could see it.

MatchLens is the tool that closes that gap: an offline, zero-dependency match analyst that turns raw event CSVs into an evidence-cited tactical report.

What MatchLens does

You export match events (minute, team, type, player, xG) and team info to CSV. MatchLens produces:

  • xG comparison - who actually created what, not who scored
  • Key moment timeline - every goal, penalty, red and yellow card with the live scoreline attached, so you see the swing points
  • Six insight types - CLINICAL_FINISHING (outscoring your xG), WASTEFUL_ATTACK (chances without end product), MAN_DOWN_EFFECT (performance before vs after a red card), SUB_IMPACT (did the changes work), MOMENTUM_PHASE (which 15-minute block each team owned), DISCIPLINE (cards, fouls)
  • A natural-language match report a coach could read in two minutes
  • Follow-up analysis suggestions - what to look at next

Every insight carries evidence citations: the event rows and numbers it used. No vibes, no narrative hand-waving.

A real run

$ python -m matchlens analyze examples/match_events.csv examples/teams.csv

MATCH: Blue Lions 3-1 Red Hawks
xG:    1.81 - 1.54   (scoreline flatters the winners)

KEY MOMENTS
  23' GOAL  Blue Lions 1-0 (K. Sato, xG 0.62)
  41' RED   Red Hawks - D. Okafor (2nd yellow)          score stays 1-0
  58' PEN   Red Hawks 1-1 from the spot (xG 0.76)
  71' GOAL  Blue Lions 2-1 (counter after save, xG 0.31)
  88' GOAL  Blue Lions 3-1 (xG 0.09 - cleanup, game over)

INSIGHTS
  [MAN_DOWN_EFFECT] Red Hawks xG 1.39 -> 0.15 after the red card
       evidence: events 1-31 (pre) vs 32-58 (post); red card at 41'
  [CLINICAL_FINISHING] Blue Lions scored 3 from 1.81 xG
       evidence: goal minutes 23/71/88 vs cumulative xG by minute
  [MOMENTUM_PHASE] Blue Lions owned 75-90 (xG 0.94 vs 0.08)
       evidence: 15-minute bins from the event xG column
  [WASTEFUL_ATTACK] Red Hawks 1.54 xG, 1 goal - chances without payoff
       evidence: 12 shots, 6 on target, xG 1.54

REPORT: A tight match turned by one red card. Before it, Red Hawks were
ahead on xG. After it, they managed 0.15 xG in 32 minutes. The 3-1
scoreline describes the card, not the football.
Enter fullscreen mode Exit fullscreen mode

The red-card finding is the one I care about: the tool splits the match at the dismissal and compares both halves on xG. "They were unlucky" becomes "they generated 1.39 xG with 11 men and 0.15 with 10" - a number you can argue with.

It ships with evals

$ python eval.py
PASS  xg_totals_match_event_data
PASS  scoreline_updates_on_every_goal
PASS  red_card_splits_the_match_timeline
PASS  stoppage_time_binned_into_90
PASS  momentum_phase_uses_15_minute_bins
PASS  sub_impact_compares_pre_post_minutes
PASS  every_insight_cites_event_rows
PASS  no_network_access
... 19/19 assertions passed.
Enter fullscreen mode Exit fullscreen mode

Building it caught three real bugs, all of the kind that make demo numbers quietly wrong: the scoreline wasn't initialized for the away team (so the first away goal rendered "1" instead of "1-0"), stoppage-time minutes like 90+3' overflowed the 15-minute bins, and one eval assertion had its own expected value wrong. The eval suite is what found them - not me reading the code.

Why offline matters

Event data for professional matches is licensed and expensive; for grassroots and academy football it's usually a coach's spreadsheet. MatchLens runs entirely on your machine - pure standard library, no accounts, no uploads. Your team's data stays yours.

Tech stack

  • Pure Python stdlib (csv, datetime, argparse) - clone and run
  • Deterministic rules with transparent thresholds - every number traceable
  • Natural-language report generation from computed facts

Try it

Repo: github.com/Zhiyilang/matchlens with a full example match (Blue Lions 3-1 Red Hawks, 58 events) and the 19-assertion eval suite. Run python eval.py, then point it at your own team's export.

I built MatchLens for the Microsoft x Premier League "Inside the Game" hackathon, but the use case is bigger than that: anyone who has ever argued about a match with data they couldn't check. The next time someone says a team "didn't deserve it", you'll have the xG split, the red-card before-and-after, and the minute-by-minute momentum bins to settle it.

What insight would you add? Mine is a candidate: set-piece xG share - how much of a team's chance creation comes from dead balls, which is the difference between a good team and a good set-piece coach.

Top comments (0)