feat(scada): add calibrated monitoring and incident recovery
This commit is contained in:
@@ -7,6 +7,7 @@ from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, Depends, Header, HTTPException, Query, status
|
||||
from psycopg import AsyncConnection
|
||||
from app.auth.metadata_dependencies import get_metadata_repository
|
||||
|
||||
from app.auth.permissions import (
|
||||
OPERATIONAL_TASK_EXECUTE,
|
||||
@@ -22,6 +23,7 @@ from app.auth.project_dependencies import (
|
||||
ProjectContext,
|
||||
get_project_context,
|
||||
get_project_pg_connection,
|
||||
get_project_timescale_connection,
|
||||
)
|
||||
from app.domain.domain_errors import DomainError
|
||||
from app.domain.schemas.workbench_runtime import WorkbenchDateTime
|
||||
@@ -90,6 +92,31 @@ from app.services.operational_tasks import OperationalTaskService
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/operational-tasks/recovery/reconcile")
|
||||
async def reconcile_task_recoveries(
|
||||
command: RecoverExpiredLeasesCommand,
|
||||
ctx: ProjectContext = Depends(require_permission(OPERATIONAL_TASK_EXECUTE)),
|
||||
conn: AsyncConnection = Depends(get_project_pg_connection),
|
||||
timescale=Depends(get_project_timescale_connection),
|
||||
metadata=Depends(get_metadata_repository),
|
||||
) -> dict[str, int]:
|
||||
from app.domain.schemas.project_settings import ProjectSettings
|
||||
from app.services.operational_recovery import reconcile_recoveries
|
||||
|
||||
if SCADA_VIEW not in permissions_for_context(ctx):
|
||||
raise HTTPException(403, "恢复监测需要 SCADA 查看权限")
|
||||
project = await metadata.get_project_by_id(ctx.project_id)
|
||||
if project is None:
|
||||
raise HTTPException(404, "当前项目不存在")
|
||||
try:
|
||||
resumed = await reconcile_recoveries(_service(ctx, conn), timescale,
|
||||
ProjectSettings.model_validate(project.settings or {}), limit=command.limit,
|
||||
created_from=command.task_created_from)
|
||||
return {"resumed": resumed}
|
||||
except DomainError as exc:
|
||||
_raise_domain(exc)
|
||||
|
||||
|
||||
def _service(ctx: ProjectContext, conn: AsyncConnection) -> OperationalTaskService:
|
||||
return OperationalTaskService(
|
||||
OperationalTaskRepository(conn, ctx.project_id),
|
||||
|
||||
@@ -44,12 +44,12 @@ async def replay_capabilities(repo=Depends(repository), ctx=Depends(require_perm
|
||||
monitoring_ready = bool(
|
||||
monitoring
|
||||
and len(monitoring.pressure_device_ids) >= 5
|
||||
and monitoring.outlet_flow_device_ids
|
||||
and (monitoring.decision_policy == "weighted" or monitoring.outlet_flow_device_ids)
|
||||
)
|
||||
if monitoring_ready:
|
||||
monitoring_reason = None
|
||||
elif monitoring is None:
|
||||
monitoring_reason = "项目尚未配置压力与出厂边界流量监测角色"
|
||||
monitoring_reason = "项目尚未配置压力监测角色及组合判定规则"
|
||||
elif len(monitoring.pressure_device_ids) < 5:
|
||||
monitoring_reason = "爆管侦测至少需要 5 个压力监测设备"
|
||||
else:
|
||||
@@ -60,6 +60,7 @@ async def replay_capabilities(repo=Depends(repository), ctx=Depends(require_perm
|
||||
monitoring_interval_seconds=settings.scada_monitoring_interval_seconds,
|
||||
burst_monitoring_ready=monitoring_ready,
|
||||
burst_monitoring_reason=monitoring_reason,
|
||||
monitoring_config=monitoring,
|
||||
current=snapshot(current) if current else None,
|
||||
)
|
||||
|
||||
@@ -86,7 +87,7 @@ async def replay_command(replay_id: UUID, command: ScadaReplayCommand, repo=Depe
|
||||
timescale=Depends(get_project_timescale_connection)):
|
||||
service = ScadaReplayService(repo, ctx, timescale)
|
||||
result = await service.command(replay_id, command)
|
||||
if command.action in {"step", "retry"} and result.step_pending:
|
||||
if command.action in {"step", "monitor", "retry"} and result.step_pending:
|
||||
return await service.process_pending_step(replay_id)
|
||||
return result
|
||||
|
||||
|
||||
Reference in New Issue
Block a user