Apply current workspace changes

This commit is contained in:
2026-04-22 01:52:21 +08:00
parent cf34b9a8ed
commit cb518b431f
449 changed files with 92252 additions and 321 deletions
+2
View File
@@ -166,10 +166,12 @@ def _build_dinsar_result_payload(record: DinsarCatalogReadRecord) -> DinsarResul
task_alias=product.task_alias,
pair_key=product.pair_key,
pair_uid=product.pair_uid,
run_key=product.run_key,
network_run_id=product.network_run_id,
network_edge_id=product.network_edge_id,
policy_version=product.policy_version,
selection_strategy=product.selection_strategy,
engine_code=product.engine_code,
file_path=file_path,
min_lon=float(min_lon or 0.0),
min_lat=float(min_lat or 0.0),
+73 -9
View File
@@ -13,6 +13,7 @@ from ..config import read_int_env, settings
from ..models import AuthUserORM
from ..services.dinsar_production_service import dinsar_production_service
from ..services.job_queue_service import job_queue_service
from ..services.pyint_input_assets_service import build_pyint_input_preview, summarize_preview_blockers
from ..services.task_service import task_service
router = APIRouter(prefix="/dinsar-production", tags=["dinsar-production"])
@@ -29,11 +30,17 @@ ISCE2_PRODUCTION_JOB_MAX_ATTEMPTS = read_int_env(
minimum=1,
maximum=10,
)
PYINT_PRODUCTION_JOB_MAX_ATTEMPTS = read_int_env(
"PYINT_PRODUCTION_JOB_MAX_ATTEMPTS",
1,
minimum=1,
maximum=10,
)
class RunJobRequest(BaseModel):
engine_code: str = Field(..., description="Engine code: sarscape / isce2 / landsar")
profile: str = Field(..., description="Engine profile, for example custom6 / lt1_stripmap")
engine_code: str = Field(..., description="Engine code: sarscape / isce2 / pyint / landsar")
profile: str = Field(..., description="Engine profile, for example custom6 / lt1_stripmap / lt1_gamma_dinsar")
root_dir: str = Field(..., description="Windows root directory")
num_to_process: int = Field(default=0, ge=0, description="How many tasks to process; 0 means all")
timeout_seconds: Optional[int] = Field(default=None, ge=60)
@@ -45,6 +52,11 @@ class WslCheckRequest(BaseModel):
smoke_test: bool = Field(default=False)
class PreviewInputAssetsRequest(BaseModel):
root_dir: str = Field(..., description="Windows root directory or a single Task_* directory")
num_to_process: int = Field(default=0, ge=0, description="How many tasks to preview; 0 means all")
def _get_registry():
from ..dinsar_engines import registry
@@ -116,6 +128,22 @@ async def run_wsl_check(
return report.to_dict()
@router.post("/engines/pyint/preview-input-assets")
async def preview_pyint_input_assets(
req: PreviewInputAssetsRequest,
current_user: AuthUserORM = Depends(_get_current_user),
):
try:
preview = await asyncio.to_thread(
build_pyint_input_preview,
req.root_dir,
req.num_to_process,
)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
return preview
@router.post("/run")
async def submit_run(
req: RunJobRequest,
@@ -144,7 +172,7 @@ async def submit_run(
)
validation_summary = None
if req.engine_code == "isce2" and hasattr(engine, "validate_root_dir"):
if hasattr(engine, "validate_root_dir"):
try:
validation_summary = await asyncio.to_thread(
engine.validate_root_dir,
@@ -154,33 +182,69 @@ async def submit_run(
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
effective_timeout_seconds = req.timeout_seconds
if effective_timeout_seconds is None:
engine_default_timeout = getattr(engine, "default_timeout_seconds", None)
if engine_default_timeout:
effective_timeout_seconds = int(engine_default_timeout)
pyint_preview = None
if req.engine_code == "pyint":
try:
pyint_preview = await asyncio.to_thread(
build_pyint_input_preview,
req.root_dir,
req.num_to_process,
)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
if not pyint_preview.get("allow_submit"):
detail = summarize_preview_blockers(pyint_preview)
raise HTTPException(
status_code=400,
detail=f"PyINT 输入资产预检未通过: {detail or '请先修复阻塞项。'}",
)
if not pyint_preview.get("allow_submit"):
detail = summarize_preview_blockers(pyint_preview)
raise HTTPException(
status_code=400,
detail=f"PyINT 输入资产预检未通过: {detail or '请先修复阻塞项。'}",
)
payload = {
"engine_code": req.engine_code,
"profile": req.profile,
"root_dir": req.root_dir,
"num_to_process": req.num_to_process,
"timeout_seconds": req.timeout_seconds,
"timeout_seconds": effective_timeout_seconds,
"extra": dict(req.extra or {}),
}
from ..services.job_handlers import JOB_TYPE_IDL_RUN_DINSAR, JOB_TYPE_ISCE2_RUN
from ..services.job_handlers import JOB_TYPE_IDL_RUN_DINSAR, JOB_TYPE_ISCE2_RUN, JOB_TYPE_PYINT_RUN
if req.engine_code == "sarscape":
payload["mode"] = "custom" if req.profile == "custom6" else "metatask"
job_type = JOB_TYPE_IDL_RUN_DINSAR
max_attempts = DINSAR_PRODUCTION_JOB_MAX_ATTEMPTS
elif req.engine_code == "isce2":
elif req.engine_code in {"isce2", "pyint"}:
if hasattr(engine, "normalize_extra"):
try:
payload["extra"] = engine.normalize_extra(payload["extra"])
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
job_type = JOB_TYPE_ISCE2_RUN
max_attempts = ISCE2_PRODUCTION_JOB_MAX_ATTEMPTS
if req.engine_code == "isce2":
job_type = JOB_TYPE_ISCE2_RUN
max_attempts = ISCE2_PRODUCTION_JOB_MAX_ATTEMPTS
else:
job_type = JOB_TYPE_PYINT_RUN
max_attempts = PYINT_PRODUCTION_JOB_MAX_ATTEMPTS
if validation_summary is not None:
validated_task_count = validation_summary.get("task_count", 0)
if pyint_preview is not None:
validated_task_count = int(pyint_preview.get("selected_task_count", validated_task_count) or 0)
payload["extra"].update(
{
"__validated_task_count": validation_summary.get("task_count", 0),
"__validated_task_count": validated_task_count,
"__validated_mode": validation_summary.get("mode", ""),
}
)
+25 -15
View File
@@ -1,32 +1,38 @@
from __future__ import annotations
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel, Field
from ..database import get_db
from ..models import AuthUserORM
from ..services.job_queue_service import job_queue_service
from ..services.task_service import task_service
from ..services.unpack_service import get_unpack_config
from ..services.unpack_service import build_unpack_run_config, get_unpack_config
from .dependencies import _require_admin
router = APIRouter()
class UnpackRunRequest(BaseModel):
max_files_per_run: Optional[int] = Field(default=None, ge=0)
max_runtime_minutes: Optional[int] = Field(default=None, ge=0)
@router.get("/unpack/config")
async def get_unpack_config_endpoint():
"""
获取解包配置 (来源 .env)。
"""
return get_unpack_config()
@router.post("/unpack/run", status_code=202)
async def run_unpack_endpoint(background_tasks: BackgroundTasks, admin_user: AuthUserORM = Depends(_require_admin)):
"""
触发一次解包任务。
"""
config = get_unpack_config()
async def run_unpack_endpoint(
request: Optional[UnpackRunRequest] = None,
admin_user: AuthUserORM = Depends(_require_admin),
):
del admin_user
overrides = request.model_dump(exclude_none=True) if request else None
config = build_unpack_run_config(overrides)
if not config.get("source_dirs"):
raise HTTPException(status_code=400, detail="UNPACK_SOURCE_DIRS is not configured.")
@@ -36,7 +42,11 @@ async def run_unpack_endpoint(background_tasks: BackgroundTasks, admin_user: Aut
"Archive unpack",
params=config,
)
await job_queue_service.create_job("UNPACK_ARCHIVES", payload=config, task_id=task_id)
await job_queue_service.create_job(
"UNPACK_ARCHIVES",
payload=config,
task_id=task_id,
)
return {"message": "Unpack task queued", "task_id": task_id}
except ValueError as e:
raise HTTPException(status_code=409, detail=str(e))
except ValueError as exc:
raise HTTPException(status_code=409, detail=str(exc)) from exc