# Copyright (c) 2025-2026, RTE (https://www.rte-france.com) # This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0. # If a copy of the Mozilla Public License, version 2.0 was not distributed with this file, # you can obtain one at http://mozilla.org/MPL/2.0/. # SPDX-License-Identifier: MPL-2.0 """Wire models of the Game Mode solution-capitalisation endpoints. Pydantic request/response models normally live at the top of ``main.py``; the game group sits here to keep ``main.py`` under the module-size ceiling (scripts/check_code_quality.py). The store logic itself is in ``services/game_solutions.py``; the frontend mirror is ``frontend/src/types.ts`` (LogGameSolutionRequest / LogGameSolutionResponse / GameLeverStatsResponse), machine-checked via ``openapi.snapshot.json``. """ from __future__ import annotations from pydantic import BaseModel class GameSolutionAction(BaseModel): """One retained (starred) remedial action of a Game Mode study. ``levers`` are magnitude-free unitary signatures computed by the frontend (``redispatch:``, ``ls:``, ``switch:=``, …); an empty list means the catalogue identity ``action:`` is used instead. See services/game_solutions.py. """ action_id: str description: str | None = None action_type: str | None = None levers: list[str] = [] # True when the action is effective (reduces the baseline worst # loading; a combined action must also beat its underlying actions by # ≥ 1 loading-point). The novelty bonus is only paid when EVERY # retained action is effective. effective: bool = True class LogGameSolutionRequest(BaseModel): player: str | None = None session_name: str | None = None study_id: str | None = None study_label: str | None = None network_path: str contingency_id: str solved: bool = False final_max_rho: float | None = None baseline_max_rho: float | None = None actions: list[GameSolutionAction] class GameSolutionNovelty(BaseModel): new_proposition: bool new_levers: list[str] # False when at least one retained action was not effective — novelty # is still reported but bonus_points stays 0. effective: bool bonus_points: int class GameSolutionFrequency(BaseModel): action_id: str | None description: str | None signatures: list[str] count: int total: int share: float class GameSolutionContextStats(BaseModel): distinct_propositions: int total_retentions: int class LogGameSolutionResponse(BaseModel): stored: bool duplicate: bool context_key: str signature: str novelty: GameSolutionNovelty frequencies: list[GameSolutionFrequency] context_stats: GameSolutionContextStats class GameLeverStat(BaseModel): signature: str label: str category: str count: int share: float sample_description: str | None class GameLeverStatsResponse(BaseModel): context_key: str total_retentions: int levers: list[GameLeverStat] class PlayerSessionsResponse(BaseModel): player: str # Distinct sessions this player already recorded in the shared base; # seeds the default session name (` — session `). session_count: int # The concrete distinct session names (sorted, case-insensitive). The # config screen suggests the first free `session ` index over these # and blocks a name that already exists — a count-plus-one heuristic # re-suggests a taken name when the recorded indices have gaps. session_names: list[str] = []