Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
There was a problem hiding this comment.
Pull request overview
Adds a dedicated /air-quality monitoring page (with map + stats) and a backing API endpoint so the team can inspect sensor.community crawler data and notification/message counts without waiting for notifications to trigger.
Changes:
- Added
/air-qualityUI with Leaflet grid-cell visualization, summary stats, and recent messages fromsensor-community. - Added
/api/air-quality/statusroute that reads the latest 24h readings from GCS/local FS, computes per-cell EAQI (NowCast), and returns DB counts. - Centralized EAQI/NowCast math in
@oboapp/sharedand updated ingest to re-export it; added a MongoDB index fornotificationMatches.messageSnapshot.source.
Reviewed changes
Copilot reviewed 11 out of 12 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| web/package.json | Adds @google-cloud/storage dependency for server-side GCS reads. |
| web/components/AirQualityMap.tsx | New client component rendering EAQI grid cells on a Leaflet map. |
| web/app/api/air-quality/status/route.ts | New API route: loads readings (GCS/local), computes per-cell EAQI, returns freshness + DB counts. |
| web/app/air-quality/page.tsx | New monitoring page UI (stats, map, legend, recent alerts, message detail). |
| web/app/air-quality/layout.tsx | Imports Leaflet CSS at route-segment layout scope. |
| web/.env.example | Documents GCS_READINGS_BUCKET and LOCAL_READINGS_PATH for the monitoring page. |
| shared/src/index.ts | Exposes the shared EAQI module via package index export. |
| shared/src/air-quality/aqi.ts | New shared EAQI/NowCast implementation (PM2.5/PM10 bands + labels/categories). |
| ingest/lib/air-quality/constants.ts | Re-exports NOWCAST_MIN_WEIGHT from @oboapp/shared. |
| ingest/lib/air-quality/aqi.ts | Replaces local EAQI implementation with re-exports from @oboapp/shared. |
| db/src/indexes.ts | Adds MongoDB index for notificationMatches.messageSnapshot.source used by the new status API. |
| pnpm-lock.yaml | Lockfile updates for added dependency and resulting resolution changes. |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
vbuch
commented
Apr 3, 2026
Owner
Author
|
@copilot address comments in this PR |
Contributor
All review comments addressed in c3caa38:
|
vbuch
added a commit
that referenced
this pull request
Apr 3, 2026
…ndling, route tests, NEXT_PUBLIC_LOCALITY docs - Replace Math.min/max spread over validTimestamps with a single loop to avoid hitting the JS argument limit on large 24h datasets - Treat calculateNowCastAqi returning 0 as "no data" (aqi/aqiLabel/aqiCategory → null); sort null-aqi cells last; derive maxAqi via .find() instead of cells[0] - Update AirQualityCell/AqiCell interfaces to allow null aqi/aqiLabel/aqiCategory; fix AirQualityMap tooltip and CATEGORY_COLORS lookup for null aqiCategory - Add /api/air-quality/status route tests (10 tests: locality validation, no-data path, with-data shape, null-aqi propagation, staleness, db stats) - Document NEXT_PUBLIC_LOCALITY in the air-quality-monitoring config table Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add /air-quality page with map, AQI overview, and notification stats - Add /api/air-quality/status route serving current AQI, GCS data window, and notification counts - Move AQI helpers from ingest into shared/src/air-quality/aqi.ts and re-export from ingest - Add AirQualityMap component using Mapbox - Add DB index for air quality station queries - Update web/package.json and pnpm-lock.yaml for new dependencies - Document required env vars in web/.env.example
Agent-Logs-Url: https://github.com/vbuch/oboapp/sessions/e7cf278e-1370-4292-a14e-9354592c1e0e Co-authored-by: vbuch <5102057+vbuch@users.noreply.github.com>
Agent-Logs-Url: https://github.com/vbuch/oboapp/sessions/71c896d6-4264-402d-8247-53da19f3493e Co-authored-by: vbuch <5102057+vbuch@users.noreply.github.com>
…ndling, route tests, NEXT_PUBLIC_LOCALITY docs - Replace Math.min/max spread over validTimestamps with a single loop to avoid hitting the JS argument limit on large 24h datasets - Treat calculateNowCastAqi returning 0 as "no data" (aqi/aqiLabel/aqiCategory → null); sort null-aqi cells last; derive maxAqi via .find() instead of cells[0] - Update AirQualityCell/AqiCell interfaces to allow null aqi/aqiLabel/aqiCategory; fix AirQualityMap tooltip and CATEGORY_COLORS lookup for null aqiCategory - Add /api/air-quality/status route tests (10 tests: locality validation, no-data path, with-data shape, null-aqi propagation, staleness, db stats) - Document NEXT_PUBLIC_LOCALITY in the air-quality-monitoring config table Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
GCS_READINGS_BUCKET is set in CI, causing the route to attempt a real GCS connection instead of falling back to the local filesystem. Mock the Storage class (using a class literal so new Storage() works) and supply test data via both paths so tests pass regardless of whether GCS_READINGS_BUCKET is set. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The previous test placed both readings at the same lat/lng, so they landed in one cell — the second mockReturnValueOnce(0) was never consumed and the sort assertion was skipped behind a conditional. Now uses (42.61, 23.21) → r0c0 and (42.75, 23.45) → r4c5 so two cells are created, calculateNowCastAqi is called once per cell, and the ordering assertion runs unconditionally. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…T to GCS_GENERIC_BUCKET Agent-Logs-Url: https://github.com/vbuch/oboapp/sessions/68cc96d3-9bdc-492e-9b94-74f230f0b5ac Co-authored-by: vbuch <5102057+vbuch@users.noreply.github.com>
Agent-Logs-Url: https://github.com/vbuch/oboapp/sessions/60ddc3a1-a428-48b7-9064-fa717bc175f5 Co-authored-by: vbuch <5102057+vbuch@users.noreply.github.com>
80fb993 to
0c129a3
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
colors→cellStylein AirQualityMap.tsxassignToCell()fromgrid.find()tofor...ofloop in route.tsStorageinstance at module scope in route.tsNumber.isFinite()instead of!Number.isNaN()in shared/src/air-quality/aqi.tsshared/src/air-quality/aqi.ts(27 tests)/air-qualitymonitoring page and update config table in docs/features/air-quality-monitoring.mdmaxAqiCategoryto be null (not "good") when there are no cells/no datafitBoundsto only run on first draw, not on every periodic refetchassignToCellto use exclusive north/east edge comparisons (except outermost boundary) — maxNorth/maxEast computed once per grid, not per readingGCS_READINGS_BUCKET→GCS_GENERIC_BUCKETin route.ts, .env.example, and test commentsr.timestamponce per reading usingDate.parse()in a single unified loopexport const runtime = "nodejs"after imports to prevent accidental Edge deploymentvi.stubEnv/vi.unstubAllEnvs()to route tests.env.examplecomment about GCS authenticationGCS_GENERIC_BUCKETis not configured (local filesystem fallback restricted to non-production); add test coverage for this case