feat(r1-touch): 建议分级+决策类推送+应用前预览 (alert不推送/同title防轰炸/preview对比)
This commit is contained in:
@@ -8,7 +8,7 @@ from app.deps import get_entity_id
|
||||
from app.auth_middleware import require_auth, require_role
|
||||
from app.models import KPIDefinition, KPIValue, KPIAlert, StrategicMap, User, ActionPlan, BudgetPlan, AISuggestion
|
||||
from app.utils.cache import get as cache_get, set as cache_set
|
||||
import json, hashlib, httpx, os
|
||||
import json, hashlib, httpx, os, urllib.request
|
||||
from datetime import datetime, date
|
||||
router = APIRouter(prefix="/api/cma/ai", tags=["AI分析"],
|
||||
dependencies=[Depends(require_role("ceo", "finance", "business", "it"))],
|
||||
@@ -25,6 +25,8 @@ def _sug_dict(s: AISuggestion) -> dict:
|
||||
"source": s.source,
|
||||
"suggestion_type": s.suggestion_type,
|
||||
"target_type": s.target_type,
|
||||
"category": s.category or "decision",
|
||||
"pushed": s.pushed or 0,
|
||||
"target_id": s.target_id,
|
||||
"title": s.title,
|
||||
"content": s.content,
|
||||
@@ -76,6 +78,7 @@ def generate_rule_suggestions(db: Session, entity_id: int,
|
||||
source=source,
|
||||
suggestion_type=suggestion_type,
|
||||
target_type=target_type,
|
||||
category="alert" if target_type == "alert" else "decision",
|
||||
target_id=tid,
|
||||
title=title,
|
||||
content=content,
|
||||
@@ -170,9 +173,51 @@ def generate_rule_suggestions(db: Session, entity_id: int,
|
||||
db.commit()
|
||||
for s in created:
|
||||
db.refresh(s)
|
||||
# R1触达修复(2026-08-31): 只对新建的决策类建议推送企微(预警类不推防噪音)
|
||||
# 防轰炸: 同 title 建议幂等不重建 + pushed 标记只推一次;存量不推(只推新建)
|
||||
for s in created:
|
||||
if s.category == "decision" and not s.pushed:
|
||||
ok = _push_decision_suggestion(s)
|
||||
if ok:
|
||||
s.pushed = 1
|
||||
db.commit()
|
||||
return created
|
||||
|
||||
|
||||
_TYPE_LABELS = {"kpi_target": "KPI目标", "budget_adjust": "预算调整", "action_plan": "行动方案"}
|
||||
|
||||
|
||||
def _push_decision_suggestion(s: AISuggestion) -> bool:
|
||||
"""决策类建议推送到企微(8800 relay,与 lead.py 同款已验证)
|
||||
|
||||
仅 decision 类;预警类不进推送流。失败不影响主流程(try/except)。
|
||||
"""
|
||||
if getattr(s, "category", "decision") != "decision":
|
||||
return False
|
||||
type_label = _TYPE_LABELS.get(s.suggestion_type, s.suggestion_type)
|
||||
content = (
|
||||
f"## 📌 AI决策建议\n"
|
||||
f"**{s.title}**\n"
|
||||
f"{str(s.content or '')[:120]}\n"
|
||||
f"类型标签: {type_label}\n"
|
||||
f"---\n"
|
||||
f"⏰ {datetime.now().strftime('%Y-%m-%d %H:%M')}"
|
||||
)
|
||||
msg = {"msgtype": "markdown", "markdown": {"content": content}}
|
||||
try:
|
||||
data = json.dumps(msg, ensure_ascii=False).encode("utf-8")
|
||||
req = urllib.request.Request(
|
||||
"http://127.0.0.1:8800/send",
|
||||
data=data,
|
||||
headers={"Content-Type": "application/json"},
|
||||
method="POST",
|
||||
)
|
||||
urllib.request.urlopen(req, timeout=5)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _unapplied_suggestions(db: Session, entity_id: int, limit: int = 20) -> list:
|
||||
items = db.query(AISuggestion).filter(
|
||||
AISuggestion.entity_id == entity_id,
|
||||
|
||||
Reference in New Issue
Block a user