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:
127
lidar_pipeline/tests/test_progress.py
Normal file
127
lidar_pipeline/tests/test_progress.py
Normal 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"
|
||||
Reference in New Issue
Block a user