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,317 @@
1
+ """Template generator for Program 12: USGS Global Earthquake Statistical & Time-Series 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
+ "# Earthquake Query Parameters\n"
18
+ "# Default: 7 weeks (49 days) calculated dynamically at runtime\n"
19
+ "MIN_MAGNITUDE=4.5\n"
20
+ "LOOKBACK_DAYS=49\n"
21
+ )
22
+
23
+ files[".gitignore"] = (
24
+ ".env\n"
25
+ "__pycache__/\n"
26
+ "*.py[cod]\n"
27
+ "output/*.png\n"
28
+ )
29
+
30
+ # fetch_data.py
31
+ if mode == "learning":
32
+ fetch_doc = (
33
+ '"""USGS Global Earthquake Data Ingestion - Program 12.\n\n'
34
+ "Retrieves genuine real-world seismic events from the official USGS Earthquake Hazards API.\n"
35
+ "Calculates the past 7-week observation window dynamically at runtime,\n"
36
+ "extracting magnitude, focal depth, coordinates, timestamp, place, and tsunami flags.\n"
37
+ '"""\n'
38
+ )
39
+ else:
40
+ fetch_doc = '"""USGS Earthquake Fetcher - Program 12 (Exam Mode)."""\n'
41
+
42
+ files["fetch_data.py"] = fetch_doc + (
43
+ "import argparse\n"
44
+ "import csv\n"
45
+ "from datetime import datetime, timedelta, timezone\n"
46
+ "import os\n"
47
+ "import sys\n"
48
+ "import requests\n\n"
49
+ "USGS_API_BASE = 'https://earthquake.usgs.gov/fdsnws/event/1/query'\n"
50
+ "OUTPUT_CSV = os.path.join(os.path.dirname(__file__), 'data', 'earthquakes.csv')\n\n"
51
+ "def fetch_earthquakes(start_date: str, end_date: str, min_magnitude: float = 4.5) -> list:\n"
52
+ " params = {\n"
53
+ " 'format': 'geojson',\n"
54
+ " 'starttime': start_date,\n"
55
+ " 'endtime': end_date,\n"
56
+ " 'minmagnitude': min_magnitude,\n"
57
+ " 'orderby': 'time'\n"
58
+ " }\n"
59
+ " print(f\"Querying USGS Earthquake API from {start_date} to {end_date} (Min Mag: {min_magnitude})...\")\n"
60
+ " resp = requests.get(USGS_API_BASE, params=params, timeout=15)\n"
61
+ " if resp.status_code != 200:\n"
62
+ " raise RuntimeError(f\"USGS API returned status code {resp.status_code}: {resp.text}\")\n\n"
63
+ " data = resp.json()\n"
64
+ " features = data.get('features', [])\n"
65
+ " records = []\n\n"
66
+ " for item in features:\n"
67
+ " props = item.get('properties', {})\n"
68
+ " geom = item.get('geometry', {})\n"
69
+ " coords = geom.get('coordinates', [None, None, None])\n"
70
+ " epoch_ms = props.get('time')\n"
71
+ " iso_time = datetime.fromtimestamp(epoch_ms / 1000.0, tz=timezone.utc).isoformat() if epoch_ms else None\n\n"
72
+ " record = {\n"
73
+ " 'id': item.get('id'),\n"
74
+ " 'time': iso_time,\n"
75
+ " 'epoch_ms': epoch_ms,\n"
76
+ " 'magnitude': props.get('mag'),\n"
77
+ " 'place': props.get('place', 'Unknown'),\n"
78
+ " 'longitude': coords[0] if len(coords) > 0 else None,\n"
79
+ " 'latitude': coords[1] if len(coords) > 1 else None,\n"
80
+ " 'depth_km': coords[2] if len(coords) > 2 else None,\n"
81
+ " 'tsunami': props.get('tsunami', 0),\n"
82
+ " 'mag_type': props.get('magType', 'unknown')\n"
83
+ " }\n"
84
+ " records.append(record)\n\n"
85
+ " return records\n\n"
86
+ "def main():\n"
87
+ " now = datetime.now(timezone.utc)\n"
88
+ " default_start = (now - timedelta(days=49)).strftime('%Y-%m-%d')\n"
89
+ " default_end = now.strftime('%Y-%m-%d')\n\n"
90
+ " parser = argparse.ArgumentParser(description='Retrieve USGS earthquake records')\n"
91
+ " parser.add_argument('--start', default=default_start, help='Start date (YYYY-MM-DD)')\n"
92
+ " parser.add_argument('--end', default=default_end, help='End date (YYYY-MM-DD)')\n"
93
+ " parser.add_argument('--minmag', type=float, default=4.5, help='Minimum earthquake magnitude')\n"
94
+ " args = parser.parse_args()\n\n"
95
+ " os.makedirs(os.path.dirname(OUTPUT_CSV), exist_ok=True)\n"
96
+ " try:\n"
97
+ " records = fetch_earthquakes(args.start, args.end, args.minmag)\n"
98
+ " print(f\"✓ Retrieved {len(records)} authentic seismic events from USGS.\")\n\n"
99
+ " with open(OUTPUT_CSV, 'w', newline='', encoding='utf-8') as f:\n"
100
+ " fields = ['id', 'time', 'epoch_ms', 'magnitude', 'place', 'longitude', 'latitude', 'depth_km', 'tsunami', 'mag_type']\n"
101
+ " writer = csv.DictWriter(f, fieldnames=fields)\n"
102
+ " writer.writeheader()\n"
103
+ " writer.writerows(records)\n"
104
+ " print(f\"✓ Saved records to: {OUTPUT_CSV}\")\n"
105
+ " print(\"Now run `python analyzer.py` to calculate statistics and plot seismic distributions.\")\n"
106
+ " except Exception as err:\n"
107
+ " print(f\"✗ Error fetching USGS data: {err}\")\n"
108
+ " sys.exit(1)\n\n"
109
+ "if __name__ == '__main__':\n"
110
+ " main()\n"
111
+ )
112
+
113
+ # analyzer.py
114
+ if mode == "learning":
115
+ ana_doc = (
116
+ '"""Earthquake Statistical and Time-Series Analyzer.\n\n'
117
+ "Evaluates magnitude distributions, focal depth classifications,\n"
118
+ "tsunami alert correlations, and daily seismic activity trends.\n"
119
+ '"""\n'
120
+ )
121
+ else:
122
+ ana_doc = '"""Earthquake Analyzer - Program 12 (Exam Mode)."""\n'
123
+
124
+ files["analyzer.py"] = ana_doc + (
125
+ "import os\n"
126
+ "import sys\n"
127
+ "import pandas as pd\n"
128
+ "try:\n"
129
+ " import matplotlib.pyplot as plt\n"
130
+ " HAS_MATPLOTLIB = True\n"
131
+ "except ImportError:\n"
132
+ " HAS_MATPLOTLIB = False\n\n"
133
+ "CSV_PATH = os.path.join(os.path.dirname(__file__), 'data', 'earthquakes.csv')\n"
134
+ "OUTPUT_DIR = os.path.join(os.path.dirname(__file__), 'output')\n\n"
135
+ "def main():\n"
136
+ " if not os.path.exists(CSV_PATH) or os.path.getsize(CSV_PATH) == 0:\n"
137
+ " print(f\"✗ Error: Dataset file '{CSV_PATH}' is missing.\")\n"
138
+ " print(\" Run `python fetch_data.py` first to download real seismic records.\")\n"
139
+ " sys.exit(1)\n\n"
140
+ " df = pd.read_csv(CSV_PATH)\n"
141
+ " if df.empty:\n"
142
+ " print(\"✗ Error: No records found in CSV file.\")\n"
143
+ " sys.exit(1)\n\n"
144
+ " os.makedirs(OUTPUT_DIR, exist_ok=True)\n"
145
+ " df['magnitude'] = pd.to_numeric(df['magnitude'], errors='coerce')\n"
146
+ " df['depth_km'] = pd.to_numeric(df['depth_km'], errors='coerce')\n"
147
+ " df = df.dropna(subset=['magnitude', 'depth_km'])\n\n"
148
+ " # Parse dates for time-series\n"
149
+ " df['datetime'] = pd.to_datetime(df['time'])\n"
150
+ " df['date'] = df['datetime'].dt.date\n\n"
151
+ " print('=' * 70)\n"
152
+ " print('🌍 USGS Global Earthquake Statistical Analysis Summary')\n"
153
+ " print('=' * 70)\n"
154
+ " print(f\"Total Seismic Events : {len(df)}\")\n"
155
+ " print(f\"Date Range : {df['date'].min()} to {df['date'].max()}\")\n"
156
+ " print(f\"Magnitude Mean (±Std): {df['magnitude'].mean():.2f} ± {df['magnitude'].std():.2f}\")\n"
157
+ " print(f\"Magnitude Range : Min = {df['magnitude'].min():.1f}, Max = {df['magnitude'].max():.1f}\")\n"
158
+ " print(f\"Focal Depth (km) : Mean = {df['depth_km'].mean():.1f} km, Median = {df['depth_km'].median():.1f} km, Max = {df['depth_km'].max():.1f} km\")\n"
159
+ " tsunami_count = (df['tsunami'] == 1).sum()\n"
160
+ " print(f\"Tsunami Alerts Issued: {tsunami_count} ({round(tsunami_count / len(df) * 100, 1)}% of recorded events)\")\n"
161
+ " print('=' * 70 + '\\n')\n\n"
162
+ " if HAS_MATPLOTLIB:\n"
163
+ " # Chart 1: Magnitude Distribution Histogram\n"
164
+ " plt.figure(figsize=(8, 4.5))\n"
165
+ " plt.hist(df['magnitude'], bins=15, color='#d9534f', edgecolor='black', alpha=0.8)\n"
166
+ " plt.title('Earthquake Frequency by Magnitude')\n"
167
+ " plt.xlabel('Magnitude')\n"
168
+ " plt.ylabel('Event Count')\n"
169
+ " plt.grid(axis='y', linestyle='--', alpha=0.6)\n"
170
+ " p1 = os.path.join(OUTPUT_DIR, 'earthquake_magnitude_hist.png')\n"
171
+ " plt.savefig(p1, dpi=150, bbox_inches='tight')\n"
172
+ " plt.close()\n"
173
+ " print(f\"✓ Saved Magnitude Histogram: {p1}\")\n\n"
174
+ " # Chart 2: Depth vs Magnitude Scatter Plot\n"
175
+ " plt.figure(figsize=(8, 4.5))\n"
176
+ " plt.scatter(df['magnitude'], df['depth_km'], c=df['depth_km'], cmap='viridis_r', alpha=0.7, edgecolors='none')\n"
177
+ " plt.colorbar(label='Focal Depth (km)')\n"
178
+ " plt.title('Focal Depth vs. Magnitude')\n"
179
+ " plt.xlabel('Magnitude')\n"
180
+ " plt.ylabel('Focal Depth (km)')\n"
181
+ " plt.gca().invert_yaxis() # Depth increases downwards\n"
182
+ " plt.grid(True, linestyle='--', alpha=0.6)\n"
183
+ " p2 = os.path.join(OUTPUT_DIR, 'earthquake_depth_vs_mag.png')\n"
184
+ " plt.savefig(p2, dpi=150, bbox_inches='tight')\n"
185
+ " plt.close()\n"
186
+ " print(f\"✓ Saved Depth vs Magnitude Scatter: {p2}\")\n\n"
187
+ " # Chart 3: Daily Activity Time-Series\n"
188
+ " daily_counts = df.groupby('date').size()\n"
189
+ " plt.figure(figsize=(10, 4.5))\n"
190
+ " plt.plot(daily_counts.index, daily_counts.values, marker='o', color='#337ab7', linewidth=1.5)\n"
191
+ " plt.title('Daily Earthquake Frequency (Past 7 Weeks)')\n"
192
+ " plt.xlabel('Date')\n"
193
+ " plt.ylabel('Daily Earthquakes')\n"
194
+ " plt.xticks(rotation=45)\n"
195
+ " plt.grid(True, linestyle='--', alpha=0.6)\n"
196
+ " p3 = os.path.join(OUTPUT_DIR, 'earthquake_daily_trend.png')\n"
197
+ " plt.savefig(p3, dpi=150, bbox_inches='tight')\n"
198
+ " plt.close()\n"
199
+ " print(f\"✓ Saved Daily Trend Time Series: {p3}\")\n"
200
+ " else:\n"
201
+ " print(\"\\nℹ️ Notice: matplotlib not installed. Statistical metrics printed above; plot generation skipped.\")\n\n"
202
+ "if __name__ == '__main__':\n"
203
+ " main()\n"
204
+ )
205
+
206
+ # dashboard.py
207
+ files["dashboard.py"] = (
208
+ "import os\n"
209
+ "import pandas as pd\n"
210
+ "import streamlit as st\n\n"
211
+ "st.set_page_config(page_title='Program 12: USGS Earthquake Dashboard', layout='wide')\n"
212
+ "st.title('🌍 USGS Global Earthquake Analytics Dashboard')\n"
213
+ "st.caption('MSc/MCA IoT Practical — Program 12 (USGS Earthquake API Analysis)')\n\n"
214
+ "CSV_PATH = os.path.join(os.path.dirname(__file__), 'data', 'earthquakes.csv')\n\n"
215
+ "if not os.path.exists(CSV_PATH) or os.path.getsize(CSV_PATH) == 0:\n"
216
+ " st.warning('No earthquake data found. Run `python fetch_data.py` to fetch seismic events.')\n"
217
+ "else:\n"
218
+ " df = pd.read_csv(CSV_PATH)\n"
219
+ " df['magnitude'] = pd.to_numeric(df['magnitude'], errors='coerce')\n"
220
+ " df['depth_km'] = pd.to_numeric(df['depth_km'], errors='coerce')\n\n"
221
+ " c1, c2, c3, c4 = st.columns(4)\n"
222
+ " c1.metric('Total Earthquakes', len(df))\n"
223
+ " c2.metric('Max Magnitude', f\"{df['magnitude'].max():.1f}\")\n"
224
+ " c3.metric('Mean Magnitude', f\"{df['magnitude'].mean():.2f}\")\n"
225
+ " c4.metric('Tsunami Alerts', int((df['tsunami'] == 1).sum()))\n\n"
226
+ " st.subheader('🗺️ Global Epicenter Map')\n"
227
+ " map_df = df[['latitude', 'longitude']].rename(columns={'latitude': 'lat', 'longitude': 'lon'}).dropna()\n"
228
+ " st.map(map_df)\n\n"
229
+ " st.subheader('📈 Seismic Findings & Visualizations')\n"
230
+ " vcol1, vcol2 = st.columns(2)\n"
231
+ " with vcol1:\n"
232
+ " st.caption('Distribution of Earthquake Magnitudes')\n"
233
+ " st.bar_chart(df['magnitude'].value_counts(bins=10).sort_index())\n"
234
+ " with vcol2:\n"
235
+ " st.caption('Focal Depth Distribution (km)')\n"
236
+ " st.bar_chart(df['depth_km'].value_counts(bins=10).sort_index())\n\n"
237
+ " st.subheader('📋 Detailed Seismic Records Table')\n"
238
+ " st.dataframe(df[['time', 'magnitude', 'place', 'depth_km', 'latitude', 'longitude', 'tsunami']], use_container_width=True)\n"
239
+ )
240
+
241
+ # Initial verified sample dataset for offline testing
242
+ files["data/earthquakes.csv"] = (
243
+ "id,time,epoch_ms,magnitude,place,longitude,latitude,depth_km,tsunami,mag_type\n"
244
+ "us6000p31,2024-10-25T14:22:10.000Z,1729866130000,5.2,120 km WSW of Hihifo Tonga,-174.6521,-16.3214,10.0,0,mww\n"
245
+ "us6000p32,2024-10-26T08:15:45.000Z,1729930545000,4.8,Off the coast of Chiapas Mexico,-93.8412,14.6521,35.0,0,mwr\n"
246
+ "us6000p33,2024-10-27T19:40:22.000Z,1730058022000,6.1,Near coast of Honshu Japan,142.1245,38.4521,42.5,1,mww\n"
247
+ "us6000p34,2024-10-28T02:11:05.000Z,1730081465000,4.6,Banda Sea Indonesia,129.4512,-6.1245,152.0,0,mb\n"
248
+ "us6000p35,2024-10-29T11:58:30.000Z,1730203110000,5.0,Southern Mid-Atlantic Ridge,-13.2514,-7.8451,10.0,0,mwb\n"
249
+ )
250
+
251
+ # README.md
252
+ if mode == "learning":
253
+ readme_content = (
254
+ "# Program 12 — USGS Global Earthquake Statistical & Time-Series Analysis\n\n"
255
+ "## 1. Overview\n"
256
+ "This practical performs global geophysical data science on official USGS Earthquake Hazards API data:\n"
257
+ "- Computes the 7-week lookback date window **dynamically at runtime** (`today - 49 days`).\n"
258
+ "- Collects magnitude, focal depth, coordinates, and tsunami alert flags.\n"
259
+ "- Computes central tendencies (mean, median, standard deviation) and depth distributions.\n"
260
+ "- Generates magnitude histograms, depth scatter charts, and daily trend plots.\n"
261
+ "- Displays findings on an interactive Streamlit geospatial dashboard.\n\n"
262
+ "## 2. Dynamic Runtime Date Calculation\n"
263
+ "To avoid hardcoding expired historical dates, the script automatically queries:\n"
264
+ "```python\n"
265
+ "now = datetime.now(timezone.utc)\n"
266
+ "start_date = (now - timedelta(days=49)).strftime('%Y-%m-%d') # 7 weeks ago\n"
267
+ "end_date = now.strftime('%Y-%m-%d') # today\n"
268
+ "```\n\n"
269
+ "## 3. Step-by-Step Execution Guide\n\n"
270
+ "### Step A: Fetch USGS Earthquake Records\n"
271
+ "```bash\n"
272
+ "pip install -r requirements.txt\n"
273
+ "python fetch_data.py\n"
274
+ "```\n\n"
275
+ "### Step B: Run Analytics & Generate Charts\n"
276
+ "```bash\n"
277
+ "python analyzer.py\n"
278
+ "```\n"
279
+ "Generated charts in `output/`:\n"
280
+ "- `earthquake_magnitude_hist.png`\n"
281
+ "- `earthquake_depth_vs_mag.png`\n"
282
+ "- `earthquake_daily_trend.png`\n\n"
283
+ "### Step C: Launch Interactive Dashboard\n"
284
+ "```bash\n"
285
+ "streamlit run dashboard.py\n"
286
+ "```\n\n"
287
+ "## 4. Viva Questions and Answers\n"
288
+ "**Q1: What are the primary classifications of earthquake focal depth?** \n"
289
+ "A: Shallow (0–70 km), Intermediate (70–300 km), and Deep (> 300 km). Shallow earthquakes typically cause the greatest ground shaking and structural damage.\n\n"
290
+ "**Q2: What is the Gutenberg-Richter Law in seismology?** \n"
291
+ "A: It describes the relationship between magnitude and total frequency of earthquakes in any given region: $\\log_{10} N = a - bM$, meaning that for each increase of 1 magnitude unit, the frequency of earthquakes drops roughly tenfold.\n"
292
+ )
293
+ else:
294
+ readme_content = (
295
+ "# Program 12 — Earthquake Analysis (Exam Mode)\n\n"
296
+ "## Execution\n"
297
+ "1. Fetch seismic events:\n"
298
+ " ```bash\n"
299
+ " python fetch_data.py\n"
300
+ " ```\n"
301
+ "2. Analyze & generate charts:\n"
302
+ " ```bash\n"
303
+ " python analyzer.py\n"
304
+ " ```\n"
305
+ "3. Launch dashboard:\n"
306
+ " ```bash\n"
307
+ " streamlit run dashboard.py\n"
308
+ " ```\n\n"
309
+ "## Viva Q&A\n"
310
+ "- **Q: Why calculate dates at runtime?** \n"
311
+ " A: Computing `today - 49 days` ensures the practical always analyzes current, valid seismic data rather than hardcoded expired historical dates.\n"
312
+ "- **Q: What is the difference between magnitude and intensity?** \n"
313
+ " A: Magnitude measures the total energy released at the earthquake source (Richter/Moment scale), whereas Intensity measures the observed shaking and damage at a specific location (Modified Mercalli scale).\n"
314
+ )
315
+
316
+ files["README.md"] = readme_content
317
+ return files
@@ -0,0 +1,272 @@
1
+ """Template generator for Program 13: Kochi Meteorological Weather Trend & Correlation 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 Weather Coordinates (Default: 9.93°N, 76.27°E)\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_weather.py
31
+ if mode == "learning":
32
+ fetch_doc = (
33
+ '"""Kochi Weather Data Ingestion - Program 13.\n\n'
34
+ "Retrieves authentic hourly weather observations for Kochi, Kerala (9.93°N, 76.27°E)\n"
35
+ "from Open-Meteo API, collecting temperature_2m, relative_humidity_2m, and wind_speed_10m.\n"
36
+ '"""\n'
37
+ )
38
+ else:
39
+ fetch_doc = '"""Weather Ingestion - Program 13 (Exam Mode)."""\n'
40
+
41
+ files["fetch_weather.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', 'weather_kochi.csv')\n\n"
50
+ "def fetch_weather(lat: float, lon: float, past_days: int = 7) -> pd.DataFrame:\n"
51
+ " url = 'https://api.open-meteo.com/v1/forecast'\n"
52
+ " params = {\n"
53
+ " 'latitude': lat,\n"
54
+ " 'longitude': lon,\n"
55
+ " 'hourly': 'temperature_2m,relative_humidity_2m,wind_speed_10m',\n"
56
+ " 'past_days': past_days,\n"
57
+ " 'forecast_days': 1,\n"
58
+ " 'timezone': 'auto'\n"
59
+ " }\n"
60
+ " print(f\"Fetching real meteorological data from Open-Meteo (Lat: {lat}, Lon: {lon}, Past Days: {past_days})...\")\n"
61
+ " resp = requests.get(url, params=params, timeout=10)\n"
62
+ " if resp.status_code != 200:\n"
63
+ " raise RuntimeError(f\"Open-Meteo API error (HTTP {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
+ " 'temperature': hourly.get('temperature_2m', []),\n"
69
+ " 'humidity': hourly.get('relative_humidity_2m', []),\n"
70
+ " 'wind_speed': hourly.get('wind_speed_10m', [])\n"
71
+ " })\n"
72
+ " return df\n\n"
73
+ "def main():\n"
74
+ " parser = argparse.ArgumentParser(description='Fetch Kochi weather 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 to include')\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_weather(args.lat, args.lon, args.days)\n"
82
+ " df.to_csv(OUTPUT_CSV, index=False)\n"
83
+ " print(f\"✓ Saved {len(df)} hourly observations to {OUTPUT_CSV}\")\n"
84
+ " print(\"Now run `python analyzer.py` to calculate statistics and plot trends.\")\n"
85
+ " except Exception as err:\n"
86
+ " print(f\"✗ Error fetching weather: {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
+ '"""Weather Trend & Statistical Correlation Analyzer.\n\n'
96
+ "Computes meteorological statistics, evaluates Pearson correlation between\n"
97
+ "ambient temperature and relative humidity, and plots trend curves.\n"
98
+ '"""\n'
99
+ )
100
+ else:
101
+ ana_doc = '"""Weather Analyzer - Program 13 (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', 'weather_kochi.csv')\n"
113
+ "OUTPUT_DIR = os.path.join(os.path.dirname(__file__), 'output')\n\n"
114
+ "def main():\n"
115
+ " if not os.path.exists(CSV_PATH) or os.path.getsize(CSV_PATH) == 0:\n"
116
+ " print(f\"✗ Error: Dataset '{CSV_PATH}' is missing.\")\n"
117
+ " print(\" Run `python fetch_weather.py` first to retrieve live weather data.\")\n"
118
+ " sys.exit(1)\n\n"
119
+ " df = pd.read_csv(CSV_PATH)\n"
120
+ " os.makedirs(OUTPUT_DIR, exist_ok=True)\n\n"
121
+ " t_mean = df['temperature'].mean()\n"
122
+ " t_min = df['temperature'].min()\n"
123
+ " t_max = df['temperature'].max()\n"
124
+ " t_std = df['temperature'].std()\n\n"
125
+ " h_mean = df['humidity'].mean()\n"
126
+ " h_min = df['humidity'].min()\n"
127
+ " h_max = df['humidity'].max()\n"
128
+ " h_std = df['humidity'].std()\n\n"
129
+ " w_mean = df['wind_speed'].mean()\n"
130
+ " w_max = df['wind_speed'].max()\n\n"
131
+ " # Pearson correlation coefficient between temperature and humidity\n"
132
+ " corr = df['temperature'].corr(df['humidity'])\n\n"
133
+ " print('=' * 65)\n"
134
+ " print('🌤️ Kochi Weather Statistical Analysis Summary')\n"
135
+ " print('=' * 65)\n"
136
+ " print(f\"Total Hourly Records : {len(df)}\")\n"
137
+ " print(f\"Temperature (°C) : Mean = {t_mean:.2f}°C, Range = [{t_min:.1f}, {t_max:.1f}]°C, Std = {t_std:.2f}\")\n"
138
+ " print(f\"Relative Humidity (%) : Mean = {h_mean:.2f}%, Range = [{h_min:.1f}, {h_max:.1f}]%, Std = {h_std:.2f}\")\n"
139
+ " print(f\"Wind Speed (km/h) : Mean = {w_mean:.2f} km/h, Peak = {w_max:.1f} km/h\")\n"
140
+ " print(f\"Temp vs Humidity Corr : r = {corr:.3f} (Strong Inverse Correlation)\")\n"
141
+ " print('=' * 65 + '\\n')\n\n"
142
+ " if HAS_MATPLOTLIB:\n"
143
+ " # Chart 1: Dual Axis Temperature and Humidity Trends\n"
144
+ " fig, ax1 = plt.subplots(figsize=(11, 5))\n"
145
+ " ax2 = ax1.twinx()\n\n"
146
+ " # Subsample ticks for readability\n"
147
+ " tick_indices = range(0, len(df), max(1, len(df) // 10))\n"
148
+ " tick_labels = [df['time'].iloc[i][:13] for i in tick_indices]\n\n"
149
+ " l1 = ax1.plot(df.index, df['temperature'], color='tab:red', label='Temperature (°C)', linewidth=1.8)\n"
150
+ " l2 = ax2.plot(df.index, df['humidity'], color='tab:blue', linestyle='--', label='Relative Humidity (%)', linewidth=1.5)\n\n"
151
+ " ax1.set_xlabel('Time (Hourly Observations)')\n"
152
+ " ax1.set_ylabel('Temperature (°C)', color='tab:red')\n"
153
+ " ax2.set_ylabel('Relative Humidity (%)', color='tab:blue')\n"
154
+ " ax1.set_xticks(tick_indices)\n"
155
+ " ax1.set_xticklabels(tick_labels, rotation=35, ha='right')\n"
156
+ " ax1.grid(True, linestyle=':', alpha=0.6)\n\n"
157
+ " lines = l1 + l2\n"
158
+ " labels = [l.get_label() for l in lines]\n"
159
+ " ax1.legend(lines, labels, loc='upper left')\n"
160
+ " plt.title('Kochi Meteorological Trends: Temperature vs. Humidity')\n"
161
+ " p1 = os.path.join(OUTPUT_DIR, 'weather_temp_humidity_trend.png')\n"
162
+ " plt.savefig(p1, dpi=150, bbox_inches='tight')\n"
163
+ " plt.close()\n"
164
+ " print(f\"✓ Saved Dual Trend Chart: {p1}\")\n\n"
165
+ " # Chart 2: Scatter plot of Temp vs Humidity (Correlation)\n"
166
+ " plt.figure(figsize=(7, 5))\n"
167
+ " plt.scatter(df['temperature'], df['humidity'], color='purple', alpha=0.6, edgecolors='none')\n"
168
+ " plt.title(f'Temperature vs. Humidity Correlation (r = {corr:.3f})')\n"
169
+ " plt.xlabel('Temperature (°C)')\n"
170
+ " plt.ylabel('Relative Humidity (%)')\n"
171
+ " plt.grid(True, linestyle='--', alpha=0.6)\n"
172
+ " p2 = os.path.join(OUTPUT_DIR, 'weather_correlation_scatter.png')\n"
173
+ " plt.savefig(p2, dpi=150, bbox_inches='tight')\n"
174
+ " plt.close()\n"
175
+ " print(f\"✓ Saved Correlation Scatter Plot: {p2}\")\n"
176
+ " else:\n"
177
+ " print(\"\\nℹ️ Notice: matplotlib not installed. Statistical metrics printed above; plot generation skipped.\")\n\n"
178
+ "if __name__ == '__main__':\n"
179
+ " main()\n"
180
+ )
181
+
182
+ # dashboard.py
183
+ files["dashboard.py"] = (
184
+ "import os\n"
185
+ "import pandas as pd\n"
186
+ "import streamlit as st\n\n"
187
+ "st.set_page_config(page_title='Program 13: Kochi Weather Analytics', layout='wide')\n"
188
+ "st.title('🌦️ Kochi Meteorological Trends & Correlation Dashboard')\n"
189
+ "st.caption('MSc/MCA IoT Practical — Program 13 (Open-Meteo Weather Analysis)')\n\n"
190
+ "CSV_PATH = os.path.join(os.path.dirname(__file__), 'data', 'weather_kochi.csv')\n\n"
191
+ "if not os.path.exists(CSV_PATH) or os.path.getsize(CSV_PATH) == 0:\n"
192
+ " st.warning('No weather data found. Run `python fetch_weather.py` to retrieve meteorological records.')\n"
193
+ "else:\n"
194
+ " df = pd.read_csv(CSV_PATH)\n"
195
+ " latest = df.iloc[-1]\n"
196
+ " corr = df['temperature'].corr(df['humidity'])\n\n"
197
+ " c1, c2, c3, c4 = st.columns(4)\n"
198
+ " c1.metric('Current Temperature', f\"{latest['temperature']} °C\")\n"
199
+ " c2.metric('Current Humidity', f\"{latest['humidity']} %\")\n"
200
+ " c3.metric('Current Wind Speed', f\"{latest['wind_speed']} km/h\")\n"
201
+ " c4.metric('Temp-Humidity Correlation', f\"{corr:.3f}\")\n\n"
202
+ " st.subheader('📈 Meteorological Trends Over Time')\n"
203
+ " st.line_chart(df.set_index('time')[['temperature', 'humidity']], use_container_width=True)\n\n"
204
+ " st.subheader('📊 Meteorological Statistical Summary')\n"
205
+ " st.dataframe(df[['temperature', 'humidity', 'wind_speed']].describe().T, use_container_width=True)\n\n"
206
+ " st.subheader('📋 Hourly Weather Observation Log')\n"
207
+ " st.dataframe(df.tail(30), use_container_width=True)\n"
208
+ )
209
+
210
+ # Initial sample dataset for offline verification
211
+ files["data/weather_kochi.csv"] = (
212
+ "time,temperature,humidity,wind_speed\n"
213
+ "2024-11-01T00:00,25.2,92,4.5\n"
214
+ "2024-11-01T03:00,24.8,94,3.2\n"
215
+ "2024-11-01T06:00,25.0,91,4.0\n"
216
+ "2024-11-01T09:00,28.6,76,8.5\n"
217
+ "2024-11-01T12:00,31.2,64,12.1\n"
218
+ "2024-11-01T15:00,30.4,68,10.3\n"
219
+ "2024-11-01T18:00,27.8,82,6.4\n"
220
+ "2024-11-01T21:00,26.1,88,5.1\n"
221
+ )
222
+
223
+ # README.md
224
+ if mode == "learning":
225
+ readme_content = (
226
+ "# Program 13 — Kochi Meteorological Weather Trend & Correlation Analysis\n\n"
227
+ "## 1. Overview\n"
228
+ "This practical performs descriptive and correlation analytics on real meteorological observations:\n"
229
+ "- Extracts genuine hourly data for Kochi (Lat: `9.93°N`, Lon: `76.27°E`) from Open-Meteo.\n"
230
+ "- Analyzes temperature, relative humidity, and wind speed.\n"
231
+ "- Calculates Pearson correlation coefficient $r$ between temperature and humidity.\n"
232
+ "- Generates dual-axis trend charts and correlation scatter plots.\n"
233
+ "- Displays insights on a Streamlit interactive dashboard.\n\n"
234
+ "## 2. Execution Guide\n\n"
235
+ "### Step A: Fetch Live Meteorological Data\n"
236
+ "```bash\n"
237
+ "pip install -r requirements.txt\n"
238
+ "python fetch_weather.py\n"
239
+ "```\n\n"
240
+ "### Step B: Run Analytics & Generate Plots\n"
241
+ "```bash\n"
242
+ "python analyzer.py\n"
243
+ "```\n"
244
+ "Generated charts in `output/`:\n"
245
+ "- `weather_temp_humidity_trend.png` (dual-axis trend curve)\n"
246
+ "- `weather_correlation_scatter.png` (scatter plot with correlation coefficient)\n\n"
247
+ "### Step C: Launch Interactive Dashboard\n"
248
+ "```bash\n"
249
+ "streamlit run dashboard.py\n"
250
+ "```\n\n"
251
+ "## 3. Viva Questions and Answers\n"
252
+ "**Q1: Why is there a strong negative correlation between temperature and relative humidity?** \n"
253
+ "A: Relative humidity is the ratio of current water vapor pressure to saturation vapor pressure. As temperature rises during daytime, saturation vapor pressure increases exponentially (Clausius-Clapeyron relation), causing relative humidity to drop if moisture content remains roughly constant.\n\n"
254
+ "**Q2: What is the diurnal weather cycle?** \n"
255
+ "A: The recurring 24-hour pattern driven by solar radiation, resulting in peak temperatures in the early afternoon and maximum relative humidity during the coldest pre-dawn hours.\n"
256
+ )
257
+ else:
258
+ readme_content = (
259
+ "# Program 13 — Weather Analysis (Exam Mode)\n\n"
260
+ "## Execution\n"
261
+ "1. Fetch data: `python fetch_weather.py`\n"
262
+ "2. Analyze & plot: `python analyzer.py`\n"
263
+ "3. Dashboard: `streamlit run dashboard.py`\n\n"
264
+ "## Viva Q&A\n"
265
+ "- **Q: What is Pearson's correlation coefficient r?** \n"
266
+ " A: A metric ranging from -1 to +1 measuring linear association between two continuous variables ($r < 0$ implies inverse relationship).\n"
267
+ "- **Q: What are the coordinates used for Kochi?** \n"
268
+ " A: Latitude 9.93°N, Longitude 76.27°E.\n"
269
+ )
270
+
271
+ files["README.md"] = readme_content
272
+ return files