From 0eb2b393f287cc7d4e1993d5a7ecba616d0cc5dc Mon Sep 17 00:00:00 2001 From: Ekaropolus Date: Tue, 16 Sep 2025 16:18:45 -0600 Subject: [PATCH] SAMI Functionality add --- Dockerfile | 44 +- data/denue/CDMX_cafe.csv | 6 + data/popgrid/CDMX_grid.csv | 9 + data/sami/imss_wages_2023.csv | 4 + data/sami/population.csv | 4 + docker-compose.yml | 1 + main.png | Bin 0 -> 165411 bytes polisplexity/settings.py | 35 +- polisplexity/urls.py | 14 +- preview.png | Bin 0 -> 112365 bytes pxy_api/__init__.py | 1 + pxy_api/apps.py | 5 + pxy_api/exceptions.py | 57 ++ pxy_bots (2).zip | Bin 0 -> 31215 bytes pxy_bots.zip | Bin 0 -> 31215 bytes .../api/__init__.py | 0 pxy_bots/api/urls.py | 7 + pxy_bots/api/views.py | 8 + pxy_bots/canonical.py | 126 +++ pxy_bots/views.py | 204 ++++- pxy_contracts.zip | Bin 0 -> 4619 bytes pxy_contracts/__init__.py | 0 pxy_contracts/admin.py | 3 + pxy_contracts/apps.py | 6 + pxy_contracts/contracts/__init__.py | 9 + pxy_contracts/contracts/sami.py | 48 ++ pxy_contracts/contracts/sites.py | 55 ++ pxy_contracts/migrations/__init__.py | 0 pxy_contracts/models.py | 3 + pxy_contracts/tests.py | 3 + pxy_contracts/version.py | 1 + pxy_contracts/views.py | 3 + pxy_dashboard/apps/urls.py | 10 + pxy_dashboard/apps/views.py | 5 + pxy_dashboard/middleware.py | 43 ++ pxy_dashboard/share_urls.py | 7 + .../apps/apps-sami-explorer.html | 313 ++++++++ .../pxy_dashboard/apps/apps-sites-runs.html | 96 +++ .../pxy_dashboard/apps/apps-sites-viewer.html | 411 ++++++++++ .../pxy_dashboard/share/sami_card.html | 35 + .../pxy_dashboard/share/sites_card.html | 47 ++ pxy_dashboard/utils/share.py | 37 + pxy_dashboard/views_share.py | 59 ++ pxy_de.zip | Bin 0 -> 20297 bytes pxy_de/api.py | 106 +++ pxy_de/providers/__init__.py | 0 pxy_de/providers/base.py | 72 ++ pxy_de/providers/csv_provider.py | 117 +++ pxy_de/urls.py | 6 + pxy_meta_pages/admin.py | 251 +++--- pxy_routing.zip | Bin 0 -> 5580 bytes pxy_routing/__init__.py | 0 pxy_routing/admin.py | 3 + pxy_routing/api/urls.py | 7 + pxy_routing/api/views.py | 76 ++ pxy_routing/apps.py | 6 + pxy_routing/migrations/__init__.py | 0 pxy_routing/models.py | 3 + pxy_routing/services/__init__.py | 18 + pxy_routing/services/crowfly_provider.py | 43 ++ pxy_routing/services/factory.py | 22 + pxy_routing/services/ors_provider.py | 204 +++++ pxy_routing/services/provider.py | 30 + pxy_routing/tests.py | 3 + pxy_routing/views.py | 3 + pxy_sami/__init__.py | 0 pxy_sami/admin.py | 3 + pxy_sami/api/urls.py | 8 + pxy_sami/api/views.py | 64 ++ pxy_sami/apps.py | 6 + pxy_sami/estimators/__init__.py | 0 pxy_sami/estimators/sami_core.py | 326 ++++++++ pxy_sami/migrations/__init__.py | 0 pxy_sami/models.py | 3 + pxy_sami/services/__init__.py | 0 pxy_sami/tests.py | 3 + pxy_sami/theory/README.md | 0 pxy_sami/validation/__init__.py | 0 pxy_sami/views.py | 3 + pxy_sites/__init__.py | 0 pxy_sites/admin.py | 26 + pxy_sites/api/__init__.py | 0 pxy_sites/api/urls.py | 16 + pxy_sites/api/views.py | 304 ++++++++ pxy_sites/apps.py | 6 + pxy_sites/migrations/0001_initial.py | 29 + pxy_sites/migrations/__init__.py | 0 pxy_sites/models.py | 26 + pxy_sites/services/__init__.py | 0 pxy_sites/services/site_scoring.py | 723 ++++++++++++++++++ pxy_sites/tests.py | 3 + pxy_sites/theory/README.md | 0 pxy_sites/validation/__init__.py | 0 pxy_sites/views.py | 3 + requirements.txt | 9 + 95 files changed, 4077 insertions(+), 173 deletions(-) create mode 100644 data/denue/CDMX_cafe.csv create mode 100644 data/popgrid/CDMX_grid.csv create mode 100644 data/sami/imss_wages_2023.csv create mode 100644 data/sami/population.csv create mode 100644 main.png create mode 100644 preview.png create mode 100644 pxy_api/__init__.py create mode 100644 pxy_api/apps.py create mode 100644 pxy_api/exceptions.py create mode 100644 pxy_bots (2).zip create mode 100644 pxy_bots.zip rename .env:Zone.Identifier => pxy_bots/api/__init__.py (100%) create mode 100644 pxy_bots/api/urls.py create mode 100644 pxy_bots/api/views.py create mode 100644 pxy_bots/canonical.py create mode 100644 pxy_contracts.zip create mode 100644 pxy_contracts/__init__.py create mode 100644 pxy_contracts/admin.py create mode 100644 pxy_contracts/apps.py create mode 100644 pxy_contracts/contracts/__init__.py create mode 100644 pxy_contracts/contracts/sami.py create mode 100644 pxy_contracts/contracts/sites.py create mode 100644 pxy_contracts/migrations/__init__.py create mode 100644 pxy_contracts/models.py create mode 100644 pxy_contracts/tests.py create mode 100644 pxy_contracts/version.py create mode 100644 pxy_contracts/views.py create mode 100644 pxy_dashboard/share_urls.py create mode 100644 pxy_dashboard/templates/pxy_dashboard/apps/apps-sami-explorer.html create mode 100644 pxy_dashboard/templates/pxy_dashboard/apps/apps-sites-runs.html create mode 100644 pxy_dashboard/templates/pxy_dashboard/apps/apps-sites-viewer.html create mode 100644 pxy_dashboard/templates/pxy_dashboard/share/sami_card.html create mode 100644 pxy_dashboard/templates/pxy_dashboard/share/sites_card.html create mode 100644 pxy_dashboard/utils/share.py create mode 100644 pxy_dashboard/views_share.py create mode 100644 pxy_de.zip create mode 100644 pxy_de/api.py create mode 100644 pxy_de/providers/__init__.py create mode 100644 pxy_de/providers/base.py create mode 100644 pxy_de/providers/csv_provider.py create mode 100644 pxy_de/urls.py create mode 100644 pxy_routing.zip create mode 100644 pxy_routing/__init__.py create mode 100644 pxy_routing/admin.py create mode 100644 pxy_routing/api/urls.py create mode 100644 pxy_routing/api/views.py create mode 100644 pxy_routing/apps.py create mode 100644 pxy_routing/migrations/__init__.py create mode 100644 pxy_routing/models.py create mode 100644 pxy_routing/services/__init__.py create mode 100644 pxy_routing/services/crowfly_provider.py create mode 100644 pxy_routing/services/factory.py create mode 100644 pxy_routing/services/ors_provider.py create mode 100644 pxy_routing/services/provider.py create mode 100644 pxy_routing/tests.py create mode 100644 pxy_routing/views.py create mode 100644 pxy_sami/__init__.py create mode 100644 pxy_sami/admin.py create mode 100644 pxy_sami/api/urls.py create mode 100644 pxy_sami/api/views.py create mode 100644 pxy_sami/apps.py create mode 100644 pxy_sami/estimators/__init__.py create mode 100644 pxy_sami/estimators/sami_core.py create mode 100644 pxy_sami/migrations/__init__.py create mode 100644 pxy_sami/models.py create mode 100644 pxy_sami/services/__init__.py create mode 100644 pxy_sami/tests.py create mode 100644 pxy_sami/theory/README.md create mode 100644 pxy_sami/validation/__init__.py create mode 100644 pxy_sami/views.py create mode 100644 pxy_sites/__init__.py create mode 100644 pxy_sites/admin.py create mode 100644 pxy_sites/api/__init__.py create mode 100644 pxy_sites/api/urls.py create mode 100644 pxy_sites/api/views.py create mode 100644 pxy_sites/apps.py create mode 100644 pxy_sites/migrations/0001_initial.py create mode 100644 pxy_sites/migrations/__init__.py create mode 100644 pxy_sites/models.py create mode 100644 pxy_sites/services/__init__.py create mode 100644 pxy_sites/services/site_scoring.py create mode 100644 pxy_sites/tests.py create mode 100644 pxy_sites/theory/README.md create mode 100644 pxy_sites/validation/__init__.py create mode 100644 pxy_sites/views.py diff --git a/Dockerfile b/Dockerfile index c306dde..1f15bed 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,40 +1,34 @@ -# Etapa base: Python oficial -FROM python:3.10-slim as base +# Dockerfile (prod) +FROM python:3.10-slim -# Variables de entorno para producción -ENV PYTHONDONTWRITEBYTECODE 1 -ENV PYTHONUNBUFFERED 1 +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + PIP_NO_CACHE_DIR=1 -# Instala dependencias del sistema (incluye lo necesario para mysqlclient) +# System deps needed by geopandas/shapely/pyproj, mysqlclient, etc. RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential \ + curl \ libgeos-dev \ libspatialindex-dev \ - libproj-dev \ - proj-data \ - proj-bin \ - gdal-bin \ - libgdal-dev \ - python3-dev \ - pkg-config \ + libproj-dev proj-data proj-bin \ + gdal-bin libgdal-dev \ + python3-dev pkg-config \ default-libmysqlclient-dev \ - && rm -rf /var/lib/apt/lists/* + && rm -rf /var/lib/apt/lists/* -# Crea directorio de trabajo WORKDIR /app -# Copia requirements primero (mejor cacheo) +# Install Python deps first (layer cache friendly) COPY requirements.txt . +RUN python -m pip install --upgrade pip \ + && pip install --no-cache-dir -r requirements.txt -# Instala dependencias Python -RUN pip install --no-cache-dir -r requirements.txt - -# Copia el resto del proyecto +# Copy project COPY . . -# Expone el puerto del contenedor -EXPOSE 8000 +# Expose prod port (compose overrides CMD/port, but this documents intent) +EXPOSE 8002 -# Comando por defecto para producción con Gunicorn -CMD ["gunicorn", "polisplexity.wsgi:application", "--bind", "0.0.0.0:8000", "--workers=3", "--timeout=120"] - \ No newline at end of file +# Default CMD (compose will override with your shell that migrates, collectstatic, and runs gunicorn:8002) +CMD ["gunicorn", "polisplexity.wsgi:application", "--bind", "0.0.0.0:8002", "--workers=3", "--timeout=180"] diff --git a/data/denue/CDMX_cafe.csv b/data/denue/CDMX_cafe.csv new file mode 100644 index 0000000..87084db --- /dev/null +++ b/data/denue/CDMX_cafe.csv @@ -0,0 +1,6 @@ +name,lat,lon,category +Cafe Centro,19.4335,-99.1342,cafe +Cafe Alameda,19.4350,-99.1410,cafe +Cafe Madero,19.4321,-99.1358,cafe +Cafe Zocalo,19.4329,-99.1320,cafe +Cafe Bellas Artes,19.4365,-99.1415,cafe diff --git a/data/popgrid/CDMX_grid.csv b/data/popgrid/CDMX_grid.csv new file mode 100644 index 0000000..f30be8f --- /dev/null +++ b/data/popgrid/CDMX_grid.csv @@ -0,0 +1,9 @@ +cell_id,lat,lon,pop +ZC_01,19.4334,-99.1322,1200 +ZC_02,19.4318,-99.1339,950 +ZC_03,19.4347,-99.1351,800 +ZC_04,19.4309,-99.1311,700 +ZC_05,19.4360,-99.1405,1100 +ZC_06,19.4298,-99.1368,600 +ZC_07,19.4382,-99.1450,900 +ZC_08,19.4355,-99.1289,750 diff --git a/data/sami/imss_wages_2023.csv b/data/sami/imss_wages_2023.csv new file mode 100644 index 0000000..16610c2 --- /dev/null +++ b/data/sami/imss_wages_2023.csv @@ -0,0 +1,4 @@ +city,value +CDMX,100 +GDL,55 +MTY,60 diff --git a/data/sami/population.csv b/data/sami/population.csv new file mode 100644 index 0000000..4e7c701 --- /dev/null +++ b/data/sami/population.csv @@ -0,0 +1,4 @@ +city,N +CDMX,9209944 +GDL,5269191 +MTY,5341174 diff --git a/docker-compose.yml b/docker-compose.yml index 70e5347..cd288e1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -36,6 +36,7 @@ services: - static_data:/app/static - media_data:/app/media - ./staticfiles:/app/staticfiles + - ./data:/app/polisplexity/data:ro # - .:/app # ←❌ No lo uses en producción: desactiva para evitar sobrescribir volumes: diff --git a/main.png b/main.png new file mode 100644 index 0000000000000000000000000000000000000000..097f98160f2a4e92ba087ddabe9a93b6001e6193 GIT binary patch literal 165411 zcmdqJc|6u@+dg`0ph2dPWM~j&3YAbHrG!)>L*}7`$XqfNG7k+%NRlKok$K3JRH#fz zri9FuDbqf#)_R`(uIGLC{=I+h&wf5nYpuBN`+I+{>%7kMIFI8vzka7wj;&k6zJ@}f ztUIo7M3q8WVNd?7qQ*}cXbvrtq;@5GxWmb1eIGZ?_(?~nDB*&KcMqEG*nIdRi%GheNrVvH(S;c7J0Xa$}3G+uV?MNt19*3)}H;-^NXt_H@$yy@w&K1$ykd(y0}w)$3dsgU-w;p z(lQzcUT2V7wZ-etKUUq$%My_N*WVcAUf*Pr`s+93*UJvC=KS|d8Lr29(f$24207od zQ*u84{+uHQJK_%i`%|`x+->;x3s*6+DGvPmgozY|8lt`F{{scRJRoD z^+Qg_A61{acz}7YMRUsg{$u_Z#G^9J8n+&p{d#BBs#PAEf;Sa-Lb6Z+)y7%_H&!y?HPW@rm%3Rk}?VN_B zGkir0Q|+xe78@jj1#+Zuq0DxT_S{RlQX#uaW?otqJ~((y>h9HvstvP~g^nZq4zat> zzv-$=KlkEYf;09aN#d^!;liw1J;XO&r+-z~^mcUW?9taK^(=DJNi zv;DC(_xD;{p6HQ(s>Y9RkAL#;^7gb_m %cAPGCbDjS^lrB8dlIv<~E4DcQ^Q53= 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 IiYDj(9|R8-1ONa4 literal 0 HcmV?d00001 diff --git a/polisplexity/settings.py b/polisplexity/settings.py index abcd4f6..27bf620 100644 --- a/polisplexity/settings.py +++ b/polisplexity/settings.py @@ -52,6 +52,14 @@ INSTALLED_APPS = [ "pxy_dashboard.layouts", "pxy_building_digital_twins", "pxy_messenger", + 'pxy_contracts', + 'pxy_sami', + 'pxy_routing', + 'pxy_sites', + + "rest_framework", + "pxy_api", + # Third-party apps "crispy_forms", @@ -139,7 +147,8 @@ STATICFILES_DIRS = [ ] MEDIA_URL = "/media/" -MEDIA_ROOT = os.path.join(BASE_DIR, "mediafiles") +MEDIA_ROOT = BASE_DIR / "media" + # Default primary key field type DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" @@ -182,3 +191,27 @@ MESSENGER_VERIFY_TOKEN = os.getenv("MESSENGER_VERIFY_TOKEN", "dev-change-me") FACEBOOK_APP_SECRET = os.getenv("FACEBOOK_APP_SECRET", "") # set this in .env for prod +REST_FRAMEWORK = { + # Deshabilitamos auth por ahora para evitar CSRF en curl + "DEFAULT_AUTHENTICATION_CLASSES": [], + + # Throttling global + por-scope + "DEFAULT_THROTTLE_CLASSES": [ + "rest_framework.throttling.AnonRateThrottle", + "rest_framework.throttling.UserRateThrottle", + "rest_framework.throttling.ScopedRateThrottle", + ], + "DEFAULT_THROTTLE_RATES": { + "anon": "100/hour", + "user": "1000/hour", + "sami_run": "30/minute", + "sites_search": "15/minute", + "routing_isochrone": "60/minute", + "routing_health": "120/minute", + "sami_health": "120/minute", + "sites_health": "120/minute", + }, + + # Manejo de errores uniforme + "EXCEPTION_HANDLER": "pxy_api.exceptions.envelope_exception_handler", +} diff --git a/polisplexity/urls.py b/polisplexity/urls.py index f75aec6..65f47ec 100644 --- a/polisplexity/urls.py +++ b/polisplexity/urls.py @@ -38,8 +38,20 @@ urlpatterns = [ namespace="pxy_building_digital_twins"), ), path("messenger/", include("pxy_messenger.urls")), + path("", include("pxy_sami.api.urls")), + path("", include("pxy_routing.api.urls")), + path("", include("pxy_sites.api.urls")), + path("", include("pxy_de.urls")), + + path("share/", include("pxy_dashboard.share_urls")), # ← NEW + + path("api/", include("pxy_bots.api.urls")), + + + ] if settings.DEBUG: - urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) \ No newline at end of file + urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) + urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \ No newline at end of file diff --git a/preview.png b/preview.png new file mode 100644 index 0000000000000000000000000000000000000000..46e61e2932d8072716076249296079aed4e2554f GIT binary patch literal 112365 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 literal 0 HcmV?d00001 diff --git a/pxy_api/__init__.py b/pxy_api/__init__.py new file mode 100644 index 0000000..4def7bd --- /dev/null +++ b/pxy_api/__init__.py @@ -0,0 +1 @@ +default_app_config = "pxy_api.apps.PxyApiConfig" diff --git a/pxy_api/apps.py b/pxy_api/apps.py new file mode 100644 index 0000000..0051700 --- /dev/null +++ b/pxy_api/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + +class PxyApiConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "pxy_api" diff --git a/pxy_api/exceptions.py b/pxy_api/exceptions.py new file mode 100644 index 0000000..0da0566 --- /dev/null +++ b/pxy_api/exceptions.py @@ -0,0 +1,57 @@ +from __future__ import annotations +import uuid, traceback +from django.conf import settings +from rest_framework.views import exception_handler as drf_exception_handler +from rest_framework.response import Response +from rest_framework import status +from rest_framework.exceptions import ValidationError as DRFValidationError +from pydantic import ValidationError as PydValidationError + +def envelope_exception_handler(exc, context): + """ + Envuelve *todas* las excepciones DRF en: + { ok: false, code, message, errors?, hint?, trace_id, detail?(DEBUG) } + """ + resp = drf_exception_handler(exc, context) + trace_id = str(uuid.uuid4()) + + if resp is not None: + # DRF ya resolvió un status_code razonable + code = getattr(exc, "default_code", "error") + message = None + + if isinstance(exc, DRFValidationError): + message = "Validation error" + else: + # fallback a string corto + message = str(getattr(exc, "detail", "")) or exc.__class__.__name__ + + data = { + "ok": False, + "code": code, + "message": message, + "errors": resp.data, # DRF normaliza los errores aquí + "hint": None, + "trace_id": trace_id, + } + if settings.DEBUG: + data["detail"] = _short_trace() + return Response(data, status=resp.status_code) + + # Excepción no manejada por DRF -> 500 + data = { + "ok": False, + "code": "server_error", + "message": "Unexpected server error", + "hint": None, + "trace_id": trace_id, + } + if settings.DEBUG: + data["detail"] = _short_trace() + return Response(data, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + +def _short_trace(): + try: + return "\n".join(traceback.format_exc().splitlines()[-6:]) + except Exception: + return None diff --git a/pxy_bots (2).zip b/pxy_bots (2).zip new file mode 100644 index 0000000000000000000000000000000000000000..399022d17d0c802a38cfbd9b7e139d1eb64e7cc0 GIT binary patch literal 31215 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 literal 0 HcmV?d00001 diff --git a/pxy_bots.zip b/pxy_bots.zip new file mode 100644 index 0000000000000000000000000000000000000000..f438163c80d510e7e03d9b7079eb65213eb322d0 GIT binary patch literal 31215 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 literal 0 HcmV?d00001 diff --git a/.env:Zone.Identifier b/pxy_bots/api/__init__.py similarity index 100% rename from .env:Zone.Identifier rename to pxy_bots/api/__init__.py diff --git a/pxy_bots/api/urls.py b/pxy_bots/api/urls.py new file mode 100644 index 0000000..687dfe9 --- /dev/null +++ b/pxy_bots/api/urls.py @@ -0,0 +1,7 @@ +# pxy_bots/api/urls.py +from django.urls import path +from . import views + +urlpatterns = [ + path("bots/health/", views.health, name="pxy_bots_health"), +] diff --git a/pxy_bots/api/views.py b/pxy_bots/api/views.py new file mode 100644 index 0000000..9ba32b3 --- /dev/null +++ b/pxy_bots/api/views.py @@ -0,0 +1,8 @@ +# pxy_bots/api/views.py +import json +from django.http import JsonResponse, HttpResponse +from django.views.decorators.csrf import csrf_exempt + +def health(request): + return JsonResponse({"ok": True, "service": "pxy_bots", "schema_ready": ["req.v1", "render.v1"]}) + diff --git a/pxy_bots/canonical.py b/pxy_bots/canonical.py new file mode 100644 index 0000000..7f0a702 --- /dev/null +++ b/pxy_bots/canonical.py @@ -0,0 +1,126 @@ +# pxy_bots/canonical.py +from typing import Any, Dict, Optional + +def _pick_photo(sizes): + # Telegram sends photos as array of sizes; pick the largest + if not sizes: + return None + sizes = sorted(sizes, key=lambda s: (s.get("width", 0) * s.get("height", 0)), reverse=True) + top = sizes[0] + return { + "type": "photo", + "file_id": top.get("file_id"), + "mime": "image/jpeg", # Telegram photos are JPEG + "size_bytes": None, # Telegram doesn't include bytes here; leave None + "width": top.get("width"), + "height": top.get("height"), + } + +def _extract_media(msg: Dict[str, Any]) -> Optional[Dict[str, Any]]: + if "photo" in msg: + return _pick_photo(msg.get("photo") or []) + if "voice" in msg: + v = msg["voice"] + return {"type": "voice", "file_id": v.get("file_id"), "mime": v.get("mime_type"), "size_bytes": v.get("file_size"), "duration": v.get("duration")} + if "audio" in msg: + a = msg["audio"] + return {"type": "audio", "file_id": a.get("file_id"), "mime": a.get("mime_type"), "size_bytes": a.get("file_size"), "duration": a.get("duration")} + if "video" in msg: + v = msg["video"] + return {"type": "video", "file_id": v.get("file_id"), "mime": v.get("mime_type"), "size_bytes": v.get("file_size"), "duration": v.get("duration"), "width": v.get("width"), "height": v.get("height")} + if "video_note" in msg: + v = msg["video_note"] + return {"type": "video_note", "file_id": v.get("file_id"), "mime": None, "size_bytes": v.get("file_size"), "duration": v.get("duration"), "length": v.get("length")} + if "animation" in msg: + a = msg["animation"] + return {"type": "animation", "file_id": a.get("file_id"), "mime": a.get("mime_type"), "size_bytes": a.get("file_size")} + if "document" in msg: + d = msg["document"] + return {"type": "document", "file_id": d.get("file_id"), "mime": d.get("mime_type"), "size_bytes": d.get("file_size"), "file_name": d.get("file_name")} + return None + +def build_req_v1(update: Dict[str, Any], bot_name: str) -> Dict[str, Any]: + """ + Normalize a Telegram update into our canonical req.v1 envelope. + Pure function. No network, no state. + """ + schema_version = "req.v1" + update_id = update.get("update_id") + + # Determine primary container: message, edited_message, callback_query + msg = update.get("message") or update.get("edited_message") + cbq = update.get("callback_query") + + # Chat/user basics + if msg: + chat = msg.get("chat") or {} + user = msg.get("from") or {} + message_id = msg.get("message_id") + ts = msg.get("date") + text = msg.get("text") + caption = msg.get("caption") + location = msg.get("location") + media = _extract_media(msg) + trigger = "message" + elif cbq: + m = cbq.get("message") or {} + chat = m.get("chat") or {} + user = cbq.get("from") or {} + message_id = m.get("message_id") + ts = m.get("date") or None + text = None + caption = None + location = None + media = None + trigger = "callback" + else: + # Fallback for other update types we haven't mapped yet + chat = {} + user = update.get("from") or {} + message_id = None + ts = None + text = None + caption = None + location = None + media = None + trigger = "unknown" + + # Command name (if text/caption starts with '/') + raw_cmd = None + if text and isinstance(text, str) and text.startswith("/"): + raw_cmd = text.split()[0][1:] + elif caption and isinstance(caption, str) and caption.startswith("/"): + raw_cmd = caption.split()[0][1:] + elif cbq and isinstance(cbq.get("data"), str): + raw_cmd = None # callbacks carry 'action' instead + + # Build envelope + env = { + "schema_version": schema_version, + "bot": {"username": bot_name}, + "chat": {"id": chat.get("id"), "type": chat.get("type")}, + "user": {"id": user.get("id"), "language": user.get("language_code")}, + "command": { + "name": raw_cmd, + "version": 1, + "trigger": ("text_command" if raw_cmd and trigger == "message" else ("callback" if trigger == "callback" else trigger)), + }, + "input": { + "text": text, + "caption": caption, + "args_raw": text or caption, + "media": media, + "location": ({"lat": location.get("latitude"), "lon": location.get("longitude")} if location else None), + }, + "callback": ( + {"id": cbq.get("id"), "data": cbq.get("data"), "origin": {"message_id": message_id, "chat_id": chat.get("id")}} + if cbq else None + ), + "context": { + "message_id": message_id, + "update_id": update_id, + "ts": ts, + "idempotency_key": f"tg:{message_id}:{user.get('id')}" if message_id and user.get("id") else None, + }, + } + return env diff --git a/pxy_bots/views.py b/pxy_bots/views.py index f67d050..25ed52f 100644 --- a/pxy_bots/views.py +++ b/pxy_bots/views.py @@ -1,10 +1,12 @@ +# pxy_bots/views.py import os import json import logging +from typing import Any, Dict, Optional import openai from telegram import Update, Bot -from django.http import JsonResponse +from django.http import JsonResponse, HttpResponse from django.views.decorators.csrf import csrf_exempt from asgiref.sync import sync_to_async @@ -20,15 +22,141 @@ from .handlers import ( logger = logging.getLogger(__name__) openai.api_key = os.getenv("OPENAI_API_KEY") +# --------------------------- +# Canonical req.v1 builder +# --------------------------- -async def handle_location_message(update): - if update.message.location: +def _pick_photo(sizes): + if not sizes: + return None + sizes = sorted(sizes, key=lambda s: (s.get("width", 0) * s.get("height", 0)), reverse=True) + top = sizes[0] + return { + "type": "photo", + "file_id": top.get("file_id"), + "mime": "image/jpeg", + "size_bytes": None, + "width": top.get("width"), + "height": top.get("height"), + } + +def _extract_media(msg: Dict[str, Any]) -> Optional[Dict[str, Any]]: + if "photo" in msg: + return _pick_photo(msg.get("photo") or []) + if "voice" in msg: + v = msg["voice"] + return {"type": "voice", "file_id": v.get("file_id"), "mime": v.get("mime_type"), "size_bytes": v.get("file_size"), "duration": v.get("duration")} + if "audio" in msg: + a = msg["audio"] + return {"type": "audio", "file_id": a.get("file_id"), "mime": a.get("mime_type"), "size_bytes": a.get("file_size"), "duration": a.get("duration")} + if "video" in msg: + v = msg["video"] + return {"type": "video", "file_id": v.get("file_id"), "mime": v.get("mime_type"), "size_bytes": v.get("file_size"), "duration": v.get("duration"), "width": v.get("width"), "height": v.get("height")} + if "video_note" in msg: + v = msg["video_note"] + return {"type": "video_note", "file_id": v.get("file_id"), "mime": None, "size_bytes": v.get("file_size"), "duration": v.get("duration"), "length": v.get("length")} + if "animation" in msg: + a = msg["animation"] + return {"type": "animation", "file_id": a.get("file_id"), "mime": a.get("mime_type"), "size_bytes": a.get("file_size")} + if "document" in msg: + d = msg["document"] + return {"type": "document", "file_id": d.get("file_id"), "mime": d.get("mime_type"), "size_bytes": d.get("file_size"), "file_name": d.get("file_name")} + return None + +def build_req_v1(update: Dict[str, Any], bot_name: str) -> Dict[str, Any]: + """ + Normalize a Telegram update into our canonical req.v1 envelope. + Pure function. No network, no state. + """ + schema_version = "req.v1" + update_id = update.get("update_id") + + msg = update.get("message") or update.get("edited_message") + cbq = update.get("callback_query") + + if msg: + chat = msg.get("chat") or {} + user = msg.get("from") or {} + message_id = msg.get("message_id") + ts = msg.get("date") + text = msg.get("text") + caption = msg.get("caption") + location = msg.get("location") + media = _extract_media(msg) + trigger = "message" + elif cbq: + m = cbq.get("message") or {} + chat = m.get("chat") or {} + user = cbq.get("from") or {} + message_id = m.get("message_id") + ts = m.get("date") or None + text = None + caption = None + location = None + media = None + trigger = "callback" + else: + chat = {} + user = update.get("from") or {} + message_id = None + ts = None + text = None + caption = None + location = None + media = None + trigger = "unknown" + + raw_cmd = None + if text and isinstance(text, str) and text.startswith("/"): + raw_cmd = text.split()[0][1:] + elif caption and isinstance(caption, str) and caption.startswith("/"): + raw_cmd = caption.split()[0][1:] + elif cbq and isinstance(cbq.get("data"), str): + raw_cmd = None # callbacks carry 'data' instead + + env = { + "schema_version": schema_version, + "bot": {"username": bot_name}, + "chat": {"id": chat.get("id"), "type": chat.get("type")}, + "user": {"id": user.get("id"), "language": user.get("language_code")}, + "command": { + "name": raw_cmd, + "version": 1, + "trigger": ("text_command" if raw_cmd and trigger == "message" + else ("callback" if trigger == "callback" else trigger)), + }, + "input": { + "text": text, + "caption": caption, + "args_raw": text or caption, + "media": media, + "location": ({"lat": location.get("latitude"), "lon": location.get("longitude")} if location else None), + }, + "callback": ( + {"id": cbq.get("id"), "data": cbq.get("data"), + "origin": {"message_id": message_id, "chat_id": chat.get("id")}} + if cbq else None + ), + "context": { + "message_id": message_id, + "update_id": update_id, + "ts": ts, + "idempotency_key": f"tg:{message_id}:{user.get('id')}" if message_id and user.get("id") else None, + }, + } + return env + +# --------------------------- +# Existing helper flows +# --------------------------- + +async def handle_location_message(update: Update): + if update.message and update.message.location: await handle_location(update) return True return False - -async def dispatch_citizen_commands(update, text): +async def dispatch_citizen_commands(update: Update, text: str): if text == "/start": await start(update) elif text == "/help": @@ -45,8 +173,7 @@ async def dispatch_citizen_commands(update, text): return False return True - -async def dispatch_city_commands(update, text): +async def dispatch_city_commands(update: Update, text: str): if text == "/start": await start(update) elif text == "/help": @@ -63,8 +190,7 @@ async def dispatch_city_commands(update, text): return False return True - -async def dispatch_private_commands(update, text): +async def dispatch_private_commands(update: Update, text: str): if text == "/start": await start(update) elif text == "/help": @@ -83,31 +209,35 @@ async def dispatch_private_commands(update, text): return False return True - -async def transcribe_with_whisper(update, bot): - # 1) Descarga el audio +async def transcribe_with_whisper(update: Update, bot: Bot) -> Optional[str]: + # 1) Download audio from Telegram tg_file = await bot.get_file(update.message.voice.file_id) download_path = f"/tmp/{update.message.voice.file_id}.ogg" await tg_file.download_to_drive(download_path) - # 2) Llama al endpoint de transcripción + # 2) Transcribe (OpenAI) with open(download_path, "rb") as audio: - # Como response_format="text", esto retorna un str transcript_str = openai.audio.transcriptions.create( - model="gpt-4o-transcribe", # o "whisper-1" + model="gpt-4o-transcribe", # or "whisper-1" file=audio, response_format="text", - language="es" + language="es", ) - return transcript_str.strip() + return transcript_str.strip() if transcript_str else None +# --------------------------- +# Webhook +# --------------------------- @csrf_exempt -async def telegram_webhook(request, bot_name): +async def telegram_webhook(request, bot_name: str): try: - logger.info(f"Webhook called for bot: {bot_name}") + logger.info("Webhook called for bot=%s", bot_name) - # Carga bot (solo ORM en sync_to_async) + if request.method != "POST": + return HttpResponse(status=405) + + # Load bot (sync ORM via sync_to_async) try: bot_instance = await sync_to_async(TelegramBot.objects.get)( name=bot_name, is_active=True @@ -117,34 +247,44 @@ async def telegram_webhook(request, bot_name): if not bot_instance.assistant: return JsonResponse({"error": "Assistant not configured."}, status=400) - if request.method != "POST": - return JsonResponse({"error": "Invalid request method"}, status=400) - payload = json.loads(request.body.decode("utf-8")) + # Parse raw payload + try: + payload = json.loads(request.body.decode("utf-8") or "{}") + except json.JSONDecodeError: + return JsonResponse({"ok": False, "error": "invalid_json"}, status=400) + + # Build canonical req.v1 (LOG ONLY for now) + try: + canon = build_req_v1(payload, bot_name) + logger.info("tg.canonical env=%s", json.dumps(canon, ensure_ascii=False)) + except Exception as e: + logger.exception("tg.canonical.failed: %s", e) + + # Convert to telegram.Update update = Update.de_json(payload, Bot(token=bot_instance.token)) if not update.message: + # No message (e.g., callback handled elsewhere in legacy); ack anyway return JsonResponse({"status": "no message"}) - # 1) Geolocalización + # 1) Location first if await handle_location_message(update): return JsonResponse({"status": "ok"}) - # 2) Voz: transcribe y report_trash + # 2) Voice → transcribe → LLM reply if update.message.voice: bot = Bot(token=bot_instance.token) transcript = await transcribe_with_whisper(update, bot) if not transcript: - await update.message.reply_text( - "No pude entender tu mensaje de voz. Intenta de nuevo." - ) + await update.message.reply_text("No pude entender tu mensaje de voz. Intenta de nuevo.") return JsonResponse({"status": "ok"}) assistant_instance = await sync_to_async(LangchainAIService)(bot_instance.assistant) bot_response = await sync_to_async(assistant_instance.generate_response)(transcript) - await update.message.reply_text(bot_response) + await update.message.reply_text(bot_response) return JsonResponse({"status": "ok"}) - # 3) Comandos de texto + # 3) Text commands by bot persona text = update.message.text or "" if bot_name == "PepeBasuritaCoinsBot" and await dispatch_citizen_commands(update, text): return JsonResponse({"status": "ok"}) @@ -153,7 +293,7 @@ async def telegram_webhook(request, bot_name): if bot_name == "PepeMotitoBot" and await dispatch_private_commands(update, text): return JsonResponse({"status": "ok"}) - # 4) Fallback LLM + # 4) Fallback LLM for any other text assistant_instance = await sync_to_async(LangchainAIService)(bot_instance.assistant) bot_response = await sync_to_async(assistant_instance.generate_response)(text) await update.message.reply_text(bot_response) @@ -161,5 +301,5 @@ async def telegram_webhook(request, bot_name): return JsonResponse({"status": "ok"}) except Exception as e: - logger.error(f"Error in webhook: {e}") + logger.exception("Error in webhook: %s", e) return JsonResponse({"error": f"Unexpected error: {str(e)}"}, status=500) diff --git a/pxy_contracts.zip b/pxy_contracts.zip new file mode 100644 index 0000000000000000000000000000000000000000..b1d0ba76d5f629b1f000ee49554c4dade6d8456c GIT binary patch literal 4619 zcmcIo4LsBNAKyG=m@J0PaYj=f+Jx#%9yX~w#Mq)z9`iJ3i)Np{sC7=~C`Do&2^n-T(i3{pa<2*lVxX`}6)he%~K%&eAeSh~N_@ zkKZ}>^3QE41OtiO6YIM>g286`?`E@1qV_04pfZA==WZTbl^}B8g*AqrjA;xNJl*E7 zU=SFj5+;nLE{5eFNT)IIk+Gk`B-1`%3ZAmHwMvPrVX~d;sWbKQN4`lm+HNX!c(C?) z!g%xQiJk=edv(T9$#+$EjE4VVujq-kbXM`Ll2^=%ALMX0*}6H)!Tw7AP16S8YzthH z8ES|zq(=k>g|q$zMQ7tyo`*w=6W5v7>+DEr=Hi-7jNF^s$&T%Y!-EvhNW7EFDo2k( zt_dzF)hIAuz2J{;pa2y)nBMe=>pFlIVNq)0!D9!p*#9jvV@Dj`{U2^H=+*C4)2Nl# zIcj-n6jf)6n$e_l8j={6s%Az>qfA|&W4AJ+_pK$s%u0xzXqlkI0v8>`WYHodB^O*U zic2G-PAIdK6kff2O)d3sGwU?xo4XB4&6y|c{rN5$Fs<>?Jn#YuR1&T%ar2AUE1dbE zwgk_GY7j-mh&i=@%Ax_`mrxd9^x~{`hNdZ^Y24~igM7Zy0qhZlqZ_7S$2sXRPC4bE zXXAQBM%AmvS1SsBtlsdjlzh9a&%0XB>jx$gHawgACwG5Uy_Y@~{)ii_`CIw~*5j^< zQNdL{Q{P2DS~og}Nif-0JF(mVsJE3c&j;-0k@{Rt{l45y2X*SVdB?j_!ItEv?cK_^5&!ufoZ4SKs za#hUL3qQZSCO~S$IhPm(t#yBzo?2l^nDG95q}WT9klxwedz~D)wHf3Cql@S-Fley!q^3sk_Hh zc9HMwj&39v=kf8lnR0p=Ehpfh_q~>Z4ZYfH32N7t^adqAsTgY6Zf%|NHt+mvS2yjh z9LNitc~(kdnYzwwxBG z-Wm{*#`qm>4&$8;(J;}NdOZ@_ITk#d{GjZ5DD+%)y8-)1_pj52i#Y^@%xHwHRZ(pkxvtG zNFNLQONaf#t$i0=Y)6~49tYO9rAmie@K_a$cN^+WGkFe313e?3%Cd5{X0Vr*f6d#j z3HA#Mw$qE%)w)(IUS=`}7}c}1XIvs@@nounF9T=O{53bIJB?kr?OW#uVf4!>xKX>J z6K<6Y4&OzZNKd4X0+(kp&%+W7uh-@diMur8rGnd%eW+1q*k@_kQmt%iU z#!aOVOQ1+j102LO}m)W^9cI1xq1rp_WRvHa;Y3GRTWXEDcx zac?!^&}*B9TR+K^dNlQ;oAq37-y**ETiz!FH9f!S6(3oY!Ra2_ZtcM1f4d4@p=yEi zDv^15^JHDq=AZ|U&u#T=ynTvtD|TS7O!=LS$DW0c24-Tbk>MfFpE@rslly9;KJjM? z(!}s?^RGR7Yl++N`rXMTTUK}%Wdu6fq!eemMDWNx@7@GjR#%R(%WDySam=+gkEfZX z!CEIQF}AqOr0e)LQ1QQ-ySbLY?Z{dI2M1Oy05_I z;=@*ip)C18w~CTh)hi_vSW@(vrCf9=Qn&!D>v|t{kD=sp4|9%q5B5s(E2jXCr}e=oU@cAm?b} zB!X|=VNf{WH(g1(h;olL!%i~nudW?f|83q;rKAQuuJ`?2^|z0C7|^dZ65m{iky1Fs zIrRX`*~Uxlett9Rr|xlE_xkrQ4A9u*u@3B7yA_zC$i|_2w~_fC-T$Ev21mps$E=Iv zoARSWpV4W7)EnJ5UEYr|_Abeh(HYS);?-zrr=MB>r~n--IWR)pXBI)1sQN!) zMIc|mDqy*gm9!l*(8Yq_D-Osgic$x`e`27{;QuBfP4Pd#g9o3QkRt/", share_sites_card, name="share_sites_card"), + path("sami//", share_sami_card, name="share_sami_card"), +] diff --git a/pxy_dashboard/templates/pxy_dashboard/apps/apps-sami-explorer.html b/pxy_dashboard/templates/pxy_dashboard/apps/apps-sami-explorer.html new file mode 100644 index 0000000..2fb62de --- /dev/null +++ b/pxy_dashboard/templates/pxy_dashboard/apps/apps-sami-explorer.html @@ -0,0 +1,313 @@ +{% extends "pxy_dashboard/partials/base.html" %} +{% load static %} + +{% block title %}SAMI · Explorer{% endblock title %} + +{% block extra_css %} + + +{% endblock extra_css %} + +{% block pagetitle %} + {% include "pxy_dashboard/partials/page-title.html" with pagetitle="SAMI" title="Explorer" %} +{% endblock pagetitle %} + +{% block content %} + +{% endblock content %} + +{% block extra_js %} + + +{% endblock extra_js %} diff --git a/pxy_dashboard/templates/pxy_dashboard/apps/apps-sites-runs.html b/pxy_dashboard/templates/pxy_dashboard/apps/apps-sites-runs.html new file mode 100644 index 0000000..68ab2c4 --- /dev/null +++ b/pxy_dashboard/templates/pxy_dashboard/apps/apps-sites-runs.html @@ -0,0 +1,96 @@ +{% extends "pxy_dashboard/partials/base.html" %} +{% load static %} + +{% block title %}Sites · Recent runs{% endblock title %} + +{% block pagetitle %} + {% include "pxy_dashboard/partials/page-title.html" with pagetitle="Sites" title="Recent runs" %} +{% endblock pagetitle %} + +{% block content %} +
+
+
+
+
+
+

Latest runs

+ Click a card to open the interactive viewer +
+
+ + +
+
+ +
+ +
+
No runs yet
+ Trigger a run via /api/sites/search and refresh. +
+
+
+
+
+{% endblock content %} + +{% block extra_js %} + +{% endblock extra_js %} diff --git a/pxy_dashboard/templates/pxy_dashboard/apps/apps-sites-viewer.html b/pxy_dashboard/templates/pxy_dashboard/apps/apps-sites-viewer.html new file mode 100644 index 0000000..f50d16a --- /dev/null +++ b/pxy_dashboard/templates/pxy_dashboard/apps/apps-sites-viewer.html @@ -0,0 +1,411 @@ +{% extends "pxy_dashboard/partials/base.html" %} +{% load static %} + +{% block title %}Sites · Run viewer{% endblock title %} + +{% block extra_css %} + + +{% endblock extra_css %} + +{% block pagetitle %} + {% include "pxy_dashboard/partials/page-title.html" with pagetitle="Sites" title="Run viewer" %} +{% endblock pagetitle %} + +{% block content %} +
+
+
+
+
+
+

Interactive run viewer

+ Loading… +
+ +
+ +
+ + +
+ + + + Area: + +
+
+ + +
+ +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ +
+ + +
+ +
+ +
+ Tip: hover candidates for score & breakdown; toggle layers on the right; use the scrubber to switch minutes. +
+
+
+
+
+{% endblock content %} + +{% block extra_js %} + + +{% endblock extra_js %} diff --git a/pxy_dashboard/templates/pxy_dashboard/share/sami_card.html b/pxy_dashboard/templates/pxy_dashboard/share/sami_card.html new file mode 100644 index 0000000..fc52dbe --- /dev/null +++ b/pxy_dashboard/templates/pxy_dashboard/share/sami_card.html @@ -0,0 +1,35 @@ +{% load static %} + + + + + {{ title|default:"SAMI · Card" }} + + {% if chart_url %}{% endif %} + + + + +
+
+
+

SAMI · {{ indicator }}

+
+ {% if beta is not None %}β {{ beta|floatformat:3 }}{% endif %} + {% if r2 is not None %}R² {{ r2|floatformat:3 }}{% endif %} + {% if n %}n = {{ n }}{% endif %} +
+ {% if chart_url %} + SAMI chart + {% endif %} +
+
+
+ + diff --git a/pxy_dashboard/templates/pxy_dashboard/share/sites_card.html b/pxy_dashboard/templates/pxy_dashboard/share/sites_card.html new file mode 100644 index 0000000..aa9f2a4 --- /dev/null +++ b/pxy_dashboard/templates/pxy_dashboard/share/sites_card.html @@ -0,0 +1,47 @@ +{% load static %} + + + + + {{ title|default:"Sites · Card" }} + + {% if main_preview_url %} + + {% endif %} + + + + +
+
+
+

{{ business }} @ {{ city }}

+
Sites · {{ created_at }}
+ + {% if main_preview_url %} + Sites preview + {% endif %} + +
+ {% if download.main %}{% endif %} + {% if download.demand %}{% endif %} + {% if download.competition %}{% endif %} +
+ +
+ {% if geojson.isochrones %}{% endif %} + {% if geojson.candidates %}{% endif %} + {% if geojson.pois_competition %}{% endif %} + {% if geojson.popgrid %}{% endif %} +
+
+
+
+ + diff --git a/pxy_dashboard/utils/share.py b/pxy_dashboard/utils/share.py new file mode 100644 index 0000000..a7d45f8 --- /dev/null +++ b/pxy_dashboard/utils/share.py @@ -0,0 +1,37 @@ +from __future__ import annotations +from typing import Any, Dict +from django.conf import settings +from django.core.signing import TimestampSigner, BadSignature, SignatureExpired, dumps, loads + +DEFAULT_TTL = int(getattr(settings, "SHARE_TTL_SECONDS", 7 * 24 * 3600)) + +def _base_url(request=None) -> str: + if request: + forwarded_proto = request.META.get("HTTP_X_FORWARDED_PROTO") + scheme = (forwarded_proto.split(",")[0].strip() if forwarded_proto else None) or ( + "https" if request.is_secure() else "http" + ) + host = request.get_host() or settings.BASE_URL.replace("https://", "").replace("http://", "") + return f"{scheme}://{host}" + return settings.BASE_URL + +def mint_sites_share_url(search_id: str, request=None, ttl: int | None = None) -> str: + payload = {"k": "sites", "sid": search_id} + token = dumps(payload) # signed + timestamped + base = _base_url(request) + return f"{base}/share/sites/{search_id}/{token}" + +def mint_sami_share_url(run_id: str, meta: Dict[str, Any], request=None, ttl: int | None = None) -> str: + # meta can include: indicator, beta, r2, n + payload = {"k": "sami", "rid": run_id, **{k: v for k, v in meta.items() if k in ("indicator","beta","r2","n")}} + token = dumps(payload) + base = _base_url(request) + return f"{base}/share/sami/{run_id}/{token}" + +def verify_token(token: str, max_age: int | None = None) -> Dict[str, Any]: + try: + return loads(token, max_age=max_age or DEFAULT_TTL) + except SignatureExpired as e: + raise + except BadSignature as e: + raise diff --git a/pxy_dashboard/views_share.py b/pxy_dashboard/views_share.py new file mode 100644 index 0000000..0796f42 --- /dev/null +++ b/pxy_dashboard/views_share.py @@ -0,0 +1,59 @@ +from __future__ import annotations +import uuid +from django.http import HttpResponse, HttpResponseForbidden, HttpResponseNotFound +from django.shortcuts import render +from django.conf import settings +from django.utils.html import escape + +from pxy_dashboard.utils.share import verify_token, DEFAULT_TTL +from pxy_sites.models import SiteRun + +# --- Sites --- +def share_sites_card(request, search_id, token): + # Django gives a UUID object when using ; normalize to str + sid = str(search_id) + try: + uuid.UUID(sid) + except Exception: + return HttpResponseNotFound("Invalid search_id") + try: + payload = verify_token(token, max_age=DEFAULT_TTL) + if payload.get("k") != "sites" or payload.get("sid") != sid: + return HttpResponseForbidden("Bad token") + except Exception: + return HttpResponseForbidden("Expired or invalid token") + + # Load run by string id + try: + run = SiteRun.objects.get(search_id=sid) + except SiteRun.DoesNotExist: + return HttpResponseNotFound("Run not found") + + # ... rest unchanged ... + + + +# --- SAMI --- +def share_sami_card(request, run_id, token): + rid = str(run_id) # normalize + try: + uuid.UUID(rid) + except Exception: + return HttpResponseNotFound("Invalid run_id") + try: + payload = verify_token(token, max_age=DEFAULT_TTL) + if payload.get("k") != "sami" or payload.get("rid") != rid: + return HttpResponseForbidden("Bad token") + except Exception: + return HttpResponseForbidden("Expired or invalid token") + + chart_url = f"{settings.MEDIA_URL}sami/sami_{rid}.png" + ctx = { + "title": f"SAMI · {payload.get('indicator', '')}", + "indicator": payload.get("indicator"), + "beta": payload.get("beta"), + "r2": payload.get("r2"), + "n": payload.get("n"), + "chart_url": chart_url, + } + return render(request, "pxy_dashboard/share/sami_card.html", ctx) diff --git a/pxy_de.zip b/pxy_de.zip new file mode 100644 index 0000000000000000000000000000000000000000..64097e01391994c6e2127ed206b5258efc3b84d0 GIT binary patch literal 20297 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 literal 0 HcmV?d00001 diff --git a/pxy_de/api.py b/pxy_de/api.py new file mode 100644 index 0000000..c9dd552 --- /dev/null +++ b/pxy_de/api.py @@ -0,0 +1,106 @@ +# pxy_de/api.py +from __future__ import annotations +from pathlib import Path +from typing import List, Dict, Any + +import pandas as pd +from django.conf import settings +from rest_framework.decorators import api_view +from rest_framework.response import Response + +from .providers.base import get_provider + + +def _rel(path: Path, base_dir: Path) -> str: + """ + Return a clean relative path like 'data/...' + """ + try: + return str(path.relative_to(settings.BASE_DIR)) + except Exception: + # Fallback: show relative to provider base_dir + try: + return str(Path("data") / path.relative_to(base_dir)) + except Exception: + return str(path) + + +def _probe_csv(path: Path) -> Dict[str, Any]: + """ + Lightweight readability probe: existence + sample columns (no full read). + """ + info: Dict[str, Any] = {"exists": path.exists()} + if not info["exists"]: + return info + try: + sample = pd.read_csv(path, nrows=5) + info["columns"] = list(sample.columns) + info["sample_rows"] = int(sample.shape[0]) # up to 5 + except Exception as e: + info["error"] = f"{type(e).__name__}: {e}" + return info + + +@api_view(["GET"]) +def de_health(request): + """ + GET /api/de/health?city=CDMX&business=cafe&indicator=imss_wages_2023 + + Reports: + - provider in use + - base_dir used by the provider + - required/missing files (population.csv always; others if params passed) + - lightweight probes for each checked file (exists, columns, sample_rows) + """ + provider = get_provider() + base_dir: Path = getattr(provider, "base_dir", Path(settings.BASE_DIR) / "data") + checks: List[Dict[str, Any]] = [] + missing: List[str] = [] + + city = (request.query_params.get("city") or "").strip() + business = (request.query_params.get("business") or "").strip() + indicator = (request.query_params.get("indicator") or "").strip() + + # Always check SAMI population + pop_path = base_dir / "sami" / "population.csv" + pop_probe = _probe_csv(pop_path) + pop_probe["path"] = _rel(pop_path, base_dir) + checks.append(pop_probe) + if not pop_probe["exists"]: + missing.append(pop_probe["path"]) + + # Optional: indicator for SAMI + if indicator: + ind_path = base_dir / "sami" / f"{indicator}.csv" + ind_probe = _probe_csv(ind_path) + ind_probe["path"] = _rel(ind_path, base_dir) + checks.append(ind_probe) + if not ind_probe["exists"]: + missing.append(ind_probe["path"]) + + # Optional: Sites (competition / DENUE) + if city and business: + denue_path = base_dir / "denue" / f"{city}_{business}.csv" + denue_probe = _probe_csv(denue_path) + denue_probe["path"] = _rel(denue_path, base_dir) + checks.append(denue_probe) + if not denue_probe["exists"]: + missing.append(denue_probe["path"]) + + # Optional: Sites (demand / population grid) + if city: + grid_path = base_dir / "popgrid" / f"{city}_grid.csv" + grid_probe = _probe_csv(grid_path) + grid_probe["path"] = _rel(grid_path, base_dir) + checks.append(grid_probe) + if not grid_probe["exists"]: + missing.append(grid_probe["path"]) + + ok = len(missing) == 0 + return Response({ + "provider": "csv-data", + "base_dir": str(base_dir), + "ok": ok, + "missing": missing, + "files": checks, + }) diff --git a/pxy_de/providers/__init__.py b/pxy_de/providers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pxy_de/providers/base.py b/pxy_de/providers/base.py new file mode 100644 index 0000000..f4e8b72 --- /dev/null +++ b/pxy_de/providers/base.py @@ -0,0 +1,72 @@ +from __future__ import annotations +from abc import ABC, abstractmethod +from functools import lru_cache +from typing import List, Dict, Any +import os + +try: + import pandas as pd # type: ignore +except Exception: # pragma: no cover + pd = None # for type hints only + + +class DataProvider(ABC): + """ + Abstract provider interface for data access used across modules (SAMI, Sites, etc.). + Implementations must live under pxy_de.providers.* and implement these methods. + """ + + # ---------- Common ---------- + @abstractmethod + def health(self) -> Dict[str, Any]: + ... + + # ---------- SAMI ---------- + @abstractmethod + def indicator(self, indicator: str, cities: List[str]) -> "pd.DataFrame": + """ + Return columns: city, value, N (N = population or scale variable) + """ + ... + + # ---------- Sites: competition (POIs) ---------- + @abstractmethod + def denue(self, city: str, business: str) -> "pd.DataFrame": + """ + Return columns: name, lat, lon, category + """ + ... + + # ---------- Sites: demand (population grid) ---------- + @abstractmethod + def popgrid(self, city: str) -> "pd.DataFrame": + """ + Return columns: cell_id, lat, lon, pop + """ + ... + + # ---------- Optional: city boundary (GeoJSON-like) ---------- + @abstractmethod + def city_boundary(self, city: str) -> Dict[str, Any]: + """ + Return a GeoJSON-like dict for city boundary, or {} if not available. + """ + ... + + +@lru_cache(maxsize=1) +def get_provider() -> DataProvider: + """ + Factory for data providers. Choose via env: + DATA_PROVIDER = csv (default) | + """ + name = os.getenv("DATA_PROVIDER", "csv").strip().lower() + if name == "csv": + from .csv_provider import CsvDataProvider + return CsvDataProvider() + # Add more providers here in the future: + # elif name == "postgres": from .pg_provider import PgDataProvider; return PgDataProvider(...) + # elif name == "bigquery": ... + # Fallback + from .csv_provider import CsvDataProvider + return CsvDataProvider() diff --git a/pxy_de/providers/csv_provider.py b/pxy_de/providers/csv_provider.py new file mode 100644 index 0000000..cf3b7b7 --- /dev/null +++ b/pxy_de/providers/csv_provider.py @@ -0,0 +1,117 @@ +from __future__ import annotations +from pathlib import Path +from typing import List, Dict, Any +import pandas as pd +from django.conf import settings + +from .base import DataProvider + + +class CsvDataProvider(DataProvider): + """ + Simple provider reading local CSVs under BASE_DIR/data/. + + Expected layout (current): + SAMI: + data/sami/population.csv -> cols: city, N + data/sami/{indicator}.csv -> cols: city, value + + Sites (competition POIs - DENUE-like): + data/denue/{city}_{business}.csv -> cols: name, lat, lon, category (name/category optional) + + Sites (demand pop grid): + data/popgrid/{city}_grid.csv -> cols: cell_id, lat, lon, pop + """ + + def __init__(self, base_dir: str | Path | None = None): + self.base_dir = Path(base_dir) if base_dir else Path(settings.BASE_DIR) / "data" + + def _exists(self, *parts: str) -> bool: + return (self.base_dir.joinpath(*parts)).exists() + + # ---------- Common ---------- + def health(self) -> Dict[str, Any]: + missing = [] + # We can only check basics here. + if not self._exists("sami", "population.csv"): + missing.append("data/sami/population.csv") + ok = len(missing) == 0 + return { + "provider": "csv-data", + "ok": ok, + "base_dir": str(self.base_dir), + "missing": missing, + } + + # ---------- SAMI ---------- + def indicator(self, indicator: str, cities: List[str]) -> pd.DataFrame: + pop_path = self.base_dir / "sami" / "population.csv" + ind_path = self.base_dir / "sami" / f"{indicator}.csv" + pop = pd.read_csv(pop_path) # cols: city, N + ind = pd.read_csv(ind_path) # cols: city, value + df = pd.merge(ind, pop, on="city", how="inner") + if cities: + df = df[df["city"].isin(cities)].copy() + # Ensure numeric + df["value"] = pd.to_numeric(df["value"], errors="coerce") + df["N"] = pd.to_numeric(df["N"], errors="coerce") + df = df.dropna(subset=["value", "N"]) + return df[["city", "value", "N"]] + + # ---------- Sites: competition (POIs) ---------- + def denue(self, city: str, business: str) -> pd.DataFrame: + """ + Reads POIs from data/denue/{city}_{business}.csv + Expected columns: + - lat (float), lon (float) + - name (str, optional), category (str, optional) + """ + path = self.base_dir / "denue" / f"{city}_{business}.csv" + if not path.exists(): + return pd.DataFrame(columns=["name", "lat", "lon", "category"]) + df = pd.read_csv(path) + # minimal columns + if "lat" not in df.columns or "lon" not in df.columns: + return pd.DataFrame(columns=["name", "lat", "lon", "category"]) + # quick cleaning + df["lat"] = pd.to_numeric(df["lat"], errors="coerce") + df["lon"] = pd.to_numeric(df["lon"], errors="coerce") + df = df.dropna(subset=["lat", "lon"]).copy() + if "name" not in df.columns: + df["name"] = None + if "category" not in df.columns: + df["category"] = business + return df[["name", "lat", "lon", "category"]] + + # ---------- Sites: demand (population grid) ---------- + def popgrid(self, city: str) -> pd.DataFrame: + """ + Loads population grid points from data/popgrid/{city}_grid.csv + Required columns: lat, lon, pop + Optional: cell_id + """ + path = self.base_dir / "popgrid" / f"{city}_grid.csv" + if not path.exists(): + return pd.DataFrame(columns=["cell_id", "lat", "lon", "pop"]) + df = pd.read_csv(path) + for col in ["lat", "lon", "pop"]: + if col not in df.columns: + return pd.DataFrame(columns=["cell_id", "lat", "lon", "pop"]) + # numeric & drop invalid + df["lat"] = pd.to_numeric(df["lat"], errors="coerce") + df["lon"] = pd.to_numeric(df["lon"], errors="coerce") + df["pop"] = pd.to_numeric(df["pop"], errors="coerce") + df = df.dropna(subset=["lat", "lon", "pop"]).copy() + if "cell_id" not in df.columns: + df["cell_id"] = None + return df[["cell_id", "lat", "lon", "pop"]] + + # ---------- Optional: city boundary ---------- + def city_boundary(self, city: str) -> Dict[str, Any]: + # Not implemented yet; return empty dict. + return {} + + # ---------- Backwards compatibility alias ---------- + # Some earlier code used "grid(city)" for population grid. + def grid(self, city: str) -> pd.DataFrame: + return self.popgrid(city) diff --git a/pxy_de/urls.py b/pxy_de/urls.py new file mode 100644 index 0000000..5331c26 --- /dev/null +++ b/pxy_de/urls.py @@ -0,0 +1,6 @@ +from django.urls import path +from pxy_de import api as de_api + +urlpatterns = [ + path("api/de/health", de_api.de_health, name="de_health"), +] diff --git a/pxy_meta_pages/admin.py b/pxy_meta_pages/admin.py index d9cf8e5..f98ae7c 100644 --- a/pxy_meta_pages/admin.py +++ b/pxy_meta_pages/admin.py @@ -1,4 +1,9 @@ +# pxy_meta_pages/admin.py +from __future__ import annotations + import json +from typing import Optional, Dict, Any + import requests from django.conf import settings from django.contrib import admin, messages @@ -6,7 +11,14 @@ from django.contrib import admin, messages from .models import FacebookPageAssistant, EventType, BotInteraction from .services import FacebookService -# Required fields we want on every Page + +# ----------------------------------------------------------------------------- +# Config +# ----------------------------------------------------------------------------- +FACEBOOK_API_VERSION: str = getattr(settings, "FACEBOOK_API_VERSION", "v22.0") +APP_ID: Optional[str] = getattr(settings, "FACEBOOK_APP_ID", None) + +# Fields we require on every Page subscription (Page Feed + Messenger) REQUIRED_FIELDS = [ # Page feed (comments/shares/mentions) "feed", @@ -20,32 +32,67 @@ REQUIRED_FIELDS = [ "message_echoes", ] -APP_ID = getattr(settings, "FACEBOOK_APP_ID", None) # optional (nice-to-have for filtering) - -def _graph_get(url, params): - r = requests.get(url, params=params, timeout=15) - # Graph often returns 200 even for failures with {"error":{...}} - data = r.json() if r.content else {} - if "error" in data: +# ----------------------------------------------------------------------------- +# Small Graph helpers with consistent error handling +# ----------------------------------------------------------------------------- +def _graph_get(url: str, params: Dict[str, Any]) -> Dict[str, Any]: + """GET wrapper that raises RequestException on Graph errors.""" + resp = requests.get(url, params=params, timeout=15) + data = resp.json() if resp.content else {} + if isinstance(data, dict) and "error" in data: + # Normalize to RequestException so callers can unify handling raise requests.RequestException(json.dumps(data["error"])) - r.raise_for_status() - return data + resp.raise_for_status() + return data or {} -def _graph_post(url, data): - r = requests.post(url, data=data, timeout=15) - data = r.json() if r.content else {} - if "error" in data: - raise requests.RequestException(json.dumps(data["error"])) - r.raise_for_status() - return data +def _graph_post(url: str, data: Dict[str, Any]) -> Dict[str, Any]: + """POST wrapper that raises RequestException on Graph errors.""" + resp = requests.post(url, data=data, timeout=15) + payload = resp.json() if resp.content else {} + if isinstance(payload, dict) and "error" in payload: + raise requests.RequestException(json.dumps(payload["error"])) + resp.raise_for_status() + return payload or {} +def _decode_graph_error(e: requests.RequestException) -> str: + """ + Attempt to pretty-print a Graph API error dict, else return the raw message. + """ + msg = str(e) + try: + err = json.loads(msg) + # Typical Graph error shape + code = err.get("code") + sub = err.get("error_subcode") + text = err.get("message", "Graph error") + return f"Graph error (code={code}, subcode={sub}): {text}" + except Exception: + return msg + + +def _get_page_token(fb_service: FacebookService, page_id: str) -> Optional[str]: + """ + Works with either a public get_page_access_token or the private _get_page_access_token. + """ + getter = getattr(fb_service, "get_page_access_token", None) + if callable(getter): + return getter(page_id) + private_getter = getattr(fb_service, "_get_page_access_token", None) + if callable(private_getter): + return private_getter(page_id) + return None + + +# ----------------------------------------------------------------------------- +# Admins +# ----------------------------------------------------------------------------- @admin.register(FacebookPageAssistant) class FacebookPageAssistantAdmin(admin.ModelAdmin): """ - Admin for wiring a Facebook Page to your assistant and managing webhook subs. + Admin for wiring a Facebook Page to an OpenAI assistant and managing webhook subscriptions. """ list_display = ( "page_name", @@ -65,75 +112,63 @@ class FacebookPageAssistantAdmin(admin.ModelAdmin): "probe_messenger_access", ] - # ----- small counters ----- - def comment_count(self, obj): + # ----- Counters ---------------------------------------------------------- + def comment_count(self, obj: FacebookPageAssistant) -> int: return obj.events.filter(event_type__code="comment").count() + comment_count.short_description = "Comments" - def share_count(self, obj): + def share_count(self, obj: FacebookPageAssistant) -> int: return obj.events.filter(event_type__code="share").count() + share_count.short_description = "Shares" - # ===================================================================== - # ACTION 1: Ensure required fields are subscribed (feed + Messenger) - # ===================================================================== + # ----- Action 1: Ensure required fields (feed + Messenger) -------------- def ensure_feed_and_messenger_subscription(self, request, queryset): """ For each selected Page: - - fetch Page Access Token with FacebookService - - read current subscribed_fields - - add any missing REQUIRED_FIELDS + 1) Fetch the Page Access Token via FacebookService. + 2) Read current subscribed_fields. + 3) Add any missing REQUIRED_FIELDS in a single POST. """ fb_service = FacebookService(user_access_token=settings.PAGE_ACCESS_TOKEN) for page in queryset: try: - # 1) token - page_token = getattr(fb_service, "get_page_access_token", None) - if callable(page_token): - page_access_token = page_token(page.page_id) - else: - # fallback to private method name in case your svc only exposes _get_page_access_token - page_access_token = fb_service._get_page_access_token(page.page_id) # noqa - + page_access_token = _get_page_token(fb_service, page.page_id) if not page_access_token: self.message_user( request, - f"[{page.page_name}] Unable to get Page Access Token.", + f"[{page.page_name}] Unable to obtain Page Access Token.", level=messages.ERROR, ) continue - # 2) read existing - url_list = f"https://graph.facebook.com/v22.0/{page.page_id}/subscribed_apps" - data = _graph_get(url_list, {"access_token": page_access_token}) or {} - entries = data.get("data", []) + list_url = f"https://graph.facebook.com/{FACEBOOK_API_VERSION}/{page.page_id}/subscribed_apps" + current_data = _graph_get(list_url, {"access_token": page_access_token}) + entries = current_data.get("data", []) - # pick this app's entry (if APP_ID known), else first entry if any + # If APP_ID is known, narrow to our app row; otherwise use first row if present app_entry = None if APP_ID: app_entry = next((e for e in entries if str(e.get("id")) == str(APP_ID)), None) if app_entry is None and entries: app_entry = entries[0] - current = set(app_entry.get("subscribed_fields", [])) if app_entry else set() + current_fields = set(app_entry.get("subscribed_fields", [])) if app_entry else set() required = set(REQUIRED_FIELDS) - union_fields = sorted(current | required) - # 3) update only if needed - if required - current: - _graph_post( - f"https://graph.facebook.com/v22.0/{page.page_id}/subscribed_apps", - { - "subscribed_fields": ",".join(union_fields), - "access_token": page_access_token, - }, - ) + if required - current_fields: + new_fields_csv = ",".join(sorted(current_fields | required)) + _graph_post(list_url, { + "subscribed_fields": new_fields_csv, + "access_token": page_access_token, + }) page.is_subscribed = True page.save(update_fields=["is_subscribed"]) self.message_user( request, - f"[{page.page_name}] Subscribed/updated. Fields now include: {', '.join(union_fields)}", + f"[{page.page_name}] Subscribed/updated. Now includes: {new_fields_csv}", level=messages.SUCCESS, ) else: @@ -141,65 +176,45 @@ class FacebookPageAssistantAdmin(admin.ModelAdmin): page.save(update_fields=["is_subscribed"]) self.message_user( request, - f"[{page.page_name}] Already has all required fields: {', '.join(sorted(current))}", + f"[{page.page_name}] Already has required fields.", level=messages.INFO, ) except requests.RequestException as e: - # try to decode Graph error for clarity - msg = str(e) - try: - err = json.loads(msg) - code = err.get("code") - sub = err.get("error_subcode") - err_msg = err.get("message", "Graph error") - self.message_user( - request, - f"[{page.page_name}] Graph error (code={code}, subcode={sub}): {err_msg}", - level=messages.ERROR, - ) - except Exception: - self.message_user( - request, - f"[{page.page_name}] Subscription failed: {msg}", - level=messages.ERROR, - ) - + self.message_user( + request, + f"[{page.page_name}] {_decode_graph_error(e)}", + level=messages.ERROR, + ) except Exception as e: self.message_user( - request, f"[{page.page_name}] Unexpected error: {e}", level=messages.ERROR + request, + f"[{page.page_name}] Unexpected error: {e}", + level=messages.ERROR, ) - ensure_feed_and_messenger_subscription.short_description = "Ensure Webhooks (feed + Messenger) on selected Pages" + ensure_feed_and_messenger_subscription.short_description = "Ensure Webhooks (feed + Messenger)" - # ===================================================================== - # ACTION 2: Check status (show exact fields) - # ===================================================================== + # ----- Action 2: Check subscription status ------------------------------ def check_subscription_status(self, request, queryset): """ - Shows the actual subscribed_fields for each Page. + Shows the exact subscribed_fields currently active for each Page. """ fb_service = FacebookService(user_access_token=settings.PAGE_ACCESS_TOKEN) for page in queryset: try: - # token - page_token = getattr(fb_service, "get_page_access_token", None) - if callable(page_token): - page_access_token = page_token(page.page_id) - else: - page_access_token = fb_service._get_page_access_token(page.page_id) # noqa - + page_access_token = _get_page_token(fb_service, page.page_id) if not page_access_token: self.message_user( request, - f"[{page.page_name}] Unable to get Page Access Token.", + f"[{page.page_name}] Unable to obtain Page Access Token.", level=messages.ERROR, ) continue - url = f"https://graph.facebook.com/v22.0/{page.page_id}/subscribed_apps" - data = _graph_get(url, {"access_token": page_access_token}) or {} + url = f"https://graph.facebook.com/{FACEBOOK_API_VERSION}/{page.page_id}/subscribed_apps" + data = _graph_get(url, {"access_token": page_access_token}) entries = data.get("data", []) app_entry = None @@ -209,66 +224,68 @@ class FacebookPageAssistantAdmin(admin.ModelAdmin): app_entry = entries[0] fields = app_entry.get("subscribed_fields", []) if app_entry else [] - has_required = set(REQUIRED_FIELDS).issubset(set(fields)) page.is_subscribed = bool(fields) page.save(update_fields=["is_subscribed"]) - level = messages.SUCCESS if has_required else messages.WARNING + has_all = set(REQUIRED_FIELDS).issubset(set(fields)) + level = messages.SUCCESS if has_all else messages.WARNING self.message_user( request, - f"[{page.page_name}] Subscribed fields: {', '.join(fields) or '(none)'}", + f"[{page.page_name}] Subscribed fields: {', '.join(fields) if fields else '(none)'}", level=level, ) except requests.RequestException as e: self.message_user( - request, f"[{page.page_name}] Check failed: {e}", level=messages.ERROR + request, + f"[{page.page_name}] {_decode_graph_error(e)}", + level=messages.ERROR, + ) + except Exception as e: + self.message_user( + request, + f"[{page.page_name}] Unexpected error: {e}", + level=messages.ERROR, ) - check_subscription_status.short_description = "Check webhook subscription fields on selected Pages" + check_subscription_status.short_description = "Check webhook subscription fields" - # ===================================================================== - # ACTION 3: Probe Messenger access (lightweight) - # ===================================================================== + # ----- Action 3: Probe Messenger access --------------------------------- def probe_messenger_access(self, request, queryset): """ - Tries /{PAGE_ID}/conversations to confirm Messenger perms are usable. - (If app is in Dev Mode, only app roles will appear here.) + Light probe for Messenger perms using /{PAGE_ID}/conversations. + (In Dev Mode, you’ll only see app-role users here.) """ fb_service = FacebookService(user_access_token=settings.PAGE_ACCESS_TOKEN) for page in queryset: try: - page_token = getattr(fb_service, "get_page_access_token", None) - if callable(page_token): - page_access_token = page_token(page.page_id) - else: - page_access_token = fb_service._get_page_access_token(page.page_id) # noqa - + page_access_token = _get_page_token(fb_service, page.page_id) if not page_access_token: self.message_user( request, - f"[{page.page_name}] Unable to get Page Access Token.", + f"[{page.page_name}] Unable to obtain Page Access Token.", level=messages.ERROR, ) continue - url = f"https://graph.facebook.com/v22.0/{page.page_id}/conversations" + url = f"https://graph.facebook.com/{FACEBOOK_API_VERSION}/{page.page_id}/conversations" data = _graph_get(url, {"access_token": page_access_token, "limit": 1}) total = len(data.get("data", [])) self.message_user( request, - f"[{page.page_name}] Messenger probe OK. Conversations sample: {total}. " - "Note: in Dev Mode you’ll only see app-role users here.", + ( + f"[{page.page_name}] Messenger probe OK. Conversations sample: {total}. " + "Note: in Dev Mode you’ll only see app-role users." + ), level=messages.SUCCESS, ) except requests.RequestException as e: - # common Graph codes for perms/token issues: - # 190 invalid/expired token, 200 permissions error, 10 permission denied - msg = str(e) + msg = _decode_graph_error(e) + # Add quick hints for common codes hint = "" - if any(x in msg for x in ('"code": 190', "Invalid OAuth 2.0")): + if '"code": 190' in msg or "Invalid OAuth 2.0" in msg: hint = " (Token invalid/expired)" elif '"code": 200' in msg: hint = " (Permissions error: check pages_messaging & pages_manage_metadata; app roles or Advanced Access)" @@ -279,8 +296,14 @@ class FacebookPageAssistantAdmin(admin.ModelAdmin): f"[{page.page_name}] Messenger probe failed: {msg}{hint}", level=messages.ERROR, ) + except Exception as e: + self.message_user( + request, + f"[{page.page_name}] Unexpected error: {e}", + level=messages.ERROR, + ) - probe_messenger_access.short_description = "Probe Messenger access on selected Pages" + probe_messenger_access.short_description = "Probe Messenger access" @admin.register(EventType) diff --git a/pxy_routing.zip b/pxy_routing.zip new file mode 100644 index 0000000000000000000000000000000000000000..4e4b3e282c5d14d413a8720ed7ef851049a505ad GIT binary patch literal 5580 zcmb7I2|UyNAD?5cA-TImj>$QS+T5w(a-~5uD_X?vVj=?@p^_9DQ`jpr}4i?ouTANJQPT-_IMA>U-qt> zLR#uKu0sOnH%KNB6R3{@Qk)v(UwEhU4(GgVobgT`7?eF;#@TB#;toV~IFUGT2AU4y zx({*qE}V#H4Uc@a|6Z0>{2?uA>BKH`>{hMgDC~Qe(l+nL0jmo7D-9a6&vwstK-Go( z>k?sS#}SP5UUv_uRP1wXuY{RuCG+0TXUpE(RJv#65Zb^{Nq2xD@Vra6-c)DZ1;KrZ zPh8au-j)_|i#babLdMLK8%?`gIzI0iW7@mr@?XPIy7k?PIAd|n8+l%A0fHuVmM;&u z`Q&Dgo2nFfIZPZKLw})tYr`MZwiKVb(#ndVxg35WE~^QQGUY^-sl@&-%(emSs8P_G z%Laz+No=_9WPDuAH6|xkepkJAh+f`V9bQFb#hdBI$hDcOSQZH+{kxN;oqBCKEDD*! zbaC&R(!t9U9q7^;_PU)AG4nH%UHeOO!}t}`YMC*opU}+q)%4KWo2I2R#ow9fo?YpD z8k-8UR#v}Qw%xTzwCL>g6&g`9)2!zcTzWc%CrxG(2FwZ7>l@JM5tm zF=E?4!ykwrG0V3Wp_|qhjvKy%2`ErtyIFrmz2A9@R@x}`R<^pd!9Rqqo`O5h%u&U? zZ~ZLH5)w+~!f8?n%qV>Z2!mybXoo)Edx2x`SD6jGh7dv%-pd-EKc?@x|(5TZUMKT@)v(muEde;w6RU4_16gbem0lx&wc`)_KzU?v0`($=(P7WRH=pPlfLjvHDk` zC$1>mj(M%8>b|U>SmS+sA#6|;;YZ6>a-z99zmn@s#&l?llR0-`*VE};QWpmN{Up*t zStGZOsUN7;EtWMjQjELtc6aG?5Bt}uAZ!0%@nt@k{zHfCa;*^&OOM5!?Kw}!zfNj! zT0MVu!_!6Iwl!wgf?HRrep&2DAtL?``5f!T?Su$5Hd4m>)k?S6(A4&g7TgV{Z8o zp{IKuibX4yiInMWU90LI@(Zz^w`FHWTEF(06B9EAKhMHcGFP6yTzH0sb->52E#ZQG zt3X}Vz~bkUW$utX!Ngiu6O~q5l(3^S7Sdn5ETelgS?Aqw23>2Uuyod!lOnpqZV|Ao zpg_|mYi#JP^C*KkdQ?2xHeldmHecs;?#0ByI#oQ{jCU9AaU8-Vj#ssZvAn(;8CU>s z^A^0ybpggN0%hBg_mck8%NCSzx+CjBH))iy#T(eOodx4I$sMb$ED8^XEK4Vbgw$Ha zMjn-&()VAS)Y^8S-4~K=!m(WFfy)Yi|DajSj#+X)H^=L9FQX@pR7Jj>ca~34PcvQR zbeKr?JfX8KKHw7pZl8{pJRrwuKP8bK^=?q!<*}gCjOFU_JoQzf*Xh{9{9JENe9F3` zX5hs|8ib2Ol_GjBu|chqxymN#6KEl#Qo{^J zD+-9oL57{@2mJ-E3D7%Sym_M96~|D5F$;Ay9&=PtEV7#`!7p7? zvk6lgZ@$Q7xc1Lk!U_Wrue4N8gcdPgSIFX(zmo zdPO`ive}p)rpf|gZSEL50v~U=RDR4Wq;1~HweNdTV9wQYu0OpzvzSOBAQyI zn`9-9H?PpKT|IUnwrZkl++~h%J9M6q#1Q#5^BQXaL=RR~Mj7J_3j`&=;&JB94 zZ%LWROZAVqd0_0%|1r0AqQ7)yr1zQ=34-K#!Vq%^M0h0yy%u?s=i#ITSyoIMfrxHI zmG}Wf8C5=ttixg)@klp}6K-?;1|%FH34xP(s_dZ-`X9}QG_mQ{{S1&M*3J4u-6G{R ze!~nP5CL=4%m5ZCM3`fEy;2BTpv3%j?vdv5BV6ir(c`>IIkL(;x55&!dsZ+%LXgR zTrw>_2B$%r?vUKeH}jG9B`(Y8+4#%6R)tS5!Jkanp5bh8;k^COsW$c@kBM_@$LwN! zjFHN!I&=Ap8Eqbc2Y|vXb1C!mA^N9}_}U1{EXDo!H}Tf53LyUb1%Wfmv=%oAW35?| zRQg~?lNUoT38Qbi3lGKy7d3fH(rTAY98Q(?;!G9g|MVf#PQ~SJ`E!_!RP2R!Bf_I~ z{Vb&g=vBx96{Jo|O($y*XeN^ieeFGVS=_erDsfc)5S#(V4Gdm*Ur_6RD+e-> z(anOv@>C~lUsxEO_I<^m`i4%~k*-F2|4Ea-2`fyh#fBr}PS5%X7oim@qIX7%?jcH= zkeOZ1U(^QYQ_QOBFN^CFg2M7EN;q`TQ9ANcE@=_#F#JyR-NJdlcMrYE(M6?i(cqoqw zb_i2)?K1dw(L(S?M8w-xc?%Xr_pY$V90IQ|8OQo-ony*bxz1XVpl;?KYLofjP`PSKmFVjzIZwsx+WK$^StWW)gstgHrW&7lgsPF)*7l^sz7WQ znPgg*vh8vGl$y@J)0WlR)Yyq-2Y#!TGs}At4-yHw+z?TD2<6NP7A=Y2omZnKeZtNmc6&Q-U|EVbfDhEio3pEbu_fxbkufx ze&Szzd)738^Ou`G7)0}J!{0#sdB-P3)T2P8zVQ>Gzis;8pnvT8r2B|CXal None: + self._geod = Geod(ellps="WGS84") + # Puedes ajustar la velocidad por entorno (urbana 25–35 km/h es razonable) + self._speed_kmh = float(os.getenv("ROUTING_CROWFLY_SPEED_KMH", "30")) + + def health(self) -> Dict[str, Any]: + return {"provider": "crowfly", "ok": True, "speed_kmh": self._speed_kmh} + + def isochrone(self, center: LatLon, minutes: int) -> Dict[str, Any]: + lat, lon = float(center[0]), float(center[1]) + # distancia en metros + km = self._speed_kmh * (float(minutes) / 60.0) + dist_m = km * 1000.0 + + # polígono aproximado (64 segmentos) + coords: List[Tuple[float, float]] = [] + for b in range(0, 360, 360 // 64): + lon2, lat2, _ = self._geod.fwd(lon, lat, b, dist_m) + coords.append((lon2, lat2)) + coords.append(coords[0]) # cerrar + + # GeoJSON-like + return { + "type": "Feature", + "properties": {"minutes": minutes, "speed_kmh": self._speed_kmh}, + "geometry": { + "type": "Polygon", + "coordinates": [coords], # lon,lat + }, + } diff --git a/pxy_routing/services/factory.py b/pxy_routing/services/factory.py new file mode 100644 index 0000000..3a6a315 --- /dev/null +++ b/pxy_routing/services/factory.py @@ -0,0 +1,22 @@ +# pxy_routing/services/factory.py +from __future__ import annotations +import os +from functools import lru_cache + +from .crowfly_provider import CrowFlyRoutingProvider +from .ors_provider import ORSRoutingProvider + +@lru_cache(maxsize=1) +def get_routing_provider(): + """ + Select routing provider by env: + ROUTING_PROVIDER = ors | crowfly (default: crowfly) + """ + name = (os.getenv("ROUTING_PROVIDER") or "crowfly").strip().lower() + + if name == "ors": + # ORS_* knobs are read inside ORSRoutingProvider + return ORSRoutingProvider() + + # Fallback/default + return CrowFlyRoutingProvider() diff --git a/pxy_routing/services/ors_provider.py b/pxy_routing/services/ors_provider.py new file mode 100644 index 0000000..10929ba --- /dev/null +++ b/pxy_routing/services/ors_provider.py @@ -0,0 +1,204 @@ +# pxy_routing/services/ors_provider.py +from __future__ import annotations +from typing import Any, Dict, Iterable, List, Tuple +import math, os, time, random, requests + +# Optional graceful fallback +try: + from .crowfly_provider import CrowFlyRoutingProvider +except Exception: + CrowFlyRoutingProvider = None # fallback disabled if not available + +LatLon = Tuple[float, float] # (lat, lon) + +class ORSRoutingProvider: + """ + ORS isochrones with retries/backoff and optional crow-fly fallback. + + Env: + ORS_BASE_URL e.g., https://api.openrouteservice.org + ORS_API_KEY key (omit/blank for self-host) + ORS_PROFILE driving-car | cycling-regular | foot-walking (default: driving-car) + ORS_TIMEOUT_S request timeout seconds (default: 5) + ORS_GENERALIZE generalization in meters for polygons (optional, e.g., 20) + ORS_MAX_RANGE safety cap in minutes (optional; e.g., 45) + + # New hardening knobs: + ORS_RETRY number of retries on 429/5xx (default: 2) + ORS_BACKOFF_BASE_S base seconds for exponential backoff (default: 0.8) + ORS_FALLBACK set to "crowfly" to degrade gracefully on errors + """ + def __init__( + self, + base_url: str | None = None, + api_key: str | None = None, + profile: str = "driving-car", + timeout_s: int = 5, + generalize: int | None = None, + max_range_min: int | None = None, + ): + self.base_url = (base_url or os.getenv("ORS_BASE_URL") or "").rstrip("/") + self.api_key = api_key if api_key is not None else os.getenv("ORS_API_KEY", "") + self.profile = os.getenv("ORS_PROFILE", profile) + self.timeout_s = int(os.getenv("ORS_TIMEOUT_S", str(timeout_s))) + gen = os.getenv("ORS_GENERALIZE") + self.generalize = int(gen) if (gen and gen.isdigit()) else generalize + mr = os.getenv("ORS_MAX_RANGE") + self.max_range_min = int(mr) if (mr and mr.isdigit()) else max_range_min + + # Hardening knobs + self.retries = int(os.getenv("ORS_RETRY", "2")) + self.backoff_base = float(os.getenv("ORS_BACKOFF_BASE_S", "0.8")) + self.fallback_mode = (os.getenv("ORS_FALLBACK") or "").strip().lower() + self._fallback = None + if self.fallback_mode == "crowfly" and CrowFlyRoutingProvider: + self._fallback = CrowFlyRoutingProvider() + + if not self.base_url: + raise ValueError("ORS_BASE_URL is required for ORSRoutingProvider") + + self._iso_url = f"{self.base_url}/v2/isochrones/{self.profile}" + self._headers = { + "Content-Type": "application/json; charset=utf-8", + "Accept": "application/json, application/geo+json", + } + if self.api_key: + self._headers["Authorization"] = self.api_key + + # ---------- internals ---------- + def _post(self, url: str, payload: Dict[str, Any]) -> requests.Response: + attempts = 1 + max(0, self.retries) + r = None + for i in range(attempts): + r = requests.post(url, json=payload, headers=self._headers, timeout=self.timeout_s) + if r.status_code in (429, 502, 503, 504) and i < attempts - 1: + delay = self.backoff_base * (2 ** i) * (0.75 + 0.5 * random.random()) + time.sleep(delay) + continue + return r + return r # type: ignore + + # ---------- public API ---------- + def health(self) -> Dict[str, Any]: + try: + lat, lon = 19.4326, -99.1332 + payload = {"locations": [[lon, lat]], "range": [60]} + if self.generalize: + payload["generalize"] = self.generalize + r = self._post(self._iso_url, payload) + ok = (r.status_code == 200) + return {"provider": "ors", "ok": ok, "profile": self.profile, + "base_url": self.base_url, "reason": None if ok else f"http {r.status_code}"} + except Exception as e: + return {"provider": "ors", "ok": False, "profile": self.profile, + "base_url": self.base_url, "reason": f"{type(e).__name__}: {e}"} + + def isochrone(self, center: LatLon, minutes: int) -> Dict[str, Any]: + if self.max_range_min and minutes > self.max_range_min: + raise ValueError(f"minutes exceeds ORS_MAX_RANGE ({minutes} > {self.max_range_min})") + + lat, lon = center + payload = {"locations": [[lon, lat]], "range": [int(minutes) * 60]} + if self.generalize: + payload["generalize"] = self.generalize + + r = self._post(self._iso_url, payload) + if r.status_code != 200: + if self._fallback is not None: + feat = self._fallback.isochrone(center, minutes) + feat["properties"]["provider"] = "ors_fallback_crowfly" + return feat + hint = { + 400: "Bad request (profile/range/params).", + 401: "Unauthorized (check ORS_API_KEY).", + 403: "Forbidden (quota/key).", + 404: "Profile/endpoint not found.", + 413: "Payload too large.", + 422: "Unprocessable (non-routable location or bad range).", + 429: "Rate limited.", + 500: "Server error.", + 502: "Bad gateway.", + 503: "Service unavailable.", + 504: "Gateway timeout.", + }.get(r.status_code, "Unexpected error.") + raise RuntimeError(f"ORS isochrone error: HTTP {r.status_code}. {hint}") + + data = r.json() + geom = _largest_polygon_geometry_from_ors(data) + if not geom: + if self._fallback is not None: + feat = self._fallback.isochrone(center, minutes) + feat["properties"]["provider"] = "ors_empty_fallback_crowfly" + return feat + raise RuntimeError("ORS returned no polygon geometry.") + + return { + "type": "Feature", + "properties": {"provider": "ors", "profile": self.profile, "minutes": minutes, "center": [lon, lat]}, + "geometry": geom, + } + + # Batch multiple ranges in one call (reduces rate-limit pressure) + def isochrones(self, center: LatLon, minutes_list: List[int]) -> List[Dict[str, Any]]: + lat, lon = center + secs = [int(m) * 60 for m in minutes_list] + payload = {"locations": [[lon, lat]], "range": secs} + if self.generalize: + payload["generalize"] = self.generalize + r = self._post(self._iso_url, payload) + if r.status_code != 200: + # degrade by single-calls if fallback exists + if self._fallback is not None: + return [self.isochrone(center, m) for m in minutes_list] + raise RuntimeError(f"ORS isochrones error: HTTP {r.status_code}") + data = r.json() + feats: List[Dict[str, Any]] = [] + for feat in (data.get("features") or []): + geom = _largest_polygon_geometry_from_ors({"features": [feat]}) + if not geom: + continue + feats.append({ + "type": "Feature", + "properties": {"provider": "ors", "profile": self.profile}, + "geometry": geom + }) + return feats + +# ---------- helpers ---------- +def _largest_polygon_geometry_from_ors(fc: Dict[str, Any]) -> Dict[str, Any] | None: + features = fc.get("features") or [] + best_geom, best_area = None, -1.0 + for feat in features: + geom = feat.get("geometry") or {} + gtype = geom.get("type"); coords = geom.get("coordinates") + if not coords: continue + if gtype == "Polygon": + area = _polygon_area_m2(coords[0]) + if area > best_area: + best_area, best_geom = area, {"type":"Polygon","coordinates":coords} + elif gtype == "MultiPolygon": + for poly in coords: + if not poly: continue + ring = poly[0] + area = _polygon_area_m2(ring) + if area > best_area: + best_area, best_geom = area, {"type":"Polygon","coordinates":[ring]} + return best_geom + +def _polygon_area_m2(ring_lonlat: Iterable[Iterable[float]]) -> float: + pts = list(ring_lonlat) + if len(pts) < 3: return 0.0 + if pts[0] != pts[-1]: pts = pts + [pts[0]] + def merc(lon: float, lat: float) -> Tuple[float, float]: + R = 6378137.0 + x = math.radians(lon) * R + lat = max(min(lat, 89.9999), -89.9999) + y = math.log(math.tan(math.pi/4 + math.radians(lat)/2)) * R + return x, y + xs, ys = [], [] + for lon, lat in pts: + x, y = merc(float(lon), float(lat)); xs.append(x); ys.append(y) + area = 0.0 + for i in range(len(xs)-1): + area += xs[i]*ys[i+1] - xs[i+1]*ys[i] + return abs(area) * 0.5 diff --git a/pxy_routing/services/provider.py b/pxy_routing/services/provider.py new file mode 100644 index 0000000..c9d95c2 --- /dev/null +++ b/pxy_routing/services/provider.py @@ -0,0 +1,30 @@ +# pxy_routing/services/provider.py +from __future__ import annotations +from typing import Any, Dict, Tuple + +from .factory import get_routing_provider + +# --- Legacy classes kept for backward compatibility --- +class RoutingProvider: + def health(self) -> Dict[str, Any]: + raise NotImplementedError + + def isochrone(self, center: Tuple[float, float], minutes: int) -> Dict[str, Any]: + raise NotImplementedError + +class NullRoutingProvider(RoutingProvider): + def health(self) -> Dict[str, Any]: + return {"provider": "null", "ok": False, "reason": "Routing provider not configured"} + + def isochrone(self, center: Tuple[float, float], minutes: int) -> Dict[str, Any]: + raise NotImplementedError("Routing provider not configured") + +# --- Functional API kept for existing callers --- +def health() -> Dict[str, Any]: + return get_routing_provider().health() + +def isochrone(center: Tuple[float, float], minutes: int) -> Dict[str, Any]: + return get_routing_provider().isochrone(center, minutes) + +# Optional convenience instance some code may import as 'rp' +rp = get_routing_provider() diff --git a/pxy_routing/tests.py b/pxy_routing/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/pxy_routing/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/pxy_routing/views.py b/pxy_routing/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/pxy_routing/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/pxy_sami/__init__.py b/pxy_sami/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pxy_sami/admin.py b/pxy_sami/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/pxy_sami/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/pxy_sami/api/urls.py b/pxy_sami/api/urls.py new file mode 100644 index 0000000..bb3b6b5 --- /dev/null +++ b/pxy_sami/api/urls.py @@ -0,0 +1,8 @@ +# pxy_sami/api/urls.py +from django.urls import path +from .views import sami_health, sami_run + +urlpatterns = [ + path("api/sami/health", sami_health, name="sami_health"), + path("api/sami/run", sami_run, name="sami_run"), +] diff --git a/pxy_sami/api/views.py b/pxy_sami/api/views.py new file mode 100644 index 0000000..3602e30 --- /dev/null +++ b/pxy_sami/api/views.py @@ -0,0 +1,64 @@ +# pxy_sami/api/views.py +from __future__ import annotations +import uuid +from rest_framework.decorators import api_view, throttle_classes +from rest_framework.response import Response +from rest_framework import status +from rest_framework.throttling import ScopedRateThrottle +from pydantic import ValidationError + +from pxy_contracts.contracts import SAMIRunRequest +from pxy_sami.estimators.sami_core import run_sami +from pxy_dashboard.utils.share import mint_sami_share_url + + +def _err(code: str, message: str, hint: str | None = None, http_status: int = 400): + return Response( + {"ok": False, "code": code, "message": message, "hint": hint, "trace_id": str(uuid.uuid4())}, + status=http_status, + ) + + +@api_view(["GET"]) +@throttle_classes([ScopedRateThrottle]) +def sami_health(request): + sami_health.throttle_scope = "sami_health" + try: + # If you have deeper checks, put them here. Keep simple/fast. + return Response({"ok": True, "service": "sami"}) + except Exception as e: + return _err( + "sami_health_error", + "SAMI health check failed", + str(e), + http_status=status.HTTP_500_INTERNAL_SERVER_ERROR, + ) + + +@api_view(["POST"]) +@throttle_classes([ScopedRateThrottle]) +def sami_run(request): + sami_run.throttle_scope = "sami_run" + try: + req = SAMIRunRequest.model_validate(request.data or {}) + except ValidationError as ve: + return _err("invalid", "Validation error", hint=str(ve), http_status=status.HTTP_400_BAD_REQUEST) + + try: + resp = run_sami(req) + data = resp.model_dump() + + # Inject share URL (signed, expiring) + rid = data.get("run_id") + if rid: + meta = { + "indicator": data.get("indicator"), + "beta": data.get("beta"), + "r2": data.get("r2"), + "n": len(data.get("residuals") or []), + } + data["share_url"] = mint_sami_share_url(rid, meta=meta, request=request) + + return Response(data) + except Exception as e: + return _err("sami_error", "SAMI run failed", hint=str(e), http_status=status.HTTP_502_BAD_GATEWAY) diff --git a/pxy_sami/apps.py b/pxy_sami/apps.py new file mode 100644 index 0000000..0736d85 --- /dev/null +++ b/pxy_sami/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class PxySamiConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'pxy_sami' diff --git a/pxy_sami/estimators/__init__.py b/pxy_sami/estimators/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pxy_sami/estimators/sami_core.py b/pxy_sami/estimators/sami_core.py new file mode 100644 index 0000000..cc4bbae --- /dev/null +++ b/pxy_sami/estimators/sami_core.py @@ -0,0 +1,326 @@ +# pxy_sami/estimators/sami_core.py +from __future__ import annotations +import uuid +from typing import List, Tuple +from pathlib import Path + +import numpy as np +import pandas as pd +from django.conf import settings + +# Headless backend for saving PNGs in containers +import matplotlib +matplotlib.use("Agg") +import matplotlib.pyplot as plt + +from pxy_contracts.contracts import ( + SAMIRunRequest, + SAMIRunResponse, + SAMICity, + SAMIPoint, # ← for 56B interactive scatter +) +from pxy_contracts.version import SPEC_VERSION +from pxy_de.providers.base import get_provider + + +def _fit_loglog(df: pd.DataFrame) -> Tuple[float, float, float, np.ndarray]: + import statsmodels.api as sm + """ + Ajusta: log(value) = alpha + beta * log(N) (OLS) + Regresa: (alpha, beta, R^2, residuales) + """ + df = df.copy() + df["logY"] = np.log(df["value"].astype(float)) + df["logN"] = np.log(df["N"].astype(float)) + X = sm.add_constant(df["logN"].values) + y = df["logY"].values + model = sm.OLS(y, X).fit() + alpha = float(model.params[0]) + beta = float(model.params[1]) + r2 = float(model.rsquared) if model.nobs and model.nobs >= 2 else 0.0 + resid = model.resid + return alpha, beta, r2, resid + + +def _color_for_sami(s: float) -> str: + """Colores sencillos: verde = arriba, rojo = abajo, gris = ~0.""" + if s > 0.15: + return "#2ca02c" # green + if s < -0.15: + return "#d62728" # red + return "#7f7f7f" # gray + + +def _size_for_N(N: float, N_med: float) -> float: + """Tamaño del punto ~ sqrt(N/mediana), acotado para demo.""" + if N <= 0 or N_med <= 0: + return 60.0 + s = 80.0 * np.sqrt(N / N_med) + return float(np.clip(s, 40.0, 300.0)) + + +def _save_chart( + df: pd.DataFrame, alpha: float, beta: float, r2: float, run_id: str, indicator: str +) -> str | None: + """ + Crea un gráfico bonito para demo: + - Izquierda: scatter log–log con línea de regresión, puntos coloreados por SAMI, + tamaño por N, etiquetas de ciudades y textbox con ecuación. + - Derecha: ranking horizontal por SAMI (barh). + Devuelve URL pública (/media/...). + """ + try: + media_dir = Path(settings.MEDIA_ROOT) / "sami" # ensure Path + media_dir.mkdir(parents=True, exist_ok=True) + out_path = media_dir / f"sami_{run_id}.png" + + # Preparación de datos + df = df.copy() + df["logN"] = np.log(df["N"].astype(float)) + df["logY"] = np.log(df["value"].astype(float)) + x = df["logN"].values + y = df["logY"].values + + # Línea de regresión + xs = np.linspace(x.min(), x.max(), 100) + ys = alpha + beta * xs + + # Tamaños por N + N_med = float(df["N"].median()) + sizes = [_size_for_N(n, N_med) for n in df["N"].values] + + # Colores por SAMI + colors = [_color_for_sami(s) for s in df["sami"].values] + + # Orden para ranking + df_rank = df[["city", "sami"]].sort_values("sami", ascending=True).reset_index(drop=True) + + # Figure + fig, axes = plt.subplots( + 1, 2, figsize=(11, 4.5), gridspec_kw={"width_ratios": [1.35, 1.0]} + ) + ax, axr = axes + + # --- (L) Scatter log–log --- + ax.scatter( + x, y, s=sizes, c=colors, alpha=0.9, edgecolors="white", linewidths=0.8, zorder=3 + ) + ax.plot(xs, ys, linewidth=2.0, zorder=2) + + # Etiquetas por ciudad (offset según signo SAMI) + for _, row in df.iterrows(): + dx = 0.02 * (x.max() - x.min() if x.max() > x.min() else 1.0) + dy = 0.02 * (y.max() - y.min() if y.max() > y.min() else 1.0) + offset_y = dy if row["sami"] >= 0 else -dy + ax.annotate( + row["city"], + (row["logN"], row["logY"]), + xytext=(row["logN"] + dx, row["logY"] + offset_y), + fontsize=9, + color="#303030", + bbox=dict(boxstyle="round,pad=0.2", fc="white", ec="none", alpha=0.7), + arrowprops=dict(arrowstyle="-", lw=0.6, color="#888888", alpha=0.8), + ) + + # Texto con ecuación y métricas + eq_txt = ( + f"log(Value) = {alpha:.2f} + {beta:.3f}·log(N)\n" + f"$R^2$ = {r2:.3f} n = {len(df)} indicador: {indicator}" + ) + ax.text( + 0.02, + 0.98, + eq_txt, + transform=ax.transAxes, + va="top", + ha="left", + fontsize=9, + bbox=dict(boxstyle="round", fc="white", ec="#dddddd", alpha=0.9), + ) + + # Estética + ax.set_xlabel("log(N)") + ax.set_ylabel("log(Value)") + ax.grid(True, linestyle=":", linewidth=0.7, alpha=0.6) + for spine in ["top", "right"]: + ax.spines[spine].set_visible(False) + ax.set_title("Escalamiento urbano y SAMI", fontsize=12, pad=8) + + # --- (R) Ranking SAMI (barh) --- + y_pos = np.arange(len(df_rank)) + bar_colors = [_color_for_sami(s) for s in df_rank["sami"].values] + axr.barh(y_pos, df_rank["sami"].values, color=bar_colors, alpha=0.9) + axr.set_yticks(y_pos, labels=df_rank["city"].values, fontsize=9) + axr.set_xlabel("SAMI (z)") + axr.axvline(0, color="#444444", linewidth=0.8) + axr.grid(axis="x", linestyle=":", linewidth=0.7, alpha=0.6) + for spine in ["top", "right"]: + axr.spines[spine].set_visible(False) + axr.set_title("Ranking por desviación (SAMI)", fontsize=12, pad=8) + + # Anotar top y bottom + try: + top_city = df_rank.iloc[-1] + bottom_city = df_rank.iloc[0] + axr.text( + float(top_city["sami"]), + float(len(df_rank) - 1), + f" ▲ {top_city['sami']:.2f}", + va="center", + ha="left", + fontsize=9, + color="#2ca02c", + weight="bold", + ) + axr.text( + float(bottom_city["sami"]), + 0, + f" ▼ {bottom_city['sami']:.2f}", + va="center", + ha="left", + fontsize=9, + color="#d62728", + weight="bold", + ) + except Exception: + pass + + fig.tight_layout() + fig.savefig(out_path, dpi=144) + plt.close(fig) + + return f"{settings.MEDIA_URL}sami/{out_path.name}" + except Exception: + return None + + +def run_sami(req: SAMIRunRequest) -> SAMIRunResponse: + """ + SAMI v2 (demo ready): + - Fit OLS log–log + - SAMI = resid / std(resid) + - Gráfico mejorado (scatter + ranking) + - 56B: return alpha + raw points for interactive scatter + """ + provider = get_provider() + warnings: List[str] = [] + + # 1) Cargar datos + try: + df = provider.indicator(req.indicator, req.cities or []) + except Exception as e: + warnings.append(f"data_provider_error: {e}") + residuals = [SAMICity(city=c, sami=0.0, rank=i + 1) for i, c in enumerate(req.cities or [])] + return SAMIRunResponse( + model_id="sami-ols-v2.0.0", + spec_version=SPEC_VERSION, + run_id=str(uuid.uuid4()), + indicator=req.indicator, + beta=1.0, + r2=0.0, + residuals=residuals, + chart_url=None, + data_release=req.data_release, + warnings=warnings or ["stub implementation"], + ) + + # 2) Limpieza mínima + n_before = len(df) + df = df.replace([np.inf, -np.inf], np.nan).dropna(subset=["value", "N"]) + df = df[(df["value"] > 0) & (df["N"] > 0)].copy() + n_after = len(df) + if n_before - n_after > 0: + warnings.append(f"filtered_nonpositive_or_nan: {n_before - n_after}") + if n_after < 2: + warnings.append("not_enough_data_for_fit") + residuals = [SAMICity(city=c, sami=0.0, rank=i + 1) for i, c in enumerate(req.cities or [])] + return SAMIRunResponse( + model_id="sami-ols-v2.0.0", + spec_version=SPEC_VERSION, + run_id=str(uuid.uuid4()), + indicator=req.indicator, + beta=1.0, + r2=0.0, + residuals=residuals, + chart_url=None, + data_release=req.data_release, + warnings=warnings, + ) + + # 3) Ajuste y SAMI + try: + alpha, beta, r2, resid = _fit_loglog(df) + except Exception as e: + warnings.append(f"ols_fit_error: {e}") + residuals = [SAMICity(city=c, sami=0.0, rank=i + 1) for i, c in enumerate(df["city"].tolist())] + return SAMIRunResponse( + model_id="sami-ols-v2.0.0", + spec_version=SPEC_VERSION, + run_id=str(uuid.uuid4()), + indicator=req.indicator, + beta=1.0, + r2=0.0, + residuals=residuals, + chart_url=None, + data_release=req.data_release, + warnings=warnings, + ) + + std = float(np.std(resid, ddof=1)) if len(resid) > 1 else 0.0 + sami_vals = (resid / std) if std > 0 else np.zeros_like(resid) + + # 56B: build raw points (with logs) for interactive scatter + df_pts = df.copy() + df_pts["log_value"] = np.log(df_pts["value"].astype(float)) + df_pts["log_N"] = np.log(df_pts["N"].astype(float)) + points: List[SAMIPoint] = [] + for row in df_pts.itertuples(index=False): + try: + points.append( + SAMIPoint( + city=str(row.city), + value=float(row.value), + N=float(row.N), + log_value=float(row.log_value), + log_N=float(row.log_N), + ) + ) + except Exception: + # If any row is malformed, skip it; interactive chart is best-effort. + continue + + out = df[["city", "value", "N"]].copy() + out["sami"] = sami_vals + out = out.sort_values("sami", ascending=False).reset_index(drop=True) + out["rank"] = np.arange(1, len(out) + 1) + + residuals = [ + SAMICity(city=row.city, sami=float(row.sami), rank=int(row.rank)) + for row in out.itertuples(index=False) + ] + + # 4) Guardar gráfico bonito + run_id = str(uuid.uuid4()) + chart_url = _save_chart(out, alpha, beta, r2, run_id, req.indicator) + if chart_url is None: + warnings.append("chart_save_failed") + else: + warnings.append("chart_saved") + + warnings.append(f"fit_ok_n={n_after}") + + return SAMIRunResponse( + model_id="sami-ols-v2.0.0", + spec_version=SPEC_VERSION, + run_id=run_id, + indicator=req.indicator, + beta=float(beta), + r2=float(r2), + residuals=residuals, + chart_url=chart_url, + data_release=req.data_release, + warnings=warnings, + # 56B extras + alpha=float(alpha), + points=points, + ) diff --git a/pxy_sami/migrations/__init__.py b/pxy_sami/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pxy_sami/models.py b/pxy_sami/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/pxy_sami/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/pxy_sami/services/__init__.py b/pxy_sami/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pxy_sami/tests.py b/pxy_sami/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/pxy_sami/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/pxy_sami/theory/README.md b/pxy_sami/theory/README.md new file mode 100644 index 0000000..e69de29 diff --git a/pxy_sami/validation/__init__.py b/pxy_sami/validation/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pxy_sami/views.py b/pxy_sami/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/pxy_sami/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/pxy_sites/__init__.py b/pxy_sites/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pxy_sites/admin.py b/pxy_sites/admin.py new file mode 100644 index 0000000..f3f6009 --- /dev/null +++ b/pxy_sites/admin.py @@ -0,0 +1,26 @@ +from __future__ import annotations +from django.contrib import admin +from django.utils.html import format_html +from .models import SiteRun + +@admin.register(SiteRun) +class SiteRunAdmin(admin.ModelAdmin): + list_display = ("created_at", "city", "business", "short_id", "preview", "download") + list_filter = ("city", "business", "created_at") + search_fields = ("search_id", "city", "business") + readonly_fields = ("created_at", "search_id", "city", "business", "payload_json", "result_json") + + def short_id(self, obj: SiteRun) -> str: + return obj.search_id[:8] + + def preview(self, obj: SiteRun): + if obj.map_url: + return format_html('map', obj.map_url) + return "—" + + def download(self, obj: SiteRun): + # if you added a PNG/CSV download endpoint, link it here later + url = (obj.result_json or {}).get("download_url") + if url: + return format_html('download', url) + return "—" diff --git a/pxy_sites/api/__init__.py b/pxy_sites/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pxy_sites/api/urls.py b/pxy_sites/api/urls.py new file mode 100644 index 0000000..9d9ef1b --- /dev/null +++ b/pxy_sites/api/urls.py @@ -0,0 +1,16 @@ +from django.urls import path +from .views import ( + SitesHealth, SiteSearchView, + sites_download, sites_geojson, sites_preview, sites_recent_runs +) + +urlpatterns = [ + path("api/sites/health", SitesHealth.as_view(), name="sites_health"), + path("api/sites/search", SiteSearchView.as_view(), name="sites_search"), + + # artifacts + path("api/sites/download//", sites_download, name="sites_download"), + path("api/sites/geojson//", sites_geojson, name="sites_geojson"), + path("api/sites/preview//", sites_preview, name="sites_preview"), + path("api/sites/runs/recent", sites_recent_runs, name="sites_recent_runs"), +] diff --git a/pxy_sites/api/views.py b/pxy_sites/api/views.py new file mode 100644 index 0000000..ad4016a --- /dev/null +++ b/pxy_sites/api/views.py @@ -0,0 +1,304 @@ +# pxy_sites/api/views.py +from __future__ import annotations + +import io +import json +import logging +import time +import uuid +from pathlib import Path + +import numpy as np +from PIL import Image +from django.conf import settings +from django.http import ( + FileResponse, + HttpRequest, + HttpResponse, + HttpResponseNotFound, + JsonResponse, +) +from django.views.decorators.csrf import csrf_exempt +from django.views.decorators.http import require_GET + +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework.exceptions import ValidationError as DRFValidationError +from rest_framework import status +from rest_framework.throttling import ScopedRateThrottle # 👈 add +from pydantic import ValidationError as PydValidationError + +from pxy_contracts.contracts.sites import SiteSearchRequest, SiteSearchResponse +from pxy_sites.models import SiteRun +from pxy_sites.services.site_scoring import run_site_search +from pxy_dashboard.utils.share import mint_sites_share_url + +log = logging.getLogger(__name__) + +# -------- uniform error envelope helpers -------- +def _env(code: str, message: str, *, hint: str | None = None, http: int = 400): + return Response( + {"ok": False, "code": code, "message": message, "hint": hint, "trace_id": str(uuid.uuid4())}, + status=http, + ) + +def _env_json(code: str, message: str, *, hint: str | None = None, http: int = 400): + return JsonResponse( + {"ok": False, "code": code, "message": message, "hint": hint, "trace_id": str(uuid.uuid4())}, + status=http, + ) + +# -------- helpers -------- +def _pyify(o): + """Make objects JSONField-safe (NumPy → native Python).""" + if isinstance(o, (np.floating, np.float32, np.float64)): + return float(o) + if isinstance(o, (np.integer, np.int32, np.int64)): + return int(o) + if isinstance(o, np.ndarray): + return o.tolist() + return str(o) + +def _build_base_url(request) -> str: + forwarded_proto = request.META.get("HTTP_X_FORWARDED_PROTO") + scheme = (forwarded_proto.split(",")[0].strip() if forwarded_proto else None) or ( + "https" if request.is_secure() else "http" + ) + host = request.get_host() or settings.BASE_URL.replace("https://", "").replace("http://", "") + return f"{scheme}://{host}" + +# -------- DRF API views -------- +class SitesHealth(APIView): + authentication_classes = [] + throttle_classes = [ScopedRateThrottle] # 👈 enable throttling + throttle_scope = "sites_health" + + def get(self, request, *args, **kwargs): + return Response({"ok": True, "app": "pxy_sites"}) + +class SiteSearchView(APIView): + # DRF ScopedRateThrottle is active via project settings; scope name here: + throttle_scope = "sites_search" + + def post(self, request, *args, **kwargs): + t0 = time.perf_counter() + # 1) Validate contract + try: + req = SiteSearchRequest(**(request.data or {})) + except PydValidationError as ve: + # DRFValidationError would be handled by your global handler too, + # but we return the consistent envelope directly + return _env("invalid", "Validation error", hint=str(ve), http=status.HTTP_400_BAD_REQUEST) + + # 2) Run scoring (catch provider/upstream failures -> 502 envelope) + try: + resp: SiteSearchResponse = run_site_search(req) + except Exception as e: + dur_ms = (time.perf_counter() - t0) * 1000.0 + log.warning( + "[sites] search_failed city=%s business=%s bands=%s err=%s duration_ms=%.1f", + getattr(req, "city", None), getattr(req, "business", None), getattr(req, "time_bands", None), + e, dur_ms, + ) + return _env("sites_error", "Sites search failed", hint=str(e), http=status.HTTP_502_BAD_GATEWAY) + + data = resp.model_dump() + + # 3) Build absolute URLs (proxy-friendly) + base = _build_base_url(request) + sid = data.get("search_id") + + if sid: + data["share_url"] = mint_sites_share_url(sid, request=request) + + def _dl(kind: str) -> str: return f"{base}/api/sites/download/{kind}/{sid}" + def _gj(kind: str) -> str: return f"{base}/api/sites/geojson/{kind}/{sid}" + def _pv(kind: str) -> str: return f"{base}/api/sites/preview/{kind}/{sid}" + + if sid and data.get("map_url"): + data["main_download_url"] = _dl("main") + data["main_preview_url"] = _pv("main") + if sid and data.get("demand_map_url"): + data["demand_download_url"] = _dl("demand") + data["demand_preview_url"] = _pv("demand") + if sid and data.get("competition_map_url"): + data["competition_download_url"] = _dl("competition") + data["competition_preview_url"] = _pv("competition") + if sid: + data["isochrones_geojson_url"] = _gj("isochrones") + data["candidates_geojson_url"] = _gj("candidates") + data["pois_competition_geojson_url"] = _gj("pois_competition") + data["popgrid_geojson_url"] = _gj("popgrid") + + # 4) Persist run in DB (best-effort) + try: + safe_payload = json.loads(json.dumps(req.model_dump(), default=_pyify)) + safe_result = json.loads(json.dumps(data, default=_pyify)) + SiteRun.objects.create( + search_id=sid, + city=safe_result.get("city"), + business=safe_result.get("business"), + payload_json=safe_payload, + result_json=safe_result, + ) + log.info("[sites] saved SiteRun %s", sid) + except Exception as e: + data.setdefault("warnings", []).append(f"persist_failed: {e}") + log.warning("[sites] persist_failed for %s: %s", sid, e) + + dur_ms = (time.perf_counter() - t0) * 1000.0 + log.info( + "[sites] search_ok city=%s business=%s bands=%s duration_ms=%.1f", + data.get("city"), data.get("business"), data.get("time_bands"), dur_ms, + ) + return Response(data, status=status.HTTP_200_OK) + +# -------- Artifacts (FBVs) -------- +_KIND_PREFIX = {"main": "sites", "demand": "demand", "competition": "competition"} + +@csrf_exempt +def sites_download(request: HttpRequest, kind: str, search_id: str): + prefix = _KIND_PREFIX.get(kind) + if not prefix: + return _env_json("invalid_kind", "Invalid kind", hint=str(list(_KIND_PREFIX)), http=400) + try: + uuid.UUID(search_id) + except Exception: + return _env_json("invalid_search_id", "search_id must be a UUID", http=400) + + fname = f"{prefix}_{search_id}.png" + fpath = Path(settings.MEDIA_ROOT) / "sites" / fname + if not fpath.exists(): + return _env_json("not_found", f"file not found: {fname}", http=404) + + return FileResponse(open(fpath, "rb"), content_type="image/png", as_attachment=True, filename=fname) + +_GJ_KEYS = { + "isochrones": "isochrones_fc", + "candidates": "candidates_fc", + "pois_competition": "pois_competition_fc", + "popgrid": "popgrid_fc", +} + +@csrf_exempt +def sites_geojson(request: HttpRequest, kind: str, search_id: str): + if kind not in _GJ_KEYS: + return _env_json("invalid_kind", "Invalid kind", hint=str(list(_GJ_KEYS)), http=400) + try: + uuid.UUID(search_id) + except Exception: + return _env_json("invalid_search_id", "search_id must be a UUID", http=400) + + fpath = Path(settings.MEDIA_ROOT) / "sites" / f"run_{search_id}.json" + if not fpath.exists(): + return _env_json("not_found", f"artifact not found: run_{search_id}.json", http=404) + + try: + with open(fpath, "r", encoding="utf-8") as f: + artifact = json.load(f) + fc = artifact.get(_GJ_KEYS[kind]) or {"type": "FeatureCollection", "features": []} + return HttpResponse(json.dumps(fc), content_type="application/geo+json") + except Exception as e: + return _env_json("artifact_read_error", "Failed to read artifact", hint=str(e), http=500) + +_PREVIEW_PREFIX = {"main": "sites", "demand": "demand", "competition": "competition"} + +@csrf_exempt +def sites_preview(request: HttpRequest, kind: str, search_id: str): + prefix = _PREVIEW_PREFIX.get(kind) + if not prefix: + return _env_json("invalid_kind", "Invalid kind", hint=str(list(_PREVIEW_PREFIX)), http=400) + try: + uuid.UUID(search_id) + except Exception: + return _env_json("invalid_search_id", "search_id must be a UUID", http=400) + + fname = f"{prefix}_{search_id}.png" + fpath = Path(settings.MEDIA_ROOT) / "sites" / fname + if not fpath.exists(): + return _env_json("not_found", f"file not found: {fname}", http=404) + + # resize params + def _clamp_int(val, lo, hi, default=None): + try: + v = int(val) + return max(lo, min(hi, v)) + except Exception: + return default + + w_q = _clamp_int(request.GET.get("w"), 16, 2000, None) + h_q = _clamp_int(request.GET.get("h"), 16, 2000, None) + try: + scale_q = float(request.GET.get("scale")) if request.GET.get("scale") else None + if scale_q is not None: + scale_q = max(0.05, min(3.0, scale_q)) + except Exception: + scale_q = None + + if not any([w_q, h_q, scale_q]): + with open(fpath, "rb") as f: + data = f.read() + resp = HttpResponse(data, content_type="image/png") + resp["Cache-Control"] = "public, max-age=3600" + return resp + + try: + im = Image.open(fpath) + im = im.convert("RGBA") if im.mode not in ("RGB", "RGBA") else im + orig_w, orig_h = im.size + + if scale_q: + w = int(round(orig_w * scale_q)); h = int(round(orig_h * scale_q)) + elif w_q and h_q: + w, h = w_q, h_q + elif w_q: + ratio = w_q / float(orig_w); w, h = w_q, max(1, int(round(orig_h * ratio))) + elif h_q: + ratio = h_q / float(orig_h); w, h = max(1, int(round(orig_w * ratio))), h_q + else: + w, h = orig_w, orig_h + + w = max(16, min(2000, w)); h = max(16, min(2000, h)) + im = im.resize((w, h), Image.LANCZOS) + + buf = io.BytesIO(); im.save(buf, format="PNG", optimize=True); buf.seek(0) + resp = HttpResponse(buf.getvalue(), content_type="image/png") + resp["Cache-Control"] = "public, max-age=600" + return resp + except Exception as e: + return _env_json("resize_failed", "Image resize failed", hint=str(e), http=500) + +@require_GET +def sites_recent_runs(request: HttpRequest): + """GET /api/sites/runs/recent?limit=10 — list latest runs with handy URLs.""" + try: + limit = int(request.GET.get("limit", "10")) + except Exception: + limit = 10 + limit = max(1, min(limit, 50)) + + items = [] + qs = SiteRun.objects.order_by("-created_at")[:limit] + for r in qs: + res = r.result_json or {} + items.append({ + "search_id": r.search_id, + "city": r.city, + "business": r.business, + "created_at": r.created_at.isoformat(), + "map_url": res.get("map_url"), + "demand_map_url": res.get("demand_map_url"), + "competition_map_url": res.get("competition_map_url"), + "download": { + "main": res.get("main_download_url"), + "demand": res.get("demand_download_url"), + "competition": res.get("competition_download_url"), + }, + "geojson": { + "isochrones": res.get("isochrones_geojson_url"), + "candidates": res.get("candidates_geojson_url"), + "pois_competition": res.get("pois_competition_geojson_url"), + "popgrid": res.get("popgrid_geojson_url"), + }, + }) + return JsonResponse({"items": items}) diff --git a/pxy_sites/apps.py b/pxy_sites/apps.py new file mode 100644 index 0000000..012a1d7 --- /dev/null +++ b/pxy_sites/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class PxySitesConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'pxy_sites' diff --git a/pxy_sites/migrations/0001_initial.py b/pxy_sites/migrations/0001_initial.py new file mode 100644 index 0000000..f6a1923 --- /dev/null +++ b/pxy_sites/migrations/0001_initial.py @@ -0,0 +1,29 @@ +# Generated by Django 5.0.3 on 2025-09-15 07:36 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='SiteRun', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('search_id', models.CharField(db_index=True, max_length=64)), + ('city', models.CharField(max_length=64)), + ('business', models.CharField(max_length=128)), + ('payload_json', models.JSONField()), + ('result_json', models.JSONField()), + ('created_at', models.DateTimeField(auto_now_add=True)), + ], + options={ + 'ordering': ['-created_at'], + }, + ), + ] diff --git a/pxy_sites/migrations/__init__.py b/pxy_sites/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pxy_sites/models.py b/pxy_sites/models.py new file mode 100644 index 0000000..f2af4f5 --- /dev/null +++ b/pxy_sites/models.py @@ -0,0 +1,26 @@ +from __future__ import annotations +from django.db import models + +class SiteRun(models.Model): + search_id = models.CharField(max_length=64, db_index=True) + city = models.CharField(max_length=64) + business = models.CharField(max_length=128) + payload_json = models.JSONField() # request we received + result_json = models.JSONField() # full response we returned + created_at = models.DateTimeField(auto_now_add=True) + + class Meta: + ordering = ["-created_at"] + + def __str__(self) -> str: + return f"{self.created_at:%Y-%m-%d %H:%M} — {self.city}/{self.business} — {self.search_id[:8]}" + + # convenience accessors + @property + def map_url(self) -> str | None: + return (self.result_json or {}).get("map_url") + + @property + def geojson_url(self) -> str | None: + # if you already expose one, wire it here later + return (self.result_json or {}).get("geojson_url") diff --git a/pxy_sites/services/__init__.py b/pxy_sites/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pxy_sites/services/site_scoring.py b/pxy_sites/services/site_scoring.py new file mode 100644 index 0000000..f500838 --- /dev/null +++ b/pxy_sites/services/site_scoring.py @@ -0,0 +1,723 @@ +# pxy_sites/services/site_scoring.py +from __future__ import annotations +import os, json, uuid, random, math +from typing import List, Tuple, Optional, Dict, Any +from datetime import datetime + +from django.conf import settings +from pyproj import Geod + +# Headless backend para matplotlib +import matplotlib +matplotlib.use("Agg") +import matplotlib.pyplot as plt +from matplotlib.patches import Polygon as MplPolygon + +import numpy as np +from shapely.geometry import Point, Polygon +from scipy.stats import gaussian_kde + +from pxy_contracts.contracts import ( + SiteSearchRequest, SiteSearchResponse, + CandidateSite, ScoreBreakdown +) +from pxy_routing.services import get_routing_provider +from pxy_de.providers.base import get_provider + + +# --------------------------- Helpers geométricos --------------------------- + +def _isochrone_area_km2(feature: dict) -> float: + geom = (feature or {}).get("geometry") or {} + if geom.get("type") != "Polygon": + return 0.0 + rings = geom.get("coordinates") or [] + if not rings: + return 0.0 + coords = rings[0] + if len(coords) < 4: + return 0.0 + geod = Geod(ellps="WGS84") + lons = [float(x[0]) for x in coords] + lats = [float(x[1]) for x in coords] + area_m2, _ = geod.polygon_area_perimeter(lons, lats) + return abs(area_m2) / 1_000_000.0 # m² -> km² + + +def _polygon_from_feature(feature: dict) -> Optional[Polygon]: + geom = (feature or {}).get("geometry") or {} + if geom.get("type") != "Polygon": + return None + coords = geom.get("coordinates") + if not coords or not coords[0]: + return None + try: + ring = [(float(x[0]), float(x[1])) for x in coords[0]] + if len(ring) < 4: + return None + return Polygon(ring) + except Exception: + return None + + +def _extent_from_iso_list(iso_list: List[dict]) -> Optional[Tuple[float, float, float, float]]: + xs, ys = [], [] + for item in iso_list or []: + feat = item.get("feature") or {} + geom = feat.get("geometry") or {} + if geom.get("type") != "Polygon": + continue + coords = geom.get("coordinates") or [] + if not coords: + continue + ring = coords[0] + for x, y in ring: + xs.append(float(x)); ys.append(float(y)) + if not xs or not ys: + return None + return (min(xs), min(ys), max(xs), max(ys)) + + +def _build_isochrones(center: Tuple[float, float], time_bands: List[int]) -> List[dict]: + """ + Build isochrones for the requested minute bands. + - If the routing provider supports `isochrones(center, minutes_list)`, use it once + (reduces ORS requests and rate-limit pressure). + - Otherwise, fall back to one call per band. + Output schema stays the same as before: a list of dicts with + {"minutes": int, "feature": Feature(Polygon), "area_km2": float} + """ + rp = get_routing_provider() + bands: List[int] = [int(m) for m in (time_bands or [])] + out: List[dict] = [] + + # Try a single batched call first + if hasattr(rp, "isochrones"): + try: + feats = rp.isochrones(center, bands) # expected same order as requested bands + n = min(len(bands), len(feats)) + for m, feat in zip(bands[:n], feats[:n]): + area_km2 = _isochrone_area_km2(feat) + props = {"minutes": int(m), "area_km2": float(area_km2)} + f = {"type": "Feature", "geometry": feat.get("geometry"), "properties": props} + out.append({"minutes": int(m), "feature": f, "area_km2": float(area_km2)}) + + # If provider returned fewer features than requested, fill the rest via single calls + for m in bands[n:]: + feat = rp.isochrone(center, int(m)) + area_km2 = _isochrone_area_km2(feat) + props = {"minutes": int(m), "area_km2": float(area_km2)} + f = {"type": "Feature", "geometry": feat.get("geometry"), "properties": props} + out.append({"minutes": int(m), "feature": f, "area_km2": float(area_km2)}) + + return out + except Exception: + # Fall back to per-band calls below if the batch call fails for any reason + pass + + # Fallback: one request per band (original behavior) + for m in bands: + feat = rp.isochrone(center, int(m)) + area_km2 = _isochrone_area_km2(feat) + props = {"minutes": int(m), "area_km2": float(area_km2)} + f = {"type": "Feature", "geometry": feat.get("geometry"), "properties": props} + out.append({"minutes": int(m), "feature": f, "area_km2": float(area_km2)}) + + return out + + + +def _access_from_iso_list(iso_list: List[dict]) -> Tuple[float, List[str]]: + if not iso_list: + return 0.0, ["no_isochrones"] + areas = [item["area_km2"] for item in iso_list] + max_a = max(areas) if areas else 0.0 + if max_a <= 0: + return 0.0, [f"{item['minutes']} min area ≈ 0.0 km²" for item in iso_list] + norms = [a / max_a for a in areas] + access = sum(norms) / len(norms) + reasons = [f"{item['minutes']} min area ≈ {item['area_km2']:.1f} km²" for item in iso_list] + return float(access), reasons + + +# --------------------------- Scores data-driven --------------------------- + +def _competition_from_pois(city: str, business: str, iso_list: List[dict]) -> Tuple[float, List[str]]: + prov = get_provider() + try: + pois = prov.denue(city, business) # DataFrame[name,lat,lon,category] + except Exception as e: + return 0.5, [f"competition_fallback: provider_error={e}"] + + if pois.empty or not iso_list: + return 0.5, ["competition_fallback: no_pois_or_isochrones"] + + largest = max(iso_list, key=lambda x: x["minutes"]) + poly = _polygon_from_feature(largest["feature"]) + if poly is None: + return 0.5, ["competition_fallback: invalid_polygon"] + + area_km2 = float(largest.get("area_km2") or 0.0) + if area_km2 <= 0.0: + return 0.5, ["competition_fallback: zero_area"] + + cnt = 0 + for row in pois.itertuples(index=False): + try: + p = Point(float(row.lon), float(row.lat)) + if poly.contains(p): + cnt += 1 + except Exception: + continue + + density = cnt / area_km2 # POIs per km² + D_ref = float(os.getenv("COMP_REF_DENSITY", "5.0")) + comp = 1.0 / (1.0 + density / D_ref) + comp = float(max(0.0, min(1.0, comp))) + + reasons = [ + f"largest_band: {largest['minutes']} min, area ≈ {area_km2:.1f} km²", + f"competitors_inside: {cnt}, density ≈ {density:.2f} /km²", + f"competition_score = 1/(1 + density/{D_ref:.1f}) ≈ {comp:.2f}", + ] + return comp, reasons + + +def _demand_from_popgrid(city: str, iso_list: List[dict]) -> Tuple[float, List[str]]: + prov = get_provider() + try: + grid = prov.popgrid(city) # DataFrame[cell_id, lat, lon, pop] + except Exception as e: + return 0.5, [f"demand_fallback: provider_error={e}"] + + if grid.empty or not iso_list: + return 0.5, ["demand_fallback: no_grid_or_isochrones"] + + largest = max(iso_list, key=lambda x: x["minutes"]) + poly = _polygon_from_feature(largest["feature"]) + if poly is None: + return 0.5, ["demand_fallback: invalid_polygon"] + + area_km2 = float(largest.get("area_km2") or 0.0) + if area_km2 <= 0.0: + return 0.5, ["demand_fallback: zero_area"] + + total_pop = 0.0 + for row in grid.itertuples(index=False): + try: + p = Point(float(row.lon), float(row.lat)) + if poly.contains(p): + total_pop += float(row.pop) + except Exception: + continue + + density = total_pop / area_km2 if area_km2 > 0 else 0.0 + P_ref = float(os.getenv("DEMAND_REF_POP", "50000")) + demand = total_pop / (total_pop + P_ref) if (total_pop + P_ref) > 0 else 0.0 + demand = float(max(0.0, min(1.0, demand))) + + reasons = [ + f"largest_band: {largest['minutes']} min, area ≈ {area_km2:.1f} km²", + f"population_inside ≈ {int(total_pop)}, density ≈ {density:.1f} /km²", + f"demand_score = pop/(pop+{int(P_ref)}) ≈ {demand:.2f}", + ] + return demand, reasons + + +# --------------------------- Sampling y Mapa principal --------------------------- + +def _sample_points_in_polygon(poly: Polygon, n: int, rng: random.Random) -> List[Tuple[float, float]]: + minx, miny, maxx, maxy = poly.bounds + pts: List[Tuple[float, float]] = [] + max_tries = n * 50 + tries = 0 + while len(pts) < n and tries < max_tries: + tries += 1 + x = rng.uniform(minx, maxx) + y = rng.uniform(miny, maxy) + if poly.contains(Point(x, y)): + pts.append((y, x)) # (lat, lon) + return pts + + +def _km_per_deg_lon(lat_deg: float) -> float: + return 111.320 * math.cos(math.radians(lat_deg)) + + +def _km_per_deg_lat() -> float: + return 110.574 + + +def _save_sites_map(center: Tuple[float, float], iso_list_for_map: List[dict], + search_id: str, city: str, business: str, + top_candidates: List[Tuple[float, float, float]]) -> str | None: + try: + media_dir = settings.MEDIA_ROOT / "sites" + media_dir.mkdir(parents=True, exist_ok=True) + out_path = media_dir / f"sites_{search_id}.png" + + # recolectar polígonos/extent + lons, lats = [center[1]], [center[0]] + polys = [] + for item in iso_list_for_map: + feat = item["feature"] + geom = feat.get("geometry") or {} + if geom.get("type") != "Polygon": + continue + coords = geom.get("coordinates")[0] + poly_xy = [(float(x[0]), float(x[1])) for x in coords] + polys.append({"minutes": item["minutes"], "coords": poly_xy, "area": item["area_km2"]}) + lons.extend([p[0] for p in poly_xy]) + lats.extend([p[1] for p in poly_xy]) + + fig, ax = plt.subplots(figsize=(7.6, 7.6)) + + band_palette = ["#2E86AB", "#F18F01", "#C73E1D", "#6C5B7B", "#17B890", "#7E57C2"] + rank_palette = ["#1B998B", "#3A86FF", "#FB5607", "#FFBE0B", "#8338EC", "#FF006E"] + + for i, item in enumerate(sorted(polys, key=lambda d: d["minutes"], reverse=True)): + poly = MplPolygon(item["coords"], closed=True, + facecolor=band_palette[i % len(band_palette)], alpha=0.25, + edgecolor=band_palette[i % len(band_palette)], linewidth=1.6, + label=f"{item['minutes']} min · {item['area']:.1f} km²") + ax.add_patch(poly) + + ax.scatter([center[1]], [center[0]], s=68, zorder=6, + facecolor="#000", edgecolor="white", linewidth=1.2) + ax.annotate("center", (center[1], center[0]), + xytext=(center[1] + 0.01, center[0] + 0.01), + fontsize=9, color="#303030", + bbox=dict(boxstyle="round,pad=0.2", fc="white", ec="none", alpha=0.75), + arrowprops=dict(arrowstyle="-", lw=0.7, color="#666", alpha=0.9)) + + sizes = [90, 80, 72, 64, 56, 50, 46, 42, 38, 34] + legend_rows = [] + for idx, (lat, lon, score) in enumerate(top_candidates, start=1): + color = rank_palette[(idx - 1) % len(rank_palette)] + size = sizes[idx - 1] if idx - 1 < len(sizes) else 30 + ax.scatter([lon], [lat], s=size, zorder=7, + facecolor=color, edgecolor="white", linewidth=1.0) + ax.annotate(f"{idx} · {score:.2f}", (lon, lat), + xytext=(lon + 0.008, lat + 0.008), + fontsize=8, color="#111", + bbox=dict(boxstyle="round,pad=0.2", fc="white", ec="#bbb", alpha=0.9)) + legend_rows.append(f"{idx}. ({score:.2f}) {lat:.4f}, {lon:.4f}") + lons.append(lon); lats.append(lat) + + if lons and lats: + minx, maxx = min(lons), max(lons) + miny, maxy = min(lats), max(lats) + pad_x = max((maxx - minx) * 0.08, 0.01) + pad_y = max((maxy - miny) * 0.08, 0.01) + ax.set_xlim(minx - pad_x, maxx + pad_x) + ax.set_ylim(miny - pad_y, maxy + pad_y) + + ax.set_title(f"Top sites — {business} @ {city}", fontsize=13, pad=10) + ax.set_xlabel("Longitude") + ax.set_ylabel("Latitude") + ax.grid(True, linestyle=":", linewidth=0.6, alpha=0.6) + for spine in ["top", "right"]: + ax.spines[spine].set_visible(False) + + leg = ax.legend(loc="lower right", frameon=True, fontsize=8, title="Isochrones") + if leg and leg.get_frame(): + leg.get_frame().set_alpha(0.9) + + x0, x1 = ax.get_xlim() + y0, y1 = ax.get_ylim() + x_text = x0 + (x1 - x0) * 0.70 + y_text = y0 + (y1 - y0) * 0.97 + ax.text(x_text, y_text, + "Top-K (score)\n" + "\n".join(legend_rows), + ha="left", va="top", fontsize=8, color="#111", + bbox=dict(boxstyle="round,pad=0.3", fc="white", ec="#ccc", alpha=0.9)) + + km_per_deg_x = _km_per_deg_lon(center[0]) + deg_len = 5.0 / km_per_deg_x if km_per_deg_x > 0 else 0.05 + px = x0 + (x1 - x0) * 0.10 + py = y0 + (y1 - y0) * 0.08 + ax.plot([px, px + deg_len], [py, py], lw=3, color="#222") + ax.plot([px, px], [py - 0.001, py + 0.001], lw=2, color="#222") + ax.plot([px + deg_len, px + deg_len], [py - 0.001, py + 0.001], lw=2, color="#222") + ax.text(px + deg_len / 2.0, py + 0.002, "5 km", + ha="center", va="bottom", fontsize=8, color="#222", + bbox=dict(boxstyle="round,pad=0.2", fc="white", ec="none", alpha=0.7)) + + fig.tight_layout() + fig.savefig(out_path, dpi=150) + plt.close(fig) + + return f"{settings.MEDIA_URL}sites/{out_path.name}" + except Exception: + return None + + +# --------------------------- Mapas densidad: Demanda / Competencia --------------------------- + +def _grid_kde(xy: np.ndarray, weights: Optional[np.ndarray], + x_grid: np.ndarray, y_grid: np.ndarray, bw: Optional[float] = None) -> np.ndarray: + if xy.shape[1] != 2 or xy.shape[0] < 2: + return np.zeros((y_grid.size, x_grid.size), dtype=float) + kde = gaussian_kde(xy.T, weights=weights, bw_method=bw) + Xg, Yg = np.meshgrid(x_grid, y_grid) + pts = np.vstack([Xg.ravel(), Yg.ravel()]) + z = kde(pts).reshape(Yg.shape) + z = z - z.min() + if z.max() > 0: + z = z / z.max() + return z + + +def _render_density_map(kind: str, + center: Tuple[float, float], + iso_list: List[dict], + points_xy: np.ndarray, + weights: Optional[np.ndarray], + search_id: str, + city: str, + business: str) -> Optional[str]: + try: + extent = _extent_from_iso_list(iso_list) + if extent is None: + cx, cy = center[1], center[0] + extent = (cx - 0.08, cy - 0.08, cx + 0.08, cy + 0.08) + minx, miny, maxx, maxy = extent + pad_x = max((maxx - minx) * 0.05, 0.01) + pad_y = max((maxy - miny) * 0.05, 0.01) + minx -= pad_x; maxx += pad_x + miny -= pad_y; maxy += pad_y + + lat0 = max(miny, min(maxy, center[0])) + kx = _km_per_deg_lon(lat0) + ky = _km_per_deg_lat() + + if points_xy.size == 0: + return None + xs = points_xy[:, 0] * kx + ys = points_xy[:, 1] * ky + + grid_n = int(os.getenv("HEAT_GRID_N", "220")) + xg = np.linspace(minx * kx, maxx * kx, grid_n) + yg = np.linspace(miny * ky, maxy * ky, grid_n) + + z = _grid_kde(np.c_[xs, ys], weights, xg, yg, bw=None) + + media_dir = settings.MEDIA_ROOT / "sites" + media_dir.mkdir(parents=True, exist_ok=True) + out_path = media_dir / f"{kind}_{search_id}.png" + + fig, ax = plt.subplots(figsize=(8.0, 7.0)) + im = ax.imshow(z, origin="lower", + extent=(minx, maxx, miny, maxy), + interpolation="bilinear", alpha=0.85) + if kind == "demand": + im.set_cmap("YlOrRd") + title = f"Demand heat — {business} @ {city}" + else: + im.set_cmap("GnBu") + title = f"Competition heat — {business} @ {city}" + + cs = ax.contour(z, levels=6, linewidths=0.8, alpha=0.8, + extent=(minx, maxx, miny, maxy), colors="k") + ax.clabel(cs, inline=True, fontsize=7, fmt="%.2f") + + for item in sorted(iso_list, key=lambda d: d["minutes"], reverse=True): + feat = item.get("feature") or {} + geom = feat.get("geometry") or {} + if geom.get("type") != "Polygon": + continue + coords = geom.get("coordinates")[0] + ring = np.array([(float(x[0]), float(x[1])) for x in coords]) + ax.plot(ring[:, 0], ring[:, 1], lw=1.2, alpha=0.9) + + ax.scatter([center[1]], [center[0]], s=55, zorder=5, + facecolor="#000", edgecolor="white", linewidth=1.0) + + ax.set_title(title, fontsize=13, pad=10) + ax.set_xlabel("Longitude") + ax.set_ylabel("Latitude") + ax.grid(True, linestyle=":", linewidth=0.5, alpha=0.5) + for spine in ["top", "right"]: + ax.spines[spine].set_visible(False) + + cbar = plt.colorbar(im, ax=ax, fraction=0.046, pad=0.04) + cbar.ax.set_ylabel("relative intensity", rotation=90, labelpad=8) + + fig.tight_layout() + fig.savefig(out_path, dpi=150) + plt.close(fig) + + return f"{settings.MEDIA_URL}sites/{out_path.name}" + except Exception: + return None + + +def _render_demand_map(center: Tuple[float, float], iso_list: List[dict], + city: str, search_id: str, business: str) -> Optional[str]: + prov = get_provider() + try: + grid = prov.popgrid(city) # cell_id, lat, lon, pop + except Exception: + return None + if grid.empty: + return None + pts = grid[["lon", "lat", "pop"]].dropna().copy() + points_xy = pts[["lon", "lat"]].to_numpy(dtype=float) + weights = pts["pop"].to_numpy(dtype=float) + return _render_density_map("demand", center, iso_list, points_xy, weights, search_id, city, business) + + +def _render_competition_map(center: Tuple[float, float], iso_list: List[dict], + city: str, business: str, search_id: str) -> Optional[str]: + prov = get_provider() + try: + pois = prov.denue(city, business) # name, lat, lon, category + except Exception: + return None + if pois.empty: + return None + pts = pois[["lon", "lat"]].dropna().copy() + points_xy = pts.to_numpy(dtype=float) + return _render_density_map("competition", center, iso_list, points_xy, None, search_id, city, business) + + +# --------------------------- Artefacto GeoJSON por búsqueda --------------------------- + +def _fc(features: List[Dict[str, Any]]) -> Dict[str, Any]: + return {"type": "FeatureCollection", "features": features} + +def _candidates_fc(center: Tuple[float,float], + top: List[Tuple[float,float,float,ScoreBreakdown,List[str],List[dict]]]) -> Dict[str, Any]: + feats = [] + for idx, (lat, lon, score, br, _reasons, _iso) in enumerate(top, start=1): + feats.append({ + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [float(lon), float(lat)]}, + "properties": { + "rank": idx, + "score": float(score), + "access": float(br.access), + "demand": float(br.demand), + "competition": float(br.competition), + "is_center": abs(lat - center[0]) < 1e-9 and abs(lon - center[1]) < 1e-9, + } + }) + return _fc(feats) + +def _isochrones_fc(iso_list: List[dict]) -> Dict[str, Any]: + feats = [] + for item in iso_list: + f = item["feature"] + # ya tiene properties {"minutes","area_km2"} + feats.append(f) + return _fc(feats) + +def _pois_fc(pois_df, poly: Polygon) -> Dict[str, Any]: + feats = [] + if pois_df is None or pois_df.empty: + return _fc(feats) + count = 0 + for row in pois_df.itertuples(index=False): + try: + lon = float(row.lon); lat = float(row.lat) + if not poly.contains(Point(lon, lat)): + continue + feats.append({ + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [lon, lat]}, + "properties": { + "name": getattr(row, "name", None), + "category": getattr(row, "category", None), + } + }) + count += 1 + if count >= int(os.getenv("MAX_POIS_GEOJSON", "1000")): + break + except Exception: + continue + return _fc(feats) + +def _popgrid_fc(grid_df, poly: Polygon) -> Dict[str, Any]: + feats = [] + if grid_df is None or grid_df.empty: + return _fc(feats) + # filtra dentro del polígono + inside = [] + for row in grid_df.itertuples(index=False): + try: + lon = float(row.lon); lat = float(row.lat); pop = float(row.pop) + if poly.contains(Point(lon, lat)): + inside.append((lon, lat, pop)) + except Exception: + continue + if not inside: + return _fc(feats) + # ordena por población desc y limita + inside.sort(key=lambda t: t[2], reverse=True) + cap = int(os.getenv("MAX_POPGRID_GEOJSON", "800")) + inside = inside[:cap] + for lon, lat, pop in inside: + feats.append({ + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [lon, lat]}, + "properties": {"pop": pop} + }) + return _fc(feats) + +def _save_run_artifact(search_id: str, + req: SiteSearchRequest, + chosen_center: Tuple[float,float], + top: List[Tuple[float,float,float,ScoreBreakdown,List[str],List[dict]]], + iso_list: List[dict]) -> Optional[str]: + """ + Guarda un JSON con: + - request_summary + - candidates_fc + - isochrones_fc + - pois_competition_fc + - popgrid_fc (muestra) + """ + try: + media_dir = settings.MEDIA_ROOT / "sites" + media_dir.mkdir(parents=True, exist_ok=True) + out_path = media_dir / f"run_{search_id}.json" + + # polígono mayor para recortes + largest = max(iso_list, key=lambda x: x["minutes"]) if iso_list else None + poly = _polygon_from_feature(largest["feature"]) if largest else None + + prov = get_provider() + try: + pois = prov.denue(req.city, req.business) + except Exception: + pois = None + try: + grid = prov.popgrid(req.city) + except Exception: + grid = None + + artifact = { + "version": "sites-artifact-1", + "created_at": datetime.utcnow().isoformat() + "Z", + "request": req.model_dump(), + "center": {"lat": chosen_center[0], "lon": chosen_center[1]}, + "candidates_fc": _candidates_fc(chosen_center, top), + "isochrones_fc": _isochrones_fc(iso_list), + "pois_competition_fc": _pois_fc(pois, poly) if poly is not None else _fc([]), + "popgrid_fc": _popgrid_fc(grid, poly) if poly is not None else _fc([]), + } + with open(out_path, "w", encoding="utf-8") as f: + json.dump(artifact, f, ensure_ascii=False) + return str(out_path) + except Exception: + return None + + +# --------------------------- Estimador principal --------------------------- + +def run_site_search(req: SiteSearchRequest) -> SiteSearchResponse: + search_id = str(uuid.uuid4()) + warnings: List[str] = [] + candidates: List[CandidateSite] = [] + map_url: str | None = None + demand_map_url: Optional[str] = None + competition_map_url: Optional[str] = None + + w_access = float(os.getenv("WEIGHT_ACCESS", "0.35")) + w_demand = float(os.getenv("WEIGHT_DEMAND", "0.40")) + w_comp = float(os.getenv("WEIGHT_COMP", "0.25")) + + if req.center: + center = (float(req.center[0]), float(req.center[1])) + base_iso = _build_isochrones(center, req.time_bands or []) + largest = max(base_iso, key=lambda x: x["minutes"]) if base_iso else None + poly = _polygon_from_feature(largest["feature"]) if largest else None + + if poly is None: + access, access_r = _access_from_iso_list(base_iso) + comp, comp_r = _competition_from_pois(req.city, req.business, base_iso) + dem, dem_r = _demand_from_popgrid(req.city, base_iso) + score = w_access * access + w_demand * dem + w_comp * comp + score = float(max(0.0, min(1.0, score))) + breakdown = ScoreBreakdown(demand=dem, competition=comp, access=access) + reasons = (["Access from isochrone areas (normalized avg)"] + access_r + + ["Competition from POI density (largest band)"] + comp_r + + ["Demand from population grid (largest band)"] + dem_r) + candidates.append(CandidateSite(lat=center[0], lon=center[1], score=score, + breakdown=breakdown, reasons=reasons)) + map_url = _save_sites_map(center, base_iso, search_id, req.city, req.business, + [(center[0], center[1], score)]) + warnings.append("sampling_fallback_invalid_polygon") + demand_map_url = _render_demand_map(center, base_iso, req.city, search_id, req.business) + competition_map_url = _render_competition_map(center, base_iso, req.city, req.business, search_id) + # artefacto (solo center) + _save_run_artifact( + search_id, req, center, + [(center[0], center[1], score, breakdown, reasons, base_iso)], + base_iso + ) + else: + rng = random.Random(int(search_id.replace("-", ""), 16) & 0xFFFFFFFF) + samples = _sample_points_in_polygon(poly, int(req.num_samples), rng) + cand_points: List[Tuple[float, float]] = [center] + samples + + scored: List[Tuple[float, float, float, ScoreBreakdown, List[str], List[dict]]] = [] + for (lat, lon) in cand_points: + iso_list = _build_isochrones((lat, lon), req.time_bands or []) + access, access_r = _access_from_iso_list(iso_list) + comp, comp_r = _competition_from_pois(req.city, req.business, iso_list) + dem, dem_r = _demand_from_popgrid(req.city, iso_list) + score = w_access * access + w_demand * dem + w_comp * comp + score = float(max(0.0, min(1.0, score))) + breakdown = ScoreBreakdown(demand=dem, competition=comp, access=access) + reasons = (["Access from isochrone areas (normalized avg)"] + access_r + + ["Competition from POI density (largest band)"] + comp_r + + ["Demand from population grid (largest band)"] + dem_r) + scored.append((lat, lon, score, breakdown, reasons, iso_list)) + + scored.sort(key=lambda t: t[2], reverse=True) + top = scored[: max(1, int(req.max_candidates))] + + for (lat, lon, score, breakdown, reasons, _iso) in top: + candidates.append(CandidateSite( + lat=lat, lon=lon, score=score, breakdown=breakdown, reasons=reasons + )) + + top1_iso = top[0][5] + top_points = [(lat, lon, score) for (lat, lon, score, *_rest) in top] + map_url = _save_sites_map((top[0][0], top[0][1]), top1_iso, search_id, + req.city, req.business, top_points) + warnings.append("multi_candidate_sampling_ok") + + demand_map_url = _render_demand_map((top[0][0], top[0][1]), top1_iso, req.city, search_id, req.business) + competition_map_url = _render_competition_map((top[0][0], top[0][1]), top1_iso, req.city, req.business, search_id) + + if demand_map_url: warnings.append("demand_map_saved") + else: warnings.append("demand_map_failed") + if competition_map_url: warnings.append("competition_map_saved") + else: warnings.append("competition_map_failed") + + # artefacto (Top-K + isócronas del Top-1) + _save_run_artifact(search_id, req, (top[0][0], top[0][1]), top, top1_iso) + else: + neutral = ScoreBreakdown(demand=0.5, competition=0.5, access=0.5) + for i in range(req.max_candidates): + candidates.append(CandidateSite( + lat=0.0, lon=0.0, score=0.5, + breakdown=neutral, + reasons=[f"stub candidate #{i+1} for {req.business} in {req.city}"], + )) + warnings.append("no_center_provided_stub_output") + + return SiteSearchResponse( + search_id=search_id, + city=req.city, + business=req.business, + time_bands=req.time_bands, + candidates=candidates, + map_url=map_url, + demand_map_url=demand_map_url, + competition_map_url=competition_map_url, + data_release=req.data_release, + warnings=warnings, + ) diff --git a/pxy_sites/tests.py b/pxy_sites/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/pxy_sites/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/pxy_sites/theory/README.md b/pxy_sites/theory/README.md new file mode 100644 index 0000000..e69de29 diff --git a/pxy_sites/validation/__init__.py b/pxy_sites/validation/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pxy_sites/views.py b/pxy_sites/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/pxy_sites/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/requirements.txt b/requirements.txt index e73888e..166a1b5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -155,3 +155,12 @@ crispy-bootstrap5>=0.6 polyline + +# --- science deps for SAMI --- +# --- science deps for SAMI / Sites --- +numpy==1.26.4 +pandas==2.2.3 +scipy==1.15.3 +statsmodels==0.14.5 + +djangorestframework==3.15.2

+
+
+
+
+
+ + +
+
+ + +
+
+ +
+
+ Calls /api/sami/run and displays results interactively. +
+
+
+
+ +
+
+ + +
+
+
+
+
β (scaling)
+
+
+
+
+
+
+
+
n (cities)
+
+
+
+
+ +
+ +
+ +
+ +
+
+ +
+
Model scatter (server-rendered)
+ SAMI scatter +
+
+ +
+
+ + + + + + + + + +
RankCitySAMI (z)
+
+ +
+
+
+