fix: skip gf3 production when results exist

This commit is contained in:
2026-06-15 15:27:05 +08:00
parent 0d54eb9ba7
commit 2f86c5a8cb
2 changed files with 68 additions and 0 deletions
@@ -85,6 +85,11 @@ def _safe_slug(value: Any, *, default: str = "unknown") -> str:
return safe or default return safe or default
def _date_from_scene_name(scene_name: str) -> str | None:
match = re.search(r"(20\d{6})", str(scene_name or ""))
return match.group(1) if match else None
def _resolve_existing_dirs(values: list[str] | tuple[str, ...] | None) -> tuple[list[Path], list[str]]: def _resolve_existing_dirs(values: list[str] | tuple[str, ...] | None) -> tuple[list[Path], list[str]]:
roots: list[Path] = [] roots: list[Path] = []
missing: list[str] = [] missing: list[str] = []
@@ -268,6 +273,36 @@ def _scene_complete(scene_dir: Path, polarizations: list[str]) -> bool:
return all(_completed_geo_product(scene_dir, pol) is not None for pol in polarizations) return all(_completed_geo_product(scene_dir, pol) is not None for pol in polarizations)
def _standardized_scene_complete(storage_root: Path | None, scene_name: str, polarizations: list[str]) -> tuple[bool, Path | None, str]:
if storage_root is None:
return False, None, "storage_root_not_configured"
date_text = _date_from_scene_name(scene_name)
candidate_dirs = []
if date_text:
candidate_dirs.append(storage_root / _safe_slug(date_text) / _safe_slug(scene_name))
candidate_dirs.append(storage_root / "unknown_batch" / _safe_slug(scene_name))
for scene_dir in candidate_dirs:
manifest_path = scene_dir / STANDARD_MANIFEST_NAME
manifest = _read_json(manifest_path)
if manifest and str(manifest.get("status") or "").upper() in {"DONE", "PARTIAL"}:
assets = manifest.get("assets") or []
complete_pols = {
str(asset.get("polarization") or "").upper()
for asset in assets
if str(asset.get("status") or "").lower() in {"converted", "skipped"}
and _is_nonempty_file(Path(str(asset.get("path") or "")))
}
if all(str(pol or "").upper() in complete_pols for pol in polarizations):
return True, scene_dir, "standard_manifest_complete"
if scene_dir.is_dir():
if all(_is_nonempty_file(scene_dir / f"{str(pol).upper()}_L2.tif") for pol in polarizations):
return True, scene_dir, "standard_tifs_complete"
return False, None, "standardized_result_missing"
def _missing_geo_polarizations(scene_dir: Path, polarizations: list[str]) -> list[str]: def _missing_geo_polarizations(scene_dir: Path, polarizations: list[str]) -> list[str]:
if not scene_dir.is_dir(): if not scene_dir.is_dir():
return list(polarizations) return list(polarizations)
@@ -423,9 +458,11 @@ def run_gf3_sarscape_production(
max_to_process = int(max_archives_per_run or 0) max_to_process = int(max_archives_per_run or 0)
timeout = int(timeout_seconds or 0) timeout = int(timeout_seconds or 0)
keep = bool(settings.GF3_SARSCAPE_KEEP_EXTRACTED if keep_extracted is None else keep_extracted) keep = bool(settings.GF3_SARSCAPE_KEEP_EXTRACTED if keep_extracted is None else keep_extracted)
storage_root = Path(os.path.normpath(settings.GF3_STORAGE_DIRS)).resolve() if settings.GF3_STORAGE_DIRS else None
_emit_log(log_callback, "INFO", f"GF3 SARscape source roots: {source_dirs}") _emit_log(log_callback, "INFO", f"GF3 SARscape source roots: {source_dirs}")
_emit_log(log_callback, "INFO", f"GF3 SARscape native root: {native_root_path}") _emit_log(log_callback, "INFO", f"GF3 SARscape native root: {native_root_path}")
_emit_log(log_callback, "INFO", f"GF3 SARscape standardized root: {storage_root or '(not configured)'}")
_emit_log(log_callback, "INFO", f"GF3 SARscape wrapper: {wrapper_path}") _emit_log(log_callback, "INFO", f"GF3 SARscape wrapper: {wrapper_path}")
_emit_log(log_callback, "INFO", f"GF3 SARscape DEM: {dem}") _emit_log(log_callback, "INFO", f"GF3 SARscape DEM: {dem}")
_emit_log(log_callback, "INFO", f"GF3 SARscape polarizations: {pol_text}") _emit_log(log_callback, "INFO", f"GF3 SARscape polarizations: {pol_text}")
@@ -469,6 +506,26 @@ def run_gf3_sarscape_production(
progress = 5 + int((idx / max(total, 1)) * 60) progress = 5 + int((idx / max(total, 1)) * 60)
_emit_progress(progress_callback, progress, f"GF3 SARscape checking {idx + 1}/{total}: {scene_name}") _emit_progress(progress_callback, progress, f"GF3 SARscape checking {idx + 1}/{total}: {scene_name}")
standardized_complete, standardized_dir, standardized_reason = _standardized_scene_complete(storage_root, scene_name, pols)
if standardized_complete:
skipped += 1
_emit_log(
log_callback,
"INFO",
f"GF3 SARscape skipping {scene_name}: standardized result already exists ({standardized_reason})",
)
results.append(
{
"scene_name": scene_name,
"input_path": str(input_path),
"scene_dir": str(scene_dir),
"standardized_dir": str(standardized_dir) if standardized_dir else None,
"status": "skipped_standardized_complete",
"skip_reason": standardized_reason,
}
)
continue
if _scene_complete(scene_dir, pols): if _scene_complete(scene_dir, pols):
skipped += 1 skipped += 1
results.append( results.append(
@@ -479,3 +479,14 @@ GF3_SARSCAPE_PRODUCE_TIMEOUT_SECONDS=0
- 不删除 `GF3_ARCHIVE_SOURCE_DIRS` 中的原始压缩包,也不删除 `GF3_STORAGE_DIRS` 中的标准 GeoTIFF。 - 不删除 `GF3_ARCHIVE_SOURCE_DIRS` 中的原始压缩包,也不删除 `GF3_STORAGE_DIRS` 中的标准 GeoTIFF。
这样 `D:\production_results\gf3\sarscape_native` 只长期保存可追溯的最终 `_geo` 原生结果组,中间过程文件在标准化完成后自动释放空间;wrapper 配置和临时运行文件放在 `D:\production_runtime\gf3\sarscape_runtime` 这样 `D:\production_results\gf3\sarscape_native` 只长期保存可追溯的最终 `_geo` 原生结果组,中间过程文件在标准化完成后自动释放空间;wrapper 配置和临时运行文件放在 `D:\production_runtime\gf3\sarscape_runtime`
## 2026-06-15 Production Preflight Rule
GF3 SARscape production must check existing results before invoking the external wrapper.
Skip production when either condition is true:
- SARscape native output is already complete in `GF3_SARSCAPE_NATIVE_DIRS` for every requested polarization.
- Standardized L2 output already exists in `GF3_STORAGE_DIRS/<imaging_date>/<scene_name>` with `gf3_standard_manifest.json` status `DONE`/`PARTIAL` and every requested polarization has a valid `*_L2.tif`.
This prevents reprocessing when native intermediates were cleaned but registered/standardized results still exist. The standardized L2 and registered assets are the durable result layer; source archives on UNC should not be reprocessed unless the operator explicitly removes or invalidates the existing result.