diff --git a/Frontend/index.html b/Frontend/index.html index c3a5c5a..a42551a 100644 --- a/Frontend/index.html +++ b/Frontend/index.html @@ -112,10 +112,36 @@

Predict climate threats before they interrupt your day.

+ +
+
+
+ Weather SMS Alerts +

Get severe weather alerts directly on your phone.

+
+
+
+ + +
+
+ + +
+
+ +
+
+
+ +
Why it helps

Built for quick decisions when weather conditions change.

+
+ Step 2 + Enter city, state, and country
diff --git a/Frontend/script.js b/Frontend/script.js index dbb5097..6826c05 100644 --- a/Frontend/script.js +++ b/Frontend/script.js @@ -243,6 +243,57 @@ window.onload = function () { ).join("
"); }; +const SMS_API_URL = + window.location.hostname === "127.0.0.1" || + window.location.hostname === "localhost" + ? "http://127.0.0.1:5000/subscribe-alert" + : window.location.origin + "/subscribe-alert"; + +// Sending Alert Function +async function enableSmsAlerts() { + + const city = document.getElementById("sms-city").value.trim(); + const phone = document.getElementById("sms-phone").value.trim(); + const status = document.getElementById("sms-status"); + + if (!city || !phone) { + status.innerHTML = "Please enter city and phone number."; + return; + } + + try { + status.innerHTML = "Registering..."; + + const response = await fetch( + SMS_API_URL, + { + method: "POST", + headers: { + "Content-Type": + "application/json" + }, + body: JSON.stringify({ + city, + phone + }) + } + ); + + const data = await response.json(); + + if (data.success) { + status.innerHTML = "SMS alerts enabled successfully."; + + } else { + status.innerHTML = data.message || "Failed to subscribe."; + } + + } catch (error) { + + console.error(error); + status.innerHTML = "Server error."; + } +} const scrollTopBtn = document.getElementById("scrollTopBtn"); if (scrollTopBtn) { @@ -262,3 +313,52 @@ if (scrollTopBtn) { }); } }; + +// Sending Alert Function +const SMS_API_URL = "http://localhost:5000/subscribe-alert"; + +async function enableSmsAlerts() { + + const city = document.getElementById("sms-city").value.trim(); + const phone = document.getElementById("sms-phone").value.trim(); + const status = document.getElementById("sms-status"); + + if (!city || !phone) { + status.innerHTML = "Please enter city and phone number."; + return; + } + + try { + status.innerHTML = "Registering..."; + + const response = await fetch( + SMS_API_URL, + { + method: "POST", + headers: { + "Content-Type": + "application/json" + }, + body: JSON.stringify({ + city, + phone + }) + } + ); + + const data = await response.json(); + + if (response.ok && data.success) { + status.innerHTML = "SMS alerts enabled successfully."; + + } else { + status.innerHTML = data.message || data.subscription?.message || "Failed to subscribe."; + } + + } catch (error) { + + console.error(error); + status.innerHTML = "Server error."; + } +} + diff --git a/Frontend/style.css b/Frontend/style.css index 8b5cb06..c96dccf 100644 --- a/Frontend/style.css +++ b/Frontend/style.css @@ -951,3 +951,68 @@ body::before { [data-theme="light"] .footer-links a:hover { color: #0369a1; } + +/* SMS Alert Feature */ +.glass-card { + padding: 24px; + border-radius: 18px; + background: var(--panel); + border: 1px solid var(--border-color); + backdrop-filter: blur(12px); + box-shadow: var(--shadow); +} + +.input-container { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 16px; + margin: 16px 0; +} + +.input-group { + display: flex; + flex-direction: column; + gap: 6px; +} + +.input-group label { + font-size: 0.85rem; + color: var(--muted); + font-weight: 600; +} + +.input-group input { + padding: 10px 12px; + border-radius: 10px; + border: 1px solid var(--border-strong); + background: var(--input-bg); + color: var(--text-primary); + outline: none; + width: 100%; +} + +.sms-alert-section button { + width: 100%; + display: block; + margin-top: 8px; + padding: 12px 24px; + border-radius: 999px; + border: none; + background: linear-gradient(135deg, var(--accent), var(--accent-2)); + color: white; + font-weight: 600; + cursor: pointer; + transition: transform 0.2s ease; +} + +.sms-alert-section button:hover { + transform: translateY(-2px); +} + +.sms-alert-section #sms-status { + text-align: center; + color: #7dd3fc; + width: 100%; + display: block; + font-weight: bold; +} \ No newline at end of file diff --git a/README.md b/README.md index a67758a..ef417fd 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,14 @@ Climate Shield includes an integrated AI chatbot that provides: The chatbot is lightweight and rule-based. +## Automatic SMS Alert +- **OpenWeatherMap-powered forecasts** — fetches real-time weather forecast data (rainfall, wind speed, humidity, temperature, and storm conditions) for each subscriber's city using the OpenWeatherMap API. + +- **Automatic severe weather detection** — analyzes the forecast against defined thresholds to detect heavy rain, strong winds, flood risk, heatwaves, and thunderstorms. + +- **Instant SMS notifications via Vonage** — when a severe condition is detected, an SMS alert is sent directly to the subscriber's phone with the relevant weather details. + +- **Simple subscription with automated scheduling** — users subscribe once with their city and phone number, and APScheduler periodically rechecks OpenWeatherMap data to send alerts automatically. --- # 🖥 Frontend diff --git a/backend/alertsystem.py b/backend/alertsystem.py index be9636d..e945634 100644 --- a/backend/alertsystem.py +++ b/backend/alertsystem.py @@ -4,8 +4,15 @@ from dotenv import load_dotenv +from flask_cors import CORS + +from sms_alert import save_subscriber,send_weather_alert,check_weather_and_send_alerts +from apscheduler.schedulers.background import BackgroundScheduler + load_dotenv() + + GIS_ALERTS_URL = os.environ.get("GIS_ALERTS_URL", "https://example.com/gis/alerts") def fetch_gis_alert_data(): @@ -46,6 +53,9 @@ def fetch_gis_alert_data(): from flask_cors import CORS +from sms_alert import save_subscriber,send_weather_alert,check_weather_and_send_alerts +from apscheduler.schedulers.background import BackgroundScheduler + # ========================================================= # APP CONFIG # ========================================================= @@ -600,6 +610,46 @@ def chatbot(): "message": "Chatbot unavailable." }) + +# ============================================ +# SENDING ALERT TO THE SUBSCRIBER +# ============================================ +@app.route("/subscribe-alert", methods=["POST"]) +def subscribe_alerts(): + + data = request.get_json() + + city = data.get("city", "").strip() + phone = data.get("phone", "").strip() + + print("Received:", city, phone) + + result = save_subscriber(city, phone) + + if not result["success"]: + return jsonify(result), 409 + + sms_result = send_weather_alert(city, phone) + + print("SMS RESULT:", sms_result) + + return jsonify({ + "subscription": result, + "sms": sms_result}) + + +scheduler = BackgroundScheduler() + +scheduler.add_job( + func=check_weather_and_send_alerts, + trigger="interval", + minutes=1 # for testing : every one minute sms alert will send to registered +) + +scheduler.start() + +print("Weather alert scheduler started.") + # ========================================================= # LOCAL RUN diff --git a/backend/sms_alert.py b/backend/sms_alert.py new file mode 100644 index 0000000..9f95c21 --- /dev/null +++ b/backend/sms_alert.py @@ -0,0 +1,143 @@ +import requests +import os +import vonage +import json + +API_KEY = os.getenv("OPENWEATHER_API_KEY") + +client = vonage.Client( + key=os.getenv("VONAGE_API_KEY"), + secret=os.getenv("VONAGE_API_SECRET") +) + +vonage_sms = vonage.Sms(client) + +SMS_SUBSCRIBERS_FILE = "subscribers.json" + + +def load_subscribers(): + if not os.path.exists(SMS_SUBSCRIBERS_FILE): + return [] + + try: + with open(SMS_SUBSCRIBERS_FILE, "r", encoding="utf-8") as file: + return json.load(file) + except (json.JSONDecodeError, IOError): + return [] + +def save_subscriber(city, phone): + + print("Saving subscriber...") + print("Current folder:", os.getcwd()) + + subscribers = load_subscribers() + city = city.strip() + phone = phone.strip() + + for subscriber in subscribers: + if subscriber["phone"] == phone: + return { + "success": False, + "message": "This phone number is already subscribed for this city."} + + subscribers.append({"city": city,"phone": phone}) + + with open(SMS_SUBSCRIBERS_FILE, "w", encoding="utf-8") as file: + json.dump(subscribers, file, indent=4) + + print("Subscriber saved successfully") + + return {"success": True,"message":"SMS enabled successfully"} + +def send_weather_alert(city, phone): + + forecast_response = requests.get( + "https://api.openweathermap.org/data/2.5/forecast", + params={ + "q": city, + "appid": API_KEY, + "units": "metric" + }, + timeout=15 + ) + + forecast_response.raise_for_status() + forecast_data = forecast_response.json() + alerts = [] + + first_forecast = forecast_data["list"][0] + temperature = first_forecast["main"]["temp"] + humidity = first_forecast["main"]["humidity"] + weather = first_forecast["weather"][0]["main"] + rainfall = first_forecast.get("rain",{}).get("3h",0) + wind_speed = round(first_forecast["wind"]["speed"] * 3.6,1) + + if rainfall >= 10: + alerts.append("Heavy Rain Warning") + + if wind_speed >= 30: + alerts.append("Strong Wind Warning") + + if humidity >= 85 and rainfall >= 5: + alerts.append("Potential Flood Risk") + + if temperature >= 35: + alerts.append("Heatwave Alert") + + if "thunderstorm" in weather.lower(): + alerts.append("Thunderstorm Alert") + + alerts = list(set(alerts)) + + if not alerts: + return { + "success": True, + "alerts": [], + "message": + "No severe weather conditions detected." + } + + sms_text = ( + "Climate Shield Alert\n\n" + f"Location: {city}\n" + f"Temperature: {temperature}°C\n" + f"Humidity: {humidity}%\n" + f"Rainfall: {rainfall} mm\n" + f"Wind Speed: {wind_speed} km/h\n\n" + "Alerts:\n" + + "\n".join( + f"- {alert}" + for alert in alerts + ) + ) + + response = vonage_sms.send_message({ + "from": "ClimateShield", + "to": phone, + "text": sms_text + }) + + print("SMS Response:") + print(response) + + return { + "success": True, + "alerts": alerts, + "sms_response": response + } + +def check_weather_and_send_alerts(): + + subscribers = load_subscribers() + print(f"Checking weather for {len(subscribers)} subscribers...") + + for subscriber in subscribers: + city = subscriber["city"] + phone = subscriber["phone"] + + try: + result = send_weather_alert(city,phone) + print(f"Checked alerts for {city}:",result) + + except Exception as e: + print(f"Failed for {city}: {e}") \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 913fefa..475dba0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,7 @@ flask-cors requests gunicorn python-dotenv -pytest \ No newline at end of file +pytest +apscheduler +vonage==2.5.5 +