"""驾驶舱聚合(首页数据与待办聚合,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}, }