152 lines
4.8 KiB
Python
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
|