Files
AR-House/location_agent/sub_agents/maritime_agent.py
T
2026-07-03 12:24:58 -04:00

152 lines
4.8 KiB
Python

"""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