Ekaropolus 30b8efac2f
All checks were successful
continuous-integration/drone/push Build is passing
Stats on view of dashboard
2025-05-20 01:17:30 -06:00

351 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from django.views.generic.base import TemplateView
from django.contrib.auth.mixins import LoginRequiredMixin
from pxy_whatsapp.views import whatsapp_stats
import json
import logging
from django.contrib.auth.decorators import login_required
from pxy_whatsapp.models import WhatsAppBot
from django.shortcuts import render
from django.utils import timezone
from datetime import timedelta
import json
from pxy_whatsapp.models import Message, WhatsAppBot
logger = logging.getLogger(__name__)
class AppsView(LoginRequiredMixin, TemplateView):
pass
# ───── Existing ──────────────────────────────────────────────────────────────
# Calendar
apps_calendar_view = AppsView.as_view(template_name="pxy_dashboard/apps/apps-calendar.html")
# Chat
apps_chat_view = AppsView.as_view(template_name="pxy_dashboard/apps/apps-chat.html")
# Email
apps_email_inbox_view = AppsView.as_view(template_name="pxy_dashboard/apps/apps-email-inbox.html")
apps_email_read = AppsView.as_view(template_name="pxy_dashboard/apps/apps-email-read.html")
# Tasks
apps_tasks = AppsView.as_view(template_name="pxy_dashboard/apps/apps-tasks.html")
apps_tasks_details = AppsView.as_view(template_name="pxy_dashboard/apps/apps-tasks-details.html")
# Kanban
apps_kanban_board = AppsView.as_view(template_name="pxy_dashboard/apps/apps-kanban.html")
# File Manager
apps_file_manager = AppsView.as_view(template_name="pxy_dashboard/apps/apps-file-manager.html")
# ───── Waste Collection Intelligence ─────────────────────────────────────────
# Pre-Operation
#apps_zone_definition = AppsView.as_view(template_name="pxy_dashboard/apps/apps-zone-definition.html")
#apps_route_optimization = AppsView.as_view(template_name="pxy_dashboard/apps/apps-route-optimization.html")
apps_dispatch_plan = AppsView.as_view(template_name="pxy_dashboard/apps/apps-dispatch-plan.html")
# Operation Physical & Social Digital Twin
apps_urban_digital_twin = AppsView.as_view(template_name="pxy_dashboard/apps/apps-urban-digital-twin.html")
#apps_whatsapp_bot = AppsView.as_view(template_name="pxy_dashboard/apps/apps-whatsapp-bot.html")
apps_telegram_bot = AppsView.as_view(template_name="pxy_dashboard/apps/apps-telegram-bot.html")
apps_facebook_pages_bot = AppsView.as_view(template_name="pxy_dashboard/apps/apps-facebook-pages-bot.html")
apps_feedback_loop = AppsView.as_view(template_name="pxy_dashboard/apps/apps-feedback-loop.html")
# Post-Operation
apps_route_analytics = AppsView.as_view(template_name="pxy_dashboard/apps/apps-route-analytics.html")
apps_feedback_review = AppsView.as_view(template_name="pxy_dashboard/apps/apps-feedback-review.html")
apps_twin_refinement = AppsView.as_view(template_name="pxy_dashboard/apps/apps-twin-refinement.html")
# System Control
apps_sync_monitor = AppsView.as_view(template_name="pxy_dashboard/apps/apps-sync-monitor.html")
apps_logs_webhooks = AppsView.as_view(template_name="pxy_dashboard/apps/apps-logs-webhooks.html")
apps_logs_parsing = AppsView.as_view(template_name="pxy_dashboard/apps/apps-logs-parsing.html")
apps_logs_limits = AppsView.as_view(template_name="pxy_dashboard/apps/apps-logs-limits.html")
apps_config_api = AppsView.as_view(template_name="pxy_dashboard/apps/apps-config-api.html")
apps_config_map = AppsView.as_view(template_name="pxy_dashboard/apps/apps-config-map.html")
apps_config_collection = AppsView.as_view(template_name="pxy_dashboard/apps/apps-config-collection.html")
from django.shortcuts import render
from .models import GeoScenario
import pandas as pd
def zone_definition_view(request):
scenario = GeoScenario.objects.last()
chart_data = {}
viviendas_data = {}
city_options = []
scatter_series = {}
if scenario and scenario.csv_file:
df = pd.read_csv(scenario.csv_file.path)
df = df.fillna(0)
# Solo conservar zonas con generación total > 0
df = df[df["GEN_TOT"] > 0]
# Opciones de ciudad
city_options = sorted(df["N_URBANO"].dropna().unique())
selected_city = request.GET.get("city") or (city_options[0] if city_options else None)
# Barras por zona (residuos)
if selected_city:
city_df = df[df["N_URBANO"] == selected_city]
grouped = (
city_df.groupby("COD_ZONA")[["GEN_ORG", "GEN_INVA", "GEN_RESTO"]]
.sum()
.reset_index()
.sort_values(by="GEN_ORG", ascending=False)
)
chart_data = {
"zones": grouped["COD_ZONA"].astype(str).tolist(),
"gen_org": grouped["GEN_ORG"].round(2).tolist(),
"gen_inva": grouped["GEN_INVA"].round(2).tolist(),
"gen_resto": grouped["GEN_RESTO"].round(2).tolist(),
}
# Barras por zona (viviendas)
viviendas_grouped = (
city_df.groupby("COD_ZONA")["num_viviendas"]
.sum()
.reset_index()
.sort_values(by="num_viviendas", ascending=False)
)
viviendas_data = {
"zones": viviendas_grouped["COD_ZONA"].astype(str).tolist(),
"viviendas": viviendas_grouped["num_viviendas"].astype(int).tolist(),
}
# Dispersión por ciudad
scatter_series = {
"GEN_ORG": [],
"GEN_INVA": [],
"GEN_RESTO": [],
}
city_grouped = (
df.groupby("N_URBANO")[["num_viviendas", "GEN_ORG", "GEN_INVA", "GEN_RESTO"]]
.sum()
.reset_index()
)
for _, row in city_grouped.iterrows():
viviendas = float(row["num_viviendas"])
if viviendas == 0:
continue
city = row["N_URBANO"]
if row["GEN_ORG"] > 0:
scatter_series["GEN_ORG"].append({"x": viviendas, "y": float(row["GEN_ORG"]), "city": city})
if row["GEN_INVA"] > 0:
scatter_series["GEN_INVA"].append({"x": viviendas, "y": float(row["GEN_INVA"]), "city": city})
if row["GEN_RESTO"] > 0:
scatter_series["GEN_RESTO"].append({"x": viviendas, "y": float(row["GEN_RESTO"]), "city": city})
return render(request, "pxy_dashboard/apps/apps-zone-definition.html", {
"chart_data": chart_data,
"viviendas_data": viviendas_data,
"scatter_series": scatter_series,
"cities": city_options,
"selected_city": selected_city,
})
from .models import OptScenario
def route_optimization_view(request):
scenario = OptScenario.objects.last()
route_data = {}
subdivisions = []
selected_subdivision = None
scenario_name = scenario.name if scenario else "No scenario loaded"
if scenario and scenario.optimized_csv:
df = pd.read_csv(scenario.optimized_csv.path)
df = df.fillna(0)
# Filtrar por subdivisión
subdivisions = sorted(df["subdivision"].dropna().unique().tolist())
selected_subdivision = request.GET.get("subdivision") or (subdivisions[0] if subdivisions else None)
if selected_subdivision:
df = df[df["subdivision"] == selected_subdivision]
# Seleccionar solo filas de tipo 'end' para obtener acumulados
end_rows = df[df["type"] == "end"].copy()
route_data = {
"routes": end_rows["route_id"].astype(str).tolist(),
"distance_km": end_rows["distance_km"].round(2).tolist(),
"load_kg": end_rows["load_kg"].round(2).tolist(),
"cost_clp": end_rows["step_cost_clp"].round(2).tolist(),
}
return render(request, "pxy_dashboard/apps/apps-route-optimization.html", {
"route_data": route_data,
"subdivisions": subdivisions,
"selected_subdivision": selected_subdivision,
"scenario_name": scenario_name,
})
import json
import polyline
from django.shortcuts import render
from .models import OptScenario
def dispatch_plan_view(request):
scenario = OptScenario.objects.last()
geojson_by_subdivision = {}
routes_by_subdivision = {}
selected_subdivision = request.GET.get("subdivision")
selected_route = request.GET.get("route")
if scenario and scenario.dispatch_json:
with open(scenario.dispatch_json.path, encoding='utf-8') as f:
raw_data = json.load(f)
for subdiv, result in raw_data.items():
features = []
route_ids = []
for idx, route in enumerate(result.get("routes", [])):
route_id = str(idx + 1)
route_ids.append(route_id)
if selected_subdivision and subdiv != selected_subdivision:
continue
if selected_route and route_id != selected_route:
continue
geometry = route.get("geometry")
if geometry:
# decode returns [[lat, lon], …]
decoded = polyline.decode(geometry)
# swap to [lon, lat] for GeoJSON
coords = [[lng, lat] for lat, lng in decoded]
features.append({
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": coords
},
"properties": {
"type": "route",
"subdivision": subdiv,
"route_id": route_id
}
})
for step in route.get("steps", []):
# step["location"] is [lon, lat]
lat, lon = step["location"][1], step["location"][0]
step_type = step.get("type", "job")
step_id = step.get("id", "")
load = step.get("load", [0])[0]
distance = step.get("distance", 0)
arrival = step.get("arrival", 0)
popup = (
f"<b>{step_type.title()}</b><br>"
f"Job ID: {step_id}<br>"
f"Load: {load} kg<br>"
f"Distance: {distance / 1000:.2f} km<br>"
f"Arrival: {arrival / 60:.1f} min"
)
features.append({
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [lon, lat]
},
"properties": {
"popup": popup,
"step_type": step_type,
"subdivision": subdiv,
"route_id": route_id
}
})
geojson_by_subdivision[subdiv] = {
"type": "FeatureCollection",
"features": features
}
routes_by_subdivision[subdiv] = route_ids
return render(request, "pxy_dashboard/apps/apps-dispatch-plan.html", {
"geojson_by_subdivision": json.dumps(geojson_by_subdivision),
"routes_by_subdivision": json.dumps(routes_by_subdivision), # ← JSON-encodes the route lists
"subdivisions": list(geojson_by_subdivision.keys()),
"selected_subdivision": selected_subdivision,
"selected_route": selected_route,
})
from django.db.models import Avg
from django.utils import timezone
from datetime import timedelta
import json, logging
from pxy_whatsapp.models import Conversation, Message
logger = logging.getLogger(__name__)
@login_required
def apps_whatsapp_bot(request):
# — 1) Calcular métricas directamente —
try:
total_conversations = Conversation.objects.count()
messages_in = Message.objects.filter(direction="in").count()
messages_out = Message.objects.filter(direction="out").count()
# Promedio de tiempo de respuesta (solo out con tiempo)
avg_rt = Message.objects.filter(
direction="out", response_time_ms__isnull=False
).aggregate(Avg("response_time_ms"))["response_time_ms__avg"] or 0
stats = {
"total_conversations": total_conversations,
"messages_in": messages_in,
"messages_out": messages_out,
"avg_response_time": int(avg_rt),
}
except Exception as e:
logger.error(f"Error calculando métricas de WhatsApp: {e}")
stats = {"total_conversations": 0, "messages_in": 0, "messages_out": 0, "avg_response_time": 0}
# — 2) Cargar información de los bots —
bots_info = []
try:
for bot in WhatsAppBot.objects.all():
bots_info.append({
"name": bot.name,
"phone_number_id": bot.phone_number_id,
"is_active": bot.is_active,
"assistant": bot.assistant.name,
})
except Exception as e:
logger.error(f"Error cargando bots de WhatsApp: {e}")
# — 3) Prepare weekly data for scatter —
today = timezone.now().date()
start_date = today - timedelta(days=6)
weekly_data = {}
for bot in WhatsAppBot.objects.all():
points = []
for i in range(7):
day = start_date + timedelta(days=i)
count = Message.objects.filter(
conversation__bot=bot,
timestamp__date=day
).count()
points.append({"x": day.strftime("%Y-%m-%d"), "y": count})
weekly_data[bot.name] = points
# 4) Render con todo el contexto
context = {
"stats": stats,
"bots_info": bots_info,
"weekly_data_json": json.dumps(weekly_data),
}
return render(request, "pxy_dashboard/apps/apps-whatsapp-bot.html", context)