fix(platform): govern geospatial analysis and raster handoffs
This commit is contained in:
@@ -12,7 +12,10 @@ from app.schemas.spw_terrain import SpwTerrainAcquireRequest
|
||||
from app.schemas.official_vector import OfficialVectorAcquireRequest
|
||||
from app.schemas.flood_hazard import FloodHazardAcquireRequest
|
||||
from app.schemas.thematic_raster import ThematicRasterAcquireRequest
|
||||
from app.schemas.bathymetry import BathymetryProfileAcquireRequest, MdkBathymetryAcquireRequest
|
||||
from app.schemas.bathymetry import (
|
||||
BathymetryProfileAcquireRequest,
|
||||
MdkBathymetryAcquireRequest,
|
||||
)
|
||||
from app.schemas.job import JobCreate
|
||||
from app.schemas.operations import VectorSelectionBBox
|
||||
from app.schemas.orthophoto import OrthophotoAcquireRequest
|
||||
@@ -20,12 +23,20 @@ from app.services.aoi_operation_service import AoiOperationService
|
||||
from app.services.grb_acquisition_service import GrbAcquisitionService
|
||||
from app.services.dhmv_acquisition_service import DhmvAcquisitionService
|
||||
from app.services.spw_terrain_service import SpwTerrainService
|
||||
from app.services.official_vector_acquisition_service import OfficialVectorAcquisitionService
|
||||
from app.services.official_vector_acquisition_service import (
|
||||
OfficialVectorAcquisitionService,
|
||||
)
|
||||
from app.services.flood_hazard_acquisition_service import FloodHazardAcquisitionService
|
||||
from app.services.thematic_raster_acquisition_service import ThematicRasterAcquisitionService
|
||||
from app.services.thematic_raster_acquisition_service import (
|
||||
ThematicRasterAcquisitionService,
|
||||
)
|
||||
from app.services.walous_land_cover_service import WalousLandCoverService
|
||||
from app.services.bathymetry_profile_acquisition_service import BathymetryProfileAcquisitionService
|
||||
from app.services.mdk_bathymetry_acquisition_service import MdkBathymetryAcquisitionService
|
||||
from app.services.bathymetry_profile_acquisition_service import (
|
||||
BathymetryProfileAcquisitionService,
|
||||
)
|
||||
from app.services.mdk_bathymetry_acquisition_service import (
|
||||
MdkBathymetryAcquisitionService,
|
||||
)
|
||||
from app.services.job_service import JobService
|
||||
from app.services.orthophoto_acquisition_service import OrthophotoAcquisitionService
|
||||
|
||||
@@ -40,65 +51,202 @@ class AoiOperationExecutor:
|
||||
AoiOperationService._refresh_parent(db, operation_id)
|
||||
return AoiOperationService.read(db, project_id, operation_id)
|
||||
operation = db.get(AoiOperation, operation_id)
|
||||
child = JobService.create_job(db, JobCreate(
|
||||
job_type=f"aoi.{operation.operation_type}.partition",
|
||||
project_id=project_id,
|
||||
parameters_json={
|
||||
"aoi_operation_id": str(operation_id),
|
||||
"partition_id": str(partition.id),
|
||||
"partition_key": partition.partition_key,
|
||||
"provider_key": partition.provider_key,
|
||||
"product_key": partition.product_key,
|
||||
},
|
||||
))
|
||||
child = JobService.create_job(
|
||||
db,
|
||||
JobCreate(
|
||||
job_type=f"aoi.{operation.operation_type}.partition",
|
||||
project_id=project_id,
|
||||
parameters_json={
|
||||
"aoi_operation_id": str(operation_id),
|
||||
"partition_id": str(partition.id),
|
||||
"partition_key": partition.partition_key,
|
||||
"provider_key": partition.provider_key,
|
||||
"product_key": partition.product_key,
|
||||
},
|
||||
),
|
||||
)
|
||||
partition = db.get(AoiOperationPartition, partition.id)
|
||||
partition.child_job_id = child.id
|
||||
db.add(partition); db.commit()
|
||||
db.add(partition)
|
||||
db.commit()
|
||||
JobService.mark_running(db, child.id)
|
||||
try:
|
||||
result = AoiOperationExecutor._dispatch(db, project_id, operation, partition)
|
||||
output_id = result.get("output_dataset_id") if isinstance(result, dict) else None
|
||||
JobService.mark_success(db, child.id, result=result, output_dataset_id=UUID(str(output_id)) if output_id else None)
|
||||
return AoiOperationService.complete(db, project_id, operation_id, partition.id, result)
|
||||
result = AoiOperationExecutor._dispatch(
|
||||
db, project_id, operation, partition
|
||||
)
|
||||
output_id = (
|
||||
result.get("output_dataset_id") if isinstance(result, dict) else None
|
||||
)
|
||||
JobService.mark_success(
|
||||
db,
|
||||
child.id,
|
||||
result=result,
|
||||
output_dataset_id=UUID(str(output_id)) if output_id else None,
|
||||
)
|
||||
return AoiOperationService.complete(
|
||||
db, project_id, operation_id, partition.id, result
|
||||
)
|
||||
except AppError as exc:
|
||||
JobService.mark_failed(db, child.id, exc.message, {"code": exc.code, "details": exc.details})
|
||||
return AoiOperationService.fail(db, project_id, operation_id, partition.id, exc.message, AoiOperationExecutor._retryable(exc), {"code": exc.code, "details": exc.details})
|
||||
JobService.mark_failed(
|
||||
db, child.id, exc.message, {"code": exc.code, "details": exc.details}
|
||||
)
|
||||
return AoiOperationService.fail(
|
||||
db,
|
||||
project_id,
|
||||
operation_id,
|
||||
partition.id,
|
||||
exc.message,
|
||||
AoiOperationExecutor._retryable(exc),
|
||||
{"code": exc.code, "details": exc.details},
|
||||
)
|
||||
except Exception:
|
||||
try:
|
||||
db.rollback()
|
||||
JobService.mark_failed(db, child.id, "Unexpected partition execution error", {"code": "AOI_PARTITION_INTERNAL_ERROR"})
|
||||
JobService.mark_failed(
|
||||
db,
|
||||
child.id,
|
||||
"Unexpected partition execution error",
|
||||
{"code": "AOI_PARTITION_INTERNAL_ERROR"},
|
||||
)
|
||||
finally:
|
||||
AoiOperationService.fail(db, project_id, operation_id, partition.id, "Unexpected partition execution error", True, {"code": "AOI_PARTITION_INTERNAL_ERROR"})
|
||||
AoiOperationService.fail(
|
||||
db,
|
||||
project_id,
|
||||
operation_id,
|
||||
partition.id,
|
||||
"Unexpected partition execution error",
|
||||
True,
|
||||
{"code": "AOI_PARTITION_INTERNAL_ERROR"},
|
||||
)
|
||||
raise
|
||||
|
||||
@staticmethod
|
||||
def _dispatch(db, project_id: UUID, operation: AoiOperation, partition: AoiOperationPartition) -> dict:
|
||||
def _dispatch(
|
||||
db, project_id: UUID, operation: AoiOperation, partition: AoiOperationPartition
|
||||
) -> dict:
|
||||
geometry = to_shape(partition.geometry)
|
||||
min_x, min_y, max_x, max_y = geometry.bounds
|
||||
bbox = VectorSelectionBBox(min_x=min_x, min_y=min_y, max_x=max_x, max_y=max_y, crs="EPSG:4326")
|
||||
force_refresh = bool((operation.request_json or {}).get("parameters_json", {}).get("force_refresh", False))
|
||||
bbox = VectorSelectionBBox(
|
||||
min_x=min_x, min_y=min_y, max_x=max_x, max_y=max_y, crs="EPSG:4326"
|
||||
)
|
||||
force_refresh = bool(
|
||||
(operation.request_json or {})
|
||||
.get("parameters_json", {})
|
||||
.get("force_refresh", False)
|
||||
)
|
||||
if partition.provider_key == "grb":
|
||||
return GrbAcquisitionService.acquire(db, project_id, GrbAcquireRequest(bbox=bbox, area_id=operation.area_id, product_key=partition.product_key, force_refresh=force_refresh))
|
||||
return GrbAcquisitionService.acquire(
|
||||
db,
|
||||
project_id,
|
||||
GrbAcquireRequest(
|
||||
bbox=bbox,
|
||||
area_id=operation.area_id,
|
||||
product_key=partition.product_key,
|
||||
force_refresh=force_refresh,
|
||||
),
|
||||
)
|
||||
if partition.provider_key == "orthophoto":
|
||||
return OrthophotoAcquisitionService.acquire(db, project_id, OrthophotoAcquireRequest(bbox=bbox, area_id=operation.area_id, product_key=partition.product_key, force_refresh=force_refresh))
|
||||
return OrthophotoAcquisitionService.acquire(
|
||||
db,
|
||||
project_id,
|
||||
OrthophotoAcquireRequest(
|
||||
bbox=bbox,
|
||||
area_id=operation.area_id,
|
||||
product_key=partition.product_key,
|
||||
force_refresh=force_refresh,
|
||||
),
|
||||
)
|
||||
if partition.provider_key == "dhmv":
|
||||
return DhmvAcquisitionService.acquire(db, project_id, DhmvAcquireRequest(bbox=bbox, area_id=operation.area_id, product_key=partition.product_key, force_refresh=force_refresh))
|
||||
return DhmvAcquisitionService.acquire(
|
||||
db,
|
||||
project_id,
|
||||
DhmvAcquireRequest(
|
||||
bbox=bbox,
|
||||
area_id=operation.area_id,
|
||||
product_key=partition.product_key,
|
||||
force_refresh=force_refresh,
|
||||
),
|
||||
)
|
||||
if partition.provider_key == "spw_terrain":
|
||||
return SpwTerrainService.acquire(db, project_id, SpwTerrainAcquireRequest(bbox=bbox, area_id=operation.area_id, product_key=partition.product_key, force_refresh=force_refresh))
|
||||
return SpwTerrainService.acquire(
|
||||
db,
|
||||
project_id,
|
||||
SpwTerrainAcquireRequest(
|
||||
bbox=bbox,
|
||||
area_id=operation.area_id,
|
||||
product_key=partition.product_key,
|
||||
force_refresh=force_refresh,
|
||||
),
|
||||
)
|
||||
if partition.provider_key == "official_vector":
|
||||
return OfficialVectorAcquisitionService.acquire(db, project_id, OfficialVectorAcquireRequest(bbox=bbox, area_id=operation.area_id, product_key=partition.product_key, force_refresh=force_refresh))
|
||||
return OfficialVectorAcquisitionService.acquire(
|
||||
db,
|
||||
project_id,
|
||||
OfficialVectorAcquireRequest(
|
||||
bbox=bbox,
|
||||
area_id=operation.area_id,
|
||||
product_key=partition.product_key,
|
||||
force_refresh=force_refresh,
|
||||
),
|
||||
)
|
||||
if partition.provider_key == "flood_hazard":
|
||||
return FloodHazardAcquisitionService.acquire(db, project_id, FloodHazardAcquireRequest(bbox=bbox, area_id=operation.area_id, product_key=partition.product_key, force_refresh=force_refresh))
|
||||
return FloodHazardAcquisitionService.acquire(
|
||||
db,
|
||||
project_id,
|
||||
FloodHazardAcquireRequest(
|
||||
bbox=bbox,
|
||||
area_id=operation.area_id,
|
||||
product_key=partition.product_key,
|
||||
force_refresh=force_refresh,
|
||||
),
|
||||
)
|
||||
if partition.provider_key == "thematic_raster":
|
||||
return ThematicRasterAcquisitionService.acquire(db, project_id, ThematicRasterAcquireRequest(bbox=bbox, area_id=operation.area_id, product_key=partition.product_key, force_refresh=force_refresh))
|
||||
return ThematicRasterAcquisitionService.acquire(
|
||||
db,
|
||||
project_id,
|
||||
ThematicRasterAcquireRequest(
|
||||
bbox=bbox,
|
||||
area_id=operation.area_id,
|
||||
product_key=partition.product_key,
|
||||
force_refresh=force_refresh,
|
||||
),
|
||||
)
|
||||
if partition.provider_key == "walous":
|
||||
return WalousLandCoverService.acquire(db, project_id, ThematicRasterAcquireRequest(bbox=bbox, area_id=operation.area_id, product_key=partition.product_key, force_refresh=force_refresh))
|
||||
return WalousLandCoverService.acquire(
|
||||
db,
|
||||
project_id,
|
||||
ThematicRasterAcquireRequest(
|
||||
bbox=bbox,
|
||||
area_id=operation.area_id,
|
||||
product_key=partition.product_key,
|
||||
force_refresh=force_refresh,
|
||||
),
|
||||
)
|
||||
if partition.provider_key == "bathymetry_profiles":
|
||||
return BathymetryProfileAcquisitionService.acquire(db, project_id, BathymetryProfileAcquireRequest(bbox=bbox, area_id=operation.area_id, force_refresh=force_refresh))
|
||||
return BathymetryProfileAcquisitionService.acquire(
|
||||
db,
|
||||
project_id,
|
||||
BathymetryProfileAcquireRequest(
|
||||
bbox=bbox, area_id=operation.area_id, force_refresh=force_refresh
|
||||
),
|
||||
)
|
||||
if partition.provider_key == "mdk_bathymetry":
|
||||
return MdkBathymetryAcquisitionService.acquire(db, project_id, MdkBathymetryAcquireRequest(bbox=bbox, area_id=operation.area_id, force_refresh=force_refresh))
|
||||
raise AppError(code="AOI_PROVIDER_UNSUPPORTED", message="No governed AOI executor is registered for this provider", details={"provider_key": partition.provider_key}, status_code=422)
|
||||
return MdkBathymetryAcquisitionService.acquire(
|
||||
db,
|
||||
project_id,
|
||||
MdkBathymetryAcquireRequest(
|
||||
bbox=bbox, area_id=operation.area_id, force_refresh=force_refresh
|
||||
),
|
||||
)
|
||||
raise AppError(
|
||||
code="AOI_PROVIDER_UNSUPPORTED",
|
||||
message="No governed AOI executor is registered for this provider",
|
||||
details={"provider_key": partition.provider_key},
|
||||
status_code=422,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _retryable(error: AppError) -> bool:
|
||||
return error.status_code >= 500 or error.code.endswith(("TIMEOUT", "UNAVAILABLE", "TLS_ERROR"))
|
||||
return error.status_code >= 500 or error.code.endswith(
|
||||
("TIMEOUT", "UNAVAILABLE", "TLS_ERROR")
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user