Skip to content

Ejemplo 2: Departamento de Respuesta ante Emergencias

Cómo un departamento de respuesta ante emergencias de una ciudad o condado podría integrar los datos diarios de CalHeatScore en su panel de control interno de emergencias.

Integración de CalHeatScores en su panel de control de emergencias

Imagine que es un analista en una Oficina de Servicios de Emergencia a nivel del condado. Su equipo ya monitorea el riesgo de incendios forestales, la calidad del aire y el estado de la red eléctrica en un panel de control interno que ayuda a decidir cuándo y dónde emitir alertas públicas, distribuir recursos y abrir centros de resiliencia comunitaria.

Le interesa añadir los pronósticos de CalHeatScore mediante la API a su panel de control interno. La API es gratuita, pública y no requiere credenciales, lo que significa que puede integrarla en su panel de control actual sin procesos de adquisición, gestión de claves de API ni contratos con proveedores. Los datos se actualizan a diario, devuelven un pronóstico a 7 días para cada código postal de California y admiten formatos JSON y GeoJSON que se conectan directamente con plataformas de panel de control comunes como Grafana, Power BI y ArcGIS o aplicaciones web personalizadas. Puede automatizar una consulta diaria, filtrar por los códigos postales de su jurisdicción y mostrar los puntajes junto con sus otros indicadores de emergencia.

El flujo de trabajo que se muestra a continuación explica cómo conectar CalHeatScore a su panel de control de emergencias, abarcando desde la consulta de los puntajes de su jurisdicción hasta la configuración de alertas automáticas cuando los niveles de riesgo superen los umbrales de activación.

Paso a paso: conecte CalHeatScore a su panel de control

Paso 1: defina los códigos postales de su jurisdicción

Compile los códigos postales dentro de los límites de su ciudad o condado. Por ejemplo, un departamento del condado de Riverside podría monitorear estos códigos postales clave en las distintas áreas geográficas del condado:

Área Códigos postales
Área metropolitana de Riverside 92501, 92503, 92505, 92507
Valle de Coachella 92234, 92260, 92262, 92274
Hemet / San Jacinto 92543, 92544, 92583
Banning / Beaumont 92220, 92223

Paso 2: consulte los puntajes de hoy para su jurisdicción

Obtenga el pronóstico actual a 7 días para todos sus códigos postales en una única solicitud:

curl "https://services1.arcgis.com/PCHfdHz4GlDNAhBb/arcgis/rest/services/CalHeatScore_Live_Data_for_API_Use/FeatureServer/0/query?where=ZIP_CODE+IN+('92501','92503','92505','92507','92234','92260','92262','92274','92543','92544','92583','92220','92223')&outFields=ZIP_CODE,DATE,CHS_Day_0,CHS_Day_1,CHS_Day_2,CHS_Day_3,CHS_Day_4,CHS_Day_5,CHS_Day_6&returnGeometry=false&f=json"
import requests

BASE_URL = (
    "https://services1.arcgis.com/PCHfdHz4GlDNAhBb/arcgis/rest/services/"
    "CalHeatScore_Live_Data_for_API_Use/FeatureServer/0/query"
)

# Riverside County ZIP codes by area
zip_codes = {
    "Riverside metro": ["92501", "92503", "92505", "92507"],
    "Coachella Valley": ["92234", "92260", "92262", "92274"],
    "Hemet / San Jacinto": ["92543", "92544", "92583"],
    "Banning / Beaumont": ["92220", "92223"],
}

all_zips = [z for group in zip_codes.values() for z in group]
where_clause = "ZIP_CODE IN (" + ",".join(f"'{z}'" for z in all_zips) + ")"

params = {
    "where": where_clause,
    "outFields": "ZIP_CODE,DATE,CHS_Day_0,CHS_Day_1,CHS_Day_2,CHS_Day_3,CHS_Day_4,CHS_Day_5,CHS_Day_6",
    "returnGeometry": "false",
    "f": "json",
}

response = requests.get(BASE_URL, params=params)
data = response.json()

if "error" in data:
    print(f"Error: {data['error']['message']}")
else:
    for feature in data["features"]:
        attrs = feature["attributes"]
        scores = [int(attrs[f"CHS_Day_{d}"]) for d in range(7)]
        peak = max(scores)
        print(f"ZIP {attrs['ZIP_CODE']}: today={scores[0]}, peak={peak} (Day {scores.index(peak)})")
const baseUrl =
  "https://services1.arcgis.com/PCHfdHz4GlDNAhBb/arcgis/rest/services/" +
  "CalHeatScore_Live_Data_for_API_Use/FeatureServer/0/query";

