File size: 3,259 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
# 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
"""Game Mode HTTP routes — solution capitalisation + lever hints.

Registered on the app via :func:`install_game_routes` (same install-once
pattern as ``services/api_errors.install_error_handlers``); kept out of
``main.py`` so it stays under the module-size ceiling
(scripts/check_code_quality.py). Store logic: ``services/game_solutions.py``;
wire models: ``services/game_solution_models.py``.

These routes are NOT lockdown-gated (D7): unlike the desktop-era filesystem
RPCs, they never touch a client-supplied path — the store root is fixed
server-side (``COSTUDY4GRID_DATA_DIR`` cascade) and ids are slugged, so they
stay available on the public HuggingFace Space where the shared base lives.
"""
from __future__ import annotations

from fastapi import FastAPI, HTTPException, Query

from expert_backend.services import game_solutions
from expert_backend.services.game_solution_models import (
    GameLeverStatsResponse,
    LogGameSolutionRequest,
    LogGameSolutionResponse,
    PlayerSessionsResponse,
)


def install_game_routes(app: FastAPI) -> None:
    """Register the Game Mode routes on ``app`` (call once at import)."""

    @app.post("/api/game/log-solution", response_model=LogGameSolutionResponse)
    def log_game_solution(request: LogGameSolutionRequest) -> dict:
        """Capitalise a Game Mode retained proposition into the shared
        solution base and report novelty (bonus points) + per-action usage
        frequencies.

        Pure file IO on the store directory — no network state involved, so
        no network lock / busy gate; the store serializes its own
        read-modify-write with a module-level lock.
        """
        try:
            return game_solutions.log_solution(request.model_dump())
        except ValueError as e:
            raise HTTPException(status_code=400, detail=str(e))

    @app.get("/api/game/lever-stats", response_model=GameLeverStatsResponse)
    def game_lever_stats(
        network_path: str = Query(""),
        contingency_id: str = Query(...),
        top_n: int = Query(5, ge=1, le=20),
    ) -> dict:
        """Most-used unitary levers of a (network, contingency) context in
        the shared solution base — the Game Mode beginner-assistance hints.
        Read-only scan of the store."""
        try:
            return game_solutions.lever_stats(
                network_path, contingency_id, top_n=top_n)
        except ValueError as e:
            raise HTTPException(status_code=400, detail=str(e))

    @app.get("/api/game/player-sessions", response_model=PlayerSessionsResponse)
    def game_player_sessions(player: str = Query("")) -> dict:
        """Distinct sessions a player has already recorded in the shared
        solution base — seeds the default session name / index on the Game
        Mode config screen. Read-only store scan; an empty handle → zero."""
        return game_solutions.player_session_count(player)