Translate the whole project to English and fix outdated comments and help

Comments, docstrings, logs, CLI help, map UI, legends, PDF sheet, scripts,
compose files and AGENTS.md are now English. Data keys stay unchanged
(relief_oriente, densite_sol, visualisations/, API JSON keys, link params).
Wrong comments and help defaults found along the way are corrected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Antoine
2026-09-27 23:16:45 +02:00
parent cc1c22d2b8
commit fb892ea9f2
52 changed files with 4356 additions and 4323 deletions

View File

@ -1,14 +1,14 @@
"""Progression structurée d'un run de génération (contrat pipeline → interface).
"""Structured progress of a generation run (pipeline → interface contract).
Le pipeline et le téléchargement IGN écrivent des événements JSON ligne à
ligne dans <sortie>/.generation.events.jsonl ; mapserve les agrège via
/api/status pour afficher les tuiles une par une (nom, type, état) dans la
file de génération, à la place du journal brut.
The pipeline and the IGN download write JSON events, one per line, to
<output>/.generation.events.jsonl; mapserve aggregates them through
/api/status to show tiles one by one (name, type, state) in the generation
queue, instead of the raw log.
Module volontairement découplé du serveur : le rendu des tuiles pourra être
déporté hors du serveur web — le producteur d'événements ne dépend que du
dossier de sortie. Les workers parallèles écrivent chacun leur ligne en mode
ajout (une ligne < 4 Ko avec O_APPEND est atomique sur POSIX).
This module is deliberately decoupled from the server: tile rendering can be
moved out of the web server — the event producer depends only on the output
directory. Parallel workers each write their own line in append mode (a line
< 4 KB written with O_APPEND is atomic on POSIX).
"""
import json
@ -19,19 +19,19 @@ from pathlib import Path
EVENTS_FILENAME = ".generation.events.jsonl"
# Phases d'une tuile, dans l'ordre d'exécution.
# Phases of a tile, in execution order.
PHASES = ("download", "classif", "dtm", "viz", "tile")
_TILE_RE = re.compile(r"LHD_FXX_(\d+)_(\d+)_")
def events_path(output_dir):
"""Chemin du journal d'événements d'un run."""
"""Path of a run's event log."""
return Path(output_dir) / EVENTS_FILENAME
def tile_short_name(name):
"""Nom court d'une tuile : LHD_FXX_1054_6882_PTS_LAMB93_IGN69 → 1054-6882."""
"""Short name of a tile: LHD_FXX_1054_6882_PTS_LAMB93_IGN69 → 1054-6882."""
m = _TILE_RE.search(name or "")
if not m:
return name
@ -39,20 +39,20 @@ def tile_short_name(name):
def reset_events(output_dir):
"""Prépare un journal vide pour un nouveau run (tronque l'ancien)."""
"""Prepare an empty log for a new run (truncates the previous one)."""
try:
path = events_path(output_dir)
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text("", encoding="utf-8")
except OSError:
pass # progression best-effort : ne jamais bloquer le traitement
pass # best-effort progress: never block processing
def report_event(output_dir, tile, phase, state, detail=None, res=None):
"""Ajoute un événement (une ligne JSON). Ne lève jamais.
"""Append an event (one JSON line). Never raises.
Ouverture en mode ajout à chaque appel : les workers parallélisés
(ProcessPoolExecutor, 'spawn') ne partagent aucun descripteur de fichier.
The file is opened in append mode on every call: parallel workers
(ProcessPoolExecutor, 'spawn') share no file descriptor.
"""
event = {"ts": round(time.time(), 3), "tile": tile, "phase": phase, "state": state}
run_id = os.environ.get("LIDAR_RUN_ID")
@ -70,7 +70,7 @@ def report_event(output_dir, tile, phase, state, detail=None, res=None):
def read_events(output_dir, limit=5000):
"""Lit les événements du journal (dernières `limit` lignes, invalides ignorées)."""
"""Read the log events (last `limit` lines, invalid ones skipped)."""
try:
lines = events_path(output_dir).read_text(
encoding="utf-8", errors="replace").splitlines()
@ -81,32 +81,32 @@ def read_events(output_dir, limit=5000):
try:
events.append(json.loads(line))
except ValueError:
continue # ligne partielle en cours d'écriture par le run
continue # partial line still being written by the run
return events
def _step_label(phase, detail, res, viz_labels):
"""Libellé français d'une étape, affiché tel quel dans la file."""
"""Label of a step, displayed as is in the queue."""
if phase == "download":
return "Téléchargement"
return "Download"
if phase == "classif":
return "Classification"
if phase == "dtm":
return f"DTM {str(res).replace('.', ',')} m"
return f"DTM {res} m"
if phase == "viz":
label = (viz_labels or {}).get(detail, detail)
if res is not None and float(res) != 0.5:
label += f" {str(res).replace('.', ',')} m"
label += f" {res} m"
return label
return detail or phase
def aggregate_tiles(events, viz_labels=None):
"""Regroupe les événements en tuiles : une entrée par tuile, étapes ordonnées.
"""Group events into tiles: one entry per tile, ordered steps.
Retourne [{short, name, state, steps: [{key, label, state, detail}]}] dans
l'ordre de première apparition. État d'une étape : running | ok | fail |
skip. État d'une tuile : pending | running | done | failed.
Returns [{short, name, state, steps: [{key, detail, res, state, label}]}]
in order of first appearance. Step state: running | ok | fail | skip.
Tile state: pending | running | done | failed.
"""
tiles = {}
for ev in events:
@ -120,7 +120,7 @@ def aggregate_tiles(events, viz_labels=None):
tile["name"] = name
state = ev.get("state")
if state == "start":
state = "running" # état d'affichage normalisé
state = "running" # normalized display state
detail = ev.get("detail")
res = ev.get("res")
if ev.get("phase") == "tile":
@ -129,14 +129,14 @@ def aggregate_tiles(events, viz_labels=None):
elif state == "fail":
tile["state"] = "failed"
continue
# Clé d'étape : le détail n'identifie une étape que pour les viz
# (leur nom) — les sous-étapes du téléchargement fusionnent en une.
# Step key: the detail identifies a step only for viz (their name)
# — download sub-steps merge into a single one.
phase = ev.get("phase")
key = f"viz:{detail}:{res}" if phase == "viz" else f"{phase}:{res}"
step = tile["steps"].setdefault(
key, {"key": key, "detail": detail, "res": res, "state": state,
"label": _step_label(phase, detail, res, viz_labels)})
# ok/fail sont terminaux — on ne régresse pas vers running/skip
# ok/fail are terminal — never regress to running/skip (fail still overrides ok)
if step["state"] not in ("ok", "fail") or state == "fail":
step["state"] = state
@ -147,19 +147,19 @@ def aggregate_tiles(events, viz_labels=None):
if any(s["state"] == "fail" for s in steps):
tile["state"] = "failed"
elif steps:
tile["state"] = "running" # pas encore d'événement tuile final
tile["state"] = "running" # no final tile event yet
result.append({"short": tile["short"], "name": tile["name"],
"state": tile["state"], "steps": steps})
return result
def progress_snapshot(output_dir, viz_labels=None, run_id=None):
"""État consolidé des tuiles du dernier run (prêt pour /api/status).
"""Consolidated tile state of the last run (ready for /api/status).
run_id (LIDAR_RUN_ID posé par mapserve) : ne garde que les événements de
CE run — les workers d'un run annulé peuvent continuer d'écrire (O_APPEND)
après la troncature du journal par le run suivant, et pollueraient sinon
la file affichée. Sans run_id (run CLI autonome) : tout le journal.
run_id (LIDAR_RUN_ID set by mapserve): keeps only the events of THIS
run — workers of a cancelled run may keep writing (O_APPEND) after the
next run truncated the log, and would otherwise pollute the displayed
queue. Without run_id (standalone CLI run): the whole log.
"""
events = read_events(output_dir)
if run_id is not None: