33All queries are optimized to filter at database level.
44"""
55
6+ import asyncio
67from typing import Any , Dict , List , Optional
78
89from app .core .logger import logger
1516 get_analytics_lead_status_counts_query ,
1617 get_analytics_lead_status_counts_total_query ,
1718 get_analytics_outbound_numbers_query ,
19+ get_analytics_outcome_breakdown_query ,
1820 get_analytics_summary_query ,
1921 get_analytics_trends_query ,
2022 get_call_details_records_query ,
23+ get_dashboard_counts_query ,
2124 get_distinct_merchant_ids_query ,
2225 get_distinct_outcomes_query ,
2326 get_distinct_resellers_query ,
@@ -52,32 +55,19 @@ async def get_summary_analytics_from_db(
5255 )
5356
5457 try :
55- query_text , values = get_analytics_summary_query (filters , group_by )
56- result = await run_parameterized_query (query_text , values )
58+ if group_by :
59+ # Grouped mode: single query (paginated groups, already fast enough)
60+ query_text , values = get_analytics_summary_query (filters , group_by )
61+ result = await run_parameterized_query (query_text , values )
5762
58- logger .debug (
59- f"[Analytics DB] Summary query returned { len (result ) if result else 0 } rows"
60- )
63+ logger .debug (
64+ f"[Analytics DB] Summary query returned { len (result ) if result else 0 } rows"
65+ )
6166
62- if not result or len (result ) == 0 :
63- logger .warning ("[Analytics DB] Summary query returned no results" )
64- if group_by :
67+ if not result or len (result ) == 0 :
68+ logger .warning ("[Analytics DB] Summary query returned no results" )
6569 return []
66- return {
67- "total_calls" : 0 ,
68- "outbound_calls" : 0 ,
69- "inbound_calls" : 0 ,
70- "completed_calls" : 0 ,
71- "failed_calls" : 0 ,
72- "success_rate" : 0.0 ,
73- "average_duration" : None ,
74- "total_templates" : 0 ,
75- "total_shops" : 0 ,
76- "outcome_breakdown" : {},
77- }
7870
79- if group_by :
80- # Return list of grouped results
8171 grouped_results = []
8272 for row in result :
8373 total_calls = row ["total_calls" ] or 0
@@ -102,8 +92,8 @@ async def get_summary_analytics_from_db(
10292 if row ["average_duration" ]
10393 else None
10494 ),
105- "total_templates" : row [ "total_templates" ] or 0 ,
106- "total_shops" : row [ "total_shops" ] or 0 ,
95+ "total_templates" : row . get ( "total_templates" ) or 0 ,
96+ "total_shops" : row . get ( "total_shops" ) or 0 ,
10797 "outcome_breakdown" : row ["outcome_breakdown" ] or {},
10898 }
10999 )
@@ -112,36 +102,54 @@ async def get_summary_analytics_from_db(
112102 f"[Analytics DB] Grouped summary returned { len (grouped_results )} groups"
113103 )
114104 return grouped_results
115- else :
116- # Return single aggregate result
117- row = result [0 ]
118- total_calls = row ["total_calls" ] or 0
119- completed_calls = row ["completed_calls" ] or 0
120- failed_calls = row ["failed_calls" ] or 0
121- success_rate = (
122- (completed_calls / total_calls * 100 ) if total_calls > 0 else 0.0
123- )
124105
125- logger .info (
126- f"[Analytics DB] Summary result: { total_calls } total calls, { completed_calls } completed ({ success_rate :.2f} % success)"
127- )
106+ # Aggregate mode (dashboard cards): run counts and outcome breakdown in
107+ # parallel so the slower jsonb_object_agg doesn't block the counts.
108+ summary_q , summary_v = get_analytics_summary_query (filters )
109+ outcome_q , outcome_v = get_analytics_outcome_breakdown_query (filters )
128110
129- return {
130- "total_calls" : total_calls ,
131- "outbound_calls" : row ["outbound_calls" ] or 0 ,
132- "inbound_calls" : row ["inbound_calls" ] or 0 ,
133- "completed_calls" : completed_calls ,
134- "failed_calls" : failed_calls ,
135- "success_rate" : round (success_rate , 2 ),
136- "average_duration" : (
137- round (float (row ["average_duration" ]), 2 )
138- if row ["average_duration" ]
139- else None
140- ),
141- "total_templates" : row ["total_templates" ] or 0 ,
142- "total_shops" : row ["total_shops" ] or 0 ,
143- "outcome_breakdown" : row ["outcome_breakdown" ] or {},
144- }
111+ summary_res , outcome_res = await asyncio .gather (
112+ run_parameterized_query (summary_q , summary_v ),
113+ run_parameterized_query (outcome_q , outcome_v ),
114+ )
115+
116+ logger .debug (
117+ f"[Analytics DB] Summary query returned { len (summary_res ) if summary_res else 0 } rows, "
118+ f"outbreakdown query returned { len (outcome_res ) if outcome_res else 0 } rows"
119+ )
120+
121+ row = dict (summary_res [0 ]) if summary_res and len (summary_res ) > 0 else {}
122+ outcome_row = (
123+ dict (outcome_res [0 ]) if outcome_res and len (outcome_res ) > 0 else {}
124+ )
125+
126+ total_calls = row .get ("total_calls" ) or 0
127+ completed_calls = row .get ("completed_calls" ) or 0
128+ failed_calls = row .get ("failed_calls" ) or 0
129+ success_rate = (
130+ (completed_calls / total_calls * 100 ) if total_calls > 0 else 0.0
131+ )
132+
133+ logger .info (
134+ f"[Analytics DB] Summary result: { total_calls } total calls, { completed_calls } completed ({ success_rate :.2f} % success)"
135+ )
136+
137+ return {
138+ "total_calls" : total_calls ,
139+ "outbound_calls" : row .get ("outbound_calls" ) or 0 ,
140+ "inbound_calls" : row .get ("inbound_calls" ) or 0 ,
141+ "completed_calls" : completed_calls ,
142+ "failed_calls" : failed_calls ,
143+ "success_rate" : round (success_rate , 2 ),
144+ "average_duration" : (
145+ round (float (row ["average_duration" ]), 2 )
146+ if row .get ("average_duration" )
147+ else None
148+ ),
149+ "total_templates" : 0 ,
150+ "total_shops" : 0 ,
151+ "outcome_breakdown" : outcome_row .get ("outcome_breakdown" ) or {},
152+ }
145153
146154 except Exception as e :
147155 logger .error (f"Error getting summary analytics: { e } " , exc_info = True )
@@ -444,6 +452,41 @@ async def get_lead_status_counts_from_db(
444452 raise
445453
446454
455+ async def get_dashboard_counts_from_db (
456+ filters : Dict [str , Any ],
457+ ) -> Dict [str , int ]:
458+ """
459+ Get lightweight dashboard card counts from DB.
460+ """
461+ logger .info (f"[Analytics DB] Getting dashboard counts with filters: { filters } " )
462+
463+ try :
464+ query_text , values = get_dashboard_counts_query (filters )
465+ result = await run_parameterized_query (query_text , values )
466+
467+ if not result or len (result ) == 0 :
468+ return {
469+ "outbound_calls" : 0 ,
470+ "inbound_calls" : 0 ,
471+ "no_answer_calls" : 0 ,
472+ "busy_calls" : 0 ,
473+ "calls_with_outcomes" : 0 ,
474+ }
475+
476+ row = result [0 ]
477+ return {
478+ "outbound_calls" : row ["outbound_calls" ] or 0 ,
479+ "inbound_calls" : row ["inbound_calls" ] or 0 ,
480+ "no_answer_calls" : row ["no_answer_calls" ] or 0 ,
481+ "busy_calls" : row ["busy_calls" ] or 0 ,
482+ "calls_with_outcomes" : row ["calls_with_outcomes" ] or 0 ,
483+ }
484+
485+ except Exception as e :
486+ logger .error (f"Error getting dashboard counts: { e } " , exc_info = True )
487+ raise
488+
489+
447490async def get_distinct_outcomes_from_db (
448491 filters : Dict [str , Any ],
449492) -> List [str ]:
0 commit comments