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"{step_type.title()}
" f"Job ID: {step_id}
" f"Load: {load} kg
" f"Distance: {distance / 1000:.2f} km
" 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) from django.contrib.auth.decorators import login_required from django.shortcuts import render from django.db.models import Avg from django.utils import timezone from datetime import timedelta import json from pxy_bots.models import TelegramBot, TelegramConversation, TelegramMessage @login_required def apps_telegram_bot(request): # — Métricas globales — total_conversations = TelegramConversation.objects.count() messages_in = TelegramMessage.objects.filter(direction='in').count() messages_out = TelegramMessage.objects.filter(direction='out').count() avg_rt = ( TelegramMessage.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), } # — Información de cada bot — bots_info = [] for bot in TelegramBot.objects.all(): bots_info.append({ 'name': bot.name, 'username': getattr(bot, 'username', '–'), 'is_active': bot.is_active, 'assistant': bot.assistant.name, }) # — Datos de los últimos 7 días para scatter — today = timezone.now().date() start_date = today - timedelta(days=6) weekly_data = {} for bot in TelegramBot.objects.all(): series = [] for i in range(7): day = start_date + timedelta(days=i) cnt = TelegramMessage.objects.filter( conversation__bot=bot, timestamp__date=day ).count() series.append({'x': day.strftime('%Y-%m-%d'), 'y': cnt}) weekly_data[bot.name] = series return render(request, 'pxy_dashboard/apps/apps-telegram-bot.html', { 'stats': stats, 'bots_info': bots_info, 'weekly_data_json': json.dumps(weekly_data), })