github-actions[bot] commited on
Commit
23bb02f
·
0 Parent(s):

Automated backend deployment for 2026-04-05

Browse files
Files changed (47) hide show
  1. .gitattributes +1 -0
  2. .github/workflows/daily_model_training.yml +39 -0
  3. .github/workflows/deploy_to_hf.yml +44 -0
  4. .github/workflows/fetch_current_data.yml +67 -0
  5. .gitignore +65 -0
  6. Dockerfile +14 -0
  7. Open-Meteo.com/open_meteo_check.py +100 -0
  8. Open-Meteo.com/open_meteo_get_historical.py +105 -0
  9. README.md +8 -0
  10. api/index.py +210 -0
  11. append_and_clean_historical_data.py +73 -0
  12. data/current_day_hourly_data.csv +24 -0
  13. data/karachi_daily_data_5_years - Copy.csv +0 -0
  14. data/karachi_daily_data_5_years.csv +0 -0
  15. data/karachi_daily_data_CORRECTLY_PROCESSED.csv +365 -0
  16. data/karachi_historical_data_5_years_hourly.csv +0 -0
  17. data/karachi_hourly_data.csv +0 -0
  18. data/last_7_days_daily_data.csv +9 -0
  19. data/last_7_days_hourly_data.csv +169 -0
  20. fetch_current_data.py +102 -0
  21. frontend/.gitignore +41 -0
  22. frontend/README.md +36 -0
  23. frontend/app/favicon.ico +0 -0
  24. frontend/app/globals.css +102 -0
  25. frontend/app/layout.js +27 -0
  26. frontend/app/page.js +122 -0
  27. frontend/app/page.module.css +167 -0
  28. frontend/eslint.config.mjs +14 -0
  29. frontend/jsconfig.json +7 -0
  30. frontend/next.config.mjs +14 -0
  31. frontend/package-lock.json +0 -0
  32. frontend/package.json +21 -0
  33. frontend/public/file.svg +1 -0
  34. frontend/public/globe.svg +1 -0
  35. frontend/public/next.svg +1 -0
  36. frontend/public/vercel.svg +1 -0
  37. frontend/public/window.svg +1 -0
  38. hourly_to_daily.py +68 -0
  39. logs/AQI Predictor Log 1.csv +74 -0
  40. logs/experiment_log (6).csv +3 -0
  41. models/MAIN MODEL.joblib +3 -0
  42. notebooks/AQI_Predictor_Dataset_EDA.ipynb +0 -0
  43. notebooks/NON ADVANCED BEST MODEL_JUST NEED TO CLEAN STUFF_WEIGHTED AVERAGE ENSEMBLE.ipynb +0 -0
  44. requirements.txt +14 -0
  45. start_instructions.txt +7 -0
  46. train_model.py +163 -0
  47. 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
+ }