RushiMane2003 commited on
Commit
11188cb
·
verified ·
1 Parent(s): 9e8af39

added all files

Browse files
.gitattributes CHANGED
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ static/alarn_tune.mp3 filter=lfs diff=lfs merge=lfs -text
37
+ static/images/img.png filter=lfs diff=lfs merge=lfs -text
Dockerfile ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use an official Python runtime as a parent image
2
+ FROM python:3.9-slim
3
+
4
+ # Set the working directory
5
+ WORKDIR /app
6
+
7
+ # Copy only requirements first to leverage Docker caching
8
+ COPY requirements.txt /app/
9
+
10
+ # Install dependencies before copying the full application
11
+ RUN pip install --no-cache-dir --upgrade pip \
12
+ && pip install --no-cache-dir -r requirements.txt
13
+
14
+ # Copy the rest of the application files
15
+ COPY . /app
16
+
17
+ # Expose the Flask port (Hugging Face Spaces uses port 7860 by default)
18
+ EXPOSE 7860
19
+
20
+ # Command to run the Flask app
21
+ CMD ["python", "app.py"]
app.py ADDED
@@ -0,0 +1,250 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template, request, jsonify
2
+ import joblib
3
+ import pandas as pd
4
+ import requests
5
+ import json
6
+ import plotly.graph_objects as go
7
+ from twilio.rest import Client
8
+ from twilio.twiml.messaging_response import MessagingResponse
9
+ import threading
10
+ import os
11
+
12
+ app = Flask(__name__)
13
+
14
+ # --- Global variable to store the latest irrigation parameters ---
15
+ # This acts as a temporary session to hold data between the prediction and the WhatsApp confirmation.
16
+ last_irrigation_params = {}
17
+
18
+ # --- Load the pre-trained SVM model ---
19
+ try:
20
+ svm_poly_model = joblib.load('svm_poly_model.pkl')
21
+ except FileNotFoundError:
22
+ print("Error: svm_poly_model.pkl not found. Make sure the model file is in the correct directory.")
23
+ svm_poly_model = None
24
+
25
+ # --- Data Mappings for the Model ---
26
+ # These mappings convert categorical form inputs into numerical values for the SVM model.
27
+ crop_type_mapping = {
28
+ 'BANANA': 0, 'BEAN': 1, 'CABBAGE': 2, 'CITRUS': 3, 'COTTON': 4, 'MAIZE': 5,
29
+ 'MELON': 6, 'MUSTARD': 7, 'ONION': 8, 'OTHER': 9, 'POTATO': 10, 'RICE': 11,
30
+ 'SOYABEAN': 12, 'SUGARCANE': 13, 'TOMATO': 14, 'WHEAT': 15
31
+ }
32
+ soil_type_mapping = {'DRY': 0, 'HUMID': 1, 'WET': 2}
33
+ weather_condition_mapping = {'NORMAL': 0, 'RAINY': 1, 'SUNNY': 2, 'WINDY': 3}
34
+
35
+ # --- API Keys and Credentials ---
36
+ # It's highly recommended to use environment variables for security.
37
+ WEATHER_API_KEY = os.getenv('WEATHER_API', 'YOUR_DEFAULT_WEATHER_API_KEY')
38
+ TWILIO_ACCOUNT_SID = os.getenv('TWILIO_ACCOUNT_SID', 'AC490e071f8d01bf0df2f03d086c788d87')
39
+ TWILIO_AUTH_TOKEN = os.getenv('TWILIO_AUTH_TOKEN', '224b23b950ad5a4052aba15893fdf083')
40
+ TWILIO_PHONE_NUMBER = 'whatsapp:+14155238886'
41
+ USER_PHONE_NUMBER = 'whatsapp:+917559355282' # The farmer's WhatsApp number
42
+
43
+ # Initialize Twilio Client
44
+ twilio_client = Client(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN)
45
+
46
+ def get_weather(city: str):
47
+ """Fetches weather data from OpenWeatherMap API."""
48
+ url = f"https://api.openweathermap.org/data/2.5/weather?q={city}&appid={WEATHER_API_KEY}&units=metric"
49
+ try:
50
+ response = requests.get(url)
51
+ response.raise_for_status() # Raises an HTTPError for bad responses (4xx or 5xx)
52
+ data = response.json()
53
+
54
+ if data['cod'] == 200:
55
+ weather_description = data['weather'][0]['description']
56
+ temperature = data['main']['temp']
57
+ humidity = data['main']['humidity']
58
+ pressure = data['main']['pressure']
59
+ return temperature, humidity, weather_description, pressure
60
+ except requests.exceptions.RequestException as e:
61
+ print(f"Error fetching weather data: {e}")
62
+ except (KeyError, json.JSONDecodeError):
63
+ print("Error parsing weather data.")
64
+
65
+ return None, None, None, None
66
+
67
+ def send_whatsapp_message(to_number, body_text):
68
+ """General function to send WhatsApp messages via Twilio."""
69
+ try:
70
+ message = twilio_client.messages.create(
71
+ from_=TWILIO_PHONE_NUMBER,
72
+ body=body_text,
73
+ to=to_number
74
+ )
75
+ print(f"Twilio Message SID: {message.sid}")
76
+ return message.sid
77
+ except Exception as e:
78
+ print(f"Error sending WhatsApp message: {e}")
79
+ return None
80
+
81
+ def trigger_irrigation_complete():
82
+ """Function called by the timer when irrigation is finished."""
83
+ global last_irrigation_params
84
+ if not last_irrigation_params:
85
+ print("No irrigation parameters found for completion message.")
86
+ return
87
+
88
+ crop_type = last_irrigation_params.get('crop_type', 'N/A')
89
+ city = last_irrigation_params.get('city', 'N/A')
90
+ estimated_time = last_irrigation_params.get('estimated_time_duration', 0)
91
+ time_unit = last_irrigation_params.get('time_unit', 'seconds')
92
+
93
+ message_text = (
94
+ f"✅ Irrigation Complete!\n\n"
95
+ f"Crop: {crop_type}\n"
96
+ f"Location: {city}\n"
97
+ f"Duration: {estimated_time:.2f} {time_unit}\n\n"
98
+ "The motor has been turned off automatically."
99
+ )
100
+ send_whatsapp_message(USER_PHONE_NUMBER, message_text)
101
+ print(f"Irrigation complete message sent after {estimated_time:.2f} {time_unit}.")
102
+
103
+ # --- Flask Routes ---
104
+
105
+ @app.route('/')
106
+ def index():
107
+ """Renders the main input page."""
108
+ return render_template('index.html')
109
+
110
+ @app.route('/fetch_weather', methods=['GET'])
111
+ def fetch_weather():
112
+ """API endpoint to fetch weather data for the front-end."""
113
+ city = request.args.get('city')
114
+ if city:
115
+ temperature, humidity, description, pressure = get_weather(city)
116
+ if temperature is not None:
117
+ return jsonify({
118
+ 'description': description.capitalize(),
119
+ 'temperature': temperature,
120
+ 'humidity': humidity,
121
+ 'pressure': pressure
122
+ })
123
+ return jsonify(None), 404
124
+
125
+ @app.route('/predict', methods=['POST'])
126
+ def predict():
127
+ """Handles form submission, runs prediction, and shows results."""
128
+ if svm_poly_model is None:
129
+ return "Model not loaded, cannot perform prediction.", 500
130
+
131
+ global last_irrigation_params
132
+
133
+ # --- 1. Get Form Data ---
134
+ crop_type = request.form['crop_type']
135
+ soil_type = request.form['soil_type']
136
+ city = request.form['city']
137
+ motor_capacity = float(request.form['motor_capacity'])
138
+
139
+ # --- 2. Get Weather Data ---
140
+ temperature, humidity, description, pressure = get_weather(city)
141
+ if temperature is None: # Fallback if API fails
142
+ temperature, humidity, description, pressure = 32.0, 60, "Not Available", 1012
143
+ weather_condition = 'NORMAL'
144
+ else:
145
+ desc_lower = description.lower()
146
+ weather_condition = ('SUNNY' if 'clear' in desc_lower else
147
+ 'RAINY' if 'rain' in desc_lower else
148
+ 'WINDY' if 'wind' in desc_lower else
149
+ 'NORMAL')
150
+
151
+ # --- 3. Prepare Data for Model ---
152
+ user_data = pd.DataFrame({
153
+ 'CROP TYPE': [crop_type_mapping.get(crop_type)],
154
+ 'SOIL TYPE': [soil_type_mapping.get(soil_type)],
155
+ 'TEMPERATURE': [temperature],
156
+ 'WEATHER CONDITION': [weather_condition_mapping.get(weather_condition)]
157
+ })
158
+
159
+ # --- 4. Predict Water Requirement ---
160
+ water_requirement = svm_poly_model.predict(user_data)[0]
161
+
162
+ # --- 5. Calculate Motor On-Time ---
163
+ estimated_time_seconds = (water_requirement / motor_capacity) if motor_capacity > 0 else 0
164
+ if estimated_time_seconds < 120:
165
+ time_unit = "seconds"
166
+ display_time = estimated_time_seconds
167
+ else:
168
+ time_unit = "minutes"
169
+ display_time = estimated_time_seconds / 60
170
+
171
+ # --- 6. Store Parameters and Send WhatsApp ---
172
+ last_irrigation_params = {
173
+ "estimated_time_seconds": estimated_time_seconds,
174
+ "estimated_time_duration": display_time,
175
+ "time_unit": time_unit,
176
+ "crop_type": crop_type,
177
+ "city": city,
178
+ }
179
+
180
+ message_to_farmer = (
181
+ f"💧 Irrigation Prediction Ready 💧\n\n"
182
+ f"Crop: *{crop_type}*\n"
183
+ f"Location: *{city}*\n"
184
+ f"Weather: {description.capitalize()}, {temperature}°C\n"
185
+ f"Water Needed: *{water_requirement:.2f} m³/sq.m*\n"
186
+ f"Est. Motor Time: *{display_time:.2f} {time_unit}*\n\n"
187
+ "Reply *1* to START the motor.\n"
188
+ "Reply *0* to CANCEL."
189
+ )
190
+ send_whatsapp_message(USER_PHONE_NUMBER, message_to_farmer)
191
+
192
+ # --- 7. Create Gauge Charts for UI ---
193
+ water_gauge = go.Figure(go.Indicator(
194
+ mode="gauge+number", value=water_requirement, title={"text": "Water Req (m³/sq.m)"},
195
+ gauge={"axis": {"range": [None, 100]}, "bar": {"color": "royalblue"}}
196
+ ))
197
+ time_gauge = go.Figure(go.Indicator(
198
+ mode="gauge+number", value=round(display_time, 2), title={"text": f"Time ({time_unit})"},
199
+ gauge={"axis": {"range": [None, 60 if time_unit == 'seconds' else 120]}, "bar": {"color": "green"}}
200
+ ))
201
+
202
+ # --- 8. Render Results Page ---
203
+ return render_template('predict.html',
204
+ water_requirement=round(water_requirement, 2),
205
+ estimated_time_duration=round(display_time, 2),
206
+ time_unit=time_unit,
207
+ water_gauge=water_gauge.to_html(full_html=False),
208
+ time_gauge=time_gauge.to_html(full_html=False),
209
+ crop_type=crop_type, city=city)
210
+
211
+ @app.route('/twilio_reply', methods=['POST'])
212
+ def twilio_reply():
213
+ """Handles incoming WhatsApp replies to start or cancel the motor."""
214
+ global last_irrigation_params
215
+ message_body = request.values.get('Body', '').strip()
216
+ resp = MessagingResponse()
217
+
218
+ if message_body == "1":
219
+ if last_irrigation_params and 'estimated_time_seconds' in last_irrigation_params:
220
+ duration_sec = last_irrigation_params['estimated_time_seconds']
221
+
222
+ # Start a background timer thread
223
+ timer = threading.Timer(duration_sec, trigger_irrigation_complete)
224
+ timer.daemon = True
225
+ timer.start()
226
+
227
+ resp.message(f"✅ Motor started! Irrigation will run for {last_irrigation_params['estimated_time_duration']:.2f} {last_irrigation_params['time_unit']} and will stop automatically.")
228
+ else:
229
+ resp.message("❌ Error: No pending irrigation task found. Please submit a new prediction first.")
230
+ elif message_body == "0":
231
+ resp.message("👍 Motor start has been canceled.")
232
+ last_irrigation_params = {} # Clear params
233
+ else:
234
+ resp.message("Invalid reply. Please reply '1' to start the motor or '0' to cancel.")
235
+
236
+ return str(resp)
237
+
238
+ # Dummy endpoints called from front-end JS, actual logic is handled via WhatsApp reply
239
+ @app.route('/start_motor', methods=['POST'])
240
+ def start_motor():
241
+ return jsonify({"status": "motor_start_request_logged"})
242
+
243
+ @app.route('/irrigation_complete', methods=['POST'])
244
+ def irrigation_complete():
245
+ return jsonify({"status": "irrigation_complete_request_logged"})
246
+
247
+
248
+ if __name__ == "__main__":
249
+ # For development, use debug=True. For production, use a proper WSGI server like Gunicorn.
250
+ app.run(host="0.0.0.0", port=7860, debug=True)
requirements.txt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ gunicorn
2
+ pandas
3
+ numpy
4
+ matplotlib
5
+ joblib==1.4.2
6
+ plotly
7
+ scikit-learn==1.3.2
8
+ flask
9
+ requests
10
+ twilio
static/alarn_tune.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:22fdba64b8d71c233e8659af532b588d7ee216bb4567c73ca87a7c71df97f293
3
+ size 228700
static/images/img.png ADDED

Git LFS Details

  • SHA256: e51cd5569634250ca0e87253f17588a6d1e4ec834153dfa72aca395e7f87fafd
  • Pointer size: 131 Bytes
  • Size of remote file: 975 kB
svm_poly_model.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b60a7814f3e851178e888f92530d7a5dbdeb4a66557f43e0fe7e43d09335a276
3
+ size 124910
templates/index.html ADDED
@@ -0,0 +1,241 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Smart Irrigation Predictor</title>
7
+ <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
8
+ <style>
9
+ body {
10
+ background-color: #f4f8f4;
11
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
12
+ }
13
+ .navbar {
14
+ background-color: #28a745;
15
+ color: white;
16
+ }
17
+ .navbar-brand {
18
+ font-weight: bold;
19
+ }
20
+ .container {
21
+ margin-top: 30px;
22
+ margin-bottom: 30px;
23
+ }
24
+ .card {
25
+ border: none;
26
+ border-radius: 15px;
27
+ box-shadow: 0 4px 12px rgba(0,0,0,0.1);
28
+ }
29
+ .card-header {
30
+ background-color: #28a745;
31
+ color: white;
32
+ text-align: center;
33
+ font-weight: bold;
34
+ font-size: 1.5rem;
35
+ border-top-left-radius: 15px;
36
+ border-top-right-radius: 15px;
37
+ }
38
+ #weather_info {
39
+ background-color: #e9f5e9;
40
+ border: 1px solid #28a745;
41
+ padding: 15px;
42
+ border-radius: 5px;
43
+ min-height: 100px;
44
+ display: flex;
45
+ justify-content: center;
46
+ align-items: center;
47
+ }
48
+ .form-control {
49
+ border-color: #28a745;
50
+ background-color: #fafffa;
51
+ }
52
+ .btn-green {
53
+ background-color: #28a745;
54
+ color: white;
55
+ font-weight: bold;
56
+ width: 100%;
57
+ padding: 10px;
58
+ border-radius: 5px;
59
+ transition: background-color 0.3s;
60
+ }
61
+ .btn-green:hover {
62
+ background-color: #218838;
63
+ }
64
+ .hero-image {
65
+ width: 100%;
66
+ height: 100%;
67
+ object-fit: cover;
68
+ border-radius: 10px;
69
+ max-height: 450px; /* Control max height */
70
+ }
71
+
72
+ /* --- Full Page Loader Styles --- */
73
+ #page-loader {
74
+ position: fixed;
75
+ top: 0;
76
+ left: 0;
77
+ width: 100%;
78
+ height: 100%;
79
+ background-color: rgba(0, 0, 0, 0.5); /* Semi-transparent background */
80
+ z-index: 9999;
81
+ display: none; /* Hidden by default */
82
+ justify-content: center;
83
+ align-items: center;
84
+ }
85
+ .loader-spinner {
86
+ width: 4rem;
87
+ height: 4rem;
88
+ border-width: .4em; /* Makes spinner thicker */
89
+ }
90
+ </style>
91
+ </head>
92
+ <body>
93
+
94
+ <!-- Full Page Loader HTML -->
95
+ <div id="page-loader">
96
+ <div class="spinner-border text-light loader-spinner" role="status">
97
+ <span class="sr-only">Loading...</span>
98
+ </div>
99
+ </div>
100
+
101
+ <nav class="navbar navbar-dark">
102
+ <span class="navbar-brand mb-0 h1 mx-auto">🌱 Smart Irrigation Water Predictor</span>
103
+ </nav>
104
+
105
+ <div class="container">
106
+ <div class="card">
107
+ <div class="card-header">
108
+ Predict Irrigation Water Requirement
109
+ </div>
110
+ <div class="card-body p-lg-5">
111
+ <form action="/predict" method="POST" id="prediction-form">
112
+ <div class="row align-items-center">
113
+
114
+ <!-- Column 1: Form Inputs -->
115
+ <!-- order-2 makes it second on mobile, order-lg-1 makes it first on desktop -->
116
+ <div class="col-lg-6 order-2 order-lg-1">
117
+ <div class="form-group">
118
+ <label for="crop_type"><strong>Crop Type</strong></label>
119
+ <select class="form-control" id="crop_type" name="crop_type" required>
120
+ <option value="BANANA">Banana</option>
121
+ <option value="BEAN">Bean</option>
122
+ <!-- Add other crop options here -->
123
+ <option value="CABBAGE">Cabbage</option>
124
+ <option value="CITRUS">Citrus</option>
125
+ <option value="COTTON">Cotton</option>
126
+ <option value="MAIZE">Maize</option>
127
+ <option value="MELON">Melon</option>
128
+ <option value="MUSTARD">Mustard</option>
129
+ <option value="ONION">Onion</option>
130
+ <option value="OTHER">Other</option>
131
+ <option value="POTATO">Potato</option>
132
+ <option value="RICE">Rice</option>
133
+ <option value="SOYABEAN">Soyabean</option>
134
+ <option value="SUGARCANE">Sugarcane</option>
135
+ <option value="TOMATO">Tomato</option>
136
+ <option value="WHEAT">Wheat</option>
137
+ </select>
138
+ </div>
139
+
140
+ <div class="form-group">
141
+ <label for="soil_type"><strong>Soil Type</strong></label>
142
+ <select class="form-control" id="soil_type" name="soil_type" required>
143
+ <option value="DRY">Dry</option>
144
+ <option value="HUMID">Humid</option>
145
+ <option value="WET">Wet</option>
146
+ </select>
147
+ </div>
148
+
149
+ <div class="form-group">
150
+ <label for="city"><strong>City / Location</strong></label>
151
+ <input type="text" class="form-control" id="city" name="city" placeholder="Enter city for weather data" required>
152
+ </div>
153
+
154
+ <button type="button" class="btn btn-secondary btn-block" id="fetch-weather-btn" onclick="fetchWeather()">Fetch Weather Data</button>
155
+
156
+ <div id="weather_info" class="mt-3" style="display: none;">
157
+ <div id="weather_data"></div>
158
+ </div>
159
+
160
+ <div class="form-group mt-3">
161
+ <label for="motor_capacity"><strong>Motor Capacity (liters/sec)</strong></label>
162
+ <input type="text" class="form-control" id="motor_capacity" name="motor_capacity" required>
163
+ </div>
164
+
165
+ <button type="submit" class="btn btn-green mt-3" id="predict-btn">Predict Water Requirement</button>
166
+ </div>
167
+
168
+ <!-- Column 2: Image -->
169
+ <!-- order-1 makes it first on mobile, order-lg-2 makes it second on desktop -->
170
+ <div class="col-lg-6 order-1 order-lg-2 mb-4 mb-lg-0">
171
+ <img src="/static/images/img.png" alt="Smart Irrigation" class="hero-image">
172
+ </div>
173
+ </div>
174
+ </form>
175
+ </div>
176
+ </div>
177
+ </div>
178
+
179
+ <script>
180
+ function fetchWeather() {
181
+ // This function remains the same as before
182
+ const city = document.getElementById('city').value;
183
+ if (!city) {
184
+ alert('Please enter a city name first.');
185
+ return;
186
+ }
187
+
188
+ const weatherDiv = document.getElementById('weather_data');
189
+ const weatherInfoBox = document.getElementById('weather_info');
190
+ const fetchBtn = document.getElementById('fetch-weather-btn');
191
+
192
+ weatherInfoBox.style.display = 'flex';
193
+ weatherDiv.innerHTML = `<div class="spinner-border text-success" role="status"><span class="sr-only">Loading...</span></div>`;
194
+ fetchBtn.disabled = true;
195
+
196
+ fetch(`/fetch_weather?city=${city}`)
197
+ .then(response => {
198
+ if (!response.ok) throw new Error('City not found or server error.');
199
+ return response.json();
200
+ })
201
+ .then(data => {
202
+ if (data) {
203
+ weatherDiv.innerHTML = `
204
+ <div style="text-align: left; width: 100%;">
205
+ <strong>Weather:</strong> ${data.description}<br>
206
+ <strong>Temperature:</strong> ${data.temperature}°C<br>
207
+ <strong>Humidity:</strong> ${data.humidity}%<br>
208
+ <strong>Pressure:</strong> ${data.pressure} hPa
209
+ </div>
210
+ `;
211
+ } else {
212
+ weatherDiv.innerHTML = 'Could not retrieve weather data.';
213
+ }
214
+ })
215
+ .catch(error => {
216
+ console.error('Error fetching weather:', error);
217
+ weatherDiv.innerHTML = 'An error occurred. Please check the city name.';
218
+ })
219
+ .finally(() => {
220
+ fetchBtn.disabled = false;
221
+ });
222
+ }
223
+
224
+ // --- NEW: TRIGGER FULL PAGE LOADER ON FORM SUBMISSION ---
225
+ document.getElementById('prediction-form').addEventListener('submit', function(event) {
226
+ // Basic form validation check
227
+ if (!this.checkValidity()) {
228
+ // If form is invalid, let the browser handle the validation messages
229
+ return;
230
+ }
231
+
232
+ // Show the full-page loader
233
+ document.getElementById('page-loader').style.display = 'flex';
234
+
235
+ // Disable the predict button to prevent multiple submissions
236
+ document.getElementById('predict-btn').disabled = true;
237
+ });
238
+ </script>
239
+
240
+ </body>
241
+ </html>
templates/predict.html ADDED
@@ -0,0 +1,206 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Prediction Results</title>
7
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
8
+ <style>
9
+ body {
10
+ background-color: #f4f8f4;
11
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
12
+ }
13
+ .header {
14
+ background-color: #28a745;
15
+ color: white;
16
+ padding: 20px;
17
+ text-align: center;
18
+ font-weight: bold;
19
+ }
20
+ .container {
21
+ margin-top: 30px;
22
+ padding: 20px;
23
+ background-color: white;
24
+ border-radius: 15px;
25
+ box-shadow: 0 4px 12px rgba(0,0,0,0.1);
26
+ }
27
+ .result-label {
28
+ font-weight: bold;
29
+ color: #555;
30
+ }
31
+ .form-control[readonly] {
32
+ font-size: 1.2rem;
33
+ text-align: center;
34
+ font-weight: bold;
35
+ color: #28a745;
36
+ background-color: #e9f5e9;
37
+ border: 2px solid #28a745;
38
+ }
39
+ .gauge-container {
40
+ margin-top: 20px;
41
+ text-align: center;
42
+ }
43
+ .info-card {
44
+ background-color: #f8f9fa;
45
+ border: 1px solid #dee2e6;
46
+ border-radius: 10px;
47
+ padding: 25px;
48
+ margin-top: 40px;
49
+ text-align: center;
50
+ }
51
+ .info-card h4 {
52
+ color: #28a745;
53
+ font-weight: bold;
54
+ }
55
+ .btn-start-motor {
56
+ background-color: #28a745;
57
+ color: white;
58
+ font-size: 1.2rem;
59
+ padding: 12px 25px;
60
+ font-weight: bold;
61
+ border-radius: 8px;
62
+ transition: background-color 0.3s;
63
+ }
64
+ .btn-start-motor:hover {
65
+ background-color: #218838;
66
+ }
67
+ .camera-container {
68
+ margin-top: 20px;
69
+ text-align: center;
70
+ display: none;
71
+ }
72
+ .timer {
73
+ font-size: 2.5rem;
74
+ color: white;
75
+ background-color: #28a745;
76
+ font-weight: bold;
77
+ padding: 15px 30px;
78
+ border-radius: 10px;
79
+ display: inline-block;
80
+ margin-top: 15px;
81
+ }
82
+ video {
83
+ width: 100%;
84
+ max-width: 600px;
85
+ height: auto;
86
+ border-radius: 10px;
87
+ border: 2px solid #ddd;
88
+ }
89
+ .alert-message {
90
+ font-size: 1.5rem;
91
+ color: #dc3545;
92
+ font-weight: bold;
93
+ margin-top: 20px;
94
+ text-align: center;
95
+ }
96
+ </style>
97
+ </head>
98
+ <body>
99
+
100
+ <div class="header">
101
+ <h1>Water Requirement Prediction Results</h1>
102
+ </div>
103
+
104
+ <div class="container">
105
+ <div class="row">
106
+ <!-- Water Requirement -->
107
+ <div class="col-md-6 mb-4">
108
+ <label for="water_requirement" class="result-label">Predicted Water Requirement (m³/sq.m):</label>
109
+ <input type="text" class="form-control" id="water_requirement" value="{{ water_requirement }}" readonly>
110
+ <div class="gauge-container">
111
+ {{ water_gauge|safe }}
112
+ </div>
113
+ </div>
114
+
115
+ <!-- Motor On Time -->
116
+ <div class="col-md-6 mb-4">
117
+ <label for="estimated_time" class="result-label">Estimated Motor On-Time:</label>
118
+ <input type="text" class="form-control" id="estimated_time" value="{{ estimated_time_duration }} {{ time_unit }}" readonly>
119
+ <div class="gauge-container">
120
+ {{ time_gauge|safe }}
121
+ </div>
122
+ </div>
123
+ </div>
124
+
125
+ <!-- AI Monitoring Section -->
126
+ <div class="info-card">
127
+ <h4>Want to automatically inspect your irrigation process?</h4>
128
+ <p class="mt-3">Use our AI-driven Irrigation Monitoring to watch the process from your phone. You will receive a WhatsApp message to start the motor. Once started, you can monitor it live here.</p>
129
+ <button id="startMonitoring" class="btn btn-start-motor">Start AI Monitoring</button>
130
+ </div>
131
+
132
+ <div class="camera-container" id="cameraContainer">
133
+ <h4 class="text-center mb-3">Live Irrigation Feed</h4>
134
+ <video id="videoElement" autoplay playsinline></video>
135
+ <div class="timer" id="timer"></div>
136
+ </div>
137
+
138
+ <div class="alert-message" id="alertMessage"></div>
139
+ </div>
140
+
141
+ <script>
142
+ document.getElementById('startMonitoring').addEventListener('click', function() {
143
+ // Show camera container
144
+ document.getElementById('cameraContainer').style.display = 'block';
145
+
146
+ // Start video stream from camera
147
+ if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
148
+ navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } })
149
+ .then(function(stream) {
150
+ document.getElementById('videoElement').srcObject = stream;
151
+ })
152
+ .catch(function(err) {
153
+ console.log("Camera Error: " + err);
154
+ alert("Could not access the camera. Please ensure you have given permission.");
155
+ });
156
+ }
157
+
158
+ // The prompt for starting the motor is now handled via WhatsApp.
159
+ // The timer will start based on the reply from WhatsApp.
160
+ // This front-end logic is now primarily for viewing the stream.
161
+ // For demonstration purposes, we will simulate the timer start here after a delay.
162
+
163
+ // Calculate estimated time in seconds for the timer display
164
+ let estimatedTime = parseFloat("{{ estimated_time_duration }}");
165
+ let timeUnit = "{{ time_unit }}";
166
+ let estimatedTimeSeconds = timeUnit === "minutes" ? estimatedTime * 60 : estimatedTime;
167
+
168
+ let timerElem = document.getElementById('timer');
169
+ let timeRemaining = Math.floor(estimatedTimeSeconds);
170
+
171
+ // A message to inform user to check WhatsApp
172
+ alert("Check your WhatsApp to start the motor. The timer and monitoring will begin here once you confirm.");
173
+
174
+ // NOTE: The actual countdown should be triggered by a confirmation from the backend (e.g., via WebSocket).
175
+ // For this example, we'll just start it to demonstrate the UI.
176
+
177
+ let countdown = setInterval(function() {
178
+ if (timeRemaining <= 0) {
179
+ clearInterval(countdown);
180
+ timerElem.textContent = "Finished";
181
+ // Play beep sound
182
+ let beep = new Audio('/static/alarn_tune.mp3');
183
+ beep.play();
184
+ document.getElementById('alertMessage').textContent = "Irrigation time is over. Motor is off automatically.";
185
+ } else {
186
+ timerElem.textContent = timeRemaining + "s";
187
+ }
188
+ timeRemaining--;
189
+ }, 1000);
190
+
191
+ // Simulate sending a request to the backend (already done on page load via python)
192
+ fetch('/start_motor', {
193
+ method: 'POST',
194
+ headers: { 'Content-Type': 'application/json' },
195
+ body: JSON.stringify({
196
+ estimated_time_duration: estimatedTimeSeconds,
197
+ time_unit: "seconds"
198
+ })
199
+ })
200
+ .then(response => response.json())
201
+ .then(data => console.log("Monitoring started:", data))
202
+ .catch(error => console.error("Error starting monitoring:", error));
203
+ });
204
+ </script>
205
+ </body>
206
+ </html>