Some checks failed
Velocity-OS Deployment Pipeline / lint (push) Has been cancelled
Velocity-OS Deployment Pipeline / build-and-push (agents) (push) Has been cancelled
Velocity-OS Deployment Pipeline / build-and-push (core) (push) Has been cancelled
Velocity-OS Deployment Pipeline / build-and-push (media-engine) (push) Has been cancelled
Velocity-OS Deployment Pipeline / build-and-push (webos) (push) Has been cancelled
Velocity-OS Deployment Pipeline / sign-images (agents) (push) Has been cancelled
Velocity-OS Deployment Pipeline / sign-images (core) (push) Has been cancelled
Velocity-OS Deployment Pipeline / sign-images (media-engine) (push) Has been cancelled
Velocity-OS Deployment Pipeline / sign-images (webos) (push) Has been cancelled
Velocity-OS Deployment Pipeline / notify-ingress (push) Has been cancelled
115 lines
4.2 KiB
Python
115 lines
4.2 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Request
|
|
|
|
from backend.auth.dependencies import UserPrincipal, get_current_user
|
|
from backend.crm.canonical_schema import ensure_canonical_crm_schema
|
|
|
|
router = APIRouter(dependencies=[Depends(get_current_user)])
|
|
|
|
|
|
async def _pool(request: Request):
|
|
await ensure_canonical_crm_schema(request.app)
|
|
pool = getattr(request.app.state, "db_pool", None)
|
|
if pool is None:
|
|
raise HTTPException(status_code=503, detail="Database unavailable.")
|
|
return pool
|
|
|
|
|
|
def _money_inr(value: Any) -> str:
|
|
amount = float(value or 0)
|
|
if amount >= 10_000_000:
|
|
return f"₹{amount / 10_000_000:.1f}Cr"
|
|
if amount >= 100_000:
|
|
return f"₹{amount / 100_000:.1f}L"
|
|
return f"₹{amount:,.0f}"
|
|
|
|
|
|
@router.get("/dashboard/morning-brief")
|
|
async def morning_brief(request: Request, user: UserPrincipal = Depends(get_current_user)) -> dict[str, Any]:
|
|
"""Command pillar briefing derived from canonical CRM data."""
|
|
pool = await _pool(request)
|
|
async with pool.acquire() as conn:
|
|
people_count = await conn.fetchval("SELECT COUNT(*) FROM crm_people")
|
|
active_leads = await conn.fetchval(
|
|
"""
|
|
SELECT COUNT(*)
|
|
FROM crm_leads
|
|
WHERE status NOT IN ('booked', 'lost', 'dormant')
|
|
"""
|
|
)
|
|
open_pipeline = await conn.fetchval(
|
|
"""
|
|
SELECT COALESCE(SUM(value), 0)
|
|
FROM crm_opportunities
|
|
WHERE stage NOT IN ('closed_won', 'closed_lost')
|
|
"""
|
|
)
|
|
avg_qd = await conn.fetchval("SELECT COALESCE(AVG(current_value), 0) FROM intel_qd_scores")
|
|
overdue_tasks = await conn.fetchval(
|
|
"""
|
|
SELECT COUNT(*)
|
|
FROM intel_reminders
|
|
WHERE status IN ('pending', 'confirmed') AND due_at < NOW()
|
|
"""
|
|
)
|
|
stages = await conn.fetch(
|
|
"""
|
|
SELECT status::text AS id, INITCAP(REPLACE(status::text, '_', ' ')) AS label, COUNT(*)::int AS count
|
|
FROM crm_leads
|
|
GROUP BY status
|
|
ORDER BY MIN(created_at) NULLS LAST, status::text
|
|
"""
|
|
)
|
|
actions = await conn.fetch(
|
|
"""
|
|
SELECT p.person_id::text, p.full_name, n.recommended_action, n.priority, n.rationale,
|
|
COALESCE(q.current_value, 0)::float AS qd_score
|
|
FROM read_next_best_action n
|
|
JOIN crm_people p ON p.person_id = n.person_id
|
|
LEFT JOIN LATERAL (
|
|
SELECT current_value
|
|
FROM intel_qd_scores
|
|
WHERE person_id = p.person_id
|
|
ORDER BY current_value DESC NULLS LAST
|
|
LIMIT 1
|
|
) q ON TRUE
|
|
ORDER BY
|
|
CASE n.priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 ELSE 3 END,
|
|
qd_score DESC,
|
|
n.computed_at DESC NULLS LAST
|
|
LIMIT 3
|
|
"""
|
|
)
|
|
|
|
priority_cards = []
|
|
for row in actions:
|
|
urgency = (row["priority"] or "medium").lower()
|
|
if urgency not in {"high", "medium", "low"}:
|
|
urgency = "high" if urgency == "critical" else "medium"
|
|
priority_cards.append(
|
|
{
|
|
"id": row["person_id"],
|
|
"type": "follow_up",
|
|
"headline": row["recommended_action"] or "Follow up with client",
|
|
"sublabel": row["rationale"],
|
|
"personId": row["person_id"],
|
|
"personName": row["full_name"],
|
|
"cta": "Open client",
|
|
"urgency": urgency,
|
|
}
|
|
)
|
|
|
|
return {
|
|
"kpis": [
|
|
{"label": "Clients", "value": str(int(people_count or 0)), "sublabel": "canonical CRM records"},
|
|
{"label": "Active leads", "value": str(int(active_leads or 0)), "sublabel": "not booked/lost/dormant"},
|
|
{"label": "Open pipeline", "value": _money_inr(open_pipeline), "sublabel": f"avg QD {float(avg_qd or 0):.0f}"},
|
|
{"label": "Overdue follow-ups", "value": str(int(overdue_tasks or 0)), "deltaPositive": False},
|
|
],
|
|
"priorityCards": priority_cards,
|
|
"pipelineStages": [dict(row) for row in stages],
|
|
}
|