feat(threshold): Implement customer loss threshold configuration and notification system

- Added `ThresholdRepository` for managing customer loss threshold configurations and notifications.
- Introduced `threshold_service` to handle loss threshold alerts based on customer portfolio performance.
- Enhanced `customer_prompts` to include new intent for querying product net values.
- Updated `customer_service` to integrate new threshold alert functionality into existing workflows.
- Implemented `sanitize_postprocess` for improved compliance handling in customer interactions.
- Enhanced course documentation to reflect updates in advisor training modules and interactive elements.

This update significantly improves the customer experience by providing proactive loss threshold notifications and enhancing the overall service framework.
This commit is contained in:
2026-09-10 10:57:40 +08:00
parent 6f222f1c56
commit 4b8e11c9bd
102 changed files with 4054 additions and 445 deletions
+14 -10
View File
@@ -62,11 +62,13 @@ from app.service.profile_service import (
from app.service.rag_service import VisitorRagService
from app.tool.core_ro_tool import (
query_holdings,
query_product_nav,
query_risk_profile,
query_suitability,
query_trades,
)
from app.utils.compliance_guard import RISK_DISCLAIMER, sanitize_reply, should_add_disclaimer
from app.utils.compliance_guard import RISK_DISCLAIMER, should_add_disclaimer
from app.utils.sanitize_postprocess import finalize_sanitized_reply
# ---------------------------------------------------------------------------
# LLM 调用
@@ -125,8 +127,11 @@ _TOOL_BY_INTENT = {
"transaction_query": query_trades,
"risk_assessment_query": query_risk_profile,
"suitability_check": query_suitability,
"nav_query": query_product_nav,
}
_DATA_QUERY_INTENTS = frozenset(_TOOL_BY_INTENT.keys())
_R_LEVEL_FULL_RE = re.compile(r"[Rr]\s*([1-5])")
@@ -201,7 +206,7 @@ def rag_search(state: CustomerState) -> CustomerState:
def param_extract(state: CustomerState) -> CustomerState:
intent = state["intent"]
if intent not in ("transaction_query", "suitability_check"):
if intent not in ("transaction_query", "suitability_check", "nav_query"):
return {"params": {}}
try:
@@ -250,6 +255,8 @@ def tool_call(state: CustomerState) -> CustomerState:
product_keyword=params.get("product_keyword"),
risk_level=params.get("risk_level"),
)
elif intent == "nav_query":
result = fn(cid, product_keyword=params.get("product_keyword"))
else:
result = fn(cid)
except Exception:
@@ -288,16 +295,13 @@ def interpret(state: CustomerState) -> CustomerState:
except Exception:
reply = fact_text # LLM 不可用时直接返回脱敏事实文本
reply, need_transfer = sanitize_reply(reply)
reply, need_transfer = finalize_sanitized_reply(
reply, intent=state.get("intent"), fact_text=fact_text,
)
if need_transfer:
return {"reply": reply, "transfer_to_human": True, "has_disclaimer": False}
return {"reply": reply, "has_disclaimer": False}
# ---------------------------------------------------------------------------
# 节点 7:RAG 生成(画像红线在 GENERATE_SYSTEM)
# ---------------------------------------------------------------------------
def generate(state: CustomerState) -> CustomerState:
if not state.get("rag_context"):
return {"reply": state.get("reply") or FALLBACK_TEXT, "has_disclaimer": False}
@@ -319,7 +323,7 @@ def generate(state: CustomerState) -> CustomerState:
except Exception:
return {"reply": FALLBACK_TEXT, "intent": "fallback"}
reply, need_transfer = sanitize_reply(reply)
reply, need_transfer = finalize_sanitized_reply(reply, intent=state.get("intent"))
if need_transfer:
return {"reply": reply, "transfer_to_human": True, "has_disclaimer": False}
@@ -351,7 +355,7 @@ def chitchat(state: CustomerState) -> CustomerState:
except Exception:
return {"reply": FALLBACK_TEXT, "intent": "fallback"}
reply, need_transfer = sanitize_reply(reply)
reply, need_transfer = finalize_sanitized_reply(reply, intent=state.get("intent"))
if need_transfer:
return {"reply": reply, "transfer_to_human": True, "has_disclaimer": False}
return {"reply": reply, "has_disclaimer": False}