feat: cycle
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
from typing import Any, Dict, Optional
|
||||
from services.database import get_cycle_runs, get_cycle_run, set_config, get_config, list_cycle_context_snapshots, get_cycle_context_snapshot, get_ai_call_logs
|
||||
from services.auto_cycle import get_status, trigger_manual, restart_scheduler
|
||||
from services.auto_cycle import get_status, trigger_manual, restart_scheduler, CYCLE_STEP_CATALOG
|
||||
|
||||
router = APIRouter(prefix="/api/cycle", tags=["cycle"])
|
||||
|
||||
@@ -13,6 +13,14 @@ def cycle_status():
|
||||
return get_status()
|
||||
|
||||
|
||||
@router.get("/step-catalog")
|
||||
def cycle_step_catalog():
|
||||
"""Self-describing catalog of every configurable cycle step (mirrors
|
||||
GET /api/ai-desks/signal-catalog) — lets Config.tsx render a generic
|
||||
form instead of hand-coded sliders per knob."""
|
||||
return {"steps": CYCLE_STEP_CATALOG}
|
||||
|
||||
|
||||
@router.get("/history")
|
||||
def cycle_history(limit: int = 20):
|
||||
"""List recent cycle runs."""
|
||||
@@ -51,6 +59,7 @@ class CycleConfigRequest(BaseModel):
|
||||
maturity_threshold_pct: Optional[int] = None
|
||||
weekend_cycle_enabled: Optional[bool] = None
|
||||
weekend_cycle_times: Optional[str] = None # "HH:MM,HH:MM" in UTC
|
||||
cycle_step_config: Optional[Dict[str, Dict[str, Any]]] = None # {step_id: {param: value}}
|
||||
|
||||
|
||||
@router.post("/config")
|
||||
@@ -102,6 +111,18 @@ def update_cycle_config(req: CycleConfigRequest):
|
||||
if not re.match(r'^(\d{2}:\d{2})(,\d{2}:\d{2})*$', req.weekend_cycle_times.strip()):
|
||||
raise HTTPException(400, "weekend_cycle_times must be 'HH:MM' or 'HH:MM,HH:MM,...'")
|
||||
set_config("weekend_cycle_times", req.weekend_cycle_times.strip())
|
||||
if req.cycle_step_config is not None:
|
||||
import json
|
||||
known_ids = {step["id"] for step in CYCLE_STEP_CATALOG}
|
||||
unknown = set(req.cycle_step_config.keys()) - known_ids
|
||||
if unknown:
|
||||
raise HTTPException(400, f"Unknown cycle step id(s): {sorted(unknown)}")
|
||||
# Merge over whatever is already saved so a partial update from the UI
|
||||
# never wipes out other steps' settings.
|
||||
current = json.loads(get_config("cycle_step_config") or "{}")
|
||||
for step_id, params in req.cycle_step_config.items():
|
||||
current[step_id] = {**current.get(step_id, {}), **params}
|
||||
set_config("cycle_step_config", json.dumps(current))
|
||||
|
||||
# Restart scheduler to pick up changes
|
||||
restart_scheduler()
|
||||
|
||||
@@ -32,6 +32,14 @@ def geo_news(force_refresh: bool = False):
|
||||
|
||||
@router.get("/risk-score")
|
||||
def risk_score():
|
||||
"""Frozen per-cycle AI-judged score (services.ai_analyzer.ai_score_geo_risk,
|
||||
saved once per auto-cycle run in geo_risk_snapshots) — no longer
|
||||
recomputed live on every request. Falls back to a live algorithmic
|
||||
compute only if no cycle has ever run yet (e.g. fresh install)."""
|
||||
from services.database import get_latest_geo_risk_snapshot
|
||||
snapshot = get_latest_geo_risk_snapshot()
|
||||
if snapshot:
|
||||
return snapshot
|
||||
news = _news_cache["data"] or fetch_geo_news()
|
||||
return compute_geo_risk_score(news)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user