Coverage for src/ecnl/adapters/inbound/tools/analytics.py: 100%
20 statements
« prev ^ index » next coverage.py v7.14.2, created at 2026-06-28 08:19 +0000
« prev ^ index » next coverage.py v7.14.2, created at 2026-06-28 08:19 +0000
1"""Analytics tools — extracted results and RPI."""
3import logging
5from mcp.server.fastmcp import FastMCP
7from ....application.service import ECNLService
8from ....ports.inbound import Authorizer
9from ..formatters import _fmt_results, _fmt_rpi, _fmt_team_rpi
10from ._base import _READ_ANNOTATIONS, _safe_call_authorized
12logger = logging.getLogger(__name__)
15def register_analytics_tools(mcp: FastMCP, service: ECNLService, authorizer: Authorizer) -> None:
16 """Register the results and RPI tools on ``mcp``."""
18 @mcp.tool(annotations=_READ_ANNOTATIONS)
19 async def get_results(event_id: int, flight_id: int) -> str:
20 """Get the completed match results for a flight.
22 Returns only games that have been played, with final scores. This is the
23 raw data the RPI tools build on.
25 Args:
26 event_id: Numeric event ID (e.g. 3933).
27 flight_id: Flight ID from get_event_overview.
28 """
29 logger.info("tool=get_results event_id=%r flight_id=%r", event_id, flight_id)
30 return await _safe_call_authorized(
31 authorizer, "get_results", service.get_results(event_id, flight_id), _fmt_results
32 )
34 @mcp.tool(annotations=_READ_ANNOTATIONS)
35 async def get_rpi(event_id: int, flight_id: int, tie_weight: float = 1 / 3) -> str:
36 """Compute the RPI ranking for every team in a flight.
38 RPI = 0.25·WP + 0.50·OWP + 0.25·OOWP, computed from the flight's
39 completed games. Returns each team's WP/OWP/OOWP components and final RPI,
40 ranked. Early in a season, sparse results make RPI noisy.
42 Args:
43 event_id: Numeric event ID (e.g. 3933).
44 flight_id: Flight ID from get_event_overview.
45 tie_weight: Tie value in the winning-percentage element — 1/3 (the
46 2024 convention, default) or 0.5 (pre-2024).
47 """
48 logger.info("tool=get_rpi event_id=%r flight_id=%r tie_weight=%r", event_id, flight_id, tie_weight)
49 return await _safe_call_authorized(
50 authorizer, "get_rpi", service.get_rpi(event_id, flight_id, tie_weight), _fmt_rpi
51 )
53 @mcp.tool(annotations=_READ_ANNOTATIONS)
54 async def get_team_rpi(event_id: int, flight_id: int, team: str, tie_weight: float = 1 / 3) -> str:
55 """Compute one team's RPI with its component breakdown.
57 Args:
58 event_id: Numeric event ID (e.g. 3933).
59 flight_id: Flight ID from get_event_overview.
60 team: Team name (or a distinctive part of it), case-insensitive.
61 tie_weight: WP tie value — 1/3 (default) or 0.5 (pre-2024).
62 """
63 logger.info("tool=get_team_rpi event_id=%r flight_id=%r team=%r", event_id, flight_id, team)
64 return await _safe_call_authorized(
65 authorizer, "get_team_rpi", service.get_team_rpi(event_id, flight_id, team, tie_weight), _fmt_team_rpi
66 )