feat:新增投顾agent和nl2sqlagent
This commit is contained in:
+46
-11
@@ -29,6 +29,7 @@ from repositories.advisor_report import AdvisorReportRepo
|
||||
from repositories.product import ProductRepo
|
||||
from repositories.risk_assessment import CustomerProfileRepo
|
||||
from repositories.sensitive_word import SensitiveWordRepo
|
||||
from repositories.sys_message import SysMessageRepo
|
||||
from schemas.advisor import DraftSaveReq, RebalanceRunReq, TalkScriptReq
|
||||
from service.advisor.agent_client import get_agent_client
|
||||
from service.advisor.permissions import ensure_customer_owned, require_owned_relation
|
||||
@@ -60,20 +61,30 @@ def _build_report_from_detail(detail: dict, advisor_id: int) -> AdvisorReport:
|
||||
)
|
||||
|
||||
|
||||
def _extract_buy_codes(suggestions: Any) -> list[str]:
|
||||
"""从建议清单提取「低配申购」产品代码(发送终审适当性校验对象)。
|
||||
def _extract_buy_codes(detail: Any) -> list[str]:
|
||||
"""从 Agent 草稿详情提取发送终审需要校验的产品代码。
|
||||
|
||||
假设:结构为 dict,申购侧键见 _BUY_KEYS;项为 {product_code} 或 {code}/{fund_code}。
|
||||
解析失败返回空列表(视为无结构化产品建议,适当性空过,不误拦)。
|
||||
兼容旧的顶层 ``suggestions``,以及 Agent 当前的 ``structured_data``:调仓只取
|
||||
``buy``,推荐取 ``items``。解析失败返回空列表,交由其它终审规则继续处理。
|
||||
"""
|
||||
if not isinstance(suggestions, dict):
|
||||
if not isinstance(detail, dict):
|
||||
return []
|
||||
|
||||
structured_data = detail.get("structured_data")
|
||||
data = structured_data if isinstance(structured_data, dict) else detail
|
||||
suggestions = data.get("suggestions")
|
||||
if isinstance(suggestions, dict):
|
||||
data = suggestions
|
||||
|
||||
items: list | None = None
|
||||
for key in _BUY_KEYS:
|
||||
value = suggestions.get(key)
|
||||
value = data.get(key)
|
||||
if isinstance(value, list):
|
||||
items = value
|
||||
break
|
||||
if items is None and isinstance(data.get("items"), list):
|
||||
items = data["items"]
|
||||
|
||||
codes: list[str] = []
|
||||
for it in items or []:
|
||||
if isinstance(it, dict):
|
||||
@@ -104,7 +115,7 @@ async def _resolve_product_risks(
|
||||
result = await get_agent_client().draft_detail(
|
||||
draft_id, auth_header=auth_header, trace_id=trace_id
|
||||
)
|
||||
codes = _extract_buy_codes((result["data"] or {}).get("suggestions"))
|
||||
codes = _extract_buy_codes(result["data"] or {})
|
||||
product_repo = ProductRepo(db)
|
||||
risks: list[str | None] = []
|
||||
for code in codes:
|
||||
@@ -214,7 +225,11 @@ async def save_draft(
|
||||
# 1) 先取草稿确认归属(避免对无权限草稿执行写操作),并拿到 intent/customer_id
|
||||
detail = await get_draft(db, user, auth_header=auth_header, trace_id=trace_id, draft_id=draft_id)
|
||||
# 2) 调 Agent 保存(Agent 重新适当性校验,违规 40020;缺免责仅告警不阻断)
|
||||
payload = {"title": req.title, "content": req.content, "suggestions": req.suggestions}
|
||||
payload = {
|
||||
"title": req.title,
|
||||
"content": req.content,
|
||||
"structured_data": req.suggestions,
|
||||
}
|
||||
result = await get_agent_client().draft_save(
|
||||
draft_id, payload, auth_header=auth_header, trace_id=trace_id
|
||||
)
|
||||
@@ -262,6 +277,9 @@ async def send_draft(
|
||||
elif report.advisor_id != user.id:
|
||||
raise ForbiddenError("无权操作该客户数据")
|
||||
|
||||
if report.send_status == REPORT_SEND_STATUS_DISCARDED:
|
||||
raise ParamError("已废弃的报告不可发送")
|
||||
|
||||
# 幂等:已发送直接返回(重复点击不重复发站内信)
|
||||
if report.send_status == REPORT_SEND_STATUS_SENT:
|
||||
return {**_sent_payload(report), "duplicated": True}
|
||||
@@ -283,6 +301,19 @@ async def send_draft(
|
||||
sensitive_words=sensitive_words,
|
||||
)
|
||||
|
||||
# 提交结果不确定后重试时,先复用已经写入的站内信,避免重复触达客户。
|
||||
existing_message = await SysMessageRepo(db).get_by_biz_id(
|
||||
report.report_id, user_id=report.customer_id
|
||||
)
|
||||
if existing_message is not None:
|
||||
report.send_status = REPORT_SEND_STATUS_SENT
|
||||
report.send_time = report.send_time or existing_message.create_time
|
||||
report.send_by = report.send_by or user.id
|
||||
report.msg_id = existing_message.id
|
||||
db.add(report)
|
||||
await db.commit()
|
||||
return {**_sent_payload(report), "duplicated": True}
|
||||
|
||||
# 写站内信 + 报告置 sent,同事务(失败可重试,避免假送达)
|
||||
msg_type = INTENT_TO_MSG_TYPE.get(report.intent, MSG_TYPE_RECOMMEND)
|
||||
message = SysMessage(
|
||||
@@ -297,9 +328,13 @@ async def send_draft(
|
||||
report.send_by = user.id
|
||||
db.add(message)
|
||||
db.add(report) # 已跟踪对象时无副作用,新对象时入 session
|
||||
await db.flush() # 生成 message.id,供回填 msg_id
|
||||
report.msg_id = message.id
|
||||
await db.commit()
|
||||
try:
|
||||
await db.flush() # 生成 message.id,供回填 msg_id
|
||||
report.msg_id = message.id
|
||||
await db.commit()
|
||||
except Exception:
|
||||
await db.rollback()
|
||||
raise
|
||||
return _sent_payload(report)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user