87 lines
3.1 KiB
Python
87 lines
3.1 KiB
Python
"""驾驶舱聚合(首页数据与待办聚合,PRD §4.1)。
|
|
|
|
口径说明(E1 最小口径):总 AUM=名下客户「持有中」持仓市值之和;有效客户=signed;
|
|
流失率=closed/总客户;报告产出/发送=advisor_report 计数。回访完成率等精细口径 P2 后置。
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from common_const import (
|
|
CUSTOMER_REL_STATUS_CLOSED,
|
|
CUSTOMER_REL_STATUS_SIGNED,
|
|
REPORT_SEND_STATUS_DRAFT,
|
|
REPORT_SEND_STATUS_SENT,
|
|
TODO_STATUS_PENDING,
|
|
)
|
|
from model.sys_user import SysUser
|
|
from repositories.advisor_report import AdvisorReportRepo
|
|
from repositories.advisor_todo import AdvisorTodoRepo
|
|
from repositories.advisor_visit_record import AdvisorVisitRecordRepo
|
|
from repositories.customer_relation import CustomerRelationRepo
|
|
|
|
|
|
def _todo_summary(todo) -> dict:
|
|
return {
|
|
"id": todo.id,
|
|
"todo_type": todo.todo_type,
|
|
"customer_id": todo.customer_id,
|
|
"priority": todo.priority,
|
|
"status": todo.status,
|
|
"due_at": todo.due_at.isoformat() if todo.due_at else None,
|
|
}
|
|
|
|
|
|
async def get_dashboard(db: AsyncSession, user: SysUser) -> dict:
|
|
relation_repo = CustomerRelationRepo(db)
|
|
todo_repo = AdvisorTodoRepo(db)
|
|
report_repo = AdvisorReportRepo(db)
|
|
visit_repo = AdvisorVisitRecordRepo(db)
|
|
|
|
pending = await todo_repo.count_by_advisor(advisor_id=user.id, status=TODO_STATUS_PENDING)
|
|
pending_items = await todo_repo.list_by_advisor(
|
|
advisor_id=user.id, status=TODO_STATUS_PENDING, limit=10, offset=0
|
|
)
|
|
total = await relation_repo.count_customer_rows(advisor_id=user.id)
|
|
signed = await relation_repo.count_customer_rows(
|
|
advisor_id=user.id, status=CUSTOMER_REL_STATUS_SIGNED
|
|
)
|
|
closed = await relation_repo.count_customer_rows(
|
|
advisor_id=user.id, status=CUSTOMER_REL_STATUS_CLOSED
|
|
)
|
|
aum = await relation_repo.sum_holdings_value(user.id)
|
|
sent = await report_repo.count_by_advisor(
|
|
advisor_id=user.id, send_status=REPORT_SEND_STATUS_SENT
|
|
)
|
|
draft = await report_repo.count_by_advisor(
|
|
advisor_id=user.id, send_status=REPORT_SEND_STATUS_DRAFT
|
|
)
|
|
visits = await visit_repo.count_by_advisor(advisor_id=user.id)
|
|
|
|
# 客户分层:C1-C5 各档人数(未知等级归入 unknown)
|
|
levels = {k: 0 for k in ("C1", "C2", "C3", "C4", "C5")}
|
|
unknown = 0
|
|
for level, cnt in await relation_repo.risk_level_distribution(user.id):
|
|
if level in levels:
|
|
levels[level] = cnt
|
|
else:
|
|
unknown += cnt
|
|
|
|
return {
|
|
"todos": {
|
|
"pending": pending,
|
|
"items": [_todo_summary(todo) for todo in pending_items],
|
|
},
|
|
"overview": {
|
|
"total_customers": total,
|
|
"signed_customers": signed,
|
|
"closed_customers": closed,
|
|
"churn_rate": round(closed / total, 4) if total else 0.0,
|
|
"total_aum": f"{aum:.2f}",
|
|
"reports_sent": sent,
|
|
"reports_draft": draft,
|
|
"visits_count": visits,
|
|
},
|
|
"customer_levels": {**levels, "unknown": unknown},
|
|
}
|