-
Notifications
You must be signed in to change notification settings - Fork 0
Heatmap Implementation #81
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: staging
Are you sure you want to change the base?
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
📝 WalkthroughWalkthroughThis update introduces a heatmap overlay feature for air quality data on the frontend map, adds supporting backend API and service logic, and implements Python scripts for data processing and MongoDB integration. The UI is simplified in several areas, with info panels removed and feature cards streamlined. Additional dependencies and configuration files are included. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant UI (LeafletMap)
participant HeatmapService
participant API (/api/heatmap-data)
participant MongoDB
User->>UI (LeafletMap): Clicks "Show Heatmap"
UI (LeafletMap)->>HeatmapService: fetchHeatmapData()
alt Cache valid
HeatmapService-->>UI (LeafletMap): Return cached data
else Cache expired or force refresh
HeatmapService->>API (/api/heatmap-data): GET /api/heatmap-data
API (/api/heatmap-data)->>MongoDB: Query latest predictions
MongoDB-->>API (/api/heatmap-data): Return data
API (/api/heatmap-data)-->>HeatmapService: Return heatmap data
HeatmapService-->>UI (LeafletMap): Return fresh data
end
UI (LeafletMap)->>UI (LeafletMap): Render HeatmapOverlay with data
Possibly related PRs
Poem
Note ⚡️ AI Code Reviews for VS Code, Cursor, WindsurfCodeRabbit now has a plugin for VS Code, Cursor and Windsurf. This brings AI code reviews directly in the code editor. Each commit is reviewed immediately, finding bugs before the PR is raised. Seamless context handoff to your AI code agent ensures that you can easily incorporate review feedback. Note ⚡️ Faster reviews with cachingCodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 30th. To opt out, configure Warning Review ran into problems🔥 ProblemsErrors were encountered while retrieving linked issues. Errors (1)
✨ Finishing Touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 11
🔭 Outside diff range comments (1)
frontend/src/app/categorize/page.tsx (1)
111-144
: 💡 Verification agent❓ Verification inconclusive
Wrap manual-input parsing in a try/catch to prevent unhandled UI crashes
handleManualSubmit
throws if any row is malformed. Because the call site (onClick
) is not wrapped in an error boundary, the entire component will crash on the first bad line a user pastes. Catch the error and surface it viatoast
instead of throwing.
Wrap manual‐input parsing in try/catch and surface errors via toast
To prevent the component from crashing on a malformed line, wrap your parsing (and the rest of the submit logic) in a
try/catch/finally
. MovesetLoading(true)
before thetry
, catch anyError
thrown during parsing, calltoast
with its message, and ensuresetLoading(false)
always runs.• File: frontend/src/app/categorize/page.tsx
• Function:handleManualSubmit
Suggested diff:
const handleManualSubmit = async () => { - const coordinates = manualInput - .split("\n") - .map((line) => line.trim()) - .filter((line) => line) - .map((line) => { - const [lat, lng] = line.split(",").map((num) => Number.parseFloat(num.trim())) - if (isNaN(lat) || isNaN(lng)) { - throw new Error("Invalid coordinates format") - } - return { lat, lng } - }) - - setLoading(true) - const newSites: SiteCategoryInfo[] = [] - - for (const coord of coordinates) { - /* …rest of logic… */ - } - - setManualInput("") - setLoading(false) + setLoading(true) + try { + const coordinates = manualInput + .split("\n") + .map((line) => line.trim()) + .filter((line) => line) + .map((line) => { + const [lat, lng] = line + .split(",") + .map((num) => Number.parseFloat(num.trim())) + if (isNaN(lat) || isNaN(lng)) { + throw new Error("Invalid coordinates format") + } + return { lat, lng } + }) + + const newSites: SiteCategoryInfo[] = [] + for (const coord of coordinates) { + if (!sites.some((s) => s.lat === coord.lat && s.lng === coord.lng)) { + const newSite = await fetchSiteCategory(coord.lat, coord.lng) + if (newSite) newSites.push(newSite) + } + } + + if (newSites.length > 0) { + setSites((prev) => [...prev, ...newSites]) + toast({ title: "Success", description: `Processed ${newSites.length} sites` }) + } + + setManualInput("") + } catch (error) { + toast({ + title: "Error processing input", + description: error instanceof Error ? error.message : "Unexpected error", + status: "error", + }) + } finally { + setLoading(false) + }
🧹 Nitpick comments (18)
frontend/src/components/map/HeatmapLegend.tsx (1)
14-31
: Clean, accessible legend implementationThe legend component is well-implemented with:
- Good semantic structure with appropriate labels
- Accessible color indicators with text labels
- Responsive layout using flex
- Helpful explanation of the "USG" abbreviation
- Consistent typography and spacing
Consider adding an ARIA label to the color squares for improved screen reader accessibility.
<span className="inline-block w-4 h-4 rounded" style={{ background: item.color }} + aria-label={`${item.label} color indicator`} />
frontend/src/lib/mongodb.ts (2)
1-8
: MongoDB connection setup looks good, but consider environment file naming consistency.The error message in line 7 refers to
.env.local
, but the PR objective mentions getting a.env
file. This could lead to confusion for developers following the setup instructions.- throw new Error('Please add your MongoDB connection string to .env.local') + throw new Error('Please add your MongoDB connection string to .env')
3-8
: Add URI format validation for better error handling.The code checks if the URI exists but doesn't validate its format. Adding basic validation would help catch configuration errors early.
const uri = process.env.MONGODB_URI const options: MongoClientOptions = {}; if (!process.env.MONGODB_URI) { throw new Error('Please add your MongoDB connection string to .env.local') +} else if (!uri.startsWith('mongodb://') && !uri.startsWith('mongodb+srv://')) { + throw new Error('Invalid MongoDB connection string format') }frontend/src/ui/loading.tsx (1)
10-18
: Consider making the loading text optional for more versatile usage.The current implementation always displays "Loading..." text. Making this text optional would allow the component to be used in more contexts, such as inline loading indicators.
-const LoadingSpinner: React.FC<{ size?: 'sm' | 'md' | 'lg' }> = ({ size = 'md' }) => { +const LoadingSpinner: React.FC<{ size?: 'sm' | 'md' | 'lg', showText?: boolean }> = ({ + size = 'md', + showText = true +}) => { const sizeClasses = { sm: 'w-4 h-4 border-2', md: 'w-6 h-6 border-2', lg: 'w-8 h-8 border-3' }; return ( <div className="flex items-center justify-center"> <div className={`${sizeClasses[size]} border-t-blue-500 border-r-blue-500 border-b-transparent border-l-transparent rounded-full animate-spin`} ></div> - <span className="ml-2 text-sm text-gray-600">Loading...</span> + {showText && <span className="ml-2 text-sm text-gray-600">Loading...</span>} </div> ); };frontend/src/components/feature-card.tsx (1)
9-19
: Clean implementation with effective hover effect.The component implementation is clean and focused, with a nice hover effect that adds subtle depth without being overly complex. The use of Tailwind classes is consistent and effective.
One suggestion: add a brief comment about the simplified design for future maintainers.
+// Simplified card component that focuses on core content without image backgrounds or external links export function FeatureCard({ title, description, Icon }: FeatureCardProps) { return ( <div className="rounded-lg border bg-card p-6 transition-all hover:shadow-lg"> <div className="mb-4"> <Icon className="h-12 w-12 text-blue-500" /> </div> <h3 className="mb-2 text-xl font-semibold">{title}</h3> <p className="text-muted-foreground">{description}</p> </div> ) }
frontend/src/components/Controls/MapOverlayToggle.tsx (1)
14-26
: Enhance accessibility for screen readers.The component provides good visual feedback with color changes and has a descriptive title attribute. However, adding an aria-label would improve accessibility for screen readers.
<Button onClick={onClick} variant="outline" size="icon" className={`${ isActive ? 'bg-orange-500 hover:bg-orange-600' : 'bg-blue-500 hover:bg-blue-600' } text-white flex items-center justify-center transition-colors`} title={`${isActive ? 'Hide' : 'Show'} Air Quality Heatmap`} + aria-label={`${isActive ? 'Hide' : 'Show'} Air Quality Heatmap`} + aria-pressed={isActive} > <Flame className="h-5 w-5" /> </Button>frontend/src/app/categorize/page.tsx (3)
20-26
: Remove stale commented-out dynamic import to keep the module tidyThe dynamic import for
useMapEvents
is now obsolete because the hook is imported directly on line 8. Leaving large commented blocks around clutters the file and can confuse future readers about the preferred import strategy.-// const useMapEvents = dynamic( -// () => import("react-leaflet").then((mod) => mod.useMapEvents), -// { ssr: false } -// );
81-100
: Fetch all CSV locations concurrently to avoid UI stalls
handleFileUpload
awaits the API call for every coordinate sequentially.
With large CSVs this causes long blocking times and leaves the spinner up for an unnecessarily long period. Switching toPromise.all
will parallelise the network requests and drastically improve perceived performance.- for (const location of locations) { - const response = await getSiteCategory(location.lat, location.lng) - newSites.push({ - ...location, - category: response.site["site-category"].category, - area_name: response.site["site-category"].area_name, - }) - } + const responses = await Promise.all( + locations.map(({ lat, lng }) => getSiteCategory(lat, lng)) + ) + + responses.forEach((response, idx) => { + const { lat, lng } = locations[idx] + newSites.push({ + lat, + lng, + category: response.site["site-category"].category, + area_name: response.site["site-category"].area_name, + }) + })
254-259
: Tailwind class names look off – spinner & text may not render correctly
pr-50
,font-xl
, andtext-white-700
are not valid Tailwind classes (should bepr-12
,text-xl
,text-white
, etc.). In addition, usingfixed
on the icon and text breaks the centering flex container provides.-<div className="bg-white p-4 pr-50 rounded-lg flex items-center space-x-2"> - <Loader2 className="fixed right-3 w-4 h-4 animate-spin" /> - <span className="font-bold font-xl text-white-700 fixed right-11">Processing...</span> +<div className="bg-white p-4 pr-12 rounded-lg flex items-center space-x-3"> + <Loader2 className="w-4 h-4 animate-spin text-blue-600" /> + <span className="font-bold text-xl text-gray-800">Processing...</span>frontend/scripts/model.py (2)
33-38
: Guard against zero-distance blow-ups in IDW weightsAlthough
1e-10
avoids division-by-zero, it does not stop extremely large weights when a grid point coincides with a monitoring site. This can dominate the interpolation and produce NaNs downstream.-weights = 1 / (dist ** power + 1e-10) +# Replace exact zeros with a small epsilon before the power operation +dist[dist == 0] = 1e-5 +weights = 1 / (dist ** power)
74-80
: Consider deterministic grids instead of purely random samplingRandom points (
np.random.uniform
) provide uneven spatial coverage run-to-run and hamper visual comparison over time. A regular lat/lon mesh (or Poisson-disk sampling) gives reproducible, uniform input to the IDW and smoother heatmaps.frontend/src/components/map/HeatmapOverlay.tsx (1)
127-129
: Legend component imported but never rendered
HeatmapLegend
is imported on line 6 yet the component returnsnull
. If the UX requires a legend toggle, mount the legend conditionally (e.g., inside aPane
or as a Leaflet control) so users can interpret colour scales.frontend/src/components/map/MapComponent.tsx (2)
119-139
: Clean implementation of heatmap toggle with proper loading states.The function handles initial data loading and subsequent toggles efficiently. Consider removing the console.log statement on line 127 before deploying to production.
- console.log("Heatmap data loaded:", data.length, "points")
141-161
: Effective map style handling with proper layer management.The implementation correctly removes existing tile layers before adding new ones. For production code, consider removing the console.log on line 142.
- console.log("Changing map style to:", style)
frontend/scripts/config.py (1)
6-11
: Environment loading is properly implemented but contains debug print statements.The environment variable loading works correctly, but the print statements should be removed before moving to production.
-print(BASE_DIR) -print(dotenv_path)frontend/src/app/page.tsx (1)
46-46
: Image source path has changed with inconsistent capitalization.The image path has been updated from "/images/homemap.webp" to "/images/homeMAP.png", but the mixed case in "homeMAP.png" is inconsistent with typical naming conventions.
- <Image src="/images/homeMAP.png" alt="Air quality monitoring dashboard" fill className="object-cover" /> + <Image src="/images/homemap.png" alt="Air quality monitoring dashboard" fill className="object-cover" />frontend/src/components/map/LeafletMap.tsx (2)
744-764
: Implemented heatmap state and loading with correct hooks pattern.The state management and data fetching implementation follow React best practices. Consider removing the console.log statement on line 758 before deployment.
- console.log("heatMapData in LeafletMap:", data);
805-829
: Comprehensive heatmap UI with legend and timestamp information.The heatmap overlay, controls, and legend are well-positioned and provide useful context for users. The timestamp display is a valuable addition for data freshness awareness.
One potential improvement would be to handle the case where heatMapData is null but showHeatmap is true, which could briefly display an empty heatmap component.
- {showHeatmap && ( + {showHeatmap && heatMapData && (
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (5)
frontend/package-lock.json
is excluded by!**/package-lock.json
frontend/pnpm-lock.yaml
is excluded by!**/pnpm-lock.yaml
frontend/public/images/homeMAP.png
is excluded by!**/*.png
frontend/scripts/__pycache__/config.cpython-311.pyc
is excluded by!**/*.pyc
frontend/scripts/__pycache__/data.cpython-311.pyc
is excluded by!**/*.pyc
📒 Files selected for processing (31)
frontend/README.md
(1 hunks)frontend/next.config.ts
(1 hunks)frontend/package.json
(2 hunks)frontend/requirement.txt
(1 hunks)frontend/scripts/config.py
(1 hunks)frontend/scripts/data.py
(1 hunks)frontend/scripts/model.py
(1 hunks)frontend/src/app/about/page.tsx
(2 hunks)frontend/src/app/api/heatmap-data/route.ts
(1 hunks)frontend/src/app/categorize/page.tsx
(7 hunks)frontend/src/app/comingsoon/page.tsx
(0 hunks)frontend/src/app/layout.tsx
(1 hunks)frontend/src/app/locate/page.tsx
(3 hunks)frontend/src/app/models/page.tsx
(0 hunks)frontend/src/app/page.tsx
(5 hunks)frontend/src/app/reports/page.tsx
(15 hunks)frontend/src/components/Controls/ControlPanel.tsx
(1 hunks)frontend/src/components/Controls/MapOverlayToggle.tsx
(1 hunks)frontend/src/components/charts/AirQualityChart.tsx
(1 hunks)frontend/src/components/feature-card.tsx
(1 hunks)frontend/src/components/map/HeatmapLegend.tsx
(1 hunks)frontend/src/components/map/HeatmapOverlay.tsx
(1 hunks)frontend/src/components/map/LeafletMap.tsx
(5 hunks)frontend/src/components/map/MapComponent.tsx
(5 hunks)frontend/src/components/navigation/navigation.tsx
(1 hunks)frontend/src/lib/mongodb.ts
(1 hunks)frontend/src/lib/types.ts
(2 hunks)frontend/src/services/apiService.tsx
(0 hunks)frontend/src/services/heatmapService.tsx
(1 hunks)frontend/src/styles/globals.css
(1 hunks)frontend/src/ui/loading.tsx
(1 hunks)
💤 Files with no reviewable changes (3)
- frontend/src/services/apiService.tsx
- frontend/src/app/comingsoon/page.tsx
- frontend/src/app/models/page.tsx
🧰 Additional context used
🧬 Code Graph Analysis (4)
frontend/src/app/categorize/page.tsx (1)
frontend/src/components/Controls/FileUpload.tsx (1)
FileUpload
(24-118)
frontend/src/components/charts/AirQualityChart.tsx (1)
frontend/src/lib/types.ts (1)
SiteData
(150-178)
frontend/src/components/map/HeatmapOverlay.tsx (2)
frontend/src/services/heatmapService.tsx (1)
AirQualityDataPoint
(1-9)javascript/retrieveAirQualityData.js (1)
data
(4-4)
frontend/scripts/config.py (1)
frontend/scripts/model.py (1)
connect_mongo
(29-31)
🪛 Ruff (0.11.9)
frontend/scripts/data.py
32-32: Within an except
clause, raise exceptions with raise ... from err
or raise ... from None
to distinguish them from errors in exception handling
(B904)
🔇 Additional comments (54)
frontend/src/app/locate/page.tsx (4)
8-8
: Nice: Importing new icons for CSV and map export.
UsingDownload
andCamera
fromlucide-react
aligns with existing icon patterns and they're both actually used in the action buttons below.
130-130
: Comment updated for clarity.
Changing the comment to simply “Control Panel” keeps it concise and matches the rendered component.
158-158
: New Save Map button integration.
The “Save Map” button with theCamera
icon and properaria-label
follows accessibility best practices and mirrors the CSV export button.
159-159
: Draw Polygon button label is clear.
Dynamic text and styling for the drawing toggle maintain good UX and accessibility consistency.frontend/src/components/Controls/ControlPanel.tsx (1)
151-151
: Label text simplified appropriately.
“Minimum Distance (km) (Optional)” aligns with the updated payload interface. If further context is needed, consider adding a tooltip or placeholder hint.frontend/package.json (2)
31-31
: Verify newleaflet.heat
dependency compatibility.
Addingleaflet.heat@^0.2.0
is essential for the heatmap overlay, but please confirm it works seamlessly withreact-leaflet@^4.2.1
and that type definitions are available (or manually declared).
45-45
: Dev dependency update for@types/leaflet
.
Bumping to^1.9.17
aligns type definitions with the installed Leaflet version—good catch.frontend/src/components/navigation/navigation.tsx (1)
26-26
: Brand naming inconsistency detected.The navigation component displays "AirQo AI" as text, but the application metadata in layout.tsx has been updated to "AirQo Platform". This creates inconsistent branding across the application.
Consider updating this text to match the application title in layout.tsx for consistent branding:
- AirQo AI + AirQo Platformfrontend/src/styles/globals.css (1)
285-295
: LGTM! Good tooltip styling implementation.The custom tooltip styles add a clean, subtle design with appropriate semi-transparency and visual cues. The styling is consistent with modern UI practices and enhances the new heatmap visualization.
frontend/next.config.ts (1)
5-18
: Appropriate webpack configuration for client-side optimization.This webpack configuration correctly disables Node.js core module fallbacks for client-side code, which is essential when working with libraries that might reference these modules. This is particularly important for the heatmap implementation that likely uses leaflet.heat library.
frontend/src/components/charts/AirQualityChart.tsx (1)
35-36
: Improved PM2.5 data handlingThe change to use direct numeric values (
site.pm2_5?.value || 0
) instead of formatted strings aligns with best practices for chart data handling. Using raw numeric values for data processing and only formatting when displaying improves code consistency and prevents potential issues with string-to-number conversions.frontend/src/lib/types.ts (2)
14-14
: LGTM - Comment simplificationThe simplified comment for the
min_distance_km
property maintains clarity while being more concise.
188-192
: Well-structured HeatmapData interfaceThis new interface properly defines the data structure needed for the heatmap implementation, with appropriate types for geographic coordinates and PM2.5 values. The interface is clearly named and follows the same style as other interfaces in this file.
frontend/src/app/about/page.tsx (2)
5-5
: Updated icon importsThe import statement correctly includes the Twitter icon from lucide-react, replacing the previous Link icon, which aligns with the UI changes in the social media section.
107-107
: Standardized social icon usageReplacing the custom XLogo component with the standard Twitter icon from lucide-react improves consistency across the UI and reduces custom component overhead.
frontend/src/components/map/HeatmapLegend.tsx (3)
3-12
: Well-defined AQI gradient constantsThe AQI gradient constants are well-structured with appropriate color codes, labels, and PM2.5 value ranges following EPA standards. The comment also correctly notes that these match gradient colors used in the HeatmapOverlay component, which is important for consistency.
1-2
: LGTM - Import statementThe import statement is clean and includes only what's needed.
33-33
: LGTM - Component exportThe default export of the component follows React best practices.
frontend/src/lib/mongodb.ts (1)
19-31
: Well-structured connection handling for different environments.The code properly manages client connections differently between development and production environments. Using a global variable in development is a good practice to prevent connection leaks during hot module reloading.
frontend/src/ui/loading.tsx (1)
3-8
: Good use of TypeScript and component organization.The component is well-typed with clear props interface and proper organization of size variants using a mapping object. This makes the component both type-safe and maintainable.
frontend/src/components/feature-card.tsx (1)
3-7
: Good simplification of the component interface.The props interface has been streamlined to focus on essential properties only, removing
imageSrc
,href
, andopenInNewTab
. This simplification aligns well with the PR's UI improvement goals.frontend/src/components/Controls/MapOverlayToggle.tsx (2)
5-8
: Well-defined TypeScript interface with sensible defaults.The props interface is clearly defined with TypeScript, making the component API explicit. The
isActive
prop having a default value offalse
is a good practice for optional boolean props.
15-25
: Great use of conditional styling and smooth transitions.The component effectively uses conditional styling to provide clear visual feedback about the toggle state. The transition effect for color changes is a nice touch that enhances the user experience.
frontend/src/app/api/heatmap-data/route.ts (3)
4-81
: Excellent implementation of the GET API route for heatmap data.This API route effectively retrieves and processes heatmap data from MongoDB. The implementation includes:
- Good use of environment variables with sensible defaults
- Well-structured MongoDB aggregation pipeline that efficiently fetches the latest prediction per airqloud
- Proper error handling with detailed logging
- Clean data transformation to extract necessary heatmap points
- Appropriate HTTP status codes for different scenarios
23-43
: Well-designed MongoDB aggregation pipeline.The aggregation pipeline is particularly well-structured:
- Case-insensitive matching for airqloud names
- Efficient sorting and grouping to get the latest prediction per airqloud
- Good use of the $first operator to retrieve the complete document
73-80
: Comprehensive error handling approach.The error handling is thorough, with detailed logging that will help with debugging in production. Including the stack trace and error name in the console log while providing a simplified message to the client is a good practice.
frontend/src/app/reports/page.tsx (7)
59-63
: Good simplification of filter defaults.Changing the default filter values from "all" to empty strings simplifies the filter logic throughout the component.
321-323
: Consistent filter reset implementation.The reset of city and district to empty strings when country changes is consistent with the new filter approach.
332-356
: Cleaner filter application logic.The simplified filter application logic now checks for truthy values rather than comparing with "all", making the code more readable and maintainable.
376-380
: Consistent filter reset values.The reset filters function now uses empty strings consistently, which aligns with the default values defined earlier.
619-620
: Standardized category terminology.Replacing "Water Body" with "Urban Background" standardizes the terminology across the application.
1130-1132
: Simplified report introduction logic.The report introduction text now uses a cleaner approach to check for filter values.
1162-1683
: Consistent decimal precision across numeric displays.All numeric values now use one decimal place (.toFixed(1)) instead of two, which improves readability and provides consistent presentation of data throughout the report.
frontend/src/services/heatmapService.tsx (4)
1-15
: Well-defined interfaces for heatmap data.The type definitions are clear and include forward-thinking optional fields for future expansion. The separation between
AirQualityDataPoint
andHeatmapResponse
interfaces provides good structure.
16-56
: Robust caching implementation.The caching mechanism is well-implemented with:
- Clear type definitions
- Appropriate TTL (1 hour)
- Proper cache expiration checks
- Comprehensive error handling for cache operations
- Cache invalidation when expired
58-89
: Effective data fetching with fallback strategy.The
fetchHeatmapData
function follows best practices:
- Uses cache first unless refresh is forced
- Handles network errors gracefully
- Falls back to stale cache when network requests fail
- Includes appropriate logging
- Properly updates the cache with fresh data
91-94
: Useful utility for cache refresh.The
refreshHeatmapCache
function provides a clean API for forcing a refresh when needed.frontend/scripts/data.py (3)
3-17
: Good setup for API integration.The script properly imports necessary libraries and sets up logging. Environment variables are loaded correctly for API credentials.
51-63
: Thorough data validation for each record.The validation of data points is very thorough - checking types for latitude, longitude, and PM2.5 values before adding them to the records list. The try-except block for each record prevents one bad record from failing the entire batch.
77-82
: Useful standalone testing capability.The optional main block allows for easy testing of the data fetching function directly from the command line.
frontend/scripts/model.py (1)
66-70
: Confirm the GeoJSON attribute used for city matching
polygon_match = country_gdf[country_gdf["region"].str.lower() == city.lower()]
Many admin-2 GeoJSON files name the column
NAME_2
,district
, or similar. Hard-coding"region"
risks skipping every city silently. Please verify the schema of the shapefiles inpublic/shapefiles
or make the field configurable.frontend/src/components/map/MapComponent.tsx (3)
113-116
: Well-defined state variables with appropriate types.Good job using TypeScript's type system effectively with proper initial values for the state variables.
170-178
: Well-organized UI controls with loading feedback.The positioning and component organization creates a clean, accessible UI. The loading spinner provides good user feedback during asynchronous operations.
204-217
: Properly implemented click event handlers for markers.Good job adding event handlers that invoke the passed
onLocationClick
callback, making markers interactive and consistent with application behavior.frontend/scripts/config.py (2)
13-30
: Well-structured configuration classes using inheritance.Good object-oriented design with a base Config class and environment-specific subclasses. This provides a clean way to manage different environment configurations.
32-39
: Effective configuration selection with sensible defaults.The code properly selects the appropriate configuration based on the environment variable with a safe default to staging if not specified.
frontend/src/app/page.tsx (5)
5-5
: Updated imports with necessary icon components.The imports now include all the required Lucide icons used throughout the page.
99-100
: Simplified section styling for better visual consistency.The section styling has been updated to use a solid background instead of a gradient, which improves visual consistency across the site.
110-126
: Clean implementation of step cards with proper layout.The StepCard components are well-organized in a grid layout, providing a clear visual hierarchy for the process steps.
164-173
: Well-defined FeatureCard component with clean styling.The FeatureCard component has a clear interface and consistent styling. The hover effect adds a nice touch of interactivity.
175-186
: Clean StepCard component implementation.The StepCard component has a clear interface with proper styling for the numbering and content layout.
frontend/src/components/map/LeafletMap.tsx (3)
17-21
: Appropriate imports for heatmap functionality.The imports for heatmap-related components and services are correctly defined.
693-699
: Improved state management with proper useEffect pattern.Moving localStorage access to useEffect is the correct approach for client-side code, avoiding reference errors during server-side rendering.
794-794
: Proper use of selected map style for TileLayer.The TileLayer now correctly uses the mapStyle state variable from localStorage, ensuring consistent user preference across sessions.
numpy | ||
pandas | ||
scipy | ||
shapely | ||
geopandas | ||
pymongo | ||
python-dotenv | ||
requests |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Pin Python package versions for reproducibility.
Currently the requirements file lists bare package names without version specifiers, which can lead to unexpected breaks as dependencies evolve. To ensure stable environments, please pin each dependency to a known good version (e.g., numpy==1.23.5
, pandas==1.5.2
, etc.).
🤖 Prompt for AI Agents
In frontend/requirement.txt lines 1 to 8, the Python packages are listed without
version numbers, which can cause instability due to evolving dependencies.
Update each package entry to include a specific version number by appending
'==<version>' with a known stable version for each package to ensure
reproducible and stable environments.
@@ -155,3 +155,4 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file | |||
|
|||
For any questions or support, please contact us at [email protected] or visit our website [https://www.airqo.net](https://www.airqo.net). | |||
|
|||
# frontend |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Revise or remove redundant README header.
Appending # frontend
at the end duplicates the top-level heading and doesn’t add contextual information about this directory. Consider replacing it with a brief overview of the frontend folder or removing it entirely.
🤖 Prompt for AI Agents
In frontend/README.md at line 158, the header "# frontend" is redundant as it
duplicates the top-level heading. Remove this line or replace it with a concise
summary describing the purpose or contents of the frontend directory to provide
meaningful context.
"lucide-react": "^0.475.0", | ||
"mongodb": "5.9.2", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: mongodb
on frontend bundle.
Including the official mongodb
driver in client-side code can drastically increase bundle size and expose backend logic. If it’s only used in Next.js API routes, move it to a server-only dependency or mark it as external.
🤖 Prompt for AI Agents
In frontend/package.json at line 33, the `mongodb` package is included in the
frontend dependencies, which unnecessarily increases bundle size and risks
exposing backend logic. To fix this, move `mongodb` from the frontend
dependencies to a server-only area such as Next.js API routes or backend
dependencies, or configure it as an external dependency so it is not bundled
into the client-side code.
title: "AirQo Platform", | ||
description: "Air quality monitoring and forecasting tool", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Simplified metadata reflects product direction.
The updated metadata provides a clearer, more concise description of the application's purpose. However, this title change to "AirQo Platform" doesn't match the "AirQo AI" text used in the navigation component.
Ensure consistent branding across the application by using the same name in both the metadata and navigation component.
🤖 Prompt for AI Agents
In frontend/src/app/layout.tsx around lines 20 to 21, the metadata title is set
to "AirQo Platform" which is inconsistent with the "AirQo AI" text used in the
navigation component. To fix this, update the metadata title to match the
navigation component's branding by changing "AirQo Platform" to "AirQo AI" to
ensure consistent branding across the application.
def get_airquality_data(): | ||
""" | ||
Fetches air quality data from AirQo API and prepares a DataFrame. | ||
Uses actual sensor coordinates from 'siteDetails.site_category.latitude/longitude'. | ||
""" | ||
if not API_URL or not API_TOKEN: | ||
raise ValueError("Missing API URL or Token in environment variables") | ||
|
||
url = f"{API_URL}devices/readings/map?token={API_TOKEN}" | ||
try: | ||
response = requests.get(url, timeout=10) | ||
response.raise_for_status() | ||
except requests.exceptions.RequestException as e: | ||
logger.error(f"Failed to fetch data from API: {e}") | ||
raise RuntimeError(f"Failed to fetch data from API: {e}") | ||
|
||
data = response.json().get("measurements", []) | ||
|
||
# Confirm it's a list | ||
if not isinstance(data, list): | ||
logger.error("API did not return a list of records") | ||
raise ValueError("API did not return a list of records") | ||
|
||
records = [] | ||
|
||
for item in data: | ||
try: | ||
site = item.get("siteDetails", {}) | ||
site_cat = site.get("site_category", {}) | ||
|
||
lat = site_cat.get("latitude") | ||
lon = site_cat.get("longitude") | ||
pm = item.get("pm2_5", {}).get("value") | ||
|
||
if isinstance(lat, (float, int)) and isinstance(lon, (float, int)) and isinstance(pm, (float, int)): | ||
records.append({ | ||
"latitude": float(lat), | ||
"longitude": float(lon), | ||
"pm2_5": float(pm), | ||
"timestamp": item.get("time"), | ||
"city": site.get("city", "unknown").strip(), | ||
"country": site.get("country", "unknown").strip() | ||
}) | ||
except Exception as e: | ||
logger.warning(f"Skipping record due to error: {e}") | ||
|
||
if not records: | ||
logger.error("No valid air quality records with usable coordinates found") | ||
raise ValueError("No valid air quality records with usable coordinates found.") | ||
|
||
df = pd.DataFrame(records) | ||
|
||
# Log unique cities and countries for debugging | ||
logger.info(f"Unique Cities: {df['city'].unique()}") | ||
logger.info(f"Unique Countries: {df['country'].unique()}") | ||
|
||
return df |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Well-structured data fetching function with robust error handling.
The get_airquality_data
function:
- Validates required environment variables
- Includes appropriate request timeout
- Validates API response structure
- Carefully extracts and validates data points
- Handles and logs errors during processing
- Returns a clean pandas DataFrame
There's one improvement to make in the error handling pattern:
- raise RuntimeError(f"Failed to fetch data from API: {e}")
+ raise RuntimeError(f"Failed to fetch data from API: {e}") from e
Using from e
preserves the original exception's traceback, which is important for debugging.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
def get_airquality_data(): | |
""" | |
Fetches air quality data from AirQo API and prepares a DataFrame. | |
Uses actual sensor coordinates from 'siteDetails.site_category.latitude/longitude'. | |
""" | |
if not API_URL or not API_TOKEN: | |
raise ValueError("Missing API URL or Token in environment variables") | |
url = f"{API_URL}devices/readings/map?token={API_TOKEN}" | |
try: | |
response = requests.get(url, timeout=10) | |
response.raise_for_status() | |
except requests.exceptions.RequestException as e: | |
logger.error(f"Failed to fetch data from API: {e}") | |
raise RuntimeError(f"Failed to fetch data from API: {e}") | |
data = response.json().get("measurements", []) | |
# Confirm it's a list | |
if not isinstance(data, list): | |
logger.error("API did not return a list of records") | |
raise ValueError("API did not return a list of records") | |
records = [] | |
for item in data: | |
try: | |
site = item.get("siteDetails", {}) | |
site_cat = site.get("site_category", {}) | |
lat = site_cat.get("latitude") | |
lon = site_cat.get("longitude") | |
pm = item.get("pm2_5", {}).get("value") | |
if isinstance(lat, (float, int)) and isinstance(lon, (float, int)) and isinstance(pm, (float, int)): | |
records.append({ | |
"latitude": float(lat), | |
"longitude": float(lon), | |
"pm2_5": float(pm), | |
"timestamp": item.get("time"), | |
"city": site.get("city", "unknown").strip(), | |
"country": site.get("country", "unknown").strip() | |
}) | |
except Exception as e: | |
logger.warning(f"Skipping record due to error: {e}") | |
if not records: | |
logger.error("No valid air quality records with usable coordinates found") | |
raise ValueError("No valid air quality records with usable coordinates found.") | |
df = pd.DataFrame(records) | |
# Log unique cities and countries for debugging | |
logger.info(f"Unique Cities: {df['city'].unique()}") | |
logger.info(f"Unique Countries: {df['country'].unique()}") | |
return df | |
try: | |
response = requests.get(url, timeout=10) | |
response.raise_for_status() | |
except requests.exceptions.RequestException as e: | |
logger.error(f"Failed to fetch data from API: {e}") | |
raise RuntimeError(f"Failed to fetch data from API: {e}") from e |
🧰 Tools
🪛 Ruff (0.11.9)
32-32: Within an except
clause, raise exceptions with raise ... from err
or raise ... from None
to distinguish them from errors in exception handling
(B904)
🤖 Prompt for AI Agents
In frontend/scripts/data.py from lines 18 to 74, the error handling in the
except blocks raises new exceptions without preserving the original traceback.
To fix this, modify the raise statements to use "raise ... from e" syntax, for
example, "raise RuntimeError(...) from e" and similarly for other exceptions.
This change will maintain the original exception context and improve debugging.
data.forEach((point) => { | ||
if (point.latitude && point.longitude && point.pm2_5 !== undefined) { | ||
const circle = L.circle([point.latitude, point.longitude], { | ||
radius: 20, // Match the heatmap radius | ||
fillOpacity: 0, | ||
weight: 0, | ||
interactive: true | ||
}); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Circles layer inherits the same “0-is-falsy” issue
if (point.latitude && point.longitude …)
likewise skips 0-degree points. Apply the same finite-number check when creating interactive circles to keep tooltips in sync with the heat layer.
🤖 Prompt for AI Agents
In frontend/src/components/map/HeatmapOverlay.tsx around lines 77 to 85, the
condition checking point.latitude and point.longitude uses a truthy check that
skips valid 0 values. Replace these checks with a finite number validation
(e.g., Number.isFinite) for latitude and longitude before creating the circle
layers to ensure points at 0 degrees are included and tooltips remain consistent
with the heatmap layer.
const heatData = data | ||
.map((point) => | ||
point.latitude && point.longitude && point.pm2_5 !== undefined | ||
? [point.latitude, point.longitude, point.pm2_5] | ||
: null | ||
) | ||
.filter(Boolean) as Array<[number, number, number]>; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Latitude / longitude filter drops legitimate “0” coordinates – fix truthy check
The current predicate treats 0
as falsy, causing points on the equator or prime meridian to be discarded. This can erase large portions of West/Central Africa, South America, etc.
-const heatData = data
- .map((point) =>
- point.latitude && point.longitude && point.pm2_5 !== undefined
- ? [point.latitude, point.longitude, point.pm2_5]
- : null
- )
+const heatData = data
+ .map(({ latitude, longitude, pm2_5 }) =>
+ Number.isFinite(latitude) &&
+ Number.isFinite(longitude) &&
+ Number.isFinite(pm2_5)
+ ? [latitude, longitude, pm2_5]
+ : null,
+ )
🤖 Prompt for AI Agents
In frontend/src/components/map/HeatmapOverlay.tsx around lines 39 to 46, the
current filter uses a truthy check on latitude and longitude, which incorrectly
excludes valid coordinates with value 0. Update the condition to explicitly
check for null or undefined instead of using a truthy check, ensuring that
latitude and longitude values of 0 are included in the heatData array.
{/* Replace HeatmapOverlay with MapboxGLOverlay */} | ||
{showHeatmap && heatMapData && <MapboxGLOverlay data={heatMapData} />} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Heatmap overlay conditional rendering is correct, but the component import is commented out.
The conditional rendering logic is sound, but ensure the MapboxGLOverlay
component is properly imported (currently commented out on line 14).
Uncomment the import statement on line 14 to fix this issue.
🤖 Prompt for AI Agents
In frontend/src/components/map/MapComponent.tsx around lines 221 to 222, the
MapboxGLOverlay component is used for conditional rendering but its import
statement on line 14 is commented out. To fix this, uncomment the import
statement for MapboxGLOverlay on line 14 so the component is properly imported
and can be used without errors.
import { fetchHeatmapData } from "@/services/heatmapService" | ||
import MapOverlayToggle from "../Controls/MapOverlayToggle" | ||
import type { AirQualityDataPoint } from "@/services/heatmapService" | ||
import LoadingSpinner from "@/ui/loading" | ||
// import MapboxGLOverlay from './MapboxGLOverlay' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good additions for heatmap functionality, but note the commented import.
The imports for heatmap functionality are appropriate, but there's a commented import for MapboxGLOverlay
on line 14 while the component is actually used on line 222. This inconsistency should be resolved.
-// import MapboxGLOverlay from './MapboxGLOverlay'
+import MapboxGLOverlay from './MapboxGLOverlay'
🤖 Prompt for AI Agents
In frontend/src/components/map/MapComponent.tsx around lines 10 to 14, the
import for MapboxGLOverlay is commented out but the component is used later on
line 222. To fix this, uncomment the import statement for MapboxGLOverlay so
that the component is properly imported and can be used without causing errors.
def connect_mongo(): | ||
print("Current environment:", environment) | ||
print("MongoDB URI:", configuration.MONGO_URI_NETMANAGER) | ||
print("Database name:", configuration.DB_NAME_NETMANAGER) | ||
client = MongoClient(configuration.MONGO_URI_NETMANAGER) | ||
db = client[configuration.DB_NAME_NETMANAGER] | ||
return db |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
MongoDB connection function with debug output.
The connection function works correctly but contains debug print statements that should be replaced with proper logging before production deployment.
- print("Current environment:", environment)
- print("MongoDB URI:", configuration.MONGO_URI_NETMANAGER)
- print("Database name:", configuration.DB_NAME_NETMANAGER)
+ # Use a proper logging framework instead of print statements
+ import logging
+ logging.info(f"Current environment: {environment}")
+ logging.info(f"Database name: {configuration.DB_NAME_NETMANAGER}")
+ # Don't log sensitive information like connection URIs in production
Also, consider adding error handling for the MongoDB connection:
def connect_mongo():
import logging
logging.info(f"Current environment: {environment}")
logging.info(f"Database name: {configuration.DB_NAME_NETMANAGER}")
try:
client = MongoClient(configuration.MONGO_URI_NETMANAGER, serverSelectionTimeoutMS=5000)
# Verify connection
client.server_info()
db = client[configuration.DB_NAME_NETMANAGER]
return db
except Exception as e:
logging.error(f"Failed to connect to MongoDB: {e}")
raise
🤖 Prompt for AI Agents
In frontend/scripts/config.py around lines 41 to 47, replace the debug print
statements with proper logging calls using the logging module. Add error
handling by wrapping the MongoClient connection and database selection in a
try-except block, setting a server selection timeout, verifying the connection
with client.server_info(), logging any connection errors, and re-raising
exceptions to ensure failures are properly reported.
WHAT DOES THIS PR DO?
HOW DO I TEST OUT THIS PR?
Summary by CodeRabbit
New Features
UI Improvements
Bug Fixes
Chores