Spaces:
Running
Running
github-actions[bot] commited on
Commit ·
23bb02f
0
Parent(s):
Automated backend deployment for 2026-04-05
Browse files- .gitattributes +1 -0
- .github/workflows/daily_model_training.yml +39 -0
- .github/workflows/deploy_to_hf.yml +44 -0
- .github/workflows/fetch_current_data.yml +67 -0
- .gitignore +65 -0
- Dockerfile +14 -0
- Open-Meteo.com/open_meteo_check.py +100 -0
- Open-Meteo.com/open_meteo_get_historical.py +105 -0
- README.md +8 -0
- api/index.py +210 -0
- append_and_clean_historical_data.py +73 -0
- data/current_day_hourly_data.csv +24 -0
- data/karachi_daily_data_5_years - Copy.csv +0 -0
- data/karachi_daily_data_5_years.csv +0 -0
- data/karachi_daily_data_CORRECTLY_PROCESSED.csv +365 -0
- data/karachi_historical_data_5_years_hourly.csv +0 -0
- data/karachi_hourly_data.csv +0 -0
- data/last_7_days_daily_data.csv +9 -0
- data/last_7_days_hourly_data.csv +169 -0
- fetch_current_data.py +102 -0
- frontend/.gitignore +41 -0
- frontend/README.md +36 -0
- frontend/app/favicon.ico +0 -0
- frontend/app/globals.css +102 -0
- frontend/app/layout.js +27 -0
- frontend/app/page.js +122 -0
- frontend/app/page.module.css +167 -0
- frontend/eslint.config.mjs +14 -0
- frontend/jsconfig.json +7 -0
- frontend/next.config.mjs +14 -0
- frontend/package-lock.json +0 -0
- frontend/package.json +21 -0
- frontend/public/file.svg +1 -0
- frontend/public/globe.svg +1 -0
- frontend/public/next.svg +1 -0
- frontend/public/vercel.svg +1 -0
- frontend/public/window.svg +1 -0
- hourly_to_daily.py +68 -0
- logs/AQI Predictor Log 1.csv +74 -0
- logs/experiment_log (6).csv +3 -0
- models/MAIN MODEL.joblib +3 -0
- notebooks/AQI_Predictor_Dataset_EDA.ipynb +0 -0
- notebooks/NON ADVANCED BEST MODEL_JUST NEED TO CLEAN STUFF_WEIGHTED AVERAGE ENSEMBLE.ipynb +0 -0
- requirements.txt +14 -0
- start_instructions.txt +7 -0
- train_model.py +163 -0
- vercel.json +15 -0
.gitattributes
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
models/*.joblib filter=lfs diff=lfs merge=lfs -text
|
.github/workflows/daily_model_training.yml
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
name: Daily Model Retraining
|
| 3 |
+
on:
|
| 4 |
+
schedule:
|
| 5 |
+
|
| 6 |
+
# Runs at 19:00 UTC, which is 12:00 AM (midnight) in Karachi (PKT / UTC+5)
|
| 7 |
+
- cron: '0 19 * * *'
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
workflow_dispatch:
|
| 11 |
+
|
| 12 |
+
jobs:
|
| 13 |
+
train-and-commit-model:
|
| 14 |
+
runs-on: ubuntu-latest
|
| 15 |
+
|
| 16 |
+
steps:
|
| 17 |
+
- name: Checkout repository
|
| 18 |
+
uses: actions/checkout@v4
|
| 19 |
+
|
| 20 |
+
- name: Set up Python 3.11
|
| 21 |
+
uses: actions/setup-python@v5
|
| 22 |
+
with:
|
| 23 |
+
python-version: '3.11'
|
| 24 |
+
|
| 25 |
+
- name: Install Python dependencies
|
| 26 |
+
run: |
|
| 27 |
+
python -m pip install --upgrade pip
|
| 28 |
+
pip install -r requirements.txt
|
| 29 |
+
|
| 30 |
+
- name: Train the champion model
|
| 31 |
+
run: python train_model.py
|
| 32 |
+
|
| 33 |
+
- name: Commit and push updated model
|
| 34 |
+
uses: stefanzweifel/git-auto-commit-action@v5
|
| 35 |
+
with:
|
| 36 |
+
commit_message: "CI: Automatically retrain and update champion model"
|
| 37 |
+
file_pattern: 'saved_models/*.joblib'
|
| 38 |
+
commit_user_name: GitHub Actions Bot
|
| 39 |
+
commit_user_email: actions@github.com
|
.github/workflows/deploy_to_hf.yml
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# This updates the hf backend
|
| 2 |
+
name: Deploy Backend to Hugging Face
|
| 3 |
+
|
| 4 |
+
on:
|
| 5 |
+
|
| 6 |
+
workflow_dispatch:
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
schedule:
|
| 10 |
+
# Runs at 2 am karachi time
|
| 11 |
+
- cron: '0 21 * * *'
|
| 12 |
+
|
| 13 |
+
jobs:
|
| 14 |
+
deploy:
|
| 15 |
+
runs-on: ubuntu-latest
|
| 16 |
+
|
| 17 |
+
steps:
|
| 18 |
+
- name: Checkout repository
|
| 19 |
+
uses: actions/checkout@v4
|
| 20 |
+
# We don't need the full history, just the latest code
|
| 21 |
+
with:
|
| 22 |
+
lfs: true
|
| 23 |
+
#tells the checkout to handle your LFS model file, LFS is a github shipping service which handles large model files
|
| 24 |
+
|
| 25 |
+
- name: Create Clean Deployment Branch
|
| 26 |
+
run: |
|
| 27 |
+
# This sequence creates a single, clean commit
|
| 28 |
+
|
| 29 |
+
git config --global user.name "github-actions[bot]"
|
| 30 |
+
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
| 31 |
+
|
| 32 |
+
git checkout --orphan deploy-branch
|
| 33 |
+
rm -f "AQI Predictor Report Final.pdf"
|
| 34 |
+
git add -A
|
| 35 |
+
git commit -m "Automated backend deployment for $(date +%F)"
|
| 36 |
+
|
| 37 |
+
- name: Push to Hugging Face Space
|
| 38 |
+
env:
|
| 39 |
+
|
| 40 |
+
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
| 41 |
+
run: |
|
| 42 |
+
# This is the same push command from your script
|
| 43 |
+
git remote add space "https://Qar-Raz:${HF_TOKEN}@huggingface.co/spaces/Qar-Raz/AQI_Predictor_Qamar"
|
| 44 |
+
git push --force space deploy-branch:main
|
.github/workflows/fetch_current_data.yml
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# This fetches and updates the historical data pool daily
|
| 2 |
+
name: Fetch Daily AQI Data
|
| 3 |
+
|
| 4 |
+
on:
|
| 5 |
+
workflow_dispatch:
|
| 6 |
+
schedule:
|
| 7 |
+
- cron: '10 5,11,17,23 * * *'
|
| 8 |
+
|
| 9 |
+
jobs:
|
| 10 |
+
build-and-commit:
|
| 11 |
+
runs-on: ubuntu-latest
|
| 12 |
+
steps:
|
| 13 |
+
- name: Checkout Repo
|
| 14 |
+
uses: actions/checkout@v4
|
| 15 |
+
|
| 16 |
+
- name: Set up Python
|
| 17 |
+
uses: actions/setup-python@v5
|
| 18 |
+
with:
|
| 19 |
+
python-version: '3.11'
|
| 20 |
+
|
| 21 |
+
- name: Install dependencies
|
| 22 |
+
run: |
|
| 23 |
+
python -m pip install --upgrade pip
|
| 24 |
+
pip install -r requirements.txt
|
| 25 |
+
|
| 26 |
+
- name: Run data processing pipeline
|
| 27 |
+
# This 'set -e' is critical. It forces the workflow to stop
|
| 28 |
+
# the moment any of your Python scripts exit with an error. --@Qamar
|
| 29 |
+
shell: bash
|
| 30 |
+
run: |
|
| 31 |
+
set -e
|
| 32 |
+
|
| 33 |
+
echo "--- Running: fetch_current_data.py ---"
|
| 34 |
+
python fetch_current_data.py
|
| 35 |
+
|
| 36 |
+
echo "--- Running: hourly_to_daily.py ---"
|
| 37 |
+
python hourly_to_daily.py
|
| 38 |
+
|
| 39 |
+
echo "--- Running: append_and_clean_historical_data.py ---"
|
| 40 |
+
python append_and_clean_historical_data.py
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
- name: Verify script outputs
|
| 44 |
+
run: |
|
| 45 |
+
echo "--- Verifying file contents ---"
|
| 46 |
+
echo "--- last_7_days_daily_data.csv (head) ---"
|
| 47 |
+
head -n 5 data/last_7_days_daily_data.csv
|
| 48 |
+
|
| 49 |
+
echo "--- karachi_daily_data_5_years.csv (tail) ---"
|
| 50 |
+
tail -n 5 data/karachi_daily_data_5_years.csv
|
| 51 |
+
|
| 52 |
+
# Step 5: Commit the newly updated CSV files back to your repository
|
| 53 |
+
- name: Commit and push changes
|
| 54 |
+
run: |
|
| 55 |
+
git config --global user.name "GitHub Actions Bot"
|
| 56 |
+
git config --global user.email "actions-bot@github.com"
|
| 57 |
+
|
| 58 |
+
git add data/last_7_days_hourly_data.csv
|
| 59 |
+
git add data/last_7_days_daily_data.csv
|
| 60 |
+
git add data/karachi_daily_data_5_years.csv
|
| 61 |
+
|
| 62 |
+
git status
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
git commit -m "Automated data update and aggregation"
|
| 66 |
+
|
| 67 |
+
git push
|
.gitignore
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# IDE / Editor folders
|
| 2 |
+
.vscode/
|
| 3 |
+
.idea/
|
| 4 |
+
|
| 5 |
+
# OS-specific files
|
| 6 |
+
.DS_Store
|
| 7 |
+
|
| 8 |
+
# -----------------------------------------------
|
| 9 |
+
# Python
|
| 10 |
+
# -----------------------------------------------
|
| 11 |
+
# Virtual environment
|
| 12 |
+
.venv/
|
| 13 |
+
venv/
|
| 14 |
+
|
| 15 |
+
# Bytecode files
|
| 16 |
+
__pycache__/
|
| 17 |
+
*.pyc
|
| 18 |
+
|
| 19 |
+
# Distribution / packaging
|
| 20 |
+
build/
|
| 21 |
+
dist/
|
| 22 |
+
*.egg-info/
|
| 23 |
+
|
| 24 |
+
# Logs and databases
|
| 25 |
+
*.log
|
| 26 |
+
*.sql
|
| 27 |
+
*.sqlite3
|
| 28 |
+
|
| 29 |
+
# Secrets
|
| 30 |
+
.env
|
| 31 |
+
|
| 32 |
+
# -----------------------------------------------
|
| 33 |
+
# Node.js / Next.js (inside your 'frontend' folder)
|
| 34 |
+
# -----------------------------------------------
|
| 35 |
+
# Logs
|
| 36 |
+
logs
|
| 37 |
+
*.log
|
| 38 |
+
npm-debug.log*
|
| 39 |
+
yarn-debug.log*
|
| 40 |
+
yarn-error.log*
|
| 41 |
+
lerna-debug.log*
|
| 42 |
+
|
| 43 |
+
# Dependency directories
|
| 44 |
+
frontend/node_modules/
|
| 45 |
+
node_modules/
|
| 46 |
+
|
| 47 |
+
# Next.js build output and cache
|
| 48 |
+
frontend/.next/
|
| 49 |
+
.next/
|
| 50 |
+
|
| 51 |
+
# Other Next.js specific
|
| 52 |
+
frontend/out/
|
| 53 |
+
out/
|
| 54 |
+
|
| 55 |
+
# Local environment variables (IMPORTANT for security)
|
| 56 |
+
frontend/.env.local
|
| 57 |
+
.env.local
|
| 58 |
+
|
| 59 |
+
# Turbopack cache
|
| 60 |
+
.turbo
|
| 61 |
+
|
| 62 |
+
catboost_info/
|
| 63 |
+
|
| 64 |
+
# Test results
|
| 65 |
+
coverage
|
Dockerfile
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
FROM python:3.9-slim
|
| 3 |
+
|
| 4 |
+
WORKDIR /code
|
| 5 |
+
|
| 6 |
+
COPY ./requirements.txt /code/requirements.txt
|
| 7 |
+
|
| 8 |
+
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
| 9 |
+
|
| 10 |
+
COPY . /code/
|
| 11 |
+
|
| 12 |
+
EXPOSE 7860
|
| 13 |
+
|
| 14 |
+
CMD ["uvicorn", "api.index:app", "--host", "0.0.0.0", "--port", "7860"]
|
Open-Meteo.com/open_meteo_check.py
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# This is a simple script to check the current weather and air quality in Karachi.
|
| 2 |
+
# It uses the Open-Meteo.com API to get the data.
|
| 3 |
+
# It then prints the data to the console in a readable format.
|
| 4 |
+
# NOTE: This is not a realtime script, it is just a check.
|
| 5 |
+
|
| 6 |
+
import requests
|
| 7 |
+
import pandas as pd
|
| 8 |
+
|
| 9 |
+
# To get karachi's data using Open-Meteo.com, you have to use coordinates.
|
| 10 |
+
LATITUDE = 24.86
|
| 11 |
+
LONGITUDE = 67.01
|
| 12 |
+
|
| 13 |
+
def get_aqi_category(aqi):
|
| 14 |
+
"""
|
| 15 |
+
This just converts the AQI value into a category name.
|
| 16 |
+
NOTE: AQI is actually returned as a number, this function is just converting it to a category.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
if 0 <= aqi <= 50:
|
| 20 |
+
return "Good"
|
| 21 |
+
elif 51 <= aqi <= 100:
|
| 22 |
+
return "Moderate"
|
| 23 |
+
elif 101 <= aqi <= 150:
|
| 24 |
+
return "Unhealthy for Sensitive Groups"
|
| 25 |
+
elif 151 <= aqi <= 200:
|
| 26 |
+
return "Unhealthy"
|
| 27 |
+
elif 201 <= aqi <= 300:
|
| 28 |
+
return "Very Unhealthy"
|
| 29 |
+
elif aqi > 300:
|
| 30 |
+
return "Hazardous"
|
| 31 |
+
else:
|
| 32 |
+
return "Unknown"
|
| 33 |
+
|
| 34 |
+
def get_and_print_current_data(latitude, longitude):
|
| 35 |
+
"""
|
| 36 |
+
Gets the latest live weather and air quality data.
|
| 37 |
+
NOTE: We actually have to use 2 seperate API calls.
|
| 38 |
+
1) For Weather data
|
| 39 |
+
2) For AQI data
|
| 40 |
+
|
| 41 |
+
Then we combine the data and print it to the console.
|
| 42 |
+
"""
|
| 43 |
+
print("Fetching live data for Karachi...")
|
| 44 |
+
|
| 45 |
+
# 1) Fetch LIVE Weather Data
|
| 46 |
+
try:
|
| 47 |
+
weather_url = "https://api.open-meteo.com/v1/forecast"
|
| 48 |
+
weather_params = {
|
| 49 |
+
"latitude": latitude, "longitude": longitude,
|
| 50 |
+
"current": "temperature_2m,relative_humidity_2m,wind_speed_10m",
|
| 51 |
+
"timezone": "Asia/Karachi"
|
| 52 |
+
}
|
| 53 |
+
weather_response = requests.get(weather_url, params=weather_params)
|
| 54 |
+
weather_response.raise_for_status()
|
| 55 |
+
current_weather = weather_response.json()['current']
|
| 56 |
+
except Exception as e:
|
| 57 |
+
print(f"!!! ERROR: Could not fetch weather data: {e}")
|
| 58 |
+
return
|
| 59 |
+
|
| 60 |
+
# 2) Fetch LIVE AQI Data
|
| 61 |
+
try:
|
| 62 |
+
aq_url = "https://air-quality-api.open-meteo.com/v1/air-quality"
|
| 63 |
+
aq_params = {
|
| 64 |
+
"latitude": latitude, "longitude": longitude,
|
| 65 |
+
"current": "pm10,pm2_5,carbon_monoxide,nitrogen_dioxide,us_aqi",
|
| 66 |
+
"timezone": "Asia/Karachi"
|
| 67 |
+
}
|
| 68 |
+
aq_response = requests.get(aq_url, params=aq_params)
|
| 69 |
+
aq_response.raise_for_status()
|
| 70 |
+
current_aq = aq_response.json()['current']
|
| 71 |
+
except Exception as e:
|
| 72 |
+
print(f"!!! ERROR: Could not fetch air quality data: {e}")
|
| 73 |
+
return
|
| 74 |
+
|
| 75 |
+
# 3) Print the Combined Data, in a formatted way.
|
| 76 |
+
aqi_value = current_aq.get('us_aqi')
|
| 77 |
+
aqi_category = get_aqi_category(aqi_value)
|
| 78 |
+
|
| 79 |
+
print("\n" + "="*45)
|
| 80 |
+
print("--- Current Conditions in Karachi ---")
|
| 81 |
+
print(f"Timestamp: {pd.to_datetime(current_weather.get('time')).strftime('%Y-%m-%d %I:%M %p')}")
|
| 82 |
+
print("="*45)
|
| 83 |
+
|
| 84 |
+
print("\n--- Air Quality ---")
|
| 85 |
+
print(f"AQI (US): {aqi_value} ({aqi_category})")
|
| 86 |
+
print(f"PM2.5: {current_aq.get('pm2_5')} µg/m³")
|
| 87 |
+
print(f"PM10: {current_aq.get('pm10')} µg/m³")
|
| 88 |
+
print(f"CO: {current_aq.get('carbon_monoxide')} µg/m³")
|
| 89 |
+
print(f"NO₂: {current_aq.get('nitrogen_dioxide')} µg/m³")
|
| 90 |
+
|
| 91 |
+
print("\n--- Weather ---")
|
| 92 |
+
print(f"Temperature: {current_weather.get('temperature_2m')}°C")
|
| 93 |
+
print(f"Humidity: {current_weather.get('relative_humidity_2m')}%")
|
| 94 |
+
print(f"Wind Speed: {current_weather.get('wind_speed_10m')} km/h")
|
| 95 |
+
print("\n" + "="*45)
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
if __name__ == "__main__":
|
| 100 |
+
get_and_print_current_data(LATITUDE, LONGITUDE)
|
Open-Meteo.com/open_meteo_get_historical.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import requests
|
| 2 |
+
import pandas as pd
|
| 3 |
+
from datetime import date, timedelta
|
| 4 |
+
|
| 5 |
+
# ----------------VERY IMPORTANT:----------------
|
| 6 |
+
# I tried to get the data for 5 years, but it had to many missing values in 2020 -2022 years, so I just manually removed those in excel
|
| 7 |
+
# Also removed the most recent 3 days as they were not complete.
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
LATITUDE = 24.86
|
| 12 |
+
LONGITUDE = 67.01
|
| 13 |
+
|
| 14 |
+
START_DATE = date.today() - timedelta(days=365 * 5)
|
| 15 |
+
END_DATE = date.today() - timedelta(days=1)
|
| 16 |
+
CSV_FILE = "data/karachi_historical_data_5_years_hourly.csv"
|
| 17 |
+
|
| 18 |
+
def fetch_and_save_hourly_data(latitude, longitude, start_date, end_date, filename):
|
| 19 |
+
"""
|
| 20 |
+
This function fetches raw hourly data for both air quality and weather,
|
| 21 |
+
merges them, and saves the result to a CSV file.
|
| 22 |
+
"""
|
| 23 |
+
print("--- Starting Data Fetch ---")
|
| 24 |
+
|
| 25 |
+
# 1. --- Fetch HOURLY Air Quality Data ---
|
| 26 |
+
# This uses the standard hourly parameters which are very reliable.
|
| 27 |
+
print("Step 1: Fetching Hourly Air Quality Data...")
|
| 28 |
+
aq_url = "https://air-quality-api.open-meteo.com/v1/air-quality"
|
| 29 |
+
# NOTE: Using the basic, reliable hourly variables.
|
| 30 |
+
aq_params = {
|
| 31 |
+
"latitude": latitude, "longitude": longitude,
|
| 32 |
+
"start_date": start_date.strftime("%Y-%m-%d"),
|
| 33 |
+
"end_date": end_date.strftime("%Y-%m-%d"),
|
| 34 |
+
"hourly": "pm10,pm2_5,carbon_monoxide,nitrogen_dioxide,us_aqi",
|
| 35 |
+
"timezone": "Asia/Karachi"
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
try:
|
| 39 |
+
aq_response = requests.get(aq_url, params=aq_params)
|
| 40 |
+
aq_response.raise_for_status()
|
| 41 |
+
aq_data = aq_response.json()['hourly']
|
| 42 |
+
df_aq = pd.DataFrame(aq_data)
|
| 43 |
+
df_aq.rename(columns={'time': 'timestamp'}, inplace=True)
|
| 44 |
+
print("-> OK: Air Quality data fetched.")
|
| 45 |
+
except Exception as e:
|
| 46 |
+
print(f"!!! FATAL ERROR fetching Air Quality data: {e}")
|
| 47 |
+
# Print the response text to see the error message from the server
|
| 48 |
+
if 'aq_response' in locals():
|
| 49 |
+
print("API Server Response:", aq_response.text)
|
| 50 |
+
return False
|
| 51 |
+
|
| 52 |
+
# 2. --- Fetch HOURLY Weather Data ---
|
| 53 |
+
# This uses the reliable archive API for historical weather.
|
| 54 |
+
print("\nStep 2: Fetching Hourly Weather Data...")
|
| 55 |
+
weather_url = "https://archive-api.open-meteo.com/v1/archive"
|
| 56 |
+
weather_params = {
|
| 57 |
+
"latitude": latitude, "longitude": longitude,
|
| 58 |
+
"start_date": start_date.strftime("%Y-%m-%d"),
|
| 59 |
+
"end_date": end_date.strftime("%Y-%m-%d"),
|
| 60 |
+
"hourly": "temperature_2m,relative_humidity_2m,wind_speed_10m",
|
| 61 |
+
"timezone": "Asia/Karachi"
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
try:
|
| 65 |
+
weather_response = requests.get(weather_url, params=weather_params)
|
| 66 |
+
weather_response.raise_for_status()
|
| 67 |
+
weather_data = weather_response.json()['hourly']
|
| 68 |
+
df_weather = pd.DataFrame(weather_data)
|
| 69 |
+
df_weather.rename(columns={'time': 'timestamp'}, inplace=True)
|
| 70 |
+
print("-> OK: Weather data fetched.")
|
| 71 |
+
except Exception as e:
|
| 72 |
+
print(f"!!! FATAL ERROR fetching Weather data: {e}")
|
| 73 |
+
if 'weather_response' in locals():
|
| 74 |
+
print("API Server Response:", weather_response.text)
|
| 75 |
+
return False
|
| 76 |
+
|
| 77 |
+
# 3. --- Merge and Save ---
|
| 78 |
+
print("\nStep 3: Merging data...")
|
| 79 |
+
# Convert timestamp columns to datetime objects to ensure a perfect merge
|
| 80 |
+
df_aq['timestamp'] = pd.to_datetime(df_aq['timestamp'])
|
| 81 |
+
df_weather['timestamp'] = pd.to_datetime(df_weather['timestamp'])
|
| 82 |
+
|
| 83 |
+
# Merge the two DataFrames on the common 'timestamp' column
|
| 84 |
+
df_merged = pd.merge(df_aq, df_weather, on='timestamp')
|
| 85 |
+
|
| 86 |
+
# Clean up column names
|
| 87 |
+
df_merged.rename(columns={
|
| 88 |
+
'pm2_5': 'pm25',
|
| 89 |
+
'us_aqi': 'aqi',
|
| 90 |
+
'temperature_2m': 'temperature',
|
| 91 |
+
'relative_humidity_2m': 'humidity',
|
| 92 |
+
'wind_speed_10m': 'wind_speed'
|
| 93 |
+
}, inplace=True)
|
| 94 |
+
|
| 95 |
+
# Save the final, merged hourly data
|
| 96 |
+
df_merged.to_csv(filename, index=False)
|
| 97 |
+
|
| 98 |
+
print(f"\n✅ --- SUCCESS --- ✅")
|
| 99 |
+
print(f"All hourly data has been saved to '{filename}'")
|
| 100 |
+
print(f"The file contains {len(df_merged)} hourly records.")
|
| 101 |
+
return True
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
if __name__ == "__main__":
|
| 105 |
+
fetch_and_save_hourly_data(LATITUDE, LONGITUDE, START_DATE, END_DATE, CSV_FILE)
|
README.md
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: AQI Predictor
|
| 3 |
+
emoji: 💨
|
| 4 |
+
colorFrom: green
|
| 5 |
+
colorTo: red
|
| 6 |
+
sdk: docker
|
| 7 |
+
app_port: 7860
|
| 8 |
+
---
|
api/index.py
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pandas as pd
|
| 2 |
+
import numpy as np
|
| 3 |
+
import joblib
|
| 4 |
+
import requests
|
| 5 |
+
import os
|
| 6 |
+
from datetime import datetime, timezone
|
| 7 |
+
from fastapi import FastAPI, HTTPException
|
| 8 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
MODEL_FILE = 'models/MAIN MODEL.joblib'
|
| 12 |
+
HISTORICAL_DATA_FILE = 'data/karachi_daily_data_5_years.csv'
|
| 13 |
+
TIMEZONE = 'Asia/Karachi'
|
| 14 |
+
LATITUDE = 24.86
|
| 15 |
+
LONGITUDE = 67.01
|
| 16 |
+
|
| 17 |
+
# Setting up the fastapi app, configs
|
| 18 |
+
app = FastAPI(
|
| 19 |
+
title="Pearls AQI Predictor API",
|
| 20 |
+
description="An API to provide today's AQI and a 3-day forecast.",
|
| 21 |
+
version="1.0.0"
|
| 22 |
+
)
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
origins = [
|
| 26 |
+
"http://localhost:3000",
|
| 27 |
+
"localhost:3000",
|
| 28 |
+
]
|
| 29 |
+
app.add_middleware(
|
| 30 |
+
CORSMiddleware,
|
| 31 |
+
allow_origins=origins,
|
| 32 |
+
allow_credentials=True,
|
| 33 |
+
allow_methods=["*"],
|
| 34 |
+
allow_headers=["*"],
|
| 35 |
+
)
|
| 36 |
+
|
| 37 |
+
# This is the main prediction logic
|
| 38 |
+
|
| 39 |
+
def get_future_forecast_from_api():
|
| 40 |
+
"""Fetches and prepares the forecast for the next 3 days."""
|
| 41 |
+
print("--- Fetching Future Forecast Data ---")
|
| 42 |
+
try:
|
| 43 |
+
FORECAST_DAYS = 4
|
| 44 |
+
weather_url = "https://api.open-meteo.com/v1/forecast"
|
| 45 |
+
weather_params = {"latitude": LATITUDE, "longitude": LONGITUDE, "daily": "temperature_2m_mean,relative_humidity_2m_mean,wind_speed_10m_mean", "forecast_days": FORECAST_DAYS, "timezone": TIMEZONE}
|
| 46 |
+
weather_json = requests.get(weather_url, params=weather_params).json()
|
| 47 |
+
|
| 48 |
+
aq_url = "https://air-quality-api.open-meteo.com/v1/air-quality"
|
| 49 |
+
aq_params = {"latitude": LATITUDE, "longitude": LONGITUDE, "hourly": "pm10,pm2_5,carbon_monoxide,nitrogen_dioxide", "forecast_days": FORECAST_DAYS, "timezone": TIMEZONE}
|
| 50 |
+
aq_json = requests.get(aq_url, params=aq_params).json()
|
| 51 |
+
|
| 52 |
+
df_weather_daily = pd.DataFrame(weather_json['daily'])
|
| 53 |
+
df_weather_daily.rename(columns={'time': 'timestamp'}, inplace=True)
|
| 54 |
+
df_weather_daily['timestamp'] = pd.to_datetime(df_weather_daily['timestamp'])
|
| 55 |
+
|
| 56 |
+
df_aq_hourly = pd.DataFrame(aq_json['hourly'])
|
| 57 |
+
df_aq_hourly.rename(columns={'time': 'timestamp'}, inplace=True)
|
| 58 |
+
df_aq_hourly['timestamp'] = pd.to_datetime(df_aq_hourly['timestamp'])
|
| 59 |
+
df_aq_hourly.set_index('timestamp', inplace=True)
|
| 60 |
+
|
| 61 |
+
pollutant_columns = ['pm10', 'pm2_5', 'carbon_monoxide', 'nitrogen_dioxide']
|
| 62 |
+
df_aq_daily = df_aq_hourly[pollutant_columns].resample('D').mean()
|
| 63 |
+
|
| 64 |
+
forecast_df = pd.merge(df_weather_daily.set_index('timestamp'), df_aq_daily, left_index=True, right_index=True)
|
| 65 |
+
forecast_df.rename(columns={'temperature_2m_mean': 'temperature', 'relative_humidity_2m_mean': 'humidity', 'wind_speed_10m_mean': 'wind_speed', 'pm2_5': 'pm25'}, inplace=True)
|
| 66 |
+
|
| 67 |
+
future_days_only = forecast_df.iloc[1:]
|
| 68 |
+
print(f"-> OK: Future forecast data fetched for the next {len(future_days_only)} days.")
|
| 69 |
+
return future_days_only
|
| 70 |
+
except Exception as e:
|
| 71 |
+
print(f"!!! FATAL: An error occurred during API fetch: {e}")
|
| 72 |
+
return None
|
| 73 |
+
|
| 74 |
+
def create_features_for_single_day(forecast_row, history_df):
|
| 75 |
+
"""
|
| 76 |
+
Creates ALL features for a single future day using historical context.
|
| 77 |
+
"""
|
| 78 |
+
features = {}
|
| 79 |
+
|
| 80 |
+
# 1. Add the forecast data for the day
|
| 81 |
+
features.update(forecast_row.to_dict())
|
| 82 |
+
|
| 83 |
+
# 2. Add base features (lags, time)
|
| 84 |
+
for i in range(1, 8):
|
| 85 |
+
features[f'aqi_lag_{i}'] = history_df['aqi'].iloc[-i]
|
| 86 |
+
|
| 87 |
+
date_to_predict = forecast_row.name
|
| 88 |
+
features['day_of_year'] = date_to_predict.dayofyear
|
| 89 |
+
|
| 90 |
+
# 3. Add advanced features (rolling, interactions, cyclical)
|
| 91 |
+
window_sizes = [3, 7]
|
| 92 |
+
cols_to_roll = ['aqi', 'pm25', 'carbon_monoxide', 'wind_speed', 'humidity']
|
| 93 |
+
|
| 94 |
+
temp_df_for_rolling = pd.concat([history_df, pd.DataFrame(forecast_row).T])
|
| 95 |
+
|
| 96 |
+
for window in window_sizes:
|
| 97 |
+
for col in cols_to_roll:
|
| 98 |
+
features[f'{col}_rolling_mean_{window}'] = temp_df_for_rolling[col].shift(1).rolling(window=window).mean().iloc[-1]
|
| 99 |
+
features[f'{col}_rolling_std_{window}'] = temp_df_for_rolling[col].shift(1).rolling(window=window).std().iloc[-1]
|
| 100 |
+
|
| 101 |
+
features['pm25_x_wind_interaction'] = features['pm25'] / (features['wind_speed'] + 1)
|
| 102 |
+
features['temp_x_humidity_interaction'] = features['temperature'] * features['humidity']
|
| 103 |
+
|
| 104 |
+
features['month_sin'] = np.sin(2 * np.pi * date_to_predict.month / 12)
|
| 105 |
+
features['month_cos'] = np.cos(2 * np.pi * date_to_predict.month / 12)
|
| 106 |
+
features['day_of_week_sin'] = np.sin(2 * np.pi * date_to_predict.dayofweek / 7)
|
| 107 |
+
features['day_of_week_cos'] = np.cos(2 * np.pi * date_to_predict.dayofweek / 7)
|
| 108 |
+
|
| 109 |
+
return features
|
| 110 |
+
|
| 111 |
+
def generate_full_response():
|
| 112 |
+
"""
|
| 113 |
+
Loads data and the champion model, generates a 3-day forecast, and returns the result.
|
| 114 |
+
"""
|
| 115 |
+
print("\n====== STARTING FULL RESPONSE GENERATION ======")
|
| 116 |
+
try:
|
| 117 |
+
|
| 118 |
+
model = joblib.load(MODEL_FILE)
|
| 119 |
+
df_historical = pd.read_csv(HISTORICAL_DATA_FILE, parse_dates=['timestamp'])
|
| 120 |
+
except FileNotFoundError as e:
|
| 121 |
+
return {"error": f"Missing required file: {e}. Ensure 'models' and 'data' directories are in the project root."}
|
| 122 |
+
|
| 123 |
+
#1: Get Today's Most Recent AQI
|
| 124 |
+
latest_data = df_historical.sort_values('timestamp').iloc[-1]
|
| 125 |
+
today_aqi_data = {
|
| 126 |
+
"date": latest_data['timestamp'].strftime('%Y-%m-%d'),
|
| 127 |
+
"aqi": round(latest_data['aqi'])
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
#2: Get the Future Forecast Ingredients
|
| 131 |
+
future_data = get_future_forecast_from_api()
|
| 132 |
+
if future_data is None:
|
| 133 |
+
return {"error": "Could not retrieve future weather forecast."}
|
| 134 |
+
|
| 135 |
+
#3: Generate the 3-day AQI Forecast, is done iteratively (IMP)
|
| 136 |
+
live_history = df_historical.sort_values('timestamp').tail(10).copy()
|
| 137 |
+
MODEL_FEATURES = model.feature_names_in_
|
| 138 |
+
|
| 139 |
+
predictions = []
|
| 140 |
+
for date_to_predict, forecast_row in future_data.iterrows():
|
| 141 |
+
|
| 142 |
+
features = create_features_for_single_day(forecast_row, live_history)
|
| 143 |
+
features_df = pd.DataFrame([features])[MODEL_FEATURES]
|
| 144 |
+
predicted_aqi = model.predict(features_df)[0]
|
| 145 |
+
|
| 146 |
+
predictions.append({
|
| 147 |
+
"date": date_to_predict.strftime('%Y-%m-%d'),
|
| 148 |
+
"predicted_aqi": round(predicted_aqi)
|
| 149 |
+
})
|
| 150 |
+
|
| 151 |
+
new_row_data = forecast_row.to_dict()
|
| 152 |
+
new_row_data['aqi'] = predicted_aqi
|
| 153 |
+
new_row_df = pd.DataFrame([new_row_data], index=[date_to_predict])
|
| 154 |
+
|
| 155 |
+
live_history = pd.concat([live_history, new_row_df])
|
| 156 |
+
|
| 157 |
+
#Assemble the Final Response
|
| 158 |
+
final_response = {
|
| 159 |
+
"today": today_aqi_data,
|
| 160 |
+
"forecast": predictions
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
print("====== FULL RESPONSE GENERATION COMPLETE ======")
|
| 164 |
+
return final_response
|
| 165 |
+
|
| 166 |
+
# API endpoints configured here
|
| 167 |
+
@app.get("/api/status")
|
| 168 |
+
def get_status():
|
| 169 |
+
"""
|
| 170 |
+
Provides the status of the API and the last update time of the model.
|
| 171 |
+
"""
|
| 172 |
+
try:
|
| 173 |
+
|
| 174 |
+
mod_time_unix = os.path.getmtime(MODEL_FILE)
|
| 175 |
+
# Convert it to a human-readable UTC datetime object
|
| 176 |
+
last_updated_dt = datetime.fromtimestamp(mod_time_unix, tz=timezone.utc)
|
| 177 |
+
|
| 178 |
+
return {
|
| 179 |
+
"status": "online",
|
| 180 |
+
"model_last_updated_utc": last_updated_dt.isoformat()
|
| 181 |
+
}
|
| 182 |
+
except FileNotFoundError:
|
| 183 |
+
raise HTTPException(status_code=404, detail="Model file not found.")
|
| 184 |
+
except Exception as e:
|
| 185 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
@app.get("/api/forecast")
|
| 189 |
+
def get_aqi_forecast():
|
| 190 |
+
"""
|
| 191 |
+
This endpoint runs the complete prediction pipeline to get today's
|
| 192 |
+
AQI value and a 3-day future forecast.
|
| 193 |
+
"""
|
| 194 |
+
print("--- Received request for /forecast ---")
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
response_data = generate_full_response()
|
| 198 |
+
|
| 199 |
+
|
| 200 |
+
if "error" in response_data:
|
| 201 |
+
raise HTTPException(status_code=500, detail=response_data["error"])
|
| 202 |
+
|
| 203 |
+
return response_data
|
| 204 |
+
|
| 205 |
+
@app.get("/api")
|
| 206 |
+
def read_root():
|
| 207 |
+
"""
|
| 208 |
+
Root endpoint for the API.
|
| 209 |
+
"""
|
| 210 |
+
return {"message": "Welcome to the AQI Predictor API."}
|
append_and_clean_historical_data.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pandas as pd
|
| 2 |
+
import sys
|
| 3 |
+
|
| 4 |
+
# --- Configuration ---
|
| 5 |
+
MAIN_HISTORICAL_FILE = "data/karachi_daily_data_5_years.csv"
|
| 6 |
+
NEW_DAILY_DATA_FILE = "data/last_7_days_daily_data.csv"
|
| 7 |
+
TIMEZONE = 'Asia/Karachi' # Define timezone as a constant for consistency
|
| 8 |
+
|
| 9 |
+
def append_and_clean_historical_data(main_file, new_data_file):
|
| 10 |
+
"""
|
| 11 |
+
Efficiently appends new daily data to the main historical dataset.
|
| 12 |
+
|
| 13 |
+
This function is idempotent: it combines data, removes any overlaps by
|
| 14 |
+
keeping the newest data, and saves a clean, sorted historical file.
|
| 15 |
+
It robustly handles both timezone-aware and timezone-naive data.
|
| 16 |
+
"""
|
| 17 |
+
print("--- Starting historical data update process ---")
|
| 18 |
+
|
| 19 |
+
# --- Step 1: Load the main historical dataset ---
|
| 20 |
+
try:
|
| 21 |
+
df_main = pd.read_csv(main_file, parse_dates=['timestamp'])
|
| 22 |
+
|
| 23 |
+
# === FIX 1: LOCALIZE THE MAIN DATAFRAME'S TIMESTAMP ===
|
| 24 |
+
# If the timestamp column is naive, assign the correct timezone.
|
| 25 |
+
# If it's already aware, this will correctly convert it.
|
| 26 |
+
if df_main['timestamp'].dt.tz is None:
|
| 27 |
+
df_main['timestamp'] = df_main['timestamp'].dt.tz_localize(TIMEZONE)
|
| 28 |
+
else:
|
| 29 |
+
df_main['timestamp'] = df_main['timestamp'].dt.tz_convert(TIMEZONE)
|
| 30 |
+
|
| 31 |
+
print(f"Loaded and standardized {len(df_main)} records from the main historical file.")
|
| 32 |
+
except FileNotFoundError:
|
| 33 |
+
print(f"!!! ERROR: New daily data file '{new_data_file}' not found. Aborting.")
|
| 34 |
+
sys.exit(1)
|
| 35 |
+
|
| 36 |
+
# --- Step 2: Load the new daily data ---
|
| 37 |
+
try:
|
| 38 |
+
df_new = pd.read_csv(new_data_file, parse_dates=['timestamp'])
|
| 39 |
+
|
| 40 |
+
# === FIX 2: LOCALIZE THE NEW DATAFRAME'S TIMESTAMP (just in case) ===
|
| 41 |
+
# This ensures the new data is also tz-aware before concatenation.
|
| 42 |
+
if df_new['timestamp'].dt.tz is None:
|
| 43 |
+
df_new['timestamp'] = df_new['timestamp'].dt.tz_localize(TIMEZONE)
|
| 44 |
+
else:
|
| 45 |
+
df_new['timestamp'] = df_new['timestamp'].dt.tz_convert(TIMEZONE)
|
| 46 |
+
|
| 47 |
+
print(f"Loaded and standardized {len(df_new)} new daily records to be merged.")
|
| 48 |
+
except FileNotFoundError:
|
| 49 |
+
print(f"!!! ERROR: New daily data file '{new_data_file}' not found. Aborting.")
|
| 50 |
+
return
|
| 51 |
+
|
| 52 |
+
# --- Step 3: Combine and De-duplicate (This will now work) ---
|
| 53 |
+
print("Combining datasets and removing duplicates...")
|
| 54 |
+
|
| 55 |
+
# Now that both dataframes have tz-aware timestamps, this is safe.
|
| 56 |
+
df_combined = pd.concat([df_main, df_new], ignore_index=True)
|
| 57 |
+
|
| 58 |
+
df_final = df_combined.sort_values('timestamp').drop_duplicates(subset=['timestamp'], keep='last')
|
| 59 |
+
|
| 60 |
+
print(f"-> Combined records: {len(df_combined)}")
|
| 61 |
+
print(f"-> Records after de-duplication: {len(df_final)}")
|
| 62 |
+
|
| 63 |
+
# --- Step 4: Save the updated historical dataset ---
|
| 64 |
+
df_final.to_csv(main_file, index=False)
|
| 65 |
+
|
| 66 |
+
print(f"\n✅ --- SUCCESS --- ✅")
|
| 67 |
+
print(f"Main historical dataset '{main_file}' has been updated.")
|
| 68 |
+
print(f"It now contains {len(df_final)} clean, unique daily records.")
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
# --- Run the script ---
|
| 72 |
+
if __name__ == "__main__":
|
| 73 |
+
append_and_clean_historical_data(MAIN_HISTORICAL_FILE, NEW_DAILY_DATA_FILE)
|
data/current_day_hourly_data.csv
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
timestamp,temperature,humidity,wind_speed,pm10,pm25,carbon_monoxide,nitrogen_dioxide,aqi
|
| 2 |
+
2025-08-04 00:00:00+05:00,27.7,87,10.9,39.6,17.5,243.0,17.7,68
|
| 3 |
+
2025-08-04 01:00:00+05:00,27.6,88,9.4,38.1,16.5,187.0,14.5,68
|
| 4 |
+
2025-08-04 02:00:00+05:00,27.6,88,11.1,36.7,15.6,145.0,11.7,68
|
| 5 |
+
2025-08-04 03:00:00+05:00,27.5,88,15.0,36.6,15.0,124.0,9.9,67
|
| 6 |
+
2025-08-04 04:00:00+05:00,27.4,88,15.1,36.2,14.5,118.0,8.5,67
|
| 7 |
+
2025-08-04 05:00:00+05:00,27.5,88,15.0,36.6,14.3,134.0,7.8,67
|
| 8 |
+
2025-08-04 06:00:00+05:00,27.4,86,14.4,35.9,14.2,192.0,8.4,67
|
| 9 |
+
2025-08-04 07:00:00+05:00,27.5,84,13.9,42.7,15.5,272.0,9.8,66
|
| 10 |
+
2025-08-04 08:00:00+05:00,28.1,79,16.6,44.6,16.3,318.0,10.2,66
|
| 11 |
+
2025-08-04 09:00:00+05:00,28.8,76,20.6,50.5,17.3,295.0,8.7,65
|
| 12 |
+
2025-08-04 10:00:00+05:00,29.3,75,22.8,57.4,18.9,238.0,6.2,65
|
| 13 |
+
2025-08-04 11:00:00+05:00,29.8,74,22.0,61.9,19.8,195.0,4.3,65
|
| 14 |
+
2025-08-04 12:00:00+05:00,30.2,72,23.5,66.8,21.2,186.0,3.4,64
|
| 15 |
+
2025-08-04 13:00:00+05:00,30.5,72,24.7,65.9,21.1,193.0,3.1,64
|
| 16 |
+
2025-08-04 14:00:00+05:00,30.5,72,26.3,67.3,21.1,204.0,3.3,64
|
| 17 |
+
2025-08-04 15:00:00+05:00,30.2,71,25.8,63.8,20.3,222.0,3.8,64
|
| 18 |
+
2025-08-04 16:00:00+05:00,30.1,70,25.0,60.1,19.5,245.0,4.9,63
|
| 19 |
+
2025-08-04 17:00:00+05:00,29.7,74,22.0,56.9,18.9,261.0,6.6,63
|
| 20 |
+
2025-08-04 18:00:00+05:00,29.1,76,20.8,51.3,17.9,264.0,9.7,63
|
| 21 |
+
2025-08-04 19:00:00+05:00,28.6,79,19.1,45.4,17.3,261.0,13.6,63
|
| 22 |
+
2025-08-04 20:00:00+05:00,28.2,82,16.8,48.7,19.1,253.0,16.2,63
|
| 23 |
+
2025-08-04 21:00:00+05:00,28.0,83,16.6,45.1,19.0,243.0,16.8,63
|
| 24 |
+
2025-08-04 22:00:00+05:00,27.8,84,17.3,38.1,17.2,229.0,16.1,62
|
data/karachi_daily_data_5_years - Copy.csv
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/karachi_daily_data_5_years.csv
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/karachi_daily_data_CORRECTLY_PROCESSED.csv
ADDED
|
@@ -0,0 +1,365 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
timestamp,pm10,pm25,carbon_monoxide,nitrogen_dioxide,temperature,humidity,wind_speed,aqi
|
| 2 |
+
2024-07-20,110.18333333333334,35.30833333333333,266.2916666666667,15.708333333333334,32.829166666666666,70.66666666666667,13.758333333333333,108
|
| 3 |
+
2024-07-21,80.56666666666666,36.62083333333333,347.75,18.320833333333333,33.270833333333336,69.25,10.804166666666667,178
|
| 4 |
+
2024-07-22,65.31666666666666,24.995833333333334,232.79166666666666,10.941666666666668,31.912499999999998,77.41666666666667,21.216666666666665,103
|
| 5 |
+
2024-07-23,85.7375,25.5,186.33333333333334,8.416666666666666,30.883333333333336,77.375,24.766666666666666,78
|
| 6 |
+
2024-07-24,134.30833333333334,31.045833333333334,174.75,7.708333333333333,30.587500000000002,76.375,25.125,94
|
| 7 |
+
2024-07-25,152.17083333333332,34.375,177.91666666666666,8.704166666666667,30.71666666666667,76.25,20.758333333333333,98
|
| 8 |
+
2024-07-26,137.06666666666666,33.32083333333333,174.125,8.058333333333334,31.195833333333336,76.16666666666667,21.866666666666664,102
|
| 9 |
+
2024-07-27,150.65,33.5625,169.54166666666666,7.766666666666667,31.245833333333334,73.875,22.495833333333334,97
|
| 10 |
+
2024-07-28,174.8166666666667,38.96666666666667,218.25,12.704166666666666,31.070833333333336,73.95833333333333,14.858333333333334,116
|
| 11 |
+
2024-07-29,164.87083333333334,34.78333333333333,237.54166666666666,14.9,30.425,76.54166666666667,12.129166666666668,112
|
| 12 |
+
2024-07-30,95.8,30.162499999999998,260.0833333333333,16.683333333333334,29.15416666666667,84.79166666666667,9.658333333333333,105
|
| 13 |
+
2024-07-31,35.979166666666664,17.349999999999998,263.75,16.4375,29.15833333333333,86.04166666666667,13.491666666666667,89
|
| 14 |
+
2024-08-01,55.625,21.920833333333334,192.04166666666666,9.595833333333333,29.504166666666666,82.83333333333333,27.858333333333334,70
|
| 15 |
+
2024-08-02,94.05416666666667,26.40833333333333,188.58333333333334,10.666666666666666,29.304166666666664,84.20833333333333,22.754166666666666,82
|
| 16 |
+
2024-08-03,116.32916666666667,32.11666666666667,231.95833333333334,15.495833333333332,29.245833333333334,81.70833333333333,19.5,93
|
| 17 |
+
2024-08-04,138.27083333333334,33.90833333333333,239.04166666666666,14.891666666666666,28.854166666666668,82.79166666666667,16.108333333333334,96
|
| 18 |
+
2024-08-05,113.37083333333334,28.191666666666666,220.70833333333334,13.379166666666668,28.599999999999998,82.29166666666667,13.137500000000001,98
|
| 19 |
+
2024-08-06,85.02916666666667,23.004166666666666,206.91666666666666,12.929166666666667,28.358333333333334,83.20833333333333,17.6125,84
|
| 20 |
+
2024-08-07,73.25833333333334,20.316666666666666,167.45833333333334,7.4624999999999995,28.849999999999998,77.66666666666667,27.066666666666666,73
|
| 21 |
+
2024-08-08,66.60833333333333,20.079166666666666,185.5,8.541666666666666,29.28333333333333,75.79166666666667,22.041666666666668,69
|
| 22 |
+
2024-08-09,65.72500000000001,18.329166666666666,186.41666666666666,8.295833333333333,29.625,75.375,22.183333333333334,67
|
| 23 |
+
2024-08-10,55.333333333333336,16.116666666666667,154.95833333333334,6.658333333333334,29.770833333333332,74.29166666666667,28.316666666666666,63
|
| 24 |
+
2024-08-11,52.1875,15.85,156.25,6.8625,29.041666666666668,79.0,29.770833333333332,59
|
| 25 |
+
2024-08-12,87.15833333333335,18.837500000000002,179.45833333333334,9.1875,28.962500000000002,81.08333333333333,21.78333333333333,66
|
| 26 |
+
2024-08-13,87.79583333333333,17.1125,194.5,10.458333333333334,29.429166666666664,80.04166666666667,17.008333333333333,66
|
| 27 |
+
2024-08-14,117.72083333333335,18.441666666666666,173.0,8.066666666666666,29.483333333333334,78.20833333333333,18.808333333333334,86
|
| 28 |
+
2024-08-15,82.10416666666667,19.037499999999998,170.79166666666666,7.1625000000000005,29.1875,79.83333333333333,21.066666666666666,81
|
| 29 |
+
2024-08-16,65.46666666666667,19.254166666666666,167.79166666666666,7.9125000000000005,28.729166666666668,79.875,19.079166666666666,67
|
| 30 |
+
2024-08-17,78.07083333333334,20.783333333333335,192.45833333333334,11.083333333333334,29.008333333333336,77.20833333333333,16.166666666666668,68
|
| 31 |
+
2024-08-18,72.47083333333333,20.179166666666667,202.33333333333334,12.2875,29.025000000000002,77.41666666666667,17.991666666666667,70
|
| 32 |
+
2024-08-19,54.020833333333336,17.929166666666667,213.08333333333334,11.458333333333334,28.95,76.70833333333333,18.220833333333335,67
|
| 33 |
+
2024-08-20,43.074999999999996,16.395833333333332,193.0,8.920833333333333,29.270833333333332,74.375,22.587500000000002,63
|
| 34 |
+
2024-08-21,44.53333333333333,16.745833333333334,176.79166666666666,7.533333333333332,29.337500000000002,74.33333333333333,22.754166666666666,60
|
| 35 |
+
2024-08-22,63.59166666666667,18.883333333333333,178.04166666666666,7.791666666666667,30.05,70.625,20.720833333333335,66
|
| 36 |
+
2024-08-23,63.225,18.004166666666666,173.79166666666666,7.066666666666666,29.745833333333334,73.20833333333333,21.587500000000002,65
|
| 37 |
+
2024-08-24,79.80833333333334,21.133333333333333,176.79166666666666,7.304166666666667,29.441666666666666,74.41666666666667,21.804166666666664,69
|
| 38 |
+
2024-08-25,81.96249999999999,23.6875,170.45833333333334,6.6000000000000005,29.425,73.70833333333333,25.133333333333336,76
|
| 39 |
+
2024-08-26,68.47083333333333,21.233333333333334,192.875,9.945833333333335,29.666666666666668,73.5,23.679166666666664,75
|
| 40 |
+
2024-08-27,50.62083333333334,20.791666666666668,284.625,13.975,29.0,82.04166666666667,16.266666666666666,99
|
| 41 |
+
2024-08-28,17.604166666666668,11.629166666666668,364.9583333333333,17.325,27.537499999999998,86.75,17.758333333333333,69
|
| 42 |
+
2024-08-29,28.0625,15.520833333333334,354.3333333333333,20.045833333333334,27.587500000000002,88.83333333333333,19.545833333333334,57
|
| 43 |
+
2024-08-30,23.129166666666666,14.033333333333333,207.16666666666666,8.458333333333334,27.266666666666666,82.66666666666667,34.4375,58
|
| 44 |
+
2024-08-31,34.770833333333336,11.9,182.16666666666666,9.775,26.3,86.91666666666667,33.541666666666664,55
|
| 45 |
+
2024-09-01,41.204166666666666,14.025,260.2083333333333,14.875,26.991666666666664,88.08333333333333,15.608333333333334,54
|
| 46 |
+
2024-09-02,48.1625,15.362499999999999,302.875,21.270833333333332,27.016666666666666,87.0,9.016666666666667,59
|
| 47 |
+
2024-09-03,62.262499999999996,16.191666666666666,284.9583333333333,20.645833333333332,27.349999999999998,86.16666666666667,11.345833333333333,59
|
| 48 |
+
2024-09-04,63.40416666666667,18.883333333333333,241.41666666666666,17.7375,27.662499999999998,83.08333333333333,15.075000000000001,65
|
| 49 |
+
2024-09-05,40.666666666666664,16.816666666666666,194.16666666666666,10.858333333333334,27.974999999999998,83.54166666666667,22.087500000000002,65
|
| 50 |
+
2024-09-06,50.875,19.058333333333334,213.33333333333334,12.533333333333333,27.866666666666664,83.875,20.091666666666665,65
|
| 51 |
+
2024-09-07,49.925000000000004,17.479166666666668,227.41666666666666,14.225,27.508333333333336,83.04166666666667,15.362499999999999,65
|
| 52 |
+
2024-09-08,48.041666666666664,14.8375,229.5,14.454166666666666,27.491666666666664,84.54166666666667,15.549999999999999,62
|
| 53 |
+
2024-09-09,66.27916666666667,17.366666666666667,200.91666666666666,12.045833333333334,27.570833333333336,82.125,18.3,61
|
| 54 |
+
2024-09-10,90.07916666666667,24.695833333333336,234.125,14.254166666666668,27.545833333333334,82.58333333333333,16.545833333333334,76
|
| 55 |
+
2024-09-11,168.1875,37.17916666666667,247.45833333333334,14.575000000000001,27.541666666666668,82.54166666666667,14.508333333333333,106
|
| 56 |
+
2024-09-12,172.32916666666665,36.32083333333333,213.125,11.391666666666666,27.90416666666667,79.29166666666667,18.3875,111
|
| 57 |
+
2024-09-13,83.99583333333334,22.804166666666664,168.33333333333334,7.795833333333333,27.941666666666666,74.20833333333333,25.229166666666668,109
|
| 58 |
+
2024-09-14,74.40416666666667,23.266666666666666,169.375,7.716666666666666,28.195833333333336,71.625,25.90833333333333,75
|
| 59 |
+
2024-09-15,51.612500000000004,21.287499999999998,181.375,8.366666666666667,28.03333333333333,71.625,21.162499999999998,74
|
| 60 |
+
2024-09-16,50.7875,21.425,181.375,9.033333333333333,27.866666666666664,75.16666666666667,19.6875,71
|
| 61 |
+
2024-09-17,51.875,17.895833333333332,187.83333333333334,9.620833333333334,28.287499999999998,76.66666666666667,18.691666666666666,70
|
| 62 |
+
2024-09-18,47.61666666666667,16.008333333333333,205.70833333333334,11.0875,28.34166666666667,77.91666666666667,16.224999999999998,63
|
| 63 |
+
2024-09-19,57.03333333333333,17.054166666666667,221.41666666666666,13.987499999999999,28.1875,79.625,13.424999999999999,61
|
| 64 |
+
2024-09-20,53.354166666666664,16.679166666666667,220.04166666666666,12.65,28.212500000000002,80.45833333333333,16.741666666666667,61
|
| 65 |
+
2024-09-21,62.112500000000004,20.108333333333334,197.20833333333334,9.220833333333333,28.316666666666666,75.375,18.1625,67
|
| 66 |
+
2024-09-22,70.82916666666667,22.25,195.79166666666666,8.854166666666666,28.454166666666666,72.75,19.033333333333335,72
|
| 67 |
+
2024-09-23,62.324999999999996,20.4375,197.25,8.637500000000001,28.866666666666664,72.875,19.7625,72
|
| 68 |
+
2024-09-24,55.5375,19.5,192.75,8.983333333333333,28.9875,73.25,20.4375,68
|
| 69 |
+
2024-09-25,43.43333333333334,18.695833333333333,192.79166666666666,10.575000000000001,28.879166666666666,74.83333333333333,17.795833333333334,66
|
| 70 |
+
2024-09-26,47.775,18.650000000000002,200.08333333333334,10.866666666666667,29.079166666666666,74.41666666666667,17.445833333333333,65
|
| 71 |
+
2024-09-27,50.3125,17.954166666666666,179.70833333333334,8.404166666666667,29.099999999999998,75.70833333333333,22.212500000000002,64
|
| 72 |
+
2024-09-28,51.57916666666667,17.8875,177.375,8.1875,29.204166666666666,74.25,20.125,65
|
| 73 |
+
2024-09-29,64.3,17.154166666666665,182.29166666666666,9.841666666666667,28.733333333333334,74.41666666666667,16.695833333333333,63
|
| 74 |
+
2024-09-30,33.237500000000004,13.391666666666666,177.79166666666666,10.295833333333333,28.745833333333334,75.29166666666667,18.258333333333333,61
|
| 75 |
+
2024-10-01,24.541666666666668,11.529166666666667,204.0,10.458333333333334,28.912499999999998,77.16666666666667,19.283333333333335,53
|
| 76 |
+
2024-10-02,28.666666666666668,12.862499999999999,197.41666666666666,9.520833333333334,28.8875,78.95833333333333,20.566666666666666,51
|
| 77 |
+
2024-10-03,32.02916666666667,13.879166666666668,191.66666666666666,7.866666666666667,28.775000000000002,78.79166666666667,23.770833333333332,55
|
| 78 |
+
2024-10-04,35.050000000000004,15.458333333333334,180.875,7.279166666666666,28.679166666666664,79.70833333333333,24.212500000000002,57
|
| 79 |
+
2024-10-05,39.520833333333336,15.479166666666666,175.95833333333334,8.862499999999999,28.675,78.45833333333333,20.741666666666667,58
|
| 80 |
+
2024-10-06,46.93333333333334,17.179166666666667,222.5,12.579166666666666,28.391666666666666,77.79166666666667,13.625,61
|
| 81 |
+
2024-10-07,47.84583333333333,19.545833333333334,275.8333333333333,17.154166666666665,29.1125,67.66666666666667,11.475,91
|
| 82 |
+
2024-10-08,43.324999999999996,22.1125,337.4166666666667,20.075,31.162499999999998,48.708333333333336,13.916666666666666,111
|
| 83 |
+
2024-10-09,47.25416666666666,26.379166666666666,343.0416666666667,19.587500000000002,31.566666666666666,49.25,13.970833333333333,81
|
| 84 |
+
2024-10-10,49.32916666666667,27.070833333333336,445.25,27.983333333333334,30.96666666666667,55.583333333333336,11.979166666666666,96
|
| 85 |
+
2024-10-11,55.77916666666667,27.5125,455.5416666666667,25.329166666666666,31.65833333333333,45.916666666666664,12.5375,88
|
| 86 |
+
2024-10-12,79.75416666666666,41.22083333333333,481.8333333333333,23.308333333333334,30.508333333333336,58.458333333333336,10.141666666666667,173
|
| 87 |
+
2024-10-13,46.15,28.295833333333334,413.8333333333333,23.758333333333336,29.90416666666667,69.41666666666667,9.058333333333334,116
|
| 88 |
+
2024-10-14,38.074999999999996,24.704166666666666,370.375,19.145833333333332,30.599999999999998,61.416666666666664,8.0,85
|
| 89 |
+
2024-10-15,40.954166666666666,26.004166666666666,352.375,17.041666666666668,31.104166666666668,56.166666666666664,13.283333333333333,132
|
| 90 |
+
2024-10-16,35.083333333333336,21.666666666666668,315.9166666666667,14.695833333333333,30.770833333333332,64.45833333333333,14.304166666666667,110
|
| 91 |
+
2024-10-17,35.625,20.666666666666668,305.2083333333333,15.325000000000001,29.916666666666668,73.79166666666667,15.295833333333334,71
|
| 92 |
+
2024-10-18,35.78333333333333,22.3875,409.4583333333333,22.974999999999998,29.208333333333332,75.625,9.775,112
|
| 93 |
+
2024-10-19,41.6,25.6125,384.25,20.729166666666668,30.570833333333336,59.041666666666664,11.479166666666666,149
|
| 94 |
+
2024-10-20,39.275,23.508333333333336,390.9583333333333,21.125,30.691666666666666,58.041666666666664,12.612499999999999,121
|
| 95 |
+
2024-10-21,34.449999999999996,19.183333333333334,332.3333333333333,17.266666666666666,29.545833333333334,71.25,13.8125,100
|
| 96 |
+
2024-10-22,47.824999999999996,26.337500000000002,346.6666666666667,17.0625,29.733333333333334,66.5,12.958333333333334,105
|
| 97 |
+
2024-10-23,58.82083333333333,33.65833333333333,417.7916666666667,22.708333333333332,29.304166666666664,71.625,13.666666666666666,115
|
| 98 |
+
2024-10-24,46.012499999999996,24.650000000000002,516.9583333333334,29.191666666666666,30.020833333333332,58.583333333333336,10.129166666666666,141
|
| 99 |
+
2024-10-25,36.416666666666664,15.775,362.5,21.308333333333334,30.529166666666665,58.416666666666664,14.424999999999999,77
|
| 100 |
+
2024-10-26,47.9375,16.141666666666666,401.4166666666667,18.883333333333333,30.079166666666666,69.79166666666667,16.162499999999998,59
|
| 101 |
+
2024-10-27,77.10833333333333,26.979166666666668,794.5833333333334,27.0,30.125,67.33333333333333,10.75,80
|
| 102 |
+
2024-10-28,77.3,30.787499999999998,572.0833333333334,20.554166666666667,29.22083333333333,77.125,12.970833333333333,91
|
| 103 |
+
2024-10-29,119.19166666666666,41.36666666666667,579.0833333333334,17.679166666666667,28.679166666666664,81.41666666666667,12.970833333333333,114
|
| 104 |
+
2024-10-30,141.03333333333333,38.775,494.5416666666667,21.2375,28.254166666666666,82.83333333333333,14.345833333333333,123
|
| 105 |
+
2024-10-31,101.10833333333333,37.887499999999996,517.25,26.2,28.2375,78.625,11.1,108
|
| 106 |
+
2024-11-01,68.59166666666667,29.7375,463.5833333333333,25.354166666666668,28.650000000000002,72.45833333333333,13.379166666666668,106
|
| 107 |
+
2024-11-02,51.800000000000004,24.25,541.3333333333334,21.275000000000002,28.15833333333333,79.66666666666667,14.533333333333333,88
|
| 108 |
+
2024-11-03,59.10833333333334,27.40833333333333,749.5833333333334,29.420833333333334,27.462500000000002,82.83333333333333,8.879166666666666,85
|
| 109 |
+
2024-11-04,49.8125,25.795833333333334,729.5833333333334,31.041666666666668,27.287499999999998,74.91666666666667,10.049999999999999,99
|
| 110 |
+
2024-11-05,48.729166666666664,28.03333333333333,583.1666666666666,29.354166666666668,27.295833333333334,71.83333333333333,9.616666666666667,96
|
| 111 |
+
2024-11-06,42.55833333333333,25.708333333333332,626.625,28.770833333333332,27.545833333333334,71.79166666666667,11.008333333333333,86
|
| 112 |
+
2024-11-07,48.041666666666664,27.208333333333332,553.2916666666666,22.575,27.683333333333334,80.25,14.208333333333334,85
|
| 113 |
+
2024-11-08,39.56666666666667,19.483333333333334,776.2916666666666,20.420833333333334,28.075,77.375,12.108333333333334,82
|
| 114 |
+
2024-11-09,43.24166666666667,21.4875,642.75,24.683333333333334,27.3,77.95833333333333,11.0,70
|
| 115 |
+
2024-11-10,40.71666666666667,20.770833333333332,570.8333333333334,25.054166666666664,27.212500000000002,77.20833333333333,12.095833333333333,70
|
| 116 |
+
2024-11-11,48.275,23.425,806.0,26.145833333333332,27.02916666666667,77.625,10.5375,75
|
| 117 |
+
2024-11-12,59.9625,29.34166666666667,938.4583333333334,34.708333333333336,27.28333333333333,76.33333333333333,8.3,85
|
| 118 |
+
2024-11-13,56.675000000000004,41.454166666666666,803.4166666666666,42.641666666666666,27.1375,67.91666666666667,5.675,114
|
| 119 |
+
2024-11-14,45.604166666666664,37.97083333333333,754.0833333333334,38.22083333333333,26.84583333333333,61.125,6.829166666666667,115
|
| 120 |
+
2024-11-15,43.39166666666667,39.92916666666667,692.0,28.991666666666664,26.837500000000002,53.291666666666664,7.558333333333334,149
|
| 121 |
+
2024-11-16,38.574999999999996,33.59166666666667,807.125,28.983333333333334,26.7625,65.16666666666667,6.829166666666667,115
|
| 122 |
+
2024-11-17,40.09166666666667,30.454166666666666,917.875,31.65416666666667,26.641666666666666,68.70833333333333,6.779166666666666,96
|
| 123 |
+
2024-11-18,51.0375,42.112500000000004,972.2083333333334,42.925000000000004,27.204166666666666,54.875,7.404166666666666,114
|
| 124 |
+
2024-11-19,48.137499999999996,41.9625,929.6666666666666,40.458333333333336,26.90416666666667,53.458333333333336,7.545833333333333,130
|
| 125 |
+
2024-11-20,32.90833333333333,28.504166666666666,851.5416666666666,28.087500000000002,26.308333333333334,56.875,7.116666666666667,116
|
| 126 |
+
2024-11-21,36.016666666666666,32.6625,1046.75,35.833333333333336,25.766666666666666,66.45833333333333,6.466666666666666,97
|
| 127 |
+
2024-11-22,26.333333333333332,23.366666666666664,973.4583333333334,27.120833333333334,26.120833333333334,65.83333333333333,7.3999999999999995,94
|
| 128 |
+
2024-11-23,25.05,21.370833333333334,962.9583333333334,26.495833333333334,26.379166666666666,64.83333333333333,7.941666666666666,74
|
| 129 |
+
2024-11-24,33.80833333333333,28.870833333333334,1013.9583333333334,34.47083333333333,26.224999999999998,67.20833333333333,6.220833333333334,108
|
| 130 |
+
2024-11-25,46.525,40.729166666666664,959.2916666666666,53.52916666666667,25.833333333333332,52.958333333333336,6.791666666666667,122
|
| 131 |
+
2024-11-26,48.62083333333334,41.99583333333333,1009.1666666666666,48.99166666666667,25.558333333333334,58.291666666666664,6.1375,137
|
| 132 |
+
2024-11-27,38.0375,28.46666666666667,1102.5,40.65416666666667,26.066666666666666,71.08333333333333,6.175,116
|
| 133 |
+
2024-11-28,43.24583333333334,36.225,1461.4583333333333,44.0,25.891666666666666,75.125,5.245833333333334,106
|
| 134 |
+
2024-11-29,32.199999999999996,25.770833333333332,869.875,33.40833333333333,25.508333333333336,73.33333333333333,6.0375000000000005,102
|
| 135 |
+
2024-11-30,40.737500000000004,37.375,829.125,37.74583333333333,25.895833333333332,56.0,7.8500000000000005,103
|
| 136 |
+
2024-12-01,52.699999999999996,50.237500000000004,1113.7916666666667,36.87083333333333,24.820833333333336,42.333333333333336,7.145833333333333,136
|
| 137 |
+
2024-12-02,44.833333333333336,42.17916666666667,938.7916666666666,36.608333333333334,25.079166666666666,39.333333333333336,6.916666666666667,137
|
| 138 |
+
2024-12-03,47.625,43.74583333333334,613.4166666666666,15.908333333333333,25.337500000000002,43.541666666666664,11.220833333333333,124
|
| 139 |
+
2024-12-04,53.90416666666667,47.73333333333333,701.7083333333334,15.441666666666668,25.454166666666666,42.25,12.808333333333332,130
|
| 140 |
+
2024-12-05,58.88333333333333,50.04583333333333,1093.7083333333333,28.925,24.0625,49.625,6.920833333333333,138
|
| 141 |
+
2024-12-06,57.38333333333333,36.65833333333333,1179.2916666666667,38.44166666666667,23.45,56.916666666666664,6.291666666666667,136
|
| 142 |
+
2024-12-07,35.27916666666667,32.1,828.8333333333334,47.34583333333333,22.629166666666666,32.583333333333336,6.979166666666667,103
|
| 143 |
+
2024-12-08,38.975,29.204166666666666,460.8333333333333,37.920833333333334,22.291666666666668,13.833333333333334,11.012500000000001,101
|
| 144 |
+
2024-12-09,37.145833333333336,25.866666666666664,1039.25,35.6625,20.395833333333332,22.375,7.7375,87
|
| 145 |
+
2024-12-10,32.46666666666667,29.6875,1030.7916666666667,28.3,20.425,26.541666666666668,6.945833333333333,86
|
| 146 |
+
2024-12-11,44.94166666666666,39.949999999999996,1033.5833333333333,43.225,20.170833333333334,31.0,5.945833333333333,111
|
| 147 |
+
2024-12-12,32.88333333333333,28.616666666666664,643.6666666666666,33.59583333333333,19.604166666666668,29.666666666666668,8.033333333333333,112
|
| 148 |
+
2024-12-13,20.662499999999998,17.525000000000002,535.875,14.125,19.154166666666665,25.958333333333332,13.450000000000001,85
|
| 149 |
+
2024-12-14,28.916666666666668,27.733333333333334,695.0,15.8375,19.870833333333334,26.083333333333332,11.924999999999999,84
|
| 150 |
+
2024-12-15,26.129166666666666,25.416666666666668,883.75,29.008333333333336,19.375,28.5,7.779166666666666,91
|
| 151 |
+
2024-12-16,29.162499999999998,28.804166666666664,1062.3333333333333,38.32083333333333,20.287499999999998,31.166666666666668,6.504166666666666,85
|
| 152 |
+
2024-12-17,57.074999999999996,38.09166666666666,904.0833333333334,28.6375,20.104166666666668,35.583333333333336,6.0249999999999995,114
|
| 153 |
+
2024-12-18,41.65,36.87916666666667,729.75,21.420833333333334,20.820833333333333,27.708333333333332,8.270833333333334,106
|
| 154 |
+
2024-12-19,43.93333333333334,39.72083333333333,928.5,27.116666666666664,19.845833333333335,33.625,6.870833333333334,112
|
| 155 |
+
2024-12-20,33.6,29.941666666666666,777.6666666666666,20.825,19.425,32.125,9.995833333333334,111
|
| 156 |
+
2024-12-21,41.824999999999996,40.208333333333336,790.5416666666666,18.454166666666666,18.675,30.041666666666668,10.275,116
|
| 157 |
+
2024-12-22,53.6875,47.19166666666666,1076.875,26.879166666666666,18.733333333333334,42.791666666666664,7.470833333333334,131
|
| 158 |
+
2024-12-23,70.54583333333333,56.76666666666667,1171.0,40.699999999999996,19.370833333333334,55.333333333333336,6.3999999999999995,150
|
| 159 |
+
2024-12-24,69.425,68.07916666666667,880.2916666666666,20.116666666666667,19.095833333333335,36.875,9.933333333333334,157
|
| 160 |
+
2024-12-25,67.75833333333334,66.79166666666667,878.3333333333334,14.9,18.675,39.916666666666664,11.266666666666666,157
|
| 161 |
+
2024-12-26,60.979166666666664,59.362500000000004,838.4583333333334,17.258333333333333,18.904166666666665,37.791666666666664,11.033333333333333,157
|
| 162 |
+
2024-12-27,43.775,42.3125,1341.8333333333333,28.004166666666666,18.858333333333334,36.083333333333336,6.816666666666666,152
|
| 163 |
+
2024-12-28,27.349999999999998,25.412499999999998,976.8333333333334,34.4375,18.620833333333334,31.416666666666668,7.070833333333333,117
|
| 164 |
+
2024-12-29,31.458333333333332,28.266666666666666,931.5416666666666,31.945833333333336,18.970833333333335,35.125,8.387500000000001,84
|
| 165 |
+
2024-12-30,40.458333333333336,39.72083333333333,1220.125,28.900000000000002,19.599999999999998,32.916666666666664,8.200000000000001,109
|
| 166 |
+
2024-12-31,40.02916666666667,39.079166666666666,1384.0833333333333,30.84583333333333,19.8125,34.666666666666664,6.2375,122
|
| 167 |
+
2025-01-01,40.68333333333333,37.52916666666667,972.75,20.754166666666666,20.408333333333335,35.083333333333336,8.125,109
|
| 168 |
+
2025-01-02,55.4,51.3375,1431.6666666666667,32.391666666666666,19.833333333333332,45.583333333333336,7.129166666666666,138
|
| 169 |
+
2025-01-03,72.95833333333333,66.60833333333333,1241.0416666666667,35.795833333333334,19.891666666666666,55.083333333333336,6.254166666666666,156
|
| 170 |
+
2025-01-04,69.32916666666667,51.67916666666667,776.3333333333334,36.06666666666667,20.587500000000002,54.833333333333336,9.5875,160
|
| 171 |
+
2025-01-05,55.06666666666666,40.22083333333333,908.6666666666666,39.32083333333333,18.075,46.833333333333336,6.6125,140
|
| 172 |
+
2025-01-06,35.25833333333333,31.079166666666666,1103.7083333333333,41.30833333333333,18.479166666666668,37.833333333333336,6.604166666666667,118
|
| 173 |
+
2025-01-07,22.954166666666666,21.925,958.9166666666666,31.349999999999998,18.829166666666666,24.416666666666668,7.670833333333333,91
|
| 174 |
+
2025-01-08,28.087500000000002,27.829166666666666,1175.4166666666667,37.4375,17.75,36.791666666666664,6.054166666666667,98
|
| 175 |
+
2025-01-09,34.52916666666667,33.829166666666666,1418.375,44.02916666666667,18.620833333333334,51.5,6.0874999999999995,105
|
| 176 |
+
2025-01-10,46.35833333333333,45.6875,1071.2083333333333,53.05833333333334,19.908333333333335,38.583333333333336,6.958333333333333,125
|
| 177 |
+
2025-01-11,45.47083333333333,45.12916666666666,1102.5,41.833333333333336,19.983333333333334,43.083333333333336,6.0249999999999995,137
|
| 178 |
+
2025-01-12,41.75416666666667,41.62083333333333,1100.9583333333333,35.40833333333333,19.9375,34.625,8.583333333333334,138
|
| 179 |
+
2025-01-13,53.9375,53.12083333333334,893.7083333333334,13.700000000000001,18.733333333333334,41.375,12.799999999999999,146
|
| 180 |
+
2025-01-14,57.770833333333336,55.854166666666664,951.75,30.587500000000002,18.354166666666668,52.375,8.041666666666666,150
|
| 181 |
+
2025-01-15,61.24166666666667,55.60833333333333,1064.6666666666667,37.358333333333334,19.416666666666668,52.541666666666664,7.170833333333333,151
|
| 182 |
+
2025-01-16,78.47500000000001,69.3,1367.6666666666667,45.82916666666667,18.875,54.166666666666664,6.295833333333333,157
|
| 183 |
+
2025-01-17,42.329166666666666,37.275,769.4583333333334,22.52916666666667,19.383333333333333,39.25,11.737499999999999,157
|
| 184 |
+
2025-01-18,45.75,43.68333333333334,782.7916666666666,19.833333333333332,19.775000000000002,35.083333333333336,11.4375,120
|
| 185 |
+
2025-01-19,47.62916666666666,46.44583333333333,1000.9166666666666,25.979166666666668,19.829166666666666,33.583333333333336,7.5375000000000005,127
|
| 186 |
+
2025-01-20,57.62916666666666,56.425000000000004,1320.125,29.395833333333332,20.349999999999998,39.291666666666664,8.633333333333333,150
|
| 187 |
+
2025-01-21,53.025,49.11666666666667,1226.2083333333333,27.354166666666668,19.695833333333333,50.416666666666664,7.3374999999999995,150
|
| 188 |
+
2025-01-22,41.12916666666667,30.96666666666667,728.875,27.65833333333333,20.608333333333334,60.083333333333336,7.7749999999999995,134
|
| 189 |
+
2025-01-23,38.175000000000004,31.825,657.25,30.104166666666668,20.599999999999998,36.125,8.358333333333333,105
|
| 190 |
+
2025-01-24,19.645833333333332,16.204166666666666,640.8333333333334,17.0125,20.495833333333334,25.125,11.295833333333334,92
|
| 191 |
+
2025-01-25,26.008333333333336,22.575,1166.7083333333333,29.991666666666664,20.404166666666665,27.875,8.445833333333333,82
|
| 192 |
+
2025-01-26,39.075,34.09583333333333,967.8333333333334,46.458333333333336,19.604166666666668,40.416666666666664,6.920833333333333,103
|
| 193 |
+
2025-01-27,44.62083333333334,38.50833333333333,970.5833333333334,46.9625,19.837500000000002,62.916666666666664,6.391666666666667,110
|
| 194 |
+
2025-01-28,33.583333333333336,31.016666666666666,803.0,42.391666666666666,19.837500000000002,75.29166666666667,6.704166666666667,108
|
| 195 |
+
2025-01-29,40.12083333333333,22.116666666666664,543.3333333333334,24.34166666666667,21.45,80.5,8.683333333333334,90
|
| 196 |
+
2025-01-30,74.84166666666667,33.324999999999996,591.0833333333334,22.166666666666668,21.45,51.583333333333336,8.404166666666667,95
|
| 197 |
+
2025-01-31,91.175,34.170833333333334,429.1666666666667,18.3875,19.404166666666665,29.0,11.4625,106
|
| 198 |
+
2025-02-01,102.58333333333333,43.708333333333336,666.75,35.99583333333333,18.816666666666666,43.916666666666664,7.4125000000000005,119
|
| 199 |
+
2025-02-02,30.962500000000002,24.616666666666664,678.0416666666666,25.099999999999998,20.783333333333335,26.75,9.054166666666667,121
|
| 200 |
+
2025-02-03,35.42916666666667,27.349999999999998,648.4166666666666,15.295833333333334,20.662499999999998,33.416666666666664,9.570833333333333,109
|
| 201 |
+
2025-02-04,34.520833333333336,18.3125,481.5833333333333,16.379166666666666,20.787499999999998,49.458333333333336,10.095833333333333,83
|
| 202 |
+
2025-02-05,37.583333333333336,22.370833333333334,731.8333333333334,29.34583333333333,20.141666666666666,43.541666666666664,7.7625,74
|
| 203 |
+
2025-02-06,31.71666666666667,24.441666666666666,746.4583333333334,34.00416666666667,21.108333333333334,29.166666666666668,9.4125,84
|
| 204 |
+
2025-02-07,25.275000000000002,18.008333333333333,563.375,21.116666666666667,22.78333333333333,18.333333333333332,10.504166666666666,76
|
| 205 |
+
2025-02-08,37.5375,29.712500000000002,801.9583333333334,27.27916666666667,21.95,26.958333333333332,8.5375,96
|
| 206 |
+
2025-02-09,38.233333333333334,35.71666666666667,864.3333333333334,35.0,21.116666666666667,39.041666666666664,7.429166666666667,121
|
| 207 |
+
2025-02-10,28.829166666666666,26.229166666666668,551.5416666666666,23.858333333333334,21.479166666666668,71.25,9.191666666666666,101
|
| 208 |
+
2025-02-11,79.67916666666666,29.516666666666666,447.9166666666667,18.0375,23.0125,75.95833333333333,10.454166666666667,86
|
| 209 |
+
2025-02-12,114.40416666666665,50.79583333333333,853.2083333333334,34.93333333333333,23.258333333333336,58.75,7.020833333333333,135
|
| 210 |
+
2025-02-13,85.99583333333334,49.95416666666667,989.7083333333334,42.07083333333333,23.145833333333332,47.833333333333336,6.554166666666667,150
|
| 211 |
+
2025-02-14,57.6,27.462500000000002,428.8333333333333,22.55,23.979166666666668,65.25,10.375,136
|
| 212 |
+
2025-02-15,67.01666666666667,29.412499999999998,359.7083333333333,16.983333333333334,24.7375,66.875,12.775,87
|
| 213 |
+
2025-02-16,57.375,24.620833333333334,424.6666666666667,17.275000000000002,24.758333333333336,67.75,10.466666666666667,87
|
| 214 |
+
2025-02-17,50.85,27.395833333333332,774.4583333333334,28.320833333333336,25.224999999999998,60.375,7.254166666666666,108
|
| 215 |
+
2025-02-18,50.9375,22.308333333333334,603.6666666666666,24.245833333333334,25.34583333333333,61.125,8.9625,91
|
| 216 |
+
2025-02-19,49.00416666666666,18.708333333333332,343.0,13.424999999999999,25.5,61.25,14.212499999999999,72
|
| 217 |
+
2025-02-20,120.53750000000001,35.96666666666667,641.4166666666666,24.379166666666666,25.883333333333336,46.791666666666664,8.0625,98
|
| 218 |
+
2025-02-21,128.275,48.15,1043.125,35.71666666666667,24.729166666666668,46.5,5.6375,127
|
| 219 |
+
2025-02-22,55.10833333333333,36.55416666666667,883.4583333333334,35.125,23.712500000000002,44.125,7.325,141
|
| 220 |
+
2025-02-23,42.69166666666666,30.3,655.4583333333334,31.15833333333333,22.96666666666667,74.79166666666667,7.133333333333333,105
|
| 221 |
+
2025-02-24,47.979166666666664,22.808333333333334,627.9166666666666,34.074999999999996,23.34583333333333,79.58333333333333,6.75,89
|
| 222 |
+
2025-02-25,79.82916666666667,34.00833333333333,881.5833333333334,47.02916666666667,24.241666666666664,65.83333333333333,5.670833333333333,96
|
| 223 |
+
2025-02-26,51.52916666666667,21.833333333333332,555.75,37.77916666666667,24.15833333333333,69.70833333333333,6.808333333333334,97
|
| 224 |
+
2025-02-27,28.075,16.570833333333333,365.6666666666667,23.65833333333333,23.516666666666666,80.08333333333333,9.616666666666667,71
|
| 225 |
+
2025-02-28,59.40833333333333,28.020833333333332,299.5416666666667,12.950000000000001,24.2625,74.41666666666667,14.954166666666666,84
|
| 226 |
+
2025-03-01,56.57916666666667,29.229166666666668,427.3333333333333,17.608333333333334,24.008333333333336,70.875,10.858333333333334,96
|
| 227 |
+
2025-03-02,43.050000000000004,27.391666666666666,608.1666666666666,22.079166666666666,22.683333333333334,69.0,8.525,128
|
| 228 |
+
2025-03-03,74.97500000000001,27.916666666666668,363.625,14.929166666666667,24.191666666666666,71.625,12.075000000000001,83
|
| 229 |
+
2025-03-04,102.96249999999999,36.358333333333334,582.5416666666666,25.941666666666666,24.21666666666667,31.208333333333332,7.954166666666667,116
|
| 230 |
+
2025-03-05,36.375,21.120833333333334,600.0416666666666,23.4875,23.09583333333333,23.166666666666668,8.379166666666666,109
|
| 231 |
+
2025-03-06,33.46666666666667,20.3625,547.4583333333334,25.054166666666664,24.754166666666666,57.125,9.029166666666667,89
|
| 232 |
+
2025-03-07,46.30416666666667,22.666666666666668,753.875,25.474999999999998,26.104166666666668,54.833333333333336,7.541666666666667,111
|
| 233 |
+
2025-03-08,69.52083333333333,22.820833333333336,524.5,20.354166666666668,28.008333333333336,41.541666666666664,9.820833333333333,78
|
| 234 |
+
2025-03-09,59.275,27.7375,619.2916666666666,28.662499999999998,28.666666666666668,41.75,8.008333333333333,143
|
| 235 |
+
2025-03-10,45.24583333333334,25.429166666666664,697.9583333333334,34.199999999999996,28.5,42.666666666666664,8.0,86
|
| 236 |
+
2025-03-11,32.1,22.558333333333334,680.5,36.829166666666666,27.454166666666666,48.5,8.095833333333333,82
|
| 237 |
+
2025-03-12,26.891666666666666,18.25,524.0416666666666,25.204166666666666,26.866666666666664,62.375,9.141666666666667,72
|
| 238 |
+
2025-03-13,30.537499999999998,15.5875,412.25,16.537499999999998,27.795833333333334,62.541666666666664,11.85,63
|
| 239 |
+
2025-03-14,66.83749999999999,24.5625,344.6666666666667,12.870833333333332,27.55,60.958333333333336,14.433333333333332,76
|
| 240 |
+
2025-03-15,66.58749999999999,28.854166666666668,450.9583333333333,16.570833333333333,27.008333333333336,49.25,9.716666666666667,133
|
| 241 |
+
2025-03-16,64.0,26.395833333333332,509.25,20.941666666666666,26.787499999999998,43.166666666666664,9.741666666666667,120
|
| 242 |
+
2025-03-17,58.99166666666667,29.108333333333334,574.875,35.00833333333333,26.3625,43.791666666666664,8.9625,95
|
| 243 |
+
2025-03-18,37.199999999999996,16.533333333333335,644.0833333333334,21.308333333333334,25.454166666666666,62.416666666666664,8.229166666666666,86
|
| 244 |
+
2025-03-19,26.8,14.370833333333332,442.25,17.658333333333335,25.679166666666664,65.20833333333333,11.083333333333334,65
|
| 245 |
+
2025-03-20,28.849999999999998,15.920833333333334,445.0,17.7,26.241666666666664,69.75,9.9625,97
|
| 246 |
+
2025-03-21,34.7875,18.316666666666666,540.0,18.6125,26.775000000000002,64.5,9.520833333333334,107
|
| 247 |
+
2025-03-22,42.42916666666667,20.875,501.2916666666667,19.816666666666666,26.75,64.41666666666667,9.737499999999999,87
|
| 248 |
+
2025-03-23,18.599999999999998,13.85,468.5416666666667,22.545833333333334,27.8,52.916666666666664,9.9375,90
|
| 249 |
+
2025-03-24,12.154166666666667,9.916666666666666,394.7083333333333,19.766666666666666,27.545833333333334,45.833333333333336,11.791666666666666,80
|
| 250 |
+
2025-03-25,19.575,13.979166666666666,329.4583333333333,17.2625,25.650000000000002,71.95833333333333,11.929166666666667,70
|
| 251 |
+
2025-03-26,28.179166666666664,15.883333333333333,349.8333333333333,14.945833333333333,26.03333333333333,70.0,12.929166666666667,115
|
| 252 |
+
2025-03-27,82.35833333333333,27.170833333333334,414.9583333333333,14.679166666666667,27.1375,42.208333333333336,11.441666666666668,111
|
| 253 |
+
2025-03-28,93.35833333333333,27.454166666666666,549.2916666666666,22.608333333333334,26.183333333333334,23.625,9.820833333333333,158
|
| 254 |
+
2025-03-29,55.916666666666664,28.587500000000002,776.4166666666666,34.61666666666667,26.945833333333336,22.458333333333332,7.654166666666666,120
|
| 255 |
+
2025-03-30,38.6,21.579166666666666,530.7083333333334,23.03333333333333,26.5,39.875,9.549999999999999,158
|
| 256 |
+
2025-03-31,38.50833333333333,19.0,627.3333333333334,22.583333333333332,26.045833333333334,65.54166666666667,8.108333333333333,119
|
| 257 |
+
2025-04-01,62.512499999999996,25.320833333333336,588.625,27.433333333333334,27.1375,62.375,7.9875,134
|
| 258 |
+
2025-04-02,44.02916666666667,25.825,689.0,34.99583333333333,27.104166666666668,60.541666666666664,6.879166666666666,131
|
| 259 |
+
2025-04-03,37.3,22.545833333333334,571.5,29.1375,27.337500000000002,65.95833333333333,8.979166666666666,114
|
| 260 |
+
2025-04-04,29.087500000000002,18.879166666666666,646.0833333333334,26.40416666666667,27.958333333333332,61.208333333333336,8.708333333333334,156
|
| 261 |
+
2025-04-05,14.7125,11.245833333333332,402.2916666666667,18.0625,28.46666666666667,60.666666666666664,11.008333333333333,87
|
| 262 |
+
2025-04-06,28.695833333333336,12.245833333333332,348.625,13.516666666666666,30.258333333333336,42.166666666666664,15.275,74
|
| 263 |
+
2025-04-07,21.34583333333333,13.295833333333334,390.5,14.65,29.133333333333336,54.291666666666664,14.058333333333332,80
|
| 264 |
+
2025-04-08,37.69166666666667,22.745833333333334,450.5833333333333,20.5625,28.016666666666666,71.5,9.5375,81
|
| 265 |
+
2025-04-09,41.84166666666667,21.045833333333334,449.9583333333333,21.412499999999998,27.666666666666668,76.58333333333333,9.85,75
|
| 266 |
+
2025-04-10,35.22083333333333,17.1625,308.375,15.816666666666668,27.6875,77.375,11.887500000000001,69
|
| 267 |
+
2025-04-11,47.68333333333334,22.391666666666666,347.2083333333333,14.529166666666667,28.008333333333336,76.95833333333333,11.733333333333334,72
|
| 268 |
+
2025-04-12,48.38333333333333,21.641666666666666,379.5416666666667,15.112499999999999,29.4375,65.54166666666667,10.737499999999999,76
|
| 269 |
+
2025-04-13,32.270833333333336,15.762500000000001,294.4166666666667,10.575000000000001,28.59166666666667,72.70833333333333,13.683333333333332,71
|
| 270 |
+
2025-04-14,29.429166666666664,15.954166666666666,287.1666666666667,10.450000000000001,28.28333333333333,76.29166666666667,16.291666666666668,59
|
| 271 |
+
2025-04-15,37.0,17.745833333333334,279.7083333333333,10.483333333333333,28.704166666666666,77.08333333333333,14.804166666666667,64
|
| 272 |
+
2025-04-16,41.791666666666664,18.662499999999998,281.8333333333333,9.9125,29.008333333333336,71.875,14.549999999999999,65
|
| 273 |
+
2025-04-17,31.425,14.716666666666667,276.5416666666667,9.816666666666666,29.349999999999998,67.29166666666667,16.783333333333335,64
|
| 274 |
+
2025-04-18,37.516666666666666,17.254166666666666,269.9166666666667,9.375,28.77916666666667,71.375,14.975000000000001,71
|
| 275 |
+
2025-04-19,209.775,52.29583333333333,314.2083333333333,9.866666666666665,29.954166666666666,60.125,13.904166666666667,136
|
| 276 |
+
2025-04-20,183.67499999999998,51.387499999999996,454.0,14.799999999999999,31.241666666666664,40.333333333333336,11.629166666666668,158
|
| 277 |
+
2025-04-21,135.11249999999998,45.1,471.9166666666667,18.116666666666667,30.25,44.541666666666664,9.970833333333333,140
|
| 278 |
+
2025-04-22,89.41666666666667,34.65,532.8333333333334,19.945833333333333,29.329166666666666,59.291666666666664,7.879166666666666,137
|
| 279 |
+
2025-04-23,81.21249999999999,28.833333333333332,440.0416666666667,17.129166666666666,28.958333333333332,66.375,9.404166666666667,98
|
| 280 |
+
2025-04-24,44.59583333333333,20.900000000000002,383.5833333333333,14.533333333333333,29.075,69.5,11.516666666666666,97
|
| 281 |
+
2025-04-25,54.17083333333333,22.504166666666666,405.2083333333333,14.720833333333333,28.8625,75.70833333333333,9.3,84
|
| 282 |
+
2025-04-26,69.31666666666666,25.849999999999998,369.7083333333333,18.104166666666668,31.095833333333335,56.416666666666664,11.625,121
|
| 283 |
+
2025-04-27,71.2625,28.212500000000002,409.0416666666667,18.658333333333335,30.383333333333336,62.625,10.408333333333333,88
|
| 284 |
+
2025-04-28,47.9,22.233333333333334,367.8333333333333,15.362499999999999,30.941666666666666,59.791666666666664,11.929166666666667,84
|
| 285 |
+
2025-04-29,20.395833333333332,13.508333333333333,322.3333333333333,12.870833333333332,31.59166666666667,51.666666666666664,14.725,72
|
| 286 |
+
2025-04-30,24.425,14.645833333333334,294.75,11.112499999999999,31.02916666666667,56.791666666666664,14.704166666666666,57
|
| 287 |
+
2025-05-01,25.241666666666664,15.679166666666667,282.0833333333333,10.008333333333335,30.183333333333334,65.16666666666667,14.695833333333333,58
|
| 288 |
+
2025-05-02,27.354166666666668,16.495833333333334,271.1666666666667,10.395833333333334,29.941666666666666,70.66666666666667,14.608333333333334,60
|
| 289 |
+
2025-05-03,31.308333333333334,17.333333333333332,305.0833333333333,14.924999999999999,30.09166666666667,71.41666666666667,12.279166666666667,75
|
| 290 |
+
2025-05-04,30.883333333333336,18.741666666666667,351.4583333333333,14.0625,30.004166666666666,71.0,11.279166666666667,64
|
| 291 |
+
2025-05-05,69.09166666666667,25.96666666666667,525.5,17.5125,29.729166666666668,68.91666666666667,7.445833333333333,79
|
| 292 |
+
2025-05-06,93.61666666666667,36.06666666666667,492.625,25.5625,29.645833333333332,74.66666666666667,8.591666666666667,140
|
| 293 |
+
2025-05-07,53.84166666666667,21.8875,397.125,15.641666666666666,29.75,75.75,7.695833333333333,108
|
| 294 |
+
2025-05-08,47.04583333333333,21.325,313.5416666666667,18.995833333333334,29.587500000000002,75.66666666666667,7.191666666666666,75
|
| 295 |
+
2025-05-09,70.41250000000001,28.429166666666664,341.0,16.466666666666665,29.829166666666666,72.0,8.275,85
|
| 296 |
+
2025-05-10,69.45,29.583333333333332,275.8333333333333,13.6,30.037499999999998,70.16666666666667,11.066666666666668,87
|
| 297 |
+
2025-05-11,77.57083333333334,33.90833333333333,259.2916666666667,10.458333333333334,29.695833333333336,76.04166666666667,12.924999999999999,95
|
| 298 |
+
2025-05-12,75.21249999999999,30.666666666666668,240.25,9.25,29.474999999999998,76.41666666666667,13.775,107
|
| 299 |
+
2025-05-13,37.4375,19.645833333333332,244.75,9.6875,29.5625,77.0,12.604166666666666,90
|
| 300 |
+
2025-05-14,45.25833333333333,22.504166666666666,294.3333333333333,12.629166666666668,30.462500000000002,75.66666666666667,10.737499999999999,75
|
| 301 |
+
2025-05-15,44.94166666666666,21.754166666666666,314.8333333333333,15.566666666666668,30.458333333333332,75.70833333333333,10.345833333333333,83
|
| 302 |
+
2025-05-16,37.31666666666667,19.516666666666666,289.0,14.470833333333333,30.145833333333332,79.29166666666667,11.179166666666667,71
|
| 303 |
+
2025-05-17,37.87083333333333,18.412499999999998,250.20833333333334,12.541666666666666,30.1375,74.75,11.991666666666667,66
|
| 304 |
+
2025-05-18,33.737500000000004,16.883333333333333,256.0833333333333,11.8125,30.15416666666667,73.33333333333333,10.216666666666667,64
|
| 305 |
+
2025-05-19,40.045833333333334,18.474999999999998,222.625,11.391666666666666,30.008333333333336,72.625,12.129166666666668,63
|
| 306 |
+
2025-05-20,69.88333333333334,23.045833333333334,208.41666666666666,8.483333333333333,30.379166666666666,70.875,13.720833333333333,73
|
| 307 |
+
2025-05-21,105.09583333333335,27.40833333333333,218.95833333333334,8.9125,30.208333333333332,72.66666666666667,13.975,82
|
| 308 |
+
2025-05-22,83.22500000000001,22.941666666666666,221.79166666666666,10.112499999999999,29.9375,74.70833333333333,13.737499999999999,83
|
| 309 |
+
2025-05-23,54.175000000000004,18.754166666666666,211.66666666666666,10.4625,30.195833333333336,73.95833333333333,14.970833333333333,73
|
| 310 |
+
2025-05-24,57.24583333333334,21.72083333333333,209.125,8.737499999999999,30.55,72.66666666666667,15.445833333333333,70
|
| 311 |
+
2025-05-25,68.5625,27.670833333333334,213.08333333333334,8.670833333333333,30.925,75.0,14.529166666666667,83
|
| 312 |
+
2025-05-26,62.32083333333333,28.395833333333332,222.375,10.029166666666667,31.25,74.0,14.245833333333332,86
|
| 313 |
+
2025-05-27,47.43333333333334,22.8625,211.83333333333334,8.291666666666666,30.787499999999998,74.625,16.008333333333333,85
|
| 314 |
+
2025-05-28,67.37916666666666,26.77916666666667,275.0833333333333,11.329166666666666,30.833333333333332,78.5,13.1,81
|
| 315 |
+
2025-05-29,126.78750000000001,33.958333333333336,386.5,12.829166666666666,32.324999999999996,66.25,11.045833333333334,116
|
| 316 |
+
2025-05-30,149.11666666666667,38.17916666666667,256.9166666666667,9.083333333333334,32.6,58.791666666666664,18.041666666666668,112
|
| 317 |
+
2025-05-31,136.88333333333333,35.00416666666667,234.41666666666666,7.0625,31.34583333333333,72.70833333333333,18.183333333333334,107
|
| 318 |
+
2025-06-01,141.975,36.333333333333336,289.375,9.583333333333334,31.925,64.75,15.258333333333333,102
|
| 319 |
+
2025-06-02,188.15833333333333,43.34166666666667,253.58333333333334,8.979166666666666,30.65416666666667,75.20833333333333,15.549999999999999,119
|
| 320 |
+
2025-06-03,90.02083333333333,29.6125,245.875,8.170833333333333,29.849999999999998,79.04166666666667,15.933333333333332,122
|
| 321 |
+
2025-06-04,84.20416666666667,29.2,217.625,8.666666666666666,30.099999999999998,73.875,15.283333333333333,94
|
| 322 |
+
2025-06-05,72.89583333333333,24.416666666666668,211.5,8.991666666666667,30.2625,69.625,12.716666666666667,87
|
| 323 |
+
2025-06-06,65.8125,25.3875,197.25,8.575000000000001,30.391666666666666,68.0,14.262500000000001,79
|
| 324 |
+
2025-06-07,67.27083333333333,24.308333333333334,187.375,7.904166666666666,29.9375,70.625,16.9875,79
|
| 325 |
+
2025-06-08,53.30416666666667,21.95,194.0,7.708333333333333,30.0,70.66666666666667,16.466666666666665,76
|
| 326 |
+
2025-06-09,50.145833333333336,22.554166666666664,191.08333333333334,8.104166666666666,30.025000000000002,69.58333333333333,16.945833333333333,75
|
| 327 |
+
2025-06-10,48.1875,20.770833333333332,196.45833333333334,7.070833333333333,30.34583333333333,67.91666666666667,17.829166666666666,72
|
| 328 |
+
2025-06-11,48.083333333333336,21.537499999999998,199.83333333333334,7.541666666666667,30.383333333333336,71.29166666666667,16.6375,70
|
| 329 |
+
2025-06-12,35.604166666666664,17.558333333333334,197.125,8.095833333333333,30.304166666666664,76.45833333333333,15.754166666666668,70
|
| 330 |
+
2025-06-13,52.88333333333333,20.1625,242.125,10.700000000000001,30.745833333333334,79.625,13.316666666666668,67
|
| 331 |
+
2025-06-14,105.69583333333333,31.058333333333334,308.8333333333333,13.820833333333333,30.395833333333332,82.33333333333333,11.1,90
|
| 332 |
+
2025-06-15,126.3625,34.050000000000004,339.3333333333333,15.8125,30.304166666666664,79.79166666666667,8.670833333333333,98
|
| 333 |
+
2025-06-16,117.09583333333335,40.545833333333334,339.5,27.645833333333332,31.854166666666668,74.16666666666667,7.5625,125
|
| 334 |
+
2025-06-17,100.62083333333334,33.025,289.4583333333333,16.291666666666668,31.895833333333332,74.54166666666667,11.095833333333333,113
|
| 335 |
+
2025-06-18,47.85833333333333,22.0,214.45833333333334,7.2749999999999995,30.541666666666668,77.33333333333333,17.691666666666666,95
|
| 336 |
+
2025-06-19,48.395833333333336,20.3625,197.91666666666666,6.083333333333333,30.3125,75.83333333333333,21.337500000000002,71
|
| 337 |
+
2025-06-20,50.9625,21.158333333333335,224.66666666666666,7.508333333333333,30.008333333333336,81.79166666666667,18.629166666666666,69
|
| 338 |
+
2025-06-21,43.6,20.054166666666667,226.25,8.362499999999999,30.054166666666664,82.91666666666667,17.129166666666666,70
|
| 339 |
+
2025-06-22,57.23333333333333,23.133333333333336,245.125,10.458333333333334,29.920833333333334,81.0,14.670833333333334,73
|
| 340 |
+
2025-06-23,64.375,26.604166666666668,241.33333333333334,11.012500000000001,30.233333333333334,79.625,13.35,80
|
| 341 |
+
2025-06-24,85.29583333333333,30.3125,260.9166666666667,10.979166666666666,30.291666666666668,79.29166666666667,12.275,92
|
| 342 |
+
2025-06-25,83.03333333333333,32.25,367.2083333333333,16.304166666666667,29.958333333333332,78.58333333333333,9.408333333333333,93
|
| 343 |
+
2025-06-26,96.22500000000001,30.058333333333334,409.625,17.104166666666668,30.90833333333333,77.08333333333333,6.816666666666666,93
|
| 344 |
+
2025-06-27,71.84166666666667,34.27916666666667,502.0416666666667,26.825,29.7625,81.95833333333333,8.0625,97
|
| 345 |
+
2025-06-28,46.74583333333334,23.020833333333332,436.8333333333333,16.845833333333335,27.96666666666667,88.58333333333333,11.279166666666667,97
|
| 346 |
+
2025-06-29,26.429166666666664,15.125,332.5416666666667,16.441666666666666,28.912499999999998,84.45833333333333,12.1,74
|
| 347 |
+
2025-06-30,61.583333333333336,24.412499999999998,257.3333333333333,12.254166666666668,29.829166666666666,81.54166666666667,16.408333333333335,76
|
| 348 |
+
2025-07-01,56.770833333333336,23.420833333333334,246.5,10.929166666666667,29.84583333333333,81.54166666666667,14.333333333333334,77
|
| 349 |
+
2025-07-02,68.82083333333334,23.075,250.08333333333334,11.520833333333334,29.791666666666668,81.125,13.570833333333333,74
|
| 350 |
+
2025-07-03,84.2375,27.691666666666666,322.25,16.370833333333334,29.929166666666664,80.375,10.450000000000001,83
|
| 351 |
+
2025-07-04,84.57916666666667,29.058333333333334,286.5416666666667,16.554166666666667,30.3875,78.41666666666667,9.987499999999999,86
|
| 352 |
+
2025-07-05,93.98333333333333,33.27916666666667,242.29166666666666,11.195833333333333,30.929166666666664,75.625,11.7875,96
|
| 353 |
+
2025-07-06,77.71249999999999,27.9875,228.54166666666666,11.658333333333333,30.575,76.25,14.2125,95
|
| 354 |
+
2025-07-07,92.66666666666667,33.829166666666666,313.7916666666667,19.412499999999998,30.40833333333333,76.375,8.945833333333333,97
|
| 355 |
+
2025-07-08,100.04583333333333,34.800000000000004,312.8333333333333,16.554166666666667,30.695833333333336,74.45833333333333,9.2125,98
|
| 356 |
+
2025-07-09,91.84583333333335,32.604166666666664,265.25,15.475,30.495833333333334,77.58333333333333,11.025,99
|
| 357 |
+
2025-07-10,99.40416666666665,32.37916666666667,270.4166666666667,16.195833333333333,30.212500000000002,76.875,10.445833333333333,94
|
| 358 |
+
2025-07-11,76.96666666666667,27.9875,259.0416666666667,15.391666666666666,30.40416666666667,75.79166666666667,11.070833333333333,94
|
| 359 |
+
2025-07-12,73.87083333333334,25.47083333333333,233.08333333333334,12.762500000000001,30.145833333333332,74.33333333333333,12.941666666666668,84
|
| 360 |
+
2025-07-13,79.7625,24.620833333333334,223.625,9.770833333333334,29.916666666666668,74.95833333333333,15.2125,79
|
| 361 |
+
2025-07-14,85.36666666666667,25.212500000000002,220.0,9.0375,29.8625,77.70833333333333,15.629166666666668,79
|
| 362 |
+
2025-07-15,82.0625,29.287499999999998,234.16666666666666,11.591666666666667,29.895833333333332,74.16666666666667,12.666666666666666,86
|
| 363 |
+
2025-07-16,74.47083333333333,27.858333333333334,242.875,11.620833333333332,29.854166666666668,73.0,11.729166666666666,90
|
| 364 |
+
2025-07-17,76.37916666666666,28.15833333333333,254.875,11.320833333333333,29.679166666666664,76.0,12.025,87
|
| 365 |
+
2025-07-18,68.875,26.008333333333336,248.54166666666666,10.5625,28.55,78.33333333333333,9.6,84
|
data/karachi_historical_data_5_years_hourly.csv
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/karachi_hourly_data.csv
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/last_7_days_daily_data.csv
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
timestamp,temperature,humidity,wind_speed,pm10,pm25,carbon_monoxide,nitrogen_dioxide,aqi
|
| 2 |
+
2026-03-29 00:00:00+05:00,25.1,64.0,12.6,45.1,19.8,304.0,17.6,64
|
| 3 |
+
2026-03-30 00:00:00+05:00,25.670833333333334,53.791666666666664,14.558333333333332,42.262499999999996,19.245833333333334,276.25,9.358333333333333,107
|
| 4 |
+
2026-03-31 00:00:00+05:00,25.2375,55.0,11.708333333333334,50.00416666666666,23.650000000000002,338.2916666666667,11.816666666666668,97
|
| 5 |
+
2026-04-01 00:00:00+05:00,25.825,61.5,11.575000000000001,40.55416666666667,20.741666666666667,347.5,14.1,88
|
| 6 |
+
2026-04-02 00:00:00+05:00,22.5625,81.25,7.495833333333334,59.887499999999996,22.479166666666668,388.9583333333333,16.883333333333333,75
|
| 7 |
+
2026-04-03 00:00:00+05:00,22.150000000000002,76.5,9.6125,26.47083333333333,15.170833333333333,605.8333333333334,18.370833333333334,72
|
| 8 |
+
2026-04-04 00:00:00+05:00,25.254166666666666,66.75,11.745833333333332,27.96666666666667,16.400000000000002,454.4166666666667,17.3125,98
|
| 9 |
+
2026-04-05 00:00:00+05:00,25.03478260869565,73.6086956521739,13.334782608695651,26.686956521739127,18.043478260869566,498.2608695652174,18.830434782608698,100
|
data/last_7_days_hourly_data.csv
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
timestamp,temperature,humidity,wind_speed,pm10,pm25,carbon_monoxide,nitrogen_dioxide,aqi
|
| 2 |
+
2026-03-29 23:00:00+05:00,25.1,64,12.6,45.1,19.8,304.0,17.6,64
|
| 3 |
+
2026-03-30 00:00:00+05:00,25.4,65,12.9,48.1,20.3,266.0,15.4,63
|
| 4 |
+
2026-03-30 01:00:00+05:00,25.5,58,14.4,50.5,20.3,219.0,12.3,63
|
| 5 |
+
2026-03-30 02:00:00+05:00,25.1,60,14.7,51.3,20.1,185.0,9.8,63
|
| 6 |
+
2026-03-30 03:00:00+05:00,24.9,65,15.1,49.7,19.7,170.0,8.1,62
|
| 7 |
+
2026-03-30 04:00:00+05:00,24.8,66,14.7,44.7,18.9,169.0,6.9,62
|
| 8 |
+
2026-03-30 05:00:00+05:00,24.0,62,14.2,36.4,17.1,189.0,5.7,62
|
| 9 |
+
2026-03-30 06:00:00+05:00,23.8,62,14.2,32.7,17.1,254.0,7.3,62
|
| 10 |
+
2026-03-30 07:00:00+05:00,23.8,63,13.9,33.1,19.0,342.0,9.4,62
|
| 11 |
+
2026-03-30 08:00:00+05:00,24.6,61,15.4,39.8,20.3,389.0,10.3,62
|
| 12 |
+
2026-03-30 09:00:00+05:00,25.7,56,14.6,41.6,20.2,357.0,8.7,63
|
| 13 |
+
2026-03-30 10:00:00+05:00,26.5,50,13.2,43.2,20.5,284.0,5.8,63
|
| 14 |
+
2026-03-30 11:00:00+05:00,27.5,48,15.3,44.6,20.7,230.0,3.6,68
|
| 15 |
+
2026-03-30 12:00:00+05:00,27.9,43,15.7,45.1,20.3,217.0,2.8,76
|
| 16 |
+
2026-03-30 13:00:00+05:00,28.2,40,16.5,45.9,19.6,222.0,2.7,84
|
| 17 |
+
2026-03-30 14:00:00+05:00,28.2,38,15.7,45.8,19.2,232.0,3.1,90
|
| 18 |
+
2026-03-30 15:00:00+05:00,28.0,38,16.3,44.8,18.7,245.0,3.6,96
|
| 19 |
+
2026-03-30 16:00:00+05:00,27.5,39,15.4,43.3,18.1,264.0,4.6,102
|
| 20 |
+
2026-03-30 17:00:00+05:00,26.6,42,15.6,34.0,14.5,286.0,6.6,106
|
| 21 |
+
2026-03-30 18:00:00+05:00,25.7,47,14.6,34.8,15.5,314.0,10.4,107
|
| 22 |
+
2026-03-30 19:00:00+05:00,24.8,54,13.9,34.2,17.9,346.0,15.5,104
|
| 23 |
+
2026-03-30 20:00:00+05:00,24.6,57,15.2,39.9,20.1,366.0,18.9,96
|
| 24 |
+
2026-03-30 21:00:00+05:00,24.5,60,13.7,42.5,20.4,371.0,19.2,87
|
| 25 |
+
2026-03-30 22:00:00+05:00,24.4,60,12.8,43.4,21.3,366.0,17.9,77
|
| 26 |
+
2026-03-30 23:00:00+05:00,24.1,57,11.4,44.9,22.1,347.0,16.0,67
|
| 27 |
+
2026-03-31 00:00:00+05:00,24.0,58,11.5,47.5,22.5,306.0,13.5,65
|
| 28 |
+
2026-03-31 01:00:00+05:00,23.9,61,11.3,51.8,23.1,251.0,10.5,66
|
| 29 |
+
2026-03-31 02:00:00+05:00,23.9,61,12.8,57.2,24.1,212.0,8.2,66
|
| 30 |
+
2026-03-31 03:00:00+05:00,23.7,59,12.9,62.2,25.2,184.0,6.2,66
|
| 31 |
+
2026-03-31 04:00:00+05:00,23.4,62,12.6,62.6,25.5,172.0,5.0,67
|
| 32 |
+
2026-03-31 05:00:00+05:00,23.4,64,8.2,53.2,23.0,214.0,5.5,67
|
| 33 |
+
2026-03-31 06:00:00+05:00,23.4,65,9.3,52.4,23.4,378.0,11.9,68
|
| 34 |
+
2026-03-31 07:00:00+05:00,23.5,64,8.7,53.1,26.6,597.0,20.5,68
|
| 35 |
+
2026-03-31 08:00:00+05:00,24.5,60,8.9,56.1,27.9,709.0,24.8,69
|
| 36 |
+
2026-03-31 09:00:00+05:00,25.8,52,10.2,58.9,24.7,608.0,20.6,70
|
| 37 |
+
2026-03-31 10:00:00+05:00,26.6,47,11.3,55.4,23.8,402.0,12.2,70
|
| 38 |
+
2026-03-31 11:00:00+05:00,26.5,45,12.3,53.3,23.7,247.0,5.8,70
|
| 39 |
+
2026-03-31 12:00:00+05:00,27.2,45,12.4,50.4,22.8,214.0,4.0,71
|
| 40 |
+
2026-03-31 13:00:00+05:00,27.4,45,12.4,48.7,22.3,233.0,4.1,71
|
| 41 |
+
2026-03-31 14:00:00+05:00,27.3,46,12.5,47.4,22.3,255.0,4.6,71
|
| 42 |
+
2026-03-31 15:00:00+05:00,27.4,45,13.7,45.8,22.3,266.0,4.8,72
|
| 43 |
+
2026-03-31 16:00:00+05:00,27.2,46,14.6,43.3,21.5,281.0,5.3,81
|
| 44 |
+
2026-03-31 17:00:00+05:00,26.5,49,13.1,39.2,19.7,293.0,6.8,90
|
| 45 |
+
2026-03-31 18:00:00+05:00,25.8,55,14.3,38.6,20.0,292.0,9.3,96
|
| 46 |
+
2026-03-31 19:00:00+05:00,25.1,58,13.9,36.9,20.7,289.0,12.9,97
|
| 47 |
+
2026-03-31 20:00:00+05:00,25.0,57,13.0,41.0,22.5,309.0,16.5,93
|
| 48 |
+
2026-03-31 21:00:00+05:00,25.0,56,12.2,45.3,24.5,385.0,20.4,87
|
| 49 |
+
2026-03-31 22:00:00+05:00,24.7,58,9.2,48.5,26.6,484.0,24.3,79
|
| 50 |
+
2026-03-31 23:00:00+05:00,24.5,62,9.7,51.3,28.9,538.0,25.9,74
|
| 51 |
+
2026-04-01 00:00:00+05:00,24.5,64,8.9,53.0,29.6,508.0,23.3,75
|
| 52 |
+
2026-04-01 01:00:00+05:00,24.5,65,8.4,51.9,27.8,432.0,18.4,75
|
| 53 |
+
2026-04-01 02:00:00+05:00,24.0,68,7.3,49.5,25.5,358.0,14.3,76
|
| 54 |
+
2026-04-01 03:00:00+05:00,23.9,69,8.8,45.8,23.6,281.0,11.6,76
|
| 55 |
+
2026-04-01 04:00:00+05:00,23.4,72,8.4,41.6,22.0,206.0,9.8,76
|
| 56 |
+
2026-04-01 05:00:00+05:00,24.0,70,9.4,39.5,21.8,197.0,11.7,76
|
| 57 |
+
2026-04-01 06:00:00+05:00,23.9,70,10.1,36.9,21.0,329.0,17.2,75
|
| 58 |
+
2026-04-01 07:00:00+05:00,23.7,72,9.4,38.2,23.7,527.0,24.5,75
|
| 59 |
+
2026-04-01 08:00:00+05:00,24.9,67,9.9,41.1,25.4,634.0,27.6,75
|
| 60 |
+
2026-04-01 09:00:00+05:00,26.4,58,13.3,40.0,21.7,552.0,22.5,75
|
| 61 |
+
2026-04-01 10:00:00+05:00,27.4,53,13.4,38.0,20.4,378.0,13.2,74
|
| 62 |
+
2026-04-01 11:00:00+05:00,27.9,50,15.3,37.0,19.8,249.0,6.1,74
|
| 63 |
+
2026-04-01 12:00:00+05:00,28.5,48,14.9,36.4,18.6,225.0,3.8,74
|
| 64 |
+
2026-04-01 13:00:00+05:00,28.6,48,14.8,35.6,16.9,245.0,3.7,73
|
| 65 |
+
2026-04-01 14:00:00+05:00,29.0,44,13.3,36.6,16.0,267.0,4.2,73
|
| 66 |
+
2026-04-01 15:00:00+05:00,28.9,44,12.8,34.9,14.8,277.0,4.8,72
|
| 67 |
+
2026-04-01 16:00:00+05:00,28.0,50,14.9,34.5,14.3,288.0,5.9,80
|
| 68 |
+
2026-04-01 17:00:00+05:00,27.0,56,15.9,43.4,18.1,300.0,8.2,87
|
| 69 |
+
2026-04-01 18:00:00+05:00,26.2,60,15.4,42.0,17.7,311.0,10.9,88
|
| 70 |
+
2026-04-01 19:00:00+05:00,25.6,63,12.5,39.8,17.9,322.0,14.8,81
|
| 71 |
+
2026-04-01 20:00:00+05:00,25.2,66,12.3,39.0,19.2,334.0,18.0,70
|
| 72 |
+
2026-04-01 21:00:00+05:00,24.9,70,10.8,39.0,20.0,357.0,20.1,70
|
| 73 |
+
2026-04-01 22:00:00+05:00,24.7,73,9.1,39.6,20.7,382.0,21.6,70
|
| 74 |
+
2026-04-01 23:00:00+05:00,24.7,76,8.5,40.0,21.3,381.0,22.2,69
|
| 75 |
+
2026-04-02 00:00:00+05:00,24.9,77,11.5,39.7,21.3,326.0,21.1,69
|
| 76 |
+
2026-04-02 01:00:00+05:00,25.0,76,11.7,38.9,20.7,245.0,19.1,68
|
| 77 |
+
2026-04-02 02:00:00+05:00,24.5,78,7.7,38.3,20.5,191.0,17.8,67
|
| 78 |
+
2026-04-02 03:00:00+05:00,24.1,80,4.3,38.8,21.0,181.0,17.7,67
|
| 79 |
+
2026-04-02 04:00:00+05:00,23.6,83,6.5,41.4,22.1,197.0,18.4,67
|
| 80 |
+
2026-04-02 05:00:00+05:00,23.2,86,1.6,36.1,18.7,239.0,14.3,67
|
| 81 |
+
2026-04-02 06:00:00+05:00,23.4,85,1.2,38.0,19.6,329.0,16.4,66
|
| 82 |
+
2026-04-02 07:00:00+05:00,23.2,87,6.9,42.6,22.9,445.0,19.3,66
|
| 83 |
+
2026-04-02 08:00:00+05:00,23.8,79,12.8,61.7,23.3,521.0,20.6,66
|
| 84 |
+
2026-04-02 09:00:00+05:00,21.4,82,15.1,69.2,24.2,522.0,19.0,66
|
| 85 |
+
2026-04-02 10:00:00+05:00,21.5,81,10.7,79.2,25.4,483.0,15.8,66
|
| 86 |
+
2026-04-02 11:00:00+05:00,23.8,74,4.9,95.9,27.5,447.0,13.1,67
|
| 87 |
+
2026-04-02 12:00:00+05:00,24.2,72,8.7,101.3,28.8,428.0,11.2,67
|
| 88 |
+
2026-04-02 13:00:00+05:00,24.0,73,11.0,110.8,31.5,413.0,9.8,68
|
| 89 |
+
2026-04-02 14:00:00+05:00,24.2,75,10.7,115.9,33.2,403.0,9.2,69
|
| 90 |
+
2026-04-02 15:00:00+05:00,21.8,82,6.5,114.8,32.8,400.0,9.4,71
|
| 91 |
+
2026-04-02 16:00:00+05:00,23.9,73,8.3,103.1,29.4,404.0,10.5,73
|
| 92 |
+
2026-04-02 17:00:00+05:00,20.3,86,1.7,59.5,21.0,413.0,11.5,74
|
| 93 |
+
2026-04-02 18:00:00+05:00,20.5,86,7.2,57.0,20.9,433.0,16.1,74
|
| 94 |
+
2026-04-02 19:00:00+05:00,20.5,86,1.9,51.7,21.2,460.0,22.4,74
|
| 95 |
+
2026-04-02 20:00:00+05:00,20.5,85,1.6,35.0,17.8,477.0,26.3,75
|
| 96 |
+
2026-04-02 21:00:00+05:00,20.5,85,1.3,25.6,12.4,467.0,25.2,75
|
| 97 |
+
2026-04-02 22:00:00+05:00,20.5,87,4.4,21.7,11.3,449.0,21.7,74
|
| 98 |
+
2026-04-02 23:00:00+05:00,18.2,92,21.7,21.1,12.0,462.0,19.3,73
|
| 99 |
+
2026-04-03 00:00:00+05:00,21.1,83,6.9,20.7,12.4,541.0,19.4,72
|
| 100 |
+
2026-04-03 01:00:00+05:00,21.2,83,2.9,20.4,13.2,653.0,20.6,72
|
| 101 |
+
2026-04-03 02:00:00+05:00,20.2,86,9.1,20.5,13.3,736.0,21.7,71
|
| 102 |
+
2026-04-03 03:00:00+05:00,19.2,87,12.2,21.2,13.1,742.0,22.2,70
|
| 103 |
+
2026-04-03 04:00:00+05:00,19.1,86,10.0,21.3,13.6,720.0,22.6,70
|
| 104 |
+
2026-04-03 05:00:00+05:00,18.5,89,12.2,26.0,14.6,754.0,19.1,69
|
| 105 |
+
2026-04-03 06:00:00+05:00,18.4,90,10.0,27.2,16.4,944.0,22.1,68
|
| 106 |
+
2026-04-03 07:00:00+05:00,18.3,91,7.4,29.2,19.7,1190.0,26.1,68
|
| 107 |
+
2026-04-03 08:00:00+05:00,18.6,89,8.3,30.6,19.7,1277.0,27.1,68
|
| 108 |
+
2026-04-03 09:00:00+05:00,19.6,83,5.9,27.0,16.3,1051.0,22.5,68
|
| 109 |
+
2026-04-03 10:00:00+05:00,21.4,75,4.3,23.3,13.6,666.0,15.0,67
|
| 110 |
+
2026-04-03 11:00:00+05:00,23.1,67,2.6,21.9,12.5,370.0,9.0,66
|
| 111 |
+
2026-04-03 12:00:00+05:00,24.1,65,8.0,21.9,12.2,269.0,6.1,64
|
| 112 |
+
2026-04-03 13:00:00+05:00,24.6,64,11.4,22.5,12.0,257.0,4.6,63
|
| 113 |
+
2026-04-03 14:00:00+05:00,24.9,63,13.4,22.0,10.7,264.0,4.0,61
|
| 114 |
+
2026-04-03 15:00:00+05:00,25.0,62,14.4,23.0,10.4,268.0,3.8,59
|
| 115 |
+
2026-04-03 16:00:00+05:00,24.8,64,14.5,23.4,10.5,291.0,4.5,57
|
| 116 |
+
2026-04-03 17:00:00+05:00,25.0,68,14.8,29.7,12.7,334.0,6.6,56
|
| 117 |
+
2026-04-03 18:00:00+05:00,24.6,70,14.3,30.6,13.7,414.0,11.7,55
|
| 118 |
+
2026-04-03 19:00:00+05:00,24.3,72,12.5,29.9,15.8,514.0,18.9,55
|
| 119 |
+
2026-04-03 20:00:00+05:00,24.2,72,10.2,32.7,18.2,581.0,25.5,54
|
| 120 |
+
2026-04-03 21:00:00+05:00,24.0,74,7.7,35.2,20.7,595.0,31.7,54
|
| 121 |
+
2026-04-03 22:00:00+05:00,23.8,73,8.3,37.4,23.9,577.0,37.2,54
|
| 122 |
+
2026-04-03 23:00:00+05:00,23.6,80,9.4,37.7,24.9,532.0,38.9,56
|
| 123 |
+
2026-04-04 00:00:00+05:00,23.8,76,9.2,35.9,23.5,444.0,33.8,57
|
| 124 |
+
2026-04-04 01:00:00+05:00,23.7,74,8.4,32.4,21.1,330.0,24.9,58
|
| 125 |
+
2026-04-04 02:00:00+05:00,23.4,74,7.8,28.3,18.5,246.0,17.7,58
|
| 126 |
+
2026-04-04 03:00:00+05:00,23.2,75,9.1,24.4,16.4,181.0,13.2,59
|
| 127 |
+
2026-04-04 04:00:00+05:00,22.9,78,10.5,21.7,15.1,147.0,10.3,59
|
| 128 |
+
2026-04-04 05:00:00+05:00,22.9,82,7.1,24.5,15.9,234.0,12.3,59
|
| 129 |
+
2026-04-04 06:00:00+05:00,22.8,80,5.0,24.8,16.9,586.0,20.6,59
|
| 130 |
+
2026-04-04 07:00:00+05:00,22.4,82,3.3,31.5,23.2,1059.0,31.7,59
|
| 131 |
+
2026-04-04 08:00:00+05:00,22.9,82,3.2,31.0,23.9,1310.0,36.6,60
|
| 132 |
+
2026-04-04 09:00:00+05:00,24.8,70,5.6,27.9,17.3,1121.0,29.4,60
|
| 133 |
+
2026-04-04 10:00:00+05:00,26.3,59,7.6,32.5,15.8,709.0,16.1,60
|
| 134 |
+
2026-04-04 11:00:00+05:00,27.6,48,6.8,36.0,15.7,386.0,5.9,60
|
| 135 |
+
2026-04-04 12:00:00+05:00,28.5,43,7.9,35.9,14.7,278.0,2.8,61
|
| 136 |
+
2026-04-04 13:00:00+05:00,28.7,45,12.2,34.1,13.6,259.0,2.9,61
|
| 137 |
+
2026-04-04 14:00:00+05:00,28.2,50,17.7,29.8,12.7,262.0,3.5,61
|
| 138 |
+
2026-04-04 15:00:00+05:00,27.7,50,20.7,27.7,12.6,269.0,3.5,63
|
| 139 |
+
2026-04-04 16:00:00+05:00,27.5,49,22.1,26.9,12.9,298.0,4.1,78
|
| 140 |
+
2026-04-04 17:00:00+05:00,27.2,56,22.3,21.2,10.7,333.0,6.5,91
|
| 141 |
+
2026-04-04 18:00:00+05:00,26.5,58,21.0,22.8,11.7,380.0,12.3,98
|
| 142 |
+
2026-04-04 19:00:00+05:00,25.8,65,17.9,22.9,14.1,433.0,20.2,96
|
| 143 |
+
2026-04-04 20:00:00+05:00,25.1,72,16.0,24.5,16.1,460.0,26.0,86
|
| 144 |
+
2026-04-04 21:00:00+05:00,24.7,76,13.6,24.9,16.6,440.0,27.8,72
|
| 145 |
+
2026-04-04 22:00:00+05:00,24.6,78,12.7,25.0,17.3,393.0,27.4,61
|
| 146 |
+
2026-04-04 23:00:00+05:00,24.9,80,14.2,24.6,17.3,348.0,26.0,60
|
| 147 |
+
2026-04-05 00:00:00+05:00,24.7,81,12.7,23.3,16.3,313.0,23.5,59
|
| 148 |
+
2026-04-05 01:00:00+05:00,24.5,81,12.2,22.4,15.7,281.0,20.1,59
|
| 149 |
+
2026-04-05 02:00:00+05:00,24.4,80,11.2,22.7,16.1,256.0,17.5,58
|
| 150 |
+
2026-04-05 03:00:00+05:00,24.1,80,10.1,22.8,16.3,205.0,15.6,58
|
| 151 |
+
2026-04-05 04:00:00+05:00,23.9,81,9.8,22.8,16.4,161.0,14.6,58
|
| 152 |
+
2026-04-05 05:00:00+05:00,23.8,82,8.2,18.7,14.2,231.0,12.0,58
|
| 153 |
+
2026-04-05 06:00:00+05:00,23.6,82,7.2,19.7,15.6,558.0,22.1,58
|
| 154 |
+
2026-04-05 07:00:00+05:00,23.6,82,6.9,28.8,24.3,998.0,35.5,58
|
| 155 |
+
2026-04-05 08:00:00+05:00,24.2,79,7.9,32.0,27.4,1226.0,41.6,58
|
| 156 |
+
2026-04-05 09:00:00+05:00,24.9,72,13.5,29.0,20.7,1031.0,33.3,58
|
| 157 |
+
2026-04-05 10:00:00+05:00,25.4,71,13.9,29.1,19.1,624.0,17.7,59
|
| 158 |
+
2026-04-05 11:00:00+05:00,25.7,69,14.0,29.9,18.8,315.0,5.9,59
|
| 159 |
+
2026-04-05 12:00:00+05:00,26.0,67,15.3,29.0,17.6,236.0,2.5,59
|
| 160 |
+
2026-04-05 13:00:00+05:00,26.5,65,17.0,27.8,16.6,255.0,2.9,59
|
| 161 |
+
2026-04-05 14:00:00+05:00,26.5,64,20.1,27.2,16.1,283.0,4.0,60
|
| 162 |
+
2026-04-05 15:00:00+05:00,26.4,62,15.0,26.3,15.4,282.0,4.3,60
|
| 163 |
+
2026-04-05 16:00:00+05:00,26.1,66,20.9,26.0,15.1,291.0,5.2,72
|
| 164 |
+
2026-04-05 17:00:00+05:00,26.4,62,17.6,25.7,14.9,338.0,8.0,90
|
| 165 |
+
2026-04-05 18:00:00+05:00,25.9,65,17.7,25.8,15.0,454.0,13.8,100
|
| 166 |
+
2026-04-05 19:00:00+05:00,25.3,72,16.3,26.4,17.1,607.0,21.5,100
|
| 167 |
+
2026-04-05 20:00:00+05:00,24.9,75,14.4,29.6,19.5,738.0,29.1,90
|
| 168 |
+
2026-04-05 21:00:00+05:00,24.6,77,13.0,32.5,21.8,846.0,37.2,74
|
| 169 |
+
2026-04-05 22:00:00+05:00,24.4,78,11.8,36.3,25.0,931.0,45.2,62
|
fetch_current_data.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import requests
|
| 2 |
+
import pandas as pd
|
| 3 |
+
from datetime import datetime, date, timedelta
|
| 4 |
+
import pytz
|
| 5 |
+
|
| 6 |
+
# --- Configuration ---
|
| 7 |
+
LATITUDE = 24.86
|
| 8 |
+
LONGITUDE = 67.01
|
| 9 |
+
HISTORICAL_CSV = "data/last_7_days_hourly_data.csv"
|
| 10 |
+
TIMEZONE = 'Asia/Karachi' # Use a constant for the timezone
|
| 11 |
+
|
| 12 |
+
def get_complete_past_week_hourly_data(latitude, longitude, filename):
|
| 13 |
+
"""
|
| 14 |
+
Fetches a complete, seamless 7-day history of hourly data by combining
|
| 15 |
+
the historical archive with the most recent real-time measurements.
|
| 16 |
+
"""
|
| 17 |
+
print("--- Starting full historical data assembly ---")
|
| 18 |
+
|
| 19 |
+
# === FIX: Get the current date *in the target timezone* ===
|
| 20 |
+
# This ensures the script works correctly on any server (like UTC-based GitHub Actions).
|
| 21 |
+
karachi_now = datetime.now(pytz.timezone(TIMEZONE))
|
| 22 |
+
today_in_karachi = karachi_now.date()
|
| 23 |
+
|
| 24 |
+
# --- Step 1: Fetch HISTORICAL data (Archive API) ---
|
| 25 |
+
hist_end_date = today_in_karachi - timedelta(days=2)
|
| 26 |
+
hist_start_date = today_in_karachi - timedelta(days=8)
|
| 27 |
+
|
| 28 |
+
print(f"Fetching historical archive from {hist_start_date} to {hist_end_date}...")
|
| 29 |
+
try:
|
| 30 |
+
weather_url = "https://archive-api.open-meteo.com/v1/archive"
|
| 31 |
+
weather_params = {"latitude": latitude, "longitude": longitude, "start_date": hist_start_date.strftime("%Y-%m-%d"), "end_date": hist_end_date.strftime("%Y-%m-%d"), "hourly": "temperature_2m,relative_humidity_2m,wind_speed_10m", "timezone": TIMEZONE}
|
| 32 |
+
df_weather_hist = pd.DataFrame(requests.get(weather_url, params=weather_params).json()['hourly'])
|
| 33 |
+
|
| 34 |
+
aq_url = "https://air-quality-api.open-meteo.com/v1/air-quality"
|
| 35 |
+
aq_params = {"latitude": latitude, "longitude": longitude, "start_date": hist_start_date.strftime("%Y-%m-%d"), "end_date": hist_end_date.strftime("%Y-%m-%d"), "hourly": "pm10,pm2_5,carbon_monoxide,nitrogen_dioxide,us_aqi", "timezone": TIMEZONE}
|
| 36 |
+
df_aq_hist = pd.DataFrame(requests.get(aq_url, params=aq_params).json()['hourly'])
|
| 37 |
+
|
| 38 |
+
df_historical = pd.merge(df_weather_hist, df_aq_hist, on='time')
|
| 39 |
+
df_historical['time'] = pd.to_datetime(df_historical['time'])
|
| 40 |
+
print(f"-> OK: Fetched {len(df_historical)} records from archive.")
|
| 41 |
+
except KeyError:
|
| 42 |
+
print("!!! WARNING: Historical data not available in the requested range (this is normal). Proceeding with recent data.")
|
| 43 |
+
df_historical = pd.DataFrame()
|
| 44 |
+
except Exception as e:
|
| 45 |
+
print(f"!!! WARNING: Could not fetch historical data. Reason: {e}")
|
| 46 |
+
df_historical = pd.DataFrame()
|
| 47 |
+
|
| 48 |
+
# --- Step 2: Fetch RECENT data (Forecast API) ---
|
| 49 |
+
recent_start_date = today_in_karachi - timedelta(days=2)
|
| 50 |
+
recent_end_date = today_in_karachi
|
| 51 |
+
|
| 52 |
+
print(f"Fetching recent measured data from {recent_start_date} to {recent_end_date}...")
|
| 53 |
+
try:
|
| 54 |
+
weather_url = "https://api.open-meteo.com/v1/forecast"
|
| 55 |
+
weather_params = {"latitude": latitude, "longitude": longitude, "start_date": recent_start_date.strftime("%Y-%m-%d"), "end_date": recent_end_date.strftime("%Y-%m-%d"), "hourly": "temperature_2m,relative_humidity_2m,wind_speed_10m", "timezone": TIMEZONE}
|
| 56 |
+
df_weather_recent = pd.DataFrame(requests.get(weather_url, params=weather_params).json()['hourly'])
|
| 57 |
+
|
| 58 |
+
aq_url = "https://air-quality-api.open-meteo.com/v1/air-quality"
|
| 59 |
+
aq_params = {"latitude": latitude, "longitude": longitude, "start_date": recent_start_date.strftime("%Y-%m-%d"), "end_date": recent_end_date.strftime("%Y-%m-%d"), "hourly": "pm10,pm2_5,carbon_monoxide,nitrogen_dioxide,us_aqi", "timezone": TIMEZONE}
|
| 60 |
+
df_aq_recent = pd.DataFrame(requests.get(aq_url, params=aq_params).json()['hourly'])
|
| 61 |
+
|
| 62 |
+
df_recent = pd.merge(df_weather_recent, df_aq_recent, on='time')
|
| 63 |
+
df_recent['time'] = pd.to_datetime(df_recent['time'])
|
| 64 |
+
print(f"-> OK: Fetched {len(df_recent)} recent records.")
|
| 65 |
+
except Exception as e:
|
| 66 |
+
print(f"!!! WARNING: Could not fetch recent data. Reason: {e}")
|
| 67 |
+
df_recent = pd.DataFrame()
|
| 68 |
+
|
| 69 |
+
# --- Step 3: Combine, De-duplicate, and Filter ---
|
| 70 |
+
print("Combining and cleaning final dataset...")
|
| 71 |
+
if df_historical.empty and df_recent.empty:
|
| 72 |
+
print("!!! FATAL: Both historical and recent data fetches failed. Cannot proceed.")
|
| 73 |
+
return
|
| 74 |
+
|
| 75 |
+
df_combined = pd.concat([df_historical, df_recent])
|
| 76 |
+
df_combined = df_combined.drop_duplicates(subset='time', keep='last').sort_values(by='time')
|
| 77 |
+
|
| 78 |
+
# The rest of the script is already correct because it uses a timezone-aware 'now'
|
| 79 |
+
df_combined['time'] = df_combined['time'].dt.tz_localize(TIMEZONE, ambiguous='infer')
|
| 80 |
+
df_measured = df_combined[df_combined['time'] <= karachi_now].copy()
|
| 81 |
+
|
| 82 |
+
seven_days_ago = karachi_now - timedelta(days=7)
|
| 83 |
+
df_final_week = df_measured[df_measured['time'] >= seven_days_ago]
|
| 84 |
+
|
| 85 |
+
# --- Step 4: Final Rename and Save ---
|
| 86 |
+
df_final = df_final_week.rename({
|
| 87 |
+
'time': 'timestamp',
|
| 88 |
+
'temperature_2m': 'temperature',
|
| 89 |
+
'relative_humidity_2m': 'humidity',
|
| 90 |
+
'wind_speed_10m': 'wind_speed',
|
| 91 |
+
'pm2_5': 'pm25',
|
| 92 |
+
'us_aqi': 'aqi'
|
| 93 |
+
}, axis='columns').dropna()
|
| 94 |
+
|
| 95 |
+
df_final.to_csv(filename, index=False)
|
| 96 |
+
|
| 97 |
+
print(f"\n DONE ")
|
| 98 |
+
print(f"Saved {len(df_final)} hourly records to '{filename}', covering a complete and up-to-date 7-day period.")
|
| 99 |
+
|
| 100 |
+
#main func
|
| 101 |
+
if __name__ == "__main__":
|
| 102 |
+
get_complete_past_week_hourly_data(LATITUDE, LONGITUDE, HISTORICAL_CSV)
|
frontend/.gitignore
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
| 2 |
+
|
| 3 |
+
# dependencies
|
| 4 |
+
/node_modules
|
| 5 |
+
/.pnp
|
| 6 |
+
.pnp.*
|
| 7 |
+
.yarn/*
|
| 8 |
+
!.yarn/patches
|
| 9 |
+
!.yarn/plugins
|
| 10 |
+
!.yarn/releases
|
| 11 |
+
!.yarn/versions
|
| 12 |
+
|
| 13 |
+
# testing
|
| 14 |
+
/coverage
|
| 15 |
+
|
| 16 |
+
# next.js
|
| 17 |
+
/.next/
|
| 18 |
+
/out/
|
| 19 |
+
|
| 20 |
+
# production
|
| 21 |
+
/build
|
| 22 |
+
|
| 23 |
+
# misc
|
| 24 |
+
.DS_Store
|
| 25 |
+
*.pem
|
| 26 |
+
|
| 27 |
+
# debug
|
| 28 |
+
npm-debug.log*
|
| 29 |
+
yarn-debug.log*
|
| 30 |
+
yarn-error.log*
|
| 31 |
+
.pnpm-debug.log*
|
| 32 |
+
|
| 33 |
+
# env files (can opt-in for committing if needed)
|
| 34 |
+
.env*
|
| 35 |
+
|
| 36 |
+
# vercel
|
| 37 |
+
.vercel
|
| 38 |
+
|
| 39 |
+
# typescript
|
| 40 |
+
*.tsbuildinfo
|
| 41 |
+
next-env.d.ts
|
frontend/README.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
| 2 |
+
|
| 3 |
+
## Getting Started
|
| 4 |
+
|
| 5 |
+
First, run the development server:
|
| 6 |
+
|
| 7 |
+
```bash
|
| 8 |
+
npm run dev
|
| 9 |
+
# or
|
| 10 |
+
yarn dev
|
| 11 |
+
# or
|
| 12 |
+
pnpm dev
|
| 13 |
+
# or
|
| 14 |
+
bun dev
|
| 15 |
+
```
|
| 16 |
+
|
| 17 |
+
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
| 18 |
+
|
| 19 |
+
You can start editing the page by modifying `app/page.js`. The page auto-updates as you edit the file.
|
| 20 |
+
|
| 21 |
+
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
| 22 |
+
|
| 23 |
+
## Learn More
|
| 24 |
+
|
| 25 |
+
To learn more about Next.js, take a look at the following resources:
|
| 26 |
+
|
| 27 |
+
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
| 28 |
+
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
| 29 |
+
|
| 30 |
+
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
| 31 |
+
|
| 32 |
+
## Deploy on Vercel
|
| 33 |
+
|
| 34 |
+
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
| 35 |
+
|
| 36 |
+
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
frontend/app/favicon.ico
ADDED
|
|
frontend/app/globals.css
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* A simple dark theme and modern font stack */
|
| 2 |
+
body {
|
| 3 |
+
background-color: #121212;
|
| 4 |
+
color: #e0e0e0;
|
| 5 |
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
| 6 |
+
margin: 0;
|
| 7 |
+
padding: 0;
|
| 8 |
+
display: flex;
|
| 9 |
+
justify-content: center;
|
| 10 |
+
align-items: center;
|
| 11 |
+
min-height: 100vh;
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
/* The main container for our content */
|
| 15 |
+
.container {
|
| 16 |
+
width: 100%;
|
| 17 |
+
max-width: 500px;
|
| 18 |
+
padding: 2rem;
|
| 19 |
+
text-align: center;
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
/* The header section with the title */
|
| 23 |
+
.header {
|
| 24 |
+
margin-bottom: 2rem;
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
.header h1 {
|
| 28 |
+
font-size: 2.5rem;
|
| 29 |
+
margin-bottom: 0.5rem;
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
.header p {
|
| 33 |
+
font-size: 1.1rem;
|
| 34 |
+
color: #a0a0a0;
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
/* The list that will hold our forecast items */
|
| 38 |
+
.forecast-list {
|
| 39 |
+
list-style: none;
|
| 40 |
+
padding: 0;
|
| 41 |
+
margin: 0;
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
/* Each individual forecast day item */
|
| 45 |
+
.forecast-item {
|
| 46 |
+
background-color: #1e1e1e;
|
| 47 |
+
border: 1px solid #333;
|
| 48 |
+
border-radius: 8px;
|
| 49 |
+
padding: 1rem 1.5rem;
|
| 50 |
+
margin-bottom: 1rem;
|
| 51 |
+
display: flex;
|
| 52 |
+
justify-content: space-between;
|
| 53 |
+
align-items: center;
|
| 54 |
+
font-size: 1.2rem;
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
.forecast-item .date {
|
| 58 |
+
color: #c0c0c0;
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
.forecast-item .aqi {
|
| 62 |
+
font-weight: bold;
|
| 63 |
+
color: #ffffff;
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
.today-aqi-container {
|
| 67 |
+
background-color: #1e1e1e; /* A slightly darker background */
|
| 68 |
+
border: 1px solid #333;
|
| 69 |
+
border-radius: 12px;
|
| 70 |
+
padding: 1.5rem;
|
| 71 |
+
margin-bottom: 3rem;
|
| 72 |
+
display: flex;
|
| 73 |
+
flex-direction: column;
|
| 74 |
+
align-items: center;
|
| 75 |
+
justify-content: center;
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
/* The large number for the AQI value */
|
| 79 |
+
.today-aqi-value {
|
| 80 |
+
font-size: 4.5rem; /* Much larger font */
|
| 81 |
+
font-weight: 700; /* Bolder */
|
| 82 |
+
line-height: 1;
|
| 83 |
+
color: #ffffff;
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
/* The label underneath the large number */
|
| 87 |
+
.today-aqi-label {
|
| 88 |
+
font-size: 1rem;
|
| 89 |
+
color: #a0a0a0;
|
| 90 |
+
margin-top: 0.5rem;
|
| 91 |
+
text-transform: uppercase;
|
| 92 |
+
letter-spacing: 0.5px;
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
/* A title for the forecast section */
|
| 96 |
+
.forecast-title {
|
| 97 |
+
font-size: 1.2rem;
|
| 98 |
+
color: #c0c0c0;
|
| 99 |
+
margin-bottom: 1rem;
|
| 100 |
+
text-align: left;
|
| 101 |
+
padding-left: 0.5rem;
|
| 102 |
+
}
|
frontend/app/layout.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Geist, Geist_Mono } from "next/font/google";
|
| 2 |
+
import "./globals.css";
|
| 3 |
+
|
| 4 |
+
const geistSans = Geist({
|
| 5 |
+
variable: "--font-geist-sans",
|
| 6 |
+
subsets: ["latin"],
|
| 7 |
+
});
|
| 8 |
+
|
| 9 |
+
const geistMono = Geist_Mono({
|
| 10 |
+
variable: "--font-geist-mono",
|
| 11 |
+
subsets: ["latin"],
|
| 12 |
+
});
|
| 13 |
+
|
| 14 |
+
export const metadata = {
|
| 15 |
+
title: "Create Next App",
|
| 16 |
+
description: "Generated by create next app",
|
| 17 |
+
};
|
| 18 |
+
|
| 19 |
+
export default function RootLayout({ children }) {
|
| 20 |
+
return (
|
| 21 |
+
<html lang="en">
|
| 22 |
+
<body className={`${geistSans.variable} ${geistMono.variable}`}>
|
| 23 |
+
{children}
|
| 24 |
+
</body>
|
| 25 |
+
</html>
|
| 26 |
+
);
|
| 27 |
+
}
|
frontend/app/page.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
'use client';
|
| 3 |
+
|
| 4 |
+
import { useState, useEffect } from 'react';
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
export default function HomePage() {
|
| 8 |
+
|
| 9 |
+
const [todayAqi, setTodayAqi] = useState(null);
|
| 10 |
+
const [forecast, setForecast] = useState(null);
|
| 11 |
+
const [loading, setLoading] = useState(true);
|
| 12 |
+
const [error, setError] = useState(null);
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
const [lastUpdated, setLastUpdated] = useState(null);
|
| 16 |
+
|
| 17 |
+
// This hook runs once when the component is first loaded.
|
| 18 |
+
useEffect(() => {
|
| 19 |
+
|
| 20 |
+
async function fetchAqiData() {
|
| 21 |
+
const response = await fetch('https://qar-raz-aqi-predictor-qamar.hf.space/api/forecast');
|
| 22 |
+
if (!response.ok) {
|
| 23 |
+
const errorData = await response.json();
|
| 24 |
+
throw new Error(errorData.detail || 'Failed to fetch AQI forecast.');
|
| 25 |
+
}
|
| 26 |
+
return response.json();
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
async function fetchStatusData() {
|
| 30 |
+
const response = await fetch('https://qar-raz-aqi-predictor-qamar.hf.space/api/status');
|
| 31 |
+
if (!response.ok) {
|
| 32 |
+
const errorData = await response.json();
|
| 33 |
+
throw new Error(errorData.detail || 'Failed to fetch API status.');
|
| 34 |
+
}
|
| 35 |
+
return response.json();
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
async function loadAllData() {
|
| 40 |
+
try {
|
| 41 |
+
|
| 42 |
+
const [aqiData, statusData] = await Promise.all([
|
| 43 |
+
fetchAqiData(),
|
| 44 |
+
fetchStatusData()
|
| 45 |
+
]);
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
setTodayAqi(aqiData.today);
|
| 49 |
+
setForecast(aqiData.forecast);
|
| 50 |
+
setLastUpdated(statusData.model_last_updated_utc);
|
| 51 |
+
// Set the new state
|
| 52 |
+
|
| 53 |
+
} catch (err) {
|
| 54 |
+
setError(err.message);
|
| 55 |
+
} finally {
|
| 56 |
+
setLoading(false);
|
| 57 |
+
}
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
loadAllData();
|
| 61 |
+
}, []); // The empty array [] means this effect runs only once.
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
// A small helper function to format the long UTC date into a more readable format.
|
| 66 |
+
const formatDate = (isoString) => {
|
| 67 |
+
if (!isoString) return '';
|
| 68 |
+
|
| 69 |
+
return new Date(isoString).toLocaleString(undefined, {
|
| 70 |
+
dateStyle: 'long',
|
| 71 |
+
timeStyle: 'short',
|
| 72 |
+
});
|
| 73 |
+
};
|
| 74 |
+
|
| 75 |
+
if (loading) {
|
| 76 |
+
return <main className="container"><p>Loading forecast...</p></main>;
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
if (error) {
|
| 80 |
+
return <main className="container"><p style={{ color: '#ff8a80' }}>Error: {error}</p></main>;
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
return (
|
| 84 |
+
<main className="container">
|
| 85 |
+
<div className="header">
|
| 86 |
+
<h1>Karachi AQI</h1>
|
| 87 |
+
<p>Live Data & 3-Day Forecast</p>
|
| 88 |
+
</div>
|
| 89 |
+
|
| 90 |
+
{/* NEW UI ELEMENT: The "Last Updated" label. */}
|
| 91 |
+
{/* This checks if 'lastUpdated' has data before trying to render it. */}
|
| 92 |
+
{lastUpdated && (
|
| 93 |
+
<div className="last-updated-label">
|
| 94 |
+
Model Last Updated: {formatDate(lastUpdated)}
|
| 95 |
+
</div>
|
| 96 |
+
)}
|
| 97 |
+
|
| 98 |
+
{/* === Section for Today's AQI (Large Label) === */}
|
| 99 |
+
{/* This checks if todayAqi has data before trying to render it. */}
|
| 100 |
+
{todayAqi && (
|
| 101 |
+
<div className="today-aqi-container">
|
| 102 |
+
<div className="today-aqi-value">{todayAqi.aqi}</div>
|
| 103 |
+
<div className="today-aqi-label">
|
| 104 |
+
{/* This creates a YYYY-MM-DD formatted date for right now */}
|
| 105 |
+
{"Today's AQI (" + new Date().toLocaleDateString('en-CA') + ")"}
|
| 106 |
+
</div>
|
| 107 |
+
</div>
|
| 108 |
+
)}
|
| 109 |
+
|
| 110 |
+
{/* Section for the 3-Day Forecast */}
|
| 111 |
+
<h2 className="forecast-title">Next 3 Days Forecast</h2>
|
| 112 |
+
<ul className="forecast-list">
|
| 113 |
+
{forecast && forecast.map((day) => (
|
| 114 |
+
<li key={day.date} className="forecast-item">
|
| 115 |
+
<span className="date">{day.date}</span>
|
| 116 |
+
<span className="aqi">{day.predicted_aqi} AQI</span>
|
| 117 |
+
</li>
|
| 118 |
+
))}
|
| 119 |
+
</ul>
|
| 120 |
+
</main>
|
| 121 |
+
);
|
| 122 |
+
}
|
frontend/app/page.module.css
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.page {
|
| 2 |
+
--gray-rgb: 0, 0, 0;
|
| 3 |
+
--gray-alpha-200: rgba(var(--gray-rgb), 0.08);
|
| 4 |
+
--gray-alpha-100: rgba(var(--gray-rgb), 0.05);
|
| 5 |
+
|
| 6 |
+
--button-primary-hover: #383838;
|
| 7 |
+
--button-secondary-hover: #f2f2f2;
|
| 8 |
+
|
| 9 |
+
display: grid;
|
| 10 |
+
grid-template-rows: 20px 1fr 20px;
|
| 11 |
+
align-items: center;
|
| 12 |
+
justify-items: center;
|
| 13 |
+
min-height: 100svh;
|
| 14 |
+
padding: 80px;
|
| 15 |
+
gap: 64px;
|
| 16 |
+
font-family: var(--font-geist-sans);
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
@media (prefers-color-scheme: dark) {
|
| 20 |
+
.page {
|
| 21 |
+
--gray-rgb: 255, 255, 255;
|
| 22 |
+
--gray-alpha-200: rgba(var(--gray-rgb), 0.145);
|
| 23 |
+
--gray-alpha-100: rgba(var(--gray-rgb), 0.06);
|
| 24 |
+
|
| 25 |
+
--button-primary-hover: #ccc;
|
| 26 |
+
--button-secondary-hover: #1a1a1a;
|
| 27 |
+
}
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
.main {
|
| 31 |
+
display: flex;
|
| 32 |
+
flex-direction: column;
|
| 33 |
+
gap: 32px;
|
| 34 |
+
grid-row-start: 2;
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
.main ol {
|
| 38 |
+
font-family: var(--font-geist-mono);
|
| 39 |
+
padding-left: 0;
|
| 40 |
+
margin: 0;
|
| 41 |
+
font-size: 14px;
|
| 42 |
+
line-height: 24px;
|
| 43 |
+
letter-spacing: -0.01em;
|
| 44 |
+
list-style-position: inside;
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
.main li:not(:last-of-type) {
|
| 48 |
+
margin-bottom: 8px;
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
.main code {
|
| 52 |
+
font-family: inherit;
|
| 53 |
+
background: var(--gray-alpha-100);
|
| 54 |
+
padding: 2px 4px;
|
| 55 |
+
border-radius: 4px;
|
| 56 |
+
font-weight: 600;
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
.ctas {
|
| 60 |
+
display: flex;
|
| 61 |
+
gap: 16px;
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
.ctas a {
|
| 65 |
+
appearance: none;
|
| 66 |
+
border-radius: 128px;
|
| 67 |
+
height: 48px;
|
| 68 |
+
padding: 0 20px;
|
| 69 |
+
border: 1px solid transparent;
|
| 70 |
+
transition:
|
| 71 |
+
background 0.2s,
|
| 72 |
+
color 0.2s,
|
| 73 |
+
border-color 0.2s;
|
| 74 |
+
cursor: pointer;
|
| 75 |
+
display: flex;
|
| 76 |
+
align-items: center;
|
| 77 |
+
justify-content: center;
|
| 78 |
+
font-size: 16px;
|
| 79 |
+
line-height: 20px;
|
| 80 |
+
font-weight: 500;
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
a.primary {
|
| 84 |
+
background: var(--foreground);
|
| 85 |
+
color: var(--background);
|
| 86 |
+
gap: 8px;
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
a.secondary {
|
| 90 |
+
border-color: var(--gray-alpha-200);
|
| 91 |
+
min-width: 158px;
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
.footer {
|
| 95 |
+
grid-row-start: 3;
|
| 96 |
+
display: flex;
|
| 97 |
+
gap: 24px;
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
.footer a {
|
| 101 |
+
display: flex;
|
| 102 |
+
align-items: center;
|
| 103 |
+
gap: 8px;
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
.footer img {
|
| 107 |
+
flex-shrink: 0;
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
/* Enable hover only on non-touch devices */
|
| 111 |
+
@media (hover: hover) and (pointer: fine) {
|
| 112 |
+
a.primary:hover {
|
| 113 |
+
background: var(--button-primary-hover);
|
| 114 |
+
border-color: transparent;
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
a.secondary:hover {
|
| 118 |
+
background: var(--button-secondary-hover);
|
| 119 |
+
border-color: transparent;
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
.footer a:hover {
|
| 123 |
+
text-decoration: underline;
|
| 124 |
+
text-underline-offset: 4px;
|
| 125 |
+
}
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
@media (max-width: 600px) {
|
| 129 |
+
.page {
|
| 130 |
+
padding: 32px;
|
| 131 |
+
padding-bottom: 80px;
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
.main {
|
| 135 |
+
align-items: center;
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
.main ol {
|
| 139 |
+
text-align: center;
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
.ctas {
|
| 143 |
+
flex-direction: column;
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
.ctas a {
|
| 147 |
+
font-size: 14px;
|
| 148 |
+
height: 40px;
|
| 149 |
+
padding: 0 16px;
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
a.secondary {
|
| 153 |
+
min-width: auto;
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
.footer {
|
| 157 |
+
flex-wrap: wrap;
|
| 158 |
+
align-items: center;
|
| 159 |
+
justify-content: center;
|
| 160 |
+
}
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
@media (prefers-color-scheme: dark) {
|
| 164 |
+
.logo {
|
| 165 |
+
filter: invert();
|
| 166 |
+
}
|
| 167 |
+
}
|
frontend/eslint.config.mjs
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { dirname } from "path";
|
| 2 |
+
import { fileURLToPath } from "url";
|
| 3 |
+
import { FlatCompat } from "@eslint/eslintrc";
|
| 4 |
+
|
| 5 |
+
const __filename = fileURLToPath(import.meta.url);
|
| 6 |
+
const __dirname = dirname(__filename);
|
| 7 |
+
|
| 8 |
+
const compat = new FlatCompat({
|
| 9 |
+
baseDirectory: __dirname,
|
| 10 |
+
});
|
| 11 |
+
|
| 12 |
+
const eslintConfig = [...compat.extends("next/core-web-vitals")];
|
| 13 |
+
|
| 14 |
+
export default eslintConfig;
|
frontend/jsconfig.json
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"compilerOptions": {
|
| 3 |
+
"paths": {
|
| 4 |
+
"@/*": ["./*"]
|
| 5 |
+
}
|
| 6 |
+
}
|
| 7 |
+
}
|
frontend/next.config.mjs
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/** @type {import('next').NextConfig} */
|
| 2 |
+
const nextConfig = {
|
| 3 |
+
// This rewrite configuration is the "bridge" for local development
|
| 4 |
+
async rewrites() {
|
| 5 |
+
return [
|
| 6 |
+
{
|
| 7 |
+
source: '/api/:path*',
|
| 8 |
+
destination: 'http://localhost:8000/api/:path*', // Proxy to your FastAPI server
|
| 9 |
+
},
|
| 10 |
+
]
|
| 11 |
+
},
|
| 12 |
+
};
|
| 13 |
+
|
| 14 |
+
export default nextConfig;
|
frontend/package-lock.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
frontend/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "frontend",
|
| 3 |
+
"version": "0.1.0",
|
| 4 |
+
"private": true,
|
| 5 |
+
"scripts": {
|
| 6 |
+
"dev": "next dev --turbopack",
|
| 7 |
+
"build": "next build",
|
| 8 |
+
"start": "next start",
|
| 9 |
+
"lint": "next lint"
|
| 10 |
+
},
|
| 11 |
+
"dependencies": {
|
| 12 |
+
"react": "19.1.0",
|
| 13 |
+
"react-dom": "19.1.0",
|
| 14 |
+
"next": "15.4.6"
|
| 15 |
+
},
|
| 16 |
+
"devDependencies": {
|
| 17 |
+
"eslint": "^9",
|
| 18 |
+
"eslint-config-next": "15.4.6",
|
| 19 |
+
"@eslint/eslintrc": "^3"
|
| 20 |
+
}
|
| 21 |
+
}
|
frontend/public/file.svg
ADDED
|
|
frontend/public/globe.svg
ADDED
|
|
frontend/public/next.svg
ADDED
|
|
frontend/public/vercel.svg
ADDED
|
|
frontend/public/window.svg
ADDED
|
|
hourly_to_daily.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# This script is used to convert the hourly data to daily data.
|
| 2 |
+
# It is used to get the daily average and maximum AQI.
|
| 3 |
+
# It is used to get the daily average and maximum temperature, humidity, and wind speed.
|
| 4 |
+
# NOTE: This this is called after the hourly data is fetched, by running the open_meteo_get_historical.py script.
|
| 5 |
+
# We have to do this as Open-Meteo.com only gives us hourly data, so I had to code this to aggregate the hourly data to daily data.
|
| 6 |
+
|
| 7 |
+
import pandas as pd
|
| 8 |
+
import sys
|
| 9 |
+
|
| 10 |
+
# NEVER USE \ IN THE FILE PATHS. IT WILL WORK ON WINDOWS BUT NOT ON LINUX. SO B/C OF THIS THE PIPELINE FAILS
|
| 11 |
+
# SMOLL CHANGE CAUSED BIG FAILURE
|
| 12 |
+
# --- Configuration ---
|
| 13 |
+
HOURLY_DATA_FILE = "data/last_7_days_hourly_data.csv"
|
| 14 |
+
DAILY_DATA_FILE = "data/last_7_days_daily_data.csv"
|
| 15 |
+
|
| 16 |
+
# --- Main Processing ---
|
| 17 |
+
def process_hourly_to_daily_correctly(input_file, output_file):
|
| 18 |
+
"""
|
| 19 |
+
Loads hourly data and correctly aggregates it to daily data.
|
| 20 |
+
- Averages pollutants and weather variables.
|
| 21 |
+
- Takes the maximum value for the final AQI score.
|
| 22 |
+
"""
|
| 23 |
+
try:
|
| 24 |
+
print(f"Loading hourly data from '{input_file}'...")
|
| 25 |
+
# Load the data, ensuring the 'timestamp' column is parsed as a date object.
|
| 26 |
+
df_hourly = pd.read_csv(input_file, parse_dates=['timestamp'])
|
| 27 |
+
df_hourly.set_index('timestamp', inplace=True)
|
| 28 |
+
print("-> Hourly data loaded successfully.")
|
| 29 |
+
except FileNotFoundError:
|
| 30 |
+
print(f"!!! ERROR: The input file '{input_file}' was not found.")
|
| 31 |
+
print("Please run the previous script to generate this file first.")
|
| 32 |
+
sys.exit(1)
|
| 33 |
+
|
| 34 |
+
print("\nProcessing daily aggregations...")
|
| 35 |
+
|
| 36 |
+
# 1. Calculate the daily MEAN for pollutants and weather data
|
| 37 |
+
# This is the correct method for these linear values.
|
| 38 |
+
daily_means = df_hourly.drop(columns=['aqi']).resample('D').mean()
|
| 39 |
+
print("-> Calculated daily means for pollutants and weather.")
|
| 40 |
+
|
| 41 |
+
# 2. Calculate the daily MAXIMUM for the AQI column
|
| 42 |
+
# This is a more meaningful representation than a simple average of the index.
|
| 43 |
+
daily_max_aqi = df_hourly['aqi'].resample('D').max()
|
| 44 |
+
print("-> Calculated daily maximum for AQI.")
|
| 45 |
+
|
| 46 |
+
# 3. Combine the results
|
| 47 |
+
# We now have the mean of the raw data and the max of the index.
|
| 48 |
+
df_daily_final = pd.concat([daily_means, daily_max_aqi], axis=1)
|
| 49 |
+
|
| 50 |
+
# 4. Clean up
|
| 51 |
+
# Remove any days that might have no data (e.g., if there was a gap in the source)
|
| 52 |
+
df_daily_final.dropna(inplace=True)
|
| 53 |
+
|
| 54 |
+
# Save the correctly processed daily data to a new CSV
|
| 55 |
+
df_daily_final.to_csv(output_file)
|
| 56 |
+
|
| 57 |
+
print(f"\n✅ --- SUCCESS --- ✅")
|
| 58 |
+
print(f"Correctly processed daily data has been saved to '{output_file}'")
|
| 59 |
+
print(f"The file contains {len(df_daily_final)} daily records.")
|
| 60 |
+
|
| 61 |
+
# Display the first few rows of the final, correct data
|
| 62 |
+
print("\n--- Sample of Correctly Processed Daily Data ---")
|
| 63 |
+
print(df_daily_final.head())
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
# --- Run the script ---
|
| 67 |
+
if __name__ == "__main__":
|
| 68 |
+
process_hourly_to_daily_correctly(HOURLY_DATA_FILE, DAILY_DATA_FILE)
|
logs/AQI Predictor Log 1.csv
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Index,Experiment_Name,Splitting_Strategy,Model_Parameters,Feature_Notes,R2_Score,MAE,RMSE,MAPE,Notes
|
| 2 |
+
0,Decision Tree with Stratified Split,"StratifiedShuffleSplit (test_size=0.2, on 5 AQI bins)","{'ccp_alpha': 0.0, 'criterion': 'squared_error', 'max_depth': None, 'max_features': None, 'max_leaf_nodes': None, 'min_impurity_decrease': 0.0, 'min_samples_leaf': 1, 'min_samples_split': 2, 'min_weight_fraction_leaf': 0.0, 'monotonic_cst': None, 'random_state': 42, 'splitter': 'best'}","7 lag features + month, day_of_year, day_of_week",0.679986,15.324201,23.221782,0.134544,Baseline model. Shows promising R2 but the gap between MAE and RMSE suggests some large prediction errors.
|
| 3 |
+
1,RandomForest with Stratified Split,"StratifiedShuffleSplit (test_size=0.2, on 5 AQI bins)","{'bootstrap': True, 'ccp_alpha': 0.0, 'criterion': 'squared_error', 'max_depth': None, 'max_features': 1.0, 'max_leaf_nodes': None, 'max_samples': None, 'min_impurity_decrease': 0.0, 'min_samples_leaf': 1, 'min_samples_split': 2, 'min_weight_fraction_leaf': 0.0, 'monotonic_cst': None, 'n_estimators': 100, 'n_jobs': -1, 'oob_score': False, 'random_state': 42, 'verbose': 0, 'warm_start': False}","7 lag features + month, day_of_year, day_of_week",0.799723,11.584795,18.370732,0.102134,Is better than decision tree
|
| 4 |
+
2,RandomForest with Stratified Split,"StratifiedShuffleSplit (test_size=0.2, on 5 AQI bins)","{'alpha': 0.9, 'ccp_alpha': 0.0, 'criterion': 'friedman_mse', 'init': None, 'learning_rate': 0.1, 'loss': 'squared_error', 'max_depth': 3, 'max_features': None, 'max_leaf_nodes': None, 'min_impurity_decrease': 0.0, 'min_samples_leaf': 1, 'min_samples_split': 2, 'min_weight_fraction_leaf': 0.0, 'n_estimators': 100, 'n_iter_no_change': None, 'random_state': 42, 'subsample': 1.0, 'tol': 0.0001, 'validation_fraction': 0.1, 'verbose': 0, 'warm_start': False}","7 lag features + month, day_of_year, day_of_week",0.801749,11.158205,18.277585,0.098714,Is better than decision tree
|
| 5 |
+
3,adaboost base,"StratifiedShuffleSplit (test_size=0.2, on 5 AQI bins)","{'estimator': None, 'learning_rate': 1.0, 'loss': 'linear', 'n_estimators': 50, 'random_state': 42}","7 lag features + month, day_of_year, day_of_week",0.720594,17.885683,21.698458,0.187625,trying default adaboost
|
| 6 |
+
4,xgboost base,"StratifiedShuffleSplit (test_size=0.2, on 5 AQI bins)","{'objective': 'reg:squarederror', 'base_score': None, 'booster': None, 'callbacks': None, 'colsample_bylevel': None, 'colsample_bynode': None, 'colsample_bytree': None, 'device': None, 'early_stopping_rounds': None, 'enable_categorical': False, 'eval_metric': None, 'feature_types': None, 'feature_weights': None, 'gamma': None, 'grow_policy': None, 'importance_type': None, 'interaction_constraints': None, 'learning_rate': None, 'max_bin': None, 'max_cat_threshold': None, 'max_cat_to_onehot': None, 'max_delta_step': None, 'max_depth': None, 'max_leaves': None, 'min_child_weight': None, 'missing': nan, 'monotone_constraints': None, 'multi_strategy': None, 'n_estimators': None, 'n_jobs': -1, 'num_parallel_tree': None, 'random_state': 42, 'reg_alpha': None, 'reg_lambda': None, 'sampling_method': None, 'scale_pos_weight': None, 'subsample': None, 'tree_method': None, 'validate_parameters': None, 'verbosity': None}","7 lag features + month, day_of_year, day_of_week",0.755335,12.423574,20.304726,0.107918,trying default xgboost
|
| 7 |
+
5,Decision Tree optimized using Randomized Search,"StratifiedShuffleSplit (test_size=0.2, on 5 AQI bins)","{'ccp_alpha': 0.0, 'criterion': 'squared_error', 'max_depth': 5, 'max_features': None, 'max_leaf_nodes': None, 'min_impurity_decrease': 0.0, 'min_samples_leaf': 4, 'min_samples_split': 10, 'min_weight_fraction_leaf': 0.0, 'monotonic_cst': None, 'random_state': 42, 'splitter': 'best'}","7 lag features + month, day_of_year, day_of_week",0.718233,13.838741,21.789947,0.121727,trying better params
|
| 8 |
+
6,RandomForest optimized using Randomized Search,"StratifiedShuffleSplit (test_size=0.2, on 5 AQI bins)","{'bootstrap': True, 'ccp_alpha': 0.0, 'criterion': 'squared_error', 'max_depth': 20, 'max_features': 'sqrt', 'max_leaf_nodes': None, 'max_samples': None, 'min_impurity_decrease': 0.0, 'min_samples_leaf': 1, 'min_samples_split': 2, 'min_weight_fraction_leaf': 0.0, 'monotonic_cst': None, 'n_estimators': 200, 'n_jobs': -1, 'oob_score': False, 'random_state': 42, 'verbose': 0, 'warm_start': False}","7 lag features + month, day_of_year, day_of_week",0.831875,10.987116,16.831691,0.099371,trying better params
|
| 9 |
+
7,GradientBoosting optimized using Randomized Search,"StratifiedShuffleSplit (test_size=0.2, on 5 AQI bins)","{'alpha': 0.9, 'ccp_alpha': 0.0, 'criterion': 'friedman_mse', 'init': None, 'learning_rate': 0.05, 'loss': 'squared_error', 'max_depth': 5, 'max_features': None, 'max_leaf_nodes': None, 'min_impurity_decrease': 0.0, 'min_samples_leaf': 1, 'min_samples_split': 2, 'min_weight_fraction_leaf': 0.0, 'n_estimators': 100, 'n_iter_no_change': None, 'random_state': 42, 'subsample': 0.7, 'tol': 0.0001, 'validation_fraction': 0.1, 'verbose': 0, 'warm_start': False}","7 lag features + month, day_of_year, day_of_week",0.809274,11.124478,17.927342,0.097859,trying better params
|
| 10 |
+
8,Adaboost optimized using Randomized Search,"StratifiedShuffleSplit (test_size=0.2, on 5 AQI bins)","{'estimator': None, 'learning_rate': 0.01, 'loss': 'exponential', 'n_estimators': 300, 'random_state': 42}","7 lag features + month, day_of_year, day_of_week",0.758373,13.53738,20.178269,0.122505,trying better params
|
| 11 |
+
9,XGBoost optimized using Randomized Search,"StratifiedShuffleSplit (test_size=0.2, on 5 AQI bins)","{'objective': 'reg:squarederror', 'base_score': None, 'booster': None, 'callbacks': None, 'colsample_bylevel': None, 'colsample_bynode': None, 'colsample_bytree': 0.8, 'device': None, 'early_stopping_rounds': None, 'enable_categorical': False, 'eval_metric': None, 'feature_types': None, 'feature_weights': None, 'gamma': None, 'grow_policy': None, 'importance_type': None, 'interaction_constraints': None, 'learning_rate': 0.1, 'max_bin': None, 'max_cat_threshold': None, 'max_cat_to_onehot': None, 'max_delta_step': None, 'max_depth': 3, 'max_leaves': None, 'min_child_weight': None, 'missing': nan, 'monotone_constraints': None, 'multi_strategy': None, 'n_estimators': 100, 'n_jobs': -1, 'num_parallel_tree': None, 'random_state': 42, 'reg_alpha': None, 'reg_lambda': None, 'sampling_method': None, 'scale_pos_weight': None, 'subsample': 0.7, 'tree_method': None, 'validate_parameters': None, 'verbosity': None}","7 lag features + month, day_of_year, day_of_week",0.815525,10.70084,17.631103,0.094617,trying better params
|
| 12 |
+
10,Base Lightgbm,"StratifiedShuffleSplit (test_size=0.2, on 5 AQI bins)","{'boosting_type': 'gbdt', 'class_weight': None, 'colsample_bytree': 1.0, 'importance_type': 'split', 'learning_rate': 0.1, 'max_depth': -1, 'min_child_samples': 20, 'min_child_weight': 0.001, 'min_split_gain': 0.0, 'n_estimators': 100, 'n_jobs': -1, 'num_leaves': 31, 'objective': None, 'random_state': 42, 'reg_alpha': 0.0, 'reg_lambda': 0.0, 'subsample': 1.0, 'subsample_for_bin': 200000, 'subsample_freq': 0, 'verbosity': -1}","7 lag features + month, day_of_year, day_of_week",0.793062,12.065966,18.67374,0.108006,Base model
|
| 13 |
+
11,Base CatBoost,"StratifiedShuffleSplit (test_size=0.2, on 5 AQI bins)","{'loss_function': 'RMSE', 'verbose': 0, 'random_state': 42}","7 lag features + month, day_of_year, day_of_week",0.826587,10.968606,17.094311,0.097084,Base model
|
| 14 |
+
12,Base SVR,"StratifiedShuffleSplit (test_size=0.2, on 5 AQI bins)","{'C': 1.0, 'cache_size': 200, 'coef0': 0.0, 'degree': 3, 'epsilon': 0.1, 'gamma': 'scale', 'kernel': 'rbf', 'max_iter': -1, 'shrinking': True, 'tol': 0.001, 'verbose': False}","7 lag features + month, day_of_year, day_of_week",0.517884,19.459834,28.502749,0.172332,Base model
|
| 15 |
+
13,Base ElasticNet,"StratifiedShuffleSplit (test_size=0.2, on 5 AQI bins)","{'alpha': 1.0, 'copy_X': True, 'fit_intercept': True, 'l1_ratio': 0.5, 'max_iter': 1000, 'positive': False, 'precompute': False, 'random_state': 42, 'selection': 'cyclic', 'tol': 0.0001, 'warm_start': False}","7 lag features + month, day_of_year, day_of_week",0.770508,12.537572,19.665026,0.108772,Base model
|
| 16 |
+
14,Optimized LightGBM,"StratifiedShuffleSplit (test_size=0.2, on 5 AQI bins)","{'boosting_type': 'gbdt', 'class_weight': None, 'colsample_bytree': 1.0, 'importance_type': 'split', 'learning_rate': 0.01, 'max_depth': 10, 'min_child_samples': 20, 'min_child_weight': 0.001, 'min_split_gain': 0.0, 'n_estimators': 500, 'n_jobs': -1, 'num_leaves': 40, 'objective': None, 'random_state': 42, 'reg_alpha': 0, 'reg_lambda': 0, 'subsample': 1.0, 'subsample_for_bin': 200000, 'subsample_freq': 0, 'verbosity': -1}","7 lag features + month, day_of_year, day_of_week",0.800756,11.744002,18.323309,0.104691,Optimized Hyperparams
|
| 17 |
+
15,Optimized CatBoost,"StratifiedShuffleSplit (test_size=0.2, on 5 AQI bins)","{'loss_function': 'RMSE', 'verbose': 0, 'random_state': 42, 'learning_rate': 0.05, 'l2_leaf_reg': 3, 'iterations': 300, 'depth': 4}","7 lag features + month, day_of_year, day_of_week",0.830674,10.687373,16.891692,0.095652,Optimized Hyperparams
|
| 18 |
+
16,Optimized SVR,"StratifiedShuffleSplit (test_size=0.2, on 5 AQI bins)","{'C': 100, 'cache_size': 200, 'coef0': 0.0, 'degree': 3, 'epsilon': 0.1, 'gamma': 'scale', 'kernel': 'rbf', 'max_iter': -1, 'shrinking': True, 'tol': 0.001, 'verbose': False}","7 lag features + month, day_of_year, day_of_week",0.800347,11.856367,18.342124,0.10192,Optimized Hyperparams
|
| 19 |
+
17,Optimized ElasticNet,"StratifiedShuffleSplit (test_size=0.2, on 5 AQI bins)","{'alpha': 0.1, 'copy_X': True, 'fit_intercept': True, 'l1_ratio': 0.1, 'max_iter': 1000, 'positive': False, 'precompute': False, 'random_state': 42, 'selection': 'cyclic', 'tol': 0.0001, 'warm_start': False}","7 lag features + month, day_of_year, day_of_week",0.769008,12.658724,19.729225,0.110292,Optimized Hyperparams
|
| 20 |
+
18,Base Linear Regression,"StratifiedShuffleSplit (test_size=0.2, on 5 AQI bins)","{'copy_X': True, 'fit_intercept': True, 'n_jobs': None, 'positive': False}","7 lag features + month, day_of_year, day_of_week",0.769085684,12.667486,19.725889,0.110437,Testing the simplest linear model
|
| 21 |
+
19,Base Linear Regression,"StratifiedShuffleSplit (test_size=0.2, on 5 AQI bins)","{'alpha': 1.0, 'copy_X': True, 'fit_intercept': True, 'max_iter': None, 'positive': False, 'random_state': 42, 'solver': 'auto', 'tol': 0.0001}","7 lag features + month, day_of_year, day_of_week",0.769081635,12.667433,19.726062,0.110436,Testing the simplest linear model
|
| 22 |
+
20,Base Lasso Regression,"StratifiedShuffleSplit (test_size=0.2, on 5 AQI bins)","{'alpha': 1.0, 'copy_X': True, 'fit_intercept': True, 'max_iter': 1000, 'positive': False, 'precompute': False, 'random_state': 42, 'selection': 'cyclic', 'tol': 0.0001, 'warm_start': False}","7 lag features + month, day_of_year, day_of_week",0.770819593,12.507577,19.65169,0.108487,Testing the simplest linear model
|
| 23 |
+
21,Base Lasso Regression,"StratifiedShuffleSplit (test_size=0.2, on 5 AQI bins)","{'alpha': 100, 'copy_X': True, 'fit_intercept': True, 'max_iter': None, 'positive': False, 'random_state': 42, 'solver': 'auto', 'tol': 0.0001}","7 lag features + month, day_of_year, day_of_week",0.769046692,12.656959,19.727555,0.110268,Testing the simplest linear model
|
| 24 |
+
22,DELETE ABOVE Optimized Ridge Regression,"StratifiedShuffleSplit (test_size=0.2, on 5 AQI bins)","{'alpha': 100, 'copy_X': True, 'fit_intercept': True, 'max_iter': None, 'positive': False, 'random_state': 42, 'solver': 'auto', 'tol': 0.0001}","7 lag features + month, day_of_year, day_of_week",0.769046692,12.656959,19.727555,0.110268,Testing optimized Linear model
|
| 25 |
+
23,DOptimized Lasso Regression,"StratifiedShuffleSplit (test_size=0.2, on 5 AQI bins)","{'alpha': 0.1, 'copy_X': True, 'fit_intercept': True, 'max_iter': 1000, 'positive': False, 'precompute': False, 'random_state': 42, 'selection': 'cyclic', 'tol': 0.0001, 'warm_start': False}","7 lag features + month, day_of_year, day_of_week",0.76881302,12.661734,19.737532,0.110329,Testing optimized Linear model
|
| 26 |
+
24,"Ensemble CatBoost, XGBoost, RandomForest","StratifiedShuffleSplit (test_size=0.2, on 5 AQI bins)","{'estimators': [('Optimized RandomForest', RandomForestRegressor(max_depth=20, max_features='sqrt', n_estimators=200,n_jobs=-1, random_state=42)), ('Optimized CatBoost', <catboost.core.CatBoostRegressor object at 0x7a1ebce2cbd0>), ('Optimized XGBoost', XGBRegressor(base_score=None, booster=None, callbacks=None,colsample_bylevel=None, colsample_bynode=None,colsample_bylevel=None, colsample_bynode=None,colsample_bytree=0.8,feature_weights=None,importance_type=None, interaction_constraints=None, gamma=None, grow_policy=None, device=None,enable_categorical=False, eval_metric=None, feature_types=None, early_stopping_rounds=None,learning_rate=0.1, max_bin=None, max_cat_threshold=None,max_cat_to_onehot=None, max_delta_step=None, max_depth=3,max_leaves=None, min_child_weight=None, missing=nan,monotone_constraints=None, multi_strategy=None, n_estimators=100,n_jobs=-1, num_parallel_tree=None, ...))]}","7 lag features + month, day_of_year, day_of_week",0.834199799,10.509287,16.714896,0.09398,Testing ensemble model
|
| 27 |
+
25,"Ensemble Stacking RandomForest, CatBoost, SVR","StratifiedShuffleSplit (test_size=0.2, on 5 AQI bins)","{'cv': 5, 'estimators': [('Optimized RandomForest', RandomForestRegressor(max_depth=20, max_features='sqrt', n_estimators=200,n_jobs=-1, random_state=42)), ('Optimized CatBoost', <catboost.core.CatBoostRegressor object at 0x7a1ebce2cbd0>), ('Optimized SVR', SVR(C=100))], 'final_estimator__alpha_per_target': False, 'final_estimator__alphas': (0.1, 1.0, 10.0), 'final_estimator__cv': None, 'final_estimator__fit_intercept': True, 'final_estimator__gcv_mode': None, 'final_estimator__scoring': None, 'final_estimator__store_cv_results': None, 'final_estimator__store_cv_values': 'deprecated', 'final_estimator': RidgeCV(), 'n_jobs': -1, 'passthrough': True, 'verbose': 0, 'Optimized RandomForest': RandomForestRegressor(max_depth=20, max_features='sqrt', n_estimators=200, n_jobs=-1, random_state=42), 'Optimized CatBoost': <catboost.core.CatBoostRegressor object at 0x7a1ebce2cbd0>, 'Optimized SVR': SVR(C=100), 'Optimized RandomForest__bootstrap': True, 'Optimized RandomForest__ccp_alpha': 0.0, 'Optimized RandomForest__criterion': 'squared_error', 'Optimized RandomForest__max_depth': 20, 'Optimized RandomForest__max_features': 'sqrt', 'Optimized RandomForest__max_leaf_nodes': None, 'Optimized RandomForest__max_samples': None, 'Optimized RandomForest__min_impurity_decrease': 0.0, 'Optimized RandomForest__min_samples_leaf': 1, 'Optimized RandomForest__min_samples_split': 2, 'Optimized RandomForest__min_weight_fraction_leaf': 0.0, 'Optimized RandomForest__monotonic_cst': None, 'Optimized RandomForest__n_estimators': 200, 'Optimized RandomForest__n_jobs': -1, 'Optimized RandomForest__oob_score': False, 'Optimized RandomForest__random_state': 42, 'Optimized RandomForest__verbose': 0, 'Optimized RandomForest__warm_start': False, 'Optimized CatBoost__iterations': 300, 'Optimized CatBoost__learning_rate': 0.05, 'Optimized CatBoost__depth': 4, 'Optimized CatBoost__l2_leaf_reg': 3, 'Optimized CatBoost__loss_function': 'RMSE', 'Optimized CatBoost__verbose': 0, 'Optimized CatBoost__random_state': 42, 'Optimized SVR__C': 100, 'Optimized SVR__cache_size': 200, 'Optimized SVR__coef0': 0.0, 'Optimized SVR__degree': 3, 'Optimized SVR__epsilon': 0.1, 'Optimized SVR__gamma': 'scale', 'Optimized SVR__kernel': 'rbf', 'Optimized SVR__max_iter': -1, 'shrinking': True, 'Optimized SVR__tol': 0.001, 'Optimized SVR__verbose': False}","7 lag features + month, day_of_year, day_of_week",0.817529396,10.924042,17.535075,0.096308,Testing ensemble model
|
| 28 |
+
26,Ensemble Weighted Averaging RF-CB-XGB,"StratifiedShuffleSplit (test_size=0.2, on 5 AQI bins),","{'estimators': [('Optimized RandomForest', RandomForestRegressor(max_depth=20, max_features='sqrt', n_estimators=200,n_jobs=-1, random_state=42)), ('Optimized CatBoost',<catboost.core.CatBoostRegressorobjectat0x7a1ebce2cbd0>),('OptimizedXGBoost',XGBRegressor(base_score=None,booster=None,callbacks=None,colsample_bylevel=None, colsample_bynode=None,colsample_bytree=0.8, device=None, early_stopping_rounds=None,
|
| 29 |
+
enable_categorical=False, eval_metric=None, feature_types=None,feature_weights=None, gamma=None, grow_policy=None,
|
| 30 |
+
importance_type=None, interaction_constraints=None,learning_rate=0.1, max_bin=None, max_cat_threshold=None,
|
| 31 |
+
max_cat_to_onehot=None, max_delta_step=None, max_depth=3,max_leaves=None, min_child_weight=None, missing=nan,
|
| 32 |
+
monotone_constraints=None, multi_strategy=None, n_estimators=100,n_jobs=-1, num_parallel_tree=None, ...))], 'n_jobs': None, 'verbose': False, 'weights': [0.4, 0.4, 0.2], 'Optimized RandomForest': RandomForestRegressor(max_depth=20, max_features='sqrt', n_estimators=200,n_jobs=-1, random_state=42), 'Optimized CatBoost': <catboost.core.CatBoostRegressor object at 0x7a1ebce2cbd0>, 'Optimized XGBoost': XGBRegressor(base_score=None, booster=None, callbacks=None,colsample_bylevel=None, colsample_bynode=None,colsample_bytree=0.8, device=None, early_stopping_rounds=None,
|
| 33 |
+
enable_categorical=False, eval_metric=None, feature_types=None,feature_weights=None, gamma=None, grow_policy=None,
|
| 34 |
+
importance_type=None, interaction_constraints=None,learning_rate=0.1, max_bin=None, max_cat_threshold=None,max_cat_to_onehot=None, max_delta_step=None,max_depth=3,max_leaves=None, min_child_weight=None, missing=nan,monotone_constraints=None, multi_strategy=None, n_estimators=100,n_jobs=-1, num_parallel_tree=None, ...), 'Optimized RandomForest__bootstrap': True, 'Optimized RandomForest__ccp_alpha': 0.0, 'Optimized RandomForest__criterion': 'squared_error', 'Optimized RandomForest__max_depth': 20, 'Optimized RandomForest__max_features': 'sqrt', 'Optimized RandomForest__max_leaf_nodes': None, 'Optimized RandomForest__max_samples': None, 'Optimized RandomForest__min_impurity_decrease': 0.0, 'Optimized RandomForest__min_samples_leaf': 1, 'Optimized RandomForest__min_samples_split': 2, 'Optimized RandomForest__min_weight_fraction_leaf': 0.0, 'Optimized RandomForest__monotonic_cst': None, 'Optimized RandomForest__n_estimators': 200, 'Optimized RandomForest__n_jobs': -1, 'Optimized RandomForest__oob_score': False, 'Optimized RandomForest__random_state': 42, 'Optimized RandomForest__verbose': 0, 'Optimized RandomForest__warm_start': False, 'Optimized CatBoost__iterations': 300, 'Optimized CatBoost__learning_rate': 0.05, 'Optimized CatBoost__depth': 4, 'Optimized CatBoost__l2_leaf_reg': 3, 'Optimized CatBoost__loss_function': 'RMSE', 'Optimized CatBoost__verbose': 0, 'Optimized CatBoost__random_state': 42, 'Optimized XGBoost__objective': 'reg:squarederror', 'Optimized XGBoost__base_score': None, 'Optimized XGBoost__booster': None, 'Optimized XGBoost__callbacks': None, 'Optimized XGBoost__colsample_bylevel': None, 'Optimized XGBoost__colsample_bynode': None, 'Optimized XGBoost__colsample_bytree': 0.8, 'Optimized XGBoost__device': None, 'Optimized XGBoost__early_stopping_rounds': None, 'Optimized XGBoost__enable_categorical': False, 'Optimized XGBoost__eval_metric': None, 'Optimized XGBoost__feature_types': None, 'Optimized XGBoost__feature_weights': None, 'Optimized XGBoost__gamma': None, 'Optimized XGBoost__grow_policy': None, 'Optimized XGBoost__importance_type': None, 'Optimized XGBoost__interaction_constraints': None, 'Optimized XGBoost__learning_rate': 0.1, 'Optimized XGBoost__max_bin': None, 'Optimized XGBoost__max_cat_threshold': None, 'Optimized XGBoost__max_cat_to_onehot': None, 'Optimized XGBoost__max_delta_step': None, 'Optimized XGBoost__max_depth': 3, 'Optimized XGBoost__max_leaves': None, 'Optimized XGBoost__min_child_weight': None, 'Optimized XGBoost__missing': nan, 'Optimized XGBoost__monotone_constraints': None, 'Optimized XGBoost__multi_strategy': None, 'Optimized XGBoost__n_estimators': 100, 'Optimized XGBoost__n_jobs': -1, 'Optimized XGBoost__num_parallel_tree': None, 'Optimized XGBoost__random_state': 42, 'Optimized XGBoost__reg_alpha': None, 'Optimized XGBoost__reg_lambda': None, 'Optimized XGBoost__sampling_method': None, 'Optimized XGBoost__scale_pos_weight': None, 'Optimized XGBoost__subsample': 0.7, 'Optimized XGBoost__tree_method': None, 'Optimized XGBoost__validate_parameters': None, 'Optimized XGBoost__verbosity': None}","7 lag features + month, day_of_year, day_of_week",0.836299243,10.551098,16.608733,0.094565,Testing ensemble model but with weighted averages
|
| 35 |
+
27,Ensemble_AdvancedStack_RF-XGB-SVR_with_LGBM,"StratifiedShuffleSplit (test_size=0.2, on 5 AQI bins)","{'cv': 5, 'estimators': [('Optimized RandomForest', RandomForestRegressor(max_depth=20, max_features='sqrt', n_estimators=200,n_jobs=-1, random_state=42)), ('OptimizedXGBoost', XGBRegressor(base_score=None, booster=None, callbacks=None,colsample_bylevel=None, colsample_bynode=None,colsample_bytree=0.8,device=None, early_stopping_rounds=None,enable_categorical=False, eval_metric=None, feature_types=None,
|
| 36 |
+
feature_weights=None,gamma=None,grow_policy=None,importance_type=None,interaction_constraints=None,learning_rate0.1,max_bin=None,max_cat_threshold=None,ax_cat_to_onehot=None, max_delta_step=None, max_depth=3,
|
| 37 |
+
max_leaves=None, min_child_weight=None, missing=nan,
|
| 38 |
+
monotone_constraints=None, multi_strategy=None, n_estimators=100,
|
| 39 |
+
n_jobs=-1, num_parallel_tree=None, ...)), ('Optimized SVR', SVR(C=100))], 'final_estimator__boosting_type': 'gbdt', 'final_estimator__class_weight': None, 'final_estimator__colsample_bytree': 1.0, 'final_estimator__importance_type': 'split', 'final_estimator__learning_rate': 0.1, 'final_estimator__max_depth': -1, 'final_estimator__min_child_samples': 20, 'final_estimator__min_child_weight': 0.001, 'final_estimator__min_split_gain': 0.0, 'final_estimator__n_estimators': 100, 'final_estimator__n_jobs': -1, 'final_estimator__num_leaves': 31, 'final_estimator__objective': None, 'final_estimator__random_state': 42, 'final_estimator__reg_alpha': 0.0, 'final_estimator__reg_lambda': 0.0, 'final_estimator__subsample': 1.0, 'final_estimator__subsample_for_bin': 200000, 'final_estimator__subsample_freq': 0, 'final_estimator__verbosity': -1, 'final_estimator': LGBMRegressor(n_jobs=-1, random_state=42, verbosity=-1), 'n_jobs': -1, 'passthrough': False, 'verbose': 0, 'Optimized RandomForest': RandomForestRegressor(max_depth=20, max_features='sqrt', n_estimators=200,
|
| 40 |
+
n_jobs=-1, random_state=42), 'Optimized XGBoost': XGBRegressor(base_score=None, booster=None, callbacks=None,
|
| 41 |
+
colsample_bylevel=None, colsample_bynode=None,
|
| 42 |
+
colsample_bytree=0.8, device=None, early_stopping_rounds=None,
|
| 43 |
+
enable_categorical=False, eval_metric=None, feature_types=None,
|
| 44 |
+
feature_weights=None, gamma=None, grow_policy=None,
|
| 45 |
+
importance_type=None, interaction_constraints=None,
|
| 46 |
+
learning_rate=0.1, max_bin=None, max_cat_threshold=None,
|
| 47 |
+
max_cat_to_onehot=None, max_delta_step=None, max_depth=3,
|
| 48 |
+
max_leaves=None, min_child_weight=None, missing=nan,
|
| 49 |
+
monotone_constraints=None, multi_strategy=None, n_estimators=100,
|
| 50 |
+
n_jobs=-1, num_parallel_tree=None, ...), 'Optimized SVR': SVR(C=100), 'Optimized RandomForest__bootstrap': True, 'Optimized RandomForest__ccp_alpha': 0.0, 'Optimized RandomForest__criterion': 'squared_error', 'Optimized RandomForest__max_depth': 20, 'Optimized RandomForest__max_features': 'sqrt', 'Optimized RandomForest__max_leaf_nodes': None, 'Optimized RandomForest__max_samples': None, 'Optimized RandomForest__min_impurity_decrease': 0.0, 'Optimized RandomForest__min_samples_leaf': 1, 'Optimized RandomForest__min_samples_split': 2, 'Optimized RandomForest__min_weight_fraction_leaf': 0.0, 'Optimized RandomForest__monotonic_cst': None, 'Optimized RandomForest__n_estimators': 200, 'Optimized RandomForest__n_jobs': -1, 'Optimized RandomForest__oob_score': False, 'Optimized RandomForest__random_state': 42, 'Optimized RandomForest__verbose': 0, 'Optimized RandomForest__warm_start': False, 'Optimized XGBoost__objective': 'reg:squarederror', 'Optimized XGBoost__base_score': None, 'Optimized XGBoost__booster': None, 'Optimized XGBoost__callbacks': None, 'Optimized XGBoost__colsample_bylevel': None, 'Optimized XGBoost__colsample_bynode': None, 'Optimized XGBoost__colsample_bytree': 0.8, 'Optimized XGBoost__device': None, 'Optimized XGBoost__early_stopping_rounds': None, 'Optimized XGBoost__enable_categorical': False, 'Optimized XGBoost__eval_metric': None, 'Optimized XGBoost__feature_types': None, 'Optimized XGBoost__feature_weights': None, 'Optimized XGBoost__gamma': None, 'Optimized XGBoost__grow_policy': None, 'Optimized XGBoost__importance_type': None, 'Optimized XGBoost__interaction_constraints': None, 'Optimized XGBoost__learning_rate': 0.1, 'Optimized XGBoost__max_bin': None, 'Optimized XGBoost__max_cat_threshold': None, 'Optimized XGBoost__max_cat_to_onehot': None, 'Optimized XGBoost__max_delta_step': None, 'Optimized XGBoost__max_depth': 3, 'Optimized XGBoost__max_leaves': None, 'Optimized XGBoost__min_child_weight': None, 'Optimized XGBoost__missing': nan, 'Optimized XGBoost__monotone_constraints': None, 'Optimized XGBoost__multi_strategy': None, 'Optimized XGBoost__n_estimators': 100, 'Optimized XGBoost__n_jobs': -1, 'Optimized XGBoost__num_parallel_tree': None, 'Optimized XGBoost__random_state': 42, 'Optimized XGBoost__reg_alpha': None, 'Optimized XGBoost__reg_lambda': None, 'Optimized XGBoost__sampling_method': None, 'Optimized XGBoost__scale_pos_weight': None, 'Optimized XGBoost__subsample': 0.7, 'Optimized XGBoost__tree_method': None, 'Optimized XGBoost__validate_parameters': None, 'Optimized XGBoost__verbosity': None, 'Optimized SVR__C': 100, 'Optimized SVR__cache_size': 200, 'Optimized SVR__coef0': 0.0, 'Optimized SVR__degree': 3, 'Optimized SVR__epsilon': 0.1, 'Optimized SVR__gamma': 'scale', 'Optimized SVR__kernel': 'rbf', 'Optimized SVR__max_iter': -1, 'shrinking': True, 'Optimized SVR__tol': 0.001, 'Optimized SVR__verbose': False}","7 lag features + month, day_of_year, day_of_week",0.800764606,12.274325,18.322908,0.110482,Testing ensemble model but advanced stacking with LGM as final regressor
|
| 51 |
+
28,Ensemble_WEIGHTED AVERAGE_AD_FE,"StratifiedShuffleSplit (test_size=0.2, on 5 AQI bins)","{'estimators': [('Optimized RandomForest', RandomForestRegressor(max_depth=20, max_features='sqrt', n_estimators=200,n_jobs=-1, random_state=42)), ('Optimized CatBoost', <catboost.core.CatBoostRegressor object at 0x7a1ebe4f9850>), ('Optimized XGBoost', XGBRegressor(base_score=None, booster=None, callbacks=None,
|
| 52 |
+
colsample_bylevel=None, colsample_bynode=None,
|
| 53 |
+
colsample_bytree=0.8, device=None, early_stopping_rounds=None,
|
| 54 |
+
enable_categorical=False, eval_metric=None, feature_types=None,
|
| 55 |
+
feature_weights=None, gamma=None, grow_policy=None,
|
| 56 |
+
importance_type=None, interaction_constraints=None,
|
| 57 |
+
learning_rate=0.1, max_bin=None, max_cat_threshold=None,
|
| 58 |
+
max_cat_to_onehot=None, max_delta_step=None, max_depth=3,
|
| 59 |
+
max_leaves=None, min_child_weight=None, missing=nan,
|
| 60 |
+
monotone_constraints=None, multi_strategy=None, n_estimators=100,
|
| 61 |
+
n_jobs=-1, num_parallel_tree=None, ...))], 'n_jobs': None, 'verbose': False, 'weights': [0.4, 0.4, 0.2], 'Optimized RandomForest': RandomForestRegressor(max_depth=20, max_features='sqrt', n_estimators=200,
|
| 62 |
+
n_jobs=-1, random_state=42), 'Optimized CatBoost': <catboost.core.CatBoostRegressor object at 0x7a1ebe4f9850>, 'Optimized XGBoost': XGBRegressor(base_score=None, booster=None, callbacks=None,
|
| 63 |
+
colsample_bylevel=None, colsample_bynode=None,
|
| 64 |
+
colsample_bytree=0.8, device=None, early_stopping_rounds=None,
|
| 65 |
+
enable_categorical=False, eval_metric=None, feature_types=None,
|
| 66 |
+
feature_weights=None, gamma=None, grow_policy=None,
|
| 67 |
+
importance_type=None, interaction_constraints=None,
|
| 68 |
+
learning_rate=0.1, max_bin=None, max_cat_threshold=None,
|
| 69 |
+
max_cat_to_onehot=None, max_delta_step=None, max_depth=3,
|
| 70 |
+
max_leaves=None, min_child_weight=None, missing=nan,
|
| 71 |
+
monotone_constraints=None, multi_strategy=None, n_estimators=100,
|
| 72 |
+
n_jobs=-1, num_parallel_tree=None, ...), 'Optimized RandomForest__bootstrap': True, 'Optimized RandomForest__ccp_alpha': 0.0, 'Optimized RandomForest__criterion': 'squared_error', 'Optimized RandomForest__max_depth': 20, 'Optimized RandomForest__max_features': 'sqrt', 'Optimized RandomForest__max_leaf_nodes': None, 'Optimized RandomForest__max_samples': None, 'Optimized RandomForest__min_impurity_decrease': 0.0, 'Optimized RandomForest__min_samples_leaf': 1, 'Optimized RandomForest__min_samples_split': 2, 'Optimized RandomForest__min_weight_fraction_leaf': 0.0, 'Optimized RandomForest__monotonic_cst': None, 'Optimized RandomForest__n_estimators': 200, 'Optimized RandomForest__n_jobs': -1, 'Optimized RandomForest__oob_score': False, 'Optimized RandomForest__random_state': 42, 'Optimized RandomForest__verbose': 0, 'Optimized RandomForest__warm_start': False, 'Optimized CatBoost__iterations': 300, 'Optimized CatBoost__learning_rate': 0.05, 'Optimized CatBoost__depth': 4, 'Optimized CatBoost__l2_leaf_reg': 3, 'Optimized CatBoost__loss_function': 'RMSE', 'Optimized CatBoost__verbose': 0, 'Optimized CatBoost__random_state': 42, 'Optimized XGBoost__objective': 'reg:squarederror', 'Optimized XGBoost__base_score': None, 'Optimized XGBoost__booster': None, 'Optimized XGBoost__callbacks': None, 'Optimized XGBoost__colsample_bylevel': None, 'Optimized XGBoost__colsample_bynode': None, 'Optimized XGBoost__colsample_bytree': 0.8, 'Optimized XGBoost__device': None, 'Optimized XGBoost__early_stopping_rounds': None, 'Optimized XGBoost__enable_categorical': False, 'Optimized XGBoost__eval_metric': None, 'Optimized XGBoost__feature_types': None, 'Optimized XGBoost__feature_weights': None, 'Optimized XGBoost__gamma': None, 'Optimized XGBoost__grow_policy': None, 'Optimized XGBoost__importance_type': None, 'Optimized XGBoost__interaction_constraints': None, 'Optimized XGBoost__learning_rate': 0.1, 'Optimized XGBoost__max_bin': None, 'Optimized XGBoost__max_cat_threshold': None, 'Optimized XGBoost__max_cat_to_onehot': None, 'Optimized XGBoost__max_delta_step': None, 'Optimized XGBoost__max_depth': 3, 'Optimized XGBoost__max_leaves': None, 'Optimized XGBoost__min_child_weight': None, 'Optimized XGBoost__missing': nan, 'Optimized XGBoost__monotone_constraints': None, 'Optimized XGBoost__multi_strategy': None, 'Optimized XGBoost__n_estimators': 100, 'Optimized XGBoost__n_jobs': -1, 'Optimized XGBoost__num_parallel_tree': None, 'Optimized XGBoost__random_state': 42, 'Optimized XGBoost__reg_alpha': None, 'Optimized XGBoost__reg_lambda': None, 'Optimized XGBoost__sampling_method': None, 'Optimized XGBoost__scale_pos_weight': None, 'Optimized XGBoost__subsample': 0.7, 'Optimized XGBoost__tree_method': None, 'Optimized XGBoost__validate_parameters': None, 'Optimized XGBoost__verbosity': None}","7 lag features + month, day_of_year, day_of_week",0.857665936,10.725567,16.181658,0.09684,Testing Best using advanced feature engineering
|
| 73 |
+
29,CNN ,"StratifiedShuffleSplit (test_size=0.2, on 5 AQI bins)",,"Advanced Features: Rolling stats, interactions, cyclical time features",-0.104625,32.95193011,44.82592568,0.321646787,Testing the improved CNN architecture with Dropout.
|
| 74 |
+
30, LSTM,"StratifiedShuffleSplit (test_size=0.2, on 5 AQI bins)",,"Advanced Features: Rolling stats, interactions, cyclical time features",-0.001024795,32.69812124,42.67212302,0.332668256,Testing LSTMS. Results not satisfactory.
|
logs/experiment_log (6).csv
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Experiment_Name,Splitting_Strategy,Model_Parameters,Feature_Notes,R2_Score,MAE,RMSE,MAPE,Notes
|
| 2 |
+
State-of-the-Art CNN v2,"StratifiedShuffleSplit (test_size=0.2, on 5 AQI bins)",,"Advanced Features: Rolling stats, interactions, cyclical time features",-0.10462499962161753,32.9519301138783,44.82592568448882,0.32164678749075704,Testing the improved CNN architecture with Dropout.
|
| 3 |
+
State-of-the-Art LSTM,"StratifiedShuffleSplit (test_size=0.2, on 5 AQI bins)",,"Advanced Features: Rolling stats, interactions, cyclical time features",-0.0010247953995508041,32.698121238093805,42.67212302292742,0.33266825623008267,Testing LSTMS. Results not satisfactory.
|
models/MAIN MODEL.joblib
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:bcdfdfd5ad16da0f4b7914192135e973f92b84ff1bc05337e2ae7e429dff7809
|
| 3 |
+
size 17837128
|
notebooks/AQI_Predictor_Dataset_EDA.ipynb
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
notebooks/NON ADVANCED BEST MODEL_JUST NEED TO CLEAN STUFF_WEIGHTED AVERAGE ENSEMBLE.ipynb
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
requirements.txt
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Core Data Science & Utility Libraries
|
| 2 |
+
pandas
|
| 3 |
+
scikit-learn
|
| 4 |
+
requests
|
| 5 |
+
joblib
|
| 6 |
+
pytz
|
| 7 |
+
xgboost
|
| 8 |
+
lightgbm
|
| 9 |
+
catboost
|
| 10 |
+
numpy
|
| 11 |
+
|
| 12 |
+
# Backend API Libraries
|
| 13 |
+
fastapi
|
| 14 |
+
uvicorn
|
start_instructions.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
RUN THIS FOR BACKEND UNICORN SERVER
|
| 3 |
+
uvicorn backend.main:app
|
| 4 |
+
|
| 5 |
+
RUN THIS FOR NEXTAPP (FRONTEND)
|
| 6 |
+
cd frontend
|
| 7 |
+
npm run dev
|
train_model.py
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# =============================================================================
|
| 2 |
+
# AQI PREDICTION - CHAMPION MODEL TRAINING SCRIPT
|
| 3 |
+
# =============================================================================
|
| 4 |
+
#
|
| 5 |
+
# Description:
|
| 6 |
+
# This script automates the process of training the champion AQI prediction model.
|
| 7 |
+
# It performs the following steps:
|
| 8 |
+
# 1. Loads the latest daily data.
|
| 9 |
+
# 2. Preprocesses the data (handles timestamps).
|
| 10 |
+
# 3. Performs two stages of feature engineering (lags, rolling stats, interactions, etc.).
|
| 11 |
+
# 4. Defines the top 3 optimized base models (RandomForest, CatBoost, XGBoost).
|
| 12 |
+
# 5. Trains a Weighted Averaging Ensemble model on the entire dataset.
|
| 13 |
+
# 6. Saves the final, trained model object to a joblib file for use in prediction.
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
import pandas as pd
|
| 17 |
+
import numpy as np
|
| 18 |
+
import joblib
|
| 19 |
+
import os
|
| 20 |
+
import time
|
| 21 |
+
|
| 22 |
+
# --- Model Imports ---
|
| 23 |
+
from sklearn.ensemble import RandomForestRegressor, VotingRegressor
|
| 24 |
+
import xgboost as xgb
|
| 25 |
+
import catboost as cb
|
| 26 |
+
|
| 27 |
+
# --- CONFIGURATION ---
|
| 28 |
+
# Define file paths here to make them easy to change.
|
| 29 |
+
DATA_FILE_PATH = 'data/karachi_daily_data_5_years.csv'
|
| 30 |
+
MODEL_OUTPUT_DIR = 'models'
|
| 31 |
+
MODEL_FILENAME = 'MAIN MODEL.joblib'
|
| 32 |
+
|
| 33 |
+
# --- DATA PROCESSING FUNCTIONS ---
|
| 34 |
+
|
| 35 |
+
def load_and_preprocess_data(file_path):
|
| 36 |
+
"""Loads and cleans the raw dataset."""
|
| 37 |
+
print(f"1/4: Loading and preprocessing data from '{file_path}'...")
|
| 38 |
+
df = pd.read_csv(file_path)
|
| 39 |
+
df['timestamp'] = pd.to_datetime(df['timestamp'])
|
| 40 |
+
df.set_index('timestamp', inplace=True)
|
| 41 |
+
df.sort_index(inplace=True)
|
| 42 |
+
print(" ...Data loaded and preprocessed.")
|
| 43 |
+
return df
|
| 44 |
+
|
| 45 |
+
def create_base_features(df, lags=7):
|
| 46 |
+
"""Creates the initial lag and time-based features."""
|
| 47 |
+
print("2/4: Creating base features (lags and time)...")
|
| 48 |
+
df_featured = df.copy()
|
| 49 |
+
|
| 50 |
+
# Lag Features for AQI
|
| 51 |
+
for i in range(1, lags + 1):
|
| 52 |
+
df_featured[f'aqi_lag_{i}'] = df_featured['aqi'].shift(i)
|
| 53 |
+
|
| 54 |
+
# Time-Based Features
|
| 55 |
+
df_featured['month'] = df_featured.index.month
|
| 56 |
+
df_featured['day_of_year'] = df_featured.index.dayofyear
|
| 57 |
+
df_featured['day_of_week'] = df_featured.index.dayofweek
|
| 58 |
+
|
| 59 |
+
print(" ...Base features created.")
|
| 60 |
+
return df_featured
|
| 61 |
+
|
| 62 |
+
def create_advanced_features(df):
|
| 63 |
+
"""Creates advanced rolling stats, interactions, and cyclical features."""
|
| 64 |
+
print("3/4: Creating advanced features (rolling stats, interactions, cyclical)...")
|
| 65 |
+
df_advanced = df.copy()
|
| 66 |
+
|
| 67 |
+
# Rolling Window Features
|
| 68 |
+
window_sizes = [3, 7]
|
| 69 |
+
cols_to_roll = ['aqi', 'pm25', 'carbon_monoxide', 'wind_speed', 'humidity']
|
| 70 |
+
for window in window_sizes:
|
| 71 |
+
for col in cols_to_roll:
|
| 72 |
+
df_advanced[f'{col}_rolling_mean_{window}'] = df_advanced[col].shift(1).rolling(window=window).mean()
|
| 73 |
+
df_advanced[f'{col}_rolling_std_{window}'] = df_advanced[col].shift(1).rolling(window=window).std()
|
| 74 |
+
|
| 75 |
+
# Interaction Features
|
| 76 |
+
df_advanced['pm25_x_wind_interaction'] = df_advanced['pm25'] / (df_advanced['wind_speed'] + 1)
|
| 77 |
+
df_advanced['temp_x_humidity_interaction'] = df_advanced['temperature'] * df_advanced['humidity']
|
| 78 |
+
|
| 79 |
+
# Cyclical Features
|
| 80 |
+
df_advanced['month_sin'] = np.sin(2 * np.pi * df_advanced['month'] / 12)
|
| 81 |
+
df_advanced['month_cos'] = np.cos(2 * np.pi * df_advanced['month'] / 12)
|
| 82 |
+
df_advanced['day_of_week_sin'] = np.sin(2 * np.pi * df_advanced['day_of_week'] / 7)
|
| 83 |
+
df_advanced['day_of_week_cos'] = np.cos(2 * np.pi * df_advanced['day_of_week'] / 7)
|
| 84 |
+
df_advanced.drop(['month', 'day_of_week'], axis=1, inplace=True)
|
| 85 |
+
|
| 86 |
+
# Drop NaNs created by the feature engineering process
|
| 87 |
+
df_advanced.dropna(inplace=True)
|
| 88 |
+
print(" ...Advanced features created.")
|
| 89 |
+
return df_advanced
|
| 90 |
+
|
| 91 |
+
def train_champion_model(df, output_path):
|
| 92 |
+
"""Trains the final weighted ensemble model and saves it to a file."""
|
| 93 |
+
print(f"4/4: Training the champion model...")
|
| 94 |
+
|
| 95 |
+
# --- a. Define the top-performing base models with their best parameters ---
|
| 96 |
+
rf_model = RandomForestRegressor(
|
| 97 |
+
n_estimators=200, max_depth=20, max_features='sqrt',
|
| 98 |
+
min_samples_split=2, min_samples_leaf=1, random_state=42, n_jobs=-1
|
| 99 |
+
)
|
| 100 |
+
catboost_model = cb.CatBoostRegressor(
|
| 101 |
+
iterations=300, learning_rate=0.05, depth=4,
|
| 102 |
+
l2_leaf_reg=3, random_state=42, verbose=0
|
| 103 |
+
)
|
| 104 |
+
xgboost_model = xgb.XGBRegressor(
|
| 105 |
+
n_estimators=100, max_depth=3, learning_rate=0.1,
|
| 106 |
+
subsample=0.7, colsample_bytree=0.8, random_state=42, n_jobs=-1
|
| 107 |
+
)
|
| 108 |
+
|
| 109 |
+
# --- b. Define the Weighted Averaging Ensemble (VotingRegressor) ---
|
| 110 |
+
# The weights correspond to the confidence in each model (40%, 40%, 20%)
|
| 111 |
+
estimators = [
|
| 112 |
+
('Optimized RandomForest', rf_model),
|
| 113 |
+
('Optimized CatBoost', catboost_model),
|
| 114 |
+
('Optimized XGBoost', xgboost_model)
|
| 115 |
+
]
|
| 116 |
+
weights = [0.4, 0.4, 0.2]
|
| 117 |
+
|
| 118 |
+
ensemble_model = VotingRegressor(estimators=estimators, weights=weights)
|
| 119 |
+
|
| 120 |
+
# --- c. Prepare final data and train the model ---
|
| 121 |
+
X_full = df.drop('aqi', axis=1)
|
| 122 |
+
y_full = df['aqi']
|
| 123 |
+
|
| 124 |
+
ensemble_model.fit(X_full, y_full)
|
| 125 |
+
|
| 126 |
+
# --- d. Save the trained model object ---
|
| 127 |
+
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
| 128 |
+
joblib.dump(ensemble_model, output_path)
|
| 129 |
+
|
| 130 |
+
print(f" ...Model training complete.")
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
# =============================================================================
|
| 134 |
+
# --- MAIN EXECUTION BLOCK ---
|
| 135 |
+
# =============================================================================
|
| 136 |
+
if __name__ == "__main__":
|
| 137 |
+
start_time = time.time()
|
| 138 |
+
print("--- Starting Daily Model Retraining Pipeline ---")
|
| 139 |
+
|
| 140 |
+
try:
|
| 141 |
+
# Step 1: Load and preprocess
|
| 142 |
+
df_clean = load_and_preprocess_data(DATA_FILE_PATH)
|
| 143 |
+
|
| 144 |
+
# Step 2: Create base features
|
| 145 |
+
df_featured = create_base_features(df_clean)
|
| 146 |
+
|
| 147 |
+
# Step 3: Create advanced features
|
| 148 |
+
df_final_features = create_advanced_features(df_featured)
|
| 149 |
+
|
| 150 |
+
# Step 4: Train and save the champion model
|
| 151 |
+
model_output_path = os.path.join(MODEL_OUTPUT_DIR, MODEL_FILENAME)
|
| 152 |
+
train_champion_model(df_final_features, model_output_path)
|
| 153 |
+
|
| 154 |
+
end_time = time.time()
|
| 155 |
+
|
| 156 |
+
print("\n--- PIPELINE COMPLETED SUCCESSFULLY ---")
|
| 157 |
+
print(f"Final model saved to: {model_output_path}")
|
| 158 |
+
print(f"Total runtime: {end_time - start_time:.2f} seconds")
|
| 159 |
+
|
| 160 |
+
except FileNotFoundError:
|
| 161 |
+
print(f"\nERROR: Input data file not found at '{DATA_FILE_PATH}'. Aborting pipeline.")
|
| 162 |
+
except Exception as e:
|
| 163 |
+
print(f"\nAn unexpected error occurred during the pipeline: {e}")
|
vercel.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"version": 2,
|
| 3 |
+
"builds": [
|
| 4 |
+
{
|
| 5 |
+
"src": "frontend/package.json",
|
| 6 |
+
"use": "@vercel/next"
|
| 7 |
+
}
|
| 8 |
+
],
|
| 9 |
+
"routes": [
|
| 10 |
+
{
|
| 11 |
+
"src": "/(.*)",
|
| 12 |
+
"dest": "/frontend/$1"
|
| 13 |
+
}
|
| 14 |
+
]
|
| 15 |
+
}
|