|
|
|
@ -18,6 +18,12 @@ from app.services.trade_parser import (
|
|
|
|
calc_overall_statistics,
|
|
|
|
calc_overall_statistics,
|
|
|
|
get_trade_pairs,
|
|
|
|
get_trade_pairs,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
from app.services.trade_pairing_engine import (
|
|
|
|
|
|
|
|
auto_pair_trades,
|
|
|
|
|
|
|
|
get_daily_trades_with_pairs,
|
|
|
|
|
|
|
|
create_manual_pair,
|
|
|
|
|
|
|
|
delete_pair,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter(prefix="/trade-review", tags=["交易复盘"])
|
|
|
|
router = APIRouter(prefix="/trade-review", tags=["交易复盘"])
|
|
|
|
@ -588,3 +594,445 @@ async def analyze_trade(
|
|
|
|
except Exception as e:
|
|
|
|
except Exception as e:
|
|
|
|
logger.exception("AI分析交易失败")
|
|
|
|
logger.exception("AI分析交易失败")
|
|
|
|
return {"success": False, "message": f"AI分析失败: {str(e)}"}
|
|
|
|
return {"success": False, "message": f"AI分析失败: {str(e)}"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ==================== 交易反思系统 API ====================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
from app.analysis_models import (
|
|
|
|
|
|
|
|
TradePair, TradeReflection, DailyReflection,
|
|
|
|
|
|
|
|
TradeTag, TradeRecordTag, TradeExperience,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
from pydantic import BaseModel
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# --- 按天交易视图 ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/daily-trades/{trade_date}")
|
|
|
|
|
|
|
|
def api_get_daily_trades(
|
|
|
|
|
|
|
|
trade_date: str,
|
|
|
|
|
|
|
|
db: Session = Depends(get_analysis_db),
|
|
|
|
|
|
|
|
):
|
|
|
|
|
|
|
|
"""获取指定日期的交易+配对+反思状态"""
|
|
|
|
|
|
|
|
# 先执行自动配对
|
|
|
|
|
|
|
|
auto_pair_trades(db, trade_date)
|
|
|
|
|
|
|
|
db.commit()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
data = get_daily_trades_with_pairs(db, trade_date)
|
|
|
|
|
|
|
|
return {"success": True, "data": data}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# --- 手动配对 ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ManualPairRequest(BaseModel):
|
|
|
|
|
|
|
|
open_record_ids: list[int]
|
|
|
|
|
|
|
|
close_record_ids: list[int]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/trade-pairs")
|
|
|
|
|
|
|
|
def api_create_manual_pair(
|
|
|
|
|
|
|
|
req: ManualPairRequest,
|
|
|
|
|
|
|
|
db: Session = Depends(get_analysis_db),
|
|
|
|
|
|
|
|
):
|
|
|
|
|
|
|
|
"""手动创建交易配对"""
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
pair = create_manual_pair(db, req.open_record_ids, req.close_record_ids)
|
|
|
|
|
|
|
|
db.commit()
|
|
|
|
|
|
|
|
return {"success": True, "data": {"id": pair.id}}
|
|
|
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
|
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
|
|
db.rollback()
|
|
|
|
|
|
|
|
logger.exception("创建配对失败")
|
|
|
|
|
|
|
|
raise HTTPException(status_code=500, detail=f"创建配对失败: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.delete("/trade-pairs/{pair_id}")
|
|
|
|
|
|
|
|
def api_delete_pair(
|
|
|
|
|
|
|
|
pair_id: int,
|
|
|
|
|
|
|
|
db: Session = Depends(get_analysis_db),
|
|
|
|
|
|
|
|
):
|
|
|
|
|
|
|
|
"""删除交易配对"""
|
|
|
|
|
|
|
|
success = delete_pair(db, pair_id)
|
|
|
|
|
|
|
|
if not success:
|
|
|
|
|
|
|
|
raise HTTPException(status_code=404, detail="配对不存在")
|
|
|
|
|
|
|
|
db.commit()
|
|
|
|
|
|
|
|
return {"success": True, "message": "配对已删除"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# --- 反思 CRUD ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ReflectionRequest(BaseModel):
|
|
|
|
|
|
|
|
trade_pair_id: int
|
|
|
|
|
|
|
|
trade_date: str
|
|
|
|
|
|
|
|
entry_reason: Optional[str] = None
|
|
|
|
|
|
|
|
entry_timing: Optional[str] = None
|
|
|
|
|
|
|
|
position_management: Optional[str] = None
|
|
|
|
|
|
|
|
exit_reason: Optional[str] = None
|
|
|
|
|
|
|
|
exit_timing: Optional[str] = None
|
|
|
|
|
|
|
|
discipline_score: Optional[int] = None
|
|
|
|
|
|
|
|
free_reflection: Optional[str] = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/reflections")
|
|
|
|
|
|
|
|
def api_create_reflection(
|
|
|
|
|
|
|
|
req: ReflectionRequest,
|
|
|
|
|
|
|
|
db: Session = Depends(get_analysis_db),
|
|
|
|
|
|
|
|
):
|
|
|
|
|
|
|
|
"""创建交易反思"""
|
|
|
|
|
|
|
|
# 检查是否已存在
|
|
|
|
|
|
|
|
existing = db.query(TradeReflection).filter(
|
|
|
|
|
|
|
|
TradeReflection.trade_pair_id == req.trade_pair_id
|
|
|
|
|
|
|
|
).first()
|
|
|
|
|
|
|
|
if existing:
|
|
|
|
|
|
|
|
raise HTTPException(status_code=400, detail="该交易已有反思,请使用更新接口")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
reflection = TradeReflection(
|
|
|
|
|
|
|
|
trade_pair_id=req.trade_pair_id,
|
|
|
|
|
|
|
|
trade_date=req.trade_date,
|
|
|
|
|
|
|
|
entry_reason=req.entry_reason,
|
|
|
|
|
|
|
|
entry_timing=req.entry_timing,
|
|
|
|
|
|
|
|
position_management=req.position_management,
|
|
|
|
|
|
|
|
exit_reason=req.exit_reason,
|
|
|
|
|
|
|
|
exit_timing=req.exit_timing,
|
|
|
|
|
|
|
|
discipline_score=req.discipline_score,
|
|
|
|
|
|
|
|
free_reflection=req.free_reflection,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
db.add(reflection)
|
|
|
|
|
|
|
|
db.commit()
|
|
|
|
|
|
|
|
return {"success": True, "data": {"id": reflection.id}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.put("/reflections/{reflection_id}")
|
|
|
|
|
|
|
|
def api_update_reflection(
|
|
|
|
|
|
|
|
reflection_id: int,
|
|
|
|
|
|
|
|
req: ReflectionRequest,
|
|
|
|
|
|
|
|
db: Session = Depends(get_analysis_db),
|
|
|
|
|
|
|
|
):
|
|
|
|
|
|
|
|
"""更新交易反思"""
|
|
|
|
|
|
|
|
reflection = db.query(TradeReflection).filter(TradeReflection.id == reflection_id).first()
|
|
|
|
|
|
|
|
if not reflection:
|
|
|
|
|
|
|
|
raise HTTPException(status_code=404, detail="反思不存在")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 更新字段
|
|
|
|
|
|
|
|
for field in ['entry_reason', 'entry_timing', 'position_management',
|
|
|
|
|
|
|
|
'exit_reason', 'exit_timing', 'discipline_score', 'free_reflection']:
|
|
|
|
|
|
|
|
val = getattr(req, field)
|
|
|
|
|
|
|
|
if val is not None:
|
|
|
|
|
|
|
|
setattr(reflection, field, val)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 重新保存时重置 AI 分析状态(因为内容变了)
|
|
|
|
|
|
|
|
reflection.ai_analyzed = False
|
|
|
|
|
|
|
|
reflection.ai_version = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
db.commit()
|
|
|
|
|
|
|
|
return {"success": True, "message": "反思已更新"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/reflections")
|
|
|
|
|
|
|
|
def api_get_reflections(
|
|
|
|
|
|
|
|
trade_pair_id: Optional[int] = Query(None),
|
|
|
|
|
|
|
|
trade_date: Optional[str] = Query(None),
|
|
|
|
|
|
|
|
db: Session = Depends(get_analysis_db),
|
|
|
|
|
|
|
|
):
|
|
|
|
|
|
|
|
"""查询反思列表"""
|
|
|
|
|
|
|
|
query = db.query(TradeReflection)
|
|
|
|
|
|
|
|
if trade_pair_id:
|
|
|
|
|
|
|
|
query = query.filter(TradeReflection.trade_pair_id == trade_pair_id)
|
|
|
|
|
|
|
|
if trade_date:
|
|
|
|
|
|
|
|
query = query.filter(TradeReflection.trade_date == trade_date)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
reflections = query.order_by(TradeReflection.created_at.desc()).all()
|
|
|
|
|
|
|
|
return {"success": True, "data": [{
|
|
|
|
|
|
|
|
"id": r.id,
|
|
|
|
|
|
|
|
"trade_pair_id": r.trade_pair_id,
|
|
|
|
|
|
|
|
"trade_date": r.trade_date,
|
|
|
|
|
|
|
|
"entry_reason": r.entry_reason,
|
|
|
|
|
|
|
|
"entry_timing": r.entry_timing,
|
|
|
|
|
|
|
|
"position_management": r.position_management,
|
|
|
|
|
|
|
|
"exit_reason": r.exit_reason,
|
|
|
|
|
|
|
|
"exit_timing": r.exit_timing,
|
|
|
|
|
|
|
|
"discipline_score": r.discipline_score,
|
|
|
|
|
|
|
|
"free_reflection": r.free_reflection,
|
|
|
|
|
|
|
|
"ai_analyzed": r.ai_analyzed,
|
|
|
|
|
|
|
|
"ai_version": r.ai_version,
|
|
|
|
|
|
|
|
"created_at": r.created_at.strftime('%Y-%m-%d %H:%M:%S') if r.created_at else None,
|
|
|
|
|
|
|
|
"updated_at": r.updated_at.strftime('%Y-%m-%d %H:%M:%S') if r.updated_at else None,
|
|
|
|
|
|
|
|
} for r in reflections]}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# --- 当日反思 CRUD ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DailyReflectionRequest(BaseModel):
|
|
|
|
|
|
|
|
reflection_date: str
|
|
|
|
|
|
|
|
emotion_state: Optional[str] = None
|
|
|
|
|
|
|
|
market_judgment: Optional[str] = None
|
|
|
|
|
|
|
|
discipline_score: Optional[int] = None
|
|
|
|
|
|
|
|
overall_rating: Optional[int] = None
|
|
|
|
|
|
|
|
summary: Optional[str] = None
|
|
|
|
|
|
|
|
improvements: Optional[str] = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/daily-reflections")
|
|
|
|
|
|
|
|
def api_create_daily_reflection(
|
|
|
|
|
|
|
|
req: DailyReflectionRequest,
|
|
|
|
|
|
|
|
db: Session = Depends(get_analysis_db),
|
|
|
|
|
|
|
|
):
|
|
|
|
|
|
|
|
"""创建或更新当日反思"""
|
|
|
|
|
|
|
|
existing = db.query(DailyReflection).filter(
|
|
|
|
|
|
|
|
DailyReflection.reflection_date == req.reflection_date
|
|
|
|
|
|
|
|
).first()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if existing:
|
|
|
|
|
|
|
|
# 更新
|
|
|
|
|
|
|
|
for field in ['emotion_state', 'market_judgment', 'discipline_score',
|
|
|
|
|
|
|
|
'overall_rating', 'summary', 'improvements']:
|
|
|
|
|
|
|
|
val = getattr(req, field)
|
|
|
|
|
|
|
|
if val is not None:
|
|
|
|
|
|
|
|
setattr(existing, field, val)
|
|
|
|
|
|
|
|
db.commit()
|
|
|
|
|
|
|
|
return {"success": True, "data": {"id": existing.id, "action": "updated"}}
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
# 创建
|
|
|
|
|
|
|
|
dr = DailyReflection(
|
|
|
|
|
|
|
|
reflection_date=req.reflection_date,
|
|
|
|
|
|
|
|
emotion_state=req.emotion_state,
|
|
|
|
|
|
|
|
market_judgment=req.market_judgment,
|
|
|
|
|
|
|
|
discipline_score=req.discipline_score,
|
|
|
|
|
|
|
|
overall_rating=req.overall_rating,
|
|
|
|
|
|
|
|
summary=req.summary,
|
|
|
|
|
|
|
|
improvements=req.improvements,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
db.add(dr)
|
|
|
|
|
|
|
|
db.commit()
|
|
|
|
|
|
|
|
return {"success": True, "data": {"id": dr.id, "action": "created"}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/daily-reflections/{reflection_date}")
|
|
|
|
|
|
|
|
def api_get_daily_reflection(
|
|
|
|
|
|
|
|
reflection_date: str,
|
|
|
|
|
|
|
|
db: Session = Depends(get_analysis_db),
|
|
|
|
|
|
|
|
):
|
|
|
|
|
|
|
|
"""获取当日反思"""
|
|
|
|
|
|
|
|
dr = db.query(DailyReflection).filter(
|
|
|
|
|
|
|
|
DailyReflection.reflection_date == reflection_date
|
|
|
|
|
|
|
|
).first()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if not dr:
|
|
|
|
|
|
|
|
return {"success": True, "data": None}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return {"success": True, "data": {
|
|
|
|
|
|
|
|
"id": dr.id,
|
|
|
|
|
|
|
|
"reflection_date": dr.reflection_date,
|
|
|
|
|
|
|
|
"emotion_state": dr.emotion_state,
|
|
|
|
|
|
|
|
"market_judgment": dr.market_judgment,
|
|
|
|
|
|
|
|
"discipline_score": dr.discipline_score,
|
|
|
|
|
|
|
|
"overall_rating": dr.overall_rating,
|
|
|
|
|
|
|
|
"summary": dr.summary,
|
|
|
|
|
|
|
|
"improvements": dr.improvements,
|
|
|
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# --- 标签系统 ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TagRequest(BaseModel):
|
|
|
|
|
|
|
|
name: str
|
|
|
|
|
|
|
|
category: str = "custom"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/tags")
|
|
|
|
|
|
|
|
def api_get_tags(
|
|
|
|
|
|
|
|
category: Optional[str] = Query(None),
|
|
|
|
|
|
|
|
db: Session = Depends(get_analysis_db),
|
|
|
|
|
|
|
|
):
|
|
|
|
|
|
|
|
"""获取所有标签"""
|
|
|
|
|
|
|
|
query = db.query(TradeTag)
|
|
|
|
|
|
|
|
if category:
|
|
|
|
|
|
|
|
query = query.filter(TradeTag.category == category)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tags = query.order_by(TradeTag.is_preset.desc(), TradeTag.name).all()
|
|
|
|
|
|
|
|
return {"success": True, "data": [{
|
|
|
|
|
|
|
|
"id": t.id,
|
|
|
|
|
|
|
|
"name": t.name,
|
|
|
|
|
|
|
|
"category": t.category,
|
|
|
|
|
|
|
|
"is_preset": t.is_preset,
|
|
|
|
|
|
|
|
} for t in tags]}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/tags")
|
|
|
|
|
|
|
|
def api_create_tag(
|
|
|
|
|
|
|
|
req: TagRequest,
|
|
|
|
|
|
|
|
db: Session = Depends(get_analysis_db),
|
|
|
|
|
|
|
|
):
|
|
|
|
|
|
|
|
"""创建自定义标签(含去重)"""
|
|
|
|
|
|
|
|
existing = db.query(TradeTag).filter(TradeTag.name == req.name).first()
|
|
|
|
|
|
|
|
if existing:
|
|
|
|
|
|
|
|
return {"success": True, "data": {"id": existing.id, "action": "exists"}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tag = TradeTag(name=req.name, category=req.category, is_preset=False)
|
|
|
|
|
|
|
|
db.add(tag)
|
|
|
|
|
|
|
|
db.commit()
|
|
|
|
|
|
|
|
return {"success": True, "data": {"id": tag.id, "action": "created"}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/trade-pairs/{pair_id}/tags")
|
|
|
|
|
|
|
|
def api_add_tag_to_trade(
|
|
|
|
|
|
|
|
pair_id: int,
|
|
|
|
|
|
|
|
tag_id: int,
|
|
|
|
|
|
|
|
db: Session = Depends(get_analysis_db),
|
|
|
|
|
|
|
|
):
|
|
|
|
|
|
|
|
"""给交易打标签"""
|
|
|
|
|
|
|
|
# 检查是否已存在
|
|
|
|
|
|
|
|
existing = db.query(TradeRecordTag).filter(
|
|
|
|
|
|
|
|
TradeRecordTag.trade_pair_id == pair_id,
|
|
|
|
|
|
|
|
TradeRecordTag.tag_id == tag_id,
|
|
|
|
|
|
|
|
).first()
|
|
|
|
|
|
|
|
if existing:
|
|
|
|
|
|
|
|
return {"success": True, "message": "标签已存在"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
relation = TradeRecordTag(trade_pair_id=pair_id, tag_id=tag_id)
|
|
|
|
|
|
|
|
db.add(relation)
|
|
|
|
|
|
|
|
db.commit()
|
|
|
|
|
|
|
|
return {"success": True, "message": "标签已添加"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.delete("/trade-pairs/{pair_id}/tags/{tag_id}")
|
|
|
|
|
|
|
|
def api_remove_tag_from_trade(
|
|
|
|
|
|
|
|
pair_id: int,
|
|
|
|
|
|
|
|
tag_id: int,
|
|
|
|
|
|
|
|
db: Session = Depends(get_analysis_db),
|
|
|
|
|
|
|
|
):
|
|
|
|
|
|
|
|
"""移除交易标签"""
|
|
|
|
|
|
|
|
db.query(TradeRecordTag).filter(
|
|
|
|
|
|
|
|
TradeRecordTag.trade_pair_id == pair_id,
|
|
|
|
|
|
|
|
TradeRecordTag.tag_id == tag_id,
|
|
|
|
|
|
|
|
).delete()
|
|
|
|
|
|
|
|
db.commit()
|
|
|
|
|
|
|
|
return {"success": True, "message": "标签已移除"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/trade-pairs/{pair_id}/tags")
|
|
|
|
|
|
|
|
def api_get_trade_tags(
|
|
|
|
|
|
|
|
pair_id: int,
|
|
|
|
|
|
|
|
db: Session = Depends(get_analysis_db),
|
|
|
|
|
|
|
|
):
|
|
|
|
|
|
|
|
"""获取交易的标签列表"""
|
|
|
|
|
|
|
|
relations = db.query(TradeRecordTag).filter(
|
|
|
|
|
|
|
|
TradeRecordTag.trade_pair_id == pair_id
|
|
|
|
|
|
|
|
).all()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tag_ids = [r.tag_id for r in relations]
|
|
|
|
|
|
|
|
tags = db.query(TradeTag).filter(TradeTag.id.in_(tag_ids)).all()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return {"success": True, "data": [{
|
|
|
|
|
|
|
|
"id": t.id,
|
|
|
|
|
|
|
|
"name": t.name,
|
|
|
|
|
|
|
|
"category": t.category,
|
|
|
|
|
|
|
|
} for t in tags]}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# --- 经验库 ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ExperienceRequest(BaseModel):
|
|
|
|
|
|
|
|
title: str
|
|
|
|
|
|
|
|
content: str
|
|
|
|
|
|
|
|
exp_type: str # lesson/tip/warning
|
|
|
|
|
|
|
|
source_pair_id: Optional[int] = None
|
|
|
|
|
|
|
|
source_date: Optional[str] = None
|
|
|
|
|
|
|
|
tags: Optional[list[str]] = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/experiences")
|
|
|
|
|
|
|
|
def api_get_experiences(
|
|
|
|
|
|
|
|
exp_type: Optional[str] = Query(None),
|
|
|
|
|
|
|
|
tag: Optional[str] = Query(None),
|
|
|
|
|
|
|
|
keyword: Optional[str] = Query(None),
|
|
|
|
|
|
|
|
page: int = Query(1, ge=1),
|
|
|
|
|
|
|
|
page_size: int = Query(20, ge=1, le=100),
|
|
|
|
|
|
|
|
db: Session = Depends(get_analysis_db),
|
|
|
|
|
|
|
|
):
|
|
|
|
|
|
|
|
"""获取经验列表(支持筛选和分页)"""
|
|
|
|
|
|
|
|
query = db.query(TradeExperience)
|
|
|
|
|
|
|
|
if exp_type:
|
|
|
|
|
|
|
|
query = query.filter(TradeExperience.exp_type == exp_type)
|
|
|
|
|
|
|
|
if tag:
|
|
|
|
|
|
|
|
query = query.filter(TradeExperience.tags.contains([tag]))
|
|
|
|
|
|
|
|
if keyword:
|
|
|
|
|
|
|
|
query = query.filter(
|
|
|
|
|
|
|
|
(TradeExperience.title.contains(keyword)) |
|
|
|
|
|
|
|
|
(TradeExperience.content.contains(keyword))
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
total = query.count()
|
|
|
|
|
|
|
|
experiences = query.order_by(
|
|
|
|
|
|
|
|
TradeExperience.created_at.desc()
|
|
|
|
|
|
|
|
).offset((page - 1) * page_size).limit(page_size).all()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return {"success": True, "data": {
|
|
|
|
|
|
|
|
"total": total,
|
|
|
|
|
|
|
|
"page": page,
|
|
|
|
|
|
|
|
"page_size": page_size,
|
|
|
|
|
|
|
|
"items": [{
|
|
|
|
|
|
|
|
"id": e.id,
|
|
|
|
|
|
|
|
"title": e.title,
|
|
|
|
|
|
|
|
"content": e.content,
|
|
|
|
|
|
|
|
"exp_type": e.exp_type,
|
|
|
|
|
|
|
|
"source_pair_id": e.source_pair_id,
|
|
|
|
|
|
|
|
"source_date": e.source_date,
|
|
|
|
|
|
|
|
"tags": e.tags or [],
|
|
|
|
|
|
|
|
"created_at": e.created_at.strftime('%Y-%m-%d %H:%M:%S') if e.created_at else None,
|
|
|
|
|
|
|
|
} for e in experiences],
|
|
|
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/experiences")
|
|
|
|
|
|
|
|
def api_create_experience(
|
|
|
|
|
|
|
|
req: ExperienceRequest,
|
|
|
|
|
|
|
|
db: Session = Depends(get_analysis_db),
|
|
|
|
|
|
|
|
):
|
|
|
|
|
|
|
|
"""创建经验"""
|
|
|
|
|
|
|
|
exp = TradeExperience(
|
|
|
|
|
|
|
|
title=req.title,
|
|
|
|
|
|
|
|
content=req.content,
|
|
|
|
|
|
|
|
exp_type=req.exp_type,
|
|
|
|
|
|
|
|
source_pair_id=req.source_pair_id,
|
|
|
|
|
|
|
|
source_date=req.source_date,
|
|
|
|
|
|
|
|
tags=req.tags or [],
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
db.add(exp)
|
|
|
|
|
|
|
|
db.commit()
|
|
|
|
|
|
|
|
return {"success": True, "data": {"id": exp.id}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.put("/experiences/{exp_id}")
|
|
|
|
|
|
|
|
def api_update_experience(
|
|
|
|
|
|
|
|
exp_id: int,
|
|
|
|
|
|
|
|
req: ExperienceRequest,
|
|
|
|
|
|
|
|
db: Session = Depends(get_analysis_db),
|
|
|
|
|
|
|
|
):
|
|
|
|
|
|
|
|
"""更新经验"""
|
|
|
|
|
|
|
|
exp = db.query(TradeExperience).filter(TradeExperience.id == exp_id).first()
|
|
|
|
|
|
|
|
if not exp:
|
|
|
|
|
|
|
|
raise HTTPException(status_code=404, detail="经验不存在")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for field in ['title', 'content', 'exp_type', 'source_pair_id', 'source_date', 'tags']:
|
|
|
|
|
|
|
|
val = getattr(req, field)
|
|
|
|
|
|
|
|
if val is not None:
|
|
|
|
|
|
|
|
setattr(exp, field, val)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
db.commit()
|
|
|
|
|
|
|
|
return {"success": True, "message": "经验已更新"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.delete("/experiences/{exp_id}")
|
|
|
|
|
|
|
|
def api_delete_experience(
|
|
|
|
|
|
|
|
exp_id: int,
|
|
|
|
|
|
|
|
db: Session = Depends(get_analysis_db),
|
|
|
|
|
|
|
|
):
|
|
|
|
|
|
|
|
"""删除经验"""
|
|
|
|
|
|
|
|
exp = db.query(TradeExperience).filter(TradeExperience.id == exp_id).first()
|
|
|
|
|
|
|
|
if not exp:
|
|
|
|
|
|
|
|
raise HTTPException(status_code=404, detail="经验不存在")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
db.delete(exp)
|
|
|
|
|
|
|
|
db.commit()
|
|
|
|
|
|
|
|
return {"success": True, "message": "经验已删除"}
|
|
|
|
|