pranit144 commited on
Commit
429a26d
·
verified ·
1 Parent(s): 0ddf876

Upload 56 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .env +43 -0
  2. .gitattributes +1 -0
  3. __pycache__/app.cpython-39.pyc +0 -0
  4. __pycache__/daily_task_service.cpython-39.pyc +0 -0
  5. __pycache__/disease_detection_service.cpython-39.pyc +0 -0
  6. __pycache__/gemini_service.cpython-39.pyc +0 -0
  7. __pycache__/market_price_service.cpython-39.pyc +0 -0
  8. __pycache__/models.cpython-39.pyc +0 -0
  9. __pycache__/pdf_generator_service.cpython-39.pyc +0 -0
  10. __pycache__/telegram_service.cpython-39.pyc +0 -0
  11. __pycache__/twilio_service.cpython-39.pyc +0 -0
  12. __pycache__/weather_alert_service.cpython-39.pyc +0 -0
  13. __pycache__/weather_service.cpython-39.pyc +0 -0
  14. app.py +0 -0
  15. daily_task_service.py +865 -0
  16. disease_detection_service.py +356 -0
  17. farm_management.db +0 -0
  18. farms.db +0 -0
  19. gemini_service.py +382 -0
  20. generated_pdfs/yearly_plan_PRANIT Ravindra CHILBULE_20250906_140900.html +120 -0
  21. generated_pdfs/yearly_plan_PRANIT Ravindra CHILBULE_20250906_142024.html +120 -0
  22. generated_pdfs/yearly_plan_PRANIT Ravindra CHILBULE_20250906_142505.html +120 -0
  23. generated_pdfs/yearly_plan_PRANIT Ravindra CHILBULE_20250906_172353.html +120 -0
  24. generated_pdfs/yearly_plan_PRANIT Ravindra CHILBULE_20250906_172354.html +120 -0
  25. generated_pdfs/yearly_plan_PRANIT Ravindra CHILBULE_20250906_172448.html +120 -0
  26. generated_pdfs/yearly_plan_PRANIT Ravindra CHILBULE_20250906_172530.html +120 -0
  27. instance/farm_management.db +3 -0
  28. instance/farms.db +0 -0
  29. market_price_service.py +329 -0
  30. migrate_db.py +314 -0
  31. models.py +775 -0
  32. pdf_generator_service.py +428 -0
  33. requirements.txt +13 -0
  34. scripts/__pycache__/register_telegram_chat.cpython-39.pyc +0 -0
  35. scripts/get_telegram_chat_id.py +44 -0
  36. scripts/register_telegram_chat.py +71 -0
  37. telegram_service.py +645 -0
  38. templates/add_farm.html +630 -0
  39. templates/admin_dashboard.html +360 -0
  40. templates/admin_farmers.html +293 -0
  41. templates/admin_login.html +52 -0
  42. templates/admin_sms_logs.html +361 -0
  43. templates/base.html +157 -0
  44. templates/edit_farm.html +422 -0
  45. templates/error.html +44 -0
  46. templates/farm_details.html +252 -0
  47. templates/farmer_dashboard.html +1348 -0
  48. templates/farmer_dashboard_fixed.html +311 -0
  49. templates/farmer_login.html +60 -0
  50. templates/farmer_register.html +131 -0
.env ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Farm Management Portal Environment Variables
2
+
3
+ # Flask Configuration
4
+ SECRET_KEY=your-secret-key-change-this-in-production
5
+ FLASK_ENV=development
6
+
7
+ # Database Configuration
8
+ DATABASE_URL=sqlite:///farm_management.db
9
+
10
+ # Gemini AI Configuration
11
+ GEMINI_API_KEY=AIzaSyDlESeCJKKyvH1y7xTfjRNaqtQiorgFxjw
12
+
13
+ # Twilio Configuration
14
+ TWILIO_ACCOUNT_SID=ACe45f7038c5338a153d1126ca6d547c84
15
+ TWILIO_AUTH_TOKEN=48b9eea898885ef395d48edc74924340
16
+ TWILIO_PHONE_NUMBER=+17627287857
17
+ TWILIO_MESSAGING_SERVICE_SID=MG9be309a19ba005c801f36d56db5fe3ae
18
+
19
+ # Telegram Bot Configuration
20
+ TELEGRAM_BOT_TOKEN=8448417664:AAEcBQ8QBas8gdMyDNlTdO0s4YDhTLSrtO8
21
+
22
+ # Optional: quick mapping of farmer chat IDs for admin convenience.
23
+ # Format: FARMER_TELEGRAM_CHAT_IDS=<farmer_db_id>:<chat_id>,<farmer_db_id2>:<chat_id2>
24
+ # Example: FARMER_TELEGRAM_CHAT_IDS=3:5397241102,5:987654321
25
+ FARMER_TELEGRAM_CHAT_IDS=3:5397241102
26
+
27
+ # Optional global fallback chat id: if a farmer has no chat id configured the app will send messages to this id.
28
+ # Use carefully - messages will go to one global recipient.
29
+ GLOBAL_TELEGRAM_CHAT_ID=5397241102
30
+
31
+ # Weather API Configuration
32
+ OPENWEATHER_API_KEY=8ed5800e5a71e3fe1751e757a1d4e986
33
+ WEATHER_API_KEY=8ed5800e5a71e3fe1751e757a1d4e986
34
+
35
+ # Email Configuration (Optional)
36
+ MAIL_SERVER=smtp.gmail.com
37
+ MAIL_PORT=587
38
+ MAIL_USE_TLS=True
39
+ MAIL_USERNAME=your-email@gmail.com
40
+ MAIL_PASSWORD=your-email-password
41
+
42
+ # Redis Configuration (Optional)
43
+ REDIS_URL=redis://localhost:6379/0
.gitattributes CHANGED
@@ -33,3 +33,4 @@ 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
+ instance/farm_management.db filter=lfs diff=lfs merge=lfs -text
__pycache__/app.cpython-39.pyc ADDED
Binary file (37.9 kB). View file
 
__pycache__/daily_task_service.cpython-39.pyc ADDED
Binary file (19 kB). View file
 
__pycache__/disease_detection_service.cpython-39.pyc ADDED
Binary file (10.4 kB). View file
 
__pycache__/gemini_service.cpython-39.pyc ADDED
Binary file (16.3 kB). View file
 
__pycache__/market_price_service.cpython-39.pyc ADDED
Binary file (10.3 kB). View file
 
__pycache__/models.cpython-39.pyc ADDED
Binary file (24.2 kB). View file
 
__pycache__/pdf_generator_service.cpython-39.pyc ADDED
Binary file (12.7 kB). View file
 
__pycache__/telegram_service.cpython-39.pyc ADDED
Binary file (20.5 kB). View file
 
__pycache__/twilio_service.cpython-39.pyc ADDED
Binary file (5.41 kB). View file
 
__pycache__/weather_alert_service.cpython-39.pyc ADDED
Binary file (8.87 kB). View file
 
__pycache__/weather_service.cpython-39.pyc ADDED
Binary file (11.1 kB). View file
 
app.py ADDED
The diff for this file is too large to render. See raw diff
 
daily_task_service.py ADDED
@@ -0,0 +1,865 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Daily Task Management Service
3
+
4
+ This service generates daily farming tasks based on:
5
+ - Seasonal farming calendar
6
+ - Weather conditions
7
+ - Crop growth stages
8
+ - Soil conditions
9
+ - Previous task completion history
10
+ """
11
+
12
+ import logging
13
+ from datetime import datetime, date, timedelta
14
+ from typing import List, Dict, Optional
15
+ import json
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+ class DailyTaskService:
20
+ """Service for generating and managing daily farming tasks"""
21
+
22
+ def __init__(self, gemini_service=None, weather_service=None):
23
+ self.gemini_service = gemini_service
24
+ self.weather_service = weather_service
25
+
26
+ # Predefined task templates based on seasons and farming activities
27
+ self.task_templates = self._initialize_task_templates()
28
+
29
+ def _initialize_task_templates(self) -> Dict:
30
+ """Initialize predefined task templates for different farming activities"""
31
+
32
+ return {
33
+ # ==================== CROP FARMING TASKS ====================
34
+ 'irrigation': {
35
+ 'morning': {
36
+ 'title': 'Morning Irrigation Check',
37
+ 'description': 'Check soil moisture levels and irrigate crops if needed. Focus on areas that dried out overnight.',
38
+ 'duration': 30,
39
+ 'priority': 'high',
40
+ 'weather_dependent': True,
41
+ 'farm_types': ['crop', 'mixed']
42
+ },
43
+ 'evening': {
44
+ 'title': 'Evening Irrigation',
45
+ 'description': 'Water crops in the evening to minimize water loss due to evaporation.',
46
+ 'duration': 45,
47
+ 'priority': 'medium',
48
+ 'weather_dependent': True,
49
+ 'farm_types': ['crop', 'mixed']
50
+ }
51
+ },
52
+ 'fertilizing': {
53
+ 'organic': {
54
+ 'title': 'Apply Organic Fertilizer',
55
+ 'description': 'Apply compost or organic manure to crops. Focus on base application around plant roots.',
56
+ 'duration': 60,
57
+ 'priority': 'high',
58
+ 'weather_dependent': False,
59
+ 'farm_types': ['crop', 'mixed']
60
+ },
61
+ 'chemical': {
62
+ 'title': 'Chemical Fertilizer Application',
63
+ 'description': 'Apply chemical fertilizers as per soil test recommendations. Ensure proper timing and quantities.',
64
+ 'duration': 45,
65
+ 'priority': 'high',
66
+ 'weather_dependent': True,
67
+ 'farm_types': ['crop', 'mixed']
68
+ }
69
+ },
70
+ 'pest_control': {
71
+ 'inspection': {
72
+ 'title': 'Pest and Disease Inspection',
73
+ 'description': 'Inspect crops for signs of pests, diseases, or nutrient deficiencies. Take photos if issues found.',
74
+ 'duration': 30,
75
+ 'priority': 'high',
76
+ 'weather_dependent': False,
77
+ 'farm_types': ['crop', 'mixed']
78
+ },
79
+ 'spraying': {
80
+ 'title': 'Pest Control Spraying',
81
+ 'description': 'Apply pesticides or bio-pesticides as needed. Avoid spraying during windy or rainy conditions.',
82
+ 'duration': 60,
83
+ 'priority': 'medium',
84
+ 'weather_dependent': True,
85
+ 'farm_types': ['crop', 'mixed']
86
+ }
87
+ },
88
+ 'weeding': {
89
+ 'manual': {
90
+ 'title': 'Manual Weeding',
91
+ 'description': 'Remove weeds manually from crop rows. Focus on areas around young plants.',
92
+ 'duration': 90,
93
+ 'priority': 'medium',
94
+ 'weather_dependent': False,
95
+ 'farm_types': ['crop', 'mixed']
96
+ },
97
+ 'mechanical': {
98
+ 'title': 'Mechanical Weeding',
99
+ 'description': 'Use hoe or cultivator to remove weeds between crop rows.',
100
+ 'duration': 60,
101
+ 'priority': 'medium',
102
+ 'weather_dependent': False,
103
+ 'farm_types': ['crop', 'mixed']
104
+ }
105
+ },
106
+
107
+ # ==================== DAIRY FARMING TASKS ====================
108
+ 'milking': {
109
+ 'morning': {
110
+ 'title': 'Morning Milking',
111
+ 'description': 'Milk cows/buffaloes in the morning. Ensure clean milking equipment and proper hygiene.',
112
+ 'duration': 90,
113
+ 'priority': 'high',
114
+ 'weather_dependent': False,
115
+ 'farm_types': ['dairy', 'mixed']
116
+ },
117
+ 'evening': {
118
+ 'title': 'Evening Milking',
119
+ 'description': 'Evening milking session. Clean udders and equipment before milking.',
120
+ 'duration': 90,
121
+ 'priority': 'high',
122
+ 'weather_dependent': False,
123
+ 'farm_types': ['dairy', 'mixed']
124
+ }
125
+ },
126
+ 'dairy_feeding': {
127
+ 'concentrate': {
128
+ 'title': 'Provide Concentrate Feed',
129
+ 'description': 'Give concentrate feed to dairy animals. Adjust quantity based on milk production.',
130
+ 'duration': 45,
131
+ 'priority': 'high',
132
+ 'weather_dependent': False,
133
+ 'farm_types': ['dairy', 'livestock', 'mixed']
134
+ },
135
+ 'fodder': {
136
+ 'title': 'Fodder Distribution',
137
+ 'description': 'Distribute fresh green fodder or dry fodder to dairy animals.',
138
+ 'duration': 60,
139
+ 'priority': 'high',
140
+ 'weather_dependent': False,
141
+ 'farm_types': ['dairy', 'livestock', 'mixed']
142
+ }
143
+ },
144
+ 'dairy_health': {
145
+ 'health_check': {
146
+ 'title': 'Daily Health Monitoring',
147
+ 'description': 'Check animals for signs of illness, lameness, or distress. Monitor body temperature if needed.',
148
+ 'duration': 30,
149
+ 'priority': 'high',
150
+ 'weather_dependent': False,
151
+ 'farm_types': ['dairy', 'livestock', 'poultry', 'mixed']
152
+ },
153
+ 'udder_care': {
154
+ 'title': 'Udder Health Check',
155
+ 'description': 'Inspect udders for mastitis signs. Apply udder care products if necessary.',
156
+ 'duration': 20,
157
+ 'priority': 'medium',
158
+ 'weather_dependent': False,
159
+ 'farm_types': ['dairy', 'mixed']
160
+ }
161
+ },
162
+
163
+ # ==================== POULTRY FARMING TASKS ====================
164
+ 'poultry_feeding': {
165
+ 'morning_feed': {
166
+ 'title': 'Morning Poultry Feeding',
167
+ 'description': 'Provide morning feed to chickens/birds. Check feed quality and quantity.',
168
+ 'duration': 30,
169
+ 'priority': 'high',
170
+ 'weather_dependent': False,
171
+ 'farm_types': ['poultry', 'mixed']
172
+ },
173
+ 'evening_feed': {
174
+ 'title': 'Evening Poultry Feeding',
175
+ 'description': 'Give evening feed to poultry. Ensure clean water supply.',
176
+ 'duration': 30,
177
+ 'priority': 'high',
178
+ 'weather_dependent': False,
179
+ 'farm_types': ['poultry', 'mixed']
180
+ }
181
+ },
182
+ 'egg_collection': {
183
+ 'daily': {
184
+ 'title': 'Egg Collection',
185
+ 'description': 'Collect eggs from nesting boxes. Handle carefully and store properly.',
186
+ 'duration': 20,
187
+ 'priority': 'high',
188
+ 'weather_dependent': False,
189
+ 'farm_types': ['poultry', 'mixed']
190
+ }
191
+ },
192
+ 'poultry_health': {
193
+ 'flock_monitoring': {
194
+ 'title': 'Flock Health Monitoring',
195
+ 'description': 'Observe bird behavior, appetite, and signs of disease. Remove sick birds if necessary.',
196
+ 'duration': 25,
197
+ 'priority': 'high',
198
+ 'weather_dependent': False,
199
+ 'farm_types': ['poultry', 'mixed']
200
+ },
201
+ 'coop_cleaning': {
202
+ 'title': 'Coop Cleaning',
203
+ 'description': 'Clean poultry house, remove droppings, and ensure proper ventilation.',
204
+ 'duration': 45,
205
+ 'priority': 'medium',
206
+ 'weather_dependent': False,
207
+ 'farm_types': ['poultry', 'mixed']
208
+ }
209
+ },
210
+
211
+ # ==================== LIVESTOCK FARMING TASKS ====================
212
+ 'livestock_feeding': {
213
+ 'grazing': {
214
+ 'title': 'Livestock Grazing Management',
215
+ 'description': 'Move livestock to fresh grazing areas. Monitor grass quality and quantity.',
216
+ 'duration': 60,
217
+ 'priority': 'medium',
218
+ 'weather_dependent': True,
219
+ 'farm_types': ['livestock', 'mixed']
220
+ },
221
+ 'supplemental': {
222
+ 'title': 'Supplemental Feeding',
223
+ 'description': 'Provide additional feed supplements to livestock. Check mineral salt availability.',
224
+ 'duration': 45,
225
+ 'priority': 'medium',
226
+ 'weather_dependent': False,
227
+ 'farm_types': ['livestock', 'mixed']
228
+ }
229
+ },
230
+ 'livestock_care': {
231
+ 'water_supply': {
232
+ 'title': 'Water Supply Check',
233
+ 'description': 'Ensure clean drinking water is available for all animals. Clean water troughs.',
234
+ 'duration': 20,
235
+ 'priority': 'high',
236
+ 'weather_dependent': False,
237
+ 'farm_types': ['dairy', 'livestock', 'poultry', 'mixed']
238
+ },
239
+ 'shelter_maintenance': {
240
+ 'title': 'Shelter Maintenance',
241
+ 'description': 'Check and maintain animal shelters. Repair any damage and ensure proper ventilation.',
242
+ 'duration': 60,
243
+ 'priority': 'low',
244
+ 'weather_dependent': False,
245
+ 'farm_types': ['dairy', 'livestock', 'poultry', 'mixed']
246
+ }
247
+ },
248
+
249
+ # ==================== FISHERY TASKS ====================
250
+ 'fishery': {
251
+ 'water_quality': {
252
+ 'title': 'Water Quality Monitoring',
253
+ 'description': 'Check pond water quality, pH levels, and oxygen content. Monitor fish behavior.',
254
+ 'duration': 30,
255
+ 'priority': 'high',
256
+ 'weather_dependent': False,
257
+ 'farm_types': ['fishery', 'mixed']
258
+ },
259
+ 'fish_feeding': {
260
+ 'title': 'Fish Feeding',
261
+ 'description': 'Provide appropriate feed to fish. Adjust quantity based on fish size and water temperature.',
262
+ 'duration': 20,
263
+ 'priority': 'high',
264
+ 'weather_dependent': False,
265
+ 'farm_types': ['fishery', 'mixed']
266
+ },
267
+ 'pond_maintenance': {
268
+ 'title': 'Pond Maintenance',
269
+ 'description': 'Clean pond surroundings, check water level, and maintain aeration systems.',
270
+ 'duration': 45,
271
+ 'priority': 'medium',
272
+ 'weather_dependent': False,
273
+ 'farm_types': ['fishery', 'mixed']
274
+ }
275
+ },
276
+
277
+ # ==================== COMMON MAINTENANCE TASKS ====================
278
+ 'maintenance': {
279
+ 'equipment': {
280
+ 'title': 'Equipment Maintenance',
281
+ 'description': 'Check and maintain farming equipment. Clean, oil, and repair as needed.',
282
+ 'duration': 60,
283
+ 'priority': 'low',
284
+ 'weather_dependent': False,
285
+ 'farm_types': ['crop', 'dairy', 'livestock', 'poultry', 'fishery', 'mixed']
286
+ },
287
+ 'infrastructure': {
288
+ 'title': 'Farm Infrastructure Check',
289
+ 'description': 'Inspect farm infrastructure including fencing, storage, and water systems.',
290
+ 'duration': 45,
291
+ 'priority': 'medium',
292
+ 'weather_dependent': False,
293
+ 'farm_types': ['crop', 'dairy', 'livestock', 'poultry', 'fishery', 'mixed']
294
+ }
295
+ }
296
+ }
297
+
298
+ def generate_daily_tasks(self, farmer, target_date: date = None) -> List[Dict]:
299
+ """
300
+ Generate daily tasks for a farmer based on various factors
301
+
302
+ Args:
303
+ farmer: Farmer object
304
+ target_date: Date to generate tasks for (default: today)
305
+
306
+ Returns:
307
+ List of task dictionaries
308
+ """
309
+
310
+ if not target_date:
311
+ target_date = date.today()
312
+
313
+ tasks = []
314
+
315
+ try:
316
+ # Get farmer's farms
317
+ farms = getattr(farmer, 'farms', [])
318
+
319
+ for farm in farms:
320
+ # Generate tasks for each farm
321
+ farm_tasks = self._generate_farm_tasks(farmer, farm, target_date)
322
+ tasks.extend(farm_tasks)
323
+
324
+ # Generate general farmer tasks (not farm-specific)
325
+ general_tasks = self._generate_general_tasks(farmer, target_date)
326
+ tasks.extend(general_tasks)
327
+
328
+ # Sort tasks by priority and estimated duration
329
+ tasks = self._prioritize_tasks(tasks)
330
+
331
+ logger.info(f"Generated {len(tasks)} daily tasks for farmer {farmer.name} for {target_date}")
332
+ return tasks
333
+
334
+ except Exception as e:
335
+ logger.error(f"Error generating daily tasks for farmer {farmer.id}: {str(e)}")
336
+ return self._get_fallback_tasks(farmer, target_date)
337
+
338
+ def _generate_farm_tasks(self, farmer, farm, target_date: date) -> List[Dict]:
339
+ """Generate tasks specific to a farm based on its type"""
340
+
341
+ tasks = []
342
+
343
+ try:
344
+ # Get farm type (default to crop if not specified)
345
+ farm_type = getattr(farm, 'farm_type', 'crop')
346
+
347
+ # Get current season and month
348
+ month = target_date.month
349
+ season = self._get_season(month)
350
+
351
+ # Get weather conditions if available
352
+ weather_info = self._get_weather_info(farm, target_date)
353
+
354
+ # Generate tasks based on farm type
355
+ if farm_type == 'crop':
356
+ tasks.extend(self._get_crop_farming_tasks(farm, target_date, season, weather_info))
357
+ elif farm_type == 'dairy':
358
+ tasks.extend(self._get_dairy_farming_tasks(farm, target_date, weather_info))
359
+ elif farm_type == 'poultry':
360
+ tasks.extend(self._get_poultry_farming_tasks(farm, target_date, weather_info))
361
+ elif farm_type == 'livestock':
362
+ tasks.extend(self._get_livestock_farming_tasks(farm, target_date, weather_info))
363
+ elif farm_type == 'fishery':
364
+ tasks.extend(self._get_fishery_tasks(farm, target_date, weather_info))
365
+ elif farm_type == 'mixed':
366
+ # For mixed farming, generate tasks from all applicable types
367
+ tasks.extend(self._get_mixed_farming_tasks(farm, target_date, season, weather_info))
368
+
369
+ # Add routine maintenance tasks for all farm types
370
+ tasks.extend(self._get_routine_maintenance_tasks(farm, target_date))
371
+
372
+ except Exception as e:
373
+ logger.error(f"Error generating farm tasks for farm {farm.id}: {str(e)}")
374
+
375
+ return tasks
376
+
377
+ def _get_crop_farming_tasks(self, farm, target_date: date, season: str, weather_info: Dict) -> List[Dict]:
378
+ """Generate tasks specific to crop farming"""
379
+
380
+ tasks = []
381
+ month = target_date.month
382
+
383
+ # Get crops grown on this farm
384
+ crops = getattr(farm, 'get_crop_types', lambda: ['rice', 'wheat'])()
385
+
386
+ # Season-specific crop tasks
387
+ if season == 'kharif': # June-October
388
+ tasks.extend(self._get_kharif_tasks(farm, target_date, weather_info))
389
+ elif season == 'rabi': # November-April
390
+ tasks.extend(self._get_rabi_tasks(farm, target_date, weather_info))
391
+ else: # Summer - May
392
+ tasks.extend(self._get_summer_tasks(farm, target_date, weather_info))
393
+
394
+ # Daily crop management tasks
395
+ if not weather_info.get('rain_expected', False):
396
+ tasks.append(self._create_task_from_template('irrigation', 'morning', farm))
397
+
398
+ # Weekly pest inspection
399
+ if target_date.weekday() == 0: # Monday
400
+ tasks.append(self._create_task_from_template('pest_control', 'inspection', farm))
401
+
402
+ return tasks
403
+
404
+ def _get_dairy_farming_tasks(self, farm, target_date: date, weather_info: Dict) -> List[Dict]:
405
+ """Generate tasks specific to dairy farming"""
406
+
407
+ tasks = []
408
+
409
+ # Essential daily tasks for dairy farms
410
+ tasks.extend([
411
+ self._create_task_from_template('milking', 'morning', farm),
412
+ self._create_task_from_template('milking', 'evening', farm),
413
+ self._create_task_from_template('dairy_feeding', 'concentrate', farm),
414
+ self._create_task_from_template('dairy_feeding', 'fodder', farm),
415
+ self._create_task_from_template('dairy_health', 'health_check', farm),
416
+ self._create_task_from_template('livestock_care', 'water_supply', farm)
417
+ ])
418
+
419
+ # Weekly udder care
420
+ if target_date.weekday() in [1, 4]: # Tuesday and Friday
421
+ tasks.append(self._create_task_from_template('dairy_health', 'udder_care', farm))
422
+
423
+ return tasks
424
+
425
+ def _get_poultry_farming_tasks(self, farm, target_date: date, weather_info: Dict) -> List[Dict]:
426
+ """Generate tasks specific to poultry farming"""
427
+
428
+ tasks = []
429
+
430
+ # Daily poultry tasks
431
+ tasks.extend([
432
+ self._create_task_from_template('poultry_feeding', 'morning_feed', farm),
433
+ self._create_task_from_template('poultry_feeding', 'evening_feed', farm),
434
+ self._create_task_from_template('egg_collection', 'daily', farm),
435
+ self._create_task_from_template('poultry_health', 'flock_monitoring', farm),
436
+ self._create_task_from_template('livestock_care', 'water_supply', farm)
437
+ ])
438
+
439
+ # Weekly coop cleaning
440
+ if target_date.weekday() == 6: # Sunday
441
+ tasks.append(self._create_task_from_template('poultry_health', 'coop_cleaning', farm))
442
+
443
+ return tasks
444
+
445
+ def _get_livestock_farming_tasks(self, farm, target_date: date, weather_info: Dict) -> List[Dict]:
446
+ """Generate tasks specific to general livestock farming"""
447
+
448
+ tasks = []
449
+
450
+ # Daily livestock care
451
+ tasks.extend([
452
+ self._create_task_from_template('livestock_feeding', 'supplemental', farm),
453
+ self._create_task_from_template('dairy_health', 'health_check', farm),
454
+ self._create_task_from_template('livestock_care', 'water_supply', farm)
455
+ ])
456
+
457
+ # Weather-dependent grazing
458
+ if not weather_info.get('rain_expected', False) and weather_info.get('temperature', 25) < 35:
459
+ tasks.append(self._create_task_from_template('livestock_feeding', 'grazing', farm))
460
+
461
+ return tasks
462
+
463
+ def _get_fishery_tasks(self, farm, target_date: date, weather_info: Dict) -> List[Dict]:
464
+ """Generate tasks specific to fish farming"""
465
+
466
+ tasks = []
467
+
468
+ # Daily fish farming tasks
469
+ tasks.extend([
470
+ self._create_task_from_template('fishery', 'water_quality', farm),
471
+ self._create_task_from_template('fishery', 'fish_feeding', farm)
472
+ ])
473
+
474
+ # Regular pond maintenance
475
+ if target_date.weekday() in [2, 5]: # Wednesday and Saturday
476
+ tasks.append(self._create_task_from_template('fishery', 'pond_maintenance', farm))
477
+
478
+ return tasks
479
+
480
+ def _get_mixed_farming_tasks(self, farm, target_date: date, season: str, weather_info: Dict) -> List[Dict]:
481
+ """Generate tasks for mixed farming operations"""
482
+
483
+ tasks = []
484
+
485
+ # Get farm's livestock and crop information
486
+ livestock_types = getattr(farm, 'get_livestock_types', lambda: [])()
487
+ crop_types = getattr(farm, 'get_crop_types', lambda: [])()
488
+
489
+ # Add crop tasks if crops are grown
490
+ if crop_types:
491
+ tasks.extend(self._get_crop_farming_tasks(farm, target_date, season, weather_info))
492
+
493
+ # Add livestock tasks based on what animals are present
494
+ has_dairy = any('cow' in str(animal).lower() or 'buffalo' in str(animal).lower()
495
+ for animal in livestock_types)
496
+ has_poultry = any('chicken' in str(animal).lower() or 'hen' in str(animal).lower()
497
+ for animal in livestock_types)
498
+
499
+ if has_dairy:
500
+ # Add essential dairy tasks
501
+ tasks.extend([
502
+ self._create_task_from_template('milking', 'morning', farm),
503
+ self._create_task_from_template('milking', 'evening', farm),
504
+ self._create_task_from_template('dairy_feeding', 'concentrate', farm)
505
+ ])
506
+
507
+ if has_poultry:
508
+ # Add essential poultry tasks
509
+ tasks.extend([
510
+ self._create_task_from_template('poultry_feeding', 'morning_feed', farm),
511
+ self._create_task_from_template('egg_collection', 'daily', farm)
512
+ ])
513
+
514
+ if livestock_types:
515
+ # Add general livestock care
516
+ tasks.extend([
517
+ self._create_task_from_template('dairy_health', 'health_check', farm),
518
+ self._create_task_from_template('livestock_care', 'water_supply', farm)
519
+ ])
520
+
521
+ return tasks
522
+
523
+ def _get_routine_maintenance_tasks(self, farm, target_date: date) -> List[Dict]:
524
+ """Generate routine maintenance tasks applicable to all farm types"""
525
+
526
+ tasks = []
527
+ day_of_week = target_date.weekday()
528
+
529
+ # Weekly equipment maintenance
530
+ if day_of_week == 0: # Monday
531
+ tasks.append(self._create_task_from_template('maintenance', 'equipment', farm))
532
+
533
+ # Monthly infrastructure check
534
+ if target_date.day <= 7 and day_of_week == 0: # First Monday of month
535
+ tasks.append(self._create_task_from_template('maintenance', 'infrastructure', farm))
536
+
537
+ return tasks
538
+
539
+ def _create_task_from_template(self, category: str, task_type: str, farm) -> Dict:
540
+ """Create a task from predefined templates"""
541
+
542
+ template = self.task_templates.get(category, {}).get(task_type, {})
543
+
544
+ return {
545
+ 'task_type': category,
546
+ 'task_title': template.get('title', 'Farm Task'),
547
+ 'task_description': template.get('description', 'Complete this farming task.'),
548
+ 'priority': template.get('priority', 'medium'),
549
+ 'estimated_duration': template.get('duration', 30),
550
+ 'weather_dependent': template.get('weather_dependent', False),
551
+ 'farm_id': farm.id,
552
+ 'crop_specific': None
553
+ }
554
+
555
+ def _generate_general_tasks(self, farmer, target_date: date) -> List[Dict]:
556
+ """Generate general tasks not specific to any farm"""
557
+
558
+ tasks = []
559
+
560
+ # Market price checking
561
+ if target_date.weekday() in [0, 3]: # Monday and Thursday
562
+ tasks.append({
563
+ 'task_type': 'market_research',
564
+ 'task_title': 'Check Market Prices',
565
+ 'task_description': 'Check current market prices for your crops and plan selling strategy accordingly.',
566
+ 'priority': 'medium',
567
+ 'estimated_duration': 15,
568
+ 'weather_dependent': False,
569
+ 'farm_id': None,
570
+ 'crop_specific': None
571
+ })
572
+
573
+ # Weather planning
574
+ tasks.append({
575
+ 'task_type': 'planning',
576
+ 'task_title': 'Review Weather Forecast',
577
+ 'task_description': 'Check 7-day weather forecast and plan farming activities accordingly.',
578
+ 'priority': 'high',
579
+ 'estimated_duration': 10,
580
+ 'weather_dependent': False,
581
+ 'farm_id': None,
582
+ 'crop_specific': None
583
+ })
584
+
585
+ return tasks
586
+
587
+ def _get_season(self, month: int) -> str:
588
+ """Determine agricultural season based on month"""
589
+
590
+ if month in [6, 7, 8, 9, 10]: # June to October
591
+ return 'kharif'
592
+ elif month in [11, 12, 1, 2, 3, 4]: # November to April
593
+ return 'rabi'
594
+ else: # May
595
+ return 'summer'
596
+
597
+ def _get_kharif_tasks(self, farm, target_date: date, weather_info: Dict) -> List[Dict]:
598
+ """Generate tasks for Kharif season"""
599
+
600
+ tasks = []
601
+ month = target_date.month
602
+
603
+ # Get crops grown on this farm
604
+ crops = farm.get_crop_types() if hasattr(farm, 'get_crop_types') else ['rice', 'cotton']
605
+
606
+ if month == 6: # June - Sowing season
607
+ tasks.append({
608
+ 'task_type': 'sowing',
609
+ 'task_title': 'Kharif Crop Sowing',
610
+ 'task_description': f'Sow {", ".join(crops)} seeds according to recommended spacing and depth.',
611
+ 'priority': 'high',
612
+ 'estimated_duration': 120,
613
+ 'weather_dependent': True,
614
+ 'farm_id': farm.id,
615
+ 'crop_specific': crops[0] if crops else None
616
+ })
617
+
618
+ elif month in [7, 8]: # July-August - Growth period
619
+ tasks.extend([
620
+ {
621
+ 'task_type': 'weeding',
622
+ 'task_title': 'Weed Control',
623
+ 'task_description': 'Remove weeds from crop fields to prevent competition for nutrients.',
624
+ 'priority': 'high',
625
+ 'estimated_duration': 90,
626
+ 'weather_dependent': False,
627
+ 'farm_id': farm.id,
628
+ 'crop_specific': None
629
+ },
630
+ {
631
+ 'task_type': 'fertilizing',
632
+ 'task_title': 'Top Dressing Fertilizer',
633
+ 'task_description': 'Apply nitrogen fertilizer for healthy crop growth.',
634
+ 'priority': 'high',
635
+ 'estimated_duration': 60,
636
+ 'weather_dependent': True,
637
+ 'farm_id': farm.id,
638
+ 'crop_specific': None
639
+ }
640
+ ])
641
+
642
+ elif month in [9, 10]: # September-October - Maturity and harvesting
643
+ tasks.append({
644
+ 'task_type': 'harvesting',
645
+ 'task_title': 'Kharif Crop Harvesting',
646
+ 'task_description': f'Harvest mature {", ".join(crops)} crops at optimal moisture content.',
647
+ 'priority': 'high',
648
+ 'estimated_duration': 180,
649
+ 'weather_dependent': True,
650
+ 'farm_id': farm.id,
651
+ 'crop_specific': crops[0] if crops else None
652
+ })
653
+
654
+ return tasks
655
+
656
+ def _get_rabi_tasks(self, farm, target_date: date, weather_info: Dict) -> List[Dict]:
657
+ """Generate tasks for Rabi season"""
658
+
659
+ tasks = []
660
+ month = target_date.month
661
+
662
+ crops = farm.get_crop_types() if hasattr(farm, 'get_crop_types') else ['wheat', 'mustard']
663
+
664
+ if month in [11, 12]: # November-December - Sowing
665
+ tasks.append({
666
+ 'task_type': 'sowing',
667
+ 'task_title': 'Rabi Crop Sowing',
668
+ 'task_description': f'Sow {", ".join(crops)} with proper seed treatment and spacing.',
669
+ 'priority': 'high',
670
+ 'estimated_duration': 120,
671
+ 'weather_dependent': False,
672
+ 'farm_id': farm.id,
673
+ 'crop_specific': crops[0] if crops else None
674
+ })
675
+
676
+ elif month in [1, 2]: # January-February - Growth
677
+ tasks.extend([
678
+ {
679
+ 'task_type': 'irrigation',
680
+ 'task_title': 'Winter Irrigation',
681
+ 'task_description': 'Provide adequate irrigation to rabi crops during dry winter period.',
682
+ 'priority': 'high',
683
+ 'estimated_duration': 45,
684
+ 'weather_dependent': False,
685
+ 'farm_id': farm.id,
686
+ 'crop_specific': None
687
+ },
688
+ {
689
+ 'task_type': 'pest_control',
690
+ 'task_title': 'Pest Monitoring',
691
+ 'task_description': 'Monitor for aphids and other winter pests. Apply control measures if needed.',
692
+ 'priority': 'medium',
693
+ 'estimated_duration': 30,
694
+ 'weather_dependent': False,
695
+ 'farm_id': farm.id,
696
+ 'crop_specific': None
697
+ }
698
+ ])
699
+
700
+ elif month in [3, 4]: # March-April - Maturity and harvesting
701
+ tasks.append({
702
+ 'task_type': 'harvesting',
703
+ 'task_title': 'Rabi Crop Harvesting',
704
+ 'task_description': f'Harvest {", ".join(crops)} when crops reach physiological maturity.',
705
+ 'priority': 'high',
706
+ 'estimated_duration': 180,
707
+ 'weather_dependent': True,
708
+ 'farm_id': farm.id,
709
+ 'crop_specific': crops[0] if crops else None
710
+ })
711
+
712
+ return tasks
713
+
714
+ def _get_summer_tasks(self, farm, target_date: date, weather_info: Dict) -> List[Dict]:
715
+ """Generate tasks for Summer season"""
716
+
717
+ tasks = []
718
+
719
+ # Summer preparation tasks
720
+ tasks.extend([
721
+ {
722
+ 'task_type': 'soil_care',
723
+ 'task_title': 'Summer Plowing',
724
+ 'task_description': 'Deep plowing to break soil crust and improve water infiltration.',
725
+ 'priority': 'medium',
726
+ 'estimated_duration': 180,
727
+ 'weather_dependent': False,
728
+ 'farm_id': farm.id,
729
+ 'crop_specific': None
730
+ },
731
+ {
732
+ 'task_type': 'maintenance',
733
+ 'task_title': 'Equipment Preparation',
734
+ 'task_description': 'Service and repair farming equipment for upcoming Kharif season.',
735
+ 'priority': 'medium',
736
+ 'estimated_duration': 120,
737
+ 'weather_dependent': False,
738
+ 'farm_id': farm.id,
739
+ 'crop_specific': None
740
+ }
741
+ ])
742
+
743
+ return tasks
744
+
745
+ def _get_routine_tasks(self, farm, target_date: date, weather_info: Dict) -> List[Dict]:
746
+ """Generate routine daily tasks"""
747
+
748
+ tasks = []
749
+ day_of_week = target_date.weekday()
750
+
751
+ # Daily irrigation check
752
+ if not weather_info.get('rain_expected', False):
753
+ tasks.append({
754
+ 'task_type': 'irrigation',
755
+ 'task_title': 'Daily Irrigation Check',
756
+ 'task_description': 'Check soil moisture and irrigate crops if needed.',
757
+ 'priority': 'high',
758
+ 'estimated_duration': 30,
759
+ 'weather_dependent': True,
760
+ 'farm_id': farm.id,
761
+ 'crop_specific': None
762
+ })
763
+
764
+ # Weekly pest inspection
765
+ if day_of_week == 0: # Monday
766
+ tasks.append({
767
+ 'task_type': 'pest_control',
768
+ 'task_title': 'Weekly Pest Inspection',
769
+ 'task_description': 'Thoroughly inspect all crops for pests, diseases, and nutrient deficiencies.',
770
+ 'priority': 'high',
771
+ 'estimated_duration': 45,
772
+ 'weather_dependent': False,
773
+ 'farm_id': farm.id,
774
+ 'crop_specific': None
775
+ })
776
+
777
+ return tasks
778
+
779
+ def _get_weather_info(self, farm, target_date: date) -> Dict:
780
+ """Get weather information for task planning"""
781
+
782
+ try:
783
+ if self.weather_service and hasattr(farm, 'latitude') and hasattr(farm, 'longitude'):
784
+ if farm.latitude and farm.longitude:
785
+ weather = self.weather_service.get_weather_data(farm.latitude, farm.longitude)
786
+ return {
787
+ 'temperature': weather.get('temperature', 25),
788
+ 'humidity': weather.get('humidity', 60),
789
+ 'rain_expected': weather.get('rainfall', 0) > 0,
790
+ 'wind_speed': weather.get('wind_speed', 5)
791
+ }
792
+ except Exception as e:
793
+ logger.error(f"Error getting weather info: {str(e)}")
794
+
795
+ return {'temperature': 25, 'humidity': 60, 'rain_expected': False, 'wind_speed': 5}
796
+
797
+ def _prioritize_tasks(self, tasks: List[Dict]) -> List[Dict]:
798
+ """Sort tasks by priority and other factors"""
799
+
800
+ priority_order = {'high': 3, 'medium': 2, 'low': 1}
801
+
802
+ return sorted(tasks, key=lambda x: (
803
+ priority_order.get(x.get('priority', 'medium'), 2),
804
+ -x.get('estimated_duration', 60) # Shorter tasks first within same priority
805
+ ), reverse=True)
806
+
807
+ def _get_fallback_tasks(self, farmer, target_date: date) -> List[Dict]:
808
+ """Provide basic fallback tasks when AI generation fails"""
809
+
810
+ return [
811
+ {
812
+ 'task_type': 'inspection',
813
+ 'task_title': 'Daily Farm Inspection',
814
+ 'task_description': 'Walk through your farm and inspect crops for any issues.',
815
+ 'priority': 'high',
816
+ 'estimated_duration': 30,
817
+ 'weather_dependent': False,
818
+ 'farm_id': None,
819
+ 'crop_specific': None
820
+ },
821
+ {
822
+ 'task_type': 'planning',
823
+ 'task_title': 'Review Daily Plan',
824
+ 'task_description': 'Review your farming activities and plan for tomorrow.',
825
+ 'priority': 'medium',
826
+ 'estimated_duration': 15,
827
+ 'weather_dependent': False,
828
+ 'farm_id': None,
829
+ 'crop_specific': None
830
+ }
831
+ ]
832
+
833
+ def format_task_for_telegram(self, task: Dict, farmer_name: str) -> str:
834
+ """Format a task for Telegram notification"""
835
+
836
+ priority_emoji = {'high': '🔴', 'medium': '🟡', 'low': '🟢'}
837
+ type_emoji = {
838
+ 'irrigation': '💧',
839
+ 'fertilizing': '🌱',
840
+ 'pest_control': '🛡️',
841
+ 'weeding': '🌾',
842
+ 'sowing': '🌱',
843
+ 'harvesting': '🌽',
844
+ 'soil_care': '🌍',
845
+ 'maintenance': '🔧',
846
+ 'inspection': '👀',
847
+ 'planning': '📋',
848
+ 'market_research': '💰'
849
+ }
850
+
851
+ emoji = type_emoji.get(task.get('task_type', ''), '📋')
852
+ priority = priority_emoji.get(task.get('priority', 'medium'), '🟡')
853
+
854
+ message = f"{emoji} <b>{task.get('task_title', 'Farm Task')}</b>\n"
855
+ message += f"{priority} Priority: {task.get('priority', 'medium').title()}\n"
856
+ message += f"⏱️ Duration: {task.get('estimated_duration', 30)} minutes\n\n"
857
+ message += f"📝 <b>Description:</b>\n{task.get('task_description', 'Complete this farming task.')}\n\n"
858
+
859
+ if task.get('crop_specific'):
860
+ message += f"🌾 <b>Crop:</b> {task.get('crop_specific')}\n"
861
+
862
+ if task.get('weather_dependent'):
863
+ message += f"🌤️ <i>Weather dependent task</i>\n"
864
+
865
+ return message
disease_detection_service.py ADDED
@@ -0,0 +1,356 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ from datetime import datetime, timedelta
4
+ from typing import Dict, List, Optional
5
+ import logging
6
+ try:
7
+ from PIL import Image
8
+ except ImportError:
9
+ Image = None
10
+ try:
11
+ import google.generativeai as genai
12
+ except ImportError:
13
+ genai = None
14
+ from models import DiseaseDetection, Farm, db
15
+
16
+ # Configure logging
17
+ logging.basicConfig(level=logging.INFO)
18
+ logger = logging.getLogger(__name__)
19
+
20
+ class DiseaseDetectionService:
21
+ """Service for AI-powered crop disease detection and treatment recommendations"""
22
+
23
+ def __init__(self, gemini_api_key: str):
24
+ self.gemini_api_key = gemini_api_key
25
+ genai.configure(api_key=gemini_api_key)
26
+ self.model = genai.GenerativeModel('gemini-2.0-flash')
27
+
28
+ # Common diseases database for quick reference
29
+ self.disease_database = {
30
+ 'rice': {
31
+ 'blast': {
32
+ 'symptoms': 'Diamond-shaped lesions on leaves, brown borders with gray centers',
33
+ 'treatment': 'Apply fungicides like Tricyclazole, improve drainage',
34
+ 'prevention': 'Use resistant varieties, avoid excessive nitrogen'
35
+ },
36
+ 'bacterial_blight': {
37
+ 'symptoms': 'Water-soaked lesions that turn yellow then brown',
38
+ 'treatment': 'Copper-based bactericides, remove infected plants',
39
+ 'prevention': 'Use certified seeds, avoid overhead irrigation'
40
+ }
41
+ },
42
+ 'wheat': {
43
+ 'rust': {
44
+ 'symptoms': 'Orange-red pustules on leaves and stems',
45
+ 'treatment': 'Fungicides like Propiconazole, early application',
46
+ 'prevention': 'Resistant varieties, proper spacing'
47
+ },
48
+ 'powdery_mildew': {
49
+ 'symptoms': 'White powdery growth on leaves',
50
+ 'treatment': 'Sulfur-based fungicides, improve air circulation',
51
+ 'prevention': 'Avoid dense planting, reduce humidity'
52
+ }
53
+ },
54
+ 'tomato': {
55
+ 'late_blight': {
56
+ 'symptoms': 'Dark green water-soaked spots, white mold on leaf undersides',
57
+ 'treatment': 'Copper fungicides, remove infected parts',
58
+ 'prevention': 'Improve ventilation, avoid overhead watering'
59
+ },
60
+ 'early_blight': {
61
+ 'symptoms': 'Concentric rings on leaves, starts from bottom',
62
+ 'treatment': 'Fungicides, remove lower leaves',
63
+ 'prevention': 'Crop rotation, proper spacing'
64
+ }
65
+ }
66
+ }
67
+
68
+ def analyze_disease_from_text(self, farm_id: int, crop_name: str, symptoms: str) -> Dict:
69
+ """Analyze disease based on text symptoms description"""
70
+ try:
71
+ farm = Farm.query.get(farm_id)
72
+ if not farm:
73
+ return {'error': 'Farm not found'}
74
+
75
+ # Create prompt for Gemini AI
76
+ prompt = self._create_disease_analysis_prompt(crop_name, symptoms)
77
+
78
+ # Get AI analysis
79
+ response = self.model.generate_content(prompt)
80
+ ai_analysis = self._parse_disease_response(response.text)
81
+
82
+ # Save to database
83
+ detection = DiseaseDetection(
84
+ farm_id=farm_id,
85
+ crop_name=crop_name,
86
+ disease_name=ai_analysis.get('disease_name', 'Unknown'),
87
+ confidence_score=ai_analysis.get('confidence', 0.0),
88
+ symptoms=symptoms,
89
+ treatment=ai_analysis.get('treatment', ''),
90
+ prevention=ai_analysis.get('prevention', ''),
91
+ ai_analysis=json.dumps(ai_analysis),
92
+ severity=ai_analysis.get('severity', 'unknown')
93
+ )
94
+
95
+ db.session.add(detection)
96
+ db.session.commit()
97
+
98
+ return {
99
+ 'success': True,
100
+ 'detection_id': detection.id,
101
+ 'analysis': ai_analysis
102
+ }
103
+
104
+ except Exception as e:
105
+ logger.error(f"Error analyzing disease: {str(e)}")
106
+ return {'error': f'Analysis failed: {str(e)}'}
107
+
108
+ def analyze_disease_from_image(self, farm_id: int, crop_name: str, image_path: str, symptoms: str = "") -> Dict:
109
+ """Analyze disease from uploaded image"""
110
+ try:
111
+ farm = Farm.query.get(farm_id)
112
+ if not farm:
113
+ return {'error': 'Farm not found'}
114
+
115
+ # Verify image exists
116
+ if not os.path.exists(image_path):
117
+ return {'error': 'Image file not found'}
118
+
119
+ # Create prompt for image analysis
120
+ prompt = self._create_image_analysis_prompt(crop_name, symptoms)
121
+
122
+ # Load and process image
123
+ image = Image.open(image_path)
124
+
125
+ # Get AI analysis with image
126
+ response = self.model.generate_content([prompt, image])
127
+ ai_analysis = self._parse_disease_response(response.text)
128
+
129
+ # Save to database
130
+ detection = DiseaseDetection(
131
+ farm_id=farm_id,
132
+ crop_name=crop_name,
133
+ disease_name=ai_analysis.get('disease_name', 'Unknown'),
134
+ confidence_score=ai_analysis.get('confidence', 0.0),
135
+ symptoms=symptoms,
136
+ treatment=ai_analysis.get('treatment', ''),
137
+ prevention=ai_analysis.get('prevention', ''),
138
+ image_path=image_path,
139
+ ai_analysis=json.dumps(ai_analysis),
140
+ severity=ai_analysis.get('severity', 'unknown')
141
+ )
142
+
143
+ db.session.add(detection)
144
+ db.session.commit()
145
+
146
+ return {
147
+ 'success': True,
148
+ 'detection_id': detection.id,
149
+ 'analysis': ai_analysis
150
+ }
151
+
152
+ except Exception as e:
153
+ logger.error(f"Error analyzing disease from image: {str(e)}")
154
+ return {'error': f'Image analysis failed: {str(e)}'}
155
+
156
+ def _create_disease_analysis_prompt(self, crop_name: str, symptoms: str) -> str:
157
+ """Create prompt for disease analysis"""
158
+ return f"""
159
+ You are an expert plant pathologist. Analyze the following crop disease symptoms and provide a detailed diagnosis.
160
+
161
+ CROP: {crop_name}
162
+ SYMPTOMS: {symptoms}
163
+
164
+ Please provide your analysis in the following JSON format:
165
+ {{
166
+ "disease_name": "Most likely disease name",
167
+ "confidence": 0.85,
168
+ "severity": "mild/moderate/severe",
169
+ "symptoms_analysis": "Detailed analysis of symptoms",
170
+ "treatment": "Immediate treatment recommendations",
171
+ "prevention": "Future prevention measures",
172
+ "additional_info": "Any additional relevant information",
173
+ "urgency": "low/medium/high"
174
+ }}
175
+
176
+ Consider:
177
+ 1. Common diseases for this crop
178
+ 2. Seasonal factors
179
+ 3. Environmental conditions
180
+ 4. Treatment urgency
181
+ 5. Cost-effective solutions for farmers
182
+
183
+ Provide practical, actionable advice that farmers can easily implement.
184
+ """
185
+
186
+ def _create_image_analysis_prompt(self, crop_name: str, symptoms: str = "") -> str:
187
+ """Create prompt for image-based disease analysis"""
188
+ base_prompt = f"""
189
+ You are an expert plant pathologist. Analyze this image of {crop_name} crop for signs of disease or pest damage.
190
+
191
+ CROP: {crop_name}
192
+ """
193
+
194
+ if symptoms:
195
+ base_prompt += f"REPORTED SYMPTOMS: {symptoms}\n"
196
+
197
+ base_prompt += """
198
+ Please examine the image carefully and provide your analysis in the following JSON format:
199
+ {
200
+ "disease_name": "Identified disease or pest issue",
201
+ "confidence": 0.85,
202
+ "severity": "mild/moderate/severe",
203
+ "visual_symptoms": "What you observe in the image",
204
+ "treatment": "Immediate treatment recommendations",
205
+ "prevention": "Future prevention measures",
206
+ "affected_area": "Percentage of plant affected",
207
+ "urgency": "low/medium/high",
208
+ "additional_observations": "Any other relevant findings"
209
+ }
210
+
211
+ Look for:
212
+ 1. Leaf discoloration or spots
213
+ 2. Wilting or deformation
214
+ 3. Pest damage
215
+ 4. Fungal growth
216
+ 5. Nutrient deficiencies
217
+ 6. Environmental stress signs
218
+
219
+ Provide practical, cost-effective treatment recommendations suitable for farmers.
220
+ """
221
+ return base_prompt
222
+
223
+ def _parse_disease_response(self, response_text: str) -> Dict:
224
+ """Parse AI response and extract disease information"""
225
+ try:
226
+ # Try to extract JSON from response
227
+ start_idx = response_text.find('{')
228
+ end_idx = response_text.rfind('}') + 1
229
+
230
+ if start_idx != -1 and end_idx != -1:
231
+ json_text = response_text[start_idx:end_idx]
232
+ analysis = json.loads(json_text)
233
+
234
+ # Ensure required fields
235
+ analysis.setdefault('disease_name', 'Unknown Disease')
236
+ analysis.setdefault('confidence', 0.5)
237
+ analysis.setdefault('severity', 'moderate')
238
+ analysis.setdefault('treatment', 'Consult agricultural expert')
239
+ analysis.setdefault('prevention', 'Maintain good crop hygiene')
240
+ analysis.setdefault('urgency', 'medium')
241
+
242
+ return analysis
243
+ else:
244
+ # Manual parsing if JSON not found
245
+ return self._manual_parse_response(response_text)
246
+
247
+ except json.JSONDecodeError:
248
+ return self._manual_parse_response(response_text)
249
+ except Exception as e:
250
+ logger.error(f"Error parsing disease response: {str(e)}")
251
+ return self._get_fallback_analysis()
252
+
253
+ def _manual_parse_response(self, response_text: str) -> Dict:
254
+ """Manually parse response if JSON parsing fails"""
255
+ analysis = {
256
+ 'disease_name': 'Unknown Disease',
257
+ 'confidence': 0.5,
258
+ 'severity': 'moderate',
259
+ 'treatment': 'Consult agricultural expert',
260
+ 'prevention': 'Maintain good crop hygiene',
261
+ 'urgency': 'medium'
262
+ }
263
+
264
+ lines = response_text.lower().split('\n')
265
+
266
+ for line in lines:
267
+ if 'disease' in line and 'name' in line:
268
+ analysis['disease_name'] = line.split(':')[-1].strip()
269
+ elif 'treatment' in line:
270
+ analysis['treatment'] = line.split(':')[-1].strip()
271
+ elif 'prevention' in line:
272
+ analysis['prevention'] = line.split(':')[-1].strip()
273
+ elif 'severe' in line:
274
+ analysis['severity'] = 'severe'
275
+ elif 'mild' in line:
276
+ analysis['severity'] = 'mild'
277
+
278
+ return analysis
279
+
280
+ def _get_fallback_analysis(self) -> Dict:
281
+ """Provide fallback analysis when AI fails"""
282
+ return {
283
+ 'disease_name': 'Analysis Failed',
284
+ 'confidence': 0.0,
285
+ 'severity': 'unknown',
286
+ 'treatment': 'Please consult with a local agricultural expert or extension officer',
287
+ 'prevention': 'Maintain good crop hygiene and monitoring practices',
288
+ 'urgency': 'medium',
289
+ 'error': 'AI analysis unavailable'
290
+ }
291
+
292
+ def get_disease_history(self, farm_id: int, days: int = 30) -> List[DiseaseDetection]:
293
+ """Get disease detection history for a farm"""
294
+ from_date = datetime.now() - timedelta(days=days)
295
+
296
+ return DiseaseDetection.query.filter(
297
+ DiseaseDetection.farm_id == farm_id,
298
+ DiseaseDetection.detected_at >= from_date
299
+ ).order_by(DiseaseDetection.detected_at.desc()).all()
300
+
301
+ def update_treatment_status(self, detection_id: int, status: str, notes: str = "") -> bool:
302
+ """Update treatment status for a disease detection"""
303
+ try:
304
+ detection = DiseaseDetection.query.get(detection_id)
305
+ if not detection:
306
+ return False
307
+
308
+ detection.status = status
309
+ if status == 'resolved':
310
+ detection.resolved_at = datetime.now()
311
+
312
+ db.session.commit()
313
+ return True
314
+
315
+ except Exception as e:
316
+ logger.error(f"Error updating treatment status: {str(e)}")
317
+ return False
318
+
319
+ def get_preventive_recommendations(self, crop_name: str, season: str = "") -> Dict:
320
+ """Get preventive recommendations for common diseases"""
321
+ crop_lower = crop_name.lower()
322
+
323
+ if crop_lower in self.disease_database:
324
+ diseases = self.disease_database[crop_lower]
325
+ recommendations = []
326
+
327
+ for disease, info in diseases.items():
328
+ recommendations.append({
329
+ 'disease': disease.replace('_', ' ').title(),
330
+ 'prevention': info['prevention'],
331
+ 'early_symptoms': info['symptoms']
332
+ })
333
+
334
+ return {
335
+ 'crop': crop_name,
336
+ 'recommendations': recommendations,
337
+ 'general_tips': [
338
+ 'Regular field monitoring',
339
+ 'Proper crop rotation',
340
+ 'Maintain field hygiene',
341
+ 'Use disease-resistant varieties',
342
+ 'Proper water management'
343
+ ]
344
+ }
345
+
346
+ return {
347
+ 'crop': crop_name,
348
+ 'recommendations': [],
349
+ 'general_tips': [
350
+ 'Regular field monitoring is essential',
351
+ 'Maintain proper spacing between plants',
352
+ 'Ensure good drainage',
353
+ 'Use certified seeds',
354
+ 'Follow integrated pest management'
355
+ ]
356
+ }
farm_management.db ADDED
File without changes
farms.db ADDED
Binary file (8.19 kB). View file
 
gemini_service.py ADDED
@@ -0,0 +1,382 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import google.generativeai as genai
2
+ import json
3
+ import os
4
+ from datetime import datetime, date
5
+ from typing import Dict, Any, Optional
6
+ import logging
7
+
8
+ # Configure logging
9
+ logging.basicConfig(level=logging.INFO)
10
+ logger = logging.getLogger(__name__)
11
+
12
+ class GeminiAIService:
13
+ """Service class for Gemini AI integration"""
14
+
15
+ def __init__(self, api_key: str):
16
+ """Initialize Gemini AI service"""
17
+ self.api_key = api_key
18
+ genai.configure(api_key=api_key)
19
+ self.model = genai.GenerativeModel('gemini-2.0-flash')
20
+
21
+ def generate_daily_advisory(self, farmer_data: Dict[str, Any], farm_data: Dict[str, Any],
22
+ soil_data: Dict[str, Any], weather_data: Dict[str, Any],
23
+ current_date: str = None) -> Dict[str, str]:
24
+ """
25
+ Generate daily farming advisory using Gemini AI
26
+
27
+ Args:
28
+ farmer_data: Farmer information
29
+ farm_data: Farm details including crops
30
+ soil_data: Current soil parameters
31
+ weather_data: Current weather data
32
+ current_date: Date for the advisory (default: today)
33
+
34
+ Returns:
35
+ Dictionary with task_to_do, task_to_avoid, and reason_explanation
36
+ """
37
+
38
+ if current_date is None:
39
+ current_date = date.today().strftime('%Y-%m-%d')
40
+
41
+ # Prepare the prompt for Gemini
42
+ prompt = self._create_advisory_prompt(farmer_data, farm_data, soil_data, weather_data, current_date)
43
+
44
+ try:
45
+ # Generate response from Gemini
46
+ response = self.model.generate_content(prompt)
47
+
48
+ # Parse the response
49
+ advisory = self._parse_gemini_response(response.text)
50
+
51
+ logger.info(f"Generated advisory for farmer {farmer_data.get('name')} on {current_date}")
52
+ return advisory
53
+
54
+ except Exception as e:
55
+ logger.error(f"Error generating advisory: {str(e)}")
56
+ return self._get_fallback_advisory()
57
+
58
+ def _create_advisory_prompt(self, farmer_data: Dict[str, Any], farm_data: Dict[str, Any],
59
+ soil_data: Dict[str, Any], weather_data: Dict[str, Any],
60
+ current_date: str) -> str:
61
+ """Create a detailed prompt for Gemini AI"""
62
+
63
+ crop_types = ', '.join(farm_data.get('crop_types', []))
64
+
65
+ prompt = f"""
66
+ You are an expert agricultural advisor. Generate a daily farming advisory for the following farmer:
67
+
68
+ FARMER INFORMATION:
69
+ - Name: {farmer_data.get('name', 'Unknown')}
70
+ - Farm Name: {farm_data.get('farm_name', 'Unknown')}
71
+ - Farm Size: {farm_data.get('farm_size', 0)} acres
72
+ - Crops: {crop_types}
73
+ - Irrigation Type: {farm_data.get('irrigation_type', 'Unknown')}
74
+
75
+ SOIL CONDITIONS:
76
+ - Soil Type: {soil_data.get('soil_type', 'Unknown')}
77
+ - pH Level: {soil_data.get('ph_level', 'Unknown')}
78
+ - Nitrogen: {soil_data.get('nitrogen_level', 'Unknown')} ppm
79
+ - Phosphorus: {soil_data.get('phosphorus_level', 'Unknown')} ppm
80
+ - Potassium: {soil_data.get('potassium_level', 'Unknown')} ppm
81
+ - Moisture: {soil_data.get('moisture_percentage', 'Unknown')}%
82
+
83
+ WEATHER CONDITIONS:
84
+ - Date: {current_date}
85
+ - Temperature: {weather_data.get('main', {}).get('temp_min', 'Unknown')}°C - {weather_data.get('main', {}).get('temp_max', 'Unknown')}°C
86
+ - Humidity: {weather_data.get('main', {}).get('humidity', 'Unknown')}%
87
+ - Wind Speed: {weather_data.get('wind', {}).get('speed', 0) * 3.6:.1f} km/h
88
+ - Weather Condition: {weather_data.get('weather', [{}])[0].get('description', 'Unknown').title()}
89
+
90
+ INSTRUCTIONS:
91
+ Please provide a daily farming advisory in the following JSON format:
92
+ {{
93
+ "task_to_do": "Specific task the farmer should do today",
94
+ "task_to_avoid": "Specific task the farmer should avoid today",
95
+ "reason_explanation": "Simple, farmer-friendly explanation for the recommendations",
96
+ "crop_stage": "Current estimated crop growth stage"
97
+ }}
98
+
99
+ Consider the weather conditions, soil parameters, crop requirements, and seasonal farming practices.
100
+ Provide practical, actionable advice that a farmer can easily understand and implement.
101
+ Make the language simple and direct.
102
+ """
103
+
104
+ return prompt
105
+
106
+ def _parse_gemini_response(self, response_text: str) -> Dict[str, str]:
107
+ """Parse Gemini's response and extract advisory information"""
108
+
109
+ try:
110
+ # Try to extract JSON from the response
111
+ # Look for JSON-like content in the response
112
+ start_idx = response_text.find('{')
113
+ end_idx = response_text.rfind('}') + 1
114
+
115
+ if start_idx != -1 and end_idx != -1:
116
+ json_text = response_text[start_idx:end_idx]
117
+ advisory = json.loads(json_text)
118
+
119
+ # Validate required fields
120
+ required_fields = ['task_to_do', 'task_to_avoid', 'reason_explanation']
121
+ for field in required_fields:
122
+ if field not in advisory:
123
+ advisory[field] = "No recommendation available"
124
+
125
+ return advisory
126
+ else:
127
+ # If no JSON found, try to parse manually
128
+ return self._manual_parse_response(response_text)
129
+
130
+ except json.JSONDecodeError:
131
+ # If JSON parsing fails, try manual parsing
132
+ return self._manual_parse_response(response_text)
133
+ except Exception as e:
134
+ logger.error(f"Error parsing Gemini response: {str(e)}")
135
+ return self._get_fallback_advisory()
136
+
137
+ def _manual_parse_response(self, response_text: str) -> Dict[str, str]:
138
+ """Manually parse response if JSON parsing fails"""
139
+
140
+ lines = response_text.split('\n')
141
+ advisory = {
142
+ 'task_to_do': 'No specific task recommended',
143
+ 'task_to_avoid': 'No specific task to avoid',
144
+ 'reason_explanation': 'Advisory not available',
145
+ 'crop_stage': 'Unknown'
146
+ }
147
+
148
+ for line in lines:
149
+ line = line.strip()
150
+ if line.startswith('✅') or 'task_to_do' in line.lower():
151
+ advisory['task_to_do'] = line.replace('✅', '').strip()
152
+ elif line.startswith('❌') or 'task_to_avoid' in line.lower():
153
+ advisory['task_to_avoid'] = line.replace('❌', '').strip()
154
+ elif line.startswith('ℹ️') or 'reason' in line.lower():
155
+ advisory['reason_explanation'] = line.replace('ℹ️', '').strip()
156
+
157
+ return advisory
158
+
159
+ def _get_fallback_advisory(self) -> Dict[str, str]:
160
+ """Provide a fallback advisory when Gemini fails"""
161
+
162
+ return {
163
+ 'task_to_do': 'Check crop condition and water levels',
164
+ 'task_to_avoid': 'Avoid heavy farm work during extreme weather',
165
+ 'reason_explanation': 'General farming best practices for safety and crop health',
166
+ 'crop_stage': 'Unknown'
167
+ }
168
+
169
+ def generate_year_plan(self, farmer_data: Dict[str, Any], farm_data: Dict[str, Any],
170
+ soil_data: Dict[str, Any]) -> Dict[str, Any]:
171
+ """Generate a comprehensive year-long farming plan with detailed HTML formatting"""
172
+
173
+ crop_types = ', '.join(farm_data.get('crop_types', []))
174
+ farm_size = farm_data.get('farm_size', 0)
175
+ irrigation_type = farm_data.get('irrigation_type', 'Unknown')
176
+ soil_type = soil_data.get('soil_type', 'Unknown')
177
+
178
+ # Construct crop details with areas (assume equal distribution if not specified)
179
+ crops = farm_data.get('crop_types', [])
180
+ if crops and farm_size:
181
+ area_per_crop = farm_size / len(crops)
182
+ crop_details = ", ".join([f"{crop} ({area_per_crop:.1f} acres)" for crop in crops])
183
+ else:
184
+ crop_details = crop_types or "Mixed crops"
185
+
186
+ prompt = f"""
187
+ You are a skilled agriculture expert and data analyst. I am providing you with the following farm details:
188
+
189
+ FARM INFORMATION:
190
+ - Farmer Name: {farmer_data.get('name', 'Unknown')}
191
+ - Farm Name: {farm_data.get('farm_name', 'Unknown')}
192
+ - Total Farm Area: {farm_size} acres
193
+ - Location/Address: {farmer_data.get('address', 'Unknown')}
194
+ - Irrigation Type: {irrigation_type}
195
+ - Soil Type: {soil_type}
196
+ - Soil pH: {soil_data.get('ph_level', 'Unknown')}
197
+ - Nitrogen Level: {soil_data.get('nitrogen_level', 'Unknown')} ppm
198
+ - Phosphorus Level: {soil_data.get('phosphorus_level', 'Unknown')} ppm
199
+ - Potassium Level: {soil_data.get('potassium_level', 'Unknown')} ppm
200
+ - Current Crops: {crop_details}
201
+
202
+ Using the above input, please generate a fully formatted HTML page with enhanced CSS styling that includes the following sections:
203
+
204
+ 1. **Main Heading:**
205
+ - A centrally aligned, bold heading in green titled "Comprehensive Yearly Farming Plan 2025" with proper spacing and clear font sizes.
206
+
207
+ 2. **Farm Overview Statistics:**
208
+ - A left-aligned, blue bold subheading "Farm Overview & Current Status".
209
+ - Below it, include a green table with columns for:
210
+ - Parameter
211
+ - Current Value
212
+ - Recommended Range
213
+ - Status (Good/Needs Improvement)
214
+ - Include farm size, soil parameters, irrigation type, etc.
215
+
216
+ 3. **Monthly Farming Calendar (12-Month Plan):**
217
+ - A left-aligned, blue bold subheading titled "Monthly Farming Calendar".
218
+ - Create a comprehensive green table displaying month-wise activities for the entire year:
219
+ - Month
220
+ - Primary Activities
221
+ - Crop Operations
222
+ - Fertilizer Schedule
223
+ - Irrigation Requirements
224
+ - Expected Weather Considerations
225
+ - Include specific activities for each month based on crop cycles, seasons (Rabi/Kharif), and local agricultural practices.
226
+
227
+ 4. **Crop-wise Annual Strategy:**
228
+ - A left-aligned, blue bold subheading "Crop-wise Annual Strategy".
229
+ - Add a detailed green table showing for each crop:
230
+ - Crop Name
231
+ - Sowing Period
232
+ - Growing Duration
233
+ - Harvest Period
234
+ - Expected Yield (per acre)
235
+ - Estimated Revenue
236
+ - Key Care Instructions
237
+
238
+ 5. **Financial Projections:**
239
+ - Include a section with a blue left-aligned subheading "Annual Financial Forecast".
240
+ - Present tables showing:
241
+ - Expected Production Costs (seeds, fertilizers, labor, etc.)
242
+ - Projected Revenue by crop
243
+ - Estimated Profit Margins
244
+ - Monthly cash flow predictions
245
+
246
+ 6. **Soil Management Plan:**
247
+ - A blue left-aligned subheading "Soil Health & Fertilizer Schedule".
248
+ - Create tables for:
249
+ - Soil testing schedule
250
+ - Fertilizer application timeline
251
+ - Organic matter enhancement plan
252
+ - pH management strategies
253
+
254
+ 7. **Risk Management & Weather Planning:**
255
+ - A blue left-aligned subheading "Risk Management Strategies".
256
+ - Include tables for:
257
+ - Seasonal weather challenges
258
+ - Pest and disease prevention calendar
259
+ - Backup crop strategies
260
+ - Insurance and financial protection
261
+
262
+ 8. **Key Recommendations & Action Items:**
263
+ - At the bottom, include a bullet-point list with specific, actionable recommendations.
264
+ - Style each bullet point in bold with yellow highlights on key dates, quantities, and financial figures.
265
+ - Include immediate actions, seasonal priorities, and long-term improvements.
266
+
267
+ IMPORTANT REQUIREMENTS:
268
+ - Generate the entire response in Hindi language. All text, headings, table headers, and content should be in Hindi.
269
+ - Use realistic data based on Indian agricultural practices and the provided farm details.
270
+ - Make tables interactive, beautiful, and colorful with proper spacing and margins.
271
+ - Increase font size for headings and optimize spacing for readability.
272
+ - Do not include any extra spacing at the beginning or end of the response.
273
+ - Ensure all content is properly formatted as HTML with appropriate CSS styling.
274
+ - Include specific dates, quantities, and actionable advice suitable for Indian farmers.
275
+ - Consider Indian seasons (Rabi, Kharif, Zayad) and local agricultural practices.
276
+ - Use metric measurements and Indian rupee currency where applicable.
277
+
278
+ Generate a comprehensive, professional, and farmer-friendly yearly plan that can guide the farmer throughout the entire agricultural year.
279
+ """
280
+
281
+ try:
282
+ response = self.model.generate_content(prompt)
283
+
284
+ # Return the comprehensive HTML plan
285
+ html_plan = response.text.strip()
286
+
287
+ return {
288
+ 'plan': html_plan,
289
+ 'type': 'comprehensive_html',
290
+ 'generated_at': datetime.now().isoformat(),
291
+ 'ai_generated': True,
292
+ 'farmer_name': farmer_data.get('name'),
293
+ 'farm_name': farm_data.get('farm_name')
294
+ }
295
+
296
+ except Exception as e:
297
+ logger.error(f"Error generating comprehensive year plan: {str(e)}")
298
+ return {
299
+ 'plan': self._get_fallback_yearly_plan(farmer_data, farm_data, soil_data),
300
+ 'type': 'fallback_html',
301
+ 'generated_at': datetime.now().isoformat(),
302
+ 'ai_generated': False
303
+ }
304
+
305
+ def _get_fallback_yearly_plan(self, farmer_data: Dict[str, Any], farm_data: Dict[str, Any], soil_data: Dict[str, Any]) -> str:
306
+ """Generate a fallback yearly plan in HTML format when AI fails"""
307
+
308
+ crop_types = ', '.join(farm_data.get('crop_types', ['Mixed crops']))
309
+
310
+ return f"""
311
+ <!DOCTYPE html>
312
+ <html>
313
+ <head>
314
+ <style>
315
+ body {{ font-family: Arial, sans-serif; margin: 20px; background-color: #f5f5f5; }}
316
+ .header {{ text-align: center; color: #2e7d32; font-size: 28px; font-weight: bold; margin-bottom: 30px; }}
317
+ .section {{ margin-bottom: 25px; }}
318
+ .section-title {{ color: #1976d2; font-size: 20px; font-weight: bold; margin-bottom: 15px; }}
319
+ table {{ width: 100%; border-collapse: collapse; background-color: #e8f5e8; margin-bottom: 20px; }}
320
+ th {{ background-color: #4caf50; color: white; padding: 12px; text-align: left; }}
321
+ td {{ padding: 10px; border: 1px solid #ddd; }}
322
+ .highlight {{ background-color: #ffeb3b; font-weight: bold; }}
323
+ .recommendation {{ margin: 10px 0; padding: 10px; background-color: #fff3e0; border-left: 4px solid #ff9800; }}
324
+ </style>
325
+ </head>
326
+ <body>
327
+ <div class="header">वार्षिक खेती योजना 2025</div>
328
+
329
+ <div class="section">
330
+ <div class="section-title">खेत की जानकारी</div>
331
+ <table>
332
+ <tr><th>विवरण</th><th>मान</th></tr>
333
+ <tr><td>किसान का ���ाम</td><td>{farmer_data.get('name', 'अज्ञात')}</td></tr>
334
+ <tr><td>खेत का नाम</td><td>{farm_data.get('farm_name', 'अज्ञात')}</td></tr>
335
+ <tr><td>कुल क्षेत्रफल</td><td>{farm_data.get('farm_size', 0)} एकड़</td></tr>
336
+ <tr><td>फसलें</td><td>{crop_types}</td></tr>
337
+ <tr><td>सिंचाई प्रकार</td><td>{farm_data.get('irrigation_type', 'अज्ञात')}</td></tr>
338
+ </table>
339
+ </div>
340
+
341
+ <div class="section">
342
+ <div class="section-title">मासिक गतिविधि कैलेंडर</div>
343
+ <table>
344
+ <tr><th>महीना</th><th>मुख्य गतिविधियां</th><th>सिंचाई</th></tr>
345
+ <tr><td>जनवरी</td><td>रबी फसल की देखभाल, खाद डालना</td><td>आवश्यकता अनुसार</td></tr>
346
+ <tr><td>फरवरी</td><td>फसल की निगरानी, कीट नियंत्रण</td><td>नियमित</td></tr>
347
+ <tr><td>मार्च</td><td>रबी फसल की कटाई तैयारी</td><td>कम</td></tr>
348
+ <tr><td>अप्रैल</td><td>रबी फसल कटाई, खरीफ की तैयारी</td><td>गर्मी के कारण अधिक</td></tr>
349
+ <tr><td>मई</td><td>खेत की तैयारी, बीज खरीदारी</td><td>अधिक</td></tr>
350
+ <tr><td>जून</td><td>खरीफ फसल बुआई</td><td>मानसून शुरुआत</td></tr>
351
+ <tr><td>जुलाई</td><td>खरीफ फसल देखभाल</td><td>मानसून</td></tr>
352
+ <tr><td>अगस्त</td><td>निराई-गुड़ाई, खाद</td><td>मानसून</td></tr>
353
+ <tr><td>सितंबर</td><td>फसल निगरानी</td><td>मध्यम</td></tr>
354
+ <tr><td>अक्टूबर</td><td>खरीफ कटाई, रबी तैयारी</td><td>आवश्यकता अनुसार</td></tr>
355
+ <tr><td>नवंबर</td><td>रबी फसल बुआई</td><td>नियमित</td></tr>
356
+ <tr><td>दिसंबर</td><td>रबी फसल देखभाल</td><td>ठंड में कम</td></tr>
357
+ </table>
358
+ </div>
359
+
360
+ <div class="recommendation">
361
+ <strong>मुख्य सुझाव:</strong><br>
362
+ • मिट्टी की जांच <span class="highlight">वर्ष में दो बार</span> कराएं<br>
363
+ • उन्नत बीजों का प्रयोग करें<br>
364
+ • <span class="highlight">समय पर</span> सिंचाई और खाद डालें<br>
365
+ • कीट-रोग की नियमित निगरानी करें<br>
366
+ • मौसम की जानकारी रखें
367
+ </div>
368
+ </body>
369
+ </html>
370
+ """
371
+
372
+ def format_sms_message(farmer_name: str, advisory: Dict[str, str]) -> str:
373
+ """Format the advisory as an SMS message"""
374
+
375
+ message = f"Good Morning, {farmer_name} 🌱\n"
376
+ message += f"✅ Task: {advisory.get('task_to_do', 'No task')}\n"
377
+ message += f"❌ Avoid: {advisory.get('task_to_avoid', 'No restrictions')}\n"
378
+
379
+ if advisory.get('reason_explanation'):
380
+ message += f"ℹ️ {advisory.get('reason_explanation')}"
381
+
382
+ return message
generated_pdfs/yearly_plan_PRANIT Ravindra CHILBULE_20250906_140900.html ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ <!DOCTYPE html>
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Yearly Farming Plan - PRANIT Ravindra CHILBULE</title>
8
+ <style>
9
+ body {
10
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
11
+ line-height: 1.6;
12
+ margin: 20px;
13
+ background-color: #f9f9f9;
14
+ }
15
+ .container {
16
+ max-width: 800px;
17
+ margin: 0 auto;
18
+ background: white;
19
+ padding: 30px;
20
+ border-radius: 10px;
21
+ box-shadow: 0 0 20px rgba(0,0,0,0.1);
22
+ }
23
+ .header {
24
+ text-align: center;
25
+ color: #2c5530;
26
+ border-bottom: 3px solid #4CAF50;
27
+ padding-bottom: 20px;
28
+ margin-bottom: 30px;
29
+ }
30
+ .section {
31
+ margin-bottom: 30px;
32
+ }
33
+ .section h2 {
34
+ color: #1976d2;
35
+ border-left: 4px solid #4CAF50;
36
+ padding-left: 15px;
37
+ }
38
+ table {
39
+ width: 100%;
40
+ border-collapse: collapse;
41
+ margin: 15px 0;
42
+ }
43
+ th, td {
44
+ padding: 12px;
45
+ text-align: left;
46
+ border: 1px solid #ddd;
47
+ }
48
+ th {
49
+ background-color: #4CAF50;
50
+ color: white;
51
+ }
52
+ tr:nth-child(even) {
53
+ background-color: #f2f2f2;
54
+ }
55
+ .info-grid {
56
+ display: grid;
57
+ grid-template-columns: 1fr 2fr;
58
+ gap: 10px;
59
+ margin: 15px 0;
60
+ }
61
+ .info-label {
62
+ font-weight: bold;
63
+ color: #333;
64
+ }
65
+ .footer {
66
+ text-align: center;
67
+ margin-top: 40px;
68
+ padding-top: 20px;
69
+ border-top: 2px solid #eee;
70
+ color: #666;
71
+ }
72
+ .highlight {
73
+ background-color: #fff3cd;
74
+ padding: 15px;
75
+ border-left: 4px solid #ffc107;
76
+ margin: 15px 0;
77
+ }
78
+ </style>
79
+ </head>
80
+ <body>
81
+ <div class="container">
82
+ <div class="header">
83
+ <h1>🌾 Comprehensive Yearly Farming Plan</h1>
84
+ <p>AI-Generated Agricultural Strategy</p>
85
+ </div>
86
+
87
+ <div class="section">
88
+ <h2>Farmer Information</h2>
89
+ <div class="info-grid">
90
+ <div class="info-label">Name:</div>
91
+ <div>PRANIT Ravindra CHILBULE</div>
92
+ <div class="info-label">Contact:</div>
93
+ <div>9763059811</div>
94
+ <div class="info-label">Address:</div>
95
+ <div>N/A</div>
96
+ <div class="info-label">Plan Year:</div>
97
+ <div>2025</div>
98
+ </div>
99
+ </div>
100
+
101
+ <div class="section">
102
+ <h2>Farm 1: jnjnn</h2>
103
+ <p><em>Plan details could not be processed: Expecting value: line 1 column 1 (char 0)</em></p> </div>
104
+
105
+ <div class="section">
106
+ <h2>Summary & Recommendations</h2>
107
+ <p>Updated plan for 1 farms</p>
108
+ <div class="highlight">
109
+ <strong>📱 Next Steps:</strong> Access your daily advisories through the farmer dashboard. Monitor weather alerts and market prices for optimal farming decisions.
110
+ </div>
111
+ </div>
112
+
113
+ <div class="footer">
114
+ <p>Generated on: September 06, 2025 at 02:09 PM</p>
115
+ <p>🌱 <strong>Powered by AI Agriculture Assistant</strong></p>
116
+ <p><em>This plan is generated based on AI analysis and should be used in conjunction with local agricultural expertise.</em></p>
117
+ </div>
118
+ </div>
119
+ </body>
120
+ </html>
generated_pdfs/yearly_plan_PRANIT Ravindra CHILBULE_20250906_142024.html ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ <!DOCTYPE html>
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Yearly Farming Plan - PRANIT Ravindra CHILBULE</title>
8
+ <style>
9
+ body {
10
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
11
+ line-height: 1.6;
12
+ margin: 20px;
13
+ background-color: #f9f9f9;
14
+ }
15
+ .container {
16
+ max-width: 800px;
17
+ margin: 0 auto;
18
+ background: white;
19
+ padding: 30px;
20
+ border-radius: 10px;
21
+ box-shadow: 0 0 20px rgba(0,0,0,0.1);
22
+ }
23
+ .header {
24
+ text-align: center;
25
+ color: #2c5530;
26
+ border-bottom: 3px solid #4CAF50;
27
+ padding-bottom: 20px;
28
+ margin-bottom: 30px;
29
+ }
30
+ .section {
31
+ margin-bottom: 30px;
32
+ }
33
+ .section h2 {
34
+ color: #1976d2;
35
+ border-left: 4px solid #4CAF50;
36
+ padding-left: 15px;
37
+ }
38
+ table {
39
+ width: 100%;
40
+ border-collapse: collapse;
41
+ margin: 15px 0;
42
+ }
43
+ th, td {
44
+ padding: 12px;
45
+ text-align: left;
46
+ border: 1px solid #ddd;
47
+ }
48
+ th {
49
+ background-color: #4CAF50;
50
+ color: white;
51
+ }
52
+ tr:nth-child(even) {
53
+ background-color: #f2f2f2;
54
+ }
55
+ .info-grid {
56
+ display: grid;
57
+ grid-template-columns: 1fr 2fr;
58
+ gap: 10px;
59
+ margin: 15px 0;
60
+ }
61
+ .info-label {
62
+ font-weight: bold;
63
+ color: #333;
64
+ }
65
+ .footer {
66
+ text-align: center;
67
+ margin-top: 40px;
68
+ padding-top: 20px;
69
+ border-top: 2px solid #eee;
70
+ color: #666;
71
+ }
72
+ .highlight {
73
+ background-color: #fff3cd;
74
+ padding: 15px;
75
+ border-left: 4px solid #ffc107;
76
+ margin: 15px 0;
77
+ }
78
+ </style>
79
+ </head>
80
+ <body>
81
+ <div class="container">
82
+ <div class="header">
83
+ <h1>🌾 Comprehensive Yearly Farming Plan</h1>
84
+ <p>AI-Generated Agricultural Strategy</p>
85
+ </div>
86
+
87
+ <div class="section">
88
+ <h2>Farmer Information</h2>
89
+ <div class="info-grid">
90
+ <div class="info-label">Name:</div>
91
+ <div>PRANIT Ravindra CHILBULE</div>
92
+ <div class="info-label">Contact:</div>
93
+ <div>9763059811</div>
94
+ <div class="info-label">Address:</div>
95
+ <div>N/A</div>
96
+ <div class="info-label">Plan Year:</div>
97
+ <div>2025</div>
98
+ </div>
99
+ </div>
100
+
101
+ <div class="section">
102
+ <h2>Farm 1: jnjnn</h2>
103
+ <p><em>Plan details could not be processed: Expecting value: line 1 column 1 (char 0)</em></p> </div>
104
+
105
+ <div class="section">
106
+ <h2>Summary & Recommendations</h2>
107
+ <p>Updated plan for 1 farms</p>
108
+ <div class="highlight">
109
+ <strong>📱 Next Steps:</strong> Access your daily advisories through the farmer dashboard. Monitor weather alerts and market prices for optimal farming decisions.
110
+ </div>
111
+ </div>
112
+
113
+ <div class="footer">
114
+ <p>Generated on: September 06, 2025 at 02:20 PM</p>
115
+ <p>🌱 <strong>Powered by AI Agriculture Assistant</strong></p>
116
+ <p><em>This plan is generated based on AI analysis and should be used in conjunction with local agricultural expertise.</em></p>
117
+ </div>
118
+ </div>
119
+ </body>
120
+ </html>
generated_pdfs/yearly_plan_PRANIT Ravindra CHILBULE_20250906_142505.html ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ <!DOCTYPE html>
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Yearly Farming Plan - PRANIT Ravindra CHILBULE</title>
8
+ <style>
9
+ body {
10
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
11
+ line-height: 1.6;
12
+ margin: 20px;
13
+ background-color: #f9f9f9;
14
+ }
15
+ .container {
16
+ max-width: 800px;
17
+ margin: 0 auto;
18
+ background: white;
19
+ padding: 30px;
20
+ border-radius: 10px;
21
+ box-shadow: 0 0 20px rgba(0,0,0,0.1);
22
+ }
23
+ .header {
24
+ text-align: center;
25
+ color: #2c5530;
26
+ border-bottom: 3px solid #4CAF50;
27
+ padding-bottom: 20px;
28
+ margin-bottom: 30px;
29
+ }
30
+ .section {
31
+ margin-bottom: 30px;
32
+ }
33
+ .section h2 {
34
+ color: #1976d2;
35
+ border-left: 4px solid #4CAF50;
36
+ padding-left: 15px;
37
+ }
38
+ table {
39
+ width: 100%;
40
+ border-collapse: collapse;
41
+ margin: 15px 0;
42
+ }
43
+ th, td {
44
+ padding: 12px;
45
+ text-align: left;
46
+ border: 1px solid #ddd;
47
+ }
48
+ th {
49
+ background-color: #4CAF50;
50
+ color: white;
51
+ }
52
+ tr:nth-child(even) {
53
+ background-color: #f2f2f2;
54
+ }
55
+ .info-grid {
56
+ display: grid;
57
+ grid-template-columns: 1fr 2fr;
58
+ gap: 10px;
59
+ margin: 15px 0;
60
+ }
61
+ .info-label {
62
+ font-weight: bold;
63
+ color: #333;
64
+ }
65
+ .footer {
66
+ text-align: center;
67
+ margin-top: 40px;
68
+ padding-top: 20px;
69
+ border-top: 2px solid #eee;
70
+ color: #666;
71
+ }
72
+ .highlight {
73
+ background-color: #fff3cd;
74
+ padding: 15px;
75
+ border-left: 4px solid #ffc107;
76
+ margin: 15px 0;
77
+ }
78
+ </style>
79
+ </head>
80
+ <body>
81
+ <div class="container">
82
+ <div class="header">
83
+ <h1>🌾 Comprehensive Yearly Farming Plan</h1>
84
+ <p>AI-Generated Agricultural Strategy</p>
85
+ </div>
86
+
87
+ <div class="section">
88
+ <h2>Farmer Information</h2>
89
+ <div class="info-grid">
90
+ <div class="info-label">Name:</div>
91
+ <div>PRANIT Ravindra CHILBULE</div>
92
+ <div class="info-label">Contact:</div>
93
+ <div>9763059811</div>
94
+ <div class="info-label">Address:</div>
95
+ <div>N/A</div>
96
+ <div class="info-label">Plan Year:</div>
97
+ <div>2025</div>
98
+ </div>
99
+ </div>
100
+
101
+ <div class="section">
102
+ <h2>Farm 1: jnjnn</h2>
103
+ <p><em>Plan details could not be processed: Expecting value: line 1 column 1 (char 0)</em></p> </div>
104
+
105
+ <div class="section">
106
+ <h2>Summary & Recommendations</h2>
107
+ <p>Updated plan for 1 farms</p>
108
+ <div class="highlight">
109
+ <strong>📱 Next Steps:</strong> Access your daily advisories through the farmer dashboard. Monitor weather alerts and market prices for optimal farming decisions.
110
+ </div>
111
+ </div>
112
+
113
+ <div class="footer">
114
+ <p>Generated on: September 06, 2025 at 02:25 PM</p>
115
+ <p>🌱 <strong>Powered by AI Agriculture Assistant</strong></p>
116
+ <p><em>This plan is generated based on AI analysis and should be used in conjunction with local agricultural expertise.</em></p>
117
+ </div>
118
+ </div>
119
+ </body>
120
+ </html>
generated_pdfs/yearly_plan_PRANIT Ravindra CHILBULE_20250906_172353.html ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ <!DOCTYPE html>
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Yearly Farming Plan - PRANIT Ravindra CHILBULE</title>
8
+ <style>
9
+ body {
10
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
11
+ line-height: 1.6;
12
+ margin: 20px;
13
+ background-color: #f9f9f9;
14
+ }
15
+ .container {
16
+ max-width: 800px;
17
+ margin: 0 auto;
18
+ background: white;
19
+ padding: 30px;
20
+ border-radius: 10px;
21
+ box-shadow: 0 0 20px rgba(0,0,0,0.1);
22
+ }
23
+ .header {
24
+ text-align: center;
25
+ color: #2c5530;
26
+ border-bottom: 3px solid #4CAF50;
27
+ padding-bottom: 20px;
28
+ margin-bottom: 30px;
29
+ }
30
+ .section {
31
+ margin-bottom: 30px;
32
+ }
33
+ .section h2 {
34
+ color: #1976d2;
35
+ border-left: 4px solid #4CAF50;
36
+ padding-left: 15px;
37
+ }
38
+ table {
39
+ width: 100%;
40
+ border-collapse: collapse;
41
+ margin: 15px 0;
42
+ }
43
+ th, td {
44
+ padding: 12px;
45
+ text-align: left;
46
+ border: 1px solid #ddd;
47
+ }
48
+ th {
49
+ background-color: #4CAF50;
50
+ color: white;
51
+ }
52
+ tr:nth-child(even) {
53
+ background-color: #f2f2f2;
54
+ }
55
+ .info-grid {
56
+ display: grid;
57
+ grid-template-columns: 1fr 2fr;
58
+ gap: 10px;
59
+ margin: 15px 0;
60
+ }
61
+ .info-label {
62
+ font-weight: bold;
63
+ color: #333;
64
+ }
65
+ .footer {
66
+ text-align: center;
67
+ margin-top: 40px;
68
+ padding-top: 20px;
69
+ border-top: 2px solid #eee;
70
+ color: #666;
71
+ }
72
+ .highlight {
73
+ background-color: #fff3cd;
74
+ padding: 15px;
75
+ border-left: 4px solid #ffc107;
76
+ margin: 15px 0;
77
+ }
78
+ </style>
79
+ </head>
80
+ <body>
81
+ <div class="container">
82
+ <div class="header">
83
+ <h1>🌾 Comprehensive Yearly Farming Plan</h1>
84
+ <p>AI-Generated Agricultural Strategy</p>
85
+ </div>
86
+
87
+ <div class="section">
88
+ <h2>Farmer Information</h2>
89
+ <div class="info-grid">
90
+ <div class="info-label">Name:</div>
91
+ <div>PRANIT Ravindra CHILBULE</div>
92
+ <div class="info-label">Contact:</div>
93
+ <div>9763059811</div>
94
+ <div class="info-label">Address:</div>
95
+ <div>N/A</div>
96
+ <div class="info-label">Plan Year:</div>
97
+ <div>2025</div>
98
+ </div>
99
+ </div>
100
+
101
+ <div class="section">
102
+ <h2>Farm 1: jnjnn</h2>
103
+ <p><em>Plan details could not be processed: Expecting value: line 1 column 1 (char 0)</em></p> </div>
104
+
105
+ <div class="section">
106
+ <h2>Summary & Recommendations</h2>
107
+ <p>Updated plan for 1 farms</p>
108
+ <div class="highlight">
109
+ <strong>📱 Next Steps:</strong> Access your daily advisories through the farmer dashboard. Monitor weather alerts and market prices for optimal farming decisions.
110
+ </div>
111
+ </div>
112
+
113
+ <div class="footer">
114
+ <p>Generated on: September 06, 2025 at 05:23 PM</p>
115
+ <p>🌱 <strong>Powered by AI Agriculture Assistant</strong></p>
116
+ <p><em>This plan is generated based on AI analysis and should be used in conjunction with local agricultural expertise.</em></p>
117
+ </div>
118
+ </div>
119
+ </body>
120
+ </html>
generated_pdfs/yearly_plan_PRANIT Ravindra CHILBULE_20250906_172354.html ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ <!DOCTYPE html>
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Yearly Farming Plan - PRANIT Ravindra CHILBULE</title>
8
+ <style>
9
+ body {
10
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
11
+ line-height: 1.6;
12
+ margin: 20px;
13
+ background-color: #f9f9f9;
14
+ }
15
+ .container {
16
+ max-width: 800px;
17
+ margin: 0 auto;
18
+ background: white;
19
+ padding: 30px;
20
+ border-radius: 10px;
21
+ box-shadow: 0 0 20px rgba(0,0,0,0.1);
22
+ }
23
+ .header {
24
+ text-align: center;
25
+ color: #2c5530;
26
+ border-bottom: 3px solid #4CAF50;
27
+ padding-bottom: 20px;
28
+ margin-bottom: 30px;
29
+ }
30
+ .section {
31
+ margin-bottom: 30px;
32
+ }
33
+ .section h2 {
34
+ color: #1976d2;
35
+ border-left: 4px solid #4CAF50;
36
+ padding-left: 15px;
37
+ }
38
+ table {
39
+ width: 100%;
40
+ border-collapse: collapse;
41
+ margin: 15px 0;
42
+ }
43
+ th, td {
44
+ padding: 12px;
45
+ text-align: left;
46
+ border: 1px solid #ddd;
47
+ }
48
+ th {
49
+ background-color: #4CAF50;
50
+ color: white;
51
+ }
52
+ tr:nth-child(even) {
53
+ background-color: #f2f2f2;
54
+ }
55
+ .info-grid {
56
+ display: grid;
57
+ grid-template-columns: 1fr 2fr;
58
+ gap: 10px;
59
+ margin: 15px 0;
60
+ }
61
+ .info-label {
62
+ font-weight: bold;
63
+ color: #333;
64
+ }
65
+ .footer {
66
+ text-align: center;
67
+ margin-top: 40px;
68
+ padding-top: 20px;
69
+ border-top: 2px solid #eee;
70
+ color: #666;
71
+ }
72
+ .highlight {
73
+ background-color: #fff3cd;
74
+ padding: 15px;
75
+ border-left: 4px solid #ffc107;
76
+ margin: 15px 0;
77
+ }
78
+ </style>
79
+ </head>
80
+ <body>
81
+ <div class="container">
82
+ <div class="header">
83
+ <h1>🌾 Comprehensive Yearly Farming Plan</h1>
84
+ <p>AI-Generated Agricultural Strategy</p>
85
+ </div>
86
+
87
+ <div class="section">
88
+ <h2>Farmer Information</h2>
89
+ <div class="info-grid">
90
+ <div class="info-label">Name:</div>
91
+ <div>PRANIT Ravindra CHILBULE</div>
92
+ <div class="info-label">Contact:</div>
93
+ <div>9763059811</div>
94
+ <div class="info-label">Address:</div>
95
+ <div>N/A</div>
96
+ <div class="info-label">Plan Year:</div>
97
+ <div>2025</div>
98
+ </div>
99
+ </div>
100
+
101
+ <div class="section">
102
+ <h2>Farm 1: jnjnn</h2>
103
+ <p><em>Plan details could not be processed: Expecting value: line 1 column 1 (char 0)</em></p> </div>
104
+
105
+ <div class="section">
106
+ <h2>Summary & Recommendations</h2>
107
+ <p>Updated plan for 1 farms</p>
108
+ <div class="highlight">
109
+ <strong>📱 Next Steps:</strong> Access your daily advisories through the farmer dashboard. Monitor weather alerts and market prices for optimal farming decisions.
110
+ </div>
111
+ </div>
112
+
113
+ <div class="footer">
114
+ <p>Generated on: September 06, 2025 at 05:23 PM</p>
115
+ <p>🌱 <strong>Powered by AI Agriculture Assistant</strong></p>
116
+ <p><em>This plan is generated based on AI analysis and should be used in conjunction with local agricultural expertise.</em></p>
117
+ </div>
118
+ </div>
119
+ </body>
120
+ </html>
generated_pdfs/yearly_plan_PRANIT Ravindra CHILBULE_20250906_172448.html ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ <!DOCTYPE html>
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Yearly Farming Plan - PRANIT Ravindra CHILBULE</title>
8
+ <style>
9
+ body {
10
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
11
+ line-height: 1.6;
12
+ margin: 20px;
13
+ background-color: #f9f9f9;
14
+ }
15
+ .container {
16
+ max-width: 800px;
17
+ margin: 0 auto;
18
+ background: white;
19
+ padding: 30px;
20
+ border-radius: 10px;
21
+ box-shadow: 0 0 20px rgba(0,0,0,0.1);
22
+ }
23
+ .header {
24
+ text-align: center;
25
+ color: #2c5530;
26
+ border-bottom: 3px solid #4CAF50;
27
+ padding-bottom: 20px;
28
+ margin-bottom: 30px;
29
+ }
30
+ .section {
31
+ margin-bottom: 30px;
32
+ }
33
+ .section h2 {
34
+ color: #1976d2;
35
+ border-left: 4px solid #4CAF50;
36
+ padding-left: 15px;
37
+ }
38
+ table {
39
+ width: 100%;
40
+ border-collapse: collapse;
41
+ margin: 15px 0;
42
+ }
43
+ th, td {
44
+ padding: 12px;
45
+ text-align: left;
46
+ border: 1px solid #ddd;
47
+ }
48
+ th {
49
+ background-color: #4CAF50;
50
+ color: white;
51
+ }
52
+ tr:nth-child(even) {
53
+ background-color: #f2f2f2;
54
+ }
55
+ .info-grid {
56
+ display: grid;
57
+ grid-template-columns: 1fr 2fr;
58
+ gap: 10px;
59
+ margin: 15px 0;
60
+ }
61
+ .info-label {
62
+ font-weight: bold;
63
+ color: #333;
64
+ }
65
+ .footer {
66
+ text-align: center;
67
+ margin-top: 40px;
68
+ padding-top: 20px;
69
+ border-top: 2px solid #eee;
70
+ color: #666;
71
+ }
72
+ .highlight {
73
+ background-color: #fff3cd;
74
+ padding: 15px;
75
+ border-left: 4px solid #ffc107;
76
+ margin: 15px 0;
77
+ }
78
+ </style>
79
+ </head>
80
+ <body>
81
+ <div class="container">
82
+ <div class="header">
83
+ <h1>🌾 Comprehensive Yearly Farming Plan</h1>
84
+ <p>AI-Generated Agricultural Strategy</p>
85
+ </div>
86
+
87
+ <div class="section">
88
+ <h2>Farmer Information</h2>
89
+ <div class="info-grid">
90
+ <div class="info-label">Name:</div>
91
+ <div>PRANIT Ravindra CHILBULE</div>
92
+ <div class="info-label">Contact:</div>
93
+ <div>9763059811</div>
94
+ <div class="info-label">Address:</div>
95
+ <div>N/A</div>
96
+ <div class="info-label">Plan Year:</div>
97
+ <div>2025</div>
98
+ </div>
99
+ </div>
100
+
101
+ <div class="section">
102
+ <h2>Farm 1: jnjnn</h2>
103
+ <p><em>Plan details could not be processed: Expecting value: line 1 column 1 (char 0)</em></p> </div>
104
+
105
+ <div class="section">
106
+ <h2>Summary & Recommendations</h2>
107
+ <p>AI-Generated Comprehensive Yearly Plan for jnjnn (12.0 acres)</p>
108
+ <div class="highlight">
109
+ <strong>📱 Next Steps:</strong> Access your daily advisories through the farmer dashboard. Monitor weather alerts and market prices for optimal farming decisions.
110
+ </div>
111
+ </div>
112
+
113
+ <div class="footer">
114
+ <p>Generated on: September 06, 2025 at 05:24 PM</p>
115
+ <p>🌱 <strong>Powered by AI Agriculture Assistant</strong></p>
116
+ <p><em>This plan is generated based on AI analysis and should be used in conjunction with local agricultural expertise.</em></p>
117
+ </div>
118
+ </div>
119
+ </body>
120
+ </html>
generated_pdfs/yearly_plan_PRANIT Ravindra CHILBULE_20250906_172530.html ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ <!DOCTYPE html>
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Yearly Farming Plan - PRANIT Ravindra CHILBULE</title>
8
+ <style>
9
+ body {
10
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
11
+ line-height: 1.6;
12
+ margin: 20px;
13
+ background-color: #f9f9f9;
14
+ }
15
+ .container {
16
+ max-width: 800px;
17
+ margin: 0 auto;
18
+ background: white;
19
+ padding: 30px;
20
+ border-radius: 10px;
21
+ box-shadow: 0 0 20px rgba(0,0,0,0.1);
22
+ }
23
+ .header {
24
+ text-align: center;
25
+ color: #2c5530;
26
+ border-bottom: 3px solid #4CAF50;
27
+ padding-bottom: 20px;
28
+ margin-bottom: 30px;
29
+ }
30
+ .section {
31
+ margin-bottom: 30px;
32
+ }
33
+ .section h2 {
34
+ color: #1976d2;
35
+ border-left: 4px solid #4CAF50;
36
+ padding-left: 15px;
37
+ }
38
+ table {
39
+ width: 100%;
40
+ border-collapse: collapse;
41
+ margin: 15px 0;
42
+ }
43
+ th, td {
44
+ padding: 12px;
45
+ text-align: left;
46
+ border: 1px solid #ddd;
47
+ }
48
+ th {
49
+ background-color: #4CAF50;
50
+ color: white;
51
+ }
52
+ tr:nth-child(even) {
53
+ background-color: #f2f2f2;
54
+ }
55
+ .info-grid {
56
+ display: grid;
57
+ grid-template-columns: 1fr 2fr;
58
+ gap: 10px;
59
+ margin: 15px 0;
60
+ }
61
+ .info-label {
62
+ font-weight: bold;
63
+ color: #333;
64
+ }
65
+ .footer {
66
+ text-align: center;
67
+ margin-top: 40px;
68
+ padding-top: 20px;
69
+ border-top: 2px solid #eee;
70
+ color: #666;
71
+ }
72
+ .highlight {
73
+ background-color: #fff3cd;
74
+ padding: 15px;
75
+ border-left: 4px solid #ffc107;
76
+ margin: 15px 0;
77
+ }
78
+ </style>
79
+ </head>
80
+ <body>
81
+ <div class="container">
82
+ <div class="header">
83
+ <h1>🌾 Comprehensive Yearly Farming Plan</h1>
84
+ <p>AI-Generated Agricultural Strategy</p>
85
+ </div>
86
+
87
+ <div class="section">
88
+ <h2>Farmer Information</h2>
89
+ <div class="info-grid">
90
+ <div class="info-label">Name:</div>
91
+ <div>PRANIT Ravindra CHILBULE</div>
92
+ <div class="info-label">Contact:</div>
93
+ <div>9763059811</div>
94
+ <div class="info-label">Address:</div>
95
+ <div>N/A</div>
96
+ <div class="info-label">Plan Year:</div>
97
+ <div>2025</div>
98
+ </div>
99
+ </div>
100
+
101
+ <div class="section">
102
+ <h2>Farm 1: jnjnn</h2>
103
+ <p><em>Plan details could not be processed: Expecting value: line 1 column 1 (char 0)</em></p> </div>
104
+
105
+ <div class="section">
106
+ <h2>Summary & Recommendations</h2>
107
+ <p>AI-Generated Comprehensive Yearly Plan for jnjnn (12.0 acres)</p>
108
+ <div class="highlight">
109
+ <strong>📱 Next Steps:</strong> Access your daily advisories through the farmer dashboard. Monitor weather alerts and market prices for optimal farming decisions.
110
+ </div>
111
+ </div>
112
+
113
+ <div class="footer">
114
+ <p>Generated on: September 06, 2025 at 05:25 PM</p>
115
+ <p>🌱 <strong>Powered by AI Agriculture Assistant</strong></p>
116
+ <p><em>This plan is generated based on AI analysis and should be used in conjunction with local agricultural expertise.</em></p>
117
+ </div>
118
+ </div>
119
+ </body>
120
+ </html>
instance/farm_management.db ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9951e724cfbaef1c93506d2a86a6b4594db68853b8f499d7580ffe714994c1c3
3
+ size 180224
instance/farms.db ADDED
Binary file (24.6 kB). View file
 
market_price_service.py ADDED
@@ -0,0 +1,329 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ import json
3
+ from datetime import datetime, date, timedelta
4
+ from typing import Dict, List, Optional
5
+ import logging
6
+ from models import MarketPrice, db
7
+
8
+ # Configure logging
9
+ logging.basicConfig(level=logging.INFO)
10
+ logger = logging.getLogger(__name__)
11
+
12
+ class MarketPriceService:
13
+ """Service for fetching and managing crop market prices"""
14
+
15
+ def __init__(self):
16
+ # Government API endpoints (these are examples - replace with actual APIs)
17
+ self.government_api = "https://api.data.gov.in/resource/9ef84268-d588-465a-a308-a864a43d0070"
18
+ self.backup_sources = [
19
+ "https://agmarknet.gov.in/", # Agricultural Marketing Division
20
+ "https://enam.gov.in/" # National Agriculture Market
21
+ ]
22
+
23
+ def fetch_and_update_prices(self, crops: List[str] = None, states: List[str] = None) -> bool:
24
+ """Fetch latest market prices and update database"""
25
+ try:
26
+ # Default crops if none specified
27
+ if not crops:
28
+ crops = [
29
+ 'Rice', 'Wheat', 'Maize', 'Sugarcane', 'Cotton', 'Soybean',
30
+ 'Groundnut', 'Sunflower', 'Mustard', 'Gram', 'Arhar', 'Moong',
31
+ 'Masoor', 'Onion', 'Potato', 'Tomato', 'Chilli', 'Turmeric'
32
+ ]
33
+
34
+ # Default states if none specified
35
+ if not states:
36
+ states = [
37
+ 'Maharashtra', 'Uttar Pradesh', 'Karnataka', 'Gujarat',
38
+ 'Rajasthan', 'Madhya Pradesh', 'Tamil Nadu', 'Andhra Pradesh'
39
+ ]
40
+
41
+ updated_count = 0
42
+
43
+ # Try government API first
44
+ updated_count += self._fetch_from_government_api(crops, states)
45
+
46
+ # If government API fails, use fallback data
47
+ if updated_count == 0:
48
+ updated_count += self._generate_fallback_prices(crops, states)
49
+
50
+ logger.info(f"Updated {updated_count} market price records")
51
+ return updated_count > 0
52
+
53
+ except Exception as e:
54
+ logger.error(f"Error updating market prices: {str(e)}")
55
+ return False
56
+
57
+ def _fetch_from_government_api(self, crops: List[str], states: List[str]) -> int:
58
+ """Fetch prices from government API"""
59
+ try:
60
+ # This is a placeholder - replace with actual government API implementation
61
+ # The actual API would require proper authentication and parameters
62
+
63
+ # For demonstration, we'll generate realistic data
64
+ return self._generate_realistic_prices(crops, states)
65
+
66
+ except Exception as e:
67
+ logger.error(f"Government API fetch failed: {str(e)}")
68
+ return 0
69
+
70
+ def _generate_realistic_prices(self, crops: List[str], states: List[str]) -> int:
71
+ """Generate realistic market prices based on typical market rates"""
72
+
73
+ # Base prices per quintal (in INR) - these are approximate market rates
74
+ base_prices = {
75
+ 'Rice': {'min': 1800, 'max': 2500, 'avg': 2100},
76
+ 'Wheat': {'min': 1900, 'max': 2300, 'avg': 2100},
77
+ 'Maize': {'min': 1400, 'max': 1800, 'avg': 1600},
78
+ 'Sugarcane': {'min': 250, 'max': 350, 'avg': 300}, # per ton
79
+ 'Cotton': {'min': 5500, 'max': 7500, 'avg': 6500},
80
+ 'Soybean': {'min': 3500, 'max': 4500, 'avg': 4000},
81
+ 'Groundnut': {'min': 4500, 'max': 6000, 'avg': 5250},
82
+ 'Sunflower': {'min': 4000, 'max': 5500, 'avg': 4750},
83
+ 'Mustard': {'min': 4200, 'max': 5200, 'avg': 4700},
84
+ 'Gram': {'min': 4500, 'max': 6000, 'avg': 5250},
85
+ 'Arhar': {'min': 5500, 'max': 7000, 'avg': 6250},
86
+ 'Moong': {'min': 6000, 'max': 8000, 'avg': 7000},
87
+ 'Masoor': {'min': 4500, 'max': 6500, 'avg': 5500},
88
+ 'Onion': {'min': 800, 'max': 2500, 'avg': 1650},
89
+ 'Potato': {'min': 600, 'max': 1800, 'avg': 1200},
90
+ 'Tomato': {'min': 800, 'max': 3000, 'avg': 1900},
91
+ 'Chilli': {'min': 8000, 'max': 15000, 'avg': 11500},
92
+ 'Turmeric': {'min': 7000, 'max': 12000, 'avg': 9500}
93
+ }
94
+
95
+ markets_by_state = {
96
+ 'Maharashtra': ['Mumbai', 'Pune', 'Nashik', 'Nagpur', 'Aurangabad'],
97
+ 'Uttar Pradesh': ['Lucknow', 'Kanpur', 'Agra', 'Varanasi', 'Meerut'],
98
+ 'Karnataka': ['Bangalore', 'Mysore', 'Hubli', 'Belgaum', 'Mangalore'],
99
+ 'Gujarat': ['Ahmedabad', 'Surat', 'Vadodara', 'Rajkot', 'Bhavnagar'],
100
+ 'Rajasthan': ['Jaipur', 'Jodhpur', 'Kota', 'Bikaner', 'Udaipur'],
101
+ 'Madhya Pradesh': ['Bhopal', 'Indore', 'Gwalior', 'Jabalpur', 'Ujjain'],
102
+ 'Tamil Nadu': ['Chennai', 'Coimbatore', 'Madurai', 'Salem', 'Trichy'],
103
+ 'Andhra Pradesh': ['Hyderabad', 'Vijayawada', 'Visakhapatnam', 'Guntur', 'Tirupati']
104
+ }
105
+
106
+ updated_count = 0
107
+ today = date.today()
108
+
109
+ for crop in crops:
110
+ if crop not in base_prices:
111
+ continue
112
+
113
+ for state in states:
114
+ if state not in markets_by_state:
115
+ continue
116
+
117
+ for market in markets_by_state[state][:3]: # Top 3 markets per state
118
+ try:
119
+ # Add some market variation (±20%)
120
+ base = base_prices[crop]
121
+ variation = 0.8 + (hash(f"{crop}{state}{market}") % 40) / 100 # 0.8 to 1.2
122
+
123
+ min_price = int(base['min'] * variation)
124
+ max_price = int(base['max'] * variation)
125
+ avg_price = int(base['avg'] * variation)
126
+
127
+ # Check if price already exists for today
128
+ existing = MarketPrice.query.filter_by(
129
+ crop_name=crop,
130
+ market_name=market,
131
+ state=state,
132
+ price_date=today
133
+ ).first()
134
+
135
+ if not existing:
136
+ price_record = MarketPrice(
137
+ crop_name=crop,
138
+ market_name=market,
139
+ state=state,
140
+ district=market, # Simplified
141
+ min_price=min_price,
142
+ max_price=max_price,
143
+ avg_price=avg_price,
144
+ price_unit='per quintal' if crop != 'Sugarcane' else 'per ton',
145
+ price_date=today,
146
+ source='market_api'
147
+ )
148
+ db.session.add(price_record)
149
+ updated_count += 1
150
+
151
+ except Exception as e:
152
+ logger.error(f"Error creating price record for {crop} in {market}: {str(e)}")
153
+ continue
154
+
155
+ try:
156
+ db.session.commit()
157
+ return updated_count
158
+ except Exception as e:
159
+ logger.error(f"Error committing price records: {str(e)}")
160
+ db.session.rollback()
161
+ return 0
162
+
163
+ def _generate_fallback_prices(self, crops: List[str], states: List[str]) -> int:
164
+ """Generate fallback prices when APIs are unavailable"""
165
+ return self._generate_realistic_prices(crops, states)
166
+
167
+ def get_latest_prices(self, crop_name: str, state: str = None, limit: int = 10) -> List[MarketPrice]:
168
+ """Get latest prices for a crop"""
169
+ query = MarketPrice.query.filter_by(crop_name=crop_name)
170
+
171
+ if state:
172
+ query = query.filter_by(state=state)
173
+
174
+ return query.order_by(MarketPrice.price_date.desc()).limit(limit).all()
175
+
176
+ def get_price_trends(self, crop_name: str, days: int = 30) -> Dict:
177
+ """Get price trends for a crop over specified days"""
178
+ from_date = date.today() - timedelta(days=days)
179
+
180
+ prices = MarketPrice.query.filter(
181
+ MarketPrice.crop_name == crop_name,
182
+ MarketPrice.price_date >= from_date
183
+ ).order_by(MarketPrice.price_date.desc()).all()
184
+
185
+ if not prices:
186
+ return {'trend': 'no_data', 'prices': []}
187
+
188
+ # Calculate trend
189
+ recent_avg = sum(p.avg_price for p in prices[:7]) / min(7, len(prices))
190
+ older_avg = sum(p.avg_price for p in prices[7:14]) / max(1, min(7, len(prices) - 7))
191
+
192
+ if recent_avg > older_avg * 1.05:
193
+ trend = 'rising'
194
+ elif recent_avg < older_avg * 0.95:
195
+ trend = 'falling'
196
+ else:
197
+ trend = 'stable'
198
+
199
+ return {
200
+ 'trend': trend,
201
+ 'recent_avg': recent_avg,
202
+ 'older_avg': older_avg,
203
+ 'prices': [p.as_dict() for p in prices]
204
+ }
205
+
206
+ def get_best_selling_recommendation(self, crop_name: str, farmer_state: str) -> Dict:
207
+ """Get recommendation for best time and place to sell"""
208
+
209
+ # Get prices from nearby states
210
+ nearby_states = self._get_nearby_states(farmer_state)
211
+ all_states = [farmer_state] + nearby_states
212
+
213
+ best_prices = []
214
+ for state in all_states:
215
+ latest_prices = self.get_latest_prices(crop_name, state, 5)
216
+ if latest_prices:
217
+ avg_price = sum(p.avg_price for p in latest_prices) / len(latest_prices)
218
+ best_prices.append({
219
+ 'state': state,
220
+ 'avg_price': avg_price,
221
+ 'markets': [p.market_name for p in latest_prices[:3]]
222
+ })
223
+
224
+ if not best_prices:
225
+ return {'recommendation': 'no_data'}
226
+
227
+ # Sort by price
228
+ best_prices.sort(key=lambda x: x['avg_price'], reverse=True)
229
+
230
+ # Get trends
231
+ trends = self.get_price_trends(crop_name)
232
+
233
+ recommendation = {
234
+ 'best_market': best_prices[0],
235
+ 'trend': trends['trend'],
236
+ 'recommendation': self._generate_selling_advice(trends['trend'], best_prices)
237
+ }
238
+
239
+ return recommendation
240
+
241
+ def _get_nearby_states(self, state: str) -> List[str]:
242
+ """Get nearby states for price comparison"""
243
+ state_neighbors = {
244
+ 'Maharashtra': ['Gujarat', 'Karnataka', 'Madhya Pradesh'],
245
+ 'Gujarat': ['Maharashtra', 'Rajasthan', 'Madhya Pradesh'],
246
+ 'Karnataka': ['Maharashtra', 'Tamil Nadu', 'Andhra Pradesh'],
247
+ 'Tamil Nadu': ['Karnataka', 'Andhra Pradesh'],
248
+ 'Uttar Pradesh': ['Madhya Pradesh', 'Rajasthan'],
249
+ 'Rajasthan': ['Gujarat', 'Madhya Pradesh', 'Uttar Pradesh'],
250
+ 'Madhya Pradesh': ['Maharashtra', 'Gujarat', 'Uttar Pradesh', 'Rajasthan'],
251
+ 'Andhra Pradesh': ['Karnataka', 'Tamil Nadu']
252
+ }
253
+ return state_neighbors.get(state, [])
254
+
255
+ def _generate_selling_advice(self, trend: str, best_prices: List[Dict]) -> str:
256
+ """Generate selling advice based on trends and prices"""
257
+ if trend == 'rising':
258
+ return "Prices are rising. Consider waiting a few more days for better rates."
259
+ elif trend == 'falling':
260
+ return "Prices are falling. Sell immediately to avoid further losses."
261
+ else:
262
+ if len(best_prices) > 1 and best_prices[0]['avg_price'] > best_prices[1]['avg_price'] * 1.1:
263
+ return f"Current prices are good. Consider selling in {best_prices[0]['state']} for best rates."
264
+ else:
265
+ return "Prices are stable. Sell when convenient."
266
+
267
+ def get_crop_price(self, crop_name: str, state: str = None) -> Dict:
268
+ """Get current price for a crop"""
269
+ try:
270
+ # Get latest prices
271
+ latest_prices = self.get_latest_prices(crop_name, state, limit=5)
272
+
273
+ if not latest_prices:
274
+ # No data found, return fallback
275
+ return {
276
+ 'crop_type': crop_name,
277
+ 'market_name': 'Market data unavailable',
278
+ 'price_per_unit': 'N/A',
279
+ 'unit': 'per quintal',
280
+ 'trend': 'stable',
281
+ 'date': date.today().isoformat(),
282
+ 'error': 'No price data available'
283
+ }
284
+
285
+ # Calculate average from latest prices
286
+ avg_price = sum(p.avg_price for p in latest_prices) / len(latest_prices)
287
+ latest = latest_prices[0]
288
+
289
+ # Determine trend based on recent prices
290
+ trend = 'stable'
291
+ if len(latest_prices) >= 2:
292
+ recent_avg = sum(p.avg_price for p in latest_prices[:2]) / 2
293
+ older_avg = sum(p.avg_price for p in latest_prices[2:]) / len(latest_prices[2:]) if len(latest_prices) > 2 else recent_avg
294
+
295
+ if recent_avg > older_avg * 1.05:
296
+ trend = 'up'
297
+ elif recent_avg < older_avg * 0.95:
298
+ trend = 'down'
299
+
300
+ return {
301
+ 'crop_type': crop_name,
302
+ 'market_name': latest.market_name,
303
+ 'price_per_unit': f"₹{int(avg_price)}",
304
+ 'unit': latest.price_unit,
305
+ 'trend': trend,
306
+ 'date': latest.price_date.isoformat()
307
+ }
308
+
309
+ except Exception as e:
310
+ logger.error(f"Error getting crop price for {crop_name}: {str(e)}")
311
+ return {
312
+ 'crop_type': crop_name,
313
+ 'market_name': 'Error',
314
+ 'price_per_unit': 'N/A',
315
+ 'unit': 'per quintal',
316
+ 'trend': 'stable',
317
+ 'date': date.today().isoformat(),
318
+ 'error': f'Failed to get price: {str(e)}'
319
+ }
320
+
321
+ def refresh_crop_price(self, crop_name: str) -> bool:
322
+ """Force refresh price data for a specific crop"""
323
+ try:
324
+ # Trigger fresh fetch for this crop
325
+ self.fetch_and_update_prices([crop_name])
326
+ return True
327
+ except Exception as e:
328
+ logger.error(f"Error refreshing price for {crop_name}: {str(e)}")
329
+ return False
migrate_db.py ADDED
@@ -0,0 +1,314 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Database migration script to add missing columns
4
+ """
5
+ import sqlite3
6
+ import os
7
+
8
+ def migrate_database():
9
+ """Add missing columns to the database"""
10
+
11
+ # Find the database file
12
+ db_paths = [
13
+ 'instance/farm_management.db',
14
+ 'farm_management.db',
15
+ 'farms.db'
16
+ ]
17
+
18
+ db_path = None
19
+ for path in db_paths:
20
+ if os.path.exists(path):
21
+ db_path = path
22
+ break
23
+
24
+ if not db_path:
25
+ print("Database file not found!")
26
+ return False
27
+
28
+ print(f"Found database at: {db_path}")
29
+
30
+ try:
31
+ conn = sqlite3.connect(db_path)
32
+ cursor = conn.cursor()
33
+
34
+ # Check if weather_alerts_enabled column exists
35
+ cursor.execute("PRAGMA table_info(farms)")
36
+ columns = [row[1] for row in cursor.fetchall()]
37
+
38
+ if 'weather_alerts_enabled' not in columns:
39
+ print("Adding weather_alerts_enabled column to farms table...")
40
+ cursor.execute("ALTER TABLE farms ADD COLUMN weather_alerts_enabled BOOLEAN DEFAULT 1")
41
+ print("✓ Added weather_alerts_enabled column")
42
+ else:
43
+ print("✓ weather_alerts_enabled column already exists")
44
+
45
+ # Create weather_alerts table if it doesn't exist
46
+ cursor.execute("""
47
+ CREATE TABLE IF NOT EXISTS weather_alerts (
48
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
49
+ farm_id INTEGER,
50
+ alert_type VARCHAR(100),
51
+ severity VARCHAR(50),
52
+ message TEXT,
53
+ recommendations TEXT,
54
+ is_active BOOLEAN DEFAULT 1,
55
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
56
+ FOREIGN KEY (farm_id) REFERENCES farms (id)
57
+ )
58
+ """)
59
+ print("✓ weather_alerts table ready")
60
+
61
+ # Create market_prices table if it doesn't exist
62
+ cursor.execute("""
63
+ CREATE TABLE IF NOT EXISTS market_prices (
64
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
65
+ crop_type VARCHAR(100),
66
+ market_name VARCHAR(200),
67
+ price_per_unit FLOAT,
68
+ unit VARCHAR(50),
69
+ trend VARCHAR(20),
70
+ date DATE,
71
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
72
+ )
73
+ """)
74
+ print("✓ market_prices table ready")
75
+
76
+ # Create disease_detections table if it doesn't exist
77
+ cursor.execute("""
78
+ CREATE TABLE IF NOT EXISTS disease_detections (
79
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
80
+ farm_id INTEGER,
81
+ disease_name VARCHAR(200),
82
+ confidence_score FLOAT,
83
+ treatment_recommendation TEXT,
84
+ image_path VARCHAR(500),
85
+ detected_at DATETIME DEFAULT CURRENT_TIMESTAMP,
86
+ FOREIGN KEY (farm_id) REFERENCES farms (id)
87
+ )
88
+ """)
89
+ print("✓ disease_detections table ready")
90
+
91
+ # Add new livestock fields to farms table
92
+ try:
93
+ # Check if farm_type column exists
94
+ cursor.execute("PRAGMA table_info(farms)")
95
+ columns = [column[1] for column in cursor.fetchall()]
96
+
97
+ if 'farm_type' not in columns:
98
+ cursor.execute("ALTER TABLE farms ADD COLUMN farm_type TEXT DEFAULT 'crop'")
99
+ print("✓ Added farm_type column to farms table")
100
+
101
+ if 'livestock_types' not in columns:
102
+ cursor.execute("ALTER TABLE farms ADD COLUMN livestock_types TEXT")
103
+ print("✓ Added livestock_types column to farms table")
104
+
105
+ if 'livestock_count' not in columns:
106
+ cursor.execute("ALTER TABLE farms ADD COLUMN livestock_count INTEGER")
107
+ print("✓ Added livestock_count column to farms table")
108
+
109
+ if 'housing_type' not in columns:
110
+ cursor.execute("ALTER TABLE farms ADD COLUMN housing_type TEXT")
111
+ print("✓ Added housing_type column to farms table")
112
+
113
+ if 'feeding_system' not in columns:
114
+ cursor.execute("ALTER TABLE farms ADD COLUMN feeding_system TEXT")
115
+ print("✓ Added feeding_system column to farms table")
116
+
117
+ if 'breed_info' not in columns:
118
+ cursor.execute("ALTER TABLE farms ADD COLUMN breed_info TEXT")
119
+ print("✓ Added breed_info column to farms table")
120
+
121
+ except Exception as e:
122
+ print(f"Error adding livestock columns: {e}")
123
+
124
+ print("✓ Multi-sector farm fields ready")
125
+
126
+ except Exception as e:
127
+ print(f"Error updating farms table: {str(e)}")
128
+
129
+ # Create livestock_records table
130
+ try:
131
+ cursor.execute('''
132
+ CREATE TABLE IF NOT EXISTS livestock_records (
133
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
134
+ farm_id INTEGER NOT NULL,
135
+ farmer_id INTEGER NOT NULL,
136
+ animal_id TEXT NOT NULL,
137
+ animal_type TEXT NOT NULL,
138
+ breed TEXT,
139
+ gender TEXT,
140
+ date_of_birth DATE,
141
+ date_acquired DATE NOT NULL DEFAULT CURRENT_DATE,
142
+ acquisition_method TEXT,
143
+ current_weight REAL,
144
+ vaccination_status TEXT,
145
+ health_status TEXT DEFAULT 'healthy',
146
+ breeding_status TEXT,
147
+ milk_production_avg REAL,
148
+ egg_production_avg INTEGER,
149
+ is_active BOOLEAN DEFAULT 1,
150
+ status TEXT DEFAULT 'active',
151
+ notes TEXT,
152
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
153
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
154
+ FOREIGN KEY (farm_id) REFERENCES farms (id),
155
+ FOREIGN KEY (farmer_id) REFERENCES farmers (id)
156
+ )
157
+ ''')
158
+ print("✓ livestock_records table ready")
159
+ except Exception as e:
160
+ print(f"Error creating livestock_records table: {str(e)}")
161
+
162
+ # Create production_records table
163
+ try:
164
+ cursor.execute('''
165
+ CREATE TABLE IF NOT EXISTS production_records (
166
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
167
+ farm_id INTEGER NOT NULL,
168
+ farmer_id INTEGER NOT NULL,
169
+ livestock_id INTEGER,
170
+ production_date DATE NOT NULL DEFAULT CURRENT_DATE,
171
+ production_type TEXT NOT NULL,
172
+ quantity REAL NOT NULL,
173
+ unit TEXT NOT NULL,
174
+ quality_grade TEXT,
175
+ unit_price REAL,
176
+ total_value REAL,
177
+ buyer_info TEXT,
178
+ notes TEXT,
179
+ weather_conditions TEXT,
180
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
181
+ FOREIGN KEY (farm_id) REFERENCES farms (id),
182
+ FOREIGN KEY (farmer_id) REFERENCES farmers (id),
183
+ FOREIGN KEY (livestock_id) REFERENCES livestock_records (id)
184
+ )
185
+ ''')
186
+ print("✓ production_records table ready")
187
+ except Exception as e:
188
+ print(f"Error creating production_records table: {str(e)}")
189
+
190
+ # Create feed_records table
191
+ try:
192
+ cursor.execute('''
193
+ CREATE TABLE IF NOT EXISTS feed_records (
194
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
195
+ farm_id INTEGER NOT NULL,
196
+ farmer_id INTEGER NOT NULL,
197
+ feed_date DATE NOT NULL DEFAULT CURRENT_DATE,
198
+ feed_type TEXT NOT NULL,
199
+ feed_name TEXT NOT NULL,
200
+ quantity REAL NOT NULL,
201
+ unit TEXT NOT NULL DEFAULT 'kg',
202
+ cost_per_unit REAL,
203
+ total_cost REAL,
204
+ supplier TEXT,
205
+ target_animals TEXT,
206
+ animals_count INTEGER,
207
+ protein_content REAL,
208
+ energy_content REAL,
209
+ notes TEXT,
210
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
211
+ FOREIGN KEY (farm_id) REFERENCES farms (id),
212
+ FOREIGN KEY (farmer_id) REFERENCES farmers (id)
213
+ )
214
+ ''')
215
+ print("✓ feed_records table ready")
216
+ except Exception as e:
217
+ print(f"Error creating feed_records table: {str(e)}")
218
+
219
+ # Create health_records table
220
+ try:
221
+ cursor.execute('''
222
+ CREATE TABLE IF NOT EXISTS health_records (
223
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
224
+ farm_id INTEGER NOT NULL,
225
+ farmer_id INTEGER NOT NULL,
226
+ livestock_id INTEGER NOT NULL,
227
+ event_date DATE NOT NULL DEFAULT CURRENT_DATE,
228
+ event_type TEXT NOT NULL,
229
+ vaccine_name TEXT,
230
+ vaccine_batch TEXT,
231
+ next_due_date DATE,
232
+ symptoms TEXT,
233
+ diagnosis TEXT,
234
+ treatment_given TEXT,
235
+ medication TEXT,
236
+ dosage TEXT,
237
+ vet_name TEXT,
238
+ vet_contact TEXT,
239
+ cost REAL,
240
+ follow_up_required BOOLEAN DEFAULT 0,
241
+ follow_up_date DATE,
242
+ recovery_status TEXT,
243
+ notes TEXT,
244
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
245
+ FOREIGN KEY (farm_id) REFERENCES farms (id),
246
+ FOREIGN KEY (farmer_id) REFERENCES farmers (id),
247
+ FOREIGN KEY (livestock_id) REFERENCES livestock_records (id)
248
+ )
249
+ ''')
250
+ print("✓ health_records table ready")
251
+ except Exception as e:
252
+ print(f"Error creating health_records table: {str(e)}")
253
+
254
+ # Create daily_tasks table
255
+ cursor.execute("""
256
+ CREATE TABLE IF NOT EXISTS daily_tasks (
257
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
258
+ farmer_id INTEGER,
259
+ farm_id INTEGER,
260
+ task_date DATE DEFAULT (date('now')),
261
+ task_type VARCHAR(100) NOT NULL,
262
+ task_title VARCHAR(200) NOT NULL,
263
+ task_description TEXT NOT NULL,
264
+ priority VARCHAR(20) DEFAULT 'medium',
265
+ estimated_duration INTEGER,
266
+ weather_dependent BOOLEAN DEFAULT 0,
267
+ crop_specific VARCHAR(100),
268
+ is_completed BOOLEAN DEFAULT 0,
269
+ completed_at DATETIME,
270
+ completion_notes TEXT,
271
+ completion_rating INTEGER,
272
+ sent_via_telegram BOOLEAN DEFAULT 0,
273
+ telegram_sent_at DATETIME,
274
+ reminder_sent BOOLEAN DEFAULT 0,
275
+ reminder_sent_at DATETIME,
276
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
277
+ created_by VARCHAR(50) DEFAULT 'system',
278
+ FOREIGN KEY (farmer_id) REFERENCES farmers (id),
279
+ FOREIGN KEY (farm_id) REFERENCES farms (id)
280
+ )
281
+ """)
282
+ print("✓ daily_tasks table ready")
283
+
284
+ # Create task_completions table if it doesn't exist
285
+ cursor.execute("""
286
+ CREATE TABLE IF NOT EXISTS task_completions (
287
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
288
+ task_id INTEGER,
289
+ farmer_id INTEGER,
290
+ completed_at DATETIME DEFAULT CURRENT_TIMESTAMP,
291
+ completion_method VARCHAR(50) DEFAULT 'dashboard',
292
+ completion_status VARCHAR(50) DEFAULT 'completed',
293
+ notes TEXT,
294
+ rating INTEGER,
295
+ issues_faced TEXT,
296
+ time_taken INTEGER,
297
+ FOREIGN KEY (task_id) REFERENCES daily_tasks (id),
298
+ FOREIGN KEY (farmer_id) REFERENCES farmers (id)
299
+ )
300
+ """)
301
+ print("✓ task_completions table ready")
302
+
303
+ conn.commit()
304
+ conn.close()
305
+
306
+ print("Database migration completed successfully!")
307
+ return True
308
+
309
+ except Exception as e:
310
+ print(f"Error during migration: {e}")
311
+ return False
312
+
313
+ if __name__ == "__main__":
314
+ migrate_database()
models.py ADDED
@@ -0,0 +1,775 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask_sqlalchemy import SQLAlchemy
2
+ from flask_login import UserMixin
3
+ from datetime import datetime, date
4
+ from werkzeug.security import generate_password_hash, check_password_hash
5
+ import json
6
+
7
+ # Initialize the SQLAlchemy db instance for the app to import
8
+ db = SQLAlchemy()
9
+
10
+
11
+ class Farmer(UserMixin, db.Model):
12
+ __tablename__ = 'farmers'
13
+ id = db.Column(db.Integer, primary_key=True)
14
+ name = db.Column(db.String(200), nullable=False)
15
+ age = db.Column(db.Integer, nullable=True)
16
+ gender = db.Column(db.String(20), nullable=True)
17
+ aadhaar_id = db.Column(db.String(50), unique=True, nullable=False)
18
+ contact_number = db.Column(db.String(50), nullable=True)
19
+ address = db.Column(db.Text, nullable=True)
20
+ password_hash = db.Column(db.String(255), nullable=False)
21
+ telegram_chat_id = db.Column(db.String(100), nullable=True)
22
+ created_at = db.Column(db.DateTime, default=datetime.utcnow)
23
+
24
+ farms = db.relationship('Farm', backref='owner', lazy=True)
25
+
26
+ def set_password(self, password: str):
27
+ self.password_hash = generate_password_hash(password)
28
+
29
+ def check_password(self, password: str) -> bool:
30
+ if not self.password_hash:
31
+ return False
32
+ return check_password_hash(self.password_hash, password)
33
+
34
+ def __repr__(self):
35
+ return f'<Farmer {self.id} {self.name}>'
36
+
37
+ def as_dict(self):
38
+ return {
39
+ 'id': self.id,
40
+ 'name': self.name,
41
+ 'age': self.age,
42
+ 'gender': self.gender,
43
+ 'aadhaar_id': self.aadhaar_id,
44
+ 'contact_number': self.contact_number,
45
+ 'address': self.address,
46
+ 'telegram_chat_id': self.telegram_chat_id,
47
+ 'created_at': self.created_at.isoformat() if self.created_at else None
48
+ }
49
+
50
+
51
+ class Farm(db.Model):
52
+ __tablename__ = 'farms'
53
+ id = db.Column(db.Integer, primary_key=True)
54
+ farmer_id = db.Column(db.Integer, db.ForeignKey('farmers.id'), nullable=False)
55
+ farm_name = db.Column(db.String(200), nullable=False)
56
+ farm_size = db.Column(db.Float, nullable=True) # acres
57
+
58
+ # Multi-sector support
59
+ farm_type = db.Column(db.String(50), default='crop') # crop, dairy, poultry, livestock, fishery, mixed
60
+
61
+ # Crop farming fields
62
+ irrigation_type = db.Column(db.String(100), nullable=True)
63
+ crop_types = db.Column(db.Text, nullable=True) # comma-separated names for quick display
64
+ crop_details = db.Column(db.Text, nullable=True) # JSON string for detailed crop info
65
+
66
+ # Livestock/Dairy/Poultry fields
67
+ livestock_types = db.Column(db.Text, nullable=True) # JSON string: cows, buffaloes, goats, etc.
68
+ livestock_count = db.Column(db.Integer, nullable=True) # Total number of animals
69
+ housing_type = db.Column(db.String(100), nullable=True) # shed, barn, free-range, cages
70
+ feeding_system = db.Column(db.String(100), nullable=True) # automatic, manual, grazing, mixed
71
+ breed_info = db.Column(db.Text, nullable=True) # JSON string for breed details
72
+
73
+ # Common fields
74
+ latitude = db.Column(db.Float, nullable=True)
75
+ longitude = db.Column(db.Float, nullable=True)
76
+ field_coordinates = db.Column(db.Text, nullable=True) # JSON string of coordinates
77
+ weather_alerts_enabled = db.Column(db.Boolean, default=True, nullable=False) # Enable/disable weather alerts
78
+ created_at = db.Column(db.DateTime, default=datetime.utcnow)
79
+
80
+ soil_data = db.relationship('SoilData', backref='farm', lazy=True)
81
+
82
+ def set_crop_types(self, types_list):
83
+ try:
84
+ if isinstance(types_list, (list, tuple)):
85
+ self.crop_types = ','.join([str(t).strip() for t in types_list if t])
86
+ else:
87
+ self.crop_types = str(types_list)
88
+ except Exception:
89
+ self.crop_types = None
90
+
91
+ def get_crop_types(self):
92
+ if not self.crop_types:
93
+ return []
94
+ return [t.strip() for t in self.crop_types.split(',') if t.strip()]
95
+
96
+ def set_crop_details(self, details):
97
+ try:
98
+ self.crop_details = json.dumps(details or [])
99
+ except Exception:
100
+ self.crop_details = json.dumps([])
101
+
102
+ def get_crop_details(self):
103
+ try:
104
+ return json.loads(self.crop_details) if self.crop_details else []
105
+ except Exception:
106
+ return []
107
+
108
+ def set_livestock_types(self, livestock_data):
109
+ """Set livestock types and details as JSON"""
110
+ try:
111
+ self.livestock_types = json.dumps(livestock_data or [])
112
+ except Exception:
113
+ self.livestock_types = json.dumps([])
114
+
115
+ def get_livestock_types(self):
116
+ """Get livestock types and details as list"""
117
+ try:
118
+ return json.loads(self.livestock_types) if self.livestock_types else []
119
+ except Exception:
120
+ return []
121
+
122
+ def set_breed_info(self, breed_data):
123
+ """Set breed information as JSON"""
124
+ try:
125
+ self.breed_info = json.dumps(breed_data or {})
126
+ except Exception:
127
+ self.breed_info = json.dumps({})
128
+
129
+ def get_breed_info(self):
130
+ """Get breed information as dictionary"""
131
+ try:
132
+ return json.loads(self.breed_info) if self.breed_info else {}
133
+ except Exception:
134
+ return {}
135
+
136
+ def get_farm_type_display(self):
137
+ """Get user-friendly farm type display name"""
138
+ type_mapping = {
139
+ 'crop': 'Crop Farming',
140
+ 'dairy': 'Dairy Farming',
141
+ 'poultry': 'Poultry Farming',
142
+ 'livestock': 'Livestock Farming',
143
+ 'fishery': 'Fish Farming',
144
+ 'mixed': 'Mixed Farming'
145
+ }
146
+ return type_mapping.get(self.farm_type, 'Crop Farming')
147
+
148
+ def __repr__(self):
149
+ return f'<Farm {self.id} {self.farm_name}>'
150
+
151
+ def as_dict(self):
152
+ return {
153
+ 'id': self.id,
154
+ 'farmer_id': self.farmer_id,
155
+ 'farm_name': self.farm_name,
156
+ 'farm_size': self.farm_size,
157
+ 'farm_type': self.farm_type,
158
+ 'farm_type_display': self.get_farm_type_display(),
159
+ 'irrigation_type': self.irrigation_type,
160
+ 'latitude': self.latitude,
161
+ 'longitude': self.longitude,
162
+ 'field_coordinates': json.loads(self.field_coordinates) if self.field_coordinates else None,
163
+ 'crop_types': self.get_crop_types(),
164
+ 'crop_details': self.get_crop_details(),
165
+ 'livestock_types': self.get_livestock_types(),
166
+ 'livestock_count': self.livestock_count,
167
+ 'housing_type': self.housing_type,
168
+ 'feeding_system': self.feeding_system,
169
+ 'breed_info': self.get_breed_info(),
170
+ 'weather_alerts_enabled': self.weather_alerts_enabled,
171
+ 'created_at': self.created_at.isoformat() if self.created_at else None
172
+ }
173
+
174
+
175
+ class SoilData(db.Model):
176
+ __tablename__ = 'soil_data'
177
+ id = db.Column(db.Integer, primary_key=True)
178
+ farm_id = db.Column(db.Integer, db.ForeignKey('farms.id'), nullable=False)
179
+ soil_type = db.Column(db.String(200), nullable=True)
180
+ ph_level = db.Column(db.Float, nullable=True)
181
+ nitrogen_level = db.Column(db.Float, nullable=True)
182
+ phosphorus_level = db.Column(db.Float, nullable=True)
183
+ potassium_level = db.Column(db.Float, nullable=True)
184
+ moisture_percentage = db.Column(db.Float, nullable=True)
185
+ # NOTE: some deployments may have older schema without recorded_at column.
186
+ # Keep as optional attribute access in as_dict to avoid OperationalError.
187
+
188
+ def __repr__(self):
189
+ return f'<SoilData {self.id} farm={self.farm_id}>'
190
+
191
+ def as_dict(self):
192
+ rec_at = getattr(self, 'recorded_at', None)
193
+ return {
194
+ 'id': self.id,
195
+ 'farm_id': self.farm_id,
196
+ 'soil_type': self.soil_type,
197
+ 'ph_level': self.ph_level,
198
+ 'nitrogen_level': self.nitrogen_level,
199
+ 'phosphorus_level': self.phosphorus_level,
200
+ 'potassium_level': self.potassium_level,
201
+ 'moisture_percentage': self.moisture_percentage,
202
+ 'recorded_at': rec_at.isoformat() if rec_at else None
203
+ }
204
+
205
+
206
+ class FarmingActivity(db.Model):
207
+ __tablename__ = 'farming_activities'
208
+ id = db.Column(db.Integer, primary_key=True)
209
+ farmer_id = db.Column(db.Integer, db.ForeignKey('farmers.id'), nullable=False)
210
+ farm_id = db.Column(db.Integer, db.ForeignKey('farms.id'), nullable=True)
211
+ activity_type = db.Column(db.String(200), nullable=True)
212
+ scheduled_date = db.Column(db.DateTime, nullable=True)
213
+ notes = db.Column(db.Text, nullable=True)
214
+ created_at = db.Column(db.DateTime, default=datetime.utcnow)
215
+
216
+ def __repr__(self):
217
+ return f'<FarmingActivity {self.id} {self.activity_type}>'
218
+
219
+ def as_dict(self):
220
+ return {
221
+ 'id': self.id,
222
+ 'farmer_id': self.farmer_id,
223
+ 'farm_id': self.farm_id,
224
+ 'activity_type': self.activity_type,
225
+ 'scheduled_date': self.scheduled_date.isoformat() if self.scheduled_date else None,
226
+ 'notes': self.notes,
227
+ 'created_at': self.created_at.isoformat() if self.created_at else None
228
+ }
229
+
230
+
231
+ class WeatherData(db.Model):
232
+ __tablename__ = 'weather_data'
233
+ id = db.Column(db.Integer, primary_key=True)
234
+ farm_id = db.Column(db.Integer, db.ForeignKey('farms.id'), nullable=True)
235
+ date = db.Column(db.Date, default=date.today)
236
+ data = db.Column(db.Text, nullable=True) # JSON blob of weather info
237
+ created_at = db.Column(db.DateTime, default=datetime.utcnow)
238
+
239
+ def get_data(self):
240
+ try:
241
+ return json.loads(self.data) if self.data else {}
242
+ except Exception:
243
+ return {}
244
+
245
+ def __repr__(self):
246
+ return f'<WeatherData {self.id} farm={self.farm_id} date={self.date}>'
247
+
248
+ def as_dict(self):
249
+ return {
250
+ 'id': self.id,
251
+ 'farm_id': self.farm_id,
252
+ 'date': self.date.isoformat() if self.date else None,
253
+ 'data': self.get_data(),
254
+ 'created_at': self.created_at.isoformat() if self.created_at else None
255
+ }
256
+
257
+
258
+ class DailyAdvisory(db.Model):
259
+ __tablename__ = 'daily_advisories'
260
+ id = db.Column(db.Integer, primary_key=True)
261
+ farm_id = db.Column(db.Integer, db.ForeignKey('farms.id'), nullable=False)
262
+ date = db.Column(db.Date, nullable=False)
263
+ # Updated columns to match the existing database schema
264
+ task_to_do = db.Column(db.Text, nullable=True)
265
+ task_to_avoid = db.Column(db.Text, nullable=True)
266
+ reason_explanation = db.Column(db.Text, nullable=True)
267
+ crop_stage = db.Column(db.String(50), nullable=True)
268
+ weather_context = db.Column(db.Text, nullable=True) # JSON blob
269
+ gemini_response = db.Column(db.Text, nullable=True) # raw AI response JSON
270
+ created_at = db.Column(db.DateTime, default=datetime.utcnow)
271
+
272
+ def __repr__(self):
273
+ return f'<DailyAdvisory {self.id} farm={self.farm_id} date={self.date}>'
274
+
275
+ def as_dict(self):
276
+ return {
277
+ 'id': self.id,
278
+ 'farm_id': self.farm_id,
279
+ 'date': self.date.isoformat() if self.date else None,
280
+ 'task_to_do': self.task_to_do,
281
+ 'task_to_avoid': self.task_to_avoid,
282
+ 'reason_explanation': self.reason_explanation,
283
+ 'crop_stage': self.crop_stage,
284
+ 'weather_context': json.loads(self.weather_context) if self.weather_context else None,
285
+ 'gemini_response': json.loads(self.gemini_response) if self.gemini_response else None,
286
+ 'created_at': self.created_at.isoformat() if self.created_at else None
287
+ }
288
+
289
+
290
+ class SMSLog(db.Model):
291
+ __tablename__ = 'sms_logs'
292
+ id = db.Column(db.Integer, primary_key=True)
293
+ # Adjusted to match the current DB schema in instance/farm_management.db
294
+ farmer_id = db.Column(db.Integer, db.ForeignKey('farmers.id'), nullable=True)
295
+ phone_number = db.Column(db.String(15), nullable=True)
296
+ message_content = db.Column(db.Text, nullable=True)
297
+ twilio_sid = db.Column(db.String(100), nullable=True)
298
+ status = db.Column(db.String(20), nullable=True)
299
+ sent_at = db.Column(db.DateTime, default=datetime.utcnow)
300
+ delivered_at = db.Column(db.DateTime, nullable=True)
301
+ error_message = db.Column(db.Text, nullable=True)
302
+
303
+ def as_dict(self):
304
+ return {
305
+ 'id': self.id,
306
+ 'farmer_id': self.farmer_id,
307
+ 'phone_number': self.phone_number,
308
+ 'message_content': self.message_content,
309
+ 'twilio_sid': self.twilio_sid,
310
+ 'status': self.status,
311
+ 'sent_at': self.sent_at.isoformat() if self.sent_at else None,
312
+ 'delivered_at': self.delivered_at.isoformat() if self.delivered_at else None,
313
+ 'error_message': self.error_message
314
+ }
315
+
316
+ def __repr__(self):
317
+ return f'<SMSLog {self.id} to={self.recipient} status={self.status}>'
318
+
319
+
320
+ class AdminUser(db.Model):
321
+ __tablename__ = 'admin_users'
322
+ id = db.Column(db.Integer, primary_key=True)
323
+ username = db.Column(db.String(150), unique=True, nullable=False)
324
+ password_hash = db.Column(db.String(255), nullable=False)
325
+ last_login = db.Column(db.DateTime, nullable=True)
326
+ created_at = db.Column(db.DateTime, default=datetime.utcnow)
327
+
328
+ def set_password(self, password: str):
329
+ self.password_hash = generate_password_hash(password)
330
+
331
+ def check_password(self, password: str) -> bool:
332
+ if not self.password_hash:
333
+ return False
334
+ return check_password_hash(self.password_hash, password)
335
+
336
+ def __repr__(self):
337
+ return f'<AdminUser {self.username}>'
338
+
339
+
340
+ class YearlyPlan(db.Model):
341
+ __tablename__ = 'yearly_plans'
342
+ id = db.Column(db.Integer, primary_key=True)
343
+ farmer_id = db.Column(db.Integer, db.ForeignKey('farmers.id'), nullable=False)
344
+ year = db.Column(db.Integer, nullable=False)
345
+ plan_json = db.Column(db.Text, nullable=True) # JSON blob of the full plan
346
+ summary_text = db.Column(db.Text, nullable=True) # short text summary for messages
347
+ created_at = db.Column(db.DateTime, default=datetime.utcnow)
348
+
349
+ def as_dict(self):
350
+ try:
351
+ return {
352
+ 'id': self.id,
353
+ 'farmer_id': self.farmer_id,
354
+ 'year': self.year,
355
+ 'plan': json.loads(self.plan_json) if self.plan_json else {},
356
+ 'summary': self.summary_text,
357
+ 'created_at': self.created_at.isoformat() if self.created_at else None
358
+ }
359
+ except Exception:
360
+ return {
361
+ 'id': self.id,
362
+ 'farmer_id': self.farmer_id,
363
+ 'year': self.year,
364
+ 'plan': {},
365
+ 'summary': self.summary_text,
366
+ 'created_at': self.created_at.isoformat() if self.created_at else None
367
+ }
368
+
369
+
370
+ class WeatherAlert(db.Model):
371
+ __tablename__ = 'weather_alerts'
372
+ id = db.Column(db.Integer, primary_key=True)
373
+ farm_id = db.Column(db.Integer, db.ForeignKey('farms.id'), nullable=False)
374
+ alert_type = db.Column(db.String(100), nullable=False) # 'rain', 'storm', 'drought', 'frost', 'heat_wave'
375
+ severity = db.Column(db.String(50), nullable=False) # 'low', 'medium', 'high', 'critical'
376
+ title = db.Column(db.String(200), nullable=False)
377
+ message = db.Column(db.Text, nullable=False)
378
+ recommended_action = db.Column(db.Text, nullable=True)
379
+ weather_data = db.Column(db.Text, nullable=True) # JSON string
380
+ is_active = db.Column(db.Boolean, default=True)
381
+ is_sent = db.Column(db.Boolean, default=False)
382
+ created_at = db.Column(db.DateTime, default=datetime.utcnow)
383
+ expires_at = db.Column(db.DateTime, nullable=True)
384
+
385
+ def as_dict(self):
386
+ return {
387
+ 'id': self.id,
388
+ 'farm_id': self.farm_id,
389
+ 'alert_type': self.alert_type,
390
+ 'severity': self.severity,
391
+ 'title': self.title,
392
+ 'message': self.message,
393
+ 'recommended_action': self.recommended_action,
394
+ 'is_active': self.is_active,
395
+ 'is_sent': self.is_sent,
396
+ 'created_at': self.created_at.isoformat() if self.created_at else None,
397
+ 'expires_at': self.expires_at.isoformat() if self.expires_at else None
398
+ }
399
+
400
+
401
+ class MarketPrice(db.Model):
402
+ __tablename__ = 'market_prices'
403
+ id = db.Column(db.Integer, primary_key=True)
404
+ crop_name = db.Column(db.String(100), nullable=False)
405
+ market_name = db.Column(db.String(200), nullable=False)
406
+ state = db.Column(db.String(100), nullable=False)
407
+ district = db.Column(db.String(100), nullable=False)
408
+ min_price = db.Column(db.Float, nullable=True)
409
+ max_price = db.Column(db.Float, nullable=True)
410
+ avg_price = db.Column(db.Float, nullable=False)
411
+ price_unit = db.Column(db.String(50), default='per quintal')
412
+ price_date = db.Column(db.Date, nullable=False)
413
+ source = db.Column(db.String(100), nullable=True) # 'government', 'market_api', 'manual'
414
+ created_at = db.Column(db.DateTime, default=datetime.utcnow)
415
+
416
+ def as_dict(self):
417
+ return {
418
+ 'id': self.id,
419
+ 'crop_name': self.crop_name,
420
+ 'market_name': self.market_name,
421
+ 'state': self.state,
422
+ 'district': self.district,
423
+ 'min_price': self.min_price,
424
+ 'max_price': self.max_price,
425
+ 'avg_price': self.avg_price,
426
+ 'price_unit': self.price_unit,
427
+ 'price_date': self.price_date.isoformat() if self.price_date else None,
428
+ 'source': self.source,
429
+ 'created_at': self.created_at.isoformat() if self.created_at else None
430
+ }
431
+
432
+
433
+ class DiseaseDetection(db.Model):
434
+ __tablename__ = 'disease_detections'
435
+ id = db.Column(db.Integer, primary_key=True)
436
+ farm_id = db.Column(db.Integer, db.ForeignKey('farms.id'), nullable=False)
437
+ crop_name = db.Column(db.String(100), nullable=False)
438
+ disease_name = db.Column(db.String(200), nullable=True)
439
+ confidence_score = db.Column(db.Float, nullable=True)
440
+ symptoms = db.Column(db.Text, nullable=True)
441
+ treatment = db.Column(db.Text, nullable=True)
442
+ prevention = db.Column(db.Text, nullable=True)
443
+ image_path = db.Column(db.String(500), nullable=True)
444
+ ai_analysis = db.Column(db.Text, nullable=True) # JSON string
445
+ status = db.Column(db.String(50), default='detected') # 'detected', 'treating', 'resolved'
446
+ severity = db.Column(db.String(50), nullable=True) # 'mild', 'moderate', 'severe'
447
+ detected_at = db.Column(db.DateTime, default=datetime.utcnow)
448
+ resolved_at = db.Column(db.DateTime, nullable=True)
449
+
450
+ def as_dict(self):
451
+ return {
452
+ 'id': self.id,
453
+ 'farm_id': self.farm_id,
454
+ 'crop_name': self.crop_name,
455
+ 'disease_name': self.disease_name,
456
+ 'confidence_score': self.confidence_score,
457
+ 'symptoms': self.symptoms,
458
+ 'treatment': self.treatment,
459
+ 'prevention': self.prevention,
460
+ 'status': self.status,
461
+ 'severity': self.severity,
462
+ 'detected_at': self.detected_at.isoformat() if self.detected_at else None,
463
+ 'resolved_at': self.resolved_at.isoformat() if self.resolved_at else None
464
+ }
465
+
466
+
467
+ class DailyTask(db.Model):
468
+ __tablename__ = 'daily_tasks'
469
+ id = db.Column(db.Integer, primary_key=True)
470
+ farmer_id = db.Column(db.Integer, db.ForeignKey('farmers.id'), nullable=False)
471
+ farm_id = db.Column(db.Integer, db.ForeignKey('farms.id'), nullable=True)
472
+ task_date = db.Column(db.Date, nullable=False, default=date.today)
473
+ task_type = db.Column(db.String(100), nullable=False) # 'irrigation', 'fertilizing', 'pest_control', etc.
474
+ task_title = db.Column(db.String(200), nullable=False)
475
+ task_description = db.Column(db.Text, nullable=False)
476
+ priority = db.Column(db.String(20), default='medium') # 'high', 'medium', 'low'
477
+ estimated_duration = db.Column(db.Integer, nullable=True) # in minutes
478
+ weather_dependent = db.Column(db.Boolean, default=False)
479
+ crop_specific = db.Column(db.String(100), nullable=True)
480
+
481
+ # Completion tracking
482
+ is_completed = db.Column(db.Boolean, default=False)
483
+ completed_at = db.Column(db.DateTime, nullable=True)
484
+ completion_notes = db.Column(db.Text, nullable=True)
485
+ completion_rating = db.Column(db.Integer, nullable=True) # 1-5 rating from farmer
486
+
487
+ # Notification tracking
488
+ sent_via_telegram = db.Column(db.Boolean, default=False)
489
+ telegram_sent_at = db.Column(db.DateTime, nullable=True)
490
+ reminder_sent = db.Column(db.Boolean, default=False)
491
+ reminder_sent_at = db.Column(db.DateTime, nullable=True)
492
+
493
+ # Meta
494
+ created_at = db.Column(db.DateTime, default=datetime.utcnow)
495
+ created_by = db.Column(db.String(50), default='system') # 'system', 'ai', 'manual'
496
+
497
+ def as_dict(self):
498
+ return {
499
+ 'id': self.id,
500
+ 'farmer_id': self.farmer_id,
501
+ 'farm_id': self.farm_id,
502
+ 'task_date': self.task_date.isoformat() if self.task_date else None,
503
+ 'task_type': self.task_type,
504
+ 'task_title': self.task_title,
505
+ 'task_description': self.task_description,
506
+ 'priority': self.priority,
507
+ 'estimated_duration': self.estimated_duration,
508
+ 'weather_dependent': self.weather_dependent,
509
+ 'crop_specific': self.crop_specific,
510
+ 'is_completed': self.is_completed,
511
+ 'completed_at': self.completed_at.isoformat() if self.completed_at else None,
512
+ 'completion_notes': self.completion_notes,
513
+ 'completion_rating': self.completion_rating,
514
+ 'sent_via_telegram': self.sent_via_telegram,
515
+ 'telegram_sent_at': self.telegram_sent_at.isoformat() if self.telegram_sent_at else None,
516
+ 'reminder_sent': self.reminder_sent,
517
+ 'reminder_sent_at': self.reminder_sent_at.isoformat() if self.reminder_sent_at else None,
518
+ 'created_at': self.created_at.isoformat() if self.created_at else None,
519
+ 'created_by': self.created_by
520
+ }
521
+
522
+ def __repr__(self):
523
+ return f'<DailyTask {self.id} {self.task_title} for {self.task_date}>'
524
+
525
+
526
+ class TaskCompletion(db.Model):
527
+ __tablename__ = 'task_completions'
528
+ id = db.Column(db.Integer, primary_key=True)
529
+ task_id = db.Column(db.Integer, db.ForeignKey('daily_tasks.id'), nullable=False)
530
+ farmer_id = db.Column(db.Integer, db.ForeignKey('farmers.id'), nullable=False)
531
+ completed_at = db.Column(db.DateTime, default=datetime.utcnow)
532
+ completion_method = db.Column(db.String(50), default='dashboard') # 'dashboard', 'telegram', 'mobile'
533
+ completion_status = db.Column(db.String(50), default='completed') # 'completed', 'partially_completed', 'skipped'
534
+ notes = db.Column(db.Text, nullable=True)
535
+ rating = db.Column(db.Integer, nullable=True) # 1-5 rating
536
+ issues_faced = db.Column(db.Text, nullable=True)
537
+ time_taken = db.Column(db.Integer, nullable=True) # actual time taken in minutes
538
+
539
+ def as_dict(self):
540
+ return {
541
+ 'id': self.id,
542
+ 'task_id': self.task_id,
543
+ 'farmer_id': self.farmer_id,
544
+ 'completed_at': self.completed_at.isoformat() if self.completed_at else None,
545
+ 'completion_method': self.completion_method,
546
+ 'completion_status': self.completion_status,
547
+ 'notes': self.notes,
548
+ 'rating': self.rating,
549
+ 'issues_faced': self.issues_faced,
550
+ 'time_taken': self.time_taken
551
+ }
552
+
553
+ def __repr__(self):
554
+ return f'<TaskCompletion {self.id} for task {self.task_id}>'
555
+
556
+
557
+ # ==================== LIVESTOCK MANAGEMENT MODELS ====================
558
+
559
+ class LivestockRecord(db.Model):
560
+ """Model for individual animal records in livestock farming"""
561
+ __tablename__ = 'livestock_records'
562
+
563
+ id = db.Column(db.Integer, primary_key=True)
564
+ farm_id = db.Column(db.Integer, db.ForeignKey('farms.id'), nullable=False)
565
+ farmer_id = db.Column(db.Integer, db.ForeignKey('farmers.id'), nullable=False)
566
+
567
+ # Animal identification
568
+ animal_id = db.Column(db.String(50), nullable=False) # Tag number or custom ID
569
+ animal_type = db.Column(db.String(50), nullable=False) # cow, buffalo, goat, chicken, fish
570
+ breed = db.Column(db.String(100), nullable=True)
571
+ gender = db.Column(db.String(10), nullable=True) # male, female
572
+
573
+ # Basic info
574
+ date_of_birth = db.Column(db.Date, nullable=True)
575
+ date_acquired = db.Column(db.Date, nullable=False, default=datetime.utcnow)
576
+ acquisition_method = db.Column(db.String(50), nullable=True) # purchased, born, gifted
577
+ current_weight = db.Column(db.Float, nullable=True) # in kg
578
+
579
+ # Health and breeding
580
+ vaccination_status = db.Column(db.Text, nullable=True) # JSON of vaccination records
581
+ health_status = db.Column(db.String(50), default='healthy') # healthy, sick, treatment
582
+ breeding_status = db.Column(db.String(50), nullable=True) # pregnant, lactating, dry
583
+
584
+ # Production tracking (for dairy, poultry, etc.)
585
+ milk_production_avg = db.Column(db.Float, nullable=True) # liters per day
586
+ egg_production_avg = db.Column(db.Integer, nullable=True) # eggs per day
587
+
588
+ # Status
589
+ is_active = db.Column(db.Boolean, default=True)
590
+ status = db.Column(db.String(50), default='active') # active, sold, deceased, transferred
591
+ notes = db.Column(db.Text, nullable=True)
592
+
593
+ created_at = db.Column(db.DateTime, default=datetime.utcnow)
594
+ updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
595
+
596
+ def as_dict(self):
597
+ return {
598
+ 'id': self.id,
599
+ 'farm_id': self.farm_id,
600
+ 'animal_id': self.animal_id,
601
+ 'animal_type': self.animal_type,
602
+ 'breed': self.breed,
603
+ 'gender': self.gender,
604
+ 'date_of_birth': self.date_of_birth.isoformat() if self.date_of_birth else None,
605
+ 'date_acquired': self.date_acquired.isoformat() if self.date_acquired else None,
606
+ 'current_weight': self.current_weight,
607
+ 'health_status': self.health_status,
608
+ 'breeding_status': self.breeding_status,
609
+ 'milk_production_avg': self.milk_production_avg,
610
+ 'egg_production_avg': self.egg_production_avg,
611
+ 'is_active': self.is_active,
612
+ 'status': self.status,
613
+ 'created_at': self.created_at.isoformat() if self.created_at else None
614
+ }
615
+
616
+ def __repr__(self):
617
+ return f'<LivestockRecord {self.animal_id} - {self.animal_type}>'
618
+
619
+
620
+ class ProductionRecord(db.Model):
621
+ """Model for tracking daily production (milk, eggs, etc.)"""
622
+ __tablename__ = 'production_records'
623
+
624
+ id = db.Column(db.Integer, primary_key=True)
625
+ farm_id = db.Column(db.Integer, db.ForeignKey('farms.id'), nullable=False)
626
+ farmer_id = db.Column(db.Integer, db.ForeignKey('farmers.id'), nullable=False)
627
+ livestock_id = db.Column(db.Integer, db.ForeignKey('livestock_records.id'), nullable=True)
628
+
629
+ # Production details
630
+ production_date = db.Column(db.Date, nullable=False, default=datetime.utcnow)
631
+ production_type = db.Column(db.String(50), nullable=False) # milk, eggs, meat, fish
632
+ quantity = db.Column(db.Float, nullable=False) # liters, pieces, kg
633
+ unit = db.Column(db.String(20), nullable=False) # liters, pieces, kg
634
+ quality_grade = db.Column(db.String(20), nullable=True) # A, B, C or premium, standard
635
+
636
+ # Economic data
637
+ unit_price = db.Column(db.Float, nullable=True) # price per unit
638
+ total_value = db.Column(db.Float, nullable=True) # total value
639
+ buyer_info = db.Column(db.String(200), nullable=True) # where sold
640
+
641
+ # Additional info
642
+ notes = db.Column(db.Text, nullable=True)
643
+ weather_conditions = db.Column(db.String(100), nullable=True)
644
+
645
+ created_at = db.Column(db.DateTime, default=datetime.utcnow)
646
+
647
+ def as_dict(self):
648
+ return {
649
+ 'id': self.id,
650
+ 'farm_id': self.farm_id,
651
+ 'livestock_id': self.livestock_id,
652
+ 'production_date': self.production_date.isoformat() if self.production_date else None,
653
+ 'production_type': self.production_type,
654
+ 'quantity': self.quantity,
655
+ 'unit': self.unit,
656
+ 'quality_grade': self.quality_grade,
657
+ 'unit_price': self.unit_price,
658
+ 'total_value': self.total_value,
659
+ 'buyer_info': self.buyer_info,
660
+ 'created_at': self.created_at.isoformat() if self.created_at else None
661
+ }
662
+
663
+ def __repr__(self):
664
+ return f'<ProductionRecord {self.production_type} - {self.quantity} {self.unit}>'
665
+
666
+
667
+ class FeedRecord(db.Model):
668
+ """Model for tracking feed consumption and costs"""
669
+ __tablename__ = 'feed_records'
670
+
671
+ id = db.Column(db.Integer, primary_key=True)
672
+ farm_id = db.Column(db.Integer, db.ForeignKey('farms.id'), nullable=False)
673
+ farmer_id = db.Column(db.Integer, db.ForeignKey('farmers.id'), nullable=False)
674
+
675
+ # Feed details
676
+ feed_date = db.Column(db.Date, nullable=False, default=datetime.utcnow)
677
+ feed_type = db.Column(db.String(100), nullable=False) # grass, concentrate, grain, etc.
678
+ feed_name = db.Column(db.String(200), nullable=False) # specific feed name/brand
679
+ quantity = db.Column(db.Float, nullable=False) # in kg or specified unit
680
+ unit = db.Column(db.String(20), nullable=False, default='kg')
681
+
682
+ # Cost tracking
683
+ cost_per_unit = db.Column(db.Float, nullable=True)
684
+ total_cost = db.Column(db.Float, nullable=True)
685
+ supplier = db.Column(db.String(200), nullable=True)
686
+
687
+ # Target animals
688
+ target_animals = db.Column(db.Text, nullable=True) # JSON list of animal IDs or types
689
+ animals_count = db.Column(db.Integer, nullable=True) # number of animals fed
690
+
691
+ # Nutritional info
692
+ protein_content = db.Column(db.Float, nullable=True) # percentage
693
+ energy_content = db.Column(db.Float, nullable=True) # kcal/kg
694
+
695
+ notes = db.Column(db.Text, nullable=True)
696
+ created_at = db.Column(db.DateTime, default=datetime.utcnow)
697
+
698
+ def as_dict(self):
699
+ return {
700
+ 'id': self.id,
701
+ 'farm_id': self.farm_id,
702
+ 'feed_date': self.feed_date.isoformat() if self.feed_date else None,
703
+ 'feed_type': self.feed_type,
704
+ 'feed_name': self.feed_name,
705
+ 'quantity': self.quantity,
706
+ 'unit': self.unit,
707
+ 'cost_per_unit': self.cost_per_unit,
708
+ 'total_cost': self.total_cost,
709
+ 'supplier': self.supplier,
710
+ 'animals_count': self.animals_count,
711
+ 'created_at': self.created_at.isoformat() if self.created_at else None
712
+ }
713
+
714
+ def __repr__(self):
715
+ return f'<FeedRecord {self.feed_name} - {self.quantity} {self.unit}>'
716
+
717
+
718
+ class HealthRecord(db.Model):
719
+ """Model for tracking animal health, vaccinations, and treatments"""
720
+ __tablename__ = 'health_records'
721
+
722
+ id = db.Column(db.Integer, primary_key=True)
723
+ farm_id = db.Column(db.Integer, db.ForeignKey('farms.id'), nullable=False)
724
+ farmer_id = db.Column(db.Integer, db.ForeignKey('farmers.id'), nullable=False)
725
+ livestock_id = db.Column(db.Integer, db.ForeignKey('livestock_records.id'), nullable=False)
726
+
727
+ # Health event details
728
+ event_date = db.Column(db.Date, nullable=False, default=datetime.utcnow)
729
+ event_type = db.Column(db.String(50), nullable=False) # vaccination, treatment, checkup, illness
730
+
731
+ # Vaccination details
732
+ vaccine_name = db.Column(db.String(200), nullable=True)
733
+ vaccine_batch = db.Column(db.String(100), nullable=True)
734
+ next_due_date = db.Column(db.Date, nullable=True)
735
+
736
+ # Treatment details
737
+ symptoms = db.Column(db.Text, nullable=True)
738
+ diagnosis = db.Column(db.Text, nullable=True)
739
+ treatment_given = db.Column(db.Text, nullable=True)
740
+ medication = db.Column(db.Text, nullable=True)
741
+ dosage = db.Column(db.String(100), nullable=True)
742
+
743
+ # Veterinary info
744
+ vet_name = db.Column(db.String(200), nullable=True)
745
+ vet_contact = db.Column(db.String(100), nullable=True)
746
+ cost = db.Column(db.Float, nullable=True)
747
+
748
+ # Follow-up
749
+ follow_up_required = db.Column(db.Boolean, default=False)
750
+ follow_up_date = db.Column(db.Date, nullable=True)
751
+ recovery_status = db.Column(db.String(50), nullable=True) # recovered, recovering, chronic
752
+
753
+ notes = db.Column(db.Text, nullable=True)
754
+ created_at = db.Column(db.DateTime, default=datetime.utcnow)
755
+
756
+ def as_dict(self):
757
+ return {
758
+ 'id': self.id,
759
+ 'farm_id': self.farm_id,
760
+ 'livestock_id': self.livestock_id,
761
+ 'event_date': self.event_date.isoformat() if self.event_date else None,
762
+ 'event_type': self.event_type,
763
+ 'vaccine_name': self.vaccine_name,
764
+ 'symptoms': self.symptoms,
765
+ 'diagnosis': self.diagnosis,
766
+ 'treatment_given': self.treatment_given,
767
+ 'vet_name': self.vet_name,
768
+ 'cost': self.cost,
769
+ 'recovery_status': self.recovery_status,
770
+ 'created_at': self.created_at.isoformat() if self.created_at else None
771
+ }
772
+
773
+ def __repr__(self):
774
+ return f'<HealthRecord {self.event_type} for livestock {self.livestock_id}>'
775
+
pdf_generator_service.py ADDED
@@ -0,0 +1,428 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ from datetime import datetime
4
+ from typing import Dict, Optional
5
+ import logging
6
+ try:
7
+ from reportlab.lib.pagesizes import letter, A4
8
+ from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle
9
+ from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
10
+ from reportlab.lib.units import inch
11
+ from reportlab.lib import colors
12
+ from reportlab.lib.enums import TA_CENTER, TA_LEFT
13
+ REPORTLAB_AVAILABLE = True
14
+ except ImportError:
15
+ REPORTLAB_AVAILABLE = False
16
+
17
+ # Configure logging
18
+ logging.basicConfig(level=logging.INFO)
19
+ logger = logging.getLogger(__name__)
20
+
21
+ class PDFGeneratorService:
22
+ """Service for generating PDF reports for yearly plans and other documents"""
23
+
24
+ def __init__(self, output_dir: str = "generated_pdfs"):
25
+ self.output_dir = output_dir
26
+ os.makedirs(output_dir, exist_ok=True)
27
+
28
+ if not REPORTLAB_AVAILABLE:
29
+ logger.warning("ReportLab not available. PDF generation will use fallback HTML to PDF")
30
+
31
+ def generate_yearly_plan_pdf(self, farmer_data: Dict, plan_data: Dict) -> Optional[str]:
32
+ """Generate PDF for yearly plan"""
33
+ try:
34
+ if REPORTLAB_AVAILABLE:
35
+ return self._generate_with_reportlab(farmer_data, plan_data)
36
+ else:
37
+ return self._generate_with_html_fallback(farmer_data, plan_data)
38
+
39
+ except Exception as e:
40
+ logger.error(f"Error generating yearly plan PDF: {str(e)}")
41
+ return None
42
+
43
+ def _generate_with_reportlab(self, farmer_data: Dict, plan_data: Dict) -> str:
44
+ """Generate PDF using ReportLab"""
45
+ try:
46
+ # Create filename
47
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
48
+ filename = f"yearly_plan_{farmer_data.get('name', 'farmer')}_{timestamp}.pdf"
49
+ filepath = os.path.join(self.output_dir, filename)
50
+
51
+ # Create document
52
+ doc = SimpleDocTemplate(filepath, pagesize=A4, topMargin=0.5*inch)
53
+ styles = getSampleStyleSheet()
54
+
55
+ # Custom styles
56
+ title_style = ParagraphStyle(
57
+ 'CustomTitle',
58
+ parent=styles['Heading1'],
59
+ fontSize=24,
60
+ textColor=colors.darkgreen,
61
+ spaceAfter=20,
62
+ alignment=TA_CENTER
63
+ )
64
+
65
+ heading_style = ParagraphStyle(
66
+ 'CustomHeading',
67
+ parent=styles['Heading2'],
68
+ fontSize=16,
69
+ textColor=colors.blue,
70
+ spaceAfter=12,
71
+ spaceBefore=20
72
+ )
73
+
74
+ story = []
75
+
76
+ # Title
77
+ story.append(Paragraph("🌾 Comprehensive Yearly Farming Plan", title_style))
78
+ story.append(Spacer(1, 20))
79
+
80
+ # Farmer Information
81
+ story.append(Paragraph("Farmer Information", heading_style))
82
+ farmer_info = [
83
+ ['Name:', farmer_data.get('name', 'N/A')],
84
+ ['Contact:', farmer_data.get('contact_number', 'N/A')],
85
+ ['Address:', farmer_data.get('address', 'N/A')],
86
+ ['Plan Year:', plan_data.get('year', datetime.now().year)]
87
+ ]
88
+
89
+ farmer_table = Table(farmer_info, colWidths=[2*inch, 4*inch])
90
+ farmer_table.setStyle(TableStyle([
91
+ ('FONTNAME', (0, 0), (-1, -1), 'Helvetica'),
92
+ ('FONTSIZE', (0, 0), (-1, -1), 12),
93
+ ('TEXTCOLOR', (0, 0), (0, -1), colors.darkblue),
94
+ ('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'),
95
+ ('VALIGN', (0, 0), (-1, -1), 'TOP'),
96
+ ('BOTTOMPADDING', (0, 0), (-1, -1), 8),
97
+ ]))
98
+ story.append(farmer_table)
99
+ story.append(Spacer(1, 20))
100
+
101
+ # Farm Plans
102
+ farms = plan_data.get('farms', [])
103
+ for i, farm in enumerate(farms):
104
+ story.append(Paragraph(f"Farm {i+1}: {farm.get('farm_name', 'Unknown Farm')}", heading_style))
105
+
106
+ # Parse farm plan
107
+ try:
108
+ farm_plan = json.loads(farm.get('plan', '{}')) if isinstance(farm.get('plan'), str) else farm.get('plan', {})
109
+
110
+ # Monthly Plan Table
111
+ if 'monthly_plan' in farm_plan:
112
+ story.append(Paragraph("Monthly Farming Schedule", styles['Heading3']))
113
+
114
+ monthly_data = [['Month', 'Planned Activities']]
115
+ for month_data in farm_plan['monthly_plan']:
116
+ month = month_data.get('month', '')
117
+ tasks = month_data.get('tasks', [])
118
+ tasks_text = '\n'.join(tasks[:3]) # Show max 3 tasks
119
+ monthly_data.append([month, tasks_text])
120
+
121
+ monthly_table = Table(monthly_data, colWidths=[1.5*inch, 4.5*inch])
122
+ monthly_table.setStyle(TableStyle([
123
+ ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
124
+ ('FONTSIZE', (0, 0), (-1, -1), 10),
125
+ ('BACKGROUND', (0, 0), (-1, 0), colors.lightgreen),
126
+ ('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
127
+ ('ALIGN', (0, 0), (-1, -1), 'LEFT'),
128
+ ('VALIGN', (0, 0), (-1, -1), 'TOP'),
129
+ ('GRID', (0, 0), (-1, -1), 1, colors.black),
130
+ ('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.lightgrey]),
131
+ ]))
132
+ story.append(monthly_table)
133
+ story.append(Spacer(1, 15))
134
+
135
+ # Crop Information
136
+ crops = farm_plan.get('crops', [])
137
+ if crops:
138
+ story.append(Paragraph("Crop Details", styles['Heading3']))
139
+ crop_data = [['Crop Name', 'Sowing Month', 'Area']]
140
+ for crop in crops:
141
+ crop_data.append([
142
+ crop.get('name', 'Unknown'),
143
+ crop.get('sowing_month', 'N/A'),
144
+ crop.get('area', 'N/A')
145
+ ])
146
+
147
+ crop_table = Table(crop_data, colWidths=[2*inch, 2*inch, 2*inch])
148
+ crop_table.setStyle(TableStyle([
149
+ ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
150
+ ('BACKGROUND', (0, 0), (-1, 0), colors.lightblue),
151
+ ('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
152
+ ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
153
+ ('GRID', (0, 0), (-1, -1), 1, colors.black),
154
+ ]))
155
+ story.append(crop_table)
156
+ story.append(Spacer(1, 15))
157
+
158
+ except Exception as e:
159
+ logger.error(f"Error processing farm plan: {str(e)}")
160
+ story.append(Paragraph("Plan details could not be processed", styles['Normal']))
161
+ story.append(Spacer(1, 10))
162
+
163
+ # Summary and Recommendations
164
+ story.append(Paragraph("Summary & Recommendations", heading_style))
165
+ summary_text = plan_data.get('summary', 'Comprehensive yearly plan generated with AI analysis.')
166
+ story.append(Paragraph(summary_text, styles['Normal']))
167
+ story.append(Spacer(1, 10))
168
+
169
+ # Footer
170
+ story.append(Spacer(1, 30))
171
+ footer_text = f"Generated on: {datetime.now().strftime('%B %d, %Y at %I:%M %p')}"
172
+ story.append(Paragraph(footer_text, styles['Normal']))
173
+ story.append(Paragraph("🌱 Powered by AI Agriculture Assistant", styles['Normal']))
174
+
175
+ # Build PDF
176
+ doc.build(story)
177
+
178
+ logger.info(f"Successfully generated PDF: {filepath}")
179
+ return filepath
180
+
181
+ except Exception as e:
182
+ logger.error(f"Error generating ReportLab PDF: {str(e)}")
183
+ return None
184
+
185
+ def _generate_with_html_fallback(self, farmer_data: Dict, plan_data: Dict) -> str:
186
+ """Generate HTML file as fallback when ReportLab is not available"""
187
+ try:
188
+ # Create filename
189
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
190
+ filename = f"yearly_plan_{farmer_data.get('name', 'farmer')}_{timestamp}.html"
191
+ filepath = os.path.join(self.output_dir, filename)
192
+
193
+ # Generate HTML content
194
+ html_content = self._create_html_report(farmer_data, plan_data)
195
+
196
+ # Save HTML file
197
+ with open(filepath, 'w', encoding='utf-8') as f:
198
+ f.write(html_content)
199
+
200
+ logger.info(f"Successfully generated HTML report: {filepath}")
201
+ return filepath
202
+
203
+ except Exception as e:
204
+ logger.error(f"Error generating HTML fallback: {str(e)}")
205
+ return None
206
+
207
+ def _create_html_report(self, farmer_data: Dict, plan_data: Dict) -> str:
208
+ """Create HTML report content"""
209
+
210
+ html = f"""
211
+ <!DOCTYPE html>
212
+ <html lang="en">
213
+ <head>
214
+ <meta charset="UTF-8">
215
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
216
+ <title>Yearly Farming Plan - {farmer_data.get('name', 'Farmer')}</title>
217
+ <style>
218
+ body {{
219
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
220
+ line-height: 1.6;
221
+ margin: 20px;
222
+ background-color: #f9f9f9;
223
+ }}
224
+ .container {{
225
+ max-width: 800px;
226
+ margin: 0 auto;
227
+ background: white;
228
+ padding: 30px;
229
+ border-radius: 10px;
230
+ box-shadow: 0 0 20px rgba(0,0,0,0.1);
231
+ }}
232
+ .header {{
233
+ text-align: center;
234
+ color: #2c5530;
235
+ border-bottom: 3px solid #4CAF50;
236
+ padding-bottom: 20px;
237
+ margin-bottom: 30px;
238
+ }}
239
+ .section {{
240
+ margin-bottom: 30px;
241
+ }}
242
+ .section h2 {{
243
+ color: #1976d2;
244
+ border-left: 4px solid #4CAF50;
245
+ padding-left: 15px;
246
+ }}
247
+ table {{
248
+ width: 100%;
249
+ border-collapse: collapse;
250
+ margin: 15px 0;
251
+ }}
252
+ th, td {{
253
+ padding: 12px;
254
+ text-align: left;
255
+ border: 1px solid #ddd;
256
+ }}
257
+ th {{
258
+ background-color: #4CAF50;
259
+ color: white;
260
+ }}
261
+ tr:nth-child(even) {{
262
+ background-color: #f2f2f2;
263
+ }}
264
+ .info-grid {{
265
+ display: grid;
266
+ grid-template-columns: 1fr 2fr;
267
+ gap: 10px;
268
+ margin: 15px 0;
269
+ }}
270
+ .info-label {{
271
+ font-weight: bold;
272
+ color: #333;
273
+ }}
274
+ .footer {{
275
+ text-align: center;
276
+ margin-top: 40px;
277
+ padding-top: 20px;
278
+ border-top: 2px solid #eee;
279
+ color: #666;
280
+ }}
281
+ .highlight {{
282
+ background-color: #fff3cd;
283
+ padding: 15px;
284
+ border-left: 4px solid #ffc107;
285
+ margin: 15px 0;
286
+ }}
287
+ </style>
288
+ </head>
289
+ <body>
290
+ <div class="container">
291
+ <div class="header">
292
+ <h1>🌾 Comprehensive Yearly Farming Plan</h1>
293
+ <p>AI-Generated Agricultural Strategy</p>
294
+ </div>
295
+
296
+ <div class="section">
297
+ <h2>Farmer Information</h2>
298
+ <div class="info-grid">
299
+ <div class="info-label">Name:</div>
300
+ <div>{farmer_data.get('name', 'N/A')}</div>
301
+ <div class="info-label">Contact:</div>
302
+ <div>{farmer_data.get('contact_number', 'N/A')}</div>
303
+ <div class="info-label">Address:</div>
304
+ <div>{farmer_data.get('address', 'N/A')}</div>
305
+ <div class="info-label">Plan Year:</div>
306
+ <div>{plan_data.get('year', datetime.now().year)}</div>
307
+ </div>
308
+ </div>
309
+ """
310
+
311
+ # Add farm details
312
+ farms = plan_data.get('farms', [])
313
+ for i, farm in enumerate(farms):
314
+ html += f"""
315
+ <div class="section">
316
+ <h2>Farm {i+1}: {farm.get('farm_name', 'Unknown Farm')}</h2>
317
+ """
318
+
319
+ # Parse farm plan
320
+ try:
321
+ farm_plan = json.loads(farm.get('plan', '{}')) if isinstance(farm.get('plan'), str) else farm.get('plan', {})
322
+
323
+ # Monthly Plan
324
+ if 'monthly_plan' in farm_plan:
325
+ html += """
326
+ <h3>Monthly Farming Schedule</h3>
327
+ <table>
328
+ <thead>
329
+ <tr>
330
+ <th>Month</th>
331
+ <th>Planned Activities</th>
332
+ </tr>
333
+ </thead>
334
+ <tbody>
335
+ """
336
+ for month_data in farm_plan['monthly_plan']:
337
+ month = month_data.get('month', '')
338
+ tasks = month_data.get('tasks', [])
339
+ tasks_html = '<br>'.join(tasks[:4]) # Show max 4 tasks
340
+ html += f"""
341
+ <tr>
342
+ <td><strong>{month}</strong></td>
343
+ <td>{tasks_html}</td>
344
+ </tr>
345
+ """
346
+ html += """
347
+ </tbody>
348
+ </table>
349
+ """
350
+
351
+ # Crop Information
352
+ crops = farm_plan.get('crops', [])
353
+ if crops:
354
+ html += """
355
+ <h3>Crop Details</h3>
356
+ <table>
357
+ <thead>
358
+ <tr>
359
+ <th>Crop Name</th>
360
+ <th>Sowing Month</th>
361
+ <th>Area</th>
362
+ </tr>
363
+ </thead>
364
+ <tbody>
365
+ """
366
+ for crop in crops:
367
+ html += f"""
368
+ <tr>
369
+ <td>{crop.get('name', 'Unknown')}</td>
370
+ <td>{crop.get('sowing_month', 'N/A')}</td>
371
+ <td>{crop.get('area', 'N/A')}</td>
372
+ </tr>
373
+ """
374
+ html += """
375
+ </tbody>
376
+ </table>
377
+ """
378
+
379
+ # AI Generation Note
380
+ if farm_plan.get('ai_generated'):
381
+ html += """
382
+ <div class="highlight">
383
+ <strong>🤖 AI-Generated Plan:</strong> This plan was created using advanced AI analysis of soil conditions, weather patterns, and agricultural best practices.
384
+ </div>
385
+ """
386
+
387
+ except Exception as e:
388
+ html += f"<p><em>Plan details could not be processed: {str(e)}</em></p>"
389
+
390
+ html += " </div>\n"
391
+
392
+ # Summary
393
+ summary_text = plan_data.get('summary', 'Comprehensive yearly plan generated with AI analysis.')
394
+ html += f"""
395
+ <div class="section">
396
+ <h2>Summary & Recommendations</h2>
397
+ <p>{summary_text}</p>
398
+ <div class="highlight">
399
+ <strong>📱 Next Steps:</strong> Access your daily advisories through the farmer dashboard. Monitor weather alerts and market prices for optimal farming decisions.
400
+ </div>
401
+ </div>
402
+
403
+ <div class="footer">
404
+ <p>Generated on: {datetime.now().strftime('%B %d, %Y at %I:%M %p')}</p>
405
+ <p>🌱 <strong>Powered by AI Agriculture Assistant</strong></p>
406
+ <p><em>This plan is generated based on AI analysis and should be used in conjunction with local agricultural expertise.</em></p>
407
+ </div>
408
+ </div>
409
+ </body>
410
+ </html>
411
+ """
412
+
413
+ return html
414
+
415
+ def cleanup_old_files(self, days_old: int = 30):
416
+ """Clean up old generated files"""
417
+ try:
418
+ import time
419
+ cutoff_time = time.time() - (days_old * 24 * 60 * 60)
420
+
421
+ for filename in os.listdir(self.output_dir):
422
+ filepath = os.path.join(self.output_dir, filename)
423
+ if os.path.isfile(filepath) and os.path.getctime(filepath) < cutoff_time:
424
+ os.remove(filepath)
425
+ logger.info(f"Removed old file: {filename}")
426
+
427
+ except Exception as e:
428
+ logger.error(f"Error cleaning up old files: {str(e)}")
requirements.txt ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ flask==2.2.3
2
+ flask-sqlalchemy==3.0.3
3
+ gunicorn==20.1.0
4
+ requests==2.28.2
5
+ python-dotenv==1.0.0
6
+ google-generativeai==0.3.2
7
+ flask-login==0.6.3
8
+ flask-wtf==1.1.1
9
+ wtforms==3.0.1
10
+ celery==5.3.4
11
+ redis==5.0.1
12
+ apscheduler==3.10.4
13
+ werkzeug==2.2.3
scripts/__pycache__/register_telegram_chat.cpython-39.pyc ADDED
Binary file (3 kB). View file
 
scripts/get_telegram_chat_id.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Helper script to fetch chat_id for users who message the bot.
2
+
3
+ Usage:
4
+ 1. Ensure TELEGRAM_BOT_TOKEN is set in your environment or in a .env file.
5
+ 2. Run the script: python scripts/get_telegram_chat_id.py
6
+ 3. Send a message to @Krushi_Mitra_Bot from the farmer's Telegram account.
7
+ 4. The script will print recent updates with chat IDs.
8
+
9
+ This script polls Telegram's getUpdates endpoint once and prints chat ids.
10
+ """
11
+ import os
12
+ import requests
13
+ from dotenv import load_dotenv
14
+
15
+ load_dotenv()
16
+ TOKEN = os.getenv('TELEGRAM_BOT_TOKEN')
17
+ if not TOKEN:
18
+ print('Please set TELEGRAM_BOT_TOKEN in environment or .env')
19
+ raise SystemExit(1)
20
+
21
+ URL = f'https://api.telegram.org/bot{TOKEN}/getUpdates'
22
+
23
+ resp = requests.get(URL, params={'timeout': 5})
24
+ if resp.status_code != 200:
25
+ print('Failed to fetch updates:', resp.status_code, resp.text)
26
+ raise SystemExit(1)
27
+
28
+ data = resp.json()
29
+ if not data.get('ok'):
30
+ print('Telegram API error:', data)
31
+ raise SystemExit(1)
32
+
33
+ results = data.get('result', [])
34
+ if not results:
35
+ print('No recent updates. Ask the farmer to send a message to the bot and retry.')
36
+ else:
37
+ print('Recent updates:')
38
+ for u in results:
39
+ update_id = u.get('update_id')
40
+ msg = u.get('message') or u.get('edited_message') or {}
41
+ chat = msg.get('chat', {})
42
+ print(f"update_id={update_id} chat.id={chat.get('id')} chat.username={chat.get('username')} name={chat.get('first_name')} {chat.get('last_name')}")
43
+
44
+ print('\nTip: copy the chat.id value and use the admin endpoint POST /admin/register_telegram/<farmer_id> with JSON {"chat_id": <id>}')
scripts/register_telegram_chat.py ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Register a Telegram chat_id for a farmer by updating the instance DB.
2
+
3
+ This is a small admin helper which writes the chat id directly into
4
+ `instance/farm_management.db` to avoid needing to authenticate via the
5
+ web admin UI. Usage:
6
+
7
+ python scripts/register_telegram_chat.py --farmer-id 1 --chat-id 123456789
8
+
9
+ It will print what it changed and exit with a non-zero code on error.
10
+ """
11
+ import argparse
12
+ import sqlite3
13
+ import os
14
+ import sys
15
+
16
+
17
+ def register_chat(farmer_id: int, chat_id: str, db_path: str):
18
+ if not os.path.exists(db_path):
19
+ print(f"Database not found: {db_path}")
20
+ return 2
21
+
22
+ con = sqlite3.connect(db_path)
23
+ cur = con.cursor()
24
+
25
+ # Check farmer exists
26
+ cur.execute("SELECT id, name, telegram_chat_id FROM farmers WHERE id = ?", (farmer_id,))
27
+ row = cur.fetchone()
28
+ if not row:
29
+ print(f"Farmer id {farmer_id} not found in {db_path}")
30
+ con.close()
31
+ return 3
32
+
33
+ old = row[2]
34
+ cur.execute("UPDATE farmers SET telegram_chat_id = ? WHERE id = ?", (str(chat_id), farmer_id))
35
+ con.commit()
36
+ con.close()
37
+
38
+ print(f"Updated farmer {farmer_id} telegram_chat_id: {old} -> {chat_id}")
39
+ return 0
40
+
41
+
42
+ def main():
43
+ p = argparse.ArgumentParser()
44
+ p.add_argument('--farmer-id', '-f', type=int, required=False, help='Farmer id to update')
45
+ p.add_argument('--chat-id', '-c', required=True, help='Telegram chat id to set')
46
+ p.add_argument('--all', '-a', action='store_true', help='Apply chat id to all farmers')
47
+ p.add_argument('--db', default=os.path.join('instance', 'farm_management.db'), help='Path to instance DB')
48
+ args = p.parse_args()
49
+
50
+ if args.all:
51
+ # Update all farmers
52
+ if not os.path.exists(args.db):
53
+ print(f"Database not found: {args.db}")
54
+ sys.exit(2)
55
+ con = sqlite3.connect(args.db)
56
+ cur = con.cursor()
57
+ cur.execute("UPDATE farmers SET telegram_chat_id = ?", (str(args.chat_id),))
58
+ con.commit()
59
+ con.close()
60
+ print(f"Updated telegram_chat_id for ALL farmers -> {args.chat_id}")
61
+ rc = 0
62
+ else:
63
+ if not args.farmer_id:
64
+ print('Either --farmer-id or --all must be provided')
65
+ sys.exit(1)
66
+ rc = register_chat(args.farmer_id, args.chat_id, args.db)
67
+ sys.exit(rc)
68
+
69
+
70
+ if __name__ == '__main__':
71
+ main()
telegram_service.py ADDED
@@ -0,0 +1,645 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ import logging
3
+ import json
4
+ from typing import Optional
5
+ from datetime import datetime
6
+
7
+ # Configure logging
8
+ logging.basicConfig(level=logging.INFO)
9
+ logger = logging.getLogger(__name__)
10
+
11
+ class TelegramBotService:
12
+ """Service class for Telegram Bot integration"""
13
+
14
+ def __init__(self, bot_token: str):
15
+ """Initialize Telegram Bot service"""
16
+ self.bot_token = bot_token
17
+ self.base_url = f"https://api.telegram.org/bot{bot_token}"
18
+
19
+ def send_message(self, chat_id: str, message: str, parse_mode: str = 'HTML') -> dict:
20
+ """
21
+ Send message via Telegram Bot
22
+
23
+ Args:
24
+ chat_id: Telegram chat ID or username
25
+ message: Message content to send
26
+ parse_mode: Message formatting (HTML, Markdown, or None)
27
+
28
+ Returns:
29
+ Dictionary with status and message details
30
+ """
31
+
32
+ try:
33
+ url = f"{self.base_url}/sendMessage"
34
+
35
+ payload = {
36
+ 'chat_id': chat_id,
37
+ 'text': message,
38
+ 'parse_mode': parse_mode
39
+ }
40
+
41
+ response = requests.post(url, json=payload, timeout=10)
42
+
43
+ if response.status_code == 200:
44
+ result = response.json()
45
+ if result.get('ok'):
46
+ logger.info(f"Telegram message sent successfully to {chat_id}")
47
+ return {
48
+ 'status': 'sent',
49
+ 'message_id': result['result']['message_id'],
50
+ 'chat_id': chat_id,
51
+ 'message': message,
52
+ 'sent_at': datetime.utcnow(),
53
+ 'error': None
54
+ }
55
+ else:
56
+ error_msg = result.get('description', 'Unknown Telegram API error')
57
+ logger.error(f"Telegram API error: {error_msg}")
58
+ return {
59
+ 'status': 'failed',
60
+ 'message_id': None,
61
+ 'chat_id': chat_id,
62
+ 'message': message,
63
+ 'sent_at': datetime.utcnow(),
64
+ 'error': error_msg
65
+ }
66
+ else:
67
+ error_msg = f"HTTP {response.status_code}: {response.text}"
68
+ logger.error(f"Failed to send Telegram message: {error_msg}")
69
+ return {
70
+ 'status': 'failed',
71
+ 'message_id': None,
72
+ 'chat_id': chat_id,
73
+ 'message': message,
74
+ 'sent_at': datetime.utcnow(),
75
+ 'error': error_msg
76
+ }
77
+
78
+ except Exception as e:
79
+ logger.error(f"Failed to send Telegram message to {chat_id}: {str(e)}")
80
+
81
+ return {
82
+ 'status': 'failed',
83
+ 'message_id': None,
84
+ 'chat_id': chat_id,
85
+ 'message': message,
86
+ 'sent_at': datetime.utcnow(),
87
+ 'error': str(e)
88
+ }
89
+
90
+ def get_me(self) -> Optional[dict]:
91
+ """
92
+ Get bot information
93
+
94
+ Returns:
95
+ Bot info dictionary or None if failed
96
+ """
97
+
98
+ try:
99
+ url = f"{self.base_url}/getMe"
100
+ response = requests.get(url, timeout=10)
101
+
102
+ if response.status_code == 200:
103
+ result = response.json()
104
+ if result.get('ok'):
105
+ return result['result']
106
+
107
+ return None
108
+
109
+ except Exception as e:
110
+ logger.error(f"Failed to get bot info: {str(e)}")
111
+ return None
112
+
113
+ def send_document(self, chat_id: str, document_path: str, caption: str = None, parse_mode: str = 'HTML') -> dict:
114
+ """
115
+ Send document (PDF, Word, etc.) via Telegram Bot
116
+
117
+ Args:
118
+ chat_id: Telegram chat ID or username
119
+ document_path: Path to the document file
120
+ caption: Optional caption for the document
121
+ parse_mode: Caption formatting (HTML, Markdown, or None)
122
+
123
+ Returns:
124
+ Dictionary with status and message details
125
+ """
126
+
127
+ try:
128
+ import os
129
+ if not os.path.exists(document_path):
130
+ return {
131
+ 'status': 'failed',
132
+ 'error': 'Document file not found'
133
+ }
134
+
135
+ url = f"{self.base_url}/sendDocument"
136
+
137
+ with open(document_path, 'rb') as document:
138
+ files = {'document': document}
139
+ data = {'chat_id': chat_id}
140
+
141
+ if caption:
142
+ data['caption'] = caption
143
+ data['parse_mode'] = parse_mode
144
+
145
+ response = requests.post(url, files=files, data=data, timeout=30)
146
+
147
+ if response.status_code == 200:
148
+ result = response.json()
149
+ if result.get('ok'):
150
+ logger.info(f"Telegram document sent successfully to {chat_id}")
151
+ return {
152
+ 'status': 'sent',
153
+ 'message_id': result['result']['message_id'],
154
+ 'chat_id': chat_id,
155
+ 'document_path': document_path,
156
+ 'sent_at': datetime.utcnow(),
157
+ 'error': None
158
+ }
159
+ else:
160
+ error_msg = result.get('description', 'Unknown Telegram API error')
161
+ logger.error(f"Telegram API error: {error_msg}")
162
+ return {
163
+ 'status': 'failed',
164
+ 'error': error_msg
165
+ }
166
+ else:
167
+ error_msg = f"HTTP {response.status_code}: {response.text}"
168
+ logger.error(f"Failed to send Telegram document: {error_msg}")
169
+ return {
170
+ 'status': 'failed',
171
+ 'error': error_msg
172
+ }
173
+
174
+ except Exception as e:
175
+ logger.error(f"Failed to send Telegram document to {chat_id}: {str(e)}")
176
+ return {
177
+ 'status': 'failed',
178
+ 'error': str(e)
179
+ }
180
+
181
+ def send_photo(self, chat_id: str, photo_path: str, caption: str = None, parse_mode: str = 'HTML') -> dict:
182
+ """
183
+ Send photo/image via Telegram Bot
184
+
185
+ Args:
186
+ chat_id: Telegram chat ID or username
187
+ photo_path: Path to the photo file
188
+ caption: Optional caption for the photo
189
+ parse_mode: Caption formatting (HTML, Markdown, or None)
190
+
191
+ Returns:
192
+ Dictionary with status and message details
193
+ """
194
+
195
+ try:
196
+ import os
197
+ if not os.path.exists(photo_path):
198
+ return {
199
+ 'status': 'failed',
200
+ 'error': 'Photo file not found'
201
+ }
202
+
203
+ url = f"{self.base_url}/sendPhoto"
204
+
205
+ with open(photo_path, 'rb') as photo:
206
+ files = {'photo': photo}
207
+ data = {'chat_id': chat_id}
208
+
209
+ if caption:
210
+ data['caption'] = caption
211
+ data['parse_mode'] = parse_mode
212
+
213
+ response = requests.post(url, files=files, data=data, timeout=30)
214
+
215
+ if response.status_code == 200:
216
+ result = response.json()
217
+ if result.get('ok'):
218
+ logger.info(f"Telegram photo sent successfully to {chat_id}")
219
+ return {
220
+ 'status': 'sent',
221
+ 'message_id': result['result']['message_id'],
222
+ 'chat_id': chat_id,
223
+ 'photo_path': photo_path,
224
+ 'sent_at': datetime.utcnow(),
225
+ 'error': None
226
+ }
227
+ else:
228
+ error_msg = result.get('description', 'Unknown Telegram API error')
229
+ logger.error(f"Telegram API error: {error_msg}")
230
+ return {
231
+ 'status': 'failed',
232
+ 'error': error_msg
233
+ }
234
+ else:
235
+ error_msg = f"HTTP {response.status_code}: {response.text}"
236
+ logger.error(f"Failed to send Telegram photo: {error_msg}")
237
+ return {
238
+ 'status': 'failed',
239
+ 'error': error_msg
240
+ }
241
+
242
+ except Exception as e:
243
+ logger.error(f"Failed to send Telegram photo to {chat_id}: {str(e)}")
244
+ return {
245
+ 'status': 'failed',
246
+ 'error': str(e)
247
+ }
248
+
249
+ def send_bulk_messages(self, recipients: list, message: str) -> list:
250
+ """
251
+ Send message to multiple recipients
252
+
253
+ Args:
254
+ recipients: List of chat IDs
255
+ message: Message content
256
+
257
+ Returns:
258
+ List of send results
259
+ """
260
+
261
+ results = []
262
+
263
+ for recipient in recipients:
264
+ result = self.send_message(recipient, message)
265
+ results.append(result)
266
+
267
+ return results
268
+
269
+ def format_daily_advisory_telegram(farmer_name: str, advisory_data: dict) -> str:
270
+ """
271
+ Format daily advisory as Telegram message with HTML formatting
272
+
273
+ Args:
274
+ farmer_name: Name of the farmer
275
+ advisory_data: Advisory data from AI
276
+
277
+ Returns:
278
+ Formatted Telegram message with HTML
279
+ """
280
+
281
+ message = f"🌱 <b>नमस्ते {farmer_name} जी!</b>\n\n"
282
+ message += f"📅 <b>आज की सलाह ({datetime.now().strftime('%d/%m/%Y')})</b>\n\n"
283
+
284
+ # Task to do
285
+ if advisory_data.get('task_to_do'):
286
+ message += f"✅ <b>आज करें:</b>\n{advisory_data['task_to_do']}\n\n"
287
+
288
+ # Task to avoid
289
+ if advisory_data.get('task_to_avoid'):
290
+ message += f"❌ <b>आज न करें:</b>\n{advisory_data['task_to_avoid']}\n\n"
291
+
292
+ # Reason/explanation
293
+ if advisory_data.get('reason_explanation'):
294
+ message += f"💡 <b>कारण:</b>\n{advisory_data['reason_explanation']}\n\n"
295
+
296
+ message += "🙏 <b>शुभ दिन!</b>\n"
297
+ message += "<i>- कृषि मित्र बॉट</i>"
298
+
299
+ return message
300
+
301
+ def format_weather_alert_telegram(farmer_name: str, weather_alert: str) -> str:
302
+ """
303
+ Format weather alert as Telegram message
304
+
305
+ Args:
306
+ farmer_name: Name of the farmer
307
+ weather_alert: Weather alert message
308
+
309
+ Returns:
310
+ Formatted Telegram message
311
+ """
312
+
313
+ message = f"🌦️ <b>मौसम अलर्ट!</b>\n\n"
314
+ message += f"<b>{farmer_name} जी,</b>\n\n"
315
+ message += f"{weather_alert}\n\n"
316
+ message += "⚠️ <b>कृपया अपनी फसल की सुरक्षा करें।</b>\n"
317
+ message += "<i>- कृषि मित्र बॉट</i>"
318
+
319
+ return message
320
+
321
+
322
+ def format_yearly_plan_summary(farmer_name: str, plan_summary) -> str:
323
+ """
324
+ Format a yearly plan summary for Telegram - handles both dict and string inputs.
325
+ """
326
+ message = f"🌾 <b>नमस्ते {farmer_name} जी!</b>\n\n"
327
+
328
+ # Handle string input (simple summary)
329
+ if isinstance(plan_summary, str):
330
+ message += f"📅 <b>आपकी वार्षिक खेती योजना तैयार है!</b>\n\n"
331
+ message += plan_summary + "\n\n"
332
+ message += "👉 पूरी विस्तृत योजना देखने के लिए अपने डैशबोर्ड पर जाएं।\n"
333
+ message += "📊 यह AI तकनीक से बनाई गई विस्तृत योजना है।\n"
334
+ message += "<i>- कृषि मित्र बॉट</i>"
335
+ return message
336
+
337
+ # Handle dict input (comprehensive plan)
338
+ year = plan_summary.get('year', 'Current Year')
339
+ message += f"📅 <b>आपकी वार्षिक खेती योजना ({year}) तैयार है!</b>\n\n"
340
+ message += "🤖 <b>AI तकनीक से बनाई गई विस्तृत योजना</b>\n\n"
341
+
342
+ farms = plan_summary.get('farms', [])
343
+ for f in farms:
344
+ farm_name = f.get('farm_name', 'Unknown Farm')
345
+ message += f"🏡 <b>{farm_name}</b>\n"
346
+
347
+ # Check if AI generated
348
+ if f.get('ai_generated', False):
349
+ message += "✅ AI द्वारा विश्लेषण किया गया\n"
350
+
351
+ message += "📋 मिली है विस्तृत जानकारी:\n"
352
+ message += "• मासिक गतिविधि कैलेंडर\n"
353
+ message += "• फसल-वार रणनीति\n"
354
+ message += "• वित्तीय अनुमान\n"
355
+ message += "• मिट्टी प्रबंधन योजना\n"
356
+ message += "• जोखिम प्रबंधन\n\n"
357
+
358
+ message += "� <b>मुख्य विशेषताएं:</b>\n"
359
+ message += "📊 विस्तृत तालिकाओं के साथ\n"
360
+ message += "📈 वित्तीय अनुमान के साथ\n"
361
+ message += "🌡️ मौसम आधारित सुझाव\n"
362
+ message += "🧪 मिट्टी परीक्षण आधारित सलाह\n\n"
363
+
364
+ message += "👆 <b>पूरी योजना देखने के लिए:</b>\n"
365
+ message += "1️⃣ अपने डैशबोर्ड पर जाएं\n"
366
+ message += "2️⃣ अपने खेत के पास 'View Plan' पर क्लिक करें\n"
367
+ message += "3️⃣ विस्तृत HTML रिपोर्ट देखें\n\n"
368
+
369
+ message += "🌟 <b>यह AI द्वारा बनाई गई पेशेवर खेती योजना है</b>\n"
370
+ message += "<i>- कृषि मित्र बॉट</i>"
371
+ return message
372
+
373
+ def format_complete_yearly_plan_telegram(farmer_name: str, farm_name: str, plan_data: dict) -> list:
374
+ """
375
+ Format complete yearly plan for Telegram (split into multiple messages due to length)
376
+
377
+ Args:
378
+ farmer_name: Name of the farmer
379
+ farm_name: Name of the farm
380
+ plan_data: Complete plan data from gemini service
381
+
382
+ Returns:
383
+ List of formatted messages for Telegram
384
+ """
385
+
386
+ messages = []
387
+
388
+ # Message 1: Header and Farm Overview
389
+ msg1 = f"🌾 <b>Comprehensive Yearly Farming Plan 2025</b>\n"
390
+ msg1 += f"👨‍🌾 <b>Farmer:</b> {farmer_name}\n"
391
+ msg1 += f"🏡 <b>Farm:</b> {farm_name}\n\n"
392
+
393
+ # Extract farm overview from plan data
394
+ farms = plan_data.get('farms', [])
395
+ if farms:
396
+ farm = farms[0] # Get first farm
397
+ if 'plan' in farm:
398
+ # Try to extract key information from HTML plan
399
+ plan_content = farm['plan']
400
+
401
+ # Extract farm size, crops, etc. from plan content
402
+ msg1 += "📊 <b>Farm Overview:</b>\n"
403
+ if 'farm_size' in str(plan_content).lower():
404
+ msg1 += "• Farm details included in comprehensive plan\n"
405
+ if 'crop' in str(plan_content).lower():
406
+ msg1 += "• Crop-wise strategies provided\n"
407
+ if 'month' in str(plan_content).lower():
408
+ msg1 += "• Monthly calendar included\n"
409
+ msg1 += "\n"
410
+
411
+ msg1 += "📋 <b>Plan Components:</b>\n"
412
+ msg1 += "✅ Monthly Farming Calendar\n"
413
+ msg1 += "✅ Crop-wise Annual Strategy\n"
414
+ msg1 += "✅ Financial Projections\n"
415
+ msg1 += "✅ Soil Management Plan\n"
416
+ msg1 += "✅ Risk Management\n"
417
+ msg1 += "✅ Seasonal Recommendations\n\n"
418
+
419
+ messages.append(msg1)
420
+
421
+ # Message 2: Key Monthly Activities (simplified)
422
+ msg2 = "📅 <b>Key Monthly Activities Summary:</b>\n\n"
423
+
424
+ monthly_activities = {
425
+ "जनवरी (January)": "• रबी फसल की देखभाल\n• गेहूं/सरसों की निराई\n• सब्जियों की बुआई",
426
+ "फरवरी (February)": "• फसल में सिंचाई\n• उर्वरक का छिड़काव\n• बीज तैयारी",
427
+ "मार्च (March)": "• रबी फसल की कटाई\n• खरीफ की तैयारी\n• मिट्टी की जांच",
428
+ "अप्रैल (April)": "• खेत की जुताई\n• कम्पोस्ट तैयारी\n• सिंचाई व्यवस्था",
429
+ "मई (May)": "• खरीफ बीज तैयारी\n• मिट्टी उपचार\n• पानी की व्यवस्था",
430
+ "जून (June)": "• खरीफ बुआई\n• धान/मक्का रोपाई\n• कीट नियंत्रण"
431
+ }
432
+
433
+ for month, activities in list(monthly_activities.items())[:6]:
434
+ msg2 += f"<b>{month}:</b>\n{activities}\n\n"
435
+
436
+ messages.append(msg2)
437
+
438
+ # Message 3: Remaining months and recommendations
439
+ msg3 = "📅 <b>Remaining Months & Key Tips:</b>\n\n"
440
+
441
+ remaining_months = {
442
+ "जुलाई (July)": "• खरीफ फसल देखभाल\n• कीट-रोग नियंत्रण\n• निराई-गुड़ाई",
443
+ "अगस्त (August)": "• उर्वरक प्रबंधन\n• सिंचाई नियमित\n• फसल निगरानी",
444
+ "सितंबर (September)": "• फसल सुरक्षा\n• कटाई तैयारी\n• बाजार जानकारी",
445
+ "अक्टूबर (October)": "• खरीफ कटाई\n• रबी तैयारी\n• भंडारण व्यवस्था",
446
+ "नवंबर (November)": "• रबी बुआई\n• गेहूं-सरसों रोपाई\n• खाद प्रबंधन",
447
+ "दिसंबर (December)": "• फसल देखभाल\n• सिंचाई व्यवस्था\n• अगले वर्ष योजना"
448
+ }
449
+
450
+ for month, activities in remaining_months.items():
451
+ msg3 += f"<b>{month}:</b>\n{activities}\n\n"
452
+
453
+ msg3 += "💡 <b>Important Tips:</b>\n"
454
+ msg3 += "• मौसम अपडेट नियमित देखें\n"
455
+ msg3 += "• मिट्टी परीक्षण साल में दो बार\n"
456
+ msg3 += "• कीट-रोग की निगरानी रखें\n"
457
+ msg3 += "• बाजार भाव की जानकारी रखें\n\n"
458
+
459
+ messages.append(msg3)
460
+
461
+ # Message 4: Financial and final recommendations
462
+ msg4 = "💰 <b>Financial Planning & Final Notes:</b>\n\n"
463
+ msg4 += "<b>वित्तीय योजना:</b>\n"
464
+ msg4 += "• बीज, खाद की लागत का बजट\n"
465
+ msg4 += "• अपेक्षित उत्पादन की गणना\n"
466
+ msg4 += "• बाजार मूल्य का अनुमान\n"
467
+ msg4 += "• मुनाफे का अनुमान\n\n"
468
+
469
+ msg4 += "<b>जोखिम प्रबंधन:</b>\n"
470
+ msg4 += "• फसल बीमा कराएं\n"
471
+ msg4 += "• मौसम आधारित बचाव\n"
472
+ msg4 += "• वैकल्पिक फसल योजना\n"
473
+ msg4 += "• पानी संरक्षण करें\n\n"
474
+
475
+ msg4 += "📄 <b>Complete detailed plan with tables and charts is available in the PDF file sent above.</b>\n\n"
476
+ msg4 += "🌟 <b>यह AI-powered comprehensive farming plan है जो आपकी मिट्टी, मौसम, और फसल के डेटा के आधार पर बनाई गई है।</b>\n\n"
477
+ msg4 += "<i>🤖 Generated by Agricultural AI Assistant</i>"
478
+
479
+ messages.append(msg4)
480
+
481
+ return messages
482
+
483
+ def extract_plan_content_from_html(html_content: str) -> dict:
484
+ """
485
+ Extract key information from HTML plan content for Telegram formatting
486
+
487
+ Args:
488
+ html_content: HTML content of the yearly plan
489
+
490
+ Returns:
491
+ Dictionary with extracted content
492
+ """
493
+
494
+ try:
495
+ # Remove HTML tags and extract text content
496
+ import re
497
+
498
+ # Clean HTML content
499
+ text_content = re.sub(r'<[^>]+>', '', html_content)
500
+ text_content = re.sub(r'\s+', ' ', text_content).strip()
501
+
502
+ extracted = {
503
+ 'farm_overview': '',
504
+ 'monthly_activities': '',
505
+ 'crop_strategy': '',
506
+ 'financial_info': '',
507
+ 'soil_management': ''
508
+ }
509
+
510
+ # Look for specific sections
511
+ if 'Farm Overview' in text_content:
512
+ try:
513
+ start = text_content.find('Farm Overview')
514
+ end = text_content.find('Monthly Farming Calendar', start) if 'Monthly Farming Calendar' in text_content else start + 500
515
+ extracted['farm_overview'] = text_content[start:end][:500] + "..."
516
+ except:
517
+ pass
518
+
519
+ if 'Monthly Farming Calendar' in text_content:
520
+ try:
521
+ start = text_content.find('Monthly Farming Calendar')
522
+ end = text_content.find('Crop-wise Annual Strategy', start) if 'Crop-wise Annual Strategy' in text_content else start + 800
523
+ extracted['monthly_activities'] = text_content[start:end][:800] + "..."
524
+ except:
525
+ pass
526
+
527
+ if 'Crop-wise Annual Strategy' in text_content:
528
+ try:
529
+ start = text_content.find('Crop-wise Annual Strategy')
530
+ end = text_content.find('Financial', start) if 'Financial' in text_content else start + 600
531
+ extracted['crop_strategy'] = text_content[start:end][:600] + "..."
532
+ except:
533
+ pass
534
+
535
+ return extracted
536
+
537
+ except Exception as e:
538
+ logger.error(f"Error extracting plan content from HTML: {str(e)}")
539
+ return {}
540
+
541
+ def format_enhanced_yearly_plan_telegram(farmer_name: str, farm_name: str, plan_data: dict) -> list:
542
+ """
543
+ Enhanced yearly plan formatting that tries to extract actual plan content
544
+
545
+ Args:
546
+ farmer_name: Name of the farmer
547
+ farm_name: Name of the farm
548
+ plan_data: Complete plan data from gemini service
549
+
550
+ Returns:
551
+ List of formatted messages for Telegram
552
+ """
553
+
554
+ messages = []
555
+
556
+ # Try to extract actual content from the plan
557
+ actual_content = {}
558
+ farms = plan_data.get('farms', [])
559
+ if farms and 'plan' in farms[0]:
560
+ actual_content = extract_plan_content_from_html(farms[0]['plan'])
561
+
562
+ # Message 1: Header and Farm Overview
563
+ msg1 = f"🌾 <b>Complete Yearly Farming Plan 2025</b>\n"
564
+ msg1 += f"👨‍🌾 <b>Farmer:</b> {farmer_name}\n"
565
+ msg1 += f"🏡 <b>Farm:</b> {farm_name}\n\n"
566
+
567
+ if actual_content.get('farm_overview'):
568
+ msg1 += "📊 <b>Farm Overview (From Your AI Plan):</b>\n"
569
+ msg1 += f"{actual_content['farm_overview'][:500]}...\n\n"
570
+ else:
571
+ msg1 += "📊 <b>Plan Components Generated:</b>\n"
572
+ msg1 += "✅ Detailed Farm Analysis\n"
573
+ msg1 += "✅ Soil & Weather Assessment\n"
574
+ msg1 += "✅ Crop Performance Metrics\n"
575
+ msg1 += "✅ Monthly Activity Schedule\n\n"
576
+
577
+ messages.append(msg1)
578
+
579
+ # Message 2: Monthly Activities
580
+ msg2 = "📅 <b>Monthly Farming Calendar:</b>\n\n"
581
+
582
+ if actual_content.get('monthly_activities'):
583
+ msg2 += f"{actual_content['monthly_activities'][:800]}...\n\n"
584
+ else:
585
+ # Provide comprehensive monthly guide
586
+ msg2 += "<b>🌱 Kharif Season (June-October):</b>\n"
587
+ msg2 += "• June: Land preparation, seed sowing\n"
588
+ msg2 += "• July: Transplanting, weed management\n"
589
+ msg2 += "• August: Fertilizer application, pest control\n"
590
+ msg2 += "• September: Crop monitoring, irrigation\n"
591
+ msg2 += "• October: Harvesting preparation\n\n"
592
+
593
+ msg2 += "<b>❄️ Rabi Season (November-April):</b>\n"
594
+ msg2 += "• November: Rabi crop sowing\n"
595
+ msg2 += "• December: Crop establishment, irrigation\n"
596
+ msg2 += "• January: Mid-season care, fertilizing\n"
597
+ msg2 += "• February: Disease monitoring, spraying\n"
598
+ msg2 += "• March: Pre-harvest activities\n"
599
+ msg2 += "• April: Harvesting, storage\n\n"
600
+
601
+ messages.append(msg2)
602
+
603
+ # Message 3: Crop Strategy and Financial Info
604
+ msg3 = "🌾 <b>Crop Strategy & Financial Planning:</b>\n\n"
605
+
606
+ if actual_content.get('crop_strategy'):
607
+ msg3 += f"<b>Crop-wise Strategy:</b>\n{actual_content['crop_strategy'][:600]}...\n\n"
608
+ else:
609
+ msg3 += "<b>Strategic Recommendations:</b>\n"
610
+ msg3 += "• Diversified cropping for risk reduction\n"
611
+ msg3 += "• Optimal seed varieties for your soil\n"
612
+ msg3 += "• Integrated pest management\n"
613
+ msg3 += "• Water-efficient irrigation scheduling\n"
614
+ msg3 += "• Market-oriented crop selection\n\n"
615
+
616
+ msg3 += "💰 <b>Financial Planning:</b>\n"
617
+ msg3 += "• Input cost optimization\n"
618
+ msg3 += "• Expected yield calculations\n"
619
+ msg3 += "• Revenue projections per crop\n"
620
+ msg3 += "• Profit margin analysis\n"
621
+ msg3 += "• Cash flow management\n\n"
622
+
623
+ messages.append(msg3)
624
+
625
+ # Message 4: Final recommendations and notes
626
+ msg4 = "🎯 <b>Key Implementation Tips:</b>\n\n"
627
+ msg4 += "<b>🔬 Soil Management:</b>\n"
628
+ msg4 += "• Regular soil testing (twice yearly)\n"
629
+ msg4 += "• Organic matter enhancement\n"
630
+ msg4 += "• Balanced fertilizer application\n"
631
+ msg4 += "• pH level monitoring\n\n"
632
+
633
+ msg4 += "<b>⚠️ Risk Management:</b>\n"
634
+ msg4 += "• Weather-based crop insurance\n"
635
+ msg4 += "• Diversified income sources\n"
636
+ msg4 += "• Emergency fund planning\n"
637
+ msg4 += "• Alternative crop options\n\n"
638
+
639
+ msg4 += "📄 <b>The complete detailed plan with tables, charts, and specific calculations is available in the PDF sent above.</b>\n\n"
640
+ msg4 += "🤖 <b>This AI-generated plan is based on your specific soil data, weather patterns, and crop requirements.</b>\n\n"
641
+ msg4 += "<i>📱 For any questions, contact our agricultural support team!</i>"
642
+
643
+ messages.append(msg4)
644
+
645
+ return messages
templates/add_farm.html ADDED
@@ -0,0 +1,630 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}Add Farm - Farm Management Portal{% endblock %}
4
+
5
+ {% block extra_css %}
6
+ <style>
7
+ #map {
8
+ height: 400px;
9
+ width: 100%;
10
+ border-radius: 8px;
11
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
12
+ margin-bottom: 20px;
13
+ }
14
+ .form-section {
15
+ background-color: #f8f9fa;
16
+ padding: 20px;
17
+ border-radius: 8px;
18
+ margin-bottom: 20px;
19
+ }
20
+ </style>
21
+ {% endblock %}
22
+
23
+ {% block content %}
24
+ <div class="container mt-4">
25
+ <div class="row">
26
+ <div class="col-12">
27
+ <div class="card">
28
+ <div class="card-header bg-success text-white">
29
+ <h4><i class="fas fa-plus me-2"></i>Add New Farm</h4>
30
+ </div>
31
+ <div class="card-body">
32
+ <form method="POST" id="farmForm">
33
+ <div class="row">
34
+ <!-- Farm Details -->
35
+ <div class="col-md-6">
36
+ <div class="form-section">
37
+ <h5><i class="fas fa-seedling me-2"></i>Farm Details</h5>
38
+
39
+ <div class="mb-3">
40
+ <label for="farm_name" class="form-label">Farm Name *</label>
41
+ <input type="text" class="form-control" id="farm_name" name="farm_name" required>
42
+ </div>
43
+
44
+ <div class="row">
45
+ <div class="col-md-6 mb-3">
46
+ <label for="farm_size" class="form-label">Farm Size (Acres) *</label>
47
+ <input type="number" class="form-control" id="farm_size" name="farm_size" step="0.01" min="0.1" required>
48
+ </div>
49
+ <div class="col-md-6 mb-3">
50
+ <label for="irrigation_type" class="form-label">Irrigation Type *</label>
51
+ <select class="form-select" id="irrigation_type" name="irrigation_type" required>
52
+ <option value="">Select Irrigation</option>
53
+ <option value="Drip">Drip Irrigation</option>
54
+ <option value="Sprinkler">Sprinkler</option>
55
+ <option value="Flood">Flood Irrigation</option>
56
+ <option value="Rain-fed">Rain-fed</option>
57
+ <option value="Bore-well">Bore-well</option>
58
+ </select>
59
+ </div>
60
+ </div>
61
+
62
+ <div class="mb-3">
63
+ <label for="farm_type" class="form-label">Farm Type *</label>
64
+ <select class="form-select" id="farm_type" name="farm_type" required onchange="toggleFarmSections()">
65
+ <option value="">Select Farm Type</option>
66
+ <option value="crop">Crop Farming</option>
67
+ <option value="dairy">Dairy Farm</option>
68
+ <option value="poultry">Poultry Farm</option>
69
+ <option value="goat">Goat Farming</option>
70
+ <option value="pig">Pig Farming</option>
71
+ <option value="fishery">Fish Farming</option>
72
+ <option value="mixed">Mixed Farming</option>
73
+ </select>
74
+ </div>
75
+
76
+ <!-- Crop Farming Section -->
77
+ <div class="mb-3" id="crop-section" style="display: none;">
78
+ <label class="form-label">Crop Types *</label>
79
+ <div class="row">
80
+ <div class="col-md-6">
81
+ <div class="form-check">
82
+ <input class="form-check-input" type="checkbox" value="Rice" id="crop_rice" name="crop_types">
83
+ <label class="form-check-label" for="crop_rice">Rice</label>
84
+ </div>
85
+ <div class="form-check">
86
+ <input class="form-check-input" type="checkbox" value="Wheat" id="crop_wheat" name="crop_types">
87
+ <label class="form-check-label" for="crop_wheat">Wheat</label>
88
+ </div>
89
+ <div class="form-check">
90
+ <input class="form-check-input" type="checkbox" value="Cotton" id="crop_cotton" name="crop_types">
91
+ <label class="form-check-label" for="crop_cotton">Cotton</label>
92
+ </div>
93
+ <div class="form-check">
94
+ <input class="form-check-input" type="checkbox" value="Sugarcane" id="crop_sugarcane" name="crop_types">
95
+ <label class="form-check-label" for="crop_sugarcane">Sugarcane</label>
96
+ </div>
97
+ </div>
98
+ <div class="col-md-6">
99
+ <div class="form-check">
100
+ <input class="form-check-input" type="checkbox" value="Maize" id="crop_maize" name="crop_types">
101
+ <label class="form-check-label" for="crop_maize">Maize</label>
102
+ </div>
103
+ <div class="form-check">
104
+ <input class="form-check-input" type="checkbox" value="Vegetables" id="crop_vegetables" name="crop_types">
105
+ <label class="form-check-label" for="crop_vegetables">Vegetables</label>
106
+ </div>
107
+ <div class="form-check">
108
+ <input class="form-check-input" type="checkbox" value="Fruits" id="crop_fruits" name="crop_types">
109
+ <label class="form-check-label" for="crop_fruits">Fruits</label>
110
+ </div>
111
+ <div class="form-check">
112
+ <input class="form-check-input" type="checkbox" value="Pulses" id="crop_pulses" name="crop_types">
113
+ <label class="form-check-label" for="crop_pulses">Pulses</label>
114
+ </div>
115
+ </div>
116
+ </div>
117
+ </div>
118
+
119
+ <!-- Livestock Section -->
120
+ <div class="mb-3" id="livestock-section" style="display: none;">
121
+ <label class="form-label">Livestock Details</label>
122
+
123
+ <div class="row">
124
+ <div class="col-md-6 mb-3">
125
+ <label for="livestock_types" class="form-label">Livestock Types</label>
126
+ <select class="form-select" id="livestock_types" name="livestock_types" multiple>
127
+ <option value="Cows">Cows</option>
128
+ <option value="Buffalo">Buffalo</option>
129
+ <option value="Chickens">Chickens</option>
130
+ <option value="Ducks">Ducks</option>
131
+ <option value="Goats">Goats</option>
132
+ <option value="Sheep">Sheep</option>
133
+ <option value="Pigs">Pigs</option>
134
+ <option value="Fish">Fish</option>
135
+ </select>
136
+ <div class="form-text">Hold Ctrl to select multiple types</div>
137
+ </div>
138
+ <div class="col-md-6 mb-3">
139
+ <label for="livestock_count" class="form-label">Total Livestock Count</label>
140
+ <input type="number" class="form-control" id="livestock_count" name="livestock_count" min="1">
141
+ </div>
142
+ </div>
143
+
144
+ <div class="row">
145
+ <div class="col-md-6 mb-3">
146
+ <label for="housing_type" class="form-label">Housing Type</label>
147
+ <select class="form-select" id="housing_type" name="housing_type">
148
+ <option value="">Select Housing Type</option>
149
+ <option value="Open">Open Housing</option>
150
+ <option value="Semi-Open">Semi-Open Housing</option>
151
+ <option value="Closed">Closed Housing</option>
152
+ <option value="Cage">Cage System</option>
153
+ <option value="Free-Range">Free Range</option>
154
+ <option value="Pond">Pond System</option>
155
+ </select>
156
+ </div>
157
+ <div class="col-md-6 mb-3">
158
+ <label for="feeding_system" class="form-label">Feeding System</label>
159
+ <select class="form-select" id="feeding_system" name="feeding_system">
160
+ <option value="">Select Feeding System</option>
161
+ <option value="Grazing">Grazing</option>
162
+ <option value="Stall-fed">Stall Feeding</option>
163
+ <option value="Mixed">Mixed System</option>
164
+ <option value="Automatic">Automatic Feeding</option>
165
+ <option value="Manual">Manual Feeding</option>
166
+ </select>
167
+ </div>
168
+ </div>
169
+
170
+ <div class="mb-3">
171
+ <label for="breed_info" class="form-label">Breed Information</label>
172
+ <textarea class="form-control" id="breed_info" name="breed_info" rows="2" placeholder="Describe the breeds of livestock you have..."></textarea>
173
+ </div>
174
+ </div>
175
+
176
+ <!-- Detailed crops table: name, area, sowing month -->
177
+ <div class="mb-3" id="crop-details-section" style="display: none;">
178
+ <label class="form-label">Crops (name, area in acres, sowing month) *</label>
179
+ <div class="table-responsive">
180
+ <table class="table table-bordered" id="crops-table-add">
181
+ <thead>
182
+ <tr>
183
+ <th>Crop Name</th>
184
+ <th>Area (acres)</th>
185
+ <th>Sowing Month</th>
186
+ <th>Actions</th>
187
+ </tr>
188
+ </thead>
189
+ <tbody id="crops-tbody-add">
190
+ <tr>
191
+ <td><input type="text" class="form-control" name="crop_name[]" required></td>
192
+ <td><input type="number" class="form-control" name="crop_area[]" step="0.01" min="0" required></td>
193
+ <td>
194
+ <select class="form-select" name="crop_month[]" required>
195
+ <option value="">Select Month</option>
196
+ <option>January</option>
197
+ <option>February</option>
198
+ <option>March</option>
199
+ <option>April</option>
200
+ <option>May</option>
201
+ <option>June</option>
202
+ <option>July</option>
203
+ <option>August</option>
204
+ <option>September</option>
205
+ <option>October</option>
206
+ <option>November</option>
207
+ <option>December</option>
208
+ </select>
209
+ </td>
210
+ <td><button type="button" class="btn btn-danger btn-sm remove-crop">Remove</button></td>
211
+ </tr>
212
+ </tbody>
213
+ </table>
214
+ <button type="button" id="add-crop-row" class="btn btn-success">Add Crop</button>
215
+ </div>
216
+ </div>
217
+
218
+ <!-- Soil Data Section -->
219
+ <h5><i class="fas fa-mountain me-2"></i>Soil Information</h5>
220
+
221
+ <div class="mb-3">
222
+ <label for="soil_type" class="form-label">Soil Type</label>
223
+ <select class="form-select" id="soil_type" name="soil_type">
224
+ <option value="">Select Soil Type</option>
225
+ <option value="Black">Black Soil</option>
226
+ <option value="Red">Red Soil</option>
227
+ <option value="Clay">Clay Soil</option>
228
+ <option value="Sandy">Sandy Soil</option>
229
+ <option value="Loamy">Loamy Soil</option>
230
+ </select>
231
+ </div>
232
+
233
+ <div class="row">
234
+ <div class="col-md-6 mb-3">
235
+ <label for="ph_level" class="form-label">pH Level</label>
236
+ <input type="number" class="form-control" id="ph_level" name="ph_level" step="0.1" min="0" max="14">
237
+ </div>
238
+ <div class="col-md-6 mb-3">
239
+ <label for="moisture_percentage" class="form-label">Moisture %</label>
240
+ <input type="number" class="form-control" id="moisture_percentage" name="moisture_percentage" step="0.1" min="0" max="100">
241
+ </div>
242
+ </div>
243
+
244
+ <div class="row">
245
+ <div class="col-md-4 mb-3">
246
+ <label for="nitrogen_level" class="form-label">Nitrogen (ppm)</label>
247
+ <input type="number" class="form-control" id="nitrogen_level" name="nitrogen_level" step="0.1" min="0">
248
+ </div>
249
+ <div class="col-md-4 mb-3">
250
+ <label for="phosphorus_level" class="form-label">Phosphorus (ppm)</label>
251
+ <input type="number" class="form-control" id="phosphorus_level" name="phosphorus_level" step="0.1" min="0">
252
+ </div>
253
+ <div class="col-md-4 mb-3">
254
+ <label for="potassium_level" class="form-label">Potassium (ppm)</label>
255
+ <input type="number" class="form-control" id="potassium_level" name="potassium_level" step="0.1" min="0">
256
+ </div>
257
+ </div>
258
+ </div>
259
+ </div>
260
+
261
+ <!-- Map Section -->
262
+ <div class="col-md-6">
263
+ <div class="form-section">
264
+ <h5><i class="fas fa-map-marker-alt me-2"></i>Farm Location</h5>
265
+
266
+ <div class="mb-3">
267
+ <div id="map"></div>
268
+ </div>
269
+
270
+ <div class="row mb-3">
271
+ <div class="col-6">
272
+ <button type="button" id="startDrawingBtn" class="btn btn-success w-100">
273
+ <i class="fas fa-draw-polygon me-2"></i>Draw Boundary
274
+ </button>
275
+ </div>
276
+ <div class="col-6">
277
+ <button type="button" id="clearDrawingBtn" class="btn btn-danger w-100">
278
+ <i class="fas fa-trash me-2"></i>Clear
279
+ </button>
280
+ </div>
281
+ </div>
282
+
283
+ <div class="alert alert-info" id="drawingStatus">
284
+ <i class="fas fa-info-circle me-2"></i>
285
+ Click "Draw Boundary" and mark your farm area on the map.
286
+ </div>
287
+
288
+ <div class="row">
289
+ <div class="col-6">
290
+ <label for="latitude" class="form-label">Latitude *</label>
291
+ <input type="number" class="form-control" id="latitude" name="latitude" step="0.000001" readonly required>
292
+ </div>
293
+ <div class="col-6">
294
+ <label for="longitude" class="form-label">Longitude *</label>
295
+ <input type="number" class="form-control" id="longitude" name="longitude" step="0.000001" readonly required>
296
+ </div>
297
+ </div>
298
+
299
+ <!-- Hidden fields for coordinates data -->
300
+ <input type="hidden" id="field_coordinates" name="field_coordinates">
301
+ </div>
302
+ </div>
303
+ </div>
304
+
305
+ <div class="row mt-4">
306
+ <div class="col-12 text-center">
307
+ <button type="submit" class="btn btn-success btn-lg me-3" id="submitBtn" disabled>
308
+ <i class="fas fa-save me-2"></i>Save Farm
309
+ </button>
310
+ <a href="{{ url_for('farmer_dashboard') }}" class="btn btn-secondary btn-lg">
311
+ <i class="fas fa-arrow-left me-2"></i>Back to Dashboard
312
+ </a>
313
+ </div>
314
+ </div>
315
+ </form>
316
+ </div>
317
+ </div>
318
+ </div>
319
+ </div>
320
+ </div>
321
+ {% endblock %}
322
+
323
+ {% block extra_js %}
324
+ <script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyBvVLjWmCja331H8SuIZ4UlJdZytuYkC6Y&libraries=drawing,places&callback=initMap" async defer></script>
325
+
326
+ <script>
327
+ // Toggle farm sections based on farm type
328
+ function toggleFarmSections() {
329
+ const farmType = document.getElementById('farm_type').value;
330
+ const cropSection = document.getElementById('crop-section');
331
+ const livestockSection = document.getElementById('livestock-section');
332
+ const cropDetailsSection = document.getElementById('crop-details-section');
333
+
334
+ // Hide all sections first
335
+ cropSection.style.display = 'none';
336
+ livestockSection.style.display = 'none';
337
+ cropDetailsSection.style.display = 'none';
338
+
339
+ // Show relevant sections based on farm type
340
+ if (farmType === 'crop' || farmType === 'mixed') {
341
+ cropSection.style.display = 'block';
342
+ cropDetailsSection.style.display = 'block';
343
+ }
344
+
345
+ if (farmType === 'dairy' || farmType === 'poultry' || farmType === 'goat' ||
346
+ farmType === 'pig' || farmType === 'fishery' || farmType === 'mixed') {
347
+ livestockSection.style.display = 'block';
348
+
349
+ // Auto-select appropriate livestock types based on farm type
350
+ const livestockSelect = document.getElementById('livestock_types');
351
+ for (let option of livestockSelect.options) {
352
+ option.selected = false; // Clear previous selections
353
+ }
354
+
355
+ switch(farmType) {
356
+ case 'dairy':
357
+ for (let option of livestockSelect.options) {
358
+ if (option.value === 'Cows' || option.value === 'Buffalo') {
359
+ option.selected = true;
360
+ }
361
+ }
362
+ break;
363
+ case 'poultry':
364
+ for (let option of livestockSelect.options) {
365
+ if (option.value === 'Chickens' || option.value === 'Ducks') {
366
+ option.selected = true;
367
+ }
368
+ }
369
+ break;
370
+ case 'goat':
371
+ for (let option of livestockSelect.options) {
372
+ if (option.value === 'Goats') {
373
+ option.selected = true;
374
+ }
375
+ }
376
+ break;
377
+ case 'pig':
378
+ for (let option of livestockSelect.options) {
379
+ if (option.value === 'Pigs') {
380
+ option.selected = true;
381
+ }
382
+ }
383
+ break;
384
+ case 'fishery':
385
+ for (let option of livestockSelect.options) {
386
+ if (option.value === 'Fish') {
387
+ option.selected = true;
388
+ }
389
+ }
390
+ break;
391
+ }
392
+ }
393
+ }
394
+
395
+ let map, drawingManager, polygon;
396
+ let isDrawing = false;
397
+
398
+ function initMap() {
399
+ // Initialize map (centered on India)
400
+ map = new google.maps.Map(document.getElementById('map'), {
401
+ center: { lat: 20.5937, lng: 78.9629 },
402
+ zoom: 5,
403
+ mapTypeControl: true,
404
+ fullscreenControl: true,
405
+ streetViewControl: true
406
+ });
407
+
408
+ // Initialize drawing manager
409
+ drawingManager = new google.maps.drawing.DrawingManager({
410
+ drawingMode: null,
411
+ drawingControl: false,
412
+ polygonOptions: {
413
+ fillColor: '#4CAF50',
414
+ fillOpacity: 0.3,
415
+ strokeWeight: 2,
416
+ strokeColor: '#4CAF50',
417
+ clickable: true,
418
+ editable: true
419
+ }
420
+ });
421
+
422
+ drawingManager.setMap(map);
423
+
424
+ // Helper: compute approximate area in acres from polygon vertices
425
+ function computeAreaAcresFromLatLngArray(latlngArray) {
426
+ // latlngArray: [{lat, lng}, ...]
427
+ if (!latlngArray || latlngArray.length < 3) return 0;
428
+
429
+ // Compute average latitude
430
+ let latSum = 0;
431
+ latlngArray.forEach(p => latSum += p.lat);
432
+ const latAvg = latSum / latlngArray.length;
433
+ const latAvgRad = latAvg * Math.PI / 180.0;
434
+
435
+ const metersPerDegLat = 111132.92;
436
+ const metersPerDegLon = 111320.0 * Math.cos(latAvgRad);
437
+
438
+ // Convert to planar meters
439
+ const pts = latlngArray.map(p => ({
440
+ x: p.lng * metersPerDegLon,
441
+ y: p.lat * metersPerDegLat
442
+ }));
443
+
444
+ // Shoelace
445
+ let area = 0;
446
+ for (let i = 0; i < pts.length; i++) {
447
+ const j = (i + 1) % pts.length;
448
+ area += pts[i].x * pts[j].y - pts[j].x * pts[i].y;
449
+ }
450
+ area = Math.abs(area) / 2.0; // in m^2
451
+
452
+ // convert to acres
453
+ const acres = area / 4046.8564224;
454
+ return Math.round(acres * 10000) / 10000; // 4 decimal places
455
+ }
456
+
457
+ // Event listener for polygon completion
458
+ drawingManager.addListener('polygoncomplete', function(poly) {
459
+ if (polygon) {
460
+ polygon.setMap(null);
461
+ }
462
+ polygon = poly;
463
+
464
+ // Get polygon coordinates
465
+ const coordinates = [];
466
+ const vertices = polygon.getPath();
467
+
468
+ for (let i = 0; i < vertices.getLength(); i++) {
469
+ const xy = vertices.getAt(i);
470
+ coordinates.push([xy.lng(), xy.lat()]);
471
+ }
472
+
473
+ // Convert coordinates to objects with lat,lng for easier processing
474
+ const latlngObjects = coordinates.map(c => ({ lat: c[1], lng: c[0] }));
475
+
476
+ // Store coordinates
477
+ document.getElementById('field_coordinates').value = JSON.stringify(latlngObjects);
478
+
479
+ // Calculate center point
480
+ const bounds = new google.maps.LatLngBounds();
481
+ vertices.forEach(vertex => bounds.extend(vertex));
482
+ const center = bounds.getCenter();
483
+
484
+ // Update latitude and longitude fields
485
+ document.getElementById('latitude').value = center.lat();
486
+ document.getElementById('longitude').value = center.lng();
487
+
488
+ // Compute area and update farm_size input automatically
489
+ const acres = computeAreaAcresFromLatLngArray(latlngObjects);
490
+ if (acres > 0) {
491
+ document.getElementById('farm_size').value = acres;
492
+ }
493
+
494
+ // Update status
495
+ document.getElementById('drawingStatus').innerHTML =
496
+ '<i class="fas fa-check-circle me-2"></i>Farm boundary marked successfully!';
497
+ document.getElementById('drawingStatus').className = 'alert alert-success';
498
+
499
+ // Enable submit button
500
+ document.getElementById('submitBtn').disabled = false;
501
+
502
+ // Stop drawing mode
503
+ drawingManager.setDrawingMode(null);
504
+ isDrawing = false;
505
+
506
+ // Update button text
507
+ document.getElementById('startDrawingBtn').innerHTML =
508
+ '<i class="fas fa-edit me-2"></i>Edit Boundary';
509
+ });
510
+ }
511
+
512
+ // Start drawing
513
+ document.getElementById('startDrawingBtn').addEventListener('click', function() {
514
+ if (!isDrawing) {
515
+ drawingManager.setDrawingMode(google.maps.drawing.OverlayType.POLYGON);
516
+ isDrawing = true;
517
+ this.innerHTML = '<i class="fas fa-stop me-2"></i>Stop Drawing';
518
+ this.className = 'btn btn-warning w-100';
519
+ } else {
520
+ drawingManager.setDrawingMode(null);
521
+ isDrawing = false;
522
+ this.innerHTML = '<i class="fas fa-draw-polygon me-2"></i>Draw Boundary';
523
+ this.className = 'btn btn-success w-100';
524
+ }
525
+ });
526
+
527
+ // Clear drawing
528
+ document.getElementById('clearDrawingBtn').addEventListener('click', function() {
529
+ if (polygon) {
530
+ polygon.setMap(null);
531
+ polygon = null;
532
+ }
533
+
534
+ // Clear form fields
535
+ document.getElementById('field_coordinates').value = '';
536
+ document.getElementById('latitude').value = '';
537
+ document.getElementById('longitude').value = '';
538
+
539
+ // Reset status
540
+ document.getElementById('drawingStatus').innerHTML =
541
+ '<i class="fas fa-info-circle me-2"></i>Click "Draw Boundary" and mark your farm area on the map.';
542
+ document.getElementById('drawingStatus').className = 'alert alert-info';
543
+
544
+ // Disable submit button
545
+ document.getElementById('submitBtn').disabled = true;
546
+
547
+ // Reset drawing mode
548
+ drawingManager.setDrawingMode(null);
549
+ isDrawing = false;
550
+
551
+ // Reset button
552
+ const drawBtn = document.getElementById('startDrawingBtn');
553
+ drawBtn.innerHTML = '<i class="fas fa-draw-polygon me-2"></i>Draw Boundary';
554
+ drawBtn.className = 'btn btn-success w-100';
555
+ });
556
+
557
+ // Form validation
558
+ document.getElementById('farmForm').addEventListener('submit', function(e) {
559
+ const cropTypes = document.querySelectorAll('input[name="crop_types"]:checked');
560
+
561
+ if (cropTypes.length === 0) {
562
+ e.preventDefault();
563
+ alert('Please select at least one crop type.');
564
+ return false;
565
+ }
566
+
567
+ if (!document.getElementById('latitude').value || !document.getElementById('longitude').value) {
568
+ e.preventDefault();
569
+ alert('Please mark your farm location on the map.');
570
+ return false;
571
+ }
572
+ });
573
+
574
+ // Crop rows management (add/remove)
575
+ document.getElementById('add-crop-row').addEventListener('click', function() {
576
+ const tbody = document.getElementById('crops-tbody-add');
577
+ const row = document.createElement('tr');
578
+ row.innerHTML = `
579
+ <td><input type="text" class="form-control" name="crop_name[]" required></td>
580
+ <td><input type="number" class="form-control" name="crop_area[]" step="0.01" min="0" required></td>
581
+ <td>
582
+ <select class="form-select" name="crop_month[]" required>
583
+ <option value="">Select Month</option>
584
+ <option>January</option>
585
+ <option>February</option>
586
+ <option>March</option>
587
+ <option>April</option>
588
+ <option>May</option>
589
+ <option>June</option>
590
+ <option>July</option>
591
+ <option>August</option>
592
+ <option>September</option>
593
+ <option>October</option>
594
+ <option>November</option>
595
+ <option>December</option>
596
+ </select>
597
+ </td>
598
+ <td><button type="button" class="btn btn-danger btn-sm remove-crop">Remove</button></td>
599
+ `;
600
+ tbody.appendChild(row);
601
+
602
+ // Attach remove handler
603
+ row.querySelector('.remove-crop').addEventListener('click', function() {
604
+ if (tbody.children.length > 1) row.remove();
605
+ else alert('At least one crop is required');
606
+ });
607
+ });
608
+
609
+ // Attach remove handler to initial row(s)
610
+ document.querySelectorAll('#crops-tbody-add .remove-crop').forEach(btn => {
611
+ btn.addEventListener('click', function() {
612
+ const tbody = document.getElementById('crops-tbody-add');
613
+ if (tbody.children.length > 1) this.closest('tr').remove();
614
+ else alert('At least one crop is required');
615
+ });
616
+ });
617
+
618
+ // Get user location
619
+ if (navigator.geolocation) {
620
+ navigator.geolocation.getCurrentPosition(function(position) {
621
+ const userLocation = {
622
+ lat: position.coords.latitude,
623
+ lng: position.coords.longitude
624
+ };
625
+ map.setCenter(userLocation);
626
+ map.setZoom(15);
627
+ });
628
+ }
629
+ </script>
630
+ {% endblock %}
templates/admin_dashboard.html ADDED
@@ -0,0 +1,360 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}Admin Dashboard - Farm Management Portal{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="container mt-4">
7
+ <!-- Admin Header -->
8
+ <div class="row mb-4">
9
+ <div class="col-12">
10
+ <div class="card bg-dark text-white">
11
+ <div class="card-body">
12
+ <h2><i class="fas fa-shield-alt me-2"></i>Admin Dashboard</h2>
13
+ <p class="mb-0">System Management and Monitoring</p>
14
+ </div>
15
+ </div>
16
+ </div>
17
+ </div>
18
+
19
+ <!-- Statistics Cards -->
20
+ <div class="row mb-4">
21
+ <div class="col-md-3 mb-3">
22
+ <div class="card bg-primary text-white">
23
+ <div class="card-body text-center">
24
+ <i class="fas fa-users fa-3x mb-2"></i>
25
+ <h3>{{ total_farmers }}</h3>
26
+ <p class="mb-0">Total Farmers</p>
27
+ </div>
28
+ </div>
29
+ </div>
30
+ <div class="col-md-3 mb-3">
31
+ <div class="card bg-success text-white">
32
+ <div class="card-body text-center">
33
+ <i class="fas fa-seedling fa-3x mb-2"></i>
34
+ <h3>{{ total_farms }}</h3>
35
+ <p class="mb-0">Total Farms</p>
36
+ </div>
37
+ </div>
38
+ </div>
39
+ <div class="col-md-3 mb-3">
40
+ <div class="card bg-info text-white">
41
+ <div class="card-body text-center">
42
+ <i class="fas fa-brain fa-3x mb-2"></i>
43
+ <h3>{{ total_advisories }}</h3>
44
+ <p class="mb-0">AI Advisories</p>
45
+ </div>
46
+ </div>
47
+ </div>
48
+ <div class="col-md-3 mb-3">
49
+ <div class="card bg-warning text-dark">
50
+ <div class="card-body text-center">
51
+ <i class="fas fa-sms fa-3x mb-2"></i>
52
+ <h3>{{ total_sms }}</h3>
53
+ <p class="mb-0">SMS Sent</p>
54
+ </div>
55
+ </div>
56
+ </div>
57
+ </div>
58
+
59
+ <div class="row">
60
+ <!-- Quick Actions -->
61
+ <div class="col-md-4 mb-4">
62
+ <div class="card">
63
+ <div class="card-header bg-secondary text-white">
64
+ <h5><i class="fas fa-bolt me-2"></i>Quick Actions</h5>
65
+ </div>
66
+ <div class="card-body">
67
+ <div class="d-grid gap-2">
68
+ <a href="{{ url_for('admin_farmers') }}" class="btn btn-primary">
69
+ <i class="fas fa-users me-2"></i>Manage Farmers
70
+ </a>
71
+ <a href="{{ url_for('admin_sms_logs') }}" class="btn btn-info">
72
+ <i class="fas fa-sms me-2"></i>SMS Logs
73
+ </a>
74
+ <button class="btn btn-success" onclick="generateAllAdvisories()">
75
+ <i class="fas fa-magic me-2"></i>Generate All Advisories
76
+ </button>
77
+ <button class="btn btn-warning" onclick="sendAllSMS()">
78
+ <i class="fas fa-broadcast-tower me-2"></i>Send All SMS
79
+ </button>
80
+ <a href="{{ url_for('admin_logout') }}" class="btn btn-outline-danger">
81
+ <i class="fas fa-sign-out-alt me-2"></i>Logout
82
+ </a>
83
+ </div>
84
+ </div>
85
+ </div>
86
+ </div>
87
+
88
+ <!-- Recent Farmers -->
89
+ <div class="col-md-8 mb-4">
90
+ <div class="card">
91
+ <div class="card-header">
92
+ <h5><i class="fas fa-user-plus me-2"></i>Recent Farmers</h5>
93
+ </div>
94
+ <div class="card-body">
95
+ {% if recent_farmers %}
96
+ <div class="table-responsive">
97
+ <table class="table table-striped table-sm">
98
+ <thead>
99
+ <tr>
100
+ <th>Name</th>
101
+ <th>Contact</th>
102
+ <th>Registered</th>
103
+ <th>Status</th>
104
+ <th>Actions</th>
105
+ </tr>
106
+ </thead>
107
+ <tbody>
108
+ {% for farmer in recent_farmers %}
109
+ <tr>
110
+ <td>{{ farmer.name }}</td>
111
+ <td>{{ farmer.contact_number }}</td>
112
+ <td>{{ farmer.created_at.strftime('%d %b %Y') }}</td>
113
+ <td>
114
+ <span class="badge bg-{{ 'success' if farmer.is_active else 'danger' }}">
115
+ {{ 'Active' if farmer.is_active else 'Inactive' }}
116
+ </span>
117
+ </td>
118
+ <td>
119
+ <button class="btn btn-sm btn-outline-primary" onclick="viewFarmerDetails({{ farmer.id }})">
120
+ <i class="fas fa-eye"></i>
121
+ </button>
122
+ </td>
123
+ </tr>
124
+ {% endfor %}
125
+ </tbody>
126
+ </table>
127
+ </div>
128
+ {% else %}
129
+ <div class="text-center text-muted py-3">
130
+ <i class="fas fa-users fa-3x mb-3"></i>
131
+ <p>No farmers registered yet</p>
132
+ </div>
133
+ {% endif %}
134
+ </div>
135
+ </div>
136
+ </div>
137
+ </div>
138
+
139
+ <!-- SMS Logs -->
140
+ <div class="row">
141
+ <div class="col-12">
142
+ <div class="card">
143
+ <div class="card-header">
144
+ <h5><i class="fas fa-history me-2"></i>Recent SMS Activity</h5>
145
+ </div>
146
+ <div class="card-body">
147
+ {% if recent_sms %}
148
+ <div class="table-responsive">
149
+ <table class="table table-striped table-sm">
150
+ <thead>
151
+ <tr>
152
+ <th>Farmer</th>
153
+ <th>Phone</th>
154
+ <th>Message</th>
155
+ <th>Status</th>
156
+ <th>Sent At</th>
157
+ </tr>
158
+ </thead>
159
+ <tbody>
160
+ {% for sms in recent_sms %}
161
+ <tr>
162
+ <td>{{ sms.farmer.name if sms.farmer else 'Unknown' }}</td>
163
+ <td>{{ sms.phone_number }}</td>
164
+ <td>
165
+ <span class="text-truncate d-inline-block" style="max-width: 200px;" title="{{ sms.message_content }}">
166
+ {{ sms.message_content[:50] + '...' if sms.message_content|length > 50 else sms.message_content }}
167
+ </span>
168
+ </td>
169
+ <td>
170
+ <span class="badge bg-{{ 'success' if sms.status == 'sent' else 'danger' if sms.status == 'failed' else 'warning' }}">
171
+ {{ sms.status|title }}
172
+ </span>
173
+ </td>
174
+ <td>{{ sms.sent_at.strftime('%d %b %Y %H:%M') }}</td>
175
+ </tr>
176
+ {% endfor %}
177
+ </tbody>
178
+ </table>
179
+ </div>
180
+ {% else %}
181
+ <div class="text-center text-muted py-3">
182
+ <i class="fas fa-sms fa-3x mb-3"></i>
183
+ <p>No SMS activity yet</p>
184
+ </div>
185
+ {% endif %}
186
+ </div>
187
+ </div>
188
+ </div>
189
+ </div>
190
+ </div>
191
+
192
+ <!-- Loading Modal -->
193
+ <div class="modal fade" id="loadingModal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1">
194
+ <div class="modal-dialog modal-dialog-centered">
195
+ <div class="modal-content">
196
+ <div class="modal-body text-center">
197
+ <div class="spinner-border text-primary" role="status">
198
+ <span class="visually-hidden">Loading...</span>
199
+ </div>
200
+ <p class="mt-3 mb-0">Processing request...</p>
201
+ </div>
202
+ </div>
203
+ </div>
204
+ </div>
205
+
206
+ <!-- Farmer Details Modal -->
207
+ <div class="modal fade" id="farmerModal" tabindex="-1">
208
+ <div class="modal-dialog modal-lg">
209
+ <div class="modal-content">
210
+ <div class="modal-header">
211
+ <h5 class="modal-title">Farmer Details</h5>
212
+ <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
213
+ </div>
214
+ <div class="modal-body" id="farmerModalBody">
215
+ <!-- Farmer details will be loaded here -->
216
+ </div>
217
+ </div>
218
+ </div>
219
+ </div>
220
+ {% endblock %}
221
+
222
+ {% block extra_js %}
223
+ <script>
224
+ // Helper to show/hide the Bootstrap 5 modal using vanilla JS
225
+ function _getLoadingModalInstance(){
226
+ const modalEl = document.getElementById('loadingModal');
227
+ return bootstrap.Modal.getOrCreateInstance(modalEl);
228
+ }
229
+
230
+ function _getFarmerModalInstance(){
231
+ const modalEl = document.getElementById('farmerModal');
232
+ return bootstrap.Modal.getOrCreateInstance(modalEl);
233
+ }
234
+
235
+ function generateAllAdvisories() {
236
+ if (!confirm('Generate advisories for all farms? This may take a few minutes.')) {
237
+ return;
238
+ }
239
+
240
+ const modal = _getLoadingModalInstance();
241
+ modal.show();
242
+
243
+ fetch('/admin/generate_all_advisories', {method: 'POST'})
244
+ .then(response => response.json())
245
+ .then(data => {
246
+ modal.hide();
247
+ if (data.success) {
248
+ alert(`Successfully generated ${data.count} advisories!`);
249
+ location.reload();
250
+ } else {
251
+ alert('Failed to generate advisories: ' + (data.error || 'Unknown error'));
252
+ }
253
+ })
254
+ .catch(error => {
255
+ modal.hide();
256
+ alert('Error: ' + error.message);
257
+ });
258
+ }
259
+
260
+ function sendAllSMS() {
261
+ if (!confirm('Send SMS advisories to all farmers? This will send messages immediately.')) {
262
+ return;
263
+ }
264
+
265
+ const modal = _getLoadingModalInstance();
266
+ modal.show();
267
+
268
+ fetch('/admin/send_all_sms', {method: 'POST'})
269
+ .then(response => response.json())
270
+ .then(data => {
271
+ modal.hide();
272
+ if (data.success) {
273
+ alert(`Successfully sent ${data.sent} SMS messages!`);
274
+ location.reload();
275
+ } else {
276
+ alert('Failed to send SMS: ' + (data.error || 'Unknown error'));
277
+ }
278
+ })
279
+ .catch(error => {
280
+ modal.hide();
281
+ alert('Error: ' + error.message);
282
+ });
283
+ }
284
+
285
+ function viewFarmerDetails(farmerId) {
286
+ const modal = _getLoadingModalInstance();
287
+ modal.show();
288
+
289
+ fetch(`/admin/farmer/${farmerId}`)
290
+ .then(response => response.json())
291
+ .then(data => {
292
+ modal.hide();
293
+ if (data.success) {
294
+ const farmer = data.farmer;
295
+ const farms = data.farms;
296
+
297
+ let farmsHtml = '';
298
+ if (farms.length > 0) {
299
+ farmsHtml = `
300
+ <h6>Farms:</h6>
301
+ <div class="table-responsive">
302
+ <table class="table table-sm">
303
+ <thead>
304
+ <tr>
305
+ <th>Name</th>
306
+ <th>Size</th>
307
+ <th>Crops</th>
308
+ </tr>
309
+ </thead>
310
+ <tbody>
311
+ `;
312
+ farms.forEach(farm => {
313
+ farmsHtml += `
314
+ <tr>
315
+ <td>${farm.farm_name}</td>
316
+ <td>${farm.farm_size} acres</td>
317
+ <td>${farm.crop_types.join(', ')}</td>
318
+ </tr>
319
+ `;
320
+ });
321
+ farmsHtml += '</tbody></table></div>';
322
+ } else {
323
+ farmsHtml = '<p class="text-muted">No farms registered</p>';
324
+ }
325
+
326
+ document.getElementById('farmerModalBody').innerHTML = `
327
+ <div class="row">
328
+ <div class="col-md-6">
329
+ <strong>Name:</strong> ${farmer.name}<br>
330
+ <strong>Age:</strong> ${farmer.age || 'N/A'}<br>
331
+ <strong>Gender:</strong> ${farmer.gender || 'N/A'}<br>
332
+ <strong>Aadhaar ID:</strong> ${farmer.aadhaar_id}
333
+ </div>
334
+ <div class="col-md-6">
335
+ <strong>Contact:</strong> ${farmer.contact_number}<br>
336
+ <strong>Address:</strong> ${farmer.address}<br>
337
+ <strong>Status:</strong>
338
+ <span class="badge bg-${farmer.is_active ? 'success' : 'danger'}">
339
+ ${farmer.is_active ? 'Active' : 'Inactive'}
340
+ </span><br>
341
+ <strong>Registered:</strong> ${new Date(farmer.created_at).toLocaleDateString()}
342
+ </div>
343
+ </div>
344
+ <hr>
345
+ ${farmsHtml}
346
+ `;
347
+
348
+ const farmerModal = _getFarmerModalInstance();
349
+ farmerModal.show();
350
+ } else {
351
+ alert('Failed to load farmer details');
352
+ }
353
+ })
354
+ .catch(error => {
355
+ modal.hide();
356
+ alert('Error: ' + error.message);
357
+ });
358
+ }
359
+ </script>
360
+ {% endblock %}
templates/admin_farmers.html ADDED
@@ -0,0 +1,293 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}Manage Farmers - Admin Portal{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="container-fluid py-4">
7
+ <div class="row">
8
+ <div class="col-12">
9
+ <div class="d-flex justify-content-between align-items-center mb-4">
10
+ <h2><i class="fas fa-users text-primary me-3"></i>Manage Farmers</h2>
11
+ <div>
12
+ <a href="{{ url_for('admin_dashboard') }}" class="btn btn-outline-secondary me-2">
13
+ <i class="fas fa-arrow-left me-2"></i>Back to Dashboard
14
+ </a>
15
+ <button class="btn btn-success" data-bs-toggle="modal" data-bs-target="#addFarmerModal">
16
+ <i class="fas fa-user-plus me-2"></i>Add Farmer
17
+ </button>
18
+ </div>
19
+ </div>
20
+ </div>
21
+ </div>
22
+
23
+ <!-- Farmers Table -->
24
+ <div class="row">
25
+ <div class="col-12">
26
+ <div class="card shadow">
27
+ <div class="card-header bg-primary text-white">
28
+ <h5 class="mb-0"><i class="fas fa-list me-2"></i>All Farmers</h5>
29
+ </div>
30
+ <div class="card-body">
31
+ <div class="table-responsive">
32
+ <table class="table table-striped" id="farmersTable">
33
+ <thead>
34
+ <tr>
35
+ <th>ID</th>
36
+ <th>Name</th>
37
+ <th>Aadhaar</th>
38
+ <th>Phone</th>
39
+ <th>District</th>
40
+ <th>State</th>
41
+ <th>Farms</th>
42
+ <th>Registered</th>
43
+ <th>Actions</th>
44
+ </tr>
45
+ </thead>
46
+ <tbody>
47
+ {% for farmer in farmers %}
48
+ <tr>
49
+ <td>{{ farmer.id }}</td>
50
+ <td>{{ farmer.name }}</td>
51
+ <td>{{ farmer.aadhaar_number[:4] }}****{{ farmer.aadhaar_number[-4:] }}</td>
52
+ <td>{{ farmer.phone_number }}</td>
53
+ <td>{{ farmer.district }}</td>
54
+ <td>{{ farmer.state }}</td>
55
+ <td>
56
+ <span class="badge bg-info">{{ farmer.farms|length }}</span>
57
+ </td>
58
+ <td>{{ farmer.created_at.strftime('%Y-%m-%d') }}</td>
59
+ <td>
60
+ <div class="btn-group" role="group">
61
+ <button class="btn btn-sm btn-primary" onclick="viewFarmer({{ farmer.id }})">
62
+ <i class="fas fa-eye"></i>
63
+ </button>
64
+ <button class="btn btn-sm btn-warning" onclick="editFarmer({{ farmer.id }})">
65
+ <i class="fas fa-edit"></i>
66
+ </button>
67
+ <button class="btn btn-sm btn-danger" onclick="deleteFarmer({{ farmer.id }})">
68
+ <i class="fas fa-trash"></i>
69
+ </button>
70
+ </div>
71
+ </td>
72
+ </tr>
73
+ {% endfor %}
74
+ </tbody>
75
+ </table>
76
+ </div>
77
+ </div>
78
+ </div>
79
+ </div>
80
+ </div>
81
+ </div>
82
+
83
+ <!-- Add Farmer Modal -->
84
+ <div class="modal fade" id="addFarmerModal" tabindex="-1">
85
+ <div class="modal-dialog modal-lg">
86
+ <div class="modal-content">
87
+ <div class="modal-header">
88
+ <h5 class="modal-title"><i class="fas fa-user-plus me-2"></i>Add New Farmer</h5>
89
+ <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
90
+ </div>
91
+ <form id="addFarmerForm">
92
+ <div class="modal-body">
93
+ <div class="row">
94
+ <div class="col-md-6 mb-3">
95
+ <label for="farmerName" class="form-label">Full Name *</label>
96
+ <input type="text" class="form-control" id="farmerName" required>
97
+ </div>
98
+ <div class="col-md-6 mb-3">
99
+ <label for="farmerAadhaar" class="form-label">Aadhaar Number *</label>
100
+ <input type="text" class="form-control" id="farmerAadhaar" maxlength="12" required>
101
+ </div>
102
+ <div class="col-md-6 mb-3">
103
+ <label for="farmerPhone" class="form-label">Phone Number *</label>
104
+ <input type="tel" class="form-control" id="farmerPhone" required>
105
+ </div>
106
+ <div class="col-md-6 mb-3">
107
+ <label for="farmerEmail" class="form-label">Email</label>
108
+ <input type="email" class="form-control" id="farmerEmail">
109
+ </div>
110
+ <div class="col-md-6 mb-3">
111
+ <label for="farmerDistrict" class="form-label">District *</label>
112
+ <input type="text" class="form-control" id="farmerDistrict" required>
113
+ </div>
114
+ <div class="col-md-6 mb-3">
115
+ <label for="farmerState" class="form-label">State *</label>
116
+ <input type="text" class="form-control" id="farmerState" required>
117
+ </div>
118
+ </div>
119
+ </div>
120
+ <div class="modal-footer">
121
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
122
+ <button type="submit" class="btn btn-success">
123
+ <i class="fas fa-save me-2"></i>Add Farmer
124
+ </button>
125
+ </div>
126
+ </form>
127
+ </div>
128
+ </div>
129
+ </div>
130
+
131
+ <!-- View Farmer Modal -->
132
+ <div class="modal fade" id="viewFarmerModal" tabindex="-1">
133
+ <div class="modal-dialog modal-lg">
134
+ <div class="modal-content">
135
+ <div class="modal-header">
136
+ <h5 class="modal-title"><i class="fas fa-user me-2"></i>Farmer Details</h5>
137
+ <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
138
+ </div>
139
+ <div class="modal-body" id="farmerDetailsContent">
140
+ <!-- Farmer details will be loaded here -->
141
+ </div>
142
+ <div class="modal-footer">
143
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
144
+ </div>
145
+ </div>
146
+ </div>
147
+ </div>
148
+
149
+ <script>
150
+ // Initialize DataTable
151
+ $(document).ready(function() {
152
+ $('#farmersTable').DataTable({
153
+ order: [[0, 'desc']],
154
+ pageLength: 10,
155
+ responsive: true
156
+ });
157
+ });
158
+
159
+ // Add Farmer Form
160
+ $('#addFarmerForm').on('submit', function(e) {
161
+ e.preventDefault();
162
+
163
+ const formData = {
164
+ name: $('#farmerName').val(),
165
+ aadhaar_number: $('#farmerAadhaar').val(),
166
+ phone_number: $('#farmerPhone').val(),
167
+ email: $('#farmerEmail').val(),
168
+ district: $('#farmerDistrict').val(),
169
+ state: $('#farmerState').val()
170
+ };
171
+
172
+ $.ajax({
173
+ url: '/admin/add_farmer',
174
+ method: 'POST',
175
+ contentType: 'application/json',
176
+ data: JSON.stringify(formData),
177
+ success: function(response) {
178
+ if (response.success) {
179
+ showToast('Farmer added successfully!', 'success');
180
+ $('#addFarmerModal').modal('hide');
181
+ location.reload();
182
+ } else {
183
+ showToast(response.message || 'Error adding farmer', 'error');
184
+ }
185
+ },
186
+ error: function() {
187
+ showToast('Error adding farmer', 'error');
188
+ }
189
+ });
190
+ });
191
+
192
+ // View Farmer Details
193
+ function viewFarmer(farmerId) {
194
+ $.ajax({
195
+ url: `/admin/farmer/${farmerId}`,
196
+ method: 'GET',
197
+ success: function(farmer) {
198
+ const content = `
199
+ <div class="row">
200
+ <div class="col-md-6">
201
+ <h6>Personal Information</h6>
202
+ <p><strong>Name:</strong> ${farmer.name}</p>
203
+ <p><strong>Aadhaar:</strong> ${farmer.aadhaar_number}</p>
204
+ <p><strong>Phone:</strong> ${farmer.phone_number}</p>
205
+ <p><strong>Email:</strong> ${farmer.email || 'Not provided'}</p>
206
+ </div>
207
+ <div class="col-md-6">
208
+ <h6>Location</h6>
209
+ <p><strong>District:</strong> ${farmer.district}</p>
210
+ <p><strong>State:</strong> ${farmer.state}</p>
211
+ <p><strong>Registered:</strong> ${new Date(farmer.created_at).toLocaleDateString()}</p>
212
+ </div>
213
+ </div>
214
+ <hr>
215
+ <h6>Farms (${farmer.farms.length})</h6>
216
+ <div class="table-responsive">
217
+ <table class="table table-sm">
218
+ <thead>
219
+ <tr>
220
+ <th>Name</th>
221
+ <th>Area</th>
222
+ <th>Crops</th>
223
+ <th>Location</th>
224
+ </tr>
225
+ </thead>
226
+ <tbody>
227
+ ${farmer.farms.map(farm => `
228
+ <tr>
229
+ <td>${farm.name}</td>
230
+ <td>${farm.area} acres</td>
231
+ <td>${farm.crop_types}</td>
232
+ <td>${farm.latitude.toFixed(4)}, ${farm.longitude.toFixed(4)}</td>
233
+ </tr>
234
+ `).join('')}
235
+ </tbody>
236
+ </table>
237
+ </div>
238
+ `;
239
+ $('#farmerDetailsContent').html(content);
240
+ $('#viewFarmerModal').modal('show');
241
+ },
242
+ error: function() {
243
+ showToast('Error loading farmer details', 'error');
244
+ }
245
+ });
246
+ }
247
+
248
+ // Edit Farmer
249
+ function editFarmer(farmerId) {
250
+ // Implementation for edit farmer
251
+ showToast('Edit farmer functionality coming soon!', 'info');
252
+ }
253
+
254
+ // Delete Farmer
255
+ function deleteFarmer(farmerId) {
256
+ if (confirm('Are you sure you want to delete this farmer? This action cannot be undone.')) {
257
+ $.ajax({
258
+ url: `/admin/farmer/${farmerId}`,
259
+ method: 'DELETE',
260
+ success: function(response) {
261
+ if (response.success) {
262
+ showToast('Farmer deleted successfully!', 'success');
263
+ location.reload();
264
+ } else {
265
+ showToast(response.message || 'Error deleting farmer', 'error');
266
+ }
267
+ },
268
+ error: function() {
269
+ showToast('Error deleting farmer', 'error');
270
+ }
271
+ });
272
+ }
273
+ }
274
+
275
+ // Toast notification function
276
+ function showToast(message, type) {
277
+ const toast = document.createElement('div');
278
+ toast.className = `alert alert-${type === 'success' ? 'success' : type === 'error' ? 'danger' : 'info'} alert-dismissible fade show position-fixed`;
279
+ toast.style.top = '20px';
280
+ toast.style.right = '20px';
281
+ toast.style.zIndex = '9999';
282
+ toast.innerHTML = `
283
+ ${message}
284
+ <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
285
+ `;
286
+ document.body.appendChild(toast);
287
+
288
+ setTimeout(() => {
289
+ toast.remove();
290
+ }, 5000);
291
+ }
292
+ </script>
293
+ {% endblock %}
templates/admin_login.html ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}Admin Login - Farm Management Portal{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="container">
7
+ <div class="row justify-content-center mt-5">
8
+ <div class="col-md-6 col-lg-4">
9
+ <div class="card">
10
+ <div class="card-header bg-dark text-white text-center">
11
+ <h4><i class="fas fa-shield-alt me-2"></i>Admin Login</h4>
12
+ </div>
13
+ <div class="card-body">
14
+ <form method="POST">
15
+ <div class="mb-3">
16
+ <label for="username" class="form-label">Username</label>
17
+ <input type="text" class="form-control" id="username" name="username"
18
+ placeholder="Enter admin username" required>
19
+ </div>
20
+
21
+ <div class="mb-3">
22
+ <label for="password" class="form-label">Password</label>
23
+ <input type="password" class="form-control" id="password" name="password"
24
+ placeholder="Enter admin password" required>
25
+ </div>
26
+
27
+ <div class="d-grid">
28
+ <button type="submit" class="btn btn-dark">
29
+ <i class="fas fa-sign-in-alt me-2"></i>Login as Admin
30
+ </button>
31
+ </div>
32
+ </form>
33
+
34
+ <hr>
35
+
36
+ <div class="text-center">
37
+ <small class="text-muted">Default Credentials:</small><br>
38
+ <small><strong>Username:</strong> admin</small><br>
39
+ <small><strong>Password:</strong> admin123</small>
40
+ </div>
41
+
42
+ <div class="text-center mt-3">
43
+ <a href="{{ url_for('index') }}" class="btn btn-outline-secondary btn-sm">
44
+ <i class="fas fa-home me-1"></i>Back to Home
45
+ </a>
46
+ </div>
47
+ </div>
48
+ </div>
49
+ </div>
50
+ </div>
51
+ </div>
52
+ {% endblock %}
templates/admin_sms_logs.html ADDED
@@ -0,0 +1,361 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}SMS Logs - Admin Portal{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="container-fluid py-4">
7
+ <div class="row">
8
+ <div class="col-12">
9
+ <div class="d-flex justify-content-between align-items-center mb-4">
10
+ <h2><i class="fas fa-sms text-success me-3"></i>SMS Logs</h2>
11
+ <div>
12
+ <a href="{{ url_for('admin_dashboard') }}" class="btn btn-outline-secondary me-2">
13
+ <i class="fas fa-arrow-left me-2"></i>Back to Dashboard
14
+ </a>
15
+ <button class="btn btn-success" onclick="sendTestSMS()">
16
+ <i class="fas fa-paper-plane me-2"></i>Send Test SMS
17
+ </button>
18
+ </div>
19
+ </div>
20
+ </div>
21
+ </div>
22
+
23
+ <!-- SMS Statistics -->
24
+ <div class="row mb-4">
25
+ <div class="col-md-3">
26
+ <div class="card bg-primary text-white">
27
+ <div class="card-body text-center">
28
+ <h3>{{ total_sms }}</h3>
29
+ <p class="mb-0">Total SMS Sent</p>
30
+ </div>
31
+ </div>
32
+ </div>
33
+ <div class="col-md-3">
34
+ <div class="card bg-success text-white">
35
+ <div class="card-body text-center">
36
+ <h3>{{ delivered_sms }}</h3>
37
+ <p class="mb-0">Delivered</p>
38
+ </div>
39
+ </div>
40
+ </div>
41
+ <div class="col-md-3">
42
+ <div class="card bg-warning text-white">
43
+ <div class="card-body text-center">
44
+ <h3>{{ pending_sms }}</h3>
45
+ <p class="mb-0">Pending</p>
46
+ </div>
47
+ </div>
48
+ </div>
49
+ <div class="col-md-3">
50
+ <div class="card bg-danger text-white">
51
+ <div class="card-body text-center">
52
+ <h3>{{ failed_sms }}</h3>
53
+ <p class="mb-0">Failed</p>
54
+ </div>
55
+ </div>
56
+ </div>
57
+ </div>
58
+
59
+ <!-- SMS Logs Table -->
60
+ <div class="row">
61
+ <div class="col-12">
62
+ <div class="card shadow">
63
+ <div class="card-header bg-success text-white">
64
+ <h5 class="mb-0"><i class="fas fa-list me-2"></i>SMS History</h5>
65
+ </div>
66
+ <div class="card-body">
67
+ <!-- Filter Controls -->
68
+ <div class="row mb-3">
69
+ <div class="col-md-3">
70
+ <select class="form-select" id="statusFilter">
71
+ <option value="">All Status</option>
72
+ <option value="sent">Sent</option>
73
+ <option value="delivered">Delivered</option>
74
+ <option value="failed">Failed</option>
75
+ <option value="pending">Pending</option>
76
+ </select>
77
+ </div>
78
+ <div class="col-md-3">
79
+ <input type="date" class="form-control" id="dateFilter" placeholder="Filter by date">
80
+ </div>
81
+ <div class="col-md-4">
82
+ <input type="text" class="form-control" id="phoneFilter" placeholder="Search by phone number">
83
+ </div>
84
+ <div class="col-md-2">
85
+ <button class="btn btn-primary" onclick="applyFilters()">
86
+ <i class="fas fa-filter me-2"></i>Filter
87
+ </button>
88
+ </div>
89
+ </div>
90
+
91
+ <div class="table-responsive">
92
+ <table class="table table-striped" id="smsLogsTable">
93
+ <thead>
94
+ <tr>
95
+ <th>ID</th>
96
+ <th>Farmer</th>
97
+ <th>Phone</th>
98
+ <th>Message Type</th>
99
+ <th>Content</th>
100
+ <th>Status</th>
101
+ <th>Sent At</th>
102
+ <th>Delivered At</th>
103
+ <th>Actions</th>
104
+ </tr>
105
+ </thead>
106
+ <tbody>
107
+ {% for sms in sms_logs %}
108
+ <tr>
109
+ <td>{{ sms.id }}</td>
110
+ <td>{{ sms.farmer.name if sms.farmer else 'N/A' }}</td>
111
+ <td>{{ sms.phone_number }}</td>
112
+ <td>
113
+ <span class="badge bg-info">{{ sms.message_type }}</span>
114
+ </td>
115
+ <td>
116
+ <div class="text-truncate" style="max-width: 200px;" title="{{ sms.message_content }}">
117
+ {{ sms.message_content }}
118
+ </div>
119
+ </td>
120
+ <td>
121
+ {% if sms.delivery_status == 'delivered' %}
122
+ <span class="badge bg-success">Delivered</span>
123
+ {% elif sms.delivery_status == 'failed' %}
124
+ <span class="badge bg-danger">Failed</span>
125
+ {% elif sms.delivery_status == 'pending' %}
126
+ <span class="badge bg-warning">Pending</span>
127
+ {% else %}
128
+ <span class="badge bg-primary">Sent</span>
129
+ {% endif %}
130
+ </td>
131
+ <td>{{ sms.sent_at.strftime('%Y-%m-%d %H:%M') if sms.sent_at else 'N/A' }}</td>
132
+ <td>{{ sms.delivered_at.strftime('%Y-%m-%d %H:%M') if sms.delivered_at else 'N/A' }}</td>
133
+ <td>
134
+ <div class="btn-group" role="group">
135
+ <button class="btn btn-sm btn-primary" onclick="viewSMS({{ sms.id }})">
136
+ <i class="fas fa-eye"></i>
137
+ </button>
138
+ {% if sms.delivery_status in ['failed', 'pending'] %}
139
+ <button class="btn btn-sm btn-warning" onclick="retrySMS({{ sms.id }})">
140
+ <i class="fas fa-redo"></i>
141
+ </button>
142
+ {% endif %}
143
+ </div>
144
+ </td>
145
+ </tr>
146
+ {% endfor %}
147
+ </tbody>
148
+ </table>
149
+ </div>
150
+ </div>
151
+ </div>
152
+ </div>
153
+ </div>
154
+ </div>
155
+
156
+ <!-- View SMS Modal -->
157
+ <div class="modal fade" id="viewSMSModal" tabindex="-1">
158
+ <div class="modal-dialog modal-lg">
159
+ <div class="modal-content">
160
+ <div class="modal-header">
161
+ <h5 class="modal-title"><i class="fas fa-sms me-2"></i>SMS Details</h5>
162
+ <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
163
+ </div>
164
+ <div class="modal-body" id="smsDetailsContent">
165
+ <!-- SMS details will be loaded here -->
166
+ </div>
167
+ <div class="modal-footer">
168
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
169
+ </div>
170
+ </div>
171
+ </div>
172
+ </div>
173
+
174
+ <!-- Send Test SMS Modal -->
175
+ <div class="modal fade" id="testSMSModal" tabindex="-1">
176
+ <div class="modal-dialog">
177
+ <div class="modal-content">
178
+ <div class="modal-header">
179
+ <h5 class="modal-title"><i class="fas fa-paper-plane me-2"></i>Send Test SMS</h5>
180
+ <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
181
+ </div>
182
+ <form id="testSMSForm">
183
+ <div class="modal-body">
184
+ <div class="mb-3">
185
+ <label for="testPhone" class="form-label">Phone Number *</label>
186
+ <input type="tel" class="form-control" id="testPhone" required>
187
+ </div>
188
+ <div class="mb-3">
189
+ <label for="testMessage" class="form-label">Message *</label>
190
+ <textarea class="form-control" id="testMessage" rows="4" maxlength="160" required placeholder="Enter your test message here..."></textarea>
191
+ <div class="form-text">
192
+ <span id="charCount">0</span>/160 characters
193
+ </div>
194
+ </div>
195
+ </div>
196
+ <div class="modal-footer">
197
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
198
+ <button type="submit" class="btn btn-success">
199
+ <i class="fas fa-paper-plane me-2"></i>Send SMS
200
+ </button>
201
+ </div>
202
+ </form>
203
+ </div>
204
+ </div>
205
+ </div>
206
+
207
+ <script>
208
+ // Initialize DataTable
209
+ $(document).ready(function() {
210
+ $('#smsLogsTable').DataTable({
211
+ order: [[0, 'desc']],
212
+ pageLength: 25,
213
+ responsive: true
214
+ });
215
+
216
+ // Character counter for test message
217
+ $('#testMessage').on('input', function() {
218
+ const length = $(this).val().length;
219
+ $('#charCount').text(length);
220
+ if (length > 160) {
221
+ $('#charCount').addClass('text-danger');
222
+ } else {
223
+ $('#charCount').removeClass('text-danger');
224
+ }
225
+ });
226
+ });
227
+
228
+ // Apply filters
229
+ function applyFilters() {
230
+ const status = $('#statusFilter').val();
231
+ const date = $('#dateFilter').val();
232
+ const phone = $('#phoneFilter').val();
233
+
234
+ const params = new URLSearchParams();
235
+ if (status) params.append('status', status);
236
+ if (date) params.append('date', date);
237
+ if (phone) params.append('phone', phone);
238
+
239
+ window.location.href = `{{ url_for('admin_sms_logs') }}?${params.toString()}`;
240
+ }
241
+
242
+ // View SMS Details
243
+ function viewSMS(smsId) {
244
+ $.ajax({
245
+ url: `/admin/sms/${smsId}`,
246
+ method: 'GET',
247
+ success: function(sms) {
248
+ const content = `
249
+ <div class="row">
250
+ <div class="col-md-6">
251
+ <h6>Message Information</h6>
252
+ <p><strong>ID:</strong> ${sms.id}</p>
253
+ <p><strong>Type:</strong> ${sms.message_type}</p>
254
+ <p><strong>Phone:</strong> ${sms.phone_number}</p>
255
+ <p><strong>Farmer:</strong> ${sms.farmer ? sms.farmer.name : 'N/A'}</p>
256
+ </div>
257
+ <div class="col-md-6">
258
+ <h6>Delivery Status</h6>
259
+ <p><strong>Status:</strong>
260
+ <span class="badge bg-${sms.delivery_status === 'delivered' ? 'success' :
261
+ sms.delivery_status === 'failed' ? 'danger' :
262
+ sms.delivery_status === 'pending' ? 'warning' : 'primary'}">
263
+ ${sms.delivery_status}
264
+ </span>
265
+ </p>
266
+ <p><strong>Sent At:</strong> ${sms.sent_at ? new Date(sms.sent_at).toLocaleString() : 'N/A'}</p>
267
+ <p><strong>Delivered At:</strong> ${sms.delivered_at ? new Date(sms.delivered_at).toLocaleString() : 'N/A'}</p>
268
+ <p><strong>Error Message:</strong> ${sms.error_message || 'None'}</p>
269
+ </div>
270
+ </div>
271
+ <hr>
272
+ <h6>Message Content</h6>
273
+ <div class="bg-light p-3 rounded">
274
+ ${sms.message_content}
275
+ </div>
276
+ `;
277
+ $('#smsDetailsContent').html(content);
278
+ $('#viewSMSModal').modal('show');
279
+ },
280
+ error: function() {
281
+ showToast('Error loading SMS details', 'error');
282
+ }
283
+ });
284
+ }
285
+
286
+ // Retry SMS
287
+ function retrySMS(smsId) {
288
+ if (confirm('Are you sure you want to retry sending this SMS?')) {
289
+ $.ajax({
290
+ url: `/admin/sms/${smsId}/retry`,
291
+ method: 'POST',
292
+ success: function(response) {
293
+ if (response.success) {
294
+ showToast('SMS retry initiated successfully!', 'success');
295
+ location.reload();
296
+ } else {
297
+ showToast(response.message || 'Error retrying SMS', 'error');
298
+ }
299
+ },
300
+ error: function() {
301
+ showToast('Error retrying SMS', 'error');
302
+ }
303
+ });
304
+ }
305
+ }
306
+
307
+ // Send Test SMS
308
+ function sendTestSMS() {
309
+ $('#testSMSModal').modal('show');
310
+ }
311
+
312
+ // Test SMS Form
313
+ $('#testSMSForm').on('submit', function(e) {
314
+ e.preventDefault();
315
+
316
+ const formData = {
317
+ phone_number: $('#testPhone').val(),
318
+ message: $('#testMessage').val()
319
+ };
320
+
321
+ $.ajax({
322
+ url: '/admin/send_test_sms',
323
+ method: 'POST',
324
+ contentType: 'application/json',
325
+ data: JSON.stringify(formData),
326
+ success: function(response) {
327
+ if (response.success) {
328
+ showToast('Test SMS sent successfully!', 'success');
329
+ $('#testSMSModal').modal('hide');
330
+ $('#testSMSForm')[0].reset();
331
+ $('#charCount').text('0');
332
+ setTimeout(() => location.reload(), 2000);
333
+ } else {
334
+ showToast(response.message || 'Error sending test SMS', 'error');
335
+ }
336
+ },
337
+ error: function() {
338
+ showToast('Error sending test SMS', 'error');
339
+ }
340
+ });
341
+ });
342
+
343
+ // Toast notification function
344
+ function showToast(message, type) {
345
+ const toast = document.createElement('div');
346
+ toast.className = `alert alert-${type === 'success' ? 'success' : type === 'error' ? 'danger' : 'info'} alert-dismissible fade show position-fixed`;
347
+ toast.style.top = '20px';
348
+ toast.style.right = '20px';
349
+ toast.style.zIndex = '9999';
350
+ toast.innerHTML = `
351
+ ${message}
352
+ <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
353
+ `;
354
+ document.body.appendChild(toast);
355
+
356
+ setTimeout(() => {
357
+ toast.remove();
358
+ }, 5000);
359
+ }
360
+ </script>
361
+ {% endblock %}
templates/base.html ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>{% block title %}Farm Management Portal{% endblock %}</title>
7
+
8
+ <!-- Bootstrap CSS -->
9
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
10
+ <!-- Font Awesome -->
11
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
12
+
13
+ <style>
14
+ .navbar-brand {
15
+ font-weight: bold;
16
+ }
17
+ .card {
18
+ box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
19
+ border: 1px solid rgba(0, 0, 0, 0.125);
20
+ }
21
+ .btn-primary {
22
+ background-color: #2d5f3f;
23
+ border-color: #2d5f3f;
24
+ }
25
+ .btn-primary:hover {
26
+ background-color: #1e3f2a;
27
+ border-color: #1e3f2a;
28
+ }
29
+ .bg-success {
30
+ background-color: #2d5f3f !important;
31
+ }
32
+ .text-success {
33
+ color: #2d5f3f !important;
34
+ }
35
+ .alert-dismissible .btn-close {
36
+ padding: 0.75rem 1.25rem;
37
+ }
38
+ .footer {
39
+ background-color: #2d5f3f;
40
+ color: white;
41
+ padding: 20px 0;
42
+ margin-top: 50px;
43
+ }
44
+ </style>
45
+
46
+ {% block extra_css %}{% endblock %}
47
+ </head>
48
+ <body class="bg-light">
49
+ <!-- Navigation -->
50
+ <nav class="navbar navbar-expand-lg navbar-dark bg-success">
51
+ <div class="container">
52
+ <a class="navbar-brand" href="{{ url_for('index') }}">
53
+ <i class="fas fa-seedling me-2"></i>Farm Management Portal
54
+ </a>
55
+
56
+ <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
57
+ <span class="navbar-toggler-icon"></span>
58
+ </button>
59
+
60
+ <div class="collapse navbar-collapse" id="navbarNav">
61
+ <ul class="navbar-nav me-auto">
62
+ <li class="nav-item">
63
+ <a class="nav-link" href="{{ url_for('index') }}">
64
+ <i class="fas fa-home me-1"></i>Home
65
+ </a>
66
+ </li>
67
+
68
+ {% if current_user.is_authenticated %}
69
+ <li class="nav-item">
70
+ <a class="nav-link" href="{{ url_for('farmer_dashboard') }}">
71
+ <i class="fas fa-dashboard me-1"></i>Dashboard
72
+ </a>
73
+ </li>
74
+ {% endif %}
75
+ </ul>
76
+
77
+ <ul class="navbar-nav">
78
+ {% if current_user.is_authenticated %}
79
+ <li class="nav-item dropdown">
80
+ <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown">
81
+ <i class="fas fa-user me-1"></i>{{ current_user.farmer.name }}
82
+ </a>
83
+ <ul class="dropdown-menu">
84
+ <li><a class="dropdown-item" href="{{ url_for('farmer_dashboard') }}">
85
+ <i class="fas fa-dashboard me-2"></i>Dashboard
86
+ </a></li>
87
+ <li><hr class="dropdown-divider"></li>
88
+ <li><a class="dropdown-item" href="{{ url_for('farmer_logout') }}">
89
+ <i class="fas fa-sign-out-alt me-2"></i>Logout
90
+ </a></li>
91
+ </ul>
92
+ </li>
93
+ {% else %}
94
+ <li class="nav-item">
95
+ <a class="nav-link" href="{{ url_for('farmer_login') }}">
96
+ <i class="fas fa-sign-in-alt me-1"></i>Login
97
+ </a>
98
+ </li>
99
+ <li class="nav-item">
100
+ <a class="nav-link" href="{{ url_for('farmer_register') }}">
101
+ <i class="fas fa-user-plus me-1"></i>Register
102
+ </a>
103
+ </li>
104
+ {% endif %}
105
+
106
+ <li class="nav-item">
107
+ <a class="nav-link" href="{{ url_for('admin_login') }}">
108
+ <i class="fas fa-cog me-1"></i>Admin
109
+ </a>
110
+ </li>
111
+ </ul>
112
+ </div>
113
+ </div>
114
+ </nav>
115
+
116
+ <!-- Flash Messages -->
117
+ {% with messages = get_flashed_messages(with_categories=true) %}
118
+ {% if messages %}
119
+ <div class="container mt-3">
120
+ {% for category, message in messages %}
121
+ <div class="alert alert-{{ 'danger' if category == 'error' else 'success' if category == 'success' else 'info' }} alert-dismissible fade show" role="alert">
122
+ <i class="fas fa-{{ 'exclamation-triangle' if category == 'error' else 'check-circle' if category == 'success' else 'info-circle' }} me-2"></i>
123
+ {{ message }}
124
+ <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
125
+ </div>
126
+ {% endfor %}
127
+ </div>
128
+ {% endif %}
129
+ {% endwith %}
130
+
131
+ <!-- Main Content -->
132
+ <main>
133
+ {% block content %}{% endblock %}
134
+ </main>
135
+
136
+ <!-- Footer -->
137
+ <footer class="footer mt-auto">
138
+ <div class="container">
139
+ <div class="row">
140
+ <div class="col-md-6">
141
+ <h6><i class="fas fa-seedling me-2"></i>Farm Management Portal</h6>
142
+ <p class="mb-0">AI-powered farming solutions with daily recommendations and SMS alerts.</p>
143
+ </div>
144
+ <div class="col-md-6 text-md-end">
145
+ <p class="mb-0">&copy; 2025 Farm Management Portal. All rights reserved.</p>
146
+ <small>Powered by Gemini AI & Twilio SMS</small>
147
+ </div>
148
+ </div>
149
+ </div>
150
+ </footer>
151
+
152
+ <!-- Bootstrap JS -->
153
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
154
+
155
+ {% block extra_js %}{% endblock %}
156
+ </body>
157
+ </html>
templates/edit_farm.html ADDED
@@ -0,0 +1,422 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>Edit Farm</title>
7
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
8
+ <style>
9
+ #map {
10
+ height: 50vh;
11
+ width: 100%;
12
+ border-radius: 8px;
13
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
14
+ margin-bottom: 20px;
15
+ }
16
+ .form-section {
17
+ background-color: #f8f9fa;
18
+ padding: 20px;
19
+ border-radius: 8px;
20
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
21
+ margin-bottom: 20px;
22
+ }
23
+ </style>
24
+ </head>
25
+ <body class="bg-light">
26
+ <div class="container py-5">
27
+ <div class="d-flex justify-content-between align-items-center mb-4">
28
+ <h1>Edit Farm</h1>
29
+ <div>
30
+ <a href="/farm_details/{{ farm.id }}" class="btn btn-secondary">Back to Farm Details</a>
31
+ </div>
32
+ </div>
33
+
34
+ <form id="editFarmForm" action="/edit_farm/{{ farm.id }}" method="POST">
35
+ <div class="row">
36
+ <!-- Farmer Details Section -->
37
+ <div class="col-lg-6">
38
+ <div class="form-section">
39
+ <h3 class="mb-3">Farmer Details</h3>
40
+
41
+ <div class="mb-3">
42
+ <label for="farmer_name" class="form-label">Farmer Name *</label>
43
+ <input type="text" class="form-control" id="farmer_name" name="farmer_name" value="{{ farm.farmer_name }}" required>
44
+ </div>
45
+
46
+ <div class="mb-3">
47
+ <label for="contact" class="form-label">Contact Number *</label>
48
+ <input type="text" class="form-control" id="contact" name="contact" value="{{ farm.contact }}" required>
49
+ </div>
50
+
51
+ <div class="mb-3">
52
+ <label for="address" class="form-label">Address *</label>
53
+ <textarea class="form-control" id="address" name="address" rows="3" required>{{ farm.address }}</textarea>
54
+ </div>
55
+
56
+ <div class="mb-3">
57
+ <label for="crop_type" class="form-label">Main Crop Type (For compatibility)</label>
58
+ <select class="form-control" id="crop_type" name="crop_type">
59
+ <option value="">Select Crop Type</option>
60
+ <option value="Rice" {% if farm.crop_type == 'Rice' %}selected{% endif %}>Rice</option>
61
+ <option value="Wheat" {% if farm.crop_type == 'Wheat' %}selected{% endif %}>Wheat</option>
62
+ <option value="Corn" {% if farm.crop_type == 'Corn' %}selected{% endif %}>Corn</option>
63
+ <option value="Cotton" {% if farm.crop_type == 'Cotton' %}selected{% endif %}>Cotton</option>
64
+ <option value="Sugarcane" {% if farm.crop_type == 'Sugarcane' %}selected{% endif %}>Sugarcane</option>
65
+ <option value="Vegetables" {% if farm.crop_type == 'Vegetables' %}selected{% endif %}>Vegetables</option>
66
+ <option value="Fruits" {% if farm.crop_type == 'Fruits' %}selected{% endif %}>Fruits</option>
67
+ <option value="Other" {% if farm.crop_type == 'Other' %}selected{% endif %}>Other</option>
68
+ </select>
69
+ </div>
70
+
71
+ <div class="mb-3">
72
+ <h4>Crop Details</h4>
73
+ <div class="table-responsive">
74
+ <table class="table table-bordered" id="crops-table">
75
+ <thead>
76
+ <tr>
77
+ <th>Crop Name</th>
78
+ <th>Area (acres)</th>
79
+ <th>Sowing Month</th>
80
+ <th>Actions</th>
81
+ </tr>
82
+ </thead>
83
+ <tbody id="crops-tbody">
84
+ {% if crops_data and crops_data|length > 0 %}
85
+ {% for crop in crops_data %}
86
+ <tr>
87
+ <td>
88
+ <input type="text" class="form-control" name="crop_name[]" value="{{ crop.name }}" required>
89
+ </td>
90
+ <td>
91
+ <input type="number" class="form-control" name="crop_area[]" step="0.01" min="0" value="{{ crop.area }}" required>
92
+ </td>
93
+ <td>
94
+ <select class="form-control" name="crop_month[]" required>
95
+ <option value="">Select Month</option>
96
+ <option value="January" {% if crop.sowing_month == 'January' %}selected{% endif %}>January</option>
97
+ <option value="February" {% if crop.sowing_month == 'February' %}selected{% endif %}>February</option>
98
+ <option value="March" {% if crop.sowing_month == 'March' %}selected{% endif %}>March</option>
99
+ <option value="April" {% if crop.sowing_month == 'April' %}selected{% endif %}>April</option>
100
+ <option value="May" {% if crop.sowing_month == 'May' %}selected{% endif %}>May</option>
101
+ <option value="June" {% if crop.sowing_month == 'June' %}selected{% endif %}>June</option>
102
+ <option value="July" {% if crop.sowing_month == 'July' %}selected{% endif %}>July</option>
103
+ <option value="August" {% if crop.sowing_month == 'August' %}selected{% endif %}>August</option>
104
+ <option value="September" {% if crop.sowing_month == 'September' %}selected{% endif %}>September</option>
105
+ <option value="October" {% if crop.sowing_month == 'October' %}selected{% endif %}>October</option>
106
+ <option value="November" {% if crop.sowing_month == 'November' %}selected{% endif %}>November</option>
107
+ <option value="December" {% if crop.sowing_month == 'December' %}selected{% endif %}>December</option>
108
+ </select>
109
+ </td>
110
+ <td>
111
+ <button type="button" class="btn btn-danger btn-sm remove-crop">Remove</button>
112
+ </td>
113
+ </tr>
114
+ {% endfor %}
115
+ {% else %}
116
+ <tr>
117
+ <td>
118
+ <input type="text" class="form-control" name="crop_name[]" required>
119
+ </td>
120
+ <td>
121
+ <input type="number" class="form-control" name="crop_area[]" step="0.01" min="0" required>
122
+ </td>
123
+ <td>
124
+ <select class="form-control" name="crop_month[]" required>
125
+ <option value="">Select Month</option>
126
+ <option value="January">January</option>
127
+ <option value="February">February</option>
128
+ <option value="March">March</option>
129
+ <option value="April">April</option>
130
+ <option value="May">May</option>
131
+ <option value="June">June</option>
132
+ <option value="July">July</option>
133
+ <option value="August">August</option>
134
+ <option value="September">September</option>
135
+ <option value="October">October</option>
136
+ <option value="November">November</option>
137
+ <option value="December">December</option>
138
+ </select>
139
+ </td>
140
+ <td>
141
+ <button type="button" class="btn btn-danger btn-sm remove-crop">Remove</button>
142
+ </td>
143
+ </tr>
144
+ {% endif %}
145
+ </tbody>
146
+ </table>
147
+ <button type="button" id="add-crop" class="btn btn-success">Add Another Crop</button>
148
+ </div>
149
+ </div>
150
+ </div>
151
+ </div>
152
+
153
+ <!-- Farm Geolocation Section -->
154
+ <div class="col-lg-6">
155
+ <div class="form-section">
156
+ <h3 class="mb-3">Farm Location</h3>
157
+
158
+ <div class="mb-3">
159
+ <div id="map"></div>
160
+ </div>
161
+
162
+ <div class="row mb-3">
163
+ <div class="col-6">
164
+ <button type="button" id="startDrawingBtn" class="btn btn-success w-100">Edit Drawing</button>
165
+ </div>
166
+ <div class="col-6">
167
+ <button type="button" id="clearDrawingBtn" class="btn btn-danger w-100">Clear Drawing</button>
168
+ </div>
169
+ </div>
170
+
171
+ <div class="alert alert-info" id="drawingStatus">
172
+ Edit the farm boundary on the map if needed.
173
+ </div>
174
+
175
+ <input type="hidden" id="field_coordinates" name="field_coordinates" value="{{ farm.field_coordinates }}">
176
+ <input type="hidden" id="center_lat" name="center_lat" value="{{ farm.center_lat }}">
177
+ <input type="hidden" id="center_lng" name="center_lng" value="{{ farm.center_lng }}">
178
+
179
+ <div class="mb-3">
180
+ <label for="area" class="form-label">Farm Area (acres)</label>
181
+ <input type="number" class="form-control" id="area" name="area" step="0.01" min="0" value="{{ farm.area }}">
182
+ <small class="text-muted">Leave empty to calculate automatically from the drawn boundary.</small>
183
+ </div>
184
+ </div>
185
+
186
+ <div class="d-grid">
187
+ <button type="submit" id="submitBtn" class="btn btn-primary btn-lg">Save Changes</button>
188
+ </div>
189
+ </div>
190
+ </div>
191
+ </form>
192
+ </div>
193
+
194
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
195
+ <script>
196
+ let map;
197
+ let polygon = null;
198
+ let drawingManager;
199
+
200
+ function initMap() {
201
+ // Center the map on farm coordinates
202
+ const farmCenter = {
203
+ lat: {{ farm.latitude }},
204
+ lng: {{ farm.longitude }}
205
+ };
206
+
207
+ map = new google.maps.Map(document.getElementById('map'), {
208
+ zoom: 16,
209
+ center: farmCenter,
210
+ mapTypeId: 'satellite',
211
+ mapTypeControl: true,
212
+ streetViewControl: false,
213
+ fullscreenControl: true,
214
+ });
215
+
216
+ // Initialize drawing tools
217
+ drawingManager = new google.maps.drawing.DrawingManager({
218
+ drawingMode: null,
219
+ drawingControl: false,
220
+ polygonOptions: {
221
+ fillColor: '#4CAF50',
222
+ fillOpacity: 0.4,
223
+ strokeWeight: 2,
224
+ strokeColor: '#4CAF50',
225
+ editable: true
226
+ }
227
+ });
228
+ drawingManager.setMap(map);
229
+
230
+ // Load existing farm boundary if exists
231
+ {% if farm.field_coordinates %}
232
+ try {
233
+ const fieldCoords = JSON.parse('{{ farm.field_coordinates|safe }}');
234
+ const paths = fieldCoords.map(coord => new google.maps.LatLng(coord.lat, coord.lng));
235
+
236
+ polygon = new google.maps.Polygon({
237
+ paths: paths,
238
+ fillColor: '#4CAF50',
239
+ fillOpacity: 0.4,
240
+ strokeWeight: 2,
241
+ strokeColor: '#4CAF50',
242
+ editable: true
243
+ });
244
+
245
+ polygon.setMap(map);
246
+
247
+ // Add listener for polygon changes
248
+ google.maps.event.addListener(polygon.getPath(), 'set_at', updateCoordinatesFields);
249
+ google.maps.event.addListener(polygon.getPath(), 'insert_at', updateCoordinatesFields);
250
+
251
+ // Update status
252
+ document.getElementById('drawingStatus').className = 'alert alert-success';
253
+ document.getElementById('drawingStatus').textContent = 'Farm boundary loaded successfully!';
254
+ } catch (e) {
255
+ console.error('Error parsing farm boundary:', e);
256
+ }
257
+ {% endif %}
258
+
259
+ // Setup event listeners for drawing
260
+ google.maps.event.addListener(drawingManager, 'polygoncomplete', function(poly) {
261
+ // Remove old polygon if exists
262
+ if (polygon !== null) {
263
+ polygon.setMap(null);
264
+ }
265
+
266
+ polygon = poly;
267
+ drawingManager.setDrawingMode(null);
268
+ document.getElementById('startDrawingBtn').textContent = "Edit Drawing";
269
+
270
+ // Update hidden fields with polygon data
271
+ updateCoordinatesFields();
272
+
273
+ // Add listener for polygon changes
274
+ google.maps.event.addListener(polygon.getPath(), 'set_at', updateCoordinatesFields);
275
+ google.maps.event.addListener(polygon.getPath(), 'insert_at', updateCoordinatesFields);
276
+
277
+ // Update status
278
+ document.getElementById('drawingStatus').className = 'alert alert-success';
279
+ document.getElementById('drawingStatus').textContent = 'Farm boundary updated successfully!';
280
+ });
281
+
282
+ // Setup buttons
283
+ document.getElementById('startDrawingBtn').addEventListener('click', function() {
284
+ if (drawingManager.getDrawingMode() == google.maps.drawing.OverlayType.POLYGON) {
285
+ drawingManager.setDrawingMode(null);
286
+ this.textContent = polygon ? "Edit Drawing" : "Start Drawing";
287
+ } else {
288
+ drawingManager.setDrawingMode(google.maps.drawing.OverlayType.POLYGON);
289
+ this.textContent = "Cancel Drawing";
290
+ }
291
+ });
292
+
293
+ document.getElementById('clearDrawingBtn').addEventListener('click', function() {
294
+ if (polygon) {
295
+ polygon.setMap(null);
296
+ polygon = null;
297
+ }
298
+ document.getElementById('startDrawingBtn').textContent = "Start Drawing";
299
+ document.getElementById('drawingStatus').className = 'alert alert-warning';
300
+ document.getElementById('drawingStatus').textContent = 'Please draw your farm boundary on the map.';
301
+
302
+ // Clear hidden fields
303
+ document.getElementById('field_coordinates').value = '';
304
+ document.getElementById('center_lat').value = '';
305
+ document.getElementById('center_lng').value = '';
306
+ });
307
+ }
308
+
309
+ function updateCoordinatesFields() {
310
+ if (!polygon) return;
311
+
312
+ // Get polygon path and convert to array of coordinates
313
+ const path = polygon.getPath();
314
+ const coordinates = [];
315
+ for (let i = 0; i < path.getLength(); i++) {
316
+ const point = path.getAt(i);
317
+ coordinates.push({
318
+ lat: point.lat(),
319
+ lng: point.lng()
320
+ });
321
+ }
322
+
323
+ // Calculate center of polygon
324
+ const bounds = new google.maps.LatLngBounds();
325
+ path.forEach(latlng => bounds.extend(latlng));
326
+ const center = bounds.getCenter();
327
+
328
+ // Update hidden fields
329
+ document.getElementById('field_coordinates').value = JSON.stringify(coordinates);
330
+ document.getElementById('center_lat').value = center.lat();
331
+ document.getElementById('center_lng').value = center.lng();
332
+
333
+ // If area input is empty, compute and populate approximate area (acres)
334
+ try {
335
+ // compute approximate area using same method as add_farm
336
+ if (coordinates.length >= 3) {
337
+ let latSum = 0;
338
+ coordinates.forEach(p => latSum += p.lat);
339
+ const latAvg = latSum / coordinates.length;
340
+ const latAvgRad = latAvg * Math.PI / 180.0;
341
+
342
+ const metersPerDegLat = 111132.92;
343
+ const metersPerDegLon = 111320.0 * Math.cos(latAvgRad);
344
+
345
+ const pts = coordinates.map(p => ({ x: p.lng * metersPerDegLon, y: p.lat * metersPerDegLat }));
346
+ let area = 0;
347
+ for (let i = 0; i < pts.length; i++) {
348
+ const j = (i + 1) % pts.length;
349
+ area += pts[i].x * pts[j].y - pts[j].x * pts[i].y;
350
+ }
351
+ area = Math.abs(area) / 2.0; // m^2
352
+ const acres = Math.round((area / 4046.8564224) * 10000) / 10000;
353
+
354
+ const areaInput = document.getElementById('area');
355
+ if (areaInput && (!areaInput.value || Number(areaInput.value) === 0)) {
356
+ areaInput.value = acres;
357
+ }
358
+ }
359
+ } catch (e) {
360
+ console.error('Error computing area:', e);
361
+ }
362
+ }
363
+
364
+ // Crop management
365
+ document.getElementById('add-crop').addEventListener('click', function() {
366
+ const tbody = document.getElementById('crops-tbody');
367
+ const newRow = document.createElement('tr');
368
+ newRow.innerHTML = `
369
+ <td>
370
+ <input type="text" class="form-control" name="crop_name[]" required>
371
+ </td>
372
+ <td>
373
+ <input type="number" class="form-control" name="crop_area[]" step="0.01" min="0" required>
374
+ </td>
375
+ <td>
376
+ <select class="form-control" name="crop_month[]" required>
377
+ <option value="">Select Month</option>
378
+ <option value="January">January</option>
379
+ <option value="February">February</option>
380
+ <option value="March">March</option>
381
+ <option value="April">April</option>
382
+ <option value="May">May</option>
383
+ <option value="June">June</option>
384
+ <option value="July">July</option>
385
+ <option value="August">August</option>
386
+ <option value="September">September</option>
387
+ <option value="October">October</option>
388
+ <option value="November">November</option>
389
+ <option value="December">December</option>
390
+ </select>
391
+ </td>
392
+ <td>
393
+ <button type="button" class="btn btn-danger btn-sm remove-crop">Remove</button>
394
+ </td>
395
+ `;
396
+ tbody.appendChild(newRow);
397
+
398
+ // Add event listener to new remove button
399
+ newRow.querySelector('.remove-crop').addEventListener('click', function() {
400
+ if (tbody.children.length > 1) {
401
+ tbody.removeChild(newRow);
402
+ } else {
403
+ alert('You must have at least one crop entry.');
404
+ }
405
+ });
406
+ });
407
+
408
+ // Add event listeners to existing remove buttons
409
+ document.querySelectorAll('.remove-crop').forEach(button => {
410
+ button.addEventListener('click', function() {
411
+ const tbody = document.getElementById('crops-tbody');
412
+ if (tbody.children.length > 1) {
413
+ tbody.removeChild(this.closest('tr'));
414
+ } else {
415
+ alert('You must have at least one crop entry.');
416
+ }
417
+ });
418
+ });
419
+ </script>
420
+ <script async defer src="https://maps.googleapis.com/maps/api/js?key=AIzaSyBvVLjWmCja331H8SuIZ4UlJdZytuYkC6Y&libraries=drawing&callback=initMap"></script>
421
+ </body>
422
+ </html>
templates/error.html ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}Error {{ error_code }} - Farm Management Portal{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="container py-5">
7
+ <div class="row justify-content-center">
8
+ <div class="col-md-6 text-center">
9
+ <div class="card shadow">
10
+ <div class="card-body py-5">
11
+ <div class="mb-4">
12
+ <i class="fas fa-exclamation-triangle fa-5x text-warning"></i>
13
+ </div>
14
+ <h1 class="display-1 text-muted">{{ error_code }}</h1>
15
+ <h3 class="mb-3">{{ error_message }}</h3>
16
+
17
+ {% if error_code == 404 %}
18
+ <p class="text-muted mb-4">
19
+ The page you're looking for doesn't exist or has been moved.
20
+ </p>
21
+ {% elif error_code == 500 %}
22
+ <p class="text-muted mb-4">
23
+ Something went wrong on our end. We're working to fix it.
24
+ </p>
25
+ {% else %}
26
+ <p class="text-muted mb-4">
27
+ An error occurred while processing your request.
28
+ </p>
29
+ {% endif %}
30
+
31
+ <div class="d-flex justify-content-center gap-3">
32
+ <a href="{{ url_for('index') }}" class="btn btn-success">
33
+ <i class="fas fa-home me-2"></i>Go Home
34
+ </a>
35
+ <button onclick="history.back()" class="btn btn-outline-secondary">
36
+ <i class="fas fa-arrow-left me-2"></i>Go Back
37
+ </button>
38
+ </div>
39
+ </div>
40
+ </div>
41
+ </div>
42
+ </div>
43
+ </div>
44
+ {% endblock %}
templates/farm_details.html ADDED
@@ -0,0 +1,252 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>Farm Details</title>
7
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
8
+ <style>
9
+ #map {
10
+ height: 400px;
11
+ width: 100%;
12
+ border-radius: 8px;
13
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
14
+ margin-bottom: 20px;
15
+ }
16
+ .card {
17
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
18
+ margin-bottom: 20px;
19
+ }
20
+ .weather-icon {
21
+ width: 64px;
22
+ height: 64px;
23
+ }
24
+ </style>
25
+ </head>
26
+ <body class="bg-light">
27
+ <div class="container py-5">
28
+ <div class="d-flex justify-content-between align-items-center mb-4">
29
+ <h1>Farm Details</h1>
30
+ <div>
31
+ <a href="/farms" class="btn btn-secondary me-2">Back to Farms</a>
32
+ <a href="/edit_farm/{{ farm.id }}" class="btn btn-primary">Edit Farm</a>
33
+ <button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#deleteFarmModal">
34
+ Delete Farm
35
+ </button>
36
+ </div>
37
+ </div>
38
+
39
+ <div class="row">
40
+ <!-- Farm Information -->
41
+ <div class="col-lg-6">
42
+ <div class="card">
43
+ <div class="card-header bg-primary text-white">
44
+ <h3 class="card-title">Farm Information</h3>
45
+ </div>
46
+ <div class="card-body">
47
+ <table class="table table-striped">
48
+ <tr>
49
+ <th>Farmer Name</th>
50
+ <td>{{ farm.owner.name }}</td>
51
+ </tr>
52
+ <tr>
53
+ <th>Contact</th>
54
+ <td>{{ farm.owner.contact_number }}</td>
55
+ </tr>
56
+ <tr>
57
+ <th>Address</th>
58
+ <td>{{ farm.owner.address }}</td>
59
+ </tr>
60
+ <tr>
61
+ <th>Total Farm Area</th>
62
+ <td>{{ farm.farm_size|round(2) if farm.farm_size else 'Not specified' }} acres</td>
63
+ </tr>
64
+ <tr>
65
+ <th>Registration Date</th>
66
+ <td>{{ farm.created_at.strftime('%d %b %Y') }}</td>
67
+ </tr>
68
+ </table>
69
+ </div>
70
+ </div>
71
+
72
+ <!-- Crops Information -->
73
+ <div class="card">
74
+ <div class="card-header bg-success text-white">
75
+ <h3 class="card-title">Crops Information</h3>
76
+ </div>
77
+ <div class="card-body">
78
+ {% if crops_data and crops_data|length > 0 %}
79
+ <table class="table table-bordered">
80
+ <thead>
81
+ <tr>
82
+ <th>Crop Name</th>
83
+ <th>Area (acres)</th>
84
+ <th>Sowing Month</th>
85
+ </tr>
86
+ </thead>
87
+ <tbody>
88
+ {% for crop in crops_data %}
89
+ <tr>
90
+ <td>{{ crop.name }}</td>
91
+ <td>{{ crop.area }}</td>
92
+ <td>{{ crop.sowing_month }}</td>
93
+ </tr>
94
+ {% endfor %}
95
+ </tbody>
96
+ </table>
97
+ {% else %}
98
+ <p class="text-muted">No crop details available.</p>
99
+ {% endif %}
100
+ </div>
101
+ </div>
102
+ </div>
103
+
104
+ <!-- Map and Weather -->
105
+ <div class="col-lg-6">
106
+ <div class="card">
107
+ <div class="card-header bg-info text-white">
108
+ <h3 class="card-title">Farm Location</h3>
109
+ </div>
110
+ <div class="card-body">
111
+ <div id="map"></div>
112
+ </div>
113
+ </div>
114
+
115
+ <div class="card">
116
+ <div class="card-header bg-warning">
117
+ <h3 class="card-title">Current Weather</h3>
118
+ </div>
119
+ <div class="card-body">
120
+ {% if weather and not weather.get('error', False) %}
121
+ <div class="row align-items-center">
122
+ <div class="col-md-6">
123
+ <h4>{{ weather.name }}</h4>
124
+ <div class="d-flex align-items-center">
125
+ <img src="http://openweathermap.org/img/wn/{{ weather.weather[0].icon }}@2x.png" alt="{{ weather.weather[0].description }}" class="weather-icon me-3">
126
+ <div>
127
+ <h2>{{ weather.main.temp|round(1) }}°C</h2>
128
+ <p class="mb-0">{{ weather.weather[0].description|capitalize }}</p>
129
+ </div>
130
+ </div>
131
+ </div>
132
+ <div class="col-md-6">
133
+ <ul class="list-unstyled">
134
+ <li><strong>Humidity:</strong> {{ weather.main.humidity }}%</li>
135
+ <li><strong>Wind:</strong> {{ weather.wind.speed }} m/s</li>
136
+ {% if weather.main.get('feels_like') %}
137
+ <li><strong>Feels like:</strong> {{ weather.main.feels_like|round(1) }}°C</li>
138
+ {% endif %}
139
+ </ul>
140
+ </div>
141
+ </div>
142
+ {% else %}
143
+ <p>Weather data unavailable.</p>
144
+ {% endif %}
145
+ </div>
146
+ </div>
147
+ </div>
148
+ </div>
149
+
150
+ <!-- Agriculture News Section -->
151
+ {% if news and news|length > 0 %}
152
+ <div class="card mt-4">
153
+ <div class="card-header bg-secondary text-white">
154
+ <h3 class="card-title">Agriculture News</h3>
155
+ </div>
156
+ <div class="card-body">
157
+ <div class="row">
158
+ {% for article in news %}
159
+ <div class="col-md-4 mb-3">
160
+ <div class="card h-100">
161
+ {% if article.urlToImage %}
162
+ <img src="{{ article.urlToImage }}" class="card-img-top" alt="{{ article.title }}">
163
+ {% endif %}
164
+ <div class="card-body">
165
+ <h5 class="card-title">{{ article.title }}</h5>
166
+ <p class="card-text small">{{ article.description|truncate(100) }}</p>
167
+ </div>
168
+ <div class="card-footer">
169
+ <a href="{{ article.url }}" target="_blank" class="btn btn-sm btn-primary">Read More</a>
170
+ </div>
171
+ </div>
172
+ </div>
173
+ {% endfor %}
174
+ </div>
175
+ </div>
176
+ </div>
177
+ {% endif %}
178
+ </div>
179
+
180
+ <!-- Delete Farm Modal -->
181
+ <div class="modal fade" id="deleteFarmModal" tabindex="-1" aria-hidden="true">
182
+ <div class="modal-dialog">
183
+ <div class="modal-content">
184
+ <div class="modal-header bg-danger text-white">
185
+ <h5 class="modal-title">Confirm Deletion</h5>
186
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
187
+ </div>
188
+ <div class="modal-body">
189
+ <p>Are you sure you want to delete this farm? This action cannot be undone.</p>
190
+ </div>
191
+ <div class="modal-footer">
192
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
193
+ <form action="/delete_farm/{{ farm.id }}" method="POST">
194
+ <button type="submit" class="btn btn-danger">Delete Farm</button>
195
+ </form>
196
+ </div>
197
+ </div>
198
+ </div>
199
+ </div>
200
+
201
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
202
+ <script>
203
+ let map;
204
+ let farmBoundary;
205
+
206
+ function initMap() {
207
+ // Center the map on farm coordinates
208
+ const farmCenter = {lat: {{ farm.latitude }}, lng: {{ farm.longitude }}};
209
+
210
+ map = new google.maps.Map(document.getElementById('map'), {
211
+ zoom: 15,
212
+ center: farmCenter,
213
+ mapTypeId: 'satellite'
214
+ });
215
+
216
+ // Add marker for farm center
217
+ const marker = new google.maps.Marker({
218
+ position: farmCenter,
219
+ map: map,
220
+ title: "{{ farm.farm_name }}"
221
+ });
222
+
223
+ // Add farm boundary if coordinates exist
224
+ {% if farm.field_coordinates %}
225
+ try {
226
+ const coordinates = JSON.parse('{{ farm.field_coordinates|safe }}');
227
+ const farmBoundaryPath = coordinates.map(coord => ({lat: coord.lat, lng: coord.lng}));
228
+
229
+ farmBoundary = new google.maps.Polygon({
230
+ paths: farmBoundaryPath,
231
+ strokeColor: '#4CAF50',
232
+ strokeOpacity: 0.8,
233
+ strokeWeight: 3,
234
+ fillColor: '#4CAF50',
235
+ fillOpacity: 0.35
236
+ });
237
+
238
+ farmBoundary.setMap(map);
239
+
240
+ // Adjust bounds to fit the farm boundary
241
+ const bounds = new google.maps.LatLngBounds();
242
+ farmBoundaryPath.forEach(coord => bounds.extend(coord));
243
+ map.fitBounds(bounds);
244
+ } catch (e) {
245
+ console.error("Error parsing farm boundary coordinates:", e);
246
+ }
247
+ {% endif %}
248
+ }
249
+ </script>
250
+ <script async defer src="https://maps.googleapis.com/maps/api/js?key=AIzaSyBvVLjWmCja331H8SuIZ4UlJdZytuYkC6Y&callback=initMap"></script>
251
+ </body>
252
+ </html>
templates/farmer_dashboard.html ADDED
@@ -0,0 +1,1348 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}Farmer Dashboard - Farm Management Portal{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="container mt-4">
7
+ <!-- Welcome Header -->
8
+ <div class="row mb-4">
9
+ <div class="col-12">
10
+ <div class="card bg-success text-white">
11
+ <div class="card-body">
12
+ <h2><i class="fas fa-tachometer-alt me-2"></i>Welcome, {{ farmer.name }}!</h2>
13
+ <p class="mb-0">Manage your farms and get AI-powered daily recommendations</p>
14
+ </div>
15
+ </div>
16
+ </div>
17
+ </div>
18
+
19
+ <!-- External AI Tools Quick Access -->
20
+
21
+
22
+ <!-- Quick Stats -->
23
+ <div class="row mb-4">
24
+ <div class="col-md-3 mb-3">
25
+ <div class="card text-center">
26
+ <div class="card-body">
27
+ <i class="fas fa-seedling fa-2x text-success mb-2"></i>
28
+ <h5>{{ farms|length }}</h5>
29
+ <small class="text-muted">Total Farms</small>
30
+ </div>
31
+ </div>
32
+ </div>
33
+ <div class="col-md-3 mb-3">
34
+ <div class="card text-center">
35
+ <div class="card-body">
36
+ <i class="fas fa-calendar-check fa-2x text-primary mb-2"></i>
37
+ <h5>{{ recent_activities|length }}</h5>
38
+ <small class="text-muted">Recent Activities</small>
39
+ </div>
40
+ </div>
41
+ </div>
42
+ <div class="col-md-3 mb-3">
43
+ <div class="card text-center">
44
+ <div class="card-body">
45
+ <i class="fas fa-sms fa-2x text-info mb-2"></i>
46
+ <h5>{% if today_advisory %}Active{% else %}Pending{% endif %}</h5>
47
+ <small class="text-muted">Today's Advisory</small>
48
+ </div>
49
+ </div>
50
+ </div>
51
+ <div class="col-md-3 mb-3">
52
+ <div class="card text-center">
53
+ <div class="card-body">
54
+ <i class="fas fa-user fa-2x text-warning mb-2"></i>
55
+ <h5>{{ farmer.contact_number }}</h5>
56
+ <small class="text-muted">Contact</small>
57
+ </div>
58
+ </div>
59
+ </div>
60
+ </div>
61
+
62
+ <div class="row">
63
+ <!-- Today's Advisory -->
64
+ <div class="col-md-8 mb-4">
65
+ <div class="card">
66
+ <div class="card-header bg-primary text-white">
67
+ <h5><i class="fas fa-brain me-2"></i>Today's AI Advisory</h5>
68
+ </div>
69
+ <div class="card-body">
70
+ {% if today_advisory %}
71
+ <div class="alert alert-success">
72
+ <h6><i class="fas fa-check-circle me-2"></i>Tasks to Do:</h6>
73
+ <p>{{ today_advisory.task_to_do }}</p>
74
+ </div>
75
+ <div class="alert alert-warning">
76
+ <h6><i class="fas fa-exclamation-triangle me-2"></i>Tasks to Avoid:</h6>
77
+ <p>{{ today_advisory.task_to_avoid }}</p>
78
+ </div>
79
+ {% if today_advisory.reason_explanation %}
80
+ <div class="alert alert-info">
81
+ <h6><i class="fas fa-info-circle me-2"></i>Explanation:</h6>
82
+ <p>{{ today_advisory.reason_explanation }}</p>
83
+ </div>
84
+ {% endif %}
85
+
86
+ {% if farms %}
87
+ <div class="mt-3">
88
+ <button class="btn btn-success" onclick="sendSMSAdvisory({{ farms[0].id }})">
89
+ <i class="fas fa-sms me-2"></i>Send SMS Alert
90
+ </button>
91
+ <button class="btn btn-info" onclick="sendTelegramAdvisory({{ farms[0].id }})">
92
+ <i class="fab fa-telegram me-2"></i>Send Telegram Alert
93
+ </button>
94
+ <button class="btn btn-primary" onclick="generateNewAdvisory({{ farms[0].id }})">
95
+ <i class="fas fa-sync me-2"></i>Refresh Advisory
96
+ </button>
97
+ </div>
98
+ {% endif %}
99
+ {% else %}
100
+ <div class="text-center text-muted py-4">
101
+ <i class="fas fa-robot fa-3x mb-3"></i>
102
+ <h6>No advisory generated yet for today</h6>
103
+ <p>Click below to generate AI-powered recommendations</p>
104
+ {% if farms %}
105
+ <button class="btn btn-primary" onclick="generateNewAdvisory({{ farms[0].id }})">
106
+ <i class="fas fa-magic me-2"></i>Generate Advisory
107
+ </button>
108
+ {% endif %}
109
+ </div>
110
+ {% endif %}
111
+ </div>
112
+ </div>
113
+ </div>
114
+
115
+ <!-- Quick Actions -->
116
+ <div class="col-md-4 mb-4">
117
+ <div class="card">
118
+ <div class="card-header bg-secondary text-white">
119
+ <h5><i class="fas fa-bolt me-2"></i>Quick Actions</h5>
120
+ <small><i class="fas fa-robot me-1"></i>AI-powered tools for better farming</small>
121
+ </div>
122
+ <div class="card-body">
123
+ <div class="d-grid gap-2">
124
+ <a href="{{ url_for('add_farm') }}" class="btn btn-success">
125
+ <i class="fas fa-plus me-2"></i>Add New Farm
126
+ </a>
127
+ {% if farms %}
128
+ <a href="{{ url_for('farm_details', farm_id=farms[0].id) }}" class="btn btn-primary">
129
+ <i class="fas fa-eye me-2"></i>View Farm Details
130
+ </a>
131
+ <button class="btn btn-success" onclick="window.open('https://pranit144-weather-forecast-farmers.hf.space', '_blank')" title="AI-powered weather forecasting tool">
132
+ <i class="fas fa-cloud me-2"></i>Smart Weather Forecast
133
+ </button>
134
+ <button class="btn btn-warning" onclick="viewWeatherAlerts({{ farms[0].id }})">
135
+ <i class="fas fa-exclamation-triangle me-2"></i>Weather Alerts
136
+ </button>
137
+ <button class="btn btn-info" onclick="window.open('https://agri-ai-rosy.vercel.app/cropMarketTrendAnalyzer', '_blank')" title="AI market trend analyzer">
138
+ <i class="fas fa-chart-line me-2"></i>Market Analyzer
139
+ </button>
140
+ <button class="btn btn-danger" onclick="window.open('https://agri-ai-rosy.vercel.app/plant-disease-detector', '_blank')" title="AI plant disease detector">
141
+ <i class="fas fa-search me-2"></i>Disease Detector
142
+ </button>
143
+ <button class="btn btn-secondary" onclick="showSendImageModal()">
144
+ <i class="fas fa-paper-plane me-2"></i>Send Image via Telegram
145
+ </button>
146
+ {% endif %}
147
+ <a href="{{ url_for('farmer_logout') }}" class="btn btn-outline-danger">
148
+ <i class="fas fa-sign-out-alt me-2"></i>Logout
149
+ </a>
150
+ </div>
151
+ </div>
152
+ </div>
153
+ </div>
154
+ </div>
155
+
156
+ <!-- Daily Tasks Section -->
157
+ <div class="row mb-4">
158
+ <div class="col-12">
159
+ <div class="card">
160
+ <div class="card-header bg-info text-white d-flex justify-content-between align-items-center">
161
+ <h5><i class="fas fa-tasks me-2"></i>Today's Daily Tasks</h5>
162
+ <small id="task-date">{{ today_date or 'Today' }}</small>
163
+ </div>
164
+ <div class="card-body">
165
+ <div id="daily-tasks-container">
166
+ <div class="text-center text-muted py-4" id="no-tasks-message">
167
+ <i class="fas fa-clipboard-list fa-3x mb-3"></i>
168
+ <h6>No daily tasks loaded</h6>
169
+ <p>Generate daily tasks to get AI-powered farming recommendations</p>
170
+ </div>
171
+ </div>
172
+
173
+ <div class="mt-3 text-center">
174
+ <div class="btn-group" role="group">
175
+ <button class="btn btn-primary" onclick="loadDailyTasks()">
176
+ <i class="fas fa-sync me-2"></i>Load Today's Tasks
177
+ </button>
178
+ <button class="btn btn-success" onclick="generateDailyTasks()">
179
+ <i class="fas fa-magic me-2"></i>Generate New Tasks
180
+ </button>
181
+ <button class="btn btn-info" onclick="sendTasksTelegram()" title="Send tasks to your Telegram">
182
+ <i class="fab fa-telegram me-2"></i>Send to Telegram
183
+ </button>
184
+ <button class="btn btn-outline-danger" onclick="deleteAllTasks()" title="Delete all tasks for today">
185
+ <i class="fas fa-trash me-2"></i>Clear All
186
+ </button>
187
+ </div>
188
+ </div>
189
+ </div>
190
+ </div>
191
+ </div>
192
+ </div>
193
+
194
+ <!-- Farms List -->
195
+ {% if farms %}
196
+ <div class="row mb-4">
197
+ <div class="col-12">
198
+ <div class="card">
199
+ <div class="card-header">
200
+ <h5><i class="fas fa-list me-2"></i>Your Farms</h5>
201
+ </div>
202
+ <div class="card-body">
203
+ <div class="table-responsive">
204
+ <table class="table table-striped">
205
+ <thead>
206
+ <tr>
207
+ <th>Farm Name</th>
208
+ <th>Size (Acres)</th>
209
+ <th>Crops</th>
210
+ <th>Irrigation</th>
211
+ <th>Actions</th>
212
+ </tr>
213
+ </thead>
214
+ <tbody>
215
+ {% for farm in farms %}
216
+ <tr>
217
+ <td>{{ farm.farm_name }}</td>
218
+ <td>{{ farm.farm_size }}</td>
219
+ <td>
220
+ {% for crop in farm.get_crop_types() %}
221
+ <span class="badge bg-success me-1">{{ crop }}</span>
222
+ {% endfor %}
223
+ </td>
224
+ <td>{{ farm.irrigation_type }}</td>
225
+ <td>
226
+ <div class="btn-group" role="group">
227
+ <!-- Primary Actions -->
228
+ <a href="{{ url_for('farm_details', farm_id=farm.id) }}" class="btn btn-sm btn-outline-primary" title="View Details">
229
+ <i class="fas fa-eye"></i>
230
+ </a>
231
+ <button class="btn btn-sm btn-outline-success" onclick="generateNewAdvisory({{ farm.id }})" title="Generate Daily Advisory">
232
+ <i class="fas fa-brain"></i>
233
+ </button>
234
+ </div>
235
+
236
+ <!-- Yearly Plan Actions -->
237
+ <div class="btn-group ms-1" role="group">
238
+ <button class="btn btn-sm btn-outline-info" onclick="generateYearlyPlan({{ farm.id }})" title="Generate Yearly Plan">
239
+ <i class="fas fa-calendar-plus"></i>
240
+ </button>
241
+ <button class="btn btn-sm btn-outline-secondary" onclick="viewYearlyPlan({{ farm.id }})" title="View Yearly Plan">
242
+ <i class="fas fa-calendar-alt"></i>
243
+ </button>
244
+ <button class="btn btn-sm btn-outline-warning" onclick="editYearlyPlan({{ farm.id }})" title="Edit Yearly Plan">
245
+ <i class="fas fa-edit"></i>
246
+ </button>
247
+ <button class="btn btn-sm btn-outline-dark" onclick="downloadYearlyPlanPDF({{ farm.id }})" title="Download Yearly Plan PDF">
248
+ <i class="fas fa-file-pdf"></i>
249
+ </button>
250
+ <button class="btn btn-sm btn-outline-primary" onclick="sendYearlyPlanTelegram({{ farm.id }})" title="Send Yearly Plan via Telegram">
251
+ <i class="fas fa-paper-plane"></i>
252
+ </button>
253
+ </div>
254
+
255
+ <!-- Smart Features -->
256
+ <div class="btn-group ms-1" role="group">
257
+ <button class="btn btn-sm btn-outline-info" onclick="window.open('https://pranit144-weather-forecast-farmers.hf.space', '_blank')" title="Weather Forecast">
258
+ <i class="fas fa-cloud"></i>
259
+ </button>
260
+ <button class="btn btn-sm btn-outline-success" onclick="window.open('https://agri-ai-rosy.vercel.app/cropMarketTrendAnalyzer', '_blank')" title="Market Prices">
261
+ <i class="fas fa-chart-line"></i>
262
+ </button>
263
+ <button class="btn btn-sm btn-outline-danger" onclick="window.open('https://agri-ai-rosy.vercel.app/plant-disease-detector', '_blank')" title="Disease Detection">
264
+ <i class="fas fa-bug"></i>
265
+ </button>
266
+ </div>
267
+
268
+ <!-- Delete Actions -->
269
+ <div class="btn-group ms-1" role="group">
270
+ <button class="btn btn-sm btn-outline-danger" onclick="deleteYearlyPlan({{ farm.id }})" title="Delete Yearly Plan">
271
+ <i class="fas fa-calendar-times"></i>
272
+ </button>
273
+ <button class="btn btn-sm btn-danger" onclick="deleteFarm({{ farm.id }})" title="Delete Farm"
274
+ style="opacity: 0.7;" onmouseover="this.style.opacity='1'" onmouseout="this.style.opacity='0.7'">
275
+ <i class="fas fa-trash-alt"></i>
276
+ </button>
277
+ </div>
278
+ </td>
279
+ </tr>
280
+ {% endfor %}
281
+ </tbody>
282
+ </table>
283
+ </div>
284
+ </div>
285
+ </div>
286
+ </div>
287
+ </div>
288
+ {% endif %}
289
+
290
+ <!-- Recent Activities -->
291
+ {% if recent_activities %}
292
+ <div class="row">
293
+ <div class="col-12">
294
+ <div class="card">
295
+ <div class="card-header">
296
+ <h5><i class="fas fa-history me-2"></i>Recent Activities</h5>
297
+ </div>
298
+ <div class="card-body">
299
+ <div class="timeline">
300
+ {% for activity in recent_activities %}
301
+ <div class="timeline-item mb-3">
302
+ <div class="d-flex">
303
+ <div class="flex-shrink-0">
304
+ <i class="fas fa-circle text-success"></i>
305
+ </div>
306
+ <div class="flex-grow-1 ms-3">
307
+ <h6 class="mb-1">{{ activity.activity_type|title }}</h6>
308
+ <p class="mb-1">{{ activity.activity_description }}</p>
309
+ <small class="text-muted">
310
+ Scheduled: {{ activity.scheduled_date.strftime('%d %b %Y') }}
311
+ | Status: <span class="badge bg-{{ 'success' if activity.status == 'completed' else 'warning' }}">{{ activity.status|title }}</span>
312
+ </small>
313
+ </div>
314
+ </div>
315
+ </div>
316
+ {% endfor %}
317
+ </div>
318
+ </div>
319
+ </div>
320
+ </div>
321
+ </div>
322
+ {% endif %}
323
+ </div>
324
+
325
+ <!-- Loading Modal -->
326
+ <div class="modal fade" id="loadingModal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1">
327
+ <div class="modal-dialog modal-dialog-centered">
328
+ <div class="modal-content">
329
+ <div class="modal-body text-center">
330
+ <div class="spinner-border text-primary" role="status">
331
+ <span class="visually-hidden">Loading...</span>
332
+ </div>
333
+ <p class="mt-3 mb-0">Processing your request...</p>
334
+ </div>
335
+ </div>
336
+ </div>
337
+ </div>
338
+
339
+ <!-- Yearly Plan Modal -->
340
+ <div class="modal fade" id="yearlyPlanModal" tabindex="-1">
341
+ <div class="modal-dialog modal-lg modal-dialog-scrollable">
342
+ <div class="modal-content">
343
+ <div class="modal-header">
344
+ <h5 class="modal-title">Yearly Plan</h5>
345
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
346
+ </div>
347
+ <div class="modal-body">
348
+ <div id="yearlyPlanContent">Loading...</div>
349
+ </div>
350
+ <div class="modal-footer">
351
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
352
+ <button type="button" class="btn btn-primary" id="saveYearlyPlanBtn" style="display:none;">Save</button>
353
+ </div>
354
+ </div>
355
+ </div>
356
+ </div>
357
+ {% endblock %}
358
+
359
+ {% block extra_js %}
360
+ <script>
361
+ // Helper to show/hide the Bootstrap 5 modal using vanilla JS
362
+ function _getLoadingModalInstance(){
363
+ const modalEl = document.getElementById('loadingModal');
364
+ return bootstrap.Modal.getOrCreateInstance(modalEl);
365
+ }
366
+
367
+ function _showYearlyPlanModal(html, isEdit = false) {
368
+ const el = document.getElementById('yearlyPlanContent');
369
+ el.innerHTML = html;
370
+ const saveBtn = document.getElementById('saveYearlyPlanBtn');
371
+ if (isEdit) {
372
+ saveBtn.style.display = 'inline-block';
373
+ } else {
374
+ saveBtn.style.display = 'none';
375
+ }
376
+ const m = new bootstrap.Modal(document.getElementById('yearlyPlanModal'));
377
+ m.show();
378
+ }
379
+
380
+ function generateYearlyPlan(farmId) {
381
+ const modal = _getLoadingModalInstance();
382
+ modal.show();
383
+
384
+ fetch(`/farmer/farm/${farmId}/yearly_plan/generate`, {method: 'POST'})
385
+ .then(response => response.json())
386
+ .then(data => {
387
+ modal.hide();
388
+ if (data.success) {
389
+ alert('Yearly plan generated successfully!');
390
+ location.reload();
391
+ } else {
392
+ alert('Failed to generate plan: ' + (data.error || 'Unknown error'));
393
+ }
394
+ })
395
+ .catch(error => {
396
+ modal.hide();
397
+ alert('Error: ' + error.message);
398
+ });
399
+ }
400
+
401
+ function viewYearlyPlan(farmId) {
402
+ const modal = _getLoadingModalInstance();
403
+ modal.show();
404
+
405
+ fetch(`/farmer/farm/${farmId}/yearly_plan`)
406
+ .then(response => response.json())
407
+ .then(data => {
408
+ modal.hide();
409
+ if (data.success && data.plan) {
410
+ if (data.is_html && data.html_content) {
411
+ // Open comprehensive HTML plan in a new window for better viewing
412
+ const newWindow = window.open('', '_blank', 'width=1200,height=800,scrollbars=yes,resizable=yes');
413
+ newWindow.document.write(data.html_content);
414
+ newWindow.document.close();
415
+ newWindow.document.title = `Yearly Plan - ${data.plan.farm_name}`;
416
+ } else {
417
+ // Fallback to modal for simple plans
418
+ const html = `
419
+ <div class="card">
420
+ <div class="card-header bg-success text-white">
421
+ <h6><i class="fas fa-seedling me-2"></i>${data.plan.farm_name || 'Farm'}</h6>
422
+ </div>
423
+ <div class="card-body">
424
+ <div class="mb-2">
425
+ <span class="badge bg-info">Year: ${data.plan.year || new Date().getFullYear()}</span>
426
+ ${data.plan.ai_generated ? '<span class="badge bg-success ms-2">AI Generated</span>' : '<span class="badge bg-secondary ms-2">Basic Plan</span>'}
427
+ </div>
428
+ <h6>Summary:</h6>
429
+ <p class="text-muted">${data.plan.summary_text || 'No summary available'}</p>
430
+ <h6>Plan Details:</h6>
431
+ <pre class="bg-light p-3 rounded" style="max-height: 400px; overflow-y: auto;">${JSON.stringify(data.plan.plan_json || data.plan.plan, null, 2)}</pre>
432
+ <small class="text-muted">Generated: ${new Date(data.plan.generated_at || data.plan.created_at).toLocaleDateString()}</small>
433
+ </div>
434
+ <div class="card-footer text-center">
435
+ <button class="btn btn-primary btn-sm" onclick="generateYearlyPlan(${farmId})">
436
+ <i class="fas fa-sync-alt me-1"></i>Regenerate with AI
437
+ </button>
438
+ </div>
439
+ </div>
440
+ `;
441
+ _showYearlyPlanModal(html);
442
+ }
443
+ } else {
444
+ alert('No yearly plan found for this farm. Generate one first.');
445
+ }
446
+ })
447
+ .catch(error => {
448
+ modal.hide();
449
+ alert('Error loading plan: ' + error.message);
450
+ });
451
+ }
452
+
453
+ function editYearlyPlan(farmId) {
454
+ // First load the existing plan
455
+ fetch(`/farmer/farm/${farmId}/yearly_plan`)
456
+ .then(response => response.json())
457
+ .then(data => {
458
+ if (!data.success || !data.plan) {
459
+ alert('No plan to edit. Generate one first.');
460
+ return;
461
+ }
462
+
463
+ const planData = data.plan;
464
+ const html = `
465
+ <div class="mb-3">
466
+ <label class="form-label"><strong>Edit Plan for ${planData.farm_name}</strong></label>
467
+ </div>
468
+ <div class="mb-3">
469
+ <label for="planSummary" class="form-label">Summary</label>
470
+ <textarea id="planSummary" class="form-control" rows="3">${planData.summary_text || ''}</textarea>
471
+ </div>
472
+ <div class="mb-3">
473
+ <label for="planJson" class="form-label">Plan Details (JSON)</label>
474
+ <textarea id="planJson" class="form-control" rows="10">${JSON.stringify(planData.plan_json, null, 2)}</textarea>
475
+ </div>
476
+ `;
477
+
478
+ _showYearlyPlanModal(html, true);
479
+
480
+ // Set up save button handler
481
+ document.getElementById('saveYearlyPlanBtn').onclick = function() {
482
+ const summary = document.getElementById('planSummary').value;
483
+ const jsonText = document.getElementById('planJson').value;
484
+
485
+ let planJson;
486
+ try {
487
+ planJson = JSON.parse(jsonText);
488
+ } catch (err) {
489
+ alert('Invalid JSON format: ' + err.message);
490
+ return;
491
+ }
492
+
493
+ const modal = _getLoadingModalInstance();
494
+ modal.show();
495
+
496
+ fetch(`/farmer/farm/${farmId}/yearly_plan`, {
497
+ method: 'POST',
498
+ headers: {'Content-Type': 'application/json'},
499
+ body: JSON.stringify({
500
+ summary_text: summary,
501
+ plan_json: planJson
502
+ })
503
+ })
504
+ .then(response => response.json())
505
+ .then(result => {
506
+ modal.hide();
507
+ if (result.success) {
508
+ alert('Plan updated successfully!');
509
+ bootstrap.Modal.getInstance(document.getElementById('yearlyPlanModal')).hide();
510
+ location.reload();
511
+ } else {
512
+ alert('Failed to save: ' + (result.error || 'Unknown error'));
513
+ }
514
+ })
515
+ .catch(error => {
516
+ modal.hide();
517
+ alert('Error saving plan: ' + error.message);
518
+ });
519
+ };
520
+ })
521
+ .catch(error => {
522
+ alert('Error loading plan: ' + error.message);
523
+ });
524
+ }
525
+
526
+ function deleteYearlyPlan(farmId) {
527
+ if (!confirm('Are you sure you want to delete the yearly plan for this farm?')) {
528
+ return;
529
+ }
530
+
531
+ const modal = _getLoadingModalInstance();
532
+ modal.show();
533
+
534
+ fetch(`/farmer/farm/${farmId}/yearly_plan/delete`, {method: 'POST'})
535
+ .then(response => response.json())
536
+ .then(data => {
537
+ modal.hide();
538
+ if (data.success) {
539
+ alert('Yearly plan deleted successfully!');
540
+ location.reload();
541
+ } else {
542
+ alert('Failed to delete plan: ' + (data.error || 'Unknown error'));
543
+ }
544
+ })
545
+ .catch(error => {
546
+ modal.hide();
547
+ alert('Error deleting plan: ' + error.message);
548
+ });
549
+ }
550
+
551
+ function deleteFarm(farmId) {
552
+ if (!confirm('Are you sure you want to delete this farm? This will remove all associated data including activities, advisories, and yearly plans. This action cannot be undone.')) {
553
+ return;
554
+ }
555
+
556
+ const modal = _getLoadingModalInstance();
557
+ modal.show();
558
+
559
+ fetch(`/farmer/farm/${farmId}/delete`, {method: 'POST'})
560
+ .then(response => response.json())
561
+ .then(data => {
562
+ modal.hide();
563
+ if (data.success) {
564
+ alert('Farm deleted successfully!');
565
+ location.reload();
566
+ } else {
567
+ alert('Failed to delete farm: ' + (data.error || 'Unknown error'));
568
+ }
569
+ })
570
+ .catch(error => {
571
+ modal.hide();
572
+ alert('Error deleting farm: ' + error.message);
573
+ });
574
+ }
575
+
576
+ function generateNewAdvisory(farmId) {
577
+ const modal = _getLoadingModalInstance();
578
+ modal.show();
579
+
580
+ fetch(`/generate_advisory/${farmId}`)
581
+ .then(response => {
582
+ if (!response.ok) {
583
+ return response.text().then(text => { throw new Error(text || 'Server error'); });
584
+ }
585
+ return response.json();
586
+ })
587
+ .then(data => {
588
+ modal.hide();
589
+ if (data && data.success) {
590
+ alert('Daily advisory generated successfully!');
591
+ location.reload();
592
+ } else {
593
+ alert('Failed to generate advisory: ' + (data && data.error ? data.error : 'Unknown error'));
594
+ }
595
+ })
596
+ .catch(error => {
597
+ modal.hide();
598
+ alert('Error generating advisory: ' + (error && error.message ? error.message : String(error)));
599
+ });
600
+ }
601
+
602
+ function sendSMSAdvisory(farmId) {
603
+ const modal = _getLoadingModalInstance();
604
+ modal.show();
605
+
606
+ fetch(`/send_sms_advisory/${farmId}`)
607
+ .then(response => {
608
+ if (!response.ok) {
609
+ return response.text().then(text => { throw new Error(text || 'Server error'); });
610
+ }
611
+ return response.json();
612
+ })
613
+ .then(data => {
614
+ modal.hide();
615
+ if (data && data.success) {
616
+ alert('SMS sent successfully!');
617
+ } else {
618
+ alert('Failed to send SMS: ' + (data && data.error ? data.error : 'Unknown error'));
619
+ }
620
+ })
621
+ .catch(error => {
622
+ modal.hide();
623
+ alert('Error sending SMS: ' + (error && error.message ? error.message : String(error)));
624
+ });
625
+ }
626
+
627
+ function sendTelegramAdvisory(farmId) {
628
+ const modal = _getLoadingModalInstance();
629
+ modal.show();
630
+
631
+ fetch(`/send_telegram_advisory/${farmId}`)
632
+ .then(response => {
633
+ if (!response.ok) {
634
+ return response.text().then(text => { throw new Error(text || 'Server error'); });
635
+ }
636
+ return response.json();
637
+ })
638
+ .then(data => {
639
+ modal.hide();
640
+ if (data && data.success) {
641
+ alert('Telegram message sent successfully!');
642
+ } else {
643
+ alert('Failed to send Telegram message: ' + (data && data.error ? data.error : 'Unknown error'));
644
+ }
645
+ })
646
+ .catch(error => {
647
+ modal.hide();
648
+ alert('Error sending Telegram message: ' + (error && error.message ? error.message : String(error)));
649
+ });
650
+ }
651
+
652
+ function checkWeather(farmId) {
653
+ const modal = _getLoadingModalInstance();
654
+ modal.show();
655
+
656
+ fetch(`/api/weather/${farmId}`)
657
+ .then(response => {
658
+ if (!response.ok) {
659
+ return response.text().then(text => { throw new Error(text || 'Server error'); });
660
+ }
661
+ return response.json();
662
+ })
663
+ .then(data => {
664
+ modal.hide();
665
+ if (data.error) {
666
+ alert('Weather data unavailable: ' + data.error);
667
+ } else {
668
+ let weatherInfo = `Current Weather:\n`;
669
+ weatherInfo += `Temperature: ${data.main?.temp || 'N/A'}°C\n`;
670
+ weatherInfo += `Humidity: ${data.main?.humidity || 'N/A'}%\n`;
671
+ weatherInfo += `Condition: ${data.weather?.[0]?.description || 'N/A'}\n`;
672
+ weatherInfo += `Wind: ${data.wind?.speed ? (data.wind.speed * 3.6).toFixed(1) : 'N/A'} km/h\n`;
673
+ alert(weatherInfo);
674
+ }
675
+ })
676
+ .catch(error => {
677
+ modal.hide();
678
+ alert('Error fetching weather: ' + (error && error.message ? error.message : String(error)));
679
+ });
680
+ }
681
+
682
+ // New feature functions
683
+ function downloadYearlyPlanPDF(farmId) {
684
+ const modal = _getLoadingModalInstance();
685
+ modal.show();
686
+
687
+ fetch(`/farmer/farm/${farmId}/yearly_plan/pdf`)
688
+ .then(response => {
689
+ modal.hide();
690
+ if (response.ok) {
691
+ // Trigger download
692
+ const link = document.createElement('a');
693
+ link.href = `/farmer/farm/${farmId}/yearly_plan/pdf`;
694
+ link.download = `yearly_plan_farm_${farmId}.pdf`;
695
+ document.body.appendChild(link);
696
+ link.click();
697
+ document.body.removeChild(link);
698
+ } else {
699
+ alert('Failed to generate PDF. Please ensure you have a yearly plan first.');
700
+ }
701
+ })
702
+ .catch(error => {
703
+ modal.hide();
704
+ alert('Error generating PDF: ' + error.message);
705
+ });
706
+ }
707
+
708
+ function sendYearlyPlanTelegram(farmId) {
709
+ const modal = _getLoadingModalInstance();
710
+ modal.show();
711
+
712
+ fetch(`/farmer/farm/${farmId}/yearly_plan/send_telegram`, {
713
+ method: 'POST',
714
+ headers: {'Content-Type': 'application/json'}
715
+ })
716
+ .then(response => response.json())
717
+ .then(data => {
718
+ modal.hide();
719
+ if (data.success) {
720
+ alert('✅ ' + data.message);
721
+ } else {
722
+ alert('❌ ' + (data.error || 'Failed to send via Telegram'));
723
+ }
724
+ })
725
+ .catch(error => {
726
+ modal.hide();
727
+ alert('Error sending via Telegram: ' + error.message);
728
+ });
729
+ }
730
+
731
+ function viewWeatherAlerts(farmId) {
732
+ const modal = _getLoadingModalInstance();
733
+ modal.show();
734
+
735
+ fetch(`/farmer/weather_alerts/${farmId}`)
736
+ .then(response => response.json())
737
+ .then(data => {
738
+ modal.hide();
739
+ if (data.success) {
740
+ let alertsHtml = '<h5><i class="fas fa-cloud-rain me-2"></i>Weather Alerts</h5>';
741
+ if (data.alerts && data.alerts.length > 0) {
742
+ alertsHtml += '<div class="list-group">';
743
+ data.alerts.forEach(alert => {
744
+ const severityClass = alert.severity === 'high' ? 'danger' :
745
+ alert.severity === 'medium' ? 'warning' : 'info';
746
+ alertsHtml += `
747
+ <div class="list-group-item list-group-item-${severityClass}">
748
+ <div class="d-flex w-100 justify-content-between">
749
+ <h6 class="mb-1">${alert.alert_type}</h6>
750
+ <small>${new Date(alert.created_at).toLocaleDateString()}</small>
751
+ </div>
752
+ <p class="mb-1">${alert.message}</p>
753
+ <small>Severity: <span class="badge bg-${severityClass}">${alert.severity}</span></small>
754
+ </div>
755
+ `;
756
+ });
757
+ alertsHtml += '</div>';
758
+ alertsHtml += `
759
+ <div class="mt-3">
760
+ <button class="btn btn-primary" onclick="enableWeatherAlerts(${farmId})">
761
+ <i class="fas fa-bell me-2"></i>Enable Alerts
762
+ </button>
763
+ <button class="btn btn-danger" onclick="disableWeatherAlerts(${farmId})">
764
+ <i class="fas fa-bell-slash me-2"></i>Disable Alerts
765
+ </button>
766
+ </div>
767
+ `;
768
+ } else {
769
+ alertsHtml += '<p class="text-muted">No weather alerts at this time.</p>';
770
+ alertsHtml += `
771
+ <button class="btn btn-primary" onclick="enableWeatherAlerts(${farmId})">
772
+ <i class="fas fa-bell me-2"></i>Enable Weather Alerts
773
+ </button>
774
+ `;
775
+ }
776
+ _showYearlyPlanModal(alertsHtml);
777
+ } else {
778
+ alert('Failed to load weather alerts: ' + (data.error || 'Unknown error'));
779
+ }
780
+ })
781
+ .catch(error => {
782
+ modal.hide();
783
+ alert('Error loading weather alerts: ' + error.message);
784
+ });
785
+ }
786
+
787
+ function enableWeatherAlerts(farmId) {
788
+ fetch(`/farmer/weather_alerts/${farmId}`, {
789
+ method: 'POST',
790
+ headers: {'Content-Type': 'application/json'},
791
+ body: JSON.stringify({action: 'enable'})
792
+ })
793
+ .then(response => response.json())
794
+ .then(data => {
795
+ if (data.success) {
796
+ alert('Weather alerts enabled successfully!');
797
+ viewWeatherAlerts(farmId); // Refresh the view
798
+ } else {
799
+ alert('Failed to enable alerts: ' + (data.error || 'Unknown error'));
800
+ }
801
+ })
802
+ .catch(error => alert('Error: ' + error.message));
803
+ }
804
+
805
+ function disableWeatherAlerts(farmId) {
806
+ fetch(`/farmer/weather_alerts/${farmId}`, {
807
+ method: 'POST',
808
+ headers: {'Content-Type': 'application/json'},
809
+ body: JSON.stringify({action: 'disable'})
810
+ })
811
+ .then(response => response.json())
812
+ .then(data => {
813
+ if (data.success) {
814
+ alert('Weather alerts disabled successfully!');
815
+ viewWeatherAlerts(farmId); // Refresh the view
816
+ } else {
817
+ alert('Failed to disable alerts: ' + (data.error || 'Unknown error'));
818
+ }
819
+ })
820
+ .catch(error => alert('Error: ' + error.message));
821
+ }
822
+
823
+ function viewMarketPrices(farmId) {
824
+ const modal = _getLoadingModalInstance();
825
+ modal.show();
826
+
827
+ fetch(`/farmer/market_prices`)
828
+ .then(response => response.json())
829
+ .then(data => {
830
+ modal.hide();
831
+ if (data.success) {
832
+ let pricesHtml = '<h5><i class="fas fa-chart-line me-2"></i>Market Prices</h5>';
833
+ if (data.prices && data.prices.length > 0) {
834
+ pricesHtml += '<div class="table-responsive">';
835
+ pricesHtml += '<table class="table table-striped">';
836
+ pricesHtml += '<thead><tr><th>Crop</th><th>Market</th><th>Price</th><th>Trend</th><th>Date</th></tr></thead><tbody>';
837
+ data.prices.forEach(price => {
838
+ const trendIcon = price.trend === 'up' ? '📈' : price.trend === 'down' ? '📉' : '➡️';
839
+ pricesHtml += `
840
+ <tr>
841
+ <td><span class="badge bg-success">${price.crop_type}</span></td>
842
+ <td>${price.market_name}</td>
843
+ <td>₹${price.price_per_unit}/${price.unit}</td>
844
+ <td>${trendIcon} ${price.trend}</td>
845
+ <td>${new Date(price.date).toLocaleDateString()}</td>
846
+ </tr>
847
+ `;
848
+ });
849
+ pricesHtml += '</tbody></table></div>';
850
+ pricesHtml += `
851
+ <div class="mt-3">
852
+ <button class="btn btn-primary" onclick="refreshMarketPrices(${farmId})">
853
+ <i class="fas fa-sync me-2"></i>Refresh Prices
854
+ </button>
855
+ <button class="btn btn-success" onclick="subscribeToMarketAlerts(${farmId})">
856
+ <i class="fas fa-bell me-2"></i>Subscribe to Alerts
857
+ </button>
858
+ </div>
859
+ `;
860
+ } else {
861
+ pricesHtml += '<p class="text-muted">No market price data available.</p>';
862
+ pricesHtml += `
863
+ <button class="btn btn-primary" onclick="refreshMarketPrices(${farmId})">
864
+ <i class="fas fa-sync me-2"></i>Fetch Market Prices
865
+ </button>
866
+ `;
867
+ }
868
+ _showYearlyPlanModal(pricesHtml);
869
+ } else {
870
+ alert('Failed to load market prices: ' + (data.error || 'Unknown error'));
871
+ }
872
+ })
873
+ .catch(error => {
874
+ modal.hide();
875
+ alert('Error loading market prices: ' + error.message);
876
+ });
877
+ }
878
+
879
+ function refreshMarketPrices(farmId) {
880
+ const modal = _getLoadingModalInstance();
881
+ modal.show();
882
+
883
+ fetch(`/farmer/market_prices`, {
884
+ method: 'POST',
885
+ headers: {'Content-Type': 'application/json'},
886
+ body: JSON.stringify({action: 'refresh'})
887
+ })
888
+ .then(response => response.json())
889
+ .then(data => {
890
+ modal.hide();
891
+ if (data.success) {
892
+ alert('Market prices updated successfully!');
893
+ viewMarketPrices(farmId); // Refresh the view
894
+ } else {
895
+ alert('Failed to refresh prices: ' + (data.error || 'Unknown error'));
896
+ }
897
+ })
898
+ .catch(error => {
899
+ modal.hide();
900
+ alert('Error refreshing prices: ' + error.message);
901
+ });
902
+ }
903
+
904
+ function subscribeToMarketAlerts(farmId) {
905
+ fetch(`/farmer/market_prices`, {
906
+ method: 'POST',
907
+ headers: {'Content-Type': 'application/json'},
908
+ body: JSON.stringify({action: 'subscribe'})
909
+ })
910
+ .then(response => response.json())
911
+ .then(data => {
912
+ if (data.success) {
913
+ alert('Subscribed to market price alerts successfully!');
914
+ } else {
915
+ alert('Failed to subscribe: ' + (data.error || 'Unknown error'));
916
+ }
917
+ })
918
+ .catch(error => alert('Error: ' + error.message));
919
+ }
920
+
921
+ function detectDisease(farmId) {
922
+ const modal = _getLoadingModalInstance();
923
+ modal.show();
924
+
925
+ fetch(`/farmer/disease_detection/${farmId}`)
926
+ .then(response => response.json())
927
+ .then(data => {
928
+ modal.hide();
929
+ if (data.success) {
930
+ let diseaseHtml = '<h5><i class="fas fa-bug me-2"></i>Disease Detection</h5>';
931
+ if (data.detections && data.detections.length > 0) {
932
+ diseaseHtml += '<div class="row">';
933
+ data.detections.forEach(detection => {
934
+ const severityClass = detection.severity === 'high' ? 'danger' :
935
+ detection.severity === 'medium' ? 'warning' : 'success';
936
+ diseaseHtml += `
937
+ <div class="col-md-6 mb-3">
938
+ <div class="card border-${severityClass}">
939
+ <div class="card-header bg-${severityClass} text-white">
940
+ <h6 class="mb-0">${detection.disease_name}</h6>
941
+ </div>
942
+ <div class="card-body">
943
+ <p><strong>Confidence:</strong> ${Math.round(detection.confidence * 100)}%</p>
944
+ <p><strong>Severity:</strong> <span class="badge bg-${severityClass}">${detection.severity}</span></p>
945
+ <p><strong>Treatment:</strong> ${detection.treatment_recommendation}</p>
946
+ <small class="text-muted">Detected: ${new Date(detection.detection_date).toLocaleDateString()}</small>
947
+ </div>
948
+ </div>
949
+ </div>
950
+ `;
951
+ });
952
+ diseaseHtml += '</div>';
953
+ } else {
954
+ diseaseHtml += '<p class="text-muted">No disease detections recorded.</p>';
955
+ }
956
+ diseaseHtml += `
957
+ <div class="mt-3">
958
+ <label for="diseaseImageUpload" class="btn btn-primary">
959
+ <i class="fas fa-camera me-2"></i>Upload Crop Image
960
+ </label>
961
+ <input type="file" id="diseaseImageUpload" accept="image/*" style="display: none;" onchange="uploadDiseaseImage(${farmId}, this)">
962
+ <button class="btn btn-success ms-2" onclick="viewDiseaseHistory(${farmId})">
963
+ <i class="fas fa-history me-2"></i>View History
964
+ </button>
965
+ </div>
966
+ `;
967
+ _showYearlyPlanModal(diseaseHtml);
968
+ } else {
969
+ alert('Failed to load disease detection data: ' + (data.error || 'Unknown error'));
970
+ }
971
+ })
972
+ .catch(error => {
973
+ modal.hide();
974
+ alert('Error loading disease detection: ' + error.message);
975
+ });
976
+ }
977
+
978
+ function uploadDiseaseImage(farmId, input) {
979
+ if (input.files && input.files[0]) {
980
+ const formData = new FormData();
981
+ formData.append('image', input.files[0]);
982
+
983
+ const modal = _getLoadingModalInstance();
984
+ modal.show();
985
+
986
+ fetch(`/farmer/disease_detection/${farmId}`, {
987
+ method: 'POST',
988
+ body: formData
989
+ })
990
+ .then(response => response.json())
991
+ .then(data => {
992
+ modal.hide();
993
+ if (data.success) {
994
+ alert('Image uploaded and analyzed successfully!');
995
+ detectDisease(farmId); // Refresh the view
996
+ } else {
997
+ alert('Failed to analyze image: ' + (data.error || 'Unknown error'));
998
+ }
999
+ })
1000
+ .catch(error => {
1001
+ modal.hide();
1002
+ alert('Error uploading image: ' + error.message);
1003
+ });
1004
+ }
1005
+ }
1006
+
1007
+ function viewDiseaseHistory(farmId) {
1008
+ window.open(`/farmer/disease_detection/${farmId}`, '_blank');
1009
+ }
1010
+
1011
+ function showSendImageModal() {
1012
+ const modalHtml = `
1013
+ <div class="modal fade" id="sendImageModal" tabindex="-1">
1014
+ <div class="modal-dialog">
1015
+ <div class="modal-content">
1016
+ <div class="modal-header">
1017
+ <h5 class="modal-title"><i class="fas fa-paper-plane me-2"></i>Send Image via Telegram</h5>
1018
+ <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
1019
+ </div>
1020
+ <div class="modal-body">
1021
+ <form id="sendImageForm" enctype="multipart/form-data">
1022
+ <div class="mb-3">
1023
+ <label for="imageFile" class="form-label">Select Image:</label>
1024
+ <input type="file" class="form-control" id="imageFile" name="image" accept="image/*" required>
1025
+ <div class="form-text">Supported formats: JPG, PNG, GIF, BMP</div>
1026
+ </div>
1027
+ <div class="mb-3">
1028
+ <label for="imageCaption" class="form-label">Caption (optional):</label>
1029
+ <textarea class="form-control" id="imageCaption" name="caption" rows="3" placeholder="Add a description for your image..."></textarea>
1030
+ </div>
1031
+ </form>
1032
+ </div>
1033
+ <div class="modal-footer">
1034
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
1035
+ <button type="button" class="btn btn-primary" onclick="sendImageTelegram()">
1036
+ <i class="fas fa-paper-plane me-2"></i>Send via Telegram
1037
+ </button>
1038
+ </div>
1039
+ </div>
1040
+ </div>
1041
+ </div>
1042
+ `;
1043
+
1044
+ // Remove existing modal if any
1045
+ const existingModal = document.getElementById('sendImageModal');
1046
+ if (existingModal) {
1047
+ existingModal.remove();
1048
+ }
1049
+
1050
+ // Add modal to body
1051
+ document.body.insertAdjacentHTML('beforeend', modalHtml);
1052
+
1053
+ // Show modal
1054
+ const modal = new bootstrap.Modal(document.getElementById('sendImageModal'));
1055
+ modal.show();
1056
+ }
1057
+
1058
+ function sendImageTelegram() {
1059
+ const form = document.getElementById('sendImageForm');
1060
+ const fileInput = document.getElementById('imageFile');
1061
+ const captionInput = document.getElementById('imageCaption');
1062
+
1063
+ if (!fileInput.files[0]) {
1064
+ alert('Please select an image file');
1065
+ return;
1066
+ }
1067
+
1068
+ const formData = new FormData();
1069
+ formData.append('image', fileInput.files[0]);
1070
+ formData.append('caption', captionInput.value || `📷 Image from {{ current_user.name if current_user else 'Farmer' }}`);
1071
+
1072
+ const modal = bootstrap.Modal.getInstance(document.getElementById('sendImageModal'));
1073
+ modal.hide();
1074
+
1075
+ const loadingModal = _getLoadingModalInstance();
1076
+ loadingModal.show();
1077
+
1078
+ fetch('/farmer/send_image_telegram', {
1079
+ method: 'POST',
1080
+ body: formData
1081
+ })
1082
+ .then(response => response.json())
1083
+ .then(data => {
1084
+ loadingModal.hide();
1085
+ if (data.success) {
1086
+ alert('✅ ' + data.message);
1087
+ } else {
1088
+ alert('❌ ' + (data.error || 'Failed to send image via Telegram'));
1089
+ }
1090
+ })
1091
+ .catch(error => {
1092
+ loadingModal.hide();
1093
+ alert('Error sending image: ' + error.message);
1094
+ });
1095
+ }
1096
+
1097
+ // ==================== DAILY TASKS FUNCTIONS ====================
1098
+
1099
+ function loadDailyTasks(date = null) {
1100
+ const targetDate = date || new Date().toISOString().split('T')[0];
1101
+ const loadingSpinner = '<div class="text-center py-4"><div class="spinner-border text-primary" role="status"><span class="visually-hidden">Loading...</span></div><p class="mt-2">Loading daily tasks...</p></div>';
1102
+
1103
+ document.getElementById('daily-tasks-container').innerHTML = loadingSpinner;
1104
+ document.getElementById('task-date').textContent = targetDate;
1105
+
1106
+ fetch(`/farmer/daily_tasks?date=${targetDate}`)
1107
+ .then(response => response.json())
1108
+ .then(data => {
1109
+ if (data.success) {
1110
+ renderDailyTasks(data.tasks);
1111
+ } else {
1112
+ showNoTasksMessage('Failed to load tasks: ' + (data.error || 'Unknown error'));
1113
+ }
1114
+ })
1115
+ .catch(error => {
1116
+ console.error('Error loading daily tasks:', error);
1117
+ showNoTasksMessage('Error loading tasks. Please try again.');
1118
+ });
1119
+ }
1120
+
1121
+ function renderDailyTasks(tasks) {
1122
+ const container = document.getElementById('daily-tasks-container');
1123
+
1124
+ if (!tasks || tasks.length === 0) {
1125
+ showNoTasksMessage();
1126
+ return;
1127
+ }
1128
+
1129
+ let html = '<div class="row">';
1130
+ tasks.forEach((task, index) => {
1131
+ const priorityClass = task.priority === 'high' ? 'danger' : task.priority === 'medium' ? 'warning' : 'success';
1132
+ const priorityIcon = task.priority === 'high' ? '🔴' : task.priority === 'medium' ? '🟡' : '🟢';
1133
+ const completedClass = task.is_completed ? 'border-success bg-light' : '';
1134
+ const completedIcon = task.is_completed ? '✅' : '⏳';
1135
+
1136
+ html += `
1137
+ <div class="col-md-6 col-lg-4 mb-3">
1138
+ <div class="card h-100 ${completedClass}">
1139
+ <div class="card-header d-flex justify-content-between align-items-center">
1140
+ <small class="text-${priorityClass}">
1141
+ ${priorityIcon} ${task.priority.toUpperCase()} Priority
1142
+ </small>
1143
+ <small>${completedIcon} ${task.estimated_duration} min</small>
1144
+ </div>
1145
+ <div class="card-body">
1146
+ <h6 class="card-title">${task.task_title}</h6>
1147
+ <p class="card-text small">${task.task_description}</p>
1148
+ ${task.crop_specific ? `<span class="badge bg-info mb-2">🌾 ${task.crop_specific}</span>` : ''}
1149
+ ${task.weather_dependent ? '<span class="badge bg-warning mb-2">🌤️ Weather Dependent</span>' : ''}
1150
+ </div>
1151
+ <div class="card-footer bg-transparent">
1152
+ <div class="d-grid gap-1">
1153
+ ${task.is_completed ? `
1154
+ <button class="btn btn-outline-secondary btn-sm" onclick="uncompleteTask(${task.id})">
1155
+ <i class="fas fa-undo me-1"></i>Mark as Incomplete
1156
+ </button>
1157
+ ${task.rating ? `<small class="text-muted">Rating: ${'⭐'.repeat(task.rating)}</small>` : ''}
1158
+ ` : `
1159
+ <button class="btn btn-success btn-sm" onclick="completeTask(${task.id})">
1160
+ <i class="fas fa-check me-1"></i>Mark as Complete
1161
+ </button>
1162
+ `}
1163
+ </div>
1164
+ </div>
1165
+ </div>
1166
+ </div>`;
1167
+ });
1168
+ html += '</div>';
1169
+
1170
+ container.innerHTML = html;
1171
+ }
1172
+
1173
+ function showNoTasksMessage(message = null) {
1174
+ const defaultMessage = `
1175
+ <div class="text-center text-muted py-4" id="no-tasks-message">
1176
+ <i class="fas fa-clipboard-list fa-3x mb-3"></i>
1177
+ <h6>No daily tasks available</h6>
1178
+ <p>Generate daily tasks to get AI-powered farming recommendations</p>
1179
+ </div>`;
1180
+
1181
+ const errorMessage = `
1182
+ <div class="text-center text-danger py-4">
1183
+ <i class="fas fa-exclamation-triangle fa-3x mb-3"></i>
1184
+ <h6>Error Loading Tasks</h6>
1185
+ <p>${message}</p>
1186
+ </div>`;
1187
+
1188
+ document.getElementById('daily-tasks-container').innerHTML = message ? errorMessage : defaultMessage;
1189
+ }
1190
+
1191
+ function generateDailyTasks() {
1192
+ const today = new Date().toISOString().split('T')[0];
1193
+ const loadingSpinner = '<div class="text-center py-4"><div class="spinner-border text-success" role="status"><span class="visually-hidden">Generating...</span></div><p class="mt-2">Generating daily tasks with AI...</p></div>';
1194
+
1195
+ document.getElementById('daily-tasks-container').innerHTML = loadingSpinner;
1196
+
1197
+ fetch('/farmer/daily_tasks/generate', {
1198
+ method: 'POST',
1199
+ headers: {
1200
+ 'Content-Type': 'application/json',
1201
+ },
1202
+ body: JSON.stringify({
1203
+ date: today
1204
+ })
1205
+ })
1206
+ .then(response => response.json())
1207
+ .then(data => {
1208
+ if (data.success) {
1209
+ alert('✅ ' + data.message);
1210
+ loadDailyTasks(); // Reload tasks to show the generated ones
1211
+ } else {
1212
+ alert('❌ ' + (data.error || 'Failed to generate daily tasks'));
1213
+ showNoTasksMessage();
1214
+ }
1215
+ })
1216
+ .catch(error => {
1217
+ console.error('Error generating daily tasks:', error);
1218
+ alert('Error generating daily tasks. Please try again.');
1219
+ showNoTasksMessage();
1220
+ });
1221
+ }
1222
+
1223
+ function completeTask(taskId) {
1224
+ // Show rating modal
1225
+ const rating = prompt('Rate this task completion (1-5 stars):');
1226
+ const feedback = prompt('Any feedback about this task? (optional):');
1227
+
1228
+ let ratingNum = null;
1229
+ if (rating && !isNaN(rating)) {
1230
+ ratingNum = Math.max(1, Math.min(5, parseInt(rating)));
1231
+ }
1232
+
1233
+ fetch(`/farmer/daily_tasks/${taskId}/complete`, {
1234
+ method: 'POST',
1235
+ headers: {
1236
+ 'Content-Type': 'application/json',
1237
+ },
1238
+ body: JSON.stringify({
1239
+ rating: ratingNum,
1240
+ feedback: feedback || ''
1241
+ })
1242
+ })
1243
+ .then(response => response.json())
1244
+ .then(data => {
1245
+ if (data.success) {
1246
+ alert('✅ Task completed successfully!');
1247
+ loadDailyTasks(); // Reload tasks to update status
1248
+ } else {
1249
+ alert('❌ ' + (data.error || 'Failed to complete task'));
1250
+ }
1251
+ })
1252
+ .catch(error => {
1253
+ console.error('Error completing task:', error);
1254
+ alert('Error completing task. Please try again.');
1255
+ });
1256
+ }
1257
+
1258
+ function uncompleteTask(taskId) {
1259
+ if (!confirm('Are you sure you want to mark this task as incomplete?')) {
1260
+ return;
1261
+ }
1262
+
1263
+ fetch(`/farmer/daily_tasks/${taskId}/uncomplete`, {
1264
+ method: 'POST',
1265
+ headers: {
1266
+ 'Content-Type': 'application/json',
1267
+ }
1268
+ })
1269
+ .then(response => response.json())
1270
+ .then(data => {
1271
+ if (data.success) {
1272
+ alert('✅ Task marked as incomplete');
1273
+ loadDailyTasks(); // Reload tasks to update status
1274
+ } else {
1275
+ alert('❌ ' + (data.error || 'Failed to uncomplete task'));
1276
+ }
1277
+ })
1278
+ .catch(error => {
1279
+ console.error('Error uncompleting task:', error);
1280
+ alert('Error updating task. Please try again.');
1281
+ });
1282
+ }
1283
+
1284
+ function sendTasksTelegram() {
1285
+ const today = new Date().toISOString().split('T')[0];
1286
+
1287
+ fetch('/farmer/daily_tasks/send_telegram', {
1288
+ method: 'POST',
1289
+ headers: {
1290
+ 'Content-Type': 'application/json',
1291
+ },
1292
+ body: JSON.stringify({
1293
+ date: today
1294
+ })
1295
+ })
1296
+ .then(response => response.json())
1297
+ .then(data => {
1298
+ if (data.success) {
1299
+ alert('✅ ' + data.message);
1300
+ } else {
1301
+ alert('❌ ' + (data.error || 'Failed to send tasks via Telegram'));
1302
+ }
1303
+ })
1304
+ .catch(error => {
1305
+ console.error('Error sending tasks via Telegram:', error);
1306
+ alert('Error sending tasks. Please try again.');
1307
+ });
1308
+ }
1309
+
1310
+ function deleteAllTasks() {
1311
+ if (!confirm('Are you sure you want to delete all tasks for today? This action cannot be undone.')) {
1312
+ return;
1313
+ }
1314
+
1315
+ const today = new Date().toISOString().split('T')[0];
1316
+
1317
+ fetch('/farmer/daily_tasks/delete_all', {
1318
+ method: 'POST',
1319
+ headers: {
1320
+ 'Content-Type': 'application/json',
1321
+ },
1322
+ body: JSON.stringify({
1323
+ date: today
1324
+ })
1325
+ })
1326
+ .then(response => response.json())
1327
+ .then(data => {
1328
+ if (data.success) {
1329
+ alert('✅ ' + data.message);
1330
+ showNoTasksMessage(); // Show the no tasks message
1331
+ } else {
1332
+ alert('❌ ' + (data.error || 'Failed to delete tasks'));
1333
+ }
1334
+ })
1335
+ .catch(error => {
1336
+ console.error('Error deleting tasks:', error);
1337
+ alert('Error deleting tasks. Please try again.');
1338
+ });
1339
+ }
1340
+
1341
+ // Auto-load today's tasks when page loads
1342
+ document.addEventListener('DOMContentLoaded', function() {
1343
+ // Load today's tasks automatically
1344
+ loadDailyTasks();
1345
+ });
1346
+ </script>
1347
+ </script>
1348
+ {% endblock %}
templates/farmer_dashboard_fixed.html ADDED
@@ -0,0 +1,311 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}Farmer Dashboard - Farm Management Portal{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="container mt-4">
7
+ <!-- Welcome Header -->
8
+ <div class="row mb-4">
9
+ <div class="col-12">
10
+ <div class="card bg-success text-white">
11
+ <div class="card-body">
12
+ <h2><i class="fas fa-tachometer-alt me-2"></i>Welcome, {{ farmer.name }}!</h2>
13
+ <p class="mb-0">Manage your farms and get AI-powered daily recommendations</p>
14
+ </div>
15
+ </div>
16
+ </div>
17
+ </div>
18
+
19
+ <!-- Quick Stats -->
20
+ <div class="row mb-4">
21
+ <div class="col-md-3 mb-3">
22
+ <div class="card text-center">
23
+ <div class="card-body">
24
+ <i class="fas fa-seedling fa-2x text-success mb-2"></i>
25
+ <h5>{{ farms|length }}</h5>
26
+ <small class="text-muted">Total Farms</small>
27
+ </div>
28
+ </div>
29
+ </div>
30
+ <div class="col-md-3 mb-3">
31
+ <div class="card text-center">
32
+ <div class="card-body">
33
+ <i class="fas fa-calendar-check fa-2x text-primary mb-2"></i>
34
+ <h5>{{ recent_activities|length }}</h5>
35
+ <small class="text-muted">Recent Activities</small>
36
+ </div>
37
+ </div>
38
+ </div>
39
+ <div class="col-md-3 mb-3">
40
+ <div class="card text-center">
41
+ <div class="card-body">
42
+ <i class="fas fa-sms fa-2x text-info mb-2"></i>
43
+ <h5>{% if today_advisory %}Active{% else %}Pending{% endif %}</h5>
44
+ <small class="text-muted">Today's Advisory</small>
45
+ </div>
46
+ </div>
47
+ </div>
48
+ <div class="col-md-3 mb-3">
49
+ <div class="card text-center">
50
+ <div class="card-body">
51
+ <i class="fas fa-user fa-2x text-warning mb-2"></i>
52
+ <h5>{{ farmer.contact_number }}</h5>
53
+ <small class="text-muted">Contact</small>
54
+ </div>
55
+ </div>
56
+ </div>
57
+ </div>
58
+
59
+ <div class="row">
60
+ <!-- Today's Advisory -->
61
+ <div class="col-md-8 mb-4">
62
+ <div class="card">
63
+ <div class="card-header bg-primary text-white">
64
+ <h5><i class="fas fa-brain me-2"></i>Today's AI Advisory</h5>
65
+ </div>
66
+ <div class="card-body">
67
+ {% if today_advisory %}
68
+ <div class="alert alert-success">
69
+ <h6><i class="fas fa-check-circle me-2"></i>Tasks to Do:</h6>
70
+ <p>{{ today_advisory.task_to_do }}</p>
71
+ </div>
72
+ <div class="alert alert-warning">
73
+ <h6><i class="fas fa-exclamation-triangle me-2"></i>Tasks to Avoid:</h6>
74
+ <p>{{ today_advisory.task_to_avoid }}</p>
75
+ </div>
76
+ {% if today_advisory.reason_explanation %}
77
+ <div class="alert alert-info">
78
+ <h6><i class="fas fa-info-circle me-2"></i>Explanation:</h6>
79
+ <p>{{ today_advisory.reason_explanation }}</p>
80
+ </div>
81
+ {% endif %}
82
+
83
+ {% if farms %}
84
+ <div class="mt-3">
85
+ <button class="btn btn-success" onclick="sendSMSAdvisory({{ farms[0].id }})">
86
+ <i class="fas fa-sms me-2"></i>Send SMS Alert
87
+ </button>
88
+ <button class="btn btn-primary" onclick="generateNewAdvisory({{ farms[0].id }})">
89
+ <i class="fas fa-sync me-2"></i>Refresh Advisory
90
+ </button>
91
+ </div>
92
+ {% endif %}
93
+ {% else %}
94
+ <div class="text-center text-muted py-4">
95
+ <i class="fas fa-robot fa-3x mb-3"></i>
96
+ <h6>No advisory generated yet for today</h6>
97
+ <p>Click below to generate AI-powered recommendations</p>
98
+ {% if farms %}
99
+ <button class="btn btn-primary" onclick="generateNewAdvisory({{ farms[0].id }})">
100
+ <i class="fas fa-magic me-2"></i>Generate Advisory
101
+ </button>
102
+ {% endif %}
103
+ </div>
104
+ {% endif %}
105
+ </div>
106
+ </div>
107
+ </div>
108
+
109
+ <!-- Quick Actions -->
110
+ <div class="col-md-4 mb-4">
111
+ <div class="card">
112
+ <div class="card-header bg-secondary text-white">
113
+ <h5><i class="fas fa-bolt me-2"></i>Quick Actions</h5>
114
+ </div>
115
+ <div class="card-body">
116
+ <div class="d-grid gap-2">
117
+ <a href="{{ url_for('add_farm') }}" class="btn btn-success">
118
+ <i class="fas fa-plus me-2"></i>Add New Farm
119
+ </a>
120
+ {% if farms %}
121
+ <a href="{{ url_for('farm_details', farm_id=farms[0].id) }}" class="btn btn-primary">
122
+ <i class="fas fa-eye me-2"></i>View Farm Details
123
+ </a>
124
+ <button class="btn btn-info" onclick="checkWeather({{ farms[0].id }})">
125
+ <i class="fas fa-cloud me-2"></i>Check Weather
126
+ </button>
127
+ {% endif %}
128
+ <a href="{{ url_for('farmer_logout') }}" class="btn btn-outline-danger">
129
+ <i class="fas fa-sign-out-alt me-2"></i>Logout
130
+ </a>
131
+ </div>
132
+ </div>
133
+ </div>
134
+ </div>
135
+ </div>
136
+
137
+ <!-- Farms List -->
138
+ {% if farms %}
139
+ <div class="row mb-4">
140
+ <div class="col-12">
141
+ <div class="card">
142
+ <div class="card-header">
143
+ <h5><i class="fas fa-list me-2"></i>Your Farms</h5>
144
+ </div>
145
+ <div class="card-body">
146
+ <div class="table-responsive">
147
+ <table class="table table-striped">
148
+ <thead>
149
+ <tr>
150
+ <th>Farm Name</th>
151
+ <th>Size (Acres)</th>
152
+ <th>Crops</th>
153
+ <th>Irrigation</th>
154
+ <th>Actions</th>
155
+ </tr>
156
+ </thead>
157
+ <tbody>
158
+ {% for farm in farms %}
159
+ <tr>
160
+ <td>{{ farm.farm_name }}</td>
161
+ <td>{{ farm.farm_size }}</td>
162
+ <td>
163
+ {% for crop in farm.get_crop_types() %}
164
+ <span class="badge bg-success me-1">{{ crop }}</span>
165
+ {% endfor %}
166
+ </td>
167
+ <td>{{ farm.irrigation_type }}</td>
168
+ <td>
169
+ <a href="{{ url_for('farm_details', farm_id=farm.id) }}" class="btn btn-sm btn-primary">
170
+ <i class="fas fa-eye"></i>
171
+ </a>
172
+ <button class="btn btn-sm btn-success" onclick="generateNewAdvisory({{ farm.id }})">
173
+ <i class="fas fa-brain"></i>
174
+ </button>
175
+ </td>
176
+ </tr>
177
+ {% endfor %}
178
+ </tbody>
179
+ </table>
180
+ </div>
181
+ </div>
182
+ </div>
183
+ </div>
184
+ </div>
185
+ {% endif %}
186
+
187
+ <!-- Recent Activities -->
188
+ {% if recent_activities %}
189
+ <div class="row">
190
+ <div class="col-12">
191
+ <div class="card">
192
+ <div class="card-header">
193
+ <h5><i class="fas fa-history me-2"></i>Recent Activities</h5>
194
+ </div>
195
+ <div class="card-body">
196
+ <div class="timeline">
197
+ {% for activity in recent_activities %}
198
+ <div class="timeline-item mb-3">
199
+ <div class="d-flex">
200
+ <div class="flex-shrink-0">
201
+ <i class="fas fa-circle text-success"></i>
202
+ </div>
203
+ <div class="flex-grow-1 ms-3">
204
+ <h6 class="mb-1">{{ activity.activity_type|title }}</h6>
205
+ <p class="mb-1">{{ activity.activity_description }}</p>
206
+ <small class="text-muted">
207
+ Scheduled: {{ activity.scheduled_date.strftime('%d %b %Y') }}
208
+ | Status: <span class="badge bg-{{ 'success' if activity.status == 'completed' else 'warning' }}">{{ activity.status|title }}</span>
209
+ </small>
210
+ </div>
211
+ </div>
212
+ </div>
213
+ {% endfor %}
214
+ </div>
215
+ </div>
216
+ </div>
217
+ </div>
218
+ </div>
219
+ {% endif %}
220
+ </div>
221
+
222
+ <!-- Loading Modal -->
223
+ <div class="modal fade" id="loadingModal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1">
224
+ <div class="modal-dialog modal-dialog-centered">
225
+ <div class="modal-content">
226
+ <div class="modal-body text-center">
227
+ <div class="spinner-border text-primary" role="status">
228
+ <span class="visually-hidden">Loading...</span>
229
+ </div>
230
+ <p class="mt-3 mb-0">Processing your request...</p>
231
+ </div>
232
+ </div>
233
+ </div>
234
+ </div>
235
+ {% endblock %}
236
+
237
+ {% block extra_js %}
238
+ <script>
239
+ // Helper to show/hide the Bootstrap 5 modal using vanilla JS
240
+ function _getLoadingModalInstance(){
241
+ const modalEl = document.getElementById('loadingModal');
242
+ return bootstrap.Modal.getOrCreateInstance(modalEl);
243
+ }
244
+
245
+ function generateNewAdvisory(farmId) {
246
+ const modal = _getLoadingModalInstance();
247
+ modal.show();
248
+
249
+ fetch(`/generate_advisory/${farmId}`)
250
+ .then(response => response.json())
251
+ .then(data => {
252
+ modal.hide();
253
+ if (data.success) {
254
+ // Reload to show updated advisory
255
+ location.reload();
256
+ } else {
257
+ alert('Failed to generate advisory: ' + (data.error || 'Unknown error'));
258
+ }
259
+ })
260
+ .catch(error => {
261
+ modal.hide();
262
+ alert('Error: ' + error.message);
263
+ });
264
+ }
265
+
266
+ function sendSMSAdvisory(farmId) {
267
+ const modal = _getLoadingModalInstance();
268
+ modal.show();
269
+
270
+ fetch(`/send_sms_advisory/${farmId}`)
271
+ .then(response => response.json())
272
+ .then(data => {
273
+ modal.hide();
274
+ if (data.success) {
275
+ alert('SMS sent successfully!');
276
+ } else {
277
+ alert('Failed to send SMS: ' + (data.error || 'Unknown error'));
278
+ }
279
+ })
280
+ .catch(error => {
281
+ modal.hide();
282
+ alert('Error: ' + error.message);
283
+ });
284
+ }
285
+
286
+ function checkWeather(farmId) {
287
+ const modal = _getLoadingModalInstance();
288
+ modal.show();
289
+
290
+ fetch(`/api/weather/${farmId}`)
291
+ .then(response => response.json())
292
+ .then(data => {
293
+ modal.hide();
294
+ if (data.error) {
295
+ alert('Weather data unavailable: ' + data.error);
296
+ } else {
297
+ let weatherInfo = `Current Weather:\n`;
298
+ weatherInfo += `Temperature: ${data.main?.temp || 'N/A'}°C\n`;
299
+ weatherInfo += `Humidity: ${data.main?.humidity || 'N/A'}%\n`;
300
+ weatherInfo += `Condition: ${data.weather?.[0]?.description || 'N/A'}\n`;
301
+ weatherInfo += `Wind: ${data.wind?.speed ? (data.wind.speed * 3.6).toFixed(1) : 'N/A'} km/h\n`;
302
+ alert(weatherInfo);
303
+ }
304
+ })
305
+ .catch(error => {
306
+ modal.hide();
307
+ alert('Error: ' + error.message);
308
+ });
309
+ }
310
+ </script>
311
+ {% endblock %}
templates/farmer_login.html ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}Farmer Login - Farm Management Portal{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="container">
7
+ <div class="row justify-content-center mt-5">
8
+ <div class="col-md-6 col-lg-4">
9
+ <div class="card">
10
+ <div class="card-header bg-success text-white text-center">
11
+ <h4><i class="fas fa-user me-2"></i>Farmer Login</h4>
12
+ </div>
13
+ <div class="card-body">
14
+ <form method="POST">
15
+ <div class="mb-3">
16
+ <label for="aadhaar_id" class="form-label">Aadhaar ID</label>
17
+ <input type="text" class="form-control" id="aadhaar_id" name="aadhaar_id"
18
+ placeholder="Enter 12-digit Aadhaar ID" maxlength="12" required>
19
+ </div>
20
+
21
+ <div class="mb-3">
22
+ <label for="password" class="form-label">Password</label>
23
+ <input type="password" class="form-control" id="password" name="password"
24
+ placeholder="Enter password" required>
25
+ </div>
26
+
27
+ <div class="d-grid">
28
+ <button type="submit" class="btn btn-success">
29
+ <i class="fas fa-sign-in-alt me-2"></i>Login
30
+ </button>
31
+ </div>
32
+ </form>
33
+
34
+ <hr>
35
+
36
+ <div class="text-center">
37
+ <p class="mb-0">Don't have an account?</p>
38
+ <a href="{{ url_for('farmer_register') }}" class="btn btn-outline-success btn-sm">
39
+ <i class="fas fa-user-plus me-1"></i>Register Now
40
+ </a>
41
+ </div>
42
+ </div>
43
+ </div>
44
+ </div>
45
+ </div>
46
+ </div>
47
+ {% endblock %}
48
+
49
+ {% block extra_js %}
50
+ <script>
51
+ // Format Aadhaar ID input
52
+ document.getElementById('aadhaar_id').addEventListener('input', function(e) {
53
+ let value = e.target.value.replace(/\D/g, '');
54
+ if (value.length > 12) {
55
+ value = value.slice(0, 12);
56
+ }
57
+ e.target.value = value;
58
+ });
59
+ </script>
60
+ {% endblock %}
templates/farmer_register.html ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}Farmer Registration - Farm Management Portal{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="container">
7
+ <div class="row justify-content-center mt-4">
8
+ <div class="col-md-8 col-lg-6">
9
+ <div class="card">
10
+ <div class="card-header bg-success text-white text-center">
11
+ <h4><i class="fas fa-user-plus me-2"></i>Farmer Registration</h4>
12
+ </div>
13
+ <div class="card-body">
14
+ <form method="POST">
15
+ <div class="row">
16
+ <div class="col-md-12 mb-3">
17
+ <label for="name" class="form-label">Full Name *</label>
18
+ <input type="text" class="form-control" id="name" name="name" required>
19
+ </div>
20
+ </div>
21
+
22
+ <div class="row">
23
+ <div class="col-md-6 mb-3">
24
+ <label for="age" class="form-label">Age</label>
25
+ <input type="number" class="form-control" id="age" name="age" min="18" max="100">
26
+ </div>
27
+ <div class="col-md-6 mb-3">
28
+ <label for="gender" class="form-label">Gender</label>
29
+ <select class="form-select" id="gender" name="gender">
30
+ <option value="">Select Gender</option>
31
+ <option value="Male">Male</option>
32
+ <option value="Female">Female</option>
33
+ <option value="Other">Other</option>
34
+ </select>
35
+ </div>
36
+ </div>
37
+
38
+ <div class="mb-3">
39
+ <label for="aadhaar_id" class="form-label">Aadhaar ID *</label>
40
+ <input type="text" class="form-control" id="aadhaar_id" name="aadhaar_id"
41
+ placeholder="Enter 12-digit Aadhaar ID" maxlength="12" required>
42
+ <small class="form-text text-muted">This will be used as your login ID</small>
43
+ </div>
44
+
45
+ <div class="mb-3">
46
+ <label for="contact_number" class="form-label">Contact Number *</label>
47
+ <input type="tel" class="form-control" id="contact_number" name="contact_number"
48
+ placeholder="Enter 10-digit mobile number" maxlength="10" required>
49
+ <small class="form-text text-muted">SMS alerts will be sent to this number</small>
50
+ </div>
51
+
52
+ <div class="mb-3">
53
+ <label for="address" class="form-label">Address *</label>
54
+ <textarea class="form-control" id="address" name="address" rows="3"
55
+ placeholder="Enter complete address" required></textarea>
56
+ </div>
57
+
58
+ <div class="mb-3">
59
+ <label for="password" class="form-label">Password *</label>
60
+ <input type="password" class="form-control" id="password" name="password"
61
+ placeholder="Enter password" minlength="6" required>
62
+ </div>
63
+
64
+ <div class="mb-3">
65
+ <label for="confirm_password" class="form-label">Confirm Password *</label>
66
+ <input type="password" class="form-control" id="confirm_password" name="confirm_password"
67
+ placeholder="Confirm password" required>
68
+ </div>
69
+
70
+ <div class="form-check mb-3">
71
+ <input class="form-check-input" type="checkbox" id="terms" required>
72
+ <label class="form-check-label" for="terms">
73
+ I agree to the Terms and Conditions and Privacy Policy
74
+ </label>
75
+ </div>
76
+
77
+ <div class="d-grid">
78
+ <button type="submit" class="btn btn-success">
79
+ <i class="fas fa-user-plus me-2"></i>Register
80
+ </button>
81
+ </div>
82
+ </form>
83
+
84
+ <hr>
85
+
86
+ <div class="text-center">
87
+ <p class="mb-0">Already have an account?</p>
88
+ <a href="{{ url_for('farmer_login') }}" class="btn btn-outline-success btn-sm">
89
+ <i class="fas fa-sign-in-alt me-1"></i>Login Here
90
+ </a>
91
+ </div>
92
+ </div>
93
+ </div>
94
+ </div>
95
+ </div>
96
+ </div>
97
+ {% endblock %}
98
+
99
+ {% block extra_js %}
100
+ <script>
101
+ // Format Aadhaar ID input
102
+ document.getElementById('aadhaar_id').addEventListener('input', function(e) {
103
+ let value = e.target.value.replace(/\D/g, '');
104
+ if (value.length > 12) {
105
+ value = value.slice(0, 12);
106
+ }
107
+ e.target.value = value;
108
+ });
109
+
110
+ // Format contact number input
111
+ document.getElementById('contact_number').addEventListener('input', function(e) {
112
+ let value = e.target.value.replace(/\D/g, '');
113
+ if (value.length > 10) {
114
+ value = value.slice(0, 10);
115
+ }
116
+ e.target.value = value;
117
+ });
118
+
119
+ // Password confirmation validation
120
+ document.getElementById('confirm_password').addEventListener('input', function(e) {
121
+ const password = document.getElementById('password').value;
122
+ const confirmPassword = e.target.value;
123
+
124
+ if (password !== confirmPassword) {
125
+ e.target.setCustomValidity('Passwords do not match');
126
+ } else {
127
+ e.target.setCustomValidity('');
128
+ }
129
+ });
130
+ </script>
131
+ {% endblock %}