mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-06-22 14:00:06 +02:00
cfbeabda1e
* feat(telegram): auto-translate OSINT channel posts to English Cherry-picked from @Bobpick PR #391 (telegram-only slice): server-side translation during fetch, SHOW ORIGINAL toggle in TelegramOsintPopup, and on-demand /api/telegram-feed?lang=. Co-authored-by: Robert Pickett <bobpickettsr@yahoo.com> Co-authored-by: Cursor <cursoragent@cursor.com> * feat(gt): experimental Derived OSINT analytics with lean-node safeguards Cherry-picked from @Bobpick PR #391 (GT + OpenClaw slice): Bayesian strategic-risk engine, map overlay, OpenClaw commands, and telegram_rhetoric watchdog. Off by default (GT_ANALYTICS_ENABLED=false, gt_risk layer false). 1 vCPU nodes get cgroup detection, UI warning on layer toggle, and lean profile that skips scheduled ingest/Louvain unless GT_ANALYTICS_ACK_LOW_CPU=true. Backtest HUD removed from dashboard (OpenClaw/API regression only). Co-authored-by: Robert Pickett <bobpickettsr@yahoo.com> Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Robert Pickett <bobpickettsr@yahoo.com> Co-authored-by: Cursor <cursoragent@cursor.com>
339 lines
10 KiB
Python
339 lines
10 KiB
Python
"""Strategic Risk Analytics API — game-theoretic early warning overlays."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from typing import Any
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Request
|
|
from pydantic import BaseModel, Field
|
|
|
|
from auth import require_local_operator
|
|
from limiter import limiter
|
|
from analytics.backtest import (
|
|
DEFAULT_BACKTEST_ALERT_THRESHOLD,
|
|
run_historical_backtest,
|
|
tune_alert_threshold,
|
|
)
|
|
from analytics.feed_adapter import normalize_feed_item
|
|
from analytics.integration import get_gt_engine, refresh_from_latest_data
|
|
from analytics.gt_alerts import top_gt_alerts
|
|
from analytics.micro_rolling import micro_rolling_report
|
|
from analytics.rolling_backtest import (
|
|
freeze_weekly_snapshot,
|
|
label_region,
|
|
label_regions,
|
|
rolling_alert_threshold,
|
|
rolling_report,
|
|
score_week,
|
|
)
|
|
from analytics.weekly_store import load_week
|
|
from analytics.settings import gt_analytics_enabled
|
|
from services.fetchers._store import _data_lock, get_latest_data_subset_refs, latest_data
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
class RiskHeatmapRequest(BaseModel):
|
|
"""Optional batch ingest + refresh controls for POST /api/analytics/risk_heatmap."""
|
|
|
|
refresh: bool = True
|
|
items: list[dict[str, Any]] = Field(default_factory=list)
|
|
|
|
|
|
class RollingFreezeRequest(BaseModel):
|
|
week_id: str | None = None
|
|
force: bool = False
|
|
|
|
|
|
class RollingLabelEntry(BaseModel):
|
|
region: str
|
|
label: str
|
|
notes: str = ""
|
|
|
|
|
|
class RollingLabelRequest(BaseModel):
|
|
week_id: str
|
|
labels: list[RollingLabelEntry] = Field(default_factory=list)
|
|
|
|
|
|
def _empty_heatmap() -> dict[str, Any]:
|
|
return {
|
|
"enabled": False,
|
|
"type": "FeatureCollection",
|
|
"features": [],
|
|
"clusters": [],
|
|
"processed": 0,
|
|
"timestamp": None,
|
|
}
|
|
|
|
|
|
def _gt_risk_payload() -> dict[str, Any]:
|
|
snap = get_latest_data_subset_refs("gt_risk")
|
|
payload = snap.get("gt_risk")
|
|
if not isinstance(payload, dict):
|
|
return _empty_heatmap()
|
|
heatmap = payload.get("heatmap") or {"type": "FeatureCollection", "features": []}
|
|
return {
|
|
"enabled": bool(payload.get("enabled")),
|
|
"type": heatmap.get("type", "FeatureCollection"),
|
|
"features": list(heatmap.get("features") or []),
|
|
"clusters": list(payload.get("clusters") or []),
|
|
"processed": int(payload.get("processed") or 0),
|
|
"timestamp": payload.get("timestamp"),
|
|
}
|
|
|
|
|
|
@router.get("/api/analytics/risk_heatmap")
|
|
@limiter.limit("60/minute")
|
|
async def risk_heatmap_get(request: Request) -> dict[str, Any]:
|
|
"""Return cached GeoJSON risk overlay (posterior scores per region)."""
|
|
if not gt_analytics_enabled():
|
|
return _empty_heatmap()
|
|
return _gt_risk_payload()
|
|
|
|
|
|
@router.post("/api/analytics/risk_heatmap")
|
|
@limiter.limit("12/minute")
|
|
async def risk_heatmap_post(
|
|
request: Request,
|
|
body: RiskHeatmapRequest,
|
|
_: None = Depends(require_local_operator),
|
|
) -> dict[str, Any]:
|
|
"""
|
|
Ingest optional feed items and/or refresh beliefs from latest intel layers.
|
|
|
|
Requires local operator auth — intended for OpenClaw agents and admin tooling.
|
|
"""
|
|
if not gt_analytics_enabled():
|
|
raise HTTPException(status_code=503, detail="Strategic Risk Analytics is disabled")
|
|
|
|
engine = get_gt_engine()
|
|
if engine is None:
|
|
raise HTTPException(status_code=503, detail="Strategic Risk Analytics engine unavailable")
|
|
|
|
ingested = 0
|
|
for raw in body.items:
|
|
if not isinstance(raw, dict):
|
|
continue
|
|
source_type = str(raw.get("source_type") or "manual")
|
|
item = normalize_feed_item(raw, source_type=source_type)
|
|
result = engine.process_feed_item(item)
|
|
if result and not result.get("skipped"):
|
|
ingested += 1
|
|
|
|
summary: dict[str, Any] = {"ingested": ingested}
|
|
if body.refresh:
|
|
with _data_lock:
|
|
snapshot = dict(latest_data)
|
|
summary.update(refresh_from_latest_data(snapshot, persist=True))
|
|
|
|
payload = _gt_risk_payload()
|
|
payload["ingested"] = ingested
|
|
payload["refresh"] = bool(body.refresh)
|
|
return payload
|
|
|
|
|
|
@router.get("/api/analytics/dossier/{region}")
|
|
@limiter.limit("30/minute")
|
|
async def analytics_dossier(request: Request, region: str) -> dict[str, Any]:
|
|
"""Game-theoretic rationale, recent costly signals, and scenario sketches."""
|
|
region_key = str(region or "").strip().lower()
|
|
if not region_key or len(region_key) > 120:
|
|
raise HTTPException(status_code=400, detail="Invalid region identifier")
|
|
|
|
if not gt_analytics_enabled():
|
|
return {
|
|
"enabled": False,
|
|
"region": region_key,
|
|
"current_risk": 0.0,
|
|
"interpretation": "Strategic Risk Analytics is disabled.",
|
|
"recent_signals": [],
|
|
"scenarios": [],
|
|
}
|
|
|
|
engine = get_gt_engine()
|
|
if engine is None:
|
|
raise HTTPException(status_code=503, detail="Strategic Risk Analytics engine unavailable")
|
|
|
|
dossier = engine.get_dossier(region_key)
|
|
dossier["enabled"] = True
|
|
return dossier
|
|
|
|
|
|
@router.get("/api/analytics/backtest")
|
|
@limiter.limit("6/minute")
|
|
async def analytics_backtest(
|
|
request: Request,
|
|
expanded: bool = True,
|
|
tune: bool = False,
|
|
target_confidence: float = 0.95,
|
|
) -> dict[str, Any]:
|
|
"""
|
|
Run labeled historical backtest and return accuracy + Wilson 95% CI.
|
|
|
|
``confidence_rate`` is the Wilson lower bound (conservative pass metric).
|
|
"""
|
|
if not gt_analytics_enabled():
|
|
return {
|
|
"enabled": False,
|
|
"message": "Strategic Risk Analytics is disabled.",
|
|
}
|
|
|
|
if tune:
|
|
threshold, report = tune_alert_threshold(target_confidence=target_confidence)
|
|
else:
|
|
threshold = DEFAULT_BACKTEST_ALERT_THRESHOLD
|
|
report = run_historical_backtest(
|
|
use_expanded_suite=expanded,
|
|
alert_threshold=threshold,
|
|
target_confidence=target_confidence,
|
|
)
|
|
|
|
payload = report.to_dict()
|
|
payload["enabled"] = True
|
|
payload["expanded_suite"] = expanded
|
|
payload["tuned"] = tune
|
|
payload["recommended_alert_threshold"] = threshold
|
|
return payload
|
|
|
|
|
|
@router.get("/api/analytics/rolling")
|
|
@limiter.limit("12/minute")
|
|
async def analytics_rolling(
|
|
request: Request,
|
|
weeks: int = 8,
|
|
target_confidence: float = 0.80,
|
|
) -> dict[str, Any]:
|
|
"""Rolling weekly operational validation — accuracy trend with delayed labels."""
|
|
if not gt_analytics_enabled():
|
|
return {
|
|
"enabled": False,
|
|
"message": "Strategic Risk Analytics is disabled.",
|
|
}
|
|
|
|
report = rolling_report(weeks=max(1, min(weeks, 52)), target_confidence=target_confidence)
|
|
report["enabled"] = True
|
|
return report
|
|
|
|
|
|
@router.get("/api/analytics/alerts")
|
|
@limiter.limit("30/minute")
|
|
async def analytics_top_alerts(
|
|
request: Request,
|
|
limit: int = 8,
|
|
) -> dict[str, Any]:
|
|
"""Top GT risk regions ranked by score — fly-to targets for the map."""
|
|
if not gt_analytics_enabled():
|
|
return {
|
|
"enabled": False,
|
|
"message": "Strategic Risk Analytics is disabled.",
|
|
}
|
|
|
|
report = top_gt_alerts(limit=max(1, min(limit, 25)))
|
|
report["enabled"] = True
|
|
return report
|
|
|
|
|
|
@router.get("/api/analytics/rolling/micro")
|
|
@limiter.limit("30/minute")
|
|
async def analytics_rolling_micro(
|
|
request: Request,
|
|
window_days: int = 3,
|
|
limit: int = 15,
|
|
) -> dict[str, Any]:
|
|
"""Rolling 3-day micro average — spot vs baseline, ignition detection."""
|
|
if not gt_analytics_enabled():
|
|
return {
|
|
"enabled": False,
|
|
"message": "Strategic Risk Analytics is disabled.",
|
|
}
|
|
|
|
report = micro_rolling_report(
|
|
window_days=max(2, min(window_days, 7)),
|
|
limit=max(1, min(limit, 50)),
|
|
)
|
|
report["enabled"] = True
|
|
return report
|
|
|
|
|
|
@router.get("/api/analytics/rolling/{week_id}")
|
|
@limiter.limit("12/minute")
|
|
async def analytics_rolling_week(request: Request, week_id: str) -> dict[str, Any]:
|
|
"""Return a single frozen week snapshot and its score."""
|
|
if not gt_analytics_enabled():
|
|
return {"enabled": False, "message": "Strategic Risk Analytics is disabled."}
|
|
|
|
snapshot = load_week(str(week_id).strip())
|
|
if snapshot is None:
|
|
raise HTTPException(status_code=404, detail=f"Week {week_id} not found")
|
|
|
|
score = score_week(snapshot)
|
|
return {
|
|
"enabled": True,
|
|
"week_id": snapshot.week_id,
|
|
"snapshot": snapshot.to_dict(),
|
|
"score": score.to_dict(),
|
|
"alert_threshold": rolling_alert_threshold(),
|
|
}
|
|
|
|
|
|
@router.post("/api/analytics/rolling/freeze")
|
|
@limiter.limit("6/minute")
|
|
async def analytics_rolling_freeze(
|
|
request: Request,
|
|
body: RollingFreezeRequest,
|
|
_: None = Depends(require_local_operator),
|
|
) -> dict[str, Any]:
|
|
"""Freeze current GT scores for the ISO week (idempotent unless force=true)."""
|
|
if not gt_analytics_enabled():
|
|
raise HTTPException(status_code=503, detail="Strategic Risk Analytics is disabled")
|
|
|
|
result = freeze_weekly_snapshot(
|
|
week_id=body.week_id,
|
|
force=body.force,
|
|
frozen_by="api",
|
|
)
|
|
if not result.get("ok"):
|
|
raise HTTPException(status_code=503, detail=result.get("detail", "Freeze failed"))
|
|
result["enabled"] = True
|
|
return result
|
|
|
|
|
|
@router.post("/api/analytics/rolling/label")
|
|
@limiter.limit("12/minute")
|
|
async def analytics_rolling_label(
|
|
request: Request,
|
|
body: RollingLabelRequest,
|
|
_: None = Depends(require_local_operator),
|
|
) -> dict[str, Any]:
|
|
"""Apply delayed outcome labels to a frozen week."""
|
|
if not gt_analytics_enabled():
|
|
raise HTTPException(status_code=503, detail="Strategic Risk Analytics is disabled")
|
|
|
|
week_id = str(body.week_id or "").strip()
|
|
if not week_id:
|
|
raise HTTPException(status_code=400, detail="week_id required")
|
|
|
|
if len(body.labels) == 1:
|
|
entry = body.labels[0]
|
|
result = label_region(
|
|
week_id,
|
|
entry.region,
|
|
entry.label, # type: ignore[arg-type]
|
|
notes=entry.notes,
|
|
labeled_by="api",
|
|
)
|
|
else:
|
|
result = label_regions(
|
|
week_id,
|
|
[row.model_dump() for row in body.labels],
|
|
labeled_by="api",
|
|
)
|
|
|
|
if not result.get("ok"):
|
|
raise HTTPException(status_code=404, detail=result.get("detail", "Label failed"))
|
|
result["enabled"] = True
|
|
return result |