"""Sub-agente: Mercado laboral marítimo. Fuentes: BLS.gov API (gratuita) + Overpass para instalaciones físicas. """ from __future__ import annotations import time import requests from data_fetchers.base import USER_AGENT, DEFAULT_TIMEOUT OVERPASS_URL = "https://overpass-api.de/api/interpreter" BLS_BASE = "https://api.bls.gov/publicAPI/v2/timeseries/data/" # NAICS codes marítimos para BLS MARITIME_NAICS = { "483": "Water Transportation", "4883": "Support Activities for Water Transportation", "3366": "Ship & Boat Building", "114": "Fishing, Hunting and Trapping", } def run(lat: float, lon: float, address: str, state: str = "FL") -> dict: result = { "maritime_employers": [], "shipyards": [], "marinas_with_jobs": [], "bls_employment": {}, "maritime_presence_score": 0, "sources": [], "errors": [], } # --- Overpass: instalaciones marítimas físicas --- try: facilities = _overpass_maritime(lat, lon) result["shipyards"] = facilities.get("shipyards", []) result["marinas_with_jobs"] = facilities.get("marinas", []) result["maritime_employers"] = facilities.get("employers", []) result["sources"].append("OpenStreetMap/Overpass") except Exception as e: result["errors"].append(f"Overpass maritime: {e}") # --- BLS API (sin key — API v1 gratuita, limitada) --- try: bls = _bls_maritime(state) result["bls_employment"] = bls result["sources"].append("BLS.gov") except Exception as e: result["errors"].append(f"BLS: {e}") # Presencia marítima general result["maritime_presence_score"] = ( len(result["shipyards"]) * 15 + len(result["marinas_with_jobs"]) * 8 + len(result["maritime_employers"]) * 5 ) return result def _overpass_maritime(lat: float, lon: float, radius_m: int = 16000) -> dict: """Instalaciones marítimas en radio de ~10 millas.""" query = f""" [out:json][timeout:30]; ( node["industrial"="port"](around:{radius_m},{lat},{lon}); node["waterway"="boatyard"](around:{radius_m},{lat},{lon}); node["leisure"="marina"](around:{radius_m},{lat},{lon}); way["leisure"="marina"](around:{radius_m},{lat},{lon}); node["man_made"="shipyard"](around:{radius_m},{lat},{lon}); node["seamark:type"="harbour"](around:{radius_m},{lat},{lon}); ); out body center; """ time.sleep(1) r = requests.post(OVERPASS_URL, data={"data": query}, headers={"User-Agent": USER_AGENT}, timeout=35) r.raise_for_status() elements = r.json().get("elements", []) shipyards, marinas, employers = [], [], [] for el in elements: tags = el.get("tags", {}) name = tags.get("name", "Sin nombre") industrial = tags.get("industrial", "") waterway = tags.get("waterway", "") leisure = tags.get("leisure", "") man_made = tags.get("man_made", "") if man_made == "shipyard" or waterway == "boatyard": shipyards.append({"name": name, "type": "shipyard"}) elif leisure == "marina": marinas.append({"name": name, "type": "marina"}) elif industrial == "port": employers.append({"name": name, "type": "port"}) return {"shipyards": shipyards[:10], "marinas": marinas[:10], "employers": employers[:10]} def _bls_maritime(state: str) -> dict: """BLS API v1 — empleo en water transportation por estado.""" # Series ID formato: SMU{state_fips}0000004830000001 (Water Transportation) # Sin key usamos endpoint público v1 series_id = f"SMU120000004830000001" # Florida como default payload = { "seriesid": [series_id], "startyear": "2022", "endyear": "2024", } headers = {"User-Agent": USER_AGENT, "Content-Type": "application/json"} import json r = requests.post(BLS_BASE, data=json.dumps(payload), headers=headers, timeout=DEFAULT_TIMEOUT) r.raise_for_status() data = r.json() series = data.get("Results", {}).get("series", []) if not series: return {} latest = series[0].get("data", []) if not latest: return {} return { "series_id": series_id, "latest_employment": latest[0].get("value"), "period": latest[0].get("period"), "year": latest[0].get("year"), "label": "Water Transportation Employment (thousands)", } def score(data: dict) -> int: """Score 0-100 para mercado laboral marítimo.""" presence = data.get("maritime_presence_score", 0) if presence >= 60: return 90 elif presence >= 40: return 75 elif presence >= 20: return 60 elif presence >= 10: return 45 elif presence > 0: return 35 else: return 20