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:
@ -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:
|
||||
|
||||
Reference in New Issue
Block a user