← BACK TO PROJECTS
AI / ML FULL-STACK 2026

Air Quality AI
Real-time monitoring + 72h forecasts for Skopje

An intelligent air quality monitoring system covering 9 stations across Skopje, with AI-powered forecasts up to 72 hours ahead using a custom BiLSTM neural network trained on data from 2020 to 2026. Includes interactive maps, historical analytics, notifications, and PDF/CSV export.

SCREENSHOTS

FEATURES

🗺️

Interactive Map

9 monitoring stations across Skopje shown on Leaflet.js map. Click any station for live readings, history, and forecasts.

🧠

BiLSTM Forecasting

Custom Bidirectional LSTM neural network in TensorFlow/Keras predicts PM2.5, PM10, NO₂, O₃ up to 72 hours ahead.

📊

Historical Analytics

Charts (Chart.js) for trends, comparisons between stations, and air quality patterns over years 2020 – 2026.

🔔

Notifications

Email alerts for when pollution exceeds WHO/EU thresholds, with custom subscription per station and pollutant.

📤

CSV & PDF Export

Export historical readings and forecast data as CSV for further analysis, or as PDF reports for sharing.

📚

Educational Content

Explains health impact thresholds, AQI scale, and what each pollutant means for ordinary citizens.

ARCHITECTURE

CLIENT
HTML + CSS + JS
Leaflet maps · Chart.js · Bootstrap
↓ HTTP / REST ↓
SERVER
Django 5
REST API · Background tasks · ML inference
DATA
Supabase / PostgreSQL
Readings · Forecasts · Users
AI MODEL
BiLSTM (Keras)
Trained on 2020 – 2026 data
EXTERNAL
Public API
Air quality data fetch

Hourly cron job fetches the latest readings from the public air quality API, stores them in Postgres, and triggers re-inference of the BiLSTM model on the rolling 72h window. The forecast is cached and served via REST to the frontend, which renders charts and map overlays.

TECH STACK

BACKEND

  • Python 3.12
  • Django 5.x
  • TensorFlow 2.x
  • Keras 3.x
  • NumPy / Pandas
  • scikit-learn
  • Celery / cron (scheduled fetch)

FRONTEND

  • HTML5 / CSS3
  • JavaScript (vanilla)
  • Bootstrap 5.x
  • Leaflet.js (interactive maps)
  • Chart.js (analytics charts)

DATA & ML

  • PostgreSQL (Supabase)
  • BiLSTM architecture
  • Training dataset: 2020 — 2026
  • Pollutants: PM2.5, PM10, NO₂, O₃
  • Forecast horizon: 72 hours
  • 9 monitoring stations

INFRASTRUCTURE

  • Render.com (hosting)
  • Supabase (DB)
  • GitHub (CI/CD)
  • WeasyPrint / ReportLab (PDF)
  • SMTP (email alerts)

ML MODEL DETAILS

🔬

Architecture

Bidirectional LSTM with 2 stacked layers, 128 units each. Dropout 0.2 for regularization. Dense output layer with 72 timesteps × 4 pollutants.

📈

Training Data

6 years of hourly readings (~525,000 samples per station). Standardized with MinMaxScaler. Train/val/test split 70/15/15.

⚙️

Input Features

Pollutant concentrations, temperature, humidity, wind, hour-of-day, day-of-week, season. Sliding window of 72 past hours predicts next 72 hours.

🎯

Performance

MAE ~5.3 μg/m³ for PM2.5 at 24h horizon, degrading to ~12 μg/m³ at 72h horizon. Outperforms naïve last-value baselines by ~40%.

API ENDPOINTS

STATIONS & READINGS

GET    /api/stations/                      site stanici (9 vkupno)
GET    /api/stations/<id>/                 detali za stanica
GET    /api/stations/<id>/readings/        istorija (?from, ?to)
GET    /api/stations/<id>/forecast/        72h prognoza

ANALYTICS

GET    /api/analytics/trends/              godi[š]ni trendovi
GET    /api/analytics/comparison/          sporedba megju stanici
GET    /api/analytics/aqi/                 AQI skala + thresholds

EXPORTS

GET    /api/export/csv/?station=X&from=Y&to=Z
GET    /api/export/pdf/?station=X&from=Y&to=Z

USER / NOTIFICATIONS

POST   /api/auth/register/
POST   /api/auth/login/
POST   /api/subscriptions/                 dodaj alert
DELETE /api/subscriptions/<id>/            otka[ž]i alert

CHALLENGES SOLVED

Missing data in historical readings

Stations sometimes have hours/days of missing readings (sensor maintenance, outages). Applied forward-fill within 6h gaps, linear interpolation within 24h, and excluded samples with longer gaps from training.

Model degradation at longer horizons

Naïve LSTM had MAE of ~25 μg/m³ at 72h — too inaccurate. Switching to Bidirectional LSTM with bigger window (72h input → 72h output as multi-output) reduced error by ~55%.

Cold start on Render free tier

Loading a 50MB Keras model on each cold start took 15+ seconds. Solved by lazy-loading the model on first inference, keeping it in memory after.

Real-time + ML in one Django app

Inference takes 1-3 seconds. Built async endpoints with thread pool for forecast generation; cached results for 1 hour to avoid recomputation on repeat requests.

Want to see the forecasts in action?