from fastapi import APIRouter, Cookie from app.config.config_loader import get_meta from app.db import auth_db from app.db.review_queries import ( backfill_strategy_failure_patterns, dry_run_strategy_candidate_performance, generate_candidates_from_review_history, get_strategy_failure_patterns, get_strategy_insights, get_strategy_iteration_dashboard, get_strategy_rule_candidates, refresh_strategy_candidate_performance, ) from app.services.llm_insights import get_latest_review_memo from app.db.schema import get_conn from app.db.altcoin_db import _derive_execution_fields from app.web.shared import require_api_user_with_subscription router = APIRouter() @router.get("/api/versions") async def api_versions(view: str = "active", altcoin_session: str = Cookie(default="")): require_api_user_with_subscription(altcoin_session) conn = get_conn() rows = conn.execute( """ SELECT r.* FROM recommendation r JOIN ( SELECT symbol, strategy_version, MAX(id) AS max_id FROM recommendation WHERE status='active' AND strategy_version IS NOT NULL AND strategy_version != '' GROUP BY symbol, strategy_version ) latest ON latest.max_id = r.id ORDER BY r.strategy_version DESC, r.rec_time DESC """ ).fetchall() conn.close() counts = {} for row in rows: item = dict(row) _derive_execution_fields(item) version = str(item.get("strategy_version") or "").strip() if not version: continue status = item.get("execution_status") or "observe" if view == "active" and status not in ("buy_now", "wait_pullback"): continue if view == "watch" and status != "observe": continue counts[version] = counts.get(version, 0) + 1 versions = [{"version": version, "count": count} for version, count in counts.items()] current_version = str(get_meta().get("strategy_version") or "").strip() if current_version and current_version not in counts: versions.append({"version": current_version, "count": 0}) def _version_key(v): try: return tuple(int(p) for p in v["version"].lstrip("v").split(".")) except Exception: return (0,) versions.sort(key=_version_key, reverse=True) return versions @router.get("/api/strategy/insights") async def api_strategy_insights(altcoin_session: str = Cookie(default="")): require_api_user_with_subscription(altcoin_session) return get_strategy_insights() @router.get("/api/strategy/lifecycle") async def api_strategy_lifecycle(days: int = 30, altcoin_session: str = Cookie(default="")): require_api_user_with_subscription(altcoin_session) data = get_strategy_iteration_dashboard(days=days) data["llm_review_memo"] = get_latest_review_memo() return data @router.get("/api/iterations") async def api_iterations(limit: int = 30, altcoin_session: str = Cookie(default="")): require_api_user_with_subscription(altcoin_session) conn = get_conn() rows = conn.execute("SELECT * FROM strategy_iteration_log ORDER BY id DESC LIMIT %s", (limit,)).fetchall() conn.close() return [dict(r) for r in rows] @router.get("/api/strategy/candidates") async def api_strategy_candidates(limit: int = 50, status: str = "", altcoin_session: str = Cookie(default="")): require_api_user_with_subscription(altcoin_session) return get_strategy_rule_candidates(limit=limit, status=status or None) @router.get("/api/strategy/failures") async def api_strategy_failures(limit: int = 50, altcoin_session: str = Cookie(default="")): require_api_user_with_subscription(altcoin_session) return get_strategy_failure_patterns(limit=limit) @router.post("/api/strategy/candidates/refresh") async def api_strategy_candidates_refresh(altcoin_session: str = Cookie(default="")): require_api_user_with_subscription(altcoin_session) return {"updated": refresh_strategy_candidate_performance()} @router.get("/api/strategy/candidates/dry-run") async def api_strategy_candidates_dry_run(altcoin_session: str = Cookie(default="")): require_api_user_with_subscription(altcoin_session) return dry_run_strategy_candidate_performance() @router.post("/api/strategy/failures/backfill") async def api_strategy_failures_backfill(dry_run: bool = False): return backfill_strategy_failure_patterns(dry_run=dry_run) @router.post("/api/strategy/candidates/generate-history") async def api_strategy_candidates_generate_history(dry_run: bool = False): result = generate_candidates_from_review_history(dry_run=dry_run) if not dry_run: result["refreshed"] = refresh_strategy_candidate_performance() return result