Skip to content

feat(rankings): dynamic sort & filter by any metric - #6

Merged
YonatanHen merged 2 commits into
masterfrom
dev/rankings-dynamic-sort-filter
Jul 1, 2026
Merged

YonatanHen merged 2 commits into
masterfrom
dev/rankings-dynamic-sort-filter

Conversation

@YonatanHen

Copy link
Copy Markdown
Owner

Summary

Adds server-side dynamic sorting and numeric filtering to the player Rankings page. Previously the table was locked to sorting by s_final and only supported categorical filters (position/team/nationality/flag). Users can now order and filter the full paginated ranking by any typed metric (xG, xA, goals, assists, clean sheets, rating, saves, …).

Because the table is server-paginated, sort/filter must run in the backend over the whole result set — a client-side sort would only reorder the visible 50 rows.

Backend

  • app/domain/metric_fields.py (new) — single-source allowlist of sortable/filterable metrics (every Stats field + the 4 score fields), auto-derived from the Stats dataclass. Doubles as the injection guard and maps each metric to its Mongo path / PlayerDTO accessor.
  • GET /v1/players — sort_by now accepts any allowlisted metric (was locked to s_final); new filters param takes a JSON array of {field, op, value} clauses (gte/lte/gt/lt). Returns 422 on unknown field / bad op / non-numeric value.
  • get_players — generic Mongo sort + numeric filter clauses on the default path; the stats_view re-aggregation path applies the same filter/sort in Python after recomputing. Filter/sort always run before pagination.

Frontend

  • Sortable column headers with asc/desc indicator (PlayerTable).
  • Stackable metric filter builder — metric / operator / value (FilterBar).
  • METRIC_OPTIONS / FILTER_OP_OPTIONS / serializeFilters helpers; sort + filter state wired into the query with page reset on change (Rankings).

Tests

  • New repo tests: sort asc/desc by metrics (both paths), single/stacked/range filters, filter+sort on the stats_view path.
  • New API tests: valid sort/filter, plus 422 validation for unknown field / malformed JSON / bad op / non-numeric value.
  • Full suite: 137 passed. Ruff lint + format clean.

Notes

  • No schema/data change — reads existing dense player_stats docs, so no DB repopulation needed.
  • eq/ne operators intentionally omitted (exact equality on continuous floats is a footgun).
  • Manual UI verification (click headers, stack filters, toggle stats_view) still pending against the running stack.
  • Phase 2 (LLM natural-language → structured filters) is deferred to its own branch.

🤖 Generated with Claude Code

Add allowlist-validated dynamic sorting and numeric filtering to the
player rankings, running server-side over the full paginated result set.

Backend:
- metric_fields.py: single-source allowlist (Stats + score fields) mapping
  each public metric to its Mongo path / PlayerDTO accessor; doubles as the
  injection guard for sort_by and filters.
- GET /v1/players: sort_by now accepts any allowlisted metric; new filters
  param takes a JSON array of {field, op, value} clauses. 422 on unknown
  field / bad op / non-numeric value.
- get_players: generic Mongo sort + numeric filter clauses; stats_view
  Python path applies the same filter/sort after re-aggregation.

Frontend:
- METRIC_OPTIONS / FILTER_OP_OPTIONS / serializeFilters helpers.
- PlayerTable: clickable sortable column headers with asc/desc indicator.
- FilterBar: stackable metric filter builder (metric / op / value).
- Rankings: sort + filter state wired into the query; page resets on change.
- Add the 8 remaining typed Stats fields to the frontend metric list
  (pk_saved/pk_taken, penalty_miss/conceded/faced, yellow_red/direct_red
  cards, scoring_frequency) so the UI matches the backend allowlist.
- Remove eq/ne filter operators (FE + BE): exact equality on continuous
  float metrics is a footgun; keep gte/lte/gt/lt.
@YonatanHen
YonatanHen merged commit 74d4c24 into master Jul 1, 2026
5 checks passed
@YonatanHen
YonatanHen deleted the dev/rankings-dynamic-sort-filter branch July 4, 2026 11:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant