iotlabkit 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,287 @@
1
+ """Template generator for Program 14: Kochi Hourly Air Quality & Health Index Analysis."""
2
+
3
+ from typing import Dict
4
+
5
+
6
+ def generate(target_dir: str, mode: str = "learning") -> Dict[str, str]:
7
+ files = {}
8
+
9
+ files["requirements.txt"] = (
10
+ "requests>=2.28.0\n"
11
+ "pandas>=1.5.0\n"
12
+ "matplotlib>=3.6.0\n"
13
+ "streamlit>=1.25.0\n"
14
+ )
15
+
16
+ files[".env.example"] = (
17
+ "# Kochi Air Quality Coordinates\n"
18
+ "LATITUDE=9.93\n"
19
+ "LONGITUDE=76.27\n"
20
+ "PAST_DAYS=7\n"
21
+ )
22
+
23
+ files[".gitignore"] = (
24
+ ".env\n"
25
+ "__pycache__/\n"
26
+ "*.py[cod]\n"
27
+ "output/*.png\n"
28
+ )
29
+
30
+ # fetch_air_quality.py
31
+ if mode == "learning":
32
+ fetch_doc = (
33
+ '"""Kochi Air Quality Data Ingestion - Program 14.\n\n'
34
+ "Retrieves genuine hourly air-quality observations from Open-Meteo Air Quality API\n"
35
+ "for Kochi (9.93°N, 76.27°E), collecting PM10, PM2.5, and US AQI indices.\n"
36
+ '"""\n'
37
+ )
38
+ else:
39
+ fetch_doc = '"""Air Quality Ingestion - Program 14 (Exam Mode)."""\n'
40
+
41
+ files["fetch_air_quality.py"] = fetch_doc + (
42
+ "import argparse\n"
43
+ "import os\n"
44
+ "import sys\n"
45
+ "import pandas as pd\n"
46
+ "import requests\n\n"
47
+ "DEFAULT_LAT = 9.93\n"
48
+ "DEFAULT_LON = 76.27\n"
49
+ "OUTPUT_CSV = os.path.join(os.path.dirname(__file__), 'data', 'air_quality_kochi.csv')\n\n"
50
+ "def fetch_air_quality(lat: float, lon: float, past_days: int = 7) -> pd.DataFrame:\n"
51
+ " url = 'https://air-quality-api.open-meteo.com/v1/air-quality'\n"
52
+ " params = {\n"
53
+ " 'latitude': lat,\n"
54
+ " 'longitude': lon,\n"
55
+ " 'hourly': 'pm10,pm2_5,us_aqi',\n"
56
+ " 'past_days': past_days,\n"
57
+ " 'forecast_days': 1,\n"
58
+ " 'timezone': 'auto'\n"
59
+ " }\n"
60
+ " print(f\"Retrieving hourly air quality observations from Open-Meteo (Lat: {lat}, Lon: {lon})...\")\n"
61
+ " resp = requests.get(url, params=params, timeout=12)\n"
62
+ " if resp.status_code != 200:\n"
63
+ " raise RuntimeError(f\"Open-Meteo Air Quality API error ({resp.status_code}): {resp.text}\")\n\n"
64
+ " data = resp.json()\n"
65
+ " hourly = data.get('hourly', {})\n"
66
+ " df = pd.DataFrame({\n"
67
+ " 'time': hourly.get('time', []),\n"
68
+ " 'pm10': hourly.get('pm10', []),\n"
69
+ " 'pm2_5': hourly.get('pm2_5', []),\n"
70
+ " 'us_aqi': hourly.get('us_aqi', [])\n"
71
+ " })\n"
72
+ " return df\n\n"
73
+ "def main():\n"
74
+ " parser = argparse.ArgumentParser(description='Fetch Kochi air quality data')\n"
75
+ " parser.add_argument('--lat', type=float, default=DEFAULT_LAT, help='Latitude')\n"
76
+ " parser.add_argument('--lon', type=float, default=DEFAULT_LON, help='Longitude')\n"
77
+ " parser.add_argument('--days', type=int, default=7, help='Past days')\n"
78
+ " args = parser.parse_args()\n\n"
79
+ " os.makedirs(os.path.dirname(OUTPUT_CSV), exist_ok=True)\n"
80
+ " try:\n"
81
+ " df = fetch_air_quality(args.lat, args.lon, args.days)\n"
82
+ " df.to_csv(OUTPUT_CSV, index=False)\n"
83
+ " print(f\"✓ Saved {len(df)} air-quality observations to {OUTPUT_CSV}\")\n"
84
+ " print(\"Now run `python analyzer.py` to calculate particulate statistics and health index trends.\")\n"
85
+ " except Exception as err:\n"
86
+ " print(f\"✗ Error fetching air quality: {err}\")\n"
87
+ " sys.exit(1)\n\n"
88
+ "if __name__ == '__main__':\n"
89
+ " main()\n"
90
+ )
91
+
92
+ # analyzer.py
93
+ if mode == "learning":
94
+ ana_doc = (
95
+ '"""Air Quality Time-Series & Statistical Health Index Analyzer.\n\n'
96
+ "Evaluates PM10 and PM2.5 concentrations, computes EPA AQI health category buckets,\n"
97
+ "distinguishes missing observations from calculated statistics, and plots trends.\n"
98
+ '"""\n'
99
+ )
100
+ else:
101
+ ana_doc = '"""Air Quality Analyzer - Program 14 (Exam Mode)."""\n'
102
+
103
+ files["analyzer.py"] = ana_doc + (
104
+ "import os\n"
105
+ "import sys\n"
106
+ "import pandas as pd\n"
107
+ "try:\n"
108
+ " import matplotlib.pyplot as plt\n"
109
+ " HAS_MATPLOTLIB = True\n"
110
+ "except ImportError:\n"
111
+ " HAS_MATPLOTLIB = False\n\n"
112
+ "CSV_PATH = os.path.join(os.path.dirname(__file__), 'data', 'air_quality_kochi.csv')\n"
113
+ "OUTPUT_DIR = os.path.join(os.path.dirname(__file__), 'output')\n\n"
114
+ "def categorize_us_aqi(aqi: float) -> str:\n"
115
+ " if pd.isna(aqi):\n"
116
+ " return 'Missing Observation'\n"
117
+ " if aqi <= 50:\n"
118
+ " return 'Good (0-50)'\n"
119
+ " elif aqi <= 100:\n"
120
+ " return 'Moderate (51-100)'\n"
121
+ " elif aqi <= 150:\n"
122
+ " return 'Unhealthy for Sensitive Groups (101-150)'\n"
123
+ " elif aqi <= 200:\n"
124
+ " return 'Unhealthy (151-200)'\n"
125
+ " elif aqi <= 300:\n"
126
+ " return 'Very Unhealthy (201-300)'\n"
127
+ " else:\n"
128
+ " return 'Hazardous (>300)'\n\n"
129
+ "def main():\n"
130
+ " if not os.path.exists(CSV_PATH) or os.path.getsize(CSV_PATH) == 0:\n"
131
+ " print(f\"✗ Error: Dataset file '{CSV_PATH}' is missing.\")\n"
132
+ " print(\" Run `python fetch_air_quality.py` first to retrieve live API observations.\")\n"
133
+ " sys.exit(1)\n\n"
134
+ " df = pd.read_csv(CSV_PATH)\n"
135
+ " os.makedirs(OUTPUT_DIR, exist_ok=True)\n\n"
136
+ " # Data Audit: Clearly distinguish raw observations, missing values, and calculated statistics\n"
137
+ " total_rows = len(df)\n"
138
+ " missing_pm25 = df['pm2_5'].isna().sum()\n"
139
+ " missing_pm10 = df['pm10'].isna().sum()\n"
140
+ " missing_aqi = df['us_aqi'].isna().sum()\n\n"
141
+ " print('=' * 70)\n"
142
+ " print('💨 Kochi Air Quality Statistical & Health Analysis')\n"
143
+ " print('=' * 70)\n"
144
+ " print(f\"Total Hourly Records : {total_rows}\")\n"
145
+ " print(f\"Missing Observations : PM2.5: {missing_pm25}, PM10: {missing_pm10}, US AQI: {missing_aqi}\")\n"
146
+ " print('-' * 70)\n"
147
+ " print('Calculated Statistics (Excluding missing observations):')\n"
148
+ " print(f\" PM2.5 (μg/m³) : Mean = {df['pm2_5'].mean():.2f}, Median = {df['pm2_5'].median():.2f}, Peak = {df['pm2_5'].max():.1f}, Std = {df['pm2_5'].std():.2f}\")\n"
149
+ " print(f\" PM10 (μg/m³) : Mean = {df['pm10'].mean():.2f}, Median = {df['pm10'].median():.2f}, Peak = {df['pm10'].max():.1f}, Std = {df['pm10'].std():.2f}\")\n"
150
+ " print(f\" US AQI (Index) : Mean = {df['us_aqi'].mean():.1f}, Peak = {df['us_aqi'].max():.0f}, Min = {df['us_aqi'].min():.0f}\")\n\n"
151
+ " df['aqi_category'] = df['us_aqi'].apply(categorize_us_aqi)\n"
152
+ " category_counts = df['aqi_category'].value_counts()\n"
153
+ " print('\\nUS EPA AQI Health Category Breakdown:')\n"
154
+ " for cat, cnt in category_counts.items():\n"
155
+ " pct = round(cnt / total_rows * 100, 1)\n"
156
+ " print(f\" - {cat:<36}: {cnt:>3} hours ({pct:>4}%)\")\n"
157
+ " print('=' * 70 + '\\n')\n\n"
158
+ " if HAS_MATPLOTLIB:\n"
159
+ " # Chart 1: Time Series Trend of PM2.5 and PM10 with WHO Air Quality Guidelines\n"
160
+ " plt.figure(figsize=(11, 5))\n"
161
+ " plt.plot(df.index, df['pm2_5'], color='darkorange', label='PM2.5 (Fine Particulate)', linewidth=1.8)\n"
162
+ " plt.plot(df.index, df['pm10'], color='royalblue', label='PM10 (Inhalable Particulate)', linewidth=1.5, linestyle='--')\n"
163
+ " plt.axhline(15, color='orange', linestyle=':', label='WHO PM2.5 24h Guideline (15 μg/m³)')\n"
164
+ " plt.axhline(45, color='blue', linestyle=':', label='WHO PM10 24h Guideline (45 μg/m³)')\n\n"
165
+ " # Subsample ticks\n"
166
+ " step = max(1, len(df) // 10)\n"
167
+ " plt.xticks(range(0, len(df), step), [df['time'].iloc[i][:13] for i in range(0, len(df), step)], rotation=35, ha='right')\n"
168
+ " plt.title('Hourly Particulate Matter Trends (Kochi, Kerala)')\n"
169
+ " plt.xlabel('Timestamp (Hourly)')\n"
170
+ " plt.ylabel('Concentration (μg/m³)')\n"
171
+ " plt.grid(True, linestyle='--', alpha=0.6)\n"
172
+ " plt.legend(loc='upper right')\n"
173
+ " p1 = os.path.join(OUTPUT_DIR, 'air_quality_pm_trends.png')\n"
174
+ " plt.savefig(p1, dpi=150, bbox_inches='tight')\n"
175
+ " plt.close()\n"
176
+ " print(f\"✓ Saved Particulate Trend Plot: {p1}\")\n\n"
177
+ " # Chart 2: US AQI Time Series with Health Threshold Bands\n"
178
+ " plt.figure(figsize=(11, 4.5))\n"
179
+ " plt.plot(df.index, df['us_aqi'], color='purple', label='US AQI', linewidth=2.0)\n"
180
+ " plt.axhspan(0, 50, color='green', alpha=0.15, label='Good (0-50)')\n"
181
+ " plt.axhspan(50, 100, color='yellow', alpha=0.15, label='Moderate (51-100)')\n"
182
+ " plt.axhspan(100, 150, color='orange', alpha=0.15, label='Sensitive (101-150)')\n"
183
+ " plt.xticks(range(0, len(df), step), [df['time'].iloc[i][:13] for i in range(0, len(df), step)], rotation=35, ha='right')\n"
184
+ " plt.title('US Air Quality Index (AQI) Health Thresholds')\n"
185
+ " plt.xlabel('Timestamp')\n"
186
+ " plt.ylabel('AQI Value')\n"
187
+ " plt.grid(True, linestyle='--', alpha=0.6)\n"
188
+ " plt.legend(loc='upper right')\n"
189
+ " p2 = os.path.join(OUTPUT_DIR, 'air_quality_aqi_thresholds.png')\n"
190
+ " plt.savefig(p2, dpi=150, bbox_inches='tight')\n"
191
+ " plt.close()\n"
192
+ " print(f\"✓ Saved AQI Threshold Chart: {p2}\")\n"
193
+ " else:\n"
194
+ " print(\"\\nℹ️ Notice: matplotlib not installed. Statistical metrics printed above; plot generation skipped.\")\n\n"
195
+ "if __name__ == '__main__':\n"
196
+ " main()\n"
197
+ )
198
+
199
+ # dashboard.py
200
+ files["dashboard.py"] = (
201
+ "import os\n"
202
+ "import pandas as pd\n"
203
+ "import streamlit as st\n\n"
204
+ "st.set_page_config(page_title='Program 14: Kochi Air Quality', layout='wide')\n"
205
+ "st.title('💨 Kochi Air Quality & Health Index Dashboard')\n"
206
+ "st.caption('MSc/MCA IoT Practical — Program 14 (Open-Meteo Air Quality Analysis)')\n\n"
207
+ "CSV_PATH = os.path.join(os.path.dirname(__file__), 'data', 'air_quality_kochi.csv')\n\n"
208
+ "if not os.path.exists(CSV_PATH) or os.path.getsize(CSV_PATH) == 0:\n"
209
+ " st.warning('No air quality data found. Run `python fetch_air_quality.py` to retrieve observations.')\n"
210
+ "else:\n"
211
+ " df = pd.read_csv(CSV_PATH)\n"
212
+ " latest = df.dropna().iloc[-1] if not df.dropna().empty else df.iloc[-1]\n\n"
213
+ " c1, c2, c3, c4 = st.columns(4)\n"
214
+ " c1.metric('Latest PM2.5', f\"{latest['pm2_5']} μg/m³\")\n"
215
+ " c2.metric('Latest PM10', f\"{latest['pm10']} μg/m³\")\n"
216
+ " c3.metric('Latest US AQI', f\"{int(latest['us_aqi']) if pd.notna(latest['us_aqi']) else 'N/A'}\")\n"
217
+ " c4.metric('Total Observations', len(df))\n\n"
218
+ " st.subheader('📈 Particulate Matter Trends (PM2.5 & PM10)')\n"
219
+ " st.line_chart(df.set_index('time')[['pm2_5', 'pm10']], use_container_width=True)\n\n"
220
+ " st.subheader('📊 Air Quality Index (US AQI)')\n"
221
+ " st.line_chart(df.set_index('time')['us_aqi'], use_container_width=True)\n\n"
222
+ " st.subheader('📋 Detailed Air Quality Observations Log')\n"
223
+ " st.dataframe(df.tail(30), use_container_width=True)\n"
224
+ )
225
+
226
+ # Initial sample dataset for offline verification
227
+ files["data/air_quality_kochi.csv"] = (
228
+ "time,pm10,pm2_5,us_aqi\n"
229
+ "2024-11-01T00:00,34.2,28.5,85\n"
230
+ "2024-11-01T03:00,38.1,31.2,91\n"
231
+ "2024-11-01T06:00,45.6,37.8,106\n"
232
+ "2024-11-01T09:00,32.4,25.1,78\n"
233
+ "2024-11-01T12:00,26.8,20.4,68\n"
234
+ "2024-11-01T15:00,24.5,18.9,65\n"
235
+ "2024-11-01T18:00,35.7,29.3,87\n"
236
+ "2024-11-01T21:00,41.2,34.6,98\n"
237
+ )
238
+
239
+ # README.md
240
+ if mode == "learning":
241
+ readme_content = (
242
+ "# Program 14 — Kochi Hourly Air Quality & Health Index Analysis\n\n"
243
+ "## 1. Overview\n"
244
+ "This practical performs environmental health analytics on real atmospheric particulate measurements:\n"
245
+ "- Collects authentic hourly PM10, PM2.5, and US AQI data for Kochi (Lat: `9.93°N`, Lon: `76.27°E`) from Open-Meteo Air Quality API.\n"
246
+ "- Performs rigorous data auditing: clearly distinguishes raw API observations, missing observations (NaNs), and calculated dispersion statistics.\n"
247
+ "- Analyzes pollution curves against WHO 24-hour health guidelines.\n"
248
+ "- Generates time-series plots and provides an interactive Streamlit dashboard.\n\n"
249
+ "## 2. Execution Guide\n\n"
250
+ "### Step A: Fetch Live Hourly Air Quality\n"
251
+ "```bash\n"
252
+ "pip install -r requirements.txt\n"
253
+ "python fetch_air_quality.py\n"
254
+ "```\n\n"
255
+ "### Step B: Run Analytics & Generate Plots\n"
256
+ "```bash\n"
257
+ "python analyzer.py\n"
258
+ "```\n"
259
+ "Generated charts in `output/`:\n"
260
+ "- `air_quality_pm_trends.png` (PM2.5 and PM10 with WHO guideline thresholds)\n"
261
+ "- `air_quality_aqi_thresholds.png` (US AQI with health risk color bands)\n\n"
262
+ "### Step C: Launch Dashboard\n"
263
+ "```bash\n"
264
+ "streamlit run dashboard.py\n"
265
+ "```\n\n"
266
+ "## 3. Viva Questions and Answers\n"
267
+ "**Q1: Why is PM2.5 considered more hazardous to human health than PM10?** \n"
268
+ "A: PM2.5 particles are small enough (<= 2.5 um) to penetrate past the nasal passage and bronchioles, reaching deep into the alveoli of the lungs and directly diffusing into the bloodstream.\n\n"
269
+ "**Q2: How does US AQI differ from raw pollutant concentration?** \n"
270
+ "A: US AQI is a standardized piecewise linear index (0 to 500) that normalizes different pollutant measurements onto a unified scale reflecting relative health hazard categories (Good, Moderate, Unhealthy, Hazardous).\n"
271
+ )
272
+ else:
273
+ readme_content = (
274
+ "# Program 14 — Air Quality Analysis (Exam Mode)\n\n"
275
+ "## Execution\n"
276
+ "1. Fetch data: `python fetch_air_quality.py`\n"
277
+ "2. Analyze & plot: `python analyzer.py`\n"
278
+ "3. Dashboard: `streamlit run dashboard.py`\n\n"
279
+ "## Viva Q&A\n"
280
+ "- **Q: What is the WHO recommended 24-hour limit for PM2.5?** \n"
281
+ " A: 15 $\\mu\\text{g/m}^3$ (updated in 2021 WHO Global Air Quality Guidelines).\n"
282
+ "- **Q: Why distinguish missing observations from calculated statistics?** \n"
283
+ " A: Failing to separate missing values from zero readings distorts statistical measures like mean and variance.\n"
284
+ )
285
+
286
+ files["README.md"] = readme_content
287
+ return files