Co-Study4Grid / scripts /game_mode /sample_rte7000.py
github-actions[bot]
Deploy f7b8bf6
1c730d1
Raw
History Blame Contribute Delete
4.3 kB
#!/usr/bin/env python3
# Copyright (c) 2025-2026, RTE (https://www.rte-france.com)
# SPDX-License-Identifier: MPL-2.0
"""Sample N scenarios of a chosen difficulty from the RTE7000 France THT graded
scenario database (``data/rte7000_tht/scenarios.json``).
Difficulty is the expert recommender's solvability at the 95 % monitoring factor:
easy - a suggested UNITARY action resolves the overloads;
medium - no unitary resolves, but a first-identified COMBINATION does;
hard - neither a unitary nor a first combination resolves.
The output is a ``GameStudy[]`` list (the exact shape the Game Mode UI / backend
consume), so it can be dropped straight into a session config. Dates stay hidden:
titles carry only month + weekday + hour-period, and grids live under opaque ids.
Usage:
python3 scripts/game_mode/sample_rte7000.py --difficulty easy --n 5
python3 scripts/game_mode/sample_rte7000.py --difficulty hard --n 3 --seed 7 \
--out /tmp/session_studies.json
"""
import argparse
import json
import os
import random
REPO = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
DB = os.path.join(REPO, "data", "rte7000_tht", "scenarios.json")
DIFFICULTIES = ("easy", "medium", "hard")
def load_db(path=DB):
with open(path, encoding="utf-8") as f:
return json.load(f)
def to_game_study(s):
"""Project a DB scenario onto the GameStudy fields the UI/backend use.
The ``solution`` field (reference remediation) is intentionally dropped — it
is analysis metadata the player must not see.
"""
return {
"id": s["id"],
"label": s["label"],
"networkPath": s["networkPath"],
"actionFilePath": s["actionFilePath"],
"layoutPath": s.get("layoutPath"),
"contingencyElementId": s["contingencyElementId"],
"contingencyLabel": s.get("contingencyLabel"),
"baselineMaxLoadingPct": s.get("baselineMaxLoadingPct"),
"description": (f"{s['title']}. Loss of {s['contingencyLabel']} drives a worst-case "
f"{s.get('baselineMaxLoadingPct')}% line loading. Bring every monitored "
f"line back under the 95% monitoring limit."),
}
def sample(difficulty, n, seed=None, db_path=DB, spread_grids=True):
"""Return up to ``n`` GameStudy dicts of ``difficulty``, sampled without
replacement. With ``spread_grids`` we round-robin over distinct grids first so
a small sample covers different operating points rather than one grid."""
if difficulty not in DIFFICULTIES:
raise ValueError(f"difficulty must be one of {DIFFICULTIES}, got {difficulty!r}")
db = load_db(db_path)
pool = [s for s in db["scenarios"] if s["difficulty"] == difficulty]
rng = random.Random(seed)
rng.shuffle(pool)
if spread_grids:
by_grid = {}
for s in pool:
by_grid.setdefault(s["gridId"], []).append(s)
ordered, grids = [], list(by_grid.values())
while any(grids):
for g in grids:
if g:
ordered.append(g.pop())
pool = ordered
return [to_game_study(s) for s in pool[:n]]
def main():
ap = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("--difficulty", choices=DIFFICULTIES, required=True)
ap.add_argument("--n", type=int, default=5, help="number of scenarios to sample")
ap.add_argument("--seed", type=int, default=None, help="RNG seed for reproducible draws")
ap.add_argument("--db", default=DB, help="path to scenarios.json")
ap.add_argument("--out", default=None, help="write the GameStudy[] JSON here (default: stdout)")
args = ap.parse_args()
db = load_db(args.db)
counts = db.get("counts", {})
studies = sample(args.difficulty, args.n, seed=args.seed, db_path=args.db)
payload = json.dumps(studies, indent=2, ensure_ascii=False)
if args.out:
with open(args.out, "w", encoding="utf-8") as f:
f.write(payload)
print(f"wrote {len(studies)} '{args.difficulty}' scenario(s) -> {args.out} "
f"(pool: {counts.get(args.difficulty, 0)})")
else:
print(payload)
if __name__ == "__main__":
main()