Spaces:
Paused
Paused
File size: 3,636 Bytes
1c730d1 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | # 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:<gen>``, ``ls:<load>``, ``switch:<id>=<state>``,
…); an empty list means the catalogue identity ``action:<action_id>``
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 (`<player> — session <count+1>`).
session_count: int
# The concrete distinct session names (sorted, case-insensitive). The
# config screen suggests the first free `session <n>` 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] = []
|