Skip to content

AirAwareLabs/AirAware

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

61 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

AirAware

Real-time Air Quality Monitoring System

A production-ready .NET Web API for monitoring air quality data from multiple stations, computing EPA-standard Air Quality Index (AQI) values, and providing real-time access to air quality metrics.


🎯 Overview

AirAware is a comprehensive backend system designed to collect, process, and serve air quality data from distributed monitoring stations. The system accepts readings from sensors or public feeds, computes standardized AQI values based on EPA guidelines, and provides RESTful APIs for data access.

Key Capabilities

  • βœ… Multi-station air quality monitoring
  • βœ… Real-time AQI computation (PM2.5 & PM10)
  • βœ… EPA-standard breakpoint calculations
  • βœ… Flexible data ingestion with raw payload storage
  • βœ… Geolocation support for stations
  • βœ… API key authentication for secure access
  • βœ… Comprehensive test coverage (89 unit tests)
  • βœ… RESTful API for managing stations and readings

πŸ—οΈ Architecture

Tech Stack

  • .NET 10 - Latest ASP.NET Core Web API
  • Entity Framework Core 10 - ORM with SQLite (production: PostgreSQL ready)
  • xUnit - Unit testing framework
  • Moq - Mocking framework for tests
  • GitHub Actions - CI/CD automation

Project Structure

AirAware/
β”œβ”€β”€ AirAware/                    # Main Web API project
β”‚   β”œβ”€β”€ Controllers/             # API endpoints
β”‚   β”‚   β”œβ”€β”€ StationController.cs     # Station management
β”‚   β”‚   └── ReadingController.cs     # Reading ingestion & retrieval
β”‚   β”œβ”€β”€ Models/                  # Domain entities
β”‚   β”‚   β”œβ”€β”€ Station.cs              # Monitoring station
β”‚   β”‚   β”œβ”€β”€ Reading.cs              # Sensor reading
β”‚   β”‚   └── AqiRecord.cs            # Computed AQI data
β”‚   β”œβ”€β”€ Services/                # Business logic
β”‚   β”‚   β”œβ”€β”€ AqiCalculator.cs     # EPA AQI computation
β”‚   β”‚   └── IAqiCalculator.cs       # Calculator interface
β”‚   β”œβ”€β”€ ViewModels/              # Request/Response DTOs
β”‚   β”œβ”€β”€ Data/                    # Database context
β”‚   β”‚   └── AppDbContext.cs
β”‚   └── Migrations/              # EF Core migrations
β”œβ”€β”€ AirAware.Tests/              # Comprehensive test suite
β”‚   β”œβ”€β”€ Services/                # Service layer tests
β”‚   β”œβ”€β”€ Controllers/             # API endpoint tests
β”‚   └── Models/                  # Domain model tests
β”œβ”€β”€ .github/workflows/           # CI/CD pipelines
└── README.md                    # This file

πŸ“Š Data Model

Entities

Station

Represents an air quality monitoring station with geolocation.

Field Type Description
Id Guid Unique identifier
Name string Station name
Latitude double Geographic latitude
Longitude double Geographic longitude
Provider string? Data provider name
Metadata string? JSON metadata for extensibility
Active bool Soft delete flag (default: true)
CreatedAt DateTime Creation timestamp

Reading

Raw air quality measurements from sensors.

Field Type Description
Id Guid Unique identifier
StationId Guid Foreign key to Station
Pm25 double PM2.5 concentration (Β΅g/mΒ³)
Pm10 double? PM10 concentration (Β΅g/mΒ³, optional)
RawPayload string? Original JSON payload from sensor
CreatedAt DateTime Reading timestamp

AqiRecord

Computed Air Quality Index values.

Field Type Description
Id Guid Unique identifier
ReadingId Guid Foreign key to Reading
StationId Guid Foreign key to Station
AqiValue int Overall AQI value (0-500+)
Category string EPA category (Good, Moderate, etc.)
Pm25Aqi int? PM2.5 AQI component
Pm10Aqi int? PM10 AQI component
Pm25Category string? PM2.5 category
Pm10Category string? PM10 category
ComputedAt DateTime Computation timestamp

πŸ”Œ API Endpoints

⚠️ Authentication Required: All API endpoints require an API key. Include the X-API-KEY header in all requests.

Example:

curl -H "X-API-KEY: your-api-key-here" http://localhost:5000/api/v1/stations

Station Management

GET /api/v1/stations

List all stations.

Response: 200 OK

[
  {
    "id": "uuid",
    "name": "Downtown Station",
    "latitude": 40.7128,
    "longitude": -74.0060,
    "provider": "PurpleAir",
    "active": true,
    "createdAt": "2026-02-08T10:00:00Z"
  }
]

GET /api/v1/stations/{id}

Get station by ID.

Response: 200 OK or 404 Not Found

POST /api/v1/stations

Create a new monitoring station.

Request Body:

{
  "name": "Downtown Station",
  "latitude": 40.7128,
  "longitude": -74.0060,
  "provider": "PurpleAir",
  "metadata": "{\"sensorType\":\"optical\"}"
}

Response: 201 Created

PUT /api/v1/stations/{id}

Update station (partial update supported).

Request Body:

{
  "name": "Updated Name",
  "active": false
}

Response: 200 OK or 404 Not Found

GET /api/v1/stations/{id}/aqi/latest

Get latest AQI data for a station.

Response: 200 OK

{
  "id": "uuid",
  "aqiValue": 101,
  "category": "Unhealthy for Sensitive Groups",
  "computedAt": "2026-02-08T10:15:00Z",
  "reading": {
    "id": "uuid",
    "pm25": 35.5,
    "pm10": 154,
    "createdAt": "2026-02-08T10:14:00Z"
  }
}

Reading Ingestion

GET /api/v1/readings

List all readings.

Response: 200 OK

GET /api/v1/readings/{id}

Get reading by ID.

Response: 200 OK or 404 Not Found

POST /api/v1/readings

Submit a new air quality reading.

Request Body:

{
  "stationId": "uuid",
  "pm25": 35.5,
  "pm10": 154,
  "rawPayload": "{\"sensor\":\"BME680\",\"temp\":22.5}"
}

Response: 201 Created

{
  "reading": {
    "id": "uuid",
    "stationId": "uuid",
    "pm25": 35.5,
    "pm10": 154,
    "createdAt": "2026-02-08T10:14:00Z"
  },
  "aqi": {
    "id": "uuid",
    "aqiValue": 101,
    "category": "Unhealthy for Sensitive Groups",
    "pm25Aqi": 101,
    "pm10Aqi": 100,
    "computedAt": "2026-02-08T10:14:00Z"
  }
}

Features:

  • βœ… Validates station exists
  • βœ… Automatically computes AQI on ingestion
  • βœ… Extracts PM10 from rawPayload if not provided
  • βœ… Supports multiple JSON field names: pm10, pm_10, pm10_atm

πŸ“ AQI Calculation

The system implements the official EPA Air Quality Index calculation using standard breakpoint tables.

EPA Breakpoints

PM2.5 (Β΅g/mΒ³)

Concentration Range AQI Range Category
0.0 - 12.0 0 - 50 Good
12.1 - 35.4 51 - 100 Moderate
35.5 - 55.4 101 - 150 Unhealthy for Sensitive Groups
55.5 - 150.4 151 - 200 Unhealthy
150.5 - 250.4 201 - 300 Very Unhealthy
250.5 - 500.4 301 - 500 Hazardous

PM10 (Β΅g/mΒ³)

Concentration Range AQI Range Category
0 - 54 0 - 50 Good
55 - 154 51 - 100 Moderate
155 - 254 101 - 150 Unhealthy for Sensitive Groups
255 - 354 151 - 200 Unhealthy
355 - 424 201 - 300 Very Unhealthy
425 - 504 301 - 500 Hazardous

Calculation Logic

AQI = ((I_hi - I_lo) / (C_hi - C_lo)) Γ— (C - C_lo) + I_lo

Where:

  • C = measured concentration
  • C_lo, C_hi = concentration breakpoints
  • I_lo, I_hi = index breakpoints
  • Final AQI = max(PM2.5 AQI, PM10 AQI)

See AirAware/Services/AqiCalculator.cs for implementation.


πŸ§ͺ Testing

Comprehensive Test Suite

89 passing tests covering all major components:

