Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/danielsl4/TFG_DAM_2526/llms.txt

Use this file to discover all available pages before exploring further.

FutsalManager builds the league table automatically from finalized group-stage match results. Every time a referee closes a match the standings cache is invalidated so the next request returns an up-to-date table. This page covers the data that makes up each row, how multiple groups are handled, the exact tie-breaking order, and how the system ranks best fourth-place teams for knockout advancement.

Standings data per team

Each row in the league table contains:
FieldDescription
pointsTotal competition points (win = 3, draw = 1, loss = 0)
playedNumber of group-stage matches completed
wonMatches won
drawnMatches drawn
lostMatches lost
goals_forGoals scored across all group matches
goals_againstGoals conceded across all group matches
goal_differencegoals_for − goals_against
yellow_cardsYellow cards received (used in fair-play tie-break)
red_cardsRed cards received (used in fair-play tie-break)
These values live in the team_stats table and are upserted atomically inside the same transaction that finalizes a match, so the database is always consistent even if the cache has not yet been refreshed.

Multi-group support

Seasons in FutsalManager can have any number of groups (Group A, Group B, etc.). The standings endpoint returns a groups object where each key is a group name and the value is the sorted array of team rows for that group. You can fetch standings for the active season or for any past season by passing season_id as a query parameter.
example response shape
{
  "groups": {
    "Grupo A": [ { "team_name": "...", "points": 9, ... }, ... ],
    "Grupo B": [ { "team_name": "...", "points": 7, ... }, ... ]
  },
  "bestFourthId": 42,
  "fourthPlacesRanking": [ ... ]
}

Tie-breaking algorithm

When two or more teams are level on points the backend applies the following criteria in order. The first criterion that produces a different result decides the ranking.
1

Points

Teams with more points rank higher. This is the primary sort key.
2

Head-to-head points

Among tied teams, a mini-league is built from only the matches played between those teams. The team with more points in those direct encounters ranks higher.
3

Goal difference

For a two-way tie, overall goal_difference is compared after head-to-head points. For a three-or-more-way tie, the goal difference within the mini-league is used.
4

Fair play (yellow and red cards)

The team with the lower fair-play score ranks higher. The fair-play score is calculated as:
fair_play = yellow_cards + (red_cards × 3)
A lower value is better — teams that commit fewer fouls advance.
5

Goals for

Total goals scored across all group matches (or mini-league matches for multi-way ties). More goals ranks higher.
6

Goals against

Total goals conceded. Fewer goals conceded ranks higher.
If all six criteria are equal the team with the lower database ID wins the tie. This is a deterministic fallback that prevents non-deterministic ordering in edge cases.

Best fourth-place ranking

In competitions where multiple groups each produce a fourth-place team, FutsalManager compares those fourth-place teams to determine which one advances to the knockout rounds. Because some groups may have more teams than others, the comparison would be unfair if results against bottom-ranked extra opponents were included. The algorithm works as follows:
  1. Identify the fourth-place finisher in each group.
  2. Find the smallest group size across all groups (minGroupSize).
  3. If a group has more teams than minGroupSize, subtract the results from the match against the last-placed team in that group before comparing fourth-place teams across groups.
  4. Apply the same tie-breaking algorithm to the adjusted fourth-place rows.
  5. The top-ranked fourth-place team (bestFourthId) is returned alongside the full fourthPlacesRanking array.
This normalization ensures that fourth-place teams from larger groups are not penalized or advantaged by their extra matches.

Caching

Standings are cached in Redis with a 1-hour TTL (CACHE_TTL = 3600 seconds). The cache is not expired by time alone — it is explicitly invalidated when a match is finalized, so the table always reflects the latest completed results without waiting for the TTL to lapse.
Pass season_id as a query parameter to retrieve standings for a historical season. Each season gets its own Redis cache key (standings:<season_id>), so past-season queries are also cached independently.
The standings endpoint only includes fase_de_grupos matches. Knockout-phase results do not appear in the league table.

Standings API endpoint

Query parameters, full response schema, and season filter usage.

Match finalization

How match results are written to team_stats and trigger cache invalidation.

Statistics overview

Season-wide aggregates, team rankings, and individual player rankings.

Admin: managing seasons

Create seasons, define groups, and control which season is active.

Build docs developers (and LLMs) love