Split webapp for Raspberry Pi deployment, remote generation API and sync

La webapp (carte + vignettes) et la génération de tuiles se déploient sur
deux machines : image légère Dockerfile.webapp (FastAPI + Pillow AVIF
natif + pyproj) sur Raspberry Pi, pipeline complet sur la machine de
traitement. LIDAR_GENERATION_URL délègue /api/generate, /api/preview et
/api/status ; /api/sync ramène les tuiles par rsync puis régénère
vignettes et index localement. Token partagé optionnel
(LIDAR_API_TOKEN/LIDAR_REMOTE_TOKEN). Retire du dépôt les journaux
internes (.swival, audit-findings) et les données (data/, notebooks/).
Doc : docs/DEPLOY_WEBAPP.md.
This commit is contained in:
Antoine Jacquin
2026-09-02 19:44:39 +02:00
parent ed3e90ea89
commit 422f58d772
24 changed files with 2390 additions and 329 deletions

View File

@ -0,0 +1,127 @@
"""Tests du journal d'événements de progression (file de génération)."""
def test_report_and_read_events(tmp_path):
"""Les événements s'écrivent en JSONL et se relisent dans l'ordre."""
from lidar_pipeline.progress import read_events, report_event, reset_events
reset_events(tmp_path)
report_event(tmp_path, "LHD_FXX_1054_6882_PTS_LAMB93_IGN69", "download", "start")
report_event(tmp_path, "LHD_FXX_1054_6882_PTS_LAMB93_IGN69", "download", "ok")
events = read_events(tmp_path)
assert len(events) == 2
assert events[0]["phase"] == "download" and events[0]["state"] == "start"
assert events[1]["state"] == "ok"
assert events[1]["tile"] == "LHD_FXX_1054_6882_PTS_LAMB93_IGN69"
def test_reset_clears_previous_run(tmp_path):
"""reset_events tronque le journal : un nouveau run repart de zéro."""
from lidar_pipeline.progress import read_events, report_event, reset_events
reset_events(tmp_path)
report_event(tmp_path, "X", "tile", "ok")
reset_events(tmp_path)
assert read_events(tmp_path) == []
def test_read_events_missing_or_partial(tmp_path):
"""Dossier sans journal → liste vide ; ligne partielle → ignorée."""
from lidar_pipeline.progress import events_path, read_events
assert read_events(tmp_path) == []
events_path(tmp_path).write_text(
'{"tile": "X", "phase": "tile", "state": "ok"}\n'
'{"tile": "X", "phase": "vi', encoding="utf-8")
events = read_events(tmp_path)
assert len(events) == 1 and events[0]["state"] == "ok"
def test_short_name():
"""Le nom court extrait col-row du nom de dalle IGN."""
from lidar_pipeline.progress import tile_short_name
assert tile_short_name("LHD_FXX_1054_6882_PTS_LAMB93_IGN69") == "1054-6882"
assert tile_short_name("LHD_FXX_1054_6882_PTS_LAMB93_IGN69.copc.laz") == "1054-6882"
assert tile_short_name("autre.tif") == "autre.tif"
assert tile_short_name("") == ""
def test_aggregate_full_run(tmp_path):
"""Run complet : étapes ordonnées avec libellés français, tuile terminée."""
from lidar_pipeline.progress import aggregate_tiles, read_events, report_event, reset_events
reset_events(tmp_path)
b = "LHD_FXX_1054_6882_PTS_LAMB93_IGN69"
report_event(tmp_path, b, "download", "skip", "déjà dans input/")
report_event(tmp_path, b, "classif", "start")
report_event(tmp_path, b, "classif", "ok")
report_event(tmp_path, b, "dtm", "ok", res=0.5)
report_event(tmp_path, b, "viz", "start", "aspect", res=0.5)
report_event(tmp_path, b, "viz", "ok", "aspect", res=0.5)
report_event(tmp_path, b, "tile", "ok", "123s")
tiles = aggregate_tiles(read_events(tmp_path), viz_labels={"aspect": "Exposition"})
assert len(tiles) == 1
t = tiles[0]
assert t["short"] == "1054-6882" and t["state"] == "done"
assert [s["label"] for s in t["steps"]] == [
"Téléchargement", "Classification", "DTM 0,5 m", "Exposition"]
assert [s["state"] for s in t["steps"]] == ["skip", "ok", "ok", "ok"]
def test_aggregate_running_step(tmp_path):
"""Une étape démarrée non terminée laisse la tuile « running »."""
from lidar_pipeline.progress import aggregate_tiles, read_events, report_event, reset_events
reset_events(tmp_path)
b = "LHD_FXX_1054_6882_PTS_LAMB93_IGN69"
report_event(tmp_path, b, "download", "ok")
report_event(tmp_path, b, "classif", "ok")
report_event(tmp_path, b, "dtm", "ok", res=0.5)
report_event(tmp_path, b, "viz", "start", "aspect", res=0.5)
tiles = aggregate_tiles(read_events(tmp_path))
assert tiles[0]["state"] == "running"
assert tiles[0]["steps"][-1]["state"] == "running"
def test_aggregate_failure_marks_tile_failed():
"""Une viz en échec marque la tuile failed même sans événement tuile."""
from lidar_pipeline.progress import aggregate_tiles
b = "LHD_FXX_1054_6882_PTS_LAMB93_IGN69"
tiles = aggregate_tiles([
{"tile": b, "phase": "classif", "state": "ok"},
{"tile": b, "phase": "dtm", "state": "ok", "res": 0.5},
{"tile": b, "phase": "viz", "state": "start", "detail": "aspect", "res": 0.5},
{"tile": b, "phase": "viz", "state": "fail", "detail": "aspect", "res": 0.5},
])
assert tiles[0]["state"] == "failed"
aspect = [s for s in tiles[0]["steps"] if s["detail"] == "aspect"][0]
assert aspect["state"] == "fail"
def test_aggregate_multi_resolution_and_bad_input():
"""Deux résolutions → deux étapes DTM/viz distinctes ; entrées invalides ignorées."""
from lidar_pipeline.progress import aggregate_tiles
b = "LHD_FXX_1054_6882_PTS_LAMB93_IGN69"
tiles = aggregate_tiles([
"pas du tout du json",
None,
{"tile": b, "phase": "inconnu", "state": "ok"},
{"tile": b, "phase": "dtm", "state": "ok", "res": 0.5},
{"tile": b, "phase": "dtm", "state": "ok", "res": 0.2},
{"tile": b, "phase": "viz", "state": "ok", "detail": "aspect", "res": 0.2},
{"tile": b, "phase": "tile", "state": "ok"},
], viz_labels={"aspect": "Exposition"})
assert len(tiles) == 1
labels = [s["label"] for s in tiles[0]["steps"]]
assert labels == ["DTM 0,5 m", "DTM 0,2 m", "Exposition 0,2 m"]
# La résolution secondaire (0,2 m) est suffixée, la primaire (0,5 m) non
def test_aggregate_multiple_tiles_ordered():
"""Une entrée par tuile, dans l'ordre de première apparition."""
from lidar_pipeline.progress import aggregate_tiles
a = "LHD_FXX_1054_6882_PTS_LAMB93_IGN69"
b = "LHD_FXX_1055_6882_PTS_LAMB93_IGN69"
tiles = aggregate_tiles([
{"tile": a, "phase": "download", "state": "ok"},
{"tile": b, "phase": "download", "state": "start", "detail": "catalogue IGN"},
{"tile": a, "phase": "tile", "state": "ok"},
])
assert [t["short"] for t in tiles] == ["1054-6882", "1055-6882"]
assert tiles[0]["state"] == "done"
assert tiles[1]["state"] == "running"