feat: cycle

This commit is contained in:
OpenSquared
2026-07-15 12:03:02 +02:00
parent ce9c0b53a9
commit 2d474c9194
9 changed files with 471 additions and 67 deletions

View File

@@ -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()

View File

@@ -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)