Example 3: App Developer
How to pull CalHeatScore data into a backend service (for notifications) and into a client app (for map visualization).
Building a Heat Alert App with CalHeatScore
Imagine you're an app developer building a heat safety app for Californians, and you want to incorporate CalHeatScore data into your tool. Your app will do two things: (i) send a push notification each morning when a user's ZIP code hits a High or Severe CalHeatScore, and (ii) show an interactive map so they can see heat risk across their region when they open the app.
You plan to use the CalHeatScore API to pull in the data. The API is free, requires no authentication, and supports JSON for your backend notification service and GeoJSON for your in-app map.
Your backend service queries the API once each morning, checks each registered user's ZIP code against the latest scores, and fires push notifications for any user whose score meets the alert threshold.
On the client side, your app fetches GeoJSON for the user's region and renders it on a map.
Note: The example code below focuses on data access and preparation only.
Step-by-Step: Daily CalHeatScore Data Workflow
Step 1: Fetch all CalHeatScore data (JSON format)
Pull the full dataset once each morning. With ~1,700 California ZIP codes and a 2,000-record limit per request, a single call typically returns everything:
import requests
import pandas as pd
BASE_URL = (
"https://services1.arcgis.com/PCHfdHz4GlDNAhBb/arcgis/rest/services/"
"CalHeatScore_Live_Data_for_API_Use/FeatureServer/0/query"
)
params = {
"where": "1=1",
"outFields": "ZIP_CODE,DATE,CHS_Day_0,CHS_Day_1,CHS_Day_2",
"returnGeometry": "false",
"f": "json"
}
response = requests.get(BASE_URL, params=params)
data = response.json()
# get dataframe
features = []
for f in data['features']:
features.append(f['attributes'])
df = pd.DataFrame(features)
print(df)
const BASE_URL =
"https://services1.arcgis.com/PCHfdHz4GlDNAhBb/arcgis/rest/services/" +
"CalHeatScore_Live_Data_for_API_Use/FeatureServer/0/query";
async function fetchCalHeatScoreData() {
const params = {
where: "1=1",
outFields: "ZIP_CODE,DATE,CHS_Day_0,CHS_Day_1,CHS_Day_2",
returnGeometry: "false",
f: "json"
};
const url = `${BASE_URL}?${new URLSearchParams(params)}`;
const response = await fetch(url);
const data = await response.json();
// Extract attributes
const df = data.features.map(f => f.attributes);
console.log("CalHeatScore Data:", df);
return df;
}
Step 2: Filter users by ZIP code score
Query your user database for registered users and their ZIP codes, then check each user's score against your notification threshold:
# example user database
users = pd.DataFrame({
'user_id': [1, 2, 3, 4, 5],
'user_zipcode': ['94607', '94612', '94609', '94610', '94611']
})
print(users)
# merge with df
merged = pd.merge(
users,
df,
how='left',
left_on='user_zipcode',
right_on='ZIP_CODE'
)
# get users who should be notified
alert_thresholds = ['3', '4']
merged_filtered = merged[merged['CHS_Day_0'].isin(alert_thresholds)]
print(merged_filtered)
// Example user database
const users = [
{ user_id: 1, user_zipcode: "94607" },
{ user_id: 2, user_zipcode: "94612" },
{ user_id: 3, user_zipcode: "94609" },
{ user_id: 4, user_zipcode: "94610" },
{ user_id: 5, user_zipcode: "94611" }
];
console.log("Users:", users);
// Merge users with CalHeatScore data
function leftJoinUsersWithData(users, df) {
return users.map(user => {
const match = df.find(row => row.ZIP_CODE === user.user_zipcode);
return {
...user,
...(match || {})
};
});
}
// Filter by alert thresholds (CHS_Day_0)
const alertThresholds = ["3", "4"];
function filterUsersByThreshold(mergedData) {
return mergedData.filter(row =>
alertThresholds.includes(String(row.CHS_Day_0))
);
}
Step 3: Fetch GeoJSON for map display
When the user opens the map view, fetch CalHeatScore data as GeoJSON for the ZIP codes near their location. Use outSR=4326 for standard latitude/longitude coordinates:
async function fetchGeoJSON(zipCodes) {
const where = `ZIP_CODE IN (${zipCodes.map(z => `'${z}'`).join(",")})`;
const params = new URLSearchParams({
where,
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",
outSR: "4326",
f: "geojson"
});
const geojson = await fetch(`${BASE_URL}?${params}`).then(r => r.json());
return geojson;
}
Step 4: Fetch a single ZIP’s score
async function fetchHeatScore(zip) {
const params = new URLSearchParams({
where: `ZIP_CODE='${zip}'`,
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",
});
const data = await fetch(`${BASE_URL}?${params}`).then(r => r.json());
return data.features?.[0]?.attributes || null;
}
Step 5: Execute workflow
(async () => {
console.log("Fetching full CalHeatScore dataset...");
const df = await fetchCalHeatScoreData();
console.log("Merging user ZIPs with heat score data...");
const merged = leftJoinUsersWithData(users, df);
console.log("Merged Data:", merged);
console.log("Filtering users requiring alerts...");
const usersToNotify = filterUsersByThreshold(merged);
console.log("Users to Notify:", usersToNotify);
console.log("Fetching GeoJSON for those ZIP codes...");
const zips = usersToNotify.map(u => u.user_zipcode);
const geojson = await fetchGeoJSON(zips);
console.log("GeoJSON:", geojson);
console.log("Fetching individual ZIP heat score example...");
const heatScore = await fetchHeatScore("94612");
console.log("Heat Score for 94612:", heatScore);
})();
Summary
- Backend JSON fetch for scheduled notifications
- Client GeoJSON fetch for map rendering
- Single ZIP fetch for summary cards