Entering Results & Calculating Correct Score vs. Correct Result
Premier League Predictor: FastAPI & PostgreSQL
Chapter 6 · Entering Results & Calculating Correct Score vs. Correct Result
Every fixture since Chapter 4 has sat with home_score/away_score null. Every prediction since Chapter 5 has sat unscored. This chapter fills in the real result and, at the same moment, scores everything that was predicted against it — with real, confirmed point values: 40 points for a correct score, 10 points for a correct result.
A Pure Scoring Function, Shared by Every Source
User, expert, AI, and every individual guest are all scored by the exact same rule — there's no special case per source, because the rule genuinely doesn't need one:
The exact-match check runs first, deliberately: as Chapter 1's own first exercise established, every correct score is automatically a correct result too, so checking the stronger condition first and returning immediately is what stops a 2-1 correctly predicted 2-1 from ever being scored as a mere 10-point correct result instead of the full 40.
Storing Points on the Prediction Itself
Extending Chapter 5's own Prediction model with one more column:
points_awarded directly on each Prediction row the moment a result is entered turns that later aggregation into a plain SUM(points_awarded) — exactly the kind of real PostgreSQL aggregate query Chapter 1 picked this stack for — rather than re-running score_prediction() against every row on every single page load.
Entering a Result
PATCH, not PUT — this route changes two specific fields and derives a third (status), it doesn't replace the whole fixture, which is exactly what PATCH is for.
enter_result loops over every prediction on the fixture and recomputes points_awarded from scratch, every single time it's called — not just the first time. That means fixing a mistyped scoreline (2-1 entered when the real result was 2-2) genuinely re-scores every user, expert, guest, and AI prediction correctly against the corrected result, rather than leaving stale points from the wrong scoreline sitting in the database. A fixture with zero predictions recorded simply loops over an empty list and does nothing extra — no special case needed.
Resolving Chapter 5's Deferred Question: Average the Points, Not the Scorelines
Chapter 5 left one thing open: multiple guests predicting the same fixture produce multiple real scorelines that don't average into another valid scoreline — 2-1 and 3-0 average to 2.5-0.5, which isn't a result anything could actually finish. This chapter resolves it, and the fix is simpler than it first looks: don't average the scorelines at all — score each guest individually, exactly like every other source, then average the resulting points.
Prediction row gets its own real points_awarded from the identical score_prediction() function used for user, expert, and AI — 40, 10, or 0, same as anyone else. A fixture with three guests that week now has three separate, real point values (say, 40, 10, and 0). Averaging those three numbers into one figure — 16.67 — is completely well-defined, in a way averaging their three original scorelines never could be. Chapter 8's own prediction league table is where this average actually gets computed and folded into the guest predictor's own season total, one averaged figure per fixture rather than one raw contribution per individual guest — deliberately, so a fixture with three guests doesn't count three times as much toward the season total as a fixture with only one.
Predictions Now Include Their Own Points
Extending Chapter 5's own PredictionResponse with the new field:
GET /api/fixtures/42/predictions after a 2-1 result is entered, reusing the same five predictions from Chapter 5's own worked example:
The real result was 2-1, a home win. The user's exact 2-1 earns the full 40. The expert's 1-1 is a draw — a different outcome from the real home win, so it earns nothing despite being close on paper. Both guests and the AI predicted a home win without the exact score (3-0, 1-0, and 2-0 respectively), so each earns 10. The guest average for this fixture is therefore (10 + 10) / 2 = 10, the figure Chapter 8 would fold into the guest predictor's own season total for this one fixture.
Where This Course Is Headed
The real league table — points, goal difference, wins/draws/losses, built from every fixture this chapter has now filled in a real result for (Chapter 7); the prediction league table, aggregating every source's own points_awarded across the season, including the guest-averaging-by-fixture this chapter just resolved (Chapter 8); and promotion/relegation (Chapter 9).
Hands-On Exercises
Explain why score_prediction checks for an exact scoreline match before checking the outcome, and what would go wrong (in terms of points actually awarded) if the outcome check ran first instead.
📄 View solutionExplain why this chapter resolves the guest-averaging problem by averaging each guest's own points rather than averaging their raw predicted scorelines, using a concrete example of two guest scorelines that cannot be meaningfully averaged directly.
📄 View solutionEnter an incorrect result for a real fixture with at least two predictions already recorded, confirm their points_awarded values, then call PATCH again with the corrected result and confirm every prediction's points_awarded actually changed to reflect the correction.
📄 View solutionChapter 6 Quick Reference
- Real point values (confirmed) — 40 points for a correct score, 10 points for a correct result, 0 otherwise
- score_prediction() — one pure function, shared by every source, exact-match checked before outcome
- Prediction.points_awarded — stored on the row itself, null until a result exists, so Chapter 8's own aggregation is a plain SUM
- PATCH /api/fixtures/{id}/result — fills in the real scoreline, flips status to "played," and rescores every prediction on the fixture from scratch
- Correcting a result — genuinely re-scores everything, not just the first entry
- Guest averaging, resolved — average each guest's own points per fixture, not their raw scorelines, which don't average into anything valid
- Next chapter: The real league table — points, goal difference, wins/draws/losses