docs: update production workflow design and runtime changes

This commit is contained in:
2026-06-14 18:47:00 +08:00
parent 58a87706ef
commit b1c59051b9
41 changed files with 2042 additions and 134 deletions
@@ -289,6 +289,50 @@ def _read_gamma_int_param(path: Path, key: str) -> Optional[int]:
return _safe_int(_read_gamma_key_values(path).get(key))
def _expert_gamma_work_run_dir(run_dir: Path) -> Path:
run_id = run_dir.name
candidates = [
run_dir,
Path(settings.GAMMA_SBAS_WORK_ROOT or "") / "runs" / run_id if settings.GAMMA_SBAS_WORK_ROOT else run_dir,
]
seen: set[str] = set()
for candidate in candidates:
normalized = _normalize_path(candidate)
if normalized in seen:
continue
seen.add(normalized)
if (
(candidate / "sbas" / "disp.TS_tab").is_file()
and (candidate / "sbas" / "mli.ave.par").is_file()
and any((candidate / "dem").glob("*.lt_fine"))
):
return candidate
return run_dir
def _expert_gamma_lookup_path(work_run_dir: Path, manifest: dict[str, Any]) -> Path:
lt_path = work_run_dir / "dem" / f"{manifest.get('reference_date') or ''}.lt_fine"
if lt_path.is_file():
return lt_path
candidates = sorted((work_run_dir / "dem").glob("*.lt_fine"))
return candidates[0] if candidates else lt_path
def _resolve_expert_gamma_tab_path(raw_path: str, *, work_run_dir: Path) -> Path:
path = Path(_wsl_path_to_windows(raw_path))
if path.is_file():
return path
run_id = work_run_dir.name
parts = list(path.parts)
if run_id in parts:
index = parts.index(run_id)
relative_parts = parts[index + 1 :]
candidate = work_run_dir.joinpath(*relative_parts)
if candidate.is_file():
return candidate
return path
def _haversine_m(lon1: float, lat1: float, lon2: float, lat2: float) -> float:
radius_m = 6371008.8
phi1 = math.radians(lat1)
@@ -762,12 +806,13 @@ def _read_geo_rate_window(
def _query_expert_gamma_point_timeseries(run_dir: Path, *, lon: float, lat: float) -> dict[str, Any]:
source_path = run_dir / "publish" / "geotiff" / "geo_los_def_rate.tif"
coverage_path = run_dir / "publish" / "geotiff" / "geo_los_def_rate_rgb.tif"
lt_path = run_dir / "dem" / f"{_safe_read_json(run_dir / 'run_manifest.json').get('reference_date') or ''}.lt_fine"
if not lt_path.is_file():
candidates = sorted((run_dir / "dem").glob("*.lt_fine"))
lt_path = candidates[0] if candidates else lt_path
mli_par = run_dir / "sbas" / "mli.ave.par"
disp_tab = run_dir / "sbas" / "disp.TS_tab"
work_run_dir = _expert_gamma_work_run_dir(run_dir)
work_manifest = _safe_read_json(work_run_dir / "run_manifest.json")
if not work_manifest:
work_manifest = _safe_read_json(run_dir / "run_manifest.json")
lt_path = _expert_gamma_lookup_path(work_run_dir, work_manifest)
mli_par = work_run_dir / "sbas" / "mli.ave.par"
disp_tab = work_run_dir / "sbas" / "disp.TS_tab"
if not source_path.is_file():
raise FileNotFoundError("geo_los_def_rate.tif is missing")
if not lt_path.is_file():
@@ -852,11 +897,13 @@ def _query_expert_gamma_point_timeseries(run_dir: Path, *, lon: float, lat: floa
img_y = min(max(0, img_y), radar_lines - 1)
raw_disp_paths = [
Path(_wsl_path_to_windows(line.strip()))
_resolve_expert_gamma_tab_path(line.strip(), work_run_dir=work_run_dir)
for line in disp_tab.read_text(encoding="utf-8", errors="ignore").splitlines()
if line.strip()
]
dates = _read_expert_sbas_dates(run_dir)
dates = _read_expert_sbas_dates(work_run_dir)
if not dates:
dates = _read_expert_sbas_dates(run_dir)
displacements: list[dict[str, Any]] = []
for index, disp_path in enumerate(raw_disp_paths):
value_m = _read_radar_float32(disp_path, width=radar_width, img_x=img_x, img_y=img_y)
@@ -872,6 +919,8 @@ def _query_expert_gamma_point_timeseries(run_dir: Path, *, lon: float, lat: floa
return {
"schema": "insar.gamma-sbas-point-query/v1",
"source_tool": "disp.TS_tab_radar_pixel_sample",
"source_run_dir": str(run_dir),
"work_run_dir": str(work_run_dir),
"query": {"lon": lon, "lat": lat},
"matched": {
**valid_choice,
@@ -906,11 +955,11 @@ def _locate_radar_points_in_geocoded_product(run_dir: Path, points: list[dict[st
source_path = run_dir / "publish" / "geotiff" / "geo_los_def_rate.tif"
coverage_path = run_dir / "publish" / "geotiff" / "geo_los_def_rate_rgb.tif"
manifest = _safe_read_json(run_dir / "run_manifest.json")
lt_path = run_dir / "dem" / f"{manifest.get('reference_date') or ''}.lt_fine"
if not lt_path.is_file():
candidates = sorted((run_dir / "dem").glob("*.lt_fine"))
lt_path = candidates[0] if candidates else lt_path
work_run_dir = _expert_gamma_work_run_dir(run_dir)
manifest = _safe_read_json(work_run_dir / "run_manifest.json")
if not manifest:
manifest = _safe_read_json(run_dir / "run_manifest.json")
lt_path = _expert_gamma_lookup_path(work_run_dir, manifest)
if not source_path.is_file() or not lt_path.is_file():
return {}
@@ -1982,6 +2031,12 @@ def _stack_dates_from_manifest(stack_manifest: dict[str, Any], manifest: dict[st
class SbasInsarCatalogService:
def get_run_root(self) -> str:
root = Path(settings.GAMMA_SBAS_PRODUCT_ROOT or Path(settings.TIMESERIES_PRODUCT_DIR) / "sbas")
run_root = root / "runs"
run_root.mkdir(parents=True, exist_ok=True)
return _normalize_path(run_root)
def get_work_run_root(self) -> str:
root = Path(settings.GAMMA_SBAS_WORK_ROOT or Path(settings.BACKEND_DIR) / "runtime" / "sbas_insar_production")
run_root = root / "runs"
run_root.mkdir(parents=True, exist_ok=True)
@@ -1989,6 +2044,9 @@ class SbasInsarCatalogService:
def get_run_roots(self) -> list[str]:
roots = [self.get_run_root()]
work_root = self.get_work_run_root()
if work_root not in roots:
roots.append(work_root)
try:
landsar_root = landsar_sbas_service.configured_run_root()
if landsar_root not in roots:
@@ -1999,17 +2057,17 @@ class SbasInsarCatalogService:
def _iter_run_manifest_paths(self, run_root: Optional[str] = None) -> list[str]:
roots = [run_root] if run_root else self.get_run_roots()
manifest_paths: list[str] = []
manifest_paths_by_run: dict[str, str] = {}
for raw_root in roots:
root = Path(raw_root)
if not root.is_dir():
continue
manifest_paths.extend(
_normalize_path(path)
for path in sorted(root.glob("*/run_manifest.json"))
if self._is_publish_ready(path.parent, _safe_read_json(path))
)
return sorted(dict.fromkeys(manifest_paths))
for path in sorted(root.glob("*/run_manifest.json")):
if not self._is_publish_ready(path.parent, _safe_read_json(path)):
continue
run_id = path.parent.name
manifest_paths_by_run.setdefault(run_id, _normalize_path(path))
return list(manifest_paths_by_run.values())
@staticmethod
def _is_landsar_manifest(manifest: dict[str, Any]) -> bool:
@@ -2295,9 +2353,12 @@ class SbasInsarCatalogService:
if self._is_landsar_manifest(manifest):
return self._build_landsar_product(run_dir, manifest_file, manifest)
detail = sbas_insar_production_service.get_run_detail(run_dir.name)
coverage = detail.get("geographic_coverage") or {}
stack_manifest = _safe_read_json(run_dir / "stack_manifest.json")
try:
detail = sbas_insar_production_service.get_run_detail(run_dir.name)
coverage = detail.get("geographic_coverage") or {}
except FileNotFoundError:
coverage = sbas_insar_production_service._build_run_geographic_coverage(run_dir, manifest)
monitor_summary = _safe_read_json(run_dir / "monitor_points_summary.json")
workflow_summary = _safe_read_json(run_dir / "workflow_summary.json")
is_expert_gamma = self._is_expert_gamma_manifest(manifest, run_dir)