Stage 211: forecast.threshold_crossed (infra/api/app.py)
Browse files- infra/api/app.py +28 -0
infra/api/app.py
CHANGED
|
@@ -2067,6 +2067,34 @@ def create_app(db_path: Optional[str] = None,
|
|
| 2067 |
except ValueError as e:
|
| 2068 |
raise ApiError("bad_request", str(e), status=400) from e
|
| 2069 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2070 |
@app.post("/tenants/{tenant_id}/forecasts/batch", tags=["runs"])
|
| 2071 |
async def post_forecasts_batch_route(
|
| 2072 |
tenant_id: str,
|
|
|
|
| 2067 |
except ValueError as e:
|
| 2068 |
raise ApiError("bad_request", str(e), status=400) from e
|
| 2069 |
|
| 2070 |
+
# Stage 211 — manual trigger for the forecast-threshold scan.
|
| 2071 |
+
# Admin role required: this fires webhooks + writes audit rows,
|
| 2072 |
+
# so the operator running it should have the same authority as
|
| 2073 |
+
# someone who'd configure a cron. The intended runtime is a
|
| 2074 |
+
# daily cron picking this up; for now the dashboard / curl is
|
| 2075 |
+
# the trigger.
|
| 2076 |
+
@app.post("/tenants/{tenant_id}/forecast/check_thresholds",
|
| 2077 |
+
tags=["runs"])
|
| 2078 |
+
async def post_forecast_check_thresholds_route(
|
| 2079 |
+
tenant_id: str, body: dict,
|
| 2080 |
+
key: ApiKey = Depends(auth_dep),
|
| 2081 |
+
):
|
| 2082 |
+
require_tenant_access(key, tenant_id)
|
| 2083 |
+
require_role(key, ROLE_ADMIN)
|
| 2084 |
+
days_threshold = body.get("days_threshold", 7)
|
| 2085 |
+
if not isinstance(days_threshold, int):
|
| 2086 |
+
raise ApiError(
|
| 2087 |
+
"bad_request",
|
| 2088 |
+
"days_threshold must be an integer", status=400,
|
| 2089 |
+
)
|
| 2090 |
+
try:
|
| 2091 |
+
return svc.check_forecast_thresholds(
|
| 2092 |
+
tenant_id, days_threshold=days_threshold,
|
| 2093 |
+
actor=key.key_id,
|
| 2094 |
+
)
|
| 2095 |
+
except ValueError as e:
|
| 2096 |
+
raise ApiError("bad_request", str(e), status=400) from e
|
| 2097 |
+
|
| 2098 |
@app.post("/tenants/{tenant_id}/forecasts/batch", tags=["runs"])
|
| 2099 |
async def post_forecasts_batch_route(
|
| 2100 |
tenant_id: str,
|