import logging from fastapi import APIRouter, HTTPException, Depends, Body from sqlalchemy.orm import Session from sqlalchemy import func from datetime import datetime, date from typing import Dict, Any, List, Optional from pathlib import Path import os import json from ..database import get_db from ..models import Topic, Article from ..core.generator import run_creator from ..core.optimizer import run_optimizer from ..core.sync import sync_all_topics from ..core.scheduler import scheduler from .auth import get_current_user, org_filter PROJECT_ROOT = Path(__file__).resolve().parents[4] if os.getenv('PROJECT_ROOT'): PROJECT_ROOT = Path(os.getenv('PROJECT_ROOT')) LOGS_DIR = PROJECT_ROOT / "automation" / "logs" logger = logging.getLogger(__name__) router = APIRouter(prefix="/api/system", tags=["system"]) def _aggregate_status_counts(q): """聚合状态计数,兼容中英文状态值""" raw = q.with_entities(Topic.status, func.count()).group_by(Topic.status).all() mapping = { 'pending': ['pending', '待处理'], 'review': ['review', '待审查'], 'ready': ['ready', '待发布'], 'published': ['published', '已发布'] } counts = {'pending': 0, 'review': 0, 'ready': 0, 'published': 0} for status_val, cnt in raw: for key, aliases in mapping.items(): if status_val in aliases: counts[key] += cnt break return counts @router.get("/status") def get_status(db: Session = Depends(get_db)): total = db.query(Topic).count() counts = _aggregate_status_counts(db.query(Topic)) today = date.today() today_count = db.query(Topic).filter(func.date(Topic.created_at) == today).count() return { "stats": { "total": total, "pending": counts['pending'], "review": counts['review'], "ready": counts['ready'], "published": counts['published'], "today": today_count } } @router.post("/generate/run", dependencies=[Depends(get_current_user)]) def trigger_generation(topic_id: str = Body(None, embed=True), db: Session = Depends(get_db), current_user=Depends(get_current_user)): logger.info(f"Received topic_id={topic_id}") try: result = run_creator(topic_id) if not result["ok"]: raise HTTPException(status_code=500, detail=result["error"]) sync_all_topics() return {"message": "Generation triggered", "result": result} except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @router.post("/review/run", dependencies=[Depends(get_current_user)]) def trigger_review(topic_ids: List[str] = Body(None, embed=True), db: Session = Depends(get_db), current_user=Depends(get_current_user)): try: result = run_optimizer(topic_ids) if not result["ok"]: raise HTTPException(status_code=500, detail=result["error"]) report = result.get("report") if report and report["summary"]["total_articles"] > 0: s = report["summary"] total = s["total_articles"] avg = s["average_score"] msg = f"审查完成: {total} 篇全部通过 ({avg:.0f}分)" return {"message": msg, "summary": s} else: if topic_ids: updated = 0 for tid in topic_ids: q = db.query(Topic).filter(Topic.id == tid) of = org_filter(current_user, Topic) if of is not True: q = q.filter(of) topic = q.first() if topic and topic.status in ('review', '待审查'): topic.status = 'ready' if not topic.generated_at: topic.generated_at = datetime.utcnow() updated += 1 db.commit() if updated: logger.info(f"Review: {updated} topics advanced to 'ready' (no release files)") return {"message": "审查完成(未找到 release 文件,仅推进状态)", "stdout": result.get("stdout", "")} except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @router.get("/logs/{log_date}", dependencies=[Depends(get_current_user)]) def get_logs(log_date: str, log_type: str = "creator"): log_file = LOGS_DIR / f"{log_type}_{log_date}.log" if not log_file.exists(): raise HTTPException(status_code=404, detail=f"Log file not found: {log_file}") content = log_file.read_text(encoding='utf-8') lines = content.splitlines()[-100:] if log_type != "collector" else content.splitlines()[-200:] return {"log_date": log_date, "log_type": log_type, "content": lines} @router.get("/pipeline/status", dependencies=[Depends(get_current_user)]) def get_pipeline_status(db: Session = Depends(get_db), current_user=Depends(get_current_user)): topic_base = db.query(Topic) of = org_filter(current_user, Topic) if of is not True: topic_base = topic_base.filter(of) total = topic_base.count() counts = _aggregate_status_counts(topic_base) log_files = { "collector": LOGS_DIR / f"collector_{date.today().isoformat()}.log", "creator": LOGS_DIR / f"creator_{date.today().isoformat()}.log", "optimizer": LOGS_DIR / f"optimizer_{date.today().isoformat()}.log", } pipeline_status = {} for name, log_file in log_files.items(): if log_file.exists(): mtime = datetime.fromtimestamp(log_file.stat().st_mtime) pipeline_status[name] = {"last_run": mtime.isoformat(), "exists": True} else: pipeline_status[name] = {"exists": False, "last_run": None} return {"topics_count": total, "status_distribution": counts, "pipeline_modules": pipeline_status} @router.post("/sync/run") def run_sync(): try: sync_all_topics() return {"message": "Sync completed (DB → JSON backup)"} except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @router.get("/automation/topics") def list_automation_topics(db: Session = Depends(get_db), current_user=Depends(get_current_user)): try: topic_base = db.query(Topic) of = org_filter(current_user, Topic) if of is not True: topic_base = topic_base.filter(of) topics = topic_base.order_by(Topic.created_at.desc()).limit(100).all() result = [] for t in topics: result.append({ "id": t.id, "title": t.title, "field": t.field, "status": t.status, "priority": t.priority, "priority_score": t.priority_score, "total_score": t.total_score, "created_at": t.created_at.isoformat() if t.created_at else None, "updated_at": t.updated_at.isoformat() if t.updated_at else None, "ready_at": t.ready_at.isoformat() if t.ready_at else None, "compliance_score": t.compliance_score }) return {"count": len(result), "topics": result[:50]} except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @router.post("/refresh") def refresh_all(): try: sync_all_topics() return {"message": "Refresh completed"} except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @router.get("/scheduler/status", dependencies=[Depends(get_current_user)]) def get_scheduler_status(): return {"running": scheduler._started, "jobs": scheduler.get_jobs()} @router.get("/modules/status", dependencies=[Depends(get_current_user)]) def get_modules_status(): today_str = date.today().isoformat() log_based: dict = { "scheduled_collect": {"name": "📡 内容采集", "log": LOGS_DIR / f"collector_{today_str}.log"}, "scheduled_sync": {"name": "🔄 指标同步", "log": LOGS_DIR / f"sync_{today_str}.log"}, "scheduled_generate": {"name": "🤖 内容创作", "log": LOGS_DIR / f"creator_{today_str}.log"}, "scheduled_optimize": {"name": "🔍 内容优化", "log": LOGS_DIR / f"optimizer_{today_str}.log"}, "scheduled_optimize_sources": {"name": "📡 信息源优化", "log": LOGS_DIR / f"optimizer_sources_{today_str}.log"}, "scheduled_metrics_sync": {"name": "📊 指标同步", "log": LOGS_DIR / f"metrics_sync_{today_str}.log"}, } jobs = {j['id']: j for j in scheduler.get_jobs()} modules = [] for mod_id, cfg in log_based.items(): log_file = cfg["log"] last_run = None task_count = 0 success_rate = None if log_file.exists(): mtime = datetime.fromtimestamp(log_file.stat().st_mtime) last_run = mtime.strftime("%Y-%m-%d %H:%M") content = log_file.read_text(encoding="utf-8", errors="ignore") task_count = content.count("完成") + content.count("success") + content.count("SUCCESS") total = task_count + content.count("失败") + content.count("failed") + content.count("ERROR") success_rate = round(task_count / total * 100) if total > 0 else None status = "running" if log_file.exists() else ("idle" if mod_id in jobs else "stopped") modules.append({ "id": mod_id, "title": cfg["name"], "status": status, "last_run": last_run or "从未运行", "task_count": task_count, "success_rate": success_rate, }) return {"modules": modules, "scheduler": {"running": scheduler._started, "jobs": scheduler.get_jobs()}}