// Riverside County ZIP codes
const zipCodes = [
  "92501", "92503", "92505", "92507",
  "92234", "92260", "92262", "92274",
  "92543", "92544", "92583",
  "92220", "92223",
];
const whereClause = `ZIP_CODE IN (${zipCodes.map((z) => `'${z}'`).join(",")})`;

const params = new URLSearchParams({
  where: whereClause,
  outFields: "ZIP_CODE,DATE,CHS_Day_0,CHS_Day_1,CHS_Day_2,CHS_Day_3,CHS_Day_4,CHS_Day_5,CHS_Day_6",
  returnGeometry: "false",
  f: "json",
});

fetch(`${baseUrl}?${params}`)
  .then((res) => res.json())
  .then((data) => {
    if (data.error) {
      console.error(`Error: ${data.error.message}`);
      return;
    }
    data.features.forEach((feature) => {
      const a = feature.attributes;
      const scores = Array.from({ length: 7 }, (_, d) => Number(a[`CHS_Day_${d}`]));
      const peak = Math.max(...scores);
      console.log(`ZIP ${a.ZIP_CODE}: today=${scores[0]}, peak=${peak} (Day ${scores.indexOf(peak)})`);
    });
  })
  .catch((err) => console.error("Request failed:", err));
Ejemplo de respuesta
{
  "features": [
    {
      "attributes": {
        "ZIP_CODE": "92234",
        "DATE": "2026-07-18",
        "CHS_Day_0": "4",
        "CHS_Day_1": "4",
        "CHS_Day_2": "4",
        "CHS_Day_3": "3",
        "CHS_Day_4": "3",
        "CHS_Day_5": "2",
        "CHS_Day_6": "1"
      }
    },
    {
      "attributes": {
        "ZIP_CODE": "92501",
        "DATE": "2026-07-18",
        "CHS_Day_0": "3",
        "CHS_Day_1": "3",
        "CHS_Day_2": "4",
        "CHS_Day_3": "3",
        "CHS_Day_4": "2",
        "CHS_Day_5": "1",
        "CHS_Day_6": "1"
      }
    },
    {
      "attributes": {
        "ZIP_CODE": "92543",
        "DATE": "2026-07-18",
        "CHS_Day_0": "2",
        "CHS_Day_1": "3",
        "CHS_Day_2": "3",
        "CHS_Day_3": "2",
        "CHS_Day_4": "1",
        "CHS_Day_5": "1",
        "CHS_Day_6": "0"
      }
    }
  ]
}

Paso 3: defina los umbrales de activación

Asigne niveles de CalHeatScore a las acciones de respuesta ante emergencia de su departamento.

Por ejemplo, un valor deCalHeatScore igual a 3 podría corresponderse con las Acciones de respuesta A y By un valor de CalHeatScore igual a 4 podría corresponderse con las Acciones de respuesta C y D.

Paso 4: automatice la ingesta diaria y las alertas por umbral

Configure un guion programado que se ejecute cada mañana, obtenga los puntajes actualizados y envíe una alerta cuando algún código postal de su jurisdicción supere el umbral de activación definido:

import requests
import json
from datetime import datetime

BASE_URL = (
    "https://services1.arcgis.com/PCHfdHz4GlDNAhBb/arcgis/rest/services/"
    "CalHeatScore_Live_Data_for_API_Use/FeatureServer/0/query"
)

# Your jurisdiction's ZIP codes
zip_codes = ["92501", "92503", "92505", "92507", "92234", "92260",
             "92262", "92274", "92543", "92544", "92583", "92220", "92223"]

ACTIVATION_THRESHOLD = 3  # Alert when any ZIP reaches this score

where_clause = "ZIP_CODE IN (" + ",".join(f"'{z}'" for z in zip_codes) + ")"

params = {
    "where": where_clause,
    "outFields": "ZIP_CODE,DATE,CHS_Day_0,CHS_Day_1,CHS_Day_2,CHS_Day_3,CHS_Day_4,CHS_Day_5,CHS_Day_6",
    "returnGeometry": "false",
    "f": "json",
}

response = requests.get(BASE_URL, params=params)
data = response.json()

if "error" in data:
    print(f"Error: {data['error']['message']}")
    exit(1)

# Check each ZIP code across the 7-day forecast
alerts = []
for feature in data["features"]:
    attrs = feature["attributes"]
    for day in range(7):
        score = int(attrs[f"CHS_Day_{day}"])
        if score >= ACTIVATION_THRESHOLD:
            alerts.append({
                "zip": attrs["ZIP_CODE"],
                "day": day,
                "score": score,
                "level": "SEVERE" if score == 4 else "HIGH",
            })

# Save full dataset for dashboard ingestion
filename = f"calheatscore_{datetime.now().strftime('%Y-%m-%d')}.json"
with open(filename, "w") as f:
    json.dump(data, f, indent=2)
print(f"Saved {len(data['features'])} ZIP codes to {filename}")

# Report alerts
if alerts:
    print(f"\n⚠️  ACTIVATION ALERT: {len(alerts)} threshold exceedances detected")
    for a in alerts:
        print(f"  ZIP {a['zip']}: {a['level']} (score {a['score']}) on Day {a['day']}")
else:
    print("\n✅ All ZIP codes below activation threshold")
const fs = require("fs");

const baseUrl =
  "https://services1.arcgis.com/PCHfdHz4GlDNAhBb/arcgis/rest/services/" +
  "CalHeatScore_Live_Data_for_API_Use/FeatureServer/0/query";

// Your jurisdiction's ZIP codes
const zipCodes = [
  "92501", "92503", "92505", "92507", "92234", "92260",
  "92262", "92274", "92543", "92544", "92583", "92220", "92223",
];

const ACTIVATION_THRESHOLD = 3; // Alert when any ZIP reaches this score

const whereClause = `ZIP_CODE IN (${zipCodes.map((z) => `'${z}'`).join(",")})`;

const params = new URLSearchParams({
  where: whereClause,
  outFields: "ZIP_CODE,DATE,CHS_Day_0,CHS_Day_1,CHS_Day_2,CHS_Day_3,CHS_Day_4,CHS_Day_5,CHS_Day_6",
  returnGeometry: "false",
  f: "json",
});

fetch(`${baseUrl}?${params}`)
  .then((res) => res.json())
  .then((data) => {
    if (data.error) {
      console.error(`Error: ${data.error.message}`);
      process.exit(1);
    }

    // Check each ZIP code across the 7-day forecast
    const alerts = [];
    data.features.forEach((feature) => {
      const a = feature.attributes;
      for (let day = 0; day <= 6; day++) {
        const score = Number(a[`CHS_Day_${day}`]);
        if (score >= ACTIVATION_THRESHOLD) {
          alerts.push({
            zip: a.ZIP_CODE,
            day,
            score,
            level: score === 4 ? "SEVERE" : "HIGH",
          });
        }
      }
    });

    // Save full dataset for dashboard ingestion
    const today = new Date().toISOString().split("T")[0];
    const filename = `calheatscore_${today}.json`;
    fs.writeFileSync(filename, JSON.stringify(data, null, 2));
    console.log(`Saved ${data.features.length} ZIP codes to ${filename}`);

    // Report alerts
    if (alerts.length > 0) {
      console.log(`\n⚠️  ACTIVATION ALERT: ${alerts.length} threshold exceedances detected`);
      alerts.forEach((a) => {
        console.log(`  ZIP ${a.zip}: ${a.level} (score ${a.score}) on Day ${a.day}`);
      });
    } else {
      console.log("\n✅ All ZIP codes below activation threshold");
    }
  })
  .catch((err) => console.error("Request failed:", err));

Programe este guion para que se ejecute cada mañana utilizando cron (Linux/Mac), el Programador de tareas de Windows o la plataforma de automatización de su departamento. Genere el archivo JSON en una ubicación que su panel de control pueda leer, como una unidad compartida, un bucket S3 o una base de datos consultada por su panel de control.

Paso 5: muestre los puntajes en su panel de control

La forma en que visualice los datos depende de la plataforma de su panel de control:

Agregue la capa de características de CalHeatScore directamente —no se necesita un guion:

  1. Abra ArcGIS Dashboards y cree o edite un panel de control.
  2. Agregue un elemento de Mapa y agregue la capa de características de CalHeatScore usando esta URL:
    https://services1.arcgis.com/PCHfdHz4GlDNAhBb/arcgis/rest/services/CalHeatScore_Live_Data_for_API_Use/FeatureServer/0
    
  3. Aplique una expresión de definición para filtrar según los códigos postales de su jurisdicción: ZIP_CODE IN ('92501','92503','92505','92507','92234','92260','92262','92274','92543','92544','92583','92220','92223')
  4. Agregue un elemento Indicador que muestre el puntaje más alto en su jurisdicción usando una expresión de estadística en CHS_Day_0.
  5. Agregue un elemento de Lista para mostros los códigos postales que actualmente tienen un puntaje igual o mayor que 3.

Use el conector de datos web de Power BI:

  1. Abra Power BI Desktop y haga clic en Get Data (Obtener datos) > Web.
  2. Ingrese la URL completa de la consulta (con sus códigos postales y f=json).
  3. Power BI interpretará la respuesta de JSON. Expanda las columnas de features y attributes.
  4. Convierta CHS_Day_0 hasta CHS_Day_6 de texto a número entero.
  5. Cree una regla de formato condicional en su tabla u objeto visual de mapa utilizando el rango de puntajes de 0 a 4.
  6. Configure la Scheduled Refresh (Actualización programada) en el Power BI Service para obtener datos nuevos cada día.

Use el complemento de fuente de datos Grafana JSON o Infinity:

  1. Instale el complemento de fuente de datos Infinity en Grafana.
  2. Agregue una nueva fuente de datos que apunte a la URL de consulta de CalHeatScore.
  3. Interprete la respuesta de JSON usando JSONPath: $.features[*].attributes.
  4. Cree un panel deStat (Estadísticas) que muestre el puntaje máximo en su jurisdicción.
  5. Cree un panel Table (Tabla) que muestre todos los códigos postales con su pronóstico a 7 días.
  6. Configure una regla de Grafana Alert (Alerta de Grafana): activarse cuando cualquier valor de CHS_Day_0 sea ≥ 3.

Paso 6: configure una vista de mapa en formato GeoJSON para tener una visión clara de la situación

Para una visualización en mapa que muestre qué partes de su jurisdicción están en riesgo elevado, solicite los datos en formato GeoJSON y aplique una codificación por colores a los códigos postales según su puntaje:

curl "https://services1.arcgis.com/PCHfdHz4GlDNAhBb/arcgis/rest/services/CalHeatScore_Live_Data_for_API_Use/FeatureServer/0/query?where=ZIP_CODE+IN+('92501','92503','92505','92507','92234','92260','92262','92274','92543','92544','92583','92220','92223')&outFields=ZIP_CODE,CHS_Day_0,CHS_Day_1,CHS_Day_2&outSR=4326&f=geojson"
import requests
import json

BASE_URL = (
    "https://services1.arcgis.com/PCHfdHz4GlDNAhBb/arcgis/rest/services/"
    "CalHeatScore_Live_Data_for_API_Use/FeatureServer/0/query"
)

zip_codes = ["92501", "92503", "92505", "92507", "92234", "92260",
             "92262", "92274", "92543", "92544", "92583", "92220", "92223"]

where_clause = "ZIP_CODE IN (" + ",".join(f"'{z}'" for z in zip_codes) + ")"

params = {
    "where": where_clause,
    "outFields": "ZIP_CODE,CHS_Day_0,CHS_Day_1,CHS_Day_2",
    "outSR": "4326",
    "f": "geojson",
}

response = requests.get(BASE_URL, params=params)
geojson = response.json()

if "error" in geojson:
    print(f"Error: {geojson['error']['message']}")
else:
    # Save GeoJSON for map visualization
    with open("jurisdiction_heat_map.geojson", "w") as f:
        json.dump(geojson, f)
    print(f"Saved {len(geojson['features'])} ZIP code polygons")
const fs = require("fs");

const baseUrl =
  "https://services1.arcgis.com/PCHfdHz4GlDNAhBb/arcgis/rest/services/" +
  "CalHeatScore_Live_Data_for_API_Use/FeatureServer/0/query";

const zipCodes = [
  "92501", "92503", "92505", "92507", "92234", "92260",
  "92262", "92274", "92543", "92544", "92583", "92220", "92223",
];
const whereClause = `ZIP_CODE IN (${zipCodes.map((z) => `'${z}'`).join(",")})`;

const params = new URLSearchParams({
  where: whereClause,
  outFields: "ZIP_CODE,CHS_Day_0,CHS_Day_1,CHS_Day_2",
  outSR: "4326",
  f: "geojson",
});

fetch(`${baseUrl}?${params}`)
  .then((res) => res.json())
  .then((geojson) => {
    if (geojson.error) {
      console.error(`Error: ${geojson.error.message}`);
      return;
    }
    fs.writeFileSync("jurisdiction_heat_map.geojson", JSON.stringify(geojson, null, 2));
    console.log(`Saved ${geojson.features.length} ZIP code polygons`);
  })
  .catch((err) => console.error("Request failed:", err));

Cargue el archivo .geojson resultante en el componente de mapa de su panel de control y aplique la codificación por colores:

Valor de CalHeatScore Código de color HEX Etiqueta de la leyenda del mapa
0 #DEDEDE Sin riesgo elevado
1 #FAE0C8 Riesgo bajo
2 #EA8753 Riesgo moderado
3 #C23A00 Riesgo elevado: umbral de activación
4 #700006 Riesgo severo: activación completa

Consejo para uso operativo: Concentre el indicador principal de su panel de control en el puntaje más alto registrado hoy en su jurisdiccióny en la cantidad de códigos postales que se encuentran en o por encima de su umbral de activación. Estos dos valores ofrecen a los responsables de incidentes una lectura rápida para decidir si escalar la respuesta. Reserve el desglose detallado por código postal para una vista secundaria, accesible al personal de operaciones para consultas en profundidad.