A modular FastAPI backend designed to retrieve and standardize air quality data across the Jammu & Kashmir region for the BreatheOSS clients. The system aggregates data from multiple providers: OpenMeteo for satellite-based estimates and AirGradient for high-precision ground sensors. It currently calculates AQI based on both Indian (CPCB) and US EPA standards.
[1] The system accepts a dictionary of raw pollutant values. Before any math occurs, the system sanitizes the input keys using a robust mapping strategy.
-
It handles variations in naming conventions (e.g., mapping "pm2.5", "pm25", or "pm2_5" all to the internal standard pm2_5).
-
This ensures that no data is dropped due to typo-sensitivity or API inconsistencies.
[2] The system calculates AQI using two major standards: Indian (CPCB) and US EPA. Each has specific unit requirements:
-
Indian AQI: PM2.5, PM10, NO2, SO2 are maintained in µg/m³. Carbon Monoxide (CO) and Methane (CH4) are divided by 1000 to convert from µg/m³ to mg/m³.
-
US EPA AQI: Gases (NO2, SO2, CO) are converted from µg/m³ to parts-per-billion (ppb) or parts-per-million (ppm) based on molar volume at 25°C.
-
Methane (CH4) is tracked but currently not included in AQI calculations for either standard.
[3] Once units are standardized, the system calculates an individual Sub-Index for each pollutant using Linear Interpolation.
-
The system scans the
AQI_BREAKPOINTS(Indian) orUS_BREAKPOINTSconfiguration to find the specific range [Clo,Chi] that the current concentration falls into. -
The system applies the standard AQI formula:
I=[(Chi−Clo)(Ihi−Ilo)×(C−Clo)]+Ilo
Where:
- I: The calculated AQI sub-index.
- C: The current pollutant concentration.
- Clo/Chi: The concentration breakpoints (lower and upper bounds).
- Ilo/Ihi: The corresponding AQI index breakpoints.
The code includes failsafes: if a value exceeds the maximum defined breakpoint, it is capped at 500; if it is below the minimum, it defaults to 0.
[4] The final Air Quality Index is not an average of the pollutants.
- The system identifies the maximum value among all calculated sub-indices.
- The pollutant responsible for this highest value is flagged as the
main_pollutant. - Both the Indian
aqiand theus_aqiare calculated and returned in the final payload.
api/
├── main.py # Entry point
├── Procfile
├── requirements.txt
├── .env
├── breathe.db # Local SQLite database (auto-generated)
└── app/
├── __init__.py
├── api/ # Routes & endpoints
│ ├── __init__.py
│ └── routes.py
├── core/ # Config, database, conversions
│ ├── __init__.py
│ ├── config.py
│ ├── database.py
│ └── conversions.py
├── data/ # Static JSON data files
│ ├── __init__.py
│ ├── zones.json
│ ├── nodes.json # AirGradient node configurations
│ ├── sensor_info.json # Hardware sensor metadata
│ └── aqi_breakpoints.json
└── services/ # Data fetching & processing
├── __init__.py
└── fetchers.py
main.pyInitializes the FastAPI application and starts a background scheduler. This scheduler runs every 15 minutes to fetch fresh data for all zones, ensuring the app serves cached data instantly without hitting API rate limits during user requests.app/api/routes.pyGenerates all/aqi/<zone_id>endpoints dynamically based onzones.json.app/core/database.pyHandles data persistence using PostgreSQL (production) or SQLite (local development). Stores sensor readings, temperature, and humidity for historical graph plotting.app/core/config.pyLoads environment variables and configuration fromzones.json,nodes.json, andaqi_breakpoints.json.app/core/conversions.pyHandles the mathematics of AQI calculation, unit conversions (e.g., CO from µg/m³ to mg/m³), and Indian CPCB sub-index mapping along with US EPA sub-index mapping.app/services/fetchers.pyContains sophisticated data fetching logic:fetch_openmeteo_live: Queries satellite-based pollutant data.fetch_multi_node_airgradient: Averages data from multiple AirGradient nodes (e.g., Jammu and Srinagar), with built-in spike detection, grace periods for erratic nodes, and stale data protection.get_zone_data: Implements a multi-layered caching strategy. It prioritize ground sensors but automatically falls back to satellite data if physical sensors are offline or reporting unrealistic spikes.
app/data/zones.jsonContains zone definitions including names, coordinates, andzone_type(hills,urban,industrial) for customized AQI calculation.app/data/nodes.jsonMaps specific physical sensor locations (location_id) to city-level zones.app/data/sensor_info.jsonStores hardware-specific metadata for ground sensors (model, coordinates, installation dates).app/data/aqi_breakpoints.jsonOfficial Indian AQI breakpoint tables.
- python ≥ 3.10
- fastapi
- httpx
- python-dotenv
- uvicorn
- psycopg2-binary (for Postgres support)
AIRGRADIENT_TOKEN=yourkeyhere
JAMMU_AIRGRADIENT_TOKEN=yourkeyhere
DATABASE_URL=postgres://... (optional, defaults to local sqlite)
From the api directory:
uvicorn main:app --reload
-
Zone Data: Access data for a specific zone using its dynamic ID (e.g.,
srinagar,jammu_city,leh):GET /aqi/<zone_id> -
Generic Lookup:
GET /aqi/zone/{zone_id} -
List All Zones:
GET /zones -
Hardware Sensor Metadata:
GET /sensor-info
The project is designed to be data-driven. Adding a new town or district does not require changing Python code; you simply add a new entry to zones.json. Similarly, if government standards change, updating aqi_breakpoints.json will instantly update the calculation logic across the entire application.