235 lines
8.1 KiB
Python
235 lines
8.1 KiB
Python
from fastapi import APIRouter, Cookie
|
|
|
|
from app.db import auth_db
|
|
from app.db.analytics import (
|
|
get_all_recommendations,
|
|
get_cron_run_logs,
|
|
get_cron_run_summary,
|
|
get_observation_candidates,
|
|
get_pipeline_run_detail,
|
|
get_pipeline_runs,
|
|
get_review_stats,
|
|
get_screening_history,
|
|
get_stats,
|
|
)
|
|
from app.db.llm_insights import get_llm_insight_by_id, list_llm_insights
|
|
from app.db.recommendation_queries import get_active_recommendations, get_active_recommendations_deduped
|
|
from app.config.config_loader import get_signal_weights
|
|
from app.web.shared import (
|
|
ObservationRequest,
|
|
PushRulesRequest,
|
|
WatchlistRequest,
|
|
require_api_user_with_subscription,
|
|
)
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
def _friendly_llm_item(item):
|
|
content = item.get("content") or {}
|
|
payload = item.get("input") or {}
|
|
target_type = item.get("target_type") or ""
|
|
status = item.get("status") or ""
|
|
type_label = {
|
|
"recommendation": "推荐解释",
|
|
"sentiment": "舆情解读",
|
|
"review": "复盘 memo",
|
|
}.get(target_type, target_type or "未知任务")
|
|
status_label = {
|
|
"success": "成功",
|
|
"failed": "失败",
|
|
"skipped": "跳过",
|
|
}.get(status, status or "未知")
|
|
subject = payload.get("symbol") or payload.get("related_symbol") or payload.get("title") or payload.get("run_date") or item.get("target_id")
|
|
summary = content.get("summary") or content.get("memo") or content.get("why_now_or_not") or content.get("raw") or item.get("error") or ""
|
|
return {
|
|
"id": item.get("id"),
|
|
"type_label": type_label,
|
|
"status_label": status_label,
|
|
"status": status,
|
|
"subject": subject,
|
|
"summary": summary,
|
|
"model": item.get("model") or "",
|
|
"prompt_version": item.get("prompt_version") or "",
|
|
"target_type": target_type,
|
|
"target_id": item.get("target_id"),
|
|
"updated_at": item.get("updated_at"),
|
|
"error": item.get("error") or "",
|
|
"content": content,
|
|
"input": payload,
|
|
}
|
|
|
|
|
|
@router.get("/api/stats")
|
|
async def api_stats(altcoin_session: str = Cookie(default="")):
|
|
require_api_user_with_subscription(altcoin_session)
|
|
return get_stats()
|
|
|
|
|
|
@router.get("/api/recommendations")
|
|
async def api_recommendations(
|
|
limit: int = 50,
|
|
offset: int = 0,
|
|
decision_only: bool = False,
|
|
version: str = "",
|
|
paged: bool = False,
|
|
compact: bool = False,
|
|
altcoin_session: str = Cookie(default=""),
|
|
):
|
|
require_api_user_with_subscription(altcoin_session)
|
|
return get_all_recommendations(limit, decision_only=decision_only, version=version, offset=offset, with_meta=(paged or compact))
|
|
|
|
|
|
@router.get("/api/recommendations/active")
|
|
async def api_recommendations_active(
|
|
dedup: bool = True,
|
|
actionable_only: bool = True,
|
|
version: str = "",
|
|
hours: float = 0,
|
|
limit: int = 0,
|
|
offset: int = 0,
|
|
paged: bool = False,
|
|
compact: bool = False,
|
|
altcoin_session: str = Cookie(default=""),
|
|
):
|
|
require_api_user_with_subscription(altcoin_session)
|
|
if dedup:
|
|
return get_active_recommendations_deduped(
|
|
actionable_only=actionable_only,
|
|
version=version,
|
|
hours=hours,
|
|
limit=limit,
|
|
offset=offset,
|
|
with_meta=(paged or compact),
|
|
)
|
|
return get_active_recommendations(actionable_only=actionable_only)
|
|
|
|
|
|
@router.get("/api/observations/active")
|
|
async def api_observations_active(
|
|
limit: int = 50,
|
|
altcoin_session: str = Cookie(default=""),
|
|
):
|
|
require_api_user_with_subscription(altcoin_session)
|
|
return get_observation_candidates(limit=limit)
|
|
|
|
|
|
@router.get("/api/personalization")
|
|
async def api_personalization(altcoin_session: str = Cookie(default="")):
|
|
user = require_api_user_with_subscription(altcoin_session)
|
|
return {
|
|
"watchlist": auth_db.get_watchlist_symbols(user["id"]),
|
|
"observations": auth_db.get_saved_observations(user["id"]),
|
|
"push_rules": auth_db.get_push_rules(user["id"]),
|
|
}
|
|
|
|
|
|
@router.post("/api/watchlist")
|
|
async def api_add_watchlist(req: WatchlistRequest, altcoin_session: str = Cookie(default="")):
|
|
user = require_api_user_with_subscription(altcoin_session)
|
|
auth_db.add_watchlist_symbol(user["id"], req.symbol)
|
|
return {"ok": True, "watchlist": auth_db.get_watchlist_symbols(user["id"])}
|
|
|
|
|
|
@router.delete("/api/watchlist/{symbol}")
|
|
async def api_remove_watchlist(symbol: str, altcoin_session: str = Cookie(default="")):
|
|
user = require_api_user_with_subscription(altcoin_session)
|
|
auth_db.remove_watchlist_symbol(user["id"], symbol)
|
|
return {"ok": True, "watchlist": auth_db.get_watchlist_symbols(user["id"])}
|
|
|
|
|
|
@router.post("/api/observations")
|
|
async def api_save_observation(req: ObservationRequest, altcoin_session: str = Cookie(default="")):
|
|
user = require_api_user_with_subscription(altcoin_session)
|
|
auth_db.save_observation(user["id"], req.rec_id, req.note)
|
|
return {"ok": True, "observations": auth_db.get_saved_observations(user["id"])}
|
|
|
|
|
|
@router.delete("/api/observations/{rec_id}")
|
|
async def api_remove_observation(rec_id: int, altcoin_session: str = Cookie(default="")):
|
|
user = require_api_user_with_subscription(altcoin_session)
|
|
auth_db.remove_observation(user["id"], rec_id)
|
|
return {"ok": True, "observations": auth_db.get_saved_observations(user["id"])}
|
|
|
|
|
|
@router.post("/api/push-rules")
|
|
async def api_update_push_rules(req: PushRulesRequest, altcoin_session: str = Cookie(default="")):
|
|
user = require_api_user_with_subscription(altcoin_session)
|
|
rules = auth_db.update_push_rules(user["id"], req.dict())
|
|
return {"ok": True, "push_rules": rules}
|
|
|
|
|
|
@router.get("/api/screening")
|
|
async def api_screening(hours: int = 24, limit: int = 100, altcoin_session: str = Cookie(default="")):
|
|
require_api_user_with_subscription(altcoin_session)
|
|
return get_screening_history(hours, limit)
|
|
|
|
|
|
@router.get("/api/review")
|
|
async def api_review(altcoin_session: str = Cookie(default="")):
|
|
require_api_user_with_subscription(altcoin_session)
|
|
return get_review_stats()
|
|
|
|
|
|
@router.get("/api/weights")
|
|
async def api_weights(altcoin_session: str = Cookie(default="")):
|
|
require_api_user_with_subscription(altcoin_session)
|
|
return get_signal_weights()
|
|
|
|
|
|
@router.get("/api/cron")
|
|
async def api_cron(limit: int = 50, job_name: str = "", altcoin_session: str = Cookie(default="")):
|
|
require_api_user_with_subscription(altcoin_session)
|
|
return get_cron_run_logs(limit=limit, job_name=job_name or None)
|
|
|
|
|
|
@router.get("/api/cron/summary")
|
|
async def api_cron_summary(hours: int = 24, altcoin_session: str = Cookie(default="")):
|
|
require_api_user_with_subscription(altcoin_session)
|
|
return get_cron_run_summary(hours=hours)
|
|
|
|
|
|
@router.get("/api/pipeline/runs")
|
|
async def api_pipeline_runs(limit: int = 30, hours: int = 24, offset: int = 0, altcoin_session: str = Cookie(default="")):
|
|
require_api_user_with_subscription(altcoin_session)
|
|
return get_pipeline_runs(limit=limit, hours=hours, offset=offset)
|
|
|
|
|
|
@router.get("/api/pipeline/runs/{run_id}")
|
|
async def api_pipeline_run_detail(run_id: int, altcoin_session: str = Cookie(default="")):
|
|
require_api_user_with_subscription(altcoin_session)
|
|
detail = get_pipeline_run_detail(run_id)
|
|
if not detail:
|
|
return {"error": "pipeline run not found", "run_id": run_id}
|
|
return detail
|
|
|
|
|
|
@router.get("/api/llm/insights")
|
|
async def api_llm_insights(
|
|
limit: int = 30,
|
|
offset: int = 0,
|
|
target_type: str = "",
|
|
status: str = "",
|
|
insight_type: str = "",
|
|
altcoin_session: str = Cookie(default=""),
|
|
):
|
|
require_api_user_with_subscription(altcoin_session)
|
|
data = list_llm_insights(
|
|
limit=limit,
|
|
offset=offset,
|
|
target_type=target_type or "",
|
|
status=status or "",
|
|
insight_type=insight_type or "",
|
|
)
|
|
data["items"] = [_friendly_llm_item(item) for item in data.get("items", [])]
|
|
return data
|
|
|
|
|
|
@router.get("/api/llm/insights/{insight_id}")
|
|
async def api_llm_insight_detail(insight_id: int, altcoin_session: str = Cookie(default="")):
|
|
require_api_user_with_subscription(altcoin_session)
|
|
item = get_llm_insight_by_id(insight_id)
|
|
if not item:
|
|
return {"error": "llm insight not found", "id": insight_id}
|
|
return _friendly_llm_item(item)
|