AirAware.Tests/
β”œβ”€β”€ Services/AqiCalculatorTests.cs      (16 tests)
β”‚   βœ… All EPA breakpoints (PM2.5 & PM10)
β”‚   βœ… Linear interpolation accuracy
β”‚   βœ… Edge cases and boundary values
β”‚   βœ… Final AQI selection logic
β”‚
β”œβ”€β”€ Controllers/StationControllerTests.cs   (9 tests)
β”‚   βœ… CRUD operations
β”‚   βœ… Partial updates
β”‚   βœ… Validation handling
β”‚
β”œβ”€β”€ Controllers/ReadingControllerTests.cs   (12 tests)
β”‚   βœ… Reading ingestion
β”‚   βœ… AQI computation integration
β”‚   βœ… Raw payload parsing
β”‚   βœ… PM10 extraction variants
β”‚
└── Models/                                 (18 tests)
    βœ… Domain model behavior
    βœ… Default values
    βœ… Relationships

Run Tests

# Run all tests
dotnet test

# Run with detailed output
dotnet test --verbosity normal

# Run specific test class
dotnet test --filter "FullyQualifiedName~AqiCalculatorTests"

Test Results

Test summary: total: 89, failed: 0, succeeded: 89, skipped: 0
Build succeeded βœ…

πŸš€ Getting Started

Prerequisites

  • .NET 10 SDK
  • SQLite (included) or PostgreSQL (production)
  • Git

Installation

  1. Clone the repository

    git clone https://github.com/yourusername/AirAware.git
    cd AirAware
  2. Restore dependencies

    dotnet restore
  3. Configure API Key

    Set the API key as an environment variable:

    export ApiKey="your-secure-api-key-here"

    Note: The API key must be set as an environment variable for security reasons. Do not store API keys in configuration files.

  4. Run migrations

    cd AirAware
    dotnet ef database update
  5. Start the application

    dotnet run

    The API will be available at http://localhost:5000

    Note: All API requests must include the X-API-KEY header:

    curl -H "X-API-KEY: your-secure-api-key-here" http://localhost:5000/api/v1/stations

Development Workflow

# Build the solution
dotnet build

# Run tests
dotnet test

# Run the application with hot reload
dotnet watch run

# Create a new migration
dotnet ef migrations add MigrationName

# Update database
dotnet ef database update

πŸ”„ CI/CD

GitHub Actions Workflow

Automated PR creation from feature branches to main:

  • Triggers on push to feature/* branches
  • Creates pull request automatically
  • Prevents duplicate PRs

See .github/workflows/1-feature-to-main.yml

Git Configuration

Set up automatic upstream tracking:

git config --global push.autoSetupRemote true

πŸ“¦ Database Migrations

Current Migrations

  • 20260208171606_CreateAqiRecordsService - Initial schema with Stations, Readings, AqiRecords

Creating New Migrations

cd AirAware
dotnet ef migrations add YourMigrationName
dotnet ef database update

Switching to PostgreSQL

The system uses SQLite by default but is designed for PostgreSQL in production:

  1. Install package:

    dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
  2. Update AppDbContext.cs:

    optionsBuilder.UseNpgsql("your-connection-string");

πŸ› οΈ Configuration

Application Settings

Edit appsettings.json for configuration:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}

Database Connection

Current: SQLite (app.db) Production: PostgreSQL (update connection string in AppDbContext.cs)


🀝 Contributing

Contributions are welcome! Please follow these guidelines:

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/your-feature
  3. Commit your changes: git commit -am 'Add some feature'
  4. Push to the branch: git push origin feature/your-feature
  5. Submit a pull request

Code Standards

  • Follow C# naming conventions
  • Write unit tests for new features
  • Maintain test coverage above 80%
  • Document public APIs with XML comments

πŸ“„ License

Copyright (c) 2026 JoΓ£o Ferreira

Non-Commercial License - This software is free for personal and non-commercial use.

Key restrictions:

  • ❌ Commercial use is strictly prohibited
  • βœ… Attribution required (credit to JoΓ£o Ferreira)
  • βœ… Free to use, modify, and distribute for non-commercial purposes

See LICENSE for full terms.


πŸ‘€ Author

JoΓ£o Ferreira

  • Built with .NET 10 and ❀️
  • February 2026

πŸ—ΊοΈ Roadmap

Future Enhancements

  • Authentication & Authorization (API keys) βœ…
  • Rate limiting for API endpoints
  • WebSocket support for real-time updates
  • Historical data aggregation
  • Geographic queries (nearest stations)
  • Alert system for unhealthy AQI levels
  • Support for additional pollutants (O3, NO2, SO2, CO)
  • Data export endpoints (CSV, JSON)
  • Grafana/Prometheus monitoring
  • Docker containerization
  • Kubernetes deployment configs

πŸ“š References


Last Updated: February 8, 2026

About

A system API to update and verify air quality in different stations/locations in the world

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors