Skip to content

fix:airnow:Migrate AirNow app to 2026 observation API.#589

Merged
tavdog merged 4 commits into
mainfrom
airnow-2026-api
Jul 21, 2026
Merged

fix:airnow:Migrate AirNow app to 2026 observation API.#589
tavdog merged 4 commits into
mainfrom
airnow-2026-api

Conversation

@tavdog

@tavdog tavdog commented Jul 20, 2026

Copy link
Copy Markdown
Member

The latLong/current endpoint retires September 30, 2026; normalize the new ziplatlong response format for existing display logic.

Summary by CodeRabbit

  • New Features
    • Improved AirNow data handling with normalized observations and clearer AQI category information.
    • Updated the AirNow current-observation retrieval to use a different endpoint and normalize results before display.
  • Bug Fixes
    • Added explicit handling for unknown AQI categories to show a clear “Unknown AQI category” message.
  • Style
    • Refreshed AirNow app imagery, including animated assets.
  • Chores
    • Updated the AirNow app metadata timestamp.

tavdog and others added 2 commits July 19, 2026 17:50
The latLong/current endpoint retires September 30, 2026; normalize the new ziplatlong response format for existing display logic.

Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2ca3e6b6-7f60-4762-9ac6-dce664b349bc

📥 Commits

Reviewing files that changed from the base of the PR and between 42499a3 and a284bd1.

📒 Files selected for processing (2)
  • apps/airnow/airnow.star
  • apps/airnow/manifest.yaml
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/airnow/manifest.yaml
  • apps/airnow/airnow.star

📝 Walkthrough

Walkthrough

The AirNow app now fetches observations from a different endpoint, normalizes AQI and parameter data, handles unknown categories during rendering, replaces both WebP assets, and updates the manifest timestamp.

Changes

AirNow update

Layer / File(s) Summary
Normalize and render current observations
apps/airnow/airnow.star
Adds AQI category mapping, normalizes returned observations, selects normalized PM2.5 data, and renders an unknown-category message when needed.
Update AirNow app assets
apps/airnow/airnow.webp, apps/airnow/airnow@2x.webp, apps/airnow/manifest.yaml
Replaces both WebP binaries and updates the manifest timestamp.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant get_current_observation
  participant AirNow_API
  participant normalize_observation
  participant main
  get_current_observation->>AirNow_API: Request current observation
  AirNow_API-->>get_current_observation: Return raw observation items
  get_current_observation->>normalize_observation: Normalize each item
  normalize_observation-->>get_current_observation: Return standardized PM2.5 observation
  get_current_observation-->>main: Provide normalized observation
  main->>main: Render unknown category when category number is -1
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly states the main change: migrating the AirNow app to the 2026 observation API.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch airnow-2026-api

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/airnow/airnow.star`:
- Around line 29-37: Update the category lookup using CATEGORY_NAME_TO_NUMBER
and the downstream get_alert_colors flow so unknown aqiCategoryName values do
not become 0 and render as Hazardous. Represent unmapped categories explicitly
as -1, then handle -1 before calling get_alert_colors by skipping the
observation or using a distinct unknown presentation; preserve the existing
mapping and Hazardous behavior for recognized categories.
- Around line 53-84: The normalize_observation function must read AirNow’s
PascalCase fields: use DateObserved, HourObserved, LocalTimeZone, ReportingArea,
ParameterName, AQI, and CategoryName in its lookups. Preserve the existing hour
parsing, ozone normalization, category-number mapping, and output structure so
get_current_observation can match PM2.5 correctly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 36efc771-db65-46db-80b1-e4f14173cb3e

📥 Commits

Reviewing files that changed from the base of the PR and between 96e64e6 and 42499a3.

📒 Files selected for processing (4)
  • apps/airnow/airnow.star
  • apps/airnow/airnow.webp
  • apps/airnow/airnow@2x.webp
  • apps/airnow/manifest.yaml

Comment thread apps/airnow/airnow.star
Comment thread apps/airnow/airnow.star
Comment on lines +53 to +84
return "https://www.airnowapi.org/aq/observation/current/ziplatlong/?format=application/json&latitude={lat}&longitude={lng}&api_key={api_key}".format(
lat = lat,
lng = lng,
api_key = api_key,
)

def normalize_observation(raw):
hour_raw = raw.get("hourObserved", 0)
if type(hour_raw) == "string":
hour = int(hour_raw.split(":")[0])
else:
hour = int(hour_raw)

category_name = raw.get("aqiCategoryName", "")
category_number = CATEGORY_NAME_TO_NUMBER.get(category_name, 0)

parameter_name = raw.get("parameterName", "")
if parameter_name == "OZONE":
parameter_name = "O3"

return {
"DateObserved": raw.get("dateObserved", ""),
"HourObserved": hour,
"LocalTimeZone": raw.get("localTimeZone", ""),
"ReportingArea": raw.get("reportingAreaName", ""),
"ParameterName": parameter_name,
"AQI": raw.get("nowcastAQI", raw.get("aqi", -1)),
"Category": {
"Number": category_number,
"Name": category_name,
},
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
printf '\n== apps/airnow/airnow.star (excerpt) ==\n'
cat -n apps/airnow/airnow.star | sed -n '1,220p'

printf '\n== repo search for ziplatlong / AirNow field names ==\n'
rg -n --hidden -S 'ziplatlong|hourObserved|dateObserved|localTimeZone|reportingAreaName|aqiCategoryName|nowcastAQI|CategoryName|ReportingArea' .

Repository: tronbyt/apps

Length of output: 9086


🌐 Web query:

AirNow API ziplatlong current observation JSON field names DateObserved HourObserved LocalTimeZone ReportingArea ParameterName AQI CategoryNumber CategoryName

💡 Result:

As of July 20, 2026, the AirNow API has recently undergone changes. In June 2026, AirNow retired certain legacy API endpoints for current observations and forecasts and replaced them with updated versions [1][2]. For legacy integrations, the response format for current observations typically included the following fields: DateObserved, HourObserved, LocalTimeZone, ReportingArea, ParameterName, AQI, and a nested Category object containing Number and Name [3][4]. However, the new 2026 API endpoints (e.g., /aq/observation/current/ziplatlong) have introduced changes to the output format [2]. Key changes noted in updated technical implementations include: - Field name changes from PascalCase (e.g., dateObserved) to camelCase (e.g., dateObserved) [1][2]. - Replacement of the AQI field with nowcastAQI [1][2]. - Changes to the Category structure and normalization of parameter names (e.g., OZONE to O3) [2]. - Conversion of HourObserved from a string format (e.g., 15:00) to an integer (e.g., 15) [2]. Developers should refer to the official AirNow API documentation for the most current schemas, as the transition to these new endpoints is mandatory following the retirement of the legacy services on September 30, 2026 [2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail
cat -n apps/airnow/airnow.star | sed -n '1,180p'

Repository: tronbyt/apps

Length of output: 6822


🌐 Web query:

site:airnowapi.org ziplatlong current observation JSON DateObserved HourObserved LocalTimeZone ReportingArea ParameterName AQI CategoryNumber CategoryName

💡 Result:

The AirNow API provides current air quality observations by zip code or latitude/longitude through its web services [1]. The JSON output for these observations typically includes the following fields: DateObserved: The date the air quality measurement was observed [2][3]. HourObserved: The hour the measurement was observed [2][3]. LocalTimeZone: The local time zone associated with the reporting area [2]. ReportingArea: The name of the city or region for which the air quality data is reported [2]. ParameterName: The specific pollutant measured, such as OZONE, PM2.5, or PM10 [2][3]. AQI: The calculated Air Quality Index value [2]. CategoryNumber: An integer (1-6) representing the AQI health category [4]. CategoryName: The descriptive name corresponding to the AQI category (e.g., Good, Moderate, Unhealthy) [4]. When requesting current observations by zip code or latitude/longitude, the API identifies the relevant Reporting Area assigned by local air quality agencies and returns the peak AQI value for that area [5][6]. Note that these observations are preliminary and are intended for public reporting of the AQI, not for regulatory use [7]. The AQI categories are standardized, ranging from 1 (Good) to 6 (Hazardous) [4].

Citations:


🌐 Web query:

site:developer.airnowapi.org ziplatlong observation current response fields

💡 Result:

AirNow’s official docs only say that Current Observations by Zip Code / LatLong return current AQI values and categories for a reporting area; the overview page doesn’t publish the full field schema there. The fact sheet says outputs can be returned in CSV, JSON, or XML, and that each service’s documentation page defines its input/output parameters. (docs.airnowapi.org)

The FAQ also notes these web services return the peak AQI for a reporting area and are meant for end-user lookup, not bulk database building. (docs.airnowapi.org)

If you want, I can help find the exact endpoint docs/sample response for the zip/lat-long observation call.

Citations:


Use AirNow’s PascalCase response keys here. normalize_observation is reading lowercase names (dateObserved, hourObserved, localTimeZone, reportingAreaName, parameterName, nowcastAQI, aqiCategoryName), but current observation payloads use DateObserved, HourObserved, LocalTimeZone, ReportingArea, ParameterName, AQI, and CategoryName. These lookups will miss and the app will fall back to the default values, so get_current_observation won’t match PM2.5.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/airnow/airnow.star` around lines 53 - 84, The normalize_observation
function must read AirNow’s PascalCase fields: use DateObserved, HourObserved,
LocalTimeZone, ReportingArea, ParameterName, AQI, and CategoryName in its
lookups. Preserve the existing hour parsing, ozone normalization,
category-number mapping, and output structure so get_current_observation can
match PM2.5 correctly.

tavdog and others added 2 commits July 20, 2026 17:45
Unmapped aqiCategoryName values now use -1 and show an explicit error instead of falling through to the Hazardous color path.

Co-authored-by: Cursor <cursoragent@cursor.com>
@tavdog
tavdog merged commit ca683aa into main Jul 21, 2026
5 checks passed
@tavdog
tavdog deleted the airnow-2026-api branch July 21, 2026 00:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant