mirror of
http://88.130.71.182:3000/BlitTech/contexta_be.git
synced 2026-06-12 23:23:21 +00:00
feat: add appointments, campaigns, admin, storage, tests and various updates
- Add new routers: admin, appointments, campaigns - Add storage service and logging config - Add migrations directory and test suite with pytest config - Add supabase_migration_features.sql - Update models, dependencies, config, and existing routers - Remove whatsapp_service (deleted) - Update pyproject.toml and uv.lock dependencies Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,7 @@ from app.database import get_supabase
|
||||
from app.dependencies import get_current_user
|
||||
from app.config import PLAN_LIMITS
|
||||
from typing import List, Optional, Dict
|
||||
from collections import defaultdict
|
||||
from pydantic import BaseModel
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
@@ -127,14 +128,7 @@ async def get_analytics_overview(user=Depends(get_current_user)):
|
||||
conversations_used=0,
|
||||
)
|
||||
|
||||
# Gather per-chatbot analytics
|
||||
chatbot_analytics = []
|
||||
total_convos = 0
|
||||
total_msgs = 0
|
||||
total_sessions = 0
|
||||
month_convos = 0
|
||||
all_ratings = []
|
||||
|
||||
# ── Batch queries (fixes N+1) ────────────────────────────────────────────────
|
||||
now = datetime.utcnow()
|
||||
month_start = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
|
||||
week_start = now - timedelta(days=now.weekday())
|
||||
@@ -142,14 +136,60 @@ async def get_analytics_overview(user=Depends(get_current_user)):
|
||||
today_start = now.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
thirty_days_ago = now - timedelta(days=30)
|
||||
|
||||
# Batch query 1: ALL conversations for all chatbots (single query)
|
||||
all_convos_resp = supabase.table("conversations") \
|
||||
.select("id, chatbot_id, session_id, language, created_at") \
|
||||
.in_("chatbot_id", chatbot_ids) \
|
||||
.execute()
|
||||
all_convos = all_convos_resp.data or []
|
||||
all_conv_ids = [c["id"] for c in all_convos]
|
||||
|
||||
# Batch query 2: ALL messages for all conversations (single query)
|
||||
all_msgs: List[Dict] = []
|
||||
if all_conv_ids:
|
||||
# Split into chunks of 500 to avoid URL length limits
|
||||
for i in range(0, len(all_conv_ids), 500):
|
||||
chunk = all_conv_ids[i:i + 500]
|
||||
msgs_resp = supabase.table("messages") \
|
||||
.select("id, conversation_id, role, content, created_at") \
|
||||
.in_("conversation_id", chunk) \
|
||||
.execute()
|
||||
all_msgs.extend(msgs_resp.data or [])
|
||||
|
||||
# Batch query 3: ALL feedback for all chatbots (single query)
|
||||
all_feedback: List[Dict] = []
|
||||
if chatbot_ids:
|
||||
fb_resp = supabase.table("message_feedback") \
|
||||
.select("chatbot_id, feedback") \
|
||||
.in_("chatbot_id", chatbot_ids) \
|
||||
.execute()
|
||||
all_feedback = fb_resp.data or []
|
||||
|
||||
# Index data by chatbot_id for O(1) lookups
|
||||
convos_by_chatbot: Dict[str, List[Dict]] = defaultdict(list)
|
||||
for c in all_convos:
|
||||
convos_by_chatbot[c["chatbot_id"]].append(c)
|
||||
|
||||
msgs_by_conv: Dict[str, List[Dict]] = defaultdict(list)
|
||||
for m in all_msgs:
|
||||
msgs_by_conv[m["conversation_id"]].append(m)
|
||||
|
||||
fb_by_chatbot: Dict[str, List[Dict]] = defaultdict(list)
|
||||
for f in all_feedback:
|
||||
fb_by_chatbot[f["chatbot_id"]].append(f)
|
||||
|
||||
# ── Aggregate per chatbot ────────────────────────────────────────────────────
|
||||
chatbot_analytics = []
|
||||
total_convos = 0
|
||||
total_msgs = 0
|
||||
total_sessions = 0
|
||||
month_convos = 0
|
||||
all_ratings = []
|
||||
|
||||
for chatbot in chatbot_list:
|
||||
cid = chatbot["id"]
|
||||
|
||||
# Total conversations
|
||||
convos = supabase.table("conversations").select("id, session_id, language, created_at", count="exact") \
|
||||
.eq("chatbot_id", cid).execute()
|
||||
conv_count = convos.count or 0
|
||||
conv_data = convos.data or []
|
||||
conv_data = convos_by_chatbot[cid]
|
||||
conv_count = len(conv_data)
|
||||
total_convos += conv_count
|
||||
|
||||
# Unique sessions
|
||||
@@ -157,34 +197,35 @@ async def get_analytics_overview(user=Depends(get_current_user)):
|
||||
unique_sess = len(sessions)
|
||||
total_sessions += unique_sess
|
||||
|
||||
# Total messages
|
||||
msgs = supabase.table("messages").select("id", count="exact") \
|
||||
.in_("conversation_id", [c["id"] for c in conv_data] if conv_data else [""]).execute()
|
||||
msg_count = msgs.count or 0
|
||||
# Messages for this chatbot
|
||||
chatbot_msgs = []
|
||||
for c in conv_data:
|
||||
chatbot_msgs.extend(msgs_by_conv[c["id"]])
|
||||
msg_count = len(chatbot_msgs)
|
||||
total_msgs += msg_count
|
||||
|
||||
# Time-based conversation counts
|
||||
today_count = sum(1 for c in conv_data if c.get("created_at") and c["created_at"][:10] == today_start.strftime("%Y-%m-%d"))
|
||||
today_str = today_start.strftime("%Y-%m-%d")
|
||||
today_count = sum(1 for c in conv_data if c.get("created_at") and c["created_at"][:10] == today_str)
|
||||
week_count = sum(1 for c in conv_data if c.get("created_at") and c["created_at"] >= week_start.isoformat())
|
||||
month_count = sum(1 for c in conv_data if c.get("created_at") and c["created_at"] >= month_start.isoformat())
|
||||
month_convos += month_count
|
||||
|
||||
# Daily conversations (last 30 days)
|
||||
daily = {}
|
||||
daily: Dict[str, int] = {}
|
||||
for c in conv_data:
|
||||
if c.get("created_at") and c["created_at"] >= thirty_days_ago.isoformat():
|
||||
day = c["created_at"][:10]
|
||||
daily[day] = daily.get(day, 0) + 1
|
||||
|
||||
daily_list = [DailyConversations(date=d, count=n) for d, n in sorted(daily.items())]
|
||||
|
||||
# Languages used
|
||||
# Languages
|
||||
lang_counts: Dict[str, int] = {}
|
||||
for c in conv_data:
|
||||
lang = c.get("language", "en")
|
||||
lang_counts[lang] = lang_counts.get(lang, 0) + 1
|
||||
|
||||
# Peak hour (approximate from created_at)
|
||||
# Peak hour
|
||||
hour_counts: Dict[int, int] = {}
|
||||
for c in conv_data:
|
||||
if c.get("created_at") and len(c["created_at"]) > 13:
|
||||
@@ -195,35 +236,25 @@ async def get_analytics_overview(user=Depends(get_current_user)):
|
||||
pass
|
||||
peak = max(hour_counts, key=hour_counts.get) if hour_counts else None
|
||||
|
||||
# Top queries (from user messages, get first message per conversation)
|
||||
top_queries: List[TopQuery] = []
|
||||
if conv_data:
|
||||
conv_ids = [c["id"] for c in conv_data[:100]] # limit to recent 100
|
||||
user_msgs = supabase.table("messages").select("content") \
|
||||
.in_("conversation_id", conv_ids) \
|
||||
.eq("role", "user") \
|
||||
.limit(200).execute()
|
||||
query_counts: Dict[str, int] = {}
|
||||
for m in (user_msgs.data or []):
|
||||
# Top queries from user messages
|
||||
query_counts: Dict[str, int] = {}
|
||||
for m in chatbot_msgs:
|
||||
if m.get("role") == "user":
|
||||
content = (m.get("content") or "")[:100].strip()
|
||||
if content:
|
||||
query_counts[content] = query_counts.get(content, 0) + 1
|
||||
top_sorted = sorted(query_counts.items(), key=lambda x: -x[1])[:5]
|
||||
top_queries = [TopQuery(query=q, count=n) for q, n in top_sorted]
|
||||
top_queries = [TopQuery(query=q, count=n) for q, n in sorted(query_counts.items(), key=lambda x: -x[1])[:5]]
|
||||
|
||||
# Rating
|
||||
rating = chatbot.get("average_rating")
|
||||
if rating:
|
||||
all_ratings.append(rating)
|
||||
|
||||
# Feedback counts
|
||||
fb_result = supabase.table("message_feedback").select("feedback", count="exact") \
|
||||
.eq("chatbot_id", cid).execute()
|
||||
total_fb = fb_result.count or 0
|
||||
fb_pos = sum(1 for f in (fb_result.data or []) if f.get("feedback") == "positive")
|
||||
fb_neg = total_fb - fb_pos
|
||||
# Feedback
|
||||
chatbot_fb = fb_by_chatbot[cid]
|
||||
fb_pos = sum(1 for f in chatbot_fb if f.get("feedback") == "positive")
|
||||
fb_neg = len(chatbot_fb) - fb_pos
|
||||
|
||||
# Average messages per conversation
|
||||
avg_msgs = round(msg_count / conv_count, 1) if conv_count > 0 else 0.0
|
||||
|
||||
chatbot_analytics.append(ChatbotAnalyticsResponse(
|
||||
@@ -234,7 +265,7 @@ async def get_analytics_overview(user=Depends(get_current_user)):
|
||||
total_messages=msg_count,
|
||||
average_messages_per_conversation=avg_msgs,
|
||||
average_rating=rating,
|
||||
total_ratings=total_fb,
|
||||
total_ratings=len(chatbot_fb),
|
||||
conversations_today=today_count,
|
||||
conversations_this_week=week_count,
|
||||
conversations_this_month=month_count,
|
||||
|
||||
Reference in New Issue
Block a user