"""Sub-agente: Criminalidad. Fuentes: SpotCrime (scraping) + FBI UCR API (key opcional). Retorna datos fail-soft — si falla, devuelve dict vacío con error. """ from __future__ import annotations import os import time import requests from bs4 import BeautifulSoup from data_fetchers.base import USER_AGENT, DEFAULT_TIMEOUT FBI_API_KEY = os.getenv("FBI_UCR_API_KEY", "") FBI_BASE = "https://api.usa.gov/crime/fbi/cde" def run(lat: float, lon: float, address: str) -> dict: """Recopila datos de criminalidad para la ubicación.""" result = { "score_input": {}, "crimes_recent": [], "crime_types": {}, "trend": "desconocido", "sources": [], "errors": [], } # --- SpotCrime scraping --- try: spot = _spotcrime(lat, lon) result["crimes_recent"] = spot.get("crimes", []) result["crime_types"] = spot.get("by_type", {}) result["sources"].append("SpotCrime.com") except Exception as e: result["errors"].append(f"SpotCrime: {e}") # --- FBI UCR API (solo si hay key) --- if FBI_API_KEY: try: fbi = _fbi_ucr(address) result["fbi_data"] = fbi result["sources"].append("FBI UCR API") except Exception as e: result["errors"].append(f"FBI UCR: {e}") # Score input: cantidad de crímenes en los últimos 30 días total = len(result["crimes_recent"]) result["score_input"]["total_crimes_30d"] = total result["score_input"]["has_violent"] = any( c.get("type", "").lower() in ("assault", "robbery", "shooting", "homicide") for c in result["crimes_recent"] ) return result def _spotcrime(lat: float, lon: float) -> dict: """Scraping básico de SpotCrime para el área.""" url = f"https://spotcrime.com/crimes.json?lat={lat}&lon={lon}&callback=spotcrime" headers = {"User-Agent": USER_AGENT, "Referer": "https://spotcrime.com"} time.sleep(2) r = requests.get(url, headers=headers, timeout=DEFAULT_TIMEOUT) r.raise_for_status() # SpotCrime devuelve JSONP — extraer JSON interior text = r.text if text.startswith("spotcrime("): text = text[len("spotcrime("):-1] import json data = json.loads(text) crimes = data.get("crimes", []) by_type: dict = {} for c in crimes: t = c.get("type", "Other") by_type[t] = by_type.get(t, 0) + 1 return {"crimes": crimes[:50], "by_type": by_type} def _fbi_ucr(address: str) -> dict: """FBI UCR API — estadísticas por estado/ciudad.""" # Extraer estado de la dirección (últimas 2 letras antes del ZIP) import re m = re.search(r",\s*([A-Z]{2})\s+\d{5}", address.upper()) state = m.group(1) if m else "FL" url = f"{FBI_BASE}/summarized/state/{state}/all?API_KEY={FBI_API_KEY}&from=2020&to=2023" r = requests.get(url, timeout=DEFAULT_TIMEOUT, headers={"User-Agent": USER_AGENT}) r.raise_for_status() return r.json() def score(data: dict) -> int: """Calcula score 0-100 de seguridad (100 = muy seguro).""" if not data or not data.get("score_input"): return 50 # neutral si no hay datos total = data["score_input"].get("total_crimes_30d", 0) has_violent = data["score_input"].get("has_violent", False) # Base score inversamente proporcional a crímenes if total == 0: base = 90 elif total <= 5: base = 75 elif total <= 15: base = 60 elif total <= 30: base = 45 elif total <= 50: base = 30 else: base = 15 # Penalización por crimen violento if has_violent: base = max(0, base - 15) return min(100, max(0, base))