mirror of
https://github.com/kjanat/livegraphs-django.git
synced 2026-01-16 11:32:13 +01:00
Initial commit
This commit is contained in:
1
dashboard_project/dashboard/__init__.py
Normal file
1
dashboard_project/dashboard/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
# This file is intentionally left empty to mark the directory as a Python package
|
||||
65
dashboard_project/dashboard/admin.py
Normal file
65
dashboard_project/dashboard/admin.py
Normal file
@ -0,0 +1,65 @@
|
||||
# dashboard/admin.py
|
||||
|
||||
from django.contrib import admin
|
||||
|
||||
from .models import ChatSession, Dashboard, DataSource
|
||||
|
||||
|
||||
class DataSourceAdmin(admin.ModelAdmin):
|
||||
list_display = ("name", "company", "uploaded_at", "get_session_count")
|
||||
list_filter = ("company", "uploaded_at")
|
||||
search_fields = ("name", "description", "company__name")
|
||||
ordering = ("-uploaded_at",)
|
||||
|
||||
def get_session_count(self, obj):
|
||||
return obj.chat_sessions.count()
|
||||
|
||||
get_session_count.short_description = "Sessions"
|
||||
|
||||
|
||||
class ChatSessionAdmin(admin.ModelAdmin):
|
||||
list_display = (
|
||||
"session_id",
|
||||
"get_company",
|
||||
"start_time",
|
||||
"end_time",
|
||||
"country",
|
||||
"language",
|
||||
"sentiment",
|
||||
)
|
||||
list_filter = (
|
||||
"data_source__company",
|
||||
"start_time",
|
||||
"country",
|
||||
"language",
|
||||
"sentiment",
|
||||
"escalated",
|
||||
"forwarded_hr",
|
||||
)
|
||||
search_fields = (
|
||||
"session_id",
|
||||
"country",
|
||||
"language",
|
||||
"initial_msg",
|
||||
"full_transcript",
|
||||
)
|
||||
ordering = ("-start_time",)
|
||||
|
||||
def get_company(self, obj):
|
||||
return obj.data_source.company.name
|
||||
|
||||
get_company.short_description = "Company"
|
||||
get_company.admin_order_field = "data_source__company__name"
|
||||
|
||||
|
||||
class DashboardAdmin(admin.ModelAdmin):
|
||||
list_display = ("name", "company", "created_at", "updated_at")
|
||||
list_filter = ("company", "created_at")
|
||||
search_fields = ("name", "description", "company__name")
|
||||
filter_horizontal = ("data_sources",)
|
||||
ordering = ("-updated_at",)
|
||||
|
||||
|
||||
admin.site.register(DataSource, DataSourceAdmin)
|
||||
admin.site.register(ChatSession, ChatSessionAdmin)
|
||||
admin.site.register(Dashboard, DashboardAdmin)
|
||||
8
dashboard_project/dashboard/apps.py
Normal file
8
dashboard_project/dashboard/apps.py
Normal file
@ -0,0 +1,8 @@
|
||||
# dashboard/apps.py
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class DashboardConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "dashboard"
|
||||
49
dashboard_project/dashboard/forms.py
Normal file
49
dashboard_project/dashboard/forms.py
Normal file
@ -0,0 +1,49 @@
|
||||
# dashboard/forms.py
|
||||
|
||||
from django import forms
|
||||
|
||||
from .models import Dashboard, DataSource
|
||||
|
||||
|
||||
class DataSourceUploadForm(forms.ModelForm):
|
||||
"""Form for uploading CSV files"""
|
||||
|
||||
class Meta:
|
||||
model = DataSource
|
||||
fields = ["name", "description", "file"]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.company = kwargs.pop("company", None)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def save(self, commit=True):
|
||||
instance = super().save(commit=False)
|
||||
if self.company:
|
||||
instance.company = self.company
|
||||
if commit:
|
||||
instance.save()
|
||||
return instance
|
||||
|
||||
|
||||
class DashboardForm(forms.ModelForm):
|
||||
"""Form for creating and editing dashboards"""
|
||||
|
||||
class Meta:
|
||||
model = Dashboard
|
||||
fields = ["name", "description", "data_sources"]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.company = kwargs.pop("company", None)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
if self.company:
|
||||
self.fields["data_sources"].queryset = DataSource.objects.filter(company=self.company)
|
||||
|
||||
def save(self, commit=True):
|
||||
instance = super().save(commit=False)
|
||||
if self.company:
|
||||
instance.company = self.company
|
||||
if commit:
|
||||
instance.save()
|
||||
self.save_m2m()
|
||||
return instance
|
||||
2
dashboard_project/dashboard/management/__init__.py
Normal file
2
dashboard_project/dashboard/management/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
# dashboard/management/__init__.py
|
||||
# This file is intentionally left empty to mark the directory as a Python package
|
||||
@ -0,0 +1,2 @@
|
||||
# dashboard/management/commands/__init__.py
|
||||
# This file is intentionally left empty to mark the directory as a Python package
|
||||
@ -0,0 +1,277 @@
|
||||
# dashboard/management/commands/create_sample_data.py
|
||||
|
||||
import csv
|
||||
import io
|
||||
import random
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from accounts.models import Company
|
||||
from dashboard.models import ChatSession, Dashboard, DataSource
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core.files.base import ContentFile
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.utils import timezone
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Create sample data for testing"
|
||||
|
||||
def handle(self, *args, **kwargs):
|
||||
self.stdout.write("Creating sample data...")
|
||||
|
||||
# Create admin user if it doesn't exist
|
||||
if not User.objects.filter(username="admin").exists():
|
||||
admin_user = User.objects.create_superuser(username="admin", email="admin@example.com", password="admin123")
|
||||
self.stdout.write(self.style.SUCCESS(f"Created admin user: {admin_user.username}"))
|
||||
else:
|
||||
admin_user = User.objects.get(username="admin")
|
||||
self.stdout.write(f"Admin user already exists: {admin_user.username}")
|
||||
|
||||
# Create companies
|
||||
companies = []
|
||||
company_names = ["Acme Inc.", "TechCorp", "GlobalServices"]
|
||||
|
||||
for name in company_names:
|
||||
company, created = Company.objects.get_or_create(
|
||||
name=name, defaults={"description": f"Sample company: {name}"}
|
||||
)
|
||||
companies.append(company)
|
||||
|
||||
if created:
|
||||
self.stdout.write(self.style.SUCCESS(f"Created company: {company.name}"))
|
||||
else:
|
||||
self.stdout.write(f"Company already exists: {company.name}")
|
||||
|
||||
# Create users for each company
|
||||
for i, company in enumerate(companies):
|
||||
# Company admin
|
||||
username = f"admin_{company.name.lower().replace(' ', '_')}"
|
||||
if not User.objects.filter(username=username).exists():
|
||||
user = User.objects.create_user(
|
||||
username=username,
|
||||
email=f"{username}@example.com",
|
||||
password="password123",
|
||||
company=company,
|
||||
is_company_admin=True,
|
||||
)
|
||||
self.stdout.write(self.style.SUCCESS(f"Created company admin: {user.username}"))
|
||||
|
||||
# Regular users
|
||||
for j in range(2):
|
||||
username = f"user_{company.name.lower().replace(' ', '_')}_{j + 1}"
|
||||
if not User.objects.filter(username=username).exists():
|
||||
user = User.objects.create_user(
|
||||
username=username,
|
||||
email=f"{username}@example.com",
|
||||
password="password123",
|
||||
company=company,
|
||||
)
|
||||
self.stdout.write(self.style.SUCCESS(f"Created user: {user.username}"))
|
||||
|
||||
# Create sample data for each company
|
||||
for company in companies:
|
||||
self._create_sample_data_for_company(company)
|
||||
|
||||
self.stdout.write(self.style.SUCCESS("Sample data created successfully!"))
|
||||
|
||||
def _create_sample_data_for_company(self, company):
|
||||
# Create sample CSV data
|
||||
csv_data = self._generate_sample_csv_data(company.name)
|
||||
|
||||
# Create data source
|
||||
data_source_name = f"{company.name} Chat Data"
|
||||
try:
|
||||
data_source = DataSource.objects.get(name=data_source_name, company=company)
|
||||
self.stdout.write(f"Data source already exists: {data_source.name}")
|
||||
except DataSource.DoesNotExist:
|
||||
# Create file from CSV data
|
||||
csv_file = ContentFile(csv_data.encode("utf-8"))
|
||||
data_source = DataSource.objects.create(
|
||||
name=data_source_name,
|
||||
description=f"Sample chat data for {company.name}",
|
||||
company=company,
|
||||
)
|
||||
data_source.file.save(f"{company.name.lower().replace(' ', '_')}_chat_data.csv", csv_file)
|
||||
self.stdout.write(self.style.SUCCESS(f"Created data source: {data_source.name}"))
|
||||
|
||||
# Parse CSV data and create chat sessions
|
||||
reader = csv.DictReader(io.StringIO(csv_data))
|
||||
for row in reader:
|
||||
# Convert datetime strings to datetime objects
|
||||
start_time = datetime.strptime(row["start_time"], "%Y-%m-%d %H:%M:%S")
|
||||
end_time = datetime.strptime(row["end_time"], "%Y-%m-%d %H:%M:%S")
|
||||
|
||||
# Convert boolean strings to actual booleans
|
||||
escalated = row["escalated"].lower() in ["true", "yes", "1", "t", "y"]
|
||||
forwarded_hr = row["forwarded_hr"].lower() in [
|
||||
"true",
|
||||
"yes",
|
||||
"1",
|
||||
"t",
|
||||
"y",
|
||||
]
|
||||
|
||||
# Create chat session
|
||||
ChatSession.objects.create(
|
||||
data_source=data_source,
|
||||
session_id=row["session_id"],
|
||||
start_time=timezone.make_aware(start_time),
|
||||
end_time=timezone.make_aware(end_time),
|
||||
ip_address=row["ip_address"],
|
||||
country=row["country"],
|
||||
language=row["language"],
|
||||
messages_sent=int(row["messages_sent"]),
|
||||
sentiment=row["sentiment"],
|
||||
escalated=escalated,
|
||||
forwarded_hr=forwarded_hr,
|
||||
full_transcript=row["full_transcript"],
|
||||
avg_response_time=float(row["avg_response_time"]),
|
||||
tokens=int(row["tokens"]),
|
||||
tokens_eur=float(row["tokens_eur"]),
|
||||
category=row["category"],
|
||||
initial_msg=row["initial_msg"],
|
||||
user_rating=row["user_rating"],
|
||||
)
|
||||
|
||||
self.stdout.write(self.style.SUCCESS(f"Created {reader.line_num} chat sessions"))
|
||||
|
||||
# Create default dashboard
|
||||
dashboard_name = f"{company.name} Dashboard"
|
||||
try:
|
||||
dashboard = Dashboard.objects.get(name=dashboard_name, company=company)
|
||||
self.stdout.write(f"Dashboard already exists: {dashboard.name}")
|
||||
except Dashboard.DoesNotExist:
|
||||
dashboard = Dashboard.objects.create(
|
||||
name=dashboard_name,
|
||||
description=f"Default dashboard for {company.name}",
|
||||
company=company,
|
||||
)
|
||||
dashboard.data_sources.add(data_source)
|
||||
self.stdout.write(self.style.SUCCESS(f"Created dashboard: {dashboard.name}"))
|
||||
|
||||
def _generate_sample_csv_data(self, company_name):
|
||||
"""Generate sample CSV data for a company"""
|
||||
rows = []
|
||||
headers = [
|
||||
"session_id",
|
||||
"start_time",
|
||||
"end_time",
|
||||
"ip_address",
|
||||
"country",
|
||||
"language",
|
||||
"messages_sent",
|
||||
"sentiment",
|
||||
"escalated",
|
||||
"forwarded_hr",
|
||||
"full_transcript",
|
||||
"avg_response_time",
|
||||
"tokens",
|
||||
"tokens_eur",
|
||||
"category",
|
||||
"initial_msg",
|
||||
"user_rating",
|
||||
]
|
||||
|
||||
# Sample data for generating random values
|
||||
countries = [
|
||||
"USA",
|
||||
"UK",
|
||||
"Germany",
|
||||
"France",
|
||||
"Spain",
|
||||
"Italy",
|
||||
"Japan",
|
||||
"Australia",
|
||||
"Canada",
|
||||
"Brazil",
|
||||
]
|
||||
languages = ["English", "Spanish", "German", "French", "Japanese", "Portuguese"]
|
||||
sentiments = [
|
||||
"Positive",
|
||||
"Negative",
|
||||
"Neutral",
|
||||
"Very Positive",
|
||||
"Very Negative",
|
||||
]
|
||||
categories = ["Support", "Sales", "Technical", "Billing", "General"]
|
||||
ratings = ["Excellent", "Good", "Average", "Poor", "Terrible", ""]
|
||||
|
||||
# Generate rows
|
||||
num_rows = random.randint(50, 100)
|
||||
|
||||
for i in range(num_rows):
|
||||
# Generate random dates in the last 30 days
|
||||
end_date = datetime.now() - timedelta(days=random.randint(0, 30))
|
||||
start_date = end_date - timedelta(minutes=random.randint(5, 60))
|
||||
|
||||
# Generate random IP address
|
||||
ip = ".".join(str(random.randint(0, 255)) for _ in range(4))
|
||||
|
||||
# Random country and language
|
||||
country = random.choice(countries)
|
||||
language = random.choice(languages)
|
||||
|
||||
# Random message count
|
||||
messages_sent = random.randint(3, 20)
|
||||
|
||||
# Random sentiment
|
||||
sentiment = random.choice(sentiments)
|
||||
|
||||
# Random escalation and forwarding
|
||||
escalated = random.random() < 0.2 # 20% chance of escalation
|
||||
forwarded_hr = random.random() < 0.1 # 10% chance of forwarding to HR
|
||||
|
||||
# Generate a sample transcript
|
||||
transcript = (
|
||||
"User: Hello, I need help with my account.\n"
|
||||
"Agent: Hello! I'd be happy to help. What seems to be the issue?\n"
|
||||
"User: I can't log in to my account.\n"
|
||||
"Agent: I understand. Let me help you reset your password."
|
||||
)
|
||||
|
||||
# Random response time, tokens, and cost
|
||||
avg_response_time = round(random.uniform(0.5, 10.0), 2)
|
||||
tokens = random.randint(100, 2000)
|
||||
tokens_eur = round(tokens * 0.00002, 4) # Example rate: €0.00002 per token
|
||||
|
||||
# Random category
|
||||
category = random.choice(categories)
|
||||
|
||||
# Initial message
|
||||
initial_msg = "Hello, I need help with my account."
|
||||
|
||||
# Random rating
|
||||
user_rating = random.choice(ratings)
|
||||
|
||||
# Create row
|
||||
row = {
|
||||
"session_id": f"{company_name.lower().replace(' ', '_')}_{i + 1}",
|
||||
"start_time": start_date.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"end_time": end_date.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"ip_address": ip,
|
||||
"country": country,
|
||||
"language": language,
|
||||
"messages_sent": str(messages_sent),
|
||||
"sentiment": sentiment,
|
||||
"escalated": str(escalated),
|
||||
"forwarded_hr": str(forwarded_hr),
|
||||
"full_transcript": transcript,
|
||||
"avg_response_time": str(avg_response_time),
|
||||
"tokens": str(tokens),
|
||||
"tokens_eur": str(tokens_eur),
|
||||
"category": category,
|
||||
"initial_msg": initial_msg,
|
||||
"user_rating": user_rating,
|
||||
}
|
||||
|
||||
rows.append(row)
|
||||
|
||||
# Write to CSV string
|
||||
output = io.StringIO()
|
||||
writer = csv.DictWriter(output, fieldnames=headers)
|
||||
writer.writeheader()
|
||||
writer.writerows(rows)
|
||||
|
||||
return output.getvalue()
|
||||
1
dashboard_project/dashboard/migrations/__init__.py
Normal file
1
dashboard_project/dashboard/migrations/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
# This file is intentionally left empty to mark the directory as a Python package
|
||||
57
dashboard_project/dashboard/models.py
Normal file
57
dashboard_project/dashboard/models.py
Normal file
@ -0,0 +1,57 @@
|
||||
# dashboard/models.py
|
||||
|
||||
from accounts.models import Company
|
||||
from django.db import models
|
||||
|
||||
|
||||
class DataSource(models.Model):
|
||||
"""Model for uploaded data sources (CSV files)"""
|
||||
|
||||
name = models.CharField(max_length=255)
|
||||
description = models.TextField(blank=True)
|
||||
file = models.FileField(upload_to="data_sources/")
|
||||
uploaded_at = models.DateTimeField(auto_now_add=True)
|
||||
company = models.ForeignKey(Company, on_delete=models.CASCADE, related_name="data_sources")
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class ChatSession(models.Model):
|
||||
"""Model to store parsed chat session data from CSV"""
|
||||
|
||||
data_source = models.ForeignKey(DataSource, on_delete=models.CASCADE, related_name="chat_sessions")
|
||||
session_id = models.CharField(max_length=255)
|
||||
start_time = models.DateTimeField(null=True, blank=True)
|
||||
end_time = models.DateTimeField(null=True, blank=True)
|
||||
ip_address = models.GenericIPAddressField(null=True, blank=True)
|
||||
country = models.CharField(max_length=100, blank=True)
|
||||
language = models.CharField(max_length=50, blank=True)
|
||||
messages_sent = models.IntegerField(default=0)
|
||||
sentiment = models.CharField(max_length=50, blank=True)
|
||||
escalated = models.BooleanField(default=False)
|
||||
forwarded_hr = models.BooleanField(default=False)
|
||||
full_transcript = models.TextField(blank=True)
|
||||
avg_response_time = models.FloatField(null=True, blank=True)
|
||||
tokens = models.IntegerField(default=0)
|
||||
tokens_eur = models.FloatField(null=True, blank=True)
|
||||
category = models.CharField(max_length=100, blank=True)
|
||||
initial_msg = models.TextField(blank=True)
|
||||
user_rating = models.CharField(max_length=50, blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"Session {self.session_id}"
|
||||
|
||||
|
||||
class Dashboard(models.Model):
|
||||
"""Model for custom dashboards that can be created by users"""
|
||||
|
||||
name = models.CharField(max_length=255)
|
||||
description = models.TextField(blank=True)
|
||||
company = models.ForeignKey(Company, on_delete=models.CASCADE, related_name="dashboards")
|
||||
data_sources = models.ManyToManyField(DataSource, related_name="dashboards")
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
2
dashboard_project/dashboard/templatetags/__init__.py
Normal file
2
dashboard_project/dashboard/templatetags/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
# dashboard/templatetags/__init__.py
|
||||
# This file is intentionally left empty to mark the directory as a Python package
|
||||
56
dashboard_project/dashboard/templatetags/dashboard_extras.py
Normal file
56
dashboard_project/dashboard/templatetags/dashboard_extras.py
Normal file
@ -0,0 +1,56 @@
|
||||
# dashboard/templatetags/dashboard_extras.py
|
||||
|
||||
from django import template
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.filter
|
||||
def split(value, delimiter):
|
||||
"""Split a string into a list based on the delimiter"""
|
||||
return value.split(delimiter)
|
||||
|
||||
|
||||
@register.filter
|
||||
def get_item(dictionary, key):
|
||||
"""Get an item from a dictionary using the key"""
|
||||
return dictionary.get(key)
|
||||
|
||||
|
||||
@register.filter
|
||||
def truncate_middle(value, max_length):
|
||||
"""Truncate a string in the middle, keeping the beginning and end"""
|
||||
if len(value) <= max_length:
|
||||
return value
|
||||
|
||||
# Calculate how many characters to keep at the start and end
|
||||
half_max = max_length // 2
|
||||
start = value[:half_max]
|
||||
end = value[-half_max:]
|
||||
|
||||
return f"{start}...{end}"
|
||||
|
||||
|
||||
@register.filter
|
||||
def format_duration(seconds):
|
||||
"""Format seconds into a human-readable duration"""
|
||||
if not seconds:
|
||||
return "0s"
|
||||
|
||||
minutes, seconds = divmod(int(seconds), 60)
|
||||
hours, minutes = divmod(minutes, 60)
|
||||
|
||||
if hours > 0:
|
||||
return f"{hours}h {minutes}m {seconds}s"
|
||||
elif minutes > 0:
|
||||
return f"{minutes}m {seconds}s"
|
||||
else:
|
||||
return f"{seconds}s"
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def url_replace(request, field, value):
|
||||
"""Replace a GET parameter in the current URL"""
|
||||
dict_ = request.GET.copy()
|
||||
dict_[field] = value
|
||||
return dict_.urlencode()
|
||||
0
dashboard_project/dashboard/tests.py
Normal file
0
dashboard_project/dashboard/tests.py
Normal file
43
dashboard_project/dashboard/urls.py
Normal file
43
dashboard_project/dashboard/urls.py
Normal file
@ -0,0 +1,43 @@
|
||||
# dashboard/urls.py
|
||||
|
||||
from django.urls import path
|
||||
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path("", views.dashboard_view, name="dashboard"),
|
||||
path("upload/", views.upload_data_view, name="upload_data"),
|
||||
path(
|
||||
"data-source/<int:data_source_id>/",
|
||||
views.data_source_detail_view,
|
||||
name="data_source_detail",
|
||||
),
|
||||
path(
|
||||
"chat-session/<str:session_id>/",
|
||||
views.chat_session_detail_view,
|
||||
name="chat_session_detail",
|
||||
),
|
||||
path("dashboard/create/", views.create_dashboard_view, name="create_dashboard"),
|
||||
path(
|
||||
"dashboard/<int:dashboard_id>/edit/",
|
||||
views.edit_dashboard_view,
|
||||
name="edit_dashboard",
|
||||
),
|
||||
path(
|
||||
"dashboard/<int:dashboard_id>/delete/",
|
||||
views.delete_dashboard_view,
|
||||
name="delete_dashboard",
|
||||
),
|
||||
path(
|
||||
"data-source/<int:data_source_id>/delete/",
|
||||
views.delete_data_source_view,
|
||||
name="delete_data_source",
|
||||
),
|
||||
path(
|
||||
"api/dashboard/<int:dashboard_id>/data/",
|
||||
views.dashboard_data_api,
|
||||
name="dashboard_data_api",
|
||||
),
|
||||
path("search/", views.search_chat_sessions, name="search_chat_sessions"),
|
||||
path("data-view/", views.data_view, name="data_view"),
|
||||
]
|
||||
161
dashboard_project/dashboard/utils.py
Normal file
161
dashboard_project/dashboard/utils.py
Normal file
@ -0,0 +1,161 @@
|
||||
# dashboard/utils.py
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from django.db import models
|
||||
from django.utils.timezone import make_aware
|
||||
|
||||
from .models import ChatSession
|
||||
|
||||
|
||||
def process_csv_file(data_source):
|
||||
"""
|
||||
Process the uploaded CSV file and create ChatSession objects
|
||||
|
||||
Args:
|
||||
data_source: DataSource model instance containing the CSV file
|
||||
"""
|
||||
try:
|
||||
# Read the CSV file
|
||||
file_path = data_source.file.path
|
||||
df = pd.read_csv(file_path)
|
||||
|
||||
# Process each row and create ChatSession objects
|
||||
for _, row in df.iterrows():
|
||||
# Handle datetime fields
|
||||
start_time = None
|
||||
end_time = None
|
||||
|
||||
if "start_time" in row and pd.notna(row["start_time"]):
|
||||
try:
|
||||
start_time = make_aware(pd.to_datetime(row["start_time"]))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if "end_time" in row and pd.notna(row["end_time"]):
|
||||
try:
|
||||
end_time = make_aware(pd.to_datetime(row["end_time"]))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Convert boolean fields
|
||||
escalated = str(row.get("escalated", "")).lower() in [
|
||||
"true",
|
||||
"yes",
|
||||
"1",
|
||||
"t",
|
||||
"y",
|
||||
]
|
||||
forwarded_hr = str(row.get("forwarded_hr", "")).lower() in [
|
||||
"true",
|
||||
"yes",
|
||||
"1",
|
||||
"t",
|
||||
"y",
|
||||
]
|
||||
|
||||
# Create ChatSession object
|
||||
session = ChatSession(
|
||||
data_source=data_source,
|
||||
session_id=str(row.get("session_id", "")),
|
||||
start_time=start_time,
|
||||
end_time=end_time,
|
||||
ip_address=row.get("ip_address") if pd.notna(row.get("ip_address", np.nan)) else None,
|
||||
country=str(row.get("country", "")),
|
||||
language=str(row.get("language", "")),
|
||||
messages_sent=int(row.get("messages_sent", 0)) if pd.notna(row.get("messages_sent", np.nan)) else 0,
|
||||
sentiment=str(row.get("sentiment", "")),
|
||||
escalated=escalated,
|
||||
forwarded_hr=forwarded_hr,
|
||||
full_transcript=str(row.get("full_transcript", "")),
|
||||
avg_response_time=float(row.get("avg_response_time", 0))
|
||||
if pd.notna(row.get("avg_response_time", np.nan))
|
||||
else None,
|
||||
tokens=int(row.get("tokens", 0)) if pd.notna(row.get("tokens", np.nan)) else 0,
|
||||
tokens_eur=float(row.get("tokens_eur", 0)) if pd.notna(row.get("tokens_eur", np.nan)) else None,
|
||||
category=str(row.get("category", "")),
|
||||
initial_msg=str(row.get("initial_msg", "")),
|
||||
user_rating=str(row.get("user_rating", "")),
|
||||
)
|
||||
session.save()
|
||||
|
||||
return True, f"Successfully processed {len(df)} records."
|
||||
|
||||
except Exception as e:
|
||||
return False, f"Error processing CSV file: {str(e)}"
|
||||
|
||||
|
||||
def generate_dashboard_data(data_sources):
|
||||
"""
|
||||
Generate aggregated data for dashboard visualization
|
||||
|
||||
Args:
|
||||
data_sources: QuerySet of DataSource objects
|
||||
|
||||
Returns:
|
||||
dict: Dictionary containing aggregated data for various charts
|
||||
"""
|
||||
# Get all chat sessions for the selected data sources
|
||||
chat_sessions = ChatSession.objects.filter(data_source__in=data_sources)
|
||||
|
||||
if not chat_sessions.exists():
|
||||
return {
|
||||
"total_sessions": 0,
|
||||
"avg_response_time": 0,
|
||||
"total_tokens": 0,
|
||||
"total_cost": 0,
|
||||
"sentiment_data": [],
|
||||
"country_data": [],
|
||||
"category_data": [],
|
||||
"time_series_data": [],
|
||||
}
|
||||
|
||||
# Basic statistics
|
||||
total_sessions = chat_sessions.count()
|
||||
avg_response_time = (
|
||||
chat_sessions.filter(avg_response_time__isnull=False).aggregate(avg=models.Avg("avg_response_time"))["avg"] or 0
|
||||
)
|
||||
total_tokens = chat_sessions.aggregate(sum=models.Sum("tokens"))["sum"] or 0
|
||||
total_cost = chat_sessions.filter(tokens_eur__isnull=False).aggregate(sum=models.Sum("tokens_eur"))["sum"] or 0
|
||||
|
||||
# Sentiment distribution
|
||||
sentiment_data = (
|
||||
chat_sessions.exclude(sentiment="").values("sentiment").annotate(count=models.Count("id")).order_by("-count")
|
||||
)
|
||||
|
||||
# Country distribution
|
||||
country_data = (
|
||||
chat_sessions.exclude(country="")
|
||||
.values("country")
|
||||
.annotate(count=models.Count("id"))
|
||||
.order_by("-count")[:10] # Top 10 countries
|
||||
)
|
||||
|
||||
# Category distribution
|
||||
category_data = (
|
||||
chat_sessions.exclude(category="").values("category").annotate(count=models.Count("id")).order_by("-count")
|
||||
)
|
||||
|
||||
# Time series data (sessions per day)
|
||||
time_series_query = (
|
||||
chat_sessions.filter(start_time__isnull=False)
|
||||
.annotate(date=models.functions.TruncDate("start_time"))
|
||||
.values("date")
|
||||
.annotate(count=models.Count("id"))
|
||||
.order_by("date")
|
||||
)
|
||||
|
||||
time_series_data = [
|
||||
{"date": entry["date"].strftime("%Y-%m-%d"), "count": entry["count"]} for entry in time_series_query
|
||||
]
|
||||
|
||||
return {
|
||||
"total_sessions": total_sessions,
|
||||
"avg_response_time": round(avg_response_time, 2),
|
||||
"total_tokens": total_tokens,
|
||||
"total_cost": round(total_cost, 2),
|
||||
"sentiment_data": list(sentiment_data),
|
||||
"country_data": list(country_data),
|
||||
"category_data": list(category_data),
|
||||
"time_series_data": time_series_data,
|
||||
}
|
||||
452
dashboard_project/dashboard/views.py
Normal file
452
dashboard_project/dashboard/views.py
Normal file
@ -0,0 +1,452 @@
|
||||
# dashboard/views.py
|
||||
|
||||
import json
|
||||
from datetime import timedelta
|
||||
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.paginator import Paginator
|
||||
from django.db.models import Avg, Q
|
||||
from django.http import JsonResponse
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.utils import timezone
|
||||
|
||||
from .forms import DashboardForm, DataSourceUploadForm
|
||||
from .models import ChatSession, Dashboard, DataSource
|
||||
from .utils import generate_dashboard_data, process_csv_file
|
||||
|
||||
|
||||
@login_required
|
||||
def dashboard_view(request):
|
||||
"""Main dashboard view"""
|
||||
user = request.user
|
||||
company = user.company
|
||||
|
||||
if not company:
|
||||
messages.warning(
|
||||
request,
|
||||
"You are not associated with any company. Please contact an administrator.",
|
||||
)
|
||||
return render(request, "dashboard/no_company.html")
|
||||
|
||||
# Get the user's dashboards or create a default one
|
||||
dashboards = Dashboard.objects.filter(company=company)
|
||||
|
||||
if not dashboards.exists():
|
||||
# Create a default dashboard if none exists
|
||||
data_sources = DataSource.objects.filter(company=company)
|
||||
if data_sources.exists():
|
||||
default_dashboard = Dashboard.objects.create(
|
||||
name="Default Dashboard",
|
||||
description="Automatically created dashboard",
|
||||
company=company,
|
||||
)
|
||||
default_dashboard.data_sources.set(data_sources)
|
||||
dashboards = [default_dashboard]
|
||||
else:
|
||||
# No data sources available
|
||||
return redirect("upload_data")
|
||||
|
||||
# Use the first dashboard by default or the one specified in the request
|
||||
selected_dashboard_id = request.GET.get("dashboard_id")
|
||||
if selected_dashboard_id:
|
||||
selected_dashboard = get_object_or_404(Dashboard, id=selected_dashboard_id, company=company)
|
||||
else:
|
||||
selected_dashboard = dashboards.first()
|
||||
|
||||
# Generate dashboard data
|
||||
dashboard_data = generate_dashboard_data(selected_dashboard.data_sources.all())
|
||||
|
||||
# Convert dashboard data to JSON for use in JavaScript
|
||||
dashboard_data_json = json.dumps(
|
||||
{
|
||||
"sentiment_data": dashboard_data["sentiment_data"],
|
||||
"country_data": dashboard_data["country_data"],
|
||||
"category_data": dashboard_data["category_data"],
|
||||
"time_series_data": dashboard_data["time_series_data"],
|
||||
}
|
||||
)
|
||||
|
||||
context = {
|
||||
"dashboards": dashboards,
|
||||
"selected_dashboard": selected_dashboard,
|
||||
"dashboard_data": dashboard_data,
|
||||
"dashboard_data_json": dashboard_data_json,
|
||||
}
|
||||
|
||||
return render(request, "dashboard/dashboard.html", context)
|
||||
|
||||
|
||||
@login_required
|
||||
def upload_data_view(request):
|
||||
"""View for uploading CSV files"""
|
||||
user = request.user
|
||||
company = user.company
|
||||
|
||||
if not company:
|
||||
messages.warning(
|
||||
request,
|
||||
"You are not associated with any company. Please contact an administrator.",
|
||||
)
|
||||
return redirect("dashboard")
|
||||
|
||||
if request.method == "POST":
|
||||
form = DataSourceUploadForm(request.POST, request.FILES, company=company)
|
||||
if form.is_valid():
|
||||
data_source = form.save()
|
||||
|
||||
# Process the uploaded CSV file
|
||||
success, message = process_csv_file(data_source)
|
||||
|
||||
if success:
|
||||
messages.success(request, f"File uploaded successfully. {message}")
|
||||
|
||||
# Add the new data source to all existing dashboards
|
||||
dashboards = Dashboard.objects.filter(company=company)
|
||||
for dashboard in dashboards:
|
||||
dashboard.data_sources.add(data_source)
|
||||
|
||||
return redirect("dashboard")
|
||||
else:
|
||||
# If processing failed, delete the data source
|
||||
data_source.delete()
|
||||
messages.error(request, message)
|
||||
else:
|
||||
messages.error(request, "Form is invalid. Please correct the errors.")
|
||||
else:
|
||||
form = DataSourceUploadForm()
|
||||
|
||||
# List existing data sources
|
||||
data_sources = DataSource.objects.filter(company=company).order_by("-uploaded_at")
|
||||
|
||||
context = {
|
||||
"form": form,
|
||||
"data_sources": data_sources,
|
||||
}
|
||||
|
||||
return render(request, "dashboard/upload.html", context)
|
||||
|
||||
|
||||
@login_required
|
||||
def data_source_detail_view(request, data_source_id):
|
||||
"""View for viewing details of a data source"""
|
||||
user = request.user
|
||||
company = user.company
|
||||
|
||||
if not company:
|
||||
messages.warning(
|
||||
request,
|
||||
"You are not associated with any company. Please contact an administrator.",
|
||||
)
|
||||
return redirect("dashboard")
|
||||
|
||||
data_source = get_object_or_404(DataSource, id=data_source_id, company=company)
|
||||
|
||||
# Get all chat sessions for this data source
|
||||
chat_sessions = ChatSession.objects.filter(data_source=data_source).order_by("-start_time")
|
||||
|
||||
# Pagination
|
||||
paginator = Paginator(chat_sessions, 20) # Show 20 records per page
|
||||
page_number = request.GET.get("page")
|
||||
page_obj = paginator.get_page(page_number)
|
||||
|
||||
context = {
|
||||
"data_source": data_source,
|
||||
"page_obj": page_obj,
|
||||
}
|
||||
|
||||
return render(request, "dashboard/data_source_detail.html", context)
|
||||
|
||||
|
||||
@login_required
|
||||
def chat_session_detail_view(request, session_id):
|
||||
"""View for viewing details of a chat session"""
|
||||
user = request.user
|
||||
company = user.company
|
||||
|
||||
if not company:
|
||||
messages.warning(
|
||||
request,
|
||||
"You are not associated with any company. Please contact an administrator.",
|
||||
)
|
||||
return redirect("dashboard")
|
||||
|
||||
chat_session = get_object_or_404(ChatSession, session_id=session_id, data_source__company=company)
|
||||
|
||||
context = {
|
||||
"session": chat_session,
|
||||
}
|
||||
|
||||
return render(request, "dashboard/chat_session_detail.html", context)
|
||||
|
||||
|
||||
@login_required
|
||||
def create_dashboard_view(request):
|
||||
"""View for creating a custom dashboard"""
|
||||
user = request.user
|
||||
company = user.company
|
||||
|
||||
if not company:
|
||||
messages.warning(
|
||||
request,
|
||||
"You are not associated with any company. Please contact an administrator.",
|
||||
)
|
||||
return redirect("dashboard")
|
||||
|
||||
if request.method == "POST":
|
||||
form = DashboardForm(request.POST, company=company)
|
||||
if form.is_valid():
|
||||
dashboard = form.save()
|
||||
messages.success(request, f"Dashboard '{dashboard.name}' created successfully.")
|
||||
return redirect("dashboard")
|
||||
else:
|
||||
messages.error(request, "Failed to create dashboard. Please correct the errors.")
|
||||
else:
|
||||
form = DashboardForm(company=company)
|
||||
|
||||
context = {
|
||||
"form": form,
|
||||
"is_create": True,
|
||||
}
|
||||
|
||||
return render(request, "dashboard/dashboard_form.html", context)
|
||||
|
||||
|
||||
@login_required
|
||||
def edit_dashboard_view(request, dashboard_id):
|
||||
"""View for editing a dashboard"""
|
||||
user = request.user
|
||||
company = user.company
|
||||
|
||||
if not company:
|
||||
messages.warning(
|
||||
request,
|
||||
"You are not associated with any company. Please contact an administrator.",
|
||||
)
|
||||
return redirect("dashboard")
|
||||
|
||||
dashboard = get_object_or_404(Dashboard, id=dashboard_id, company=company)
|
||||
|
||||
if request.method == "POST":
|
||||
form = DashboardForm(request.POST, instance=dashboard, company=company)
|
||||
if form.is_valid():
|
||||
dashboard = form.save()
|
||||
messages.success(request, f"Dashboard '{dashboard.name}' updated successfully.")
|
||||
return redirect("dashboard")
|
||||
else:
|
||||
messages.error(request, "Failed to update dashboard. Please correct the errors.")
|
||||
else:
|
||||
form = DashboardForm(instance=dashboard, company=company)
|
||||
|
||||
context = {
|
||||
"form": form,
|
||||
"dashboard": dashboard,
|
||||
"is_create": False,
|
||||
}
|
||||
|
||||
return render(request, "dashboard/dashboard_form.html", context)
|
||||
|
||||
|
||||
@login_required
|
||||
def delete_dashboard_view(request, dashboard_id):
|
||||
"""View for deleting a dashboard"""
|
||||
user = request.user
|
||||
company = user.company
|
||||
|
||||
if not company:
|
||||
messages.warning(
|
||||
request,
|
||||
"You are not associated with any company. Please contact an administrator.",
|
||||
)
|
||||
return redirect("dashboard")
|
||||
|
||||
dashboard = get_object_or_404(Dashboard, id=dashboard_id, company=company)
|
||||
|
||||
if request.method == "POST":
|
||||
dashboard_name = dashboard.name
|
||||
dashboard.delete()
|
||||
messages.success(request, f"Dashboard '{dashboard_name}' deleted successfully.")
|
||||
return redirect("dashboard")
|
||||
|
||||
context = {
|
||||
"dashboard": dashboard,
|
||||
}
|
||||
|
||||
return render(request, "dashboard/dashboard_confirm_delete.html", context)
|
||||
|
||||
|
||||
@login_required
|
||||
def delete_data_source_view(request, data_source_id):
|
||||
"""View for deleting a data source"""
|
||||
user = request.user
|
||||
company = user.company
|
||||
|
||||
if not company:
|
||||
messages.warning(
|
||||
request,
|
||||
"You are not associated with any company. Please contact an administrator.",
|
||||
)
|
||||
return redirect("dashboard")
|
||||
|
||||
data_source = get_object_or_404(DataSource, id=data_source_id, company=company)
|
||||
|
||||
if request.method == "POST":
|
||||
data_source_name = data_source.name
|
||||
data_source.delete()
|
||||
messages.success(request, f"Data source '{data_source_name}' deleted successfully.")
|
||||
return redirect("upload_data")
|
||||
|
||||
context = {
|
||||
"data_source": data_source,
|
||||
}
|
||||
|
||||
return render(request, "dashboard/data_source_confirm_delete.html", context)
|
||||
|
||||
|
||||
# API views for dashboard data
|
||||
@login_required
|
||||
def dashboard_data_api(request, dashboard_id):
|
||||
"""API endpoint for dashboard data"""
|
||||
user = request.user
|
||||
company = user.company
|
||||
|
||||
if not company:
|
||||
return JsonResponse({"error": "User not associated with a company"}, status=403)
|
||||
|
||||
dashboard = get_object_or_404(Dashboard, id=dashboard_id, company=company)
|
||||
dashboard_data = generate_dashboard_data(dashboard.data_sources.all())
|
||||
|
||||
return JsonResponse(dashboard_data)
|
||||
|
||||
|
||||
@login_required
|
||||
def search_chat_sessions(request):
|
||||
"""View for searching chat sessions"""
|
||||
user = request.user
|
||||
company = user.company
|
||||
|
||||
if not company:
|
||||
messages.warning(
|
||||
request,
|
||||
"You are not associated with any company. Please contact an administrator.",
|
||||
)
|
||||
return redirect("dashboard")
|
||||
|
||||
query = request.GET.get("q", "")
|
||||
data_source_id = request.GET.get("data_source_id")
|
||||
|
||||
# Base queryset
|
||||
chat_sessions = ChatSession.objects.filter(data_source__company=company)
|
||||
|
||||
# Filter by data source if provided
|
||||
if data_source_id:
|
||||
chat_sessions = chat_sessions.filter(data_source_id=data_source_id)
|
||||
|
||||
# Apply search query if provided
|
||||
if query:
|
||||
chat_sessions = chat_sessions.filter(
|
||||
Q(session_id__icontains=query)
|
||||
| Q(country__icontains=query)
|
||||
| Q(language__icontains=query)
|
||||
| Q(sentiment__icontains=query)
|
||||
| Q(category__icontains=query)
|
||||
| Q(initial_msg__icontains=query)
|
||||
| Q(full_transcript__icontains=query)
|
||||
)
|
||||
|
||||
# Order by most recent first
|
||||
chat_sessions = chat_sessions.order_by("-start_time")
|
||||
|
||||
# Pagination
|
||||
paginator = Paginator(chat_sessions, 20) # Show 20 records per page
|
||||
page_number = request.GET.get("page")
|
||||
page_obj = paginator.get_page(page_number)
|
||||
|
||||
# Get data source for context if filtered by data source
|
||||
data_source = None
|
||||
if data_source_id:
|
||||
data_source = get_object_or_404(DataSource, id=data_source_id, company=company)
|
||||
|
||||
context = {
|
||||
"query": query,
|
||||
"page_obj": page_obj,
|
||||
"data_source": data_source,
|
||||
}
|
||||
|
||||
return render(request, "dashboard/search_results.html", context)
|
||||
|
||||
|
||||
@login_required
|
||||
def data_view(request):
|
||||
"""View for viewing all data with filtering options"""
|
||||
user = request.user
|
||||
company = user.company
|
||||
|
||||
if not company:
|
||||
messages.warning(
|
||||
request,
|
||||
"You are not associated with any company. Please contact an administrator.",
|
||||
)
|
||||
return redirect("dashboard")
|
||||
|
||||
# Get available data sources
|
||||
data_sources = DataSource.objects.filter(company=company)
|
||||
|
||||
# Get selected data source if any
|
||||
data_source_id = request.GET.get("data_source_id")
|
||||
selected_data_source = None
|
||||
if data_source_id:
|
||||
selected_data_source = get_object_or_404(DataSource, id=data_source_id, company=company)
|
||||
|
||||
# Base queryset
|
||||
chat_sessions = ChatSession.objects.filter(data_source__company=company)
|
||||
|
||||
# Apply data source filter if selected
|
||||
if selected_data_source:
|
||||
chat_sessions = chat_sessions.filter(data_source=selected_data_source)
|
||||
|
||||
# Apply view filter if any
|
||||
view = request.GET.get("view", "all")
|
||||
|
||||
if view == "recent":
|
||||
# Sessions from the last 7 days
|
||||
seven_days_ago = timezone.now() - timedelta(days=7)
|
||||
chat_sessions = chat_sessions.filter(start_time__gte=seven_days_ago)
|
||||
elif view == "positive":
|
||||
# Sessions with positive sentiment
|
||||
chat_sessions = chat_sessions.filter(Q(sentiment__icontains="positive"))
|
||||
elif view == "negative":
|
||||
# Sessions with negative sentiment
|
||||
chat_sessions = chat_sessions.filter(Q(sentiment__icontains="negative"))
|
||||
elif view == "escalated":
|
||||
# Escalated sessions
|
||||
chat_sessions = chat_sessions.filter(escalated=True)
|
||||
|
||||
# Order by most recent first
|
||||
chat_sessions = chat_sessions.order_by("-start_time")
|
||||
|
||||
# Calculate some statistics
|
||||
total_sessions = chat_sessions.count()
|
||||
avg_response_time = (
|
||||
chat_sessions.filter(avg_response_time__isnull=False).aggregate(avg=Avg("avg_response_time"))["avg"] or 0
|
||||
)
|
||||
avg_messages = chat_sessions.filter(messages_sent__gt=0).aggregate(avg=Avg("messages_sent"))["avg"] or 0
|
||||
escalated_count = chat_sessions.filter(escalated=True).count()
|
||||
escalation_rate = (escalated_count / total_sessions * 100) if total_sessions > 0 else 0
|
||||
|
||||
# Pagination
|
||||
paginator = Paginator(chat_sessions, 20) # Show 20 records per page
|
||||
page_number = request.GET.get("page")
|
||||
page_obj = paginator.get_page(page_number)
|
||||
|
||||
context = {
|
||||
"data_sources": data_sources,
|
||||
"selected_data_source": selected_data_source,
|
||||
"page_obj": page_obj,
|
||||
"view": view,
|
||||
"avg_response_time": avg_response_time,
|
||||
"avg_messages": avg_messages,
|
||||
"escalation_rate": escalation_rate,
|
||||
}
|
||||
|
||||
return render(request, "dashboard/data_view.html", context)
|
||||
Reference in New Issue
Block a user