Initial commit: yu-zhi-ran platform with automation integration
This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
from fastapi import APIRouter, HTTPException, Depends
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import func
|
||||
from datetime import datetime, date, timedelta
|
||||
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 ..schemas import SystemStatus
|
||||
from ..core.generator import run_creator
|
||||
from ..core.optimizer import run_optimizer
|
||||
from ..core.sync import sync_topic_to_db, sync_all_topics
|
||||
|
||||
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"
|
||||
DATA_DIR = PROJECT_ROOT / "automation" / "data"
|
||||
|
||||
router = APIRouter(prefix="/api/system", tags=["system"])
|
||||
|
||||
@router.get("/status", response_model=SystemStatus)
|
||||
def get_status(db: Session = Depends(get_db)):
|
||||
"""系统状态概览"""
|
||||
total = db.query(Topic).count()
|
||||
by_status_result = db.query(Topic.status, func.count()).group_by(Topic.status).all()
|
||||
by_status = {status: count for status, count in by_status_result}
|
||||
# 确保返回所有状态,避免前端 undefined
|
||||
for key in ('pending', 'ready', 'published'):
|
||||
by_status.setdefault(key, 0)
|
||||
|
||||
ready = db.query(Topic).filter(Topic.status == "ready").all()
|
||||
|
||||
today_str = date.today().isoformat()
|
||||
# 计算今日文章数:查找 releases/2026-04-16 目录下的 html 文件
|
||||
# 这里简单统计数据库中 created_at 为今天的文章(不完全准确)
|
||||
today_articles = db.query(Article).filter(
|
||||
func.date(Article.created_at) == date.today()
|
||||
).count()
|
||||
|
||||
# 合规率:假设所有 ready 的都是合规的(实际从report读取)
|
||||
# 可以后续优化
|
||||
|
||||
# 获取最后一次优化时间
|
||||
last_opt = db.query(Article).filter(
|
||||
Article.status == "optimized"
|
||||
).order_by(Article.created_at.desc()).first()
|
||||
|
||||
return SystemStatus(
|
||||
total_topics=total,
|
||||
topics_by_status=by_status,
|
||||
ready_topics=ready,
|
||||
today_articles=today_articles,
|
||||
compliance_rate=100.0, # placeholder
|
||||
last_optimization=last_opt.created_at if last_opt else None
|
||||
)
|
||||
|
||||
@router.post("/generate/run")
|
||||
def trigger_generation(topic_id: str = None, db: Session = Depends(get_db)):
|
||||
"""手动触发内容创作任务
|
||||
|
||||
Args:
|
||||
topic_id: 可选,指定要创作的选题ID。不指定则创作优先级最高的待处理选题。
|
||||
"""
|
||||
try:
|
||||
result = run_creator(topic_id)
|
||||
if not result["ok"]:
|
||||
raise HTTPException(status_code=500, detail=result["error"])
|
||||
|
||||
from ..core.sync import sync_all_topics
|
||||
sync_all_topics()
|
||||
|
||||
return {"message": "Generation triggered", "result": result}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.post("/optimize/run")
|
||||
def trigger_optimization(topic_ids: List[str] = None, db: Session = Depends(get_db)):
|
||||
"""手动触发合规优化任务
|
||||
|
||||
Args:
|
||||
topic_ids: 可选,指定要优化的选题ID列表。不指定则优化所有 draft 状态文章。
|
||||
"""
|
||||
try:
|
||||
result = run_optimizer(topic_ids)
|
||||
if not result["ok"]:
|
||||
raise HTTPException(status_code=500, detail=result["error"])
|
||||
|
||||
report = result.get("report")
|
||||
if report:
|
||||
from ..core.sync import sync_all_topics
|
||||
sync_all_topics()
|
||||
return {
|
||||
"message": "Optimization completed",
|
||||
"summary": report["summary"]
|
||||
}
|
||||
else:
|
||||
return {"message": "Optimization completed but no report found", "stdout": result.get("stdout", "")}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.get("/logs/{log_date}")
|
||||
def get_logs(log_date: str, log_type: str = "creator"):
|
||||
"""读取日志文件内容,log_type: creator, optimizer, collector"""
|
||||
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")
|
||||
def get_pipeline_status():
|
||||
"""获取流水线各模块状态(最后运行时间和结果)"""
|
||||
try:
|
||||
# 读取选题文件
|
||||
topics_file = DATA_DIR / "sustainability_topics.json"
|
||||
topics = []
|
||||
if topics_file.exists():
|
||||
topics = json.loads(topics_file.read_text(encoding='utf-8'))
|
||||
|
||||
# 统计状态分布
|
||||
status_counts = {}
|
||||
for t in topics:
|
||||
s = t.get('status', 'unknown')
|
||||
status_counts[s] = status_counts.get(s, 0) + 1
|
||||
|
||||
# 检查各日志文件的最新修改时间
|
||||
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",
|
||||
"publisher": LOGS_DIR / f"publisher_{date.today()}.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,
|
||||
"size_bytes": log_file.stat().st_size
|
||||
}
|
||||
# 简单推断成功/失败(TODO: 解析日志加强)
|
||||
last_lines = log_file.read_text(encoding='utf-8').splitlines()[-10:]
|
||||
has_error = any("error" in line.lower() or "失败" in line or "failed" in line.lower() for line in last_lines)
|
||||
pipeline_status[name]["has_error"] = has_error
|
||||
else:
|
||||
pipeline_status[name] = {"exists": False, "last_run": None}
|
||||
|
||||
return {
|
||||
"topics_count": len(topics),
|
||||
"status_distribution": status_counts,
|
||||
"pipeline_modules": pipeline_status,
|
||||
"data_dir": str(DATA_DIR),
|
||||
"logs_dir": str(LOGS_DIR)
|
||||
}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.post("/sync/run")
|
||||
def run_sync():
|
||||
"""手动触发数据同步(流水线JSON → 平台数据库)"""
|
||||
try:
|
||||
sync_all_topics()
|
||||
return {"message": "Sync completed"}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.get("/automation/topics")
|
||||
def list_automation_topics():
|
||||
"""直接读取自动化流水线的选题JSON(供调试)"""
|
||||
try:
|
||||
topics_file = DATA_DIR / "sustainability_topics.json"
|
||||
if not topics_file.exists():
|
||||
raise HTTPException(status_code=404, detail="Topics JSON not found")
|
||||
topics = json.loads(topics_file.read_text(encoding='utf-8'))
|
||||
return {
|
||||
"count": len(topics),
|
||||
"topics": topics[-50:] # 只返回最近50个,避免过大
|
||||
}
|
||||
except json.JSONDecodeError as e:
|
||||
raise HTTPException(status_code=500, detail=f"JSON parse error: {e}")
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.post("/refresh")
|
||||
def refresh_all():
|
||||
"""刷新所有数据:同步JSON + 更新状态"""
|
||||
try:
|
||||
sync_all_topics()
|
||||
return {"message": "Refresh completed"}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
Reference in New Issue
Block a user