pranit144 commited on
Commit
e622805
·
verified ·
1 Parent(s): 967c9eb

Upload 3 files

Browse files
Files changed (3) hide show
  1. app.py +395 -0
  2. requirements.txt +7 -0
  3. templates/index.html +1093 -0
app.py ADDED
@@ -0,0 +1,395 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import logging
4
+ import tempfile
5
+ import re
6
+ from dotenv import load_dotenv
7
+ from flask import Flask, request, jsonify, render_template, send_file
8
+ from groq import Groq
9
+ import google.generativeai as genai
10
+ import pandas as pd
11
+ from datetime import datetime, date, time
12
+ from apscheduler.schedulers.background import BackgroundScheduler
13
+ from twilio.rest import Client
14
+ import io
15
+
16
+ # Load environment variables
17
+ load_dotenv()
18
+
19
+ app = Flask(__name__)
20
+
21
+ # Configure API keys
22
+ GROQ_API_KEY = os.getenv('GROQ_API_KEY', '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')
23
+ GEMINI_API_KEY = os.getenv('GEMINI_API_KEY', 'AIzaSyCizcswP6vlKDMdB3HRAtVi2JbifOpbPvA')
24
+
25
+ # Initialize clients
26
+ groq_client = Groq(api_key=GROQ_API_KEY)
27
+ genai.configure(api_key=GEMINI_API_KEY)
28
+ MODEL_NAME = "gemini-1.5-flash"
29
+
30
+ # Initialize scheduler
31
+ scheduler = BackgroundScheduler()
32
+ scheduler.start()
33
+
34
+ # Logger setup
35
+ logging.basicConfig(level=logging.INFO)
36
+ logger = logging.getLogger(__name__)
37
+
38
+ # Add back the Twilio configuration
39
+ ACCOUNT_SID = 'AC490e071f8d01bf0df2f03d086c788d87'
40
+ AUTH_TOKEN = '224b23b950ad5a4052aba15893fdf083'
41
+ TWILIO_FROM = 'whatsapp:+14155238886'
42
+ PATIENT_PHONE = 'whatsapp:+917559355282'
43
+
44
+ # Initialize Twilio client
45
+ twilio_client = Client(ACCOUNT_SID, AUTH_TOKEN)
46
+
47
+
48
+ def send_whatsapp_message(message):
49
+ """Send WhatsApp notification"""
50
+ try:
51
+ msg = twilio_client.messages.create(
52
+ from_=TWILIO_FROM,
53
+ body=message,
54
+ to=PATIENT_PHONE
55
+ )
56
+ logger.info(f"WhatsApp notification sent: {msg.sid}")
57
+ return True
58
+ except Exception as e:
59
+ logger.error(f"Failed to send WhatsApp notification: {str(e)}")
60
+ return False
61
+
62
+
63
+
64
+ # define defaults for descriptive slots
65
+ DESCRIPTIVE_SLOTS = {
66
+ 'Morning': time(hour= 8, minute=0),
67
+ 'Afternoon': time(hour=13, minute=0),
68
+ 'Evening': time(hour=18, minute=0),
69
+ 'Night': time(hour=21, minute=0),
70
+ }
71
+
72
+ def parse_time_slot(time_str):
73
+ """
74
+ Try to parse “hh:mm AM/PM”, else map descriptive slots,
75
+ else return None.
76
+ """
77
+ try:
78
+ return datetime.strptime(time_str, '%I:%M %p')
79
+ except ValueError:
80
+ # fallback to descriptor map
81
+ slot = DESCRIPTIVE_SLOTS.get(time_str.title())
82
+ if slot:
83
+ # combine with today’s date just for hour/minute extraction
84
+ return datetime.combine(date.today(), slot)
85
+ else:
86
+ logger.warning(f"Unrecognized time format: '{time_str}'")
87
+ return None
88
+
89
+ def schedule_notifications(schedule_data):
90
+ """Schedule notifications for medications, meals, and activities."""
91
+ try:
92
+ scheduler.remove_all_jobs()
93
+
94
+ for section, prefix, key_map in [
95
+ ('medication_schedule', 'med', {'time': 'Scheduled Time', 'fields': ['Meal', 'Medication Name', 'Dosage']}),
96
+ ('meal_schedule', 'meal', {'time': 'Time', 'fields': ['Meal', 'Details']}),
97
+ ('activity_schedule', 'activity', {'time': 'Time', 'fields': ['Activity', 'Duration', 'Notes']}),
98
+ ]:
99
+ for item in schedule_data.get(section, []):
100
+ time_str = item[key_map['time']]
101
+ time_obj = parse_time_slot(time_str)
102
+ if not time_obj:
103
+ # skip invalid entries
104
+ continue
105
+
106
+ # build the message dynamically
107
+ if section == 'medication_schedule':
108
+ message = (
109
+ f"🔔 Medication Reminder\n"
110
+ f"Time: {time_str}\n"
111
+ f"Meal: {item['Meal']}\n"
112
+ f"Medication: {item['Medication Name']}\n"
113
+ f"Dosage: {item.get('Dosage', 'As prescribed')}"
114
+ )
115
+ elif section == 'meal_schedule':
116
+ message = (
117
+ f"🍽 Meal Reminder\n"
118
+ f"Time: {time_str}\n"
119
+ f"Meal: {item['Meal']}\n"
120
+ f"Details: {item.get('Details', '')}"
121
+ )
122
+ else: # activity_schedule
123
+ message = (
124
+ f"🏃 Activity Reminder\n"
125
+ f"Time: {time_str}\n"
126
+ f"Activity: {item['Activity']}\n"
127
+ f"Duration: {item.get('Duration', '')}\n"
128
+ f"Notes: {item.get('Notes', '')}"
129
+ )
130
+
131
+ job_id = f"{prefix}_{item.get(key_map['fields'][0], '').replace(' ', '_')}_{time_str}"
132
+ scheduler.add_job(
133
+ send_whatsapp_message,
134
+ trigger='cron',
135
+ args=[message],
136
+ hour=time_obj.hour,
137
+ minute=time_obj.minute,
138
+ id=job_id,
139
+ replace_existing=True
140
+ )
141
+
142
+ return True
143
+
144
+ except Exception as e:
145
+ logger.error(f"Failed to schedule notifications: {e}")
146
+ return False
147
+
148
+
149
+
150
+ # ✅ Function to Extract JSON from Response
151
+ def extract_json(text):
152
+ json_match = re.search(r'\{.*\}', text, re.DOTALL)
153
+ if json_match:
154
+ return json_match.group(0)
155
+ return None
156
+
157
+
158
+ # ✅ Function to Transcribe Audio Using Groq
159
+ def transcribe_audio(audio_file):
160
+ try:
161
+ temp_dir = tempfile.mkdtemp()
162
+ temp_path = os.path.join(temp_dir, "temp_audio.mp3")
163
+ audio_file.save(temp_path)
164
+
165
+ # Transcribe audio
166
+ with open(temp_path, "rb") as file:
167
+ transcription = groq_client.audio.transcriptions.create(
168
+ file=(temp_path, file.read()),
169
+ model="whisper-large-v3-turbo",
170
+ response_format="json",
171
+ language="en",
172
+ temperature=0.0
173
+ )
174
+
175
+ # Cleanup temporary files
176
+ os.remove(temp_path)
177
+ os.rmdir(temp_dir)
178
+
179
+ logger.info(f"Transcription successful: {transcription.text[:100]}...")
180
+ return transcription.text
181
+
182
+ except Exception as e:
183
+ logger.error(f"Transcription error: {str(e)}")
184
+ return None
185
+
186
+
187
+ # ✅ Function to Generate Care Plan Using Gemini
188
+ def generate_care_plan(conversation_text):
189
+ try:
190
+ model = genai.GenerativeModel(MODEL_NAME)
191
+ prompt = f"""
192
+ Based on the following conversation transcript between a doctor and a patient, generate a structured care plan in *valid JSON format*.
193
+
194
+ *JSON Format:*
195
+ {{
196
+ "medication_schedule": [{{"Medication Name": ""}}],
197
+ "meal_schedule": [{{"Time": "", "Meal": "", "Details": ""}}],
198
+ "activity_schedule": [{{"Time": "", "Activity": "", "Duration": "", "Notes": ""}}]
199
+ }}
200
+
201
+ *Fixed Meal Times:*
202
+ - Breakfast: 7:00 AM
203
+ - Snack: 10:00 AM
204
+ - Lunch: 1:00 PM
205
+ - Snack: 5:00 PM
206
+ - Dinner: 8:00 PM
207
+
208
+ Ensure medications align with meals. Adjust exercise based on health conditions.
209
+
210
+ *Conversation Transcript:*
211
+ {conversation_text}
212
+
213
+ *Output Strictly JSON. Do NOT add explanations or extra text.*
214
+ """
215
+
216
+ response = model.generate_content(prompt)
217
+ raw_response = response.text
218
+ logger.info(f"Raw Gemini Response: {raw_response[:100]}...")
219
+
220
+ json_text = extract_json(raw_response)
221
+ if json_text:
222
+ return json.loads(json_text)
223
+
224
+ logger.error("No valid JSON found in response.")
225
+ return create_default_care_plan()
226
+
227
+ except Exception as e:
228
+ logger.error(f"Care plan generation error: {str(e)}")
229
+ return create_default_care_plan()
230
+
231
+
232
+ # ✅ Function to Create a Default Care Plan (Fallback)
233
+ def create_default_care_plan():
234
+ return {
235
+ "medication_schedule": [],
236
+ "meal_schedule": [
237
+ {"Time": "7:00 AM", "Meal": "Breakfast", "Details": ""},
238
+ {"Time": "1:00 PM", "Meal": "Lunch", "Details": ""},
239
+ {"Time": "8:00 PM", "Meal": "Dinner", "Details": ""}
240
+ ],
241
+ "activity_schedule": []
242
+ }
243
+
244
+
245
+ # ✅ Route: Homepage
246
+ @app.route('/')
247
+ def index():
248
+ return render_template('index.html')
249
+
250
+
251
+ # ✅ Route: Generate Care Plan
252
+ @app.route("/generate_care_plan", methods=["POST"])
253
+ def generate_care_plan_route():
254
+ try:
255
+ if 'audio' not in request.files:
256
+ return jsonify({"error": "No audio file provided"}), 400
257
+
258
+ audio_file = request.files['audio']
259
+ if not audio_file:
260
+ return jsonify({"error": "Empty audio file"}), 400
261
+
262
+ transcript = transcribe_audio(audio_file)
263
+ if not transcript:
264
+ return jsonify({"error": "Transcription failed"}), 500
265
+
266
+ care_plan = generate_care_plan(transcript)
267
+ return jsonify(care_plan)
268
+
269
+ except Exception as e:
270
+ logger.error(f"Error in generate_care_plan_route: {str(e)}")
271
+ return jsonify({"error": str(e)}), 500
272
+
273
+
274
+ # ✅ Route: Generate Medication Schedule
275
+ @app.route('/generate_schedule', methods=['POST'])
276
+ def generate_schedule():
277
+ try:
278
+ data = request.get_json()
279
+ medicines = data.get("medicines", [])
280
+ schedule_list = []
281
+
282
+ meal_times = {
283
+ 3: [("Breakfast", "7:00 AM"), ("Lunch", "1:00 PM"), ("Dinner", "8:00 PM")],
284
+ 2: [("Breakfast", "7:00 AM"), ("Dinner", "8:00 PM")],
285
+ 1: [("Dinner", "8:00 PM")]
286
+ }
287
+
288
+ for med in medicines:
289
+ medication_name = med.get("medication_name", "")
290
+ frequency = int(med.get("frequency", 1))
291
+ dosage = med.get("dosage", "")
292
+
293
+ for meal, time in meal_times.get(frequency, []):
294
+ schedule_list.append({
295
+ "Medication Name": medication_name,
296
+ "Dosage": dosage,
297
+ "Meal": meal,
298
+ "Scheduled Time": time
299
+ })
300
+
301
+ # Schedule notifications
302
+ schedule_data = {
303
+ 'medication_schedule': schedule_list,
304
+ 'meal_schedule': data.get('meal_schedule', []),
305
+ 'activity_schedule': data.get('activity_schedule', [])
306
+ }
307
+ notifications_scheduled = schedule_notifications(schedule_data)
308
+
309
+ return jsonify({
310
+ "schedule_list": schedule_list,
311
+ "notifications_scheduled": notifications_scheduled
312
+ })
313
+
314
+ except Exception as e:
315
+ logger.error(f"Schedule generation error: {str(e)}")
316
+ return jsonify({"error": str(e)}), 500
317
+
318
+
319
+ @app.route('/download_schedule', methods=['POST'])
320
+ def download_schedule():
321
+ try:
322
+ data = request.json
323
+
324
+ # Create Excel writer object
325
+ output = io.BytesIO()
326
+ with pd.ExcelWriter(output, engine='xlsxwriter') as writer:
327
+ workbook = writer.book
328
+
329
+ # Convert medication schedule to DataFrame and write to Excel
330
+ if data.get('medication_schedule'):
331
+ med_df = pd.DataFrame(data['medication_schedule'])
332
+ med_df = med_df.sort_values('Time') if 'Time' in med_df.columns else med_df
333
+ med_df.to_excel(writer, sheet_name='Medication Schedule', index=False)
334
+
335
+ worksheet = writer.sheets['Medication Schedule']
336
+ header_format = workbook.add_format({
337
+ 'bold': True,
338
+ 'bg_color': '#3498db',
339
+ 'font_color': 'white'
340
+ })
341
+
342
+ for col_num, value in enumerate(med_df.columns.values):
343
+ worksheet.write(0, col_num, value, header_format)
344
+ worksheet.set_column(col_num, col_num, 15) # Set column width
345
+
346
+ # Write meal schedule
347
+ if data.get('meal_schedule'):
348
+ meal_df = pd.DataFrame(data['meal_schedule'])
349
+ meal_df = meal_df.sort_values('Time')
350
+ meal_df.to_excel(writer, sheet_name='Meal Schedule', index=False)
351
+
352
+ worksheet = writer.sheets['Meal Schedule']
353
+ header_format = workbook.add_format({
354
+ 'bold': True,
355
+ 'bg_color': '#2ecc71',
356
+ 'font_color': 'white'
357
+ })
358
+
359
+ for col_num, value in enumerate(meal_df.columns.values):
360
+ worksheet.write(0, col_num, value, header_format)
361
+ worksheet.set_column(col_num, col_num, 15)
362
+
363
+ # Write activity schedule
364
+ if data.get('activity_schedule'):
365
+ activity_df = pd.DataFrame(data['activity_schedule'])
366
+ activity_df = activity_df.sort_values('Time')
367
+ activity_df.to_excel(writer, sheet_name='Activity Schedule', index=False)
368
+
369
+ worksheet = writer.sheets['Activity Schedule']
370
+ header_format = workbook.add_format({
371
+ 'bold': True,
372
+ 'bg_color': '#e74c3c',
373
+ 'font_color': 'white'
374
+ })
375
+
376
+ for col_num, value in enumerate(activity_df.columns.values):
377
+ worksheet.write(0, col_num, value, header_format)
378
+ worksheet.set_column(col_num, col_num, 15)
379
+
380
+ output.seek(0)
381
+ return send_file(
382
+ output,
383
+ mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
384
+ as_attachment=True,
385
+ download_name='daily_schedule.xlsx'
386
+ )
387
+
388
+ except Exception as e:
389
+ logger.error(f"Error generating Excel file: {str(e)}")
390
+ return jsonify({"error": str(e)}), 500
391
+
392
+
393
+ # ✅ Run Flask App
394
+ if __name__ == '__main__':
395
+ app.run(debug=True, threaded=True)
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ Flask
2
+ python-dotenv
3
+ groq
4
+ google-generativeai
5
+ pandas
6
+ APScheduler
7
+ twilio
templates/index.html ADDED
@@ -0,0 +1,1093 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Medical Care Plan Generator</title>
6
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
7
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
8
+ <style>
9
+ body {
10
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
11
+ max-width: 1200px;
12
+ margin: 0 auto;
13
+ padding: 20px;
14
+ background-color: #f5f5f5;
15
+ }
16
+ .container {
17
+ background-color: white;
18
+ padding: 30px;
19
+ border-radius: 12px;
20
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
21
+ margin-bottom: 30px;
22
+ }
23
+ h1, h2, h3 {
24
+ color: #2c3e50;
25
+ }
26
+ h1 {
27
+ text-align: center;
28
+ margin-bottom: 30px;
29
+ }
30
+ .section {
31
+ margin-bottom: 40px;
32
+ }
33
+ .upload-section {
34
+ text-align: center;
35
+ margin: 30px 0;
36
+ padding: 20px;
37
+ background-color: #f8f9fa;
38
+ border-radius: 8px;
39
+ }
40
+ #uploadForm {
41
+ margin: 20px 0;
42
+ }
43
+ .results-container {
44
+ margin-top: 30px;
45
+ }
46
+ table {
47
+ width: 100%;
48
+ border-collapse: collapse;
49
+ margin-bottom: 20px;
50
+ }
51
+ th, td {
52
+ padding: 12px;
53
+ text-align: left;
54
+ border: 1px solid #ddd;
55
+ }
56
+ th {
57
+ background-color: #3498db;
58
+ color: white;
59
+ }
60
+ button {
61
+ background-color: #3498db;
62
+ color: white;
63
+ padding: 12px 24px;
64
+ border: none;
65
+ border-radius: 6px;
66
+ cursor: pointer;
67
+ font-size: 16px;
68
+ transition: background-color 0.3s;
69
+ }
70
+ button:hover {
71
+ background-color: #2980b9;
72
+ }
73
+ .loading {
74
+ text-align: center;
75
+ margin: 20px 0;
76
+ display: none;
77
+ }
78
+ .error {
79
+ color: #e74c3c;
80
+ padding: 10px;
81
+ background-color: #fde8e8;
82
+ border-radius: 4px;
83
+ margin: 10px 0;
84
+ }
85
+ /* Print styles */
86
+ @media print {
87
+ body {
88
+ padding: 0;
89
+ margin: 0;
90
+ }
91
+ .container {
92
+ box-shadow: none;
93
+ padding: 20px;
94
+ }
95
+ .upload-section,
96
+ #uploadForm,
97
+ #loading,
98
+ #downloadPDF,
99
+ #notificationControls,
100
+ .btn {
101
+ display: none !important;
102
+ }
103
+ table {
104
+ width: 100%;
105
+ border-collapse: collapse;
106
+ margin-bottom: 20px;
107
+ page-break-inside: avoid;
108
+ }
109
+ th, td {
110
+ border: 1px solid #000;
111
+ padding: 8px;
112
+ text-align: left;
113
+ }
114
+ th {
115
+ background-color: #f0f0f0 !important;
116
+ -webkit-print-color-adjust: exact;
117
+ }
118
+ h3 {
119
+ margin-top: 20px;
120
+ margin-bottom: 10px;
121
+ }
122
+ }
123
+ .loading-overlay {
124
+ display: none;
125
+ position: fixed;
126
+ top: 0;
127
+ left: 0;
128
+ width: 100%;
129
+ height: 100%;
130
+ background: rgba(0, 0, 0, 0.5);
131
+ z-index: 1000;
132
+ }
133
+
134
+ .loading-content {
135
+ position: absolute;
136
+ top: 50%;
137
+ left: 50%;
138
+ transform: translate(-50%, -50%);
139
+ background: white;
140
+ padding: 30px;
141
+ border-radius: 10px;
142
+ text-align: center;
143
+ }
144
+
145
+ .spinner {
146
+ width: 50px;
147
+ height: 50px;
148
+ border: 5px solid #f3f3f3;
149
+ border-top: 5px solid #3498db;
150
+ border-radius: 50%;
151
+ animation: spin 1s linear infinite;
152
+ margin: 0 auto 20px;
153
+ }
154
+
155
+ @keyframes spin {
156
+ 0% { transform: rotate(0deg); }
157
+ 100% { transform: rotate(360deg); }
158
+ }
159
+
160
+ .loading-text {
161
+ color: #2c3e50;
162
+ font-size: 18px;
163
+ margin-top: 15px;
164
+ }
165
+
166
+ .progress-steps {
167
+ margin-top: 15px;
168
+ text-align: left;
169
+ }
170
+
171
+ .step {
172
+ margin: 8px 0;
173
+ color: #666;
174
+ }
175
+
176
+ .step.active {
177
+ color: #3498db;
178
+ font-weight: bold;
179
+ }
180
+
181
+ .step.completed {
182
+ color: #2ecc71;
183
+ }
184
+
185
+ .care-plan-container {
186
+ margin-top: 30px;
187
+ }
188
+
189
+ .table-responsive {
190
+ margin-bottom: 30px;
191
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
192
+ border-radius: 8px;
193
+ overflow: hidden;
194
+ }
195
+
196
+ .table thead {
197
+ background-color: #3498db;
198
+ color: white;
199
+ }
200
+
201
+ .table tbody tr:nth-child(even) {
202
+ background-color: #f8f9fa;
203
+ }
204
+
205
+ .table tbody tr:hover {
206
+ background-color: #e9ecef;
207
+ }
208
+
209
+ h3 {
210
+ color: #2c3e50;
211
+ margin-bottom: 20px;
212
+ padding-bottom: 10px;
213
+ border-bottom: 2px solid #3498db;
214
+ }
215
+
216
+ td[contenteditable="true"] {
217
+ padding: 5px;
218
+ border: 1px solid #ffc107;
219
+ border-radius: 4px;
220
+ }
221
+
222
+ .edit-btn, .save-btn {
223
+ padding: 2px 8px;
224
+ margin: 0 2px;
225
+ }
226
+
227
+ .save-btn {
228
+ background-color: #28a745;
229
+ border-color: #28a745;
230
+ }
231
+
232
+ .edit-btn:hover {
233
+ background-color: #0056b3;
234
+ }
235
+
236
+ .save-btn:hover {
237
+ background-color: #218838;
238
+ }
239
+
240
+ @keyframes slideIn {
241
+ from { transform: translateX(100%); opacity: 0; }
242
+ to { transform: translateX(0); opacity: 1; }
243
+ }
244
+
245
+ @keyframes slideOut {
246
+ from { transform: translateX(0); opacity: 1; }
247
+ to { transform: translateX(100%); opacity: 0; }
248
+ }
249
+
250
+ .notification-status {
251
+ border-left: 4px solid;
252
+ margin-top: 20px;
253
+ }
254
+
255
+ .alert-success.notification-status {
256
+ border-left-color: #28a745;
257
+ }
258
+
259
+ .alert-warning.notification-status {
260
+ border-left-color: #ffc107;
261
+ }
262
+
263
+ .alert-danger.notification-status {
264
+ border-left-color: #dc3545;
265
+ }
266
+
267
+ .meal-activity-section {
268
+ margin-top: 30px;
269
+ }
270
+
271
+ .action-buttons {
272
+ display: flex;
273
+ gap: 10px;
274
+ margin-top: 20px;
275
+ justify-content: center;
276
+ }
277
+
278
+ .action-buttons button {
279
+ flex: 1;
280
+ max-width: 200px;
281
+ }
282
+
283
+ .add-item-btn {
284
+ margin-top: 10px;
285
+ background-color: #17a2b8;
286
+ }
287
+
288
+ #notificationControls {
289
+ margin-top: 20px;
290
+ padding: 15px;
291
+ border: 1px solid #ddd;
292
+ border-radius: 8px;
293
+ background-color: #f8f9fa;
294
+ }
295
+ </style>
296
+ </head>
297
+ <body>
298
+ <div class="loading-overlay" id="loadingOverlay">
299
+ <div class="loading-content">
300
+ <div class="spinner"></div>
301
+ <div class="loading-text">Processing your audio file...</div>
302
+ <div class="progress-steps">
303
+ <div class="step" id="step1">1. Uploading audio file...</div>
304
+ <div class="step" id="step2">2. Transcribing audio...</div>
305
+ <div class="step" id="step3">3. Generating care plan...</div>
306
+ </div>
307
+ </div>
308
+ </div>
309
+ <div class="container section">
310
+ <h1>Medical Care Plan Generator</h1>
311
+ <form id="uploadForm" class="mb-4">
312
+ <div class="mb-3">
313
+ <label for="audioFile" class="form-label">Upload Audio Recording</label>
314
+ <input type="file" id="audioFile" name="audio" accept="audio/*" class="form-control" required>
315
+ </div>
316
+ <button type="submit" class="btn btn-primary">Generate Care Plan</button>
317
+ </form>
318
+ <div id="loading" class="loading">
319
+ Processing audio and generating care plan...
320
+ </div>
321
+ <div id="carePlanResult"></div>
322
+ </div>
323
+
324
+ <!-- Medication Schedule Section -->
325
+ <div class="container section">
326
+ <h2 class="text-center mb-4">Patient Daily Schedule</h2>
327
+ <div id="medicationTableSection"></div>
328
+
329
+ <!-- Meal Schedule Section -->
330
+ <div id="mealScheduleSection" class="meal-activity-section"></div>
331
+
332
+ <!-- Activity Schedule Section -->
333
+ <div id="activityScheduleSection" class="meal-activity-section"></div>
334
+
335
+ <div class="action-buttons">
336
+ <button id="generateTimetableBtn" class="btn btn-primary mt-3" style="display:none;">
337
+ <i class="fas fa-calendar-check"></i> Generate Daily Timetables
338
+ </button>
339
+ <button id="downloadExcelBtn" class="btn btn-success mt-3" style="display:none;">
340
+ <i class="fas fa-file-excel"></i> Download Excel Schedule
341
+ </button>
342
+ </div>
343
+
344
+ <!-- Notification Controls Section -->
345
+ <div id="notificationControls" style="display:none;">
346
+ <h3><i class="fas fa-bell"></i> WhatsApp Notifications</h3>
347
+ <div class="form-check form-switch mb-3">
348
+ <input class="form-check-input" type="checkbox" id="enableNotifications" checked>
349
+ <label class="form-check-label" for="enableNotifications">Enable WhatsApp Reminders</label>
350
+ </div>
351
+ <div id="notificationStatus"></div>
352
+ </div>
353
+
354
+ <div id="timetableResult" class="mt-4"></div>
355
+ </div>
356
+
357
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
358
+ <script>
359
+ // Add a variable to store the care plan data
360
+ let carePlanData = null;
361
+ let allScheduleData = {};
362
+
363
+ function createTable(title, headers, data, editable = true, tableType = '') {
364
+ if (!data || data.length === 0) {
365
+ let html = `
366
+ <div class="mt-4">
367
+ <h3>${title}</h3>
368
+ <p class="text-muted">No data available</p>`;
369
+
370
+ if (tableType) {
371
+ html += `<button class="btn btn-sm add-item-btn" onclick="addNewItem('${tableType}')">
372
+ <i class="fas fa-plus"></i> Add New Item
373
+ </button>`;
374
+ }
375
+
376
+ html += `</div>`;
377
+ return html;
378
+ }
379
+
380
+ let html = `<div class="mt-4"><h3>${title}</h3>`;
381
+ html += '<div class="table-responsive">';
382
+ const tableId = tableType ? ` id="${tableType}Table"` : '';
383
+ html += `<table class="table table-bordered"${tableId}>`;
384
+ html += '<thead class="table-primary"><tr>';
385
+
386
+ headers.forEach(header => {
387
+ html += `<th>${header}</th>`;
388
+ });
389
+
390
+ // Add Dosage column for medication table
391
+ if (tableType === 'medication') {
392
+ html += '<th>Dosage</th>';
393
+ html += '<th>Frequency per Day</th>';
394
+ }
395
+
396
+ if (editable) {
397
+ html += '<th>Actions</th>';
398
+ }
399
+
400
+ html += '</tr></thead><tbody>';
401
+
402
+ data.forEach((row, index) => {
403
+ html += '<tr>';
404
+ headers.forEach(header => {
405
+ const value = row[header] || '';
406
+ html += `<td contenteditable="false">${value}</td>`;
407
+ });
408
+
409
+ // Add dosage field for medication table
410
+ if (tableType === 'medication') {
411
+ const dosage = row['Dosage'] || '';
412
+ const frequency = row['frequency'] || 1;
413
+
414
+ html += `<td contenteditable="false">${dosage}</td>`;
415
+ html += `
416
+ <td>
417
+ <select class="form-select frequency-select">
418
+ <option value="1" ${frequency == 1 ? 'selected' : ''}>Once daily (Dinner)</option>
419
+ <option value="2" ${frequency == 2 ? 'selected' : ''}>Twice daily (Breakfast & Dinner)</option>
420
+ <option value="3" ${frequency == 3 ? 'selected' : ''}>Three times daily (Breakfast, Lunch & Dinner)</option>
421
+ </select>
422
+ </td>
423
+ `;
424
+ }
425
+
426
+ if (editable) {
427
+ html += `
428
+ <td>
429
+ <button class="btn btn-sm btn-primary edit-btn" onclick="toggleEdit(this, '${tableType}', ${index})">
430
+ <i class="fas fa-edit"></i> Edit
431
+ </button>
432
+ <button class="btn btn-sm btn-success save-btn" onclick="saveEdit(this, '${tableType}', ${index})" style="display:none;">
433
+ <i class="fas fa-save"></i> Save
434
+ </button>
435
+ <button class="btn btn-sm btn-danger" onclick="deleteItem('${tableType}', ${index})">
436
+ <i class="fas fa-trash"></i>
437
+ </button>
438
+ </td>
439
+ `;
440
+ }
441
+
442
+ html += '</tr>';
443
+ });
444
+
445
+ html += '</tbody></table></div>';
446
+
447
+ // Add button to add new items
448
+ if (tableType) {
449
+ html += `<button class="btn btn-sm add-item-btn" onclick="addNewItem('${tableType}')">
450
+ <i class="fas fa-plus"></i> Add New Item
451
+ </button>`;
452
+ }
453
+
454
+ html += '</div>';
455
+ return html;
456
+ }
457
+
458
+ function addNewItem(tableType) {
459
+ let newItem = {};
460
+
461
+ if (tableType === 'medication') {
462
+ newItem = { "Medication Name": "New Medication", "Dosage": "1 tablet", "frequency": 1 };
463
+ if (!carePlanData.medication_schedule) carePlanData.medication_schedule = [];
464
+ carePlanData.medication_schedule.push(newItem);
465
+ document.getElementById('medicationTableSection').innerHTML = createTable(
466
+ 'Medication Schedule',
467
+ ["Medication Name"],
468
+ carePlanData.medication_schedule,
469
+ true,
470
+ 'medication'
471
+ );
472
+ }
473
+ else if (tableType === 'meal') {
474
+ newItem = { "Time": "12:00 PM", "Meal": "New Meal", "Details": "Meal details" };
475
+ if (!carePlanData.meal_schedule) carePlanData.meal_schedule = [];
476
+ carePlanData.meal_schedule.push(newItem);
477
+ document.getElementById('mealScheduleSection').innerHTML = createTable(
478
+ 'Meal Schedule',
479
+ ["Time", "Meal", "Details"],
480
+ carePlanData.meal_schedule,
481
+ true,
482
+ 'meal'
483
+ );
484
+ }
485
+ else if (tableType === 'activity') {
486
+ newItem = { "Time": "5:00 PM", "Activity": "New Activity", "Duration": "30 min", "Notes": "Activity notes" };
487
+ if (!carePlanData.activity_schedule) carePlanData.activity_schedule = [];
488
+ carePlanData.activity_schedule.push(newItem);
489
+ document.getElementById('activityScheduleSection').innerHTML = createTable(
490
+ 'Activity Schedule',
491
+ ["Time", "Activity", "Duration", "Notes"],
492
+ carePlanData.activity_schedule,
493
+ true,
494
+ 'activity'
495
+ );
496
+ }
497
+
498
+ // Show generate button
499
+ document.getElementById('generateTimetableBtn').style.display = 'block';
500
+ }
501
+
502
+ function deleteItem(tableType, index) {
503
+ if (confirm('Are you sure you want to delete this item?')) {
504
+ if (tableType === 'medication' && carePlanData.medication_schedule) {
505
+ carePlanData.medication_schedule.splice(index, 1);
506
+ document.getElementById('medicationTableSection').innerHTML = createTable(
507
+ 'Medication Schedule',
508
+ ["Medication Name"],
509
+ carePlanData.medication_schedule,
510
+ true,
511
+ 'medication'
512
+ );
513
+ }
514
+ else if (tableType === 'meal' && carePlanData.meal_schedule) {
515
+ carePlanData.meal_schedule.splice(index, 1);
516
+ document.getElementById('mealScheduleSection').innerHTML = createTable(
517
+ 'Meal Schedule',
518
+ ["Time", "Meal", "Details"],
519
+ carePlanData.meal_schedule,
520
+ true,
521
+ 'meal'
522
+ );
523
+ }
524
+ else if (tableType === 'activity' && carePlanData.activity_schedule) {
525
+ carePlanData.activity_schedule.splice(index, 1);
526
+ document.getElementById('activityScheduleSection').innerHTML = createTable(
527
+ 'Activity Schedule',
528
+ ["Time", "Activity", "Duration", "Notes"],
529
+ carePlanData.activity_schedule,
530
+ true,
531
+ 'activity'
532
+ );
533
+ }
534
+
535
+ // Regenerate timetables if they're already displayed
536
+ if (document.getElementById('timetableResult').innerHTML !== '') {
537
+ document.getElementById('generateTimetableBtn').click();
538
+ }
539
+ }
540
+ }
541
+
542
+ function toggleEdit(button, tableType, index) {
543
+ const row = button.closest('tr');
544
+ const cells = row.querySelectorAll('td:not(:last-child)');
545
+ const editBtn = button;
546
+ const saveBtn = row.querySelector('.save-btn');
547
+
548
+ // Don't make frequency selector cell editable for medication table
549
+ const editableCells = tableType === 'medication' ?
550
+ cells.slice(0, -1) : cells;
551
+
552
+ editableCells.forEach(cell => {
553
+ cell.contentEditable = "true";
554
+ cell.style.backgroundColor = "#fff3cd";
555
+ });
556
+
557
+ editBtn.style.display = "none";
558
+ saveBtn.style.display = "inline-block";
559
+ }
560
+
561
+ function saveEdit(button, tableType, index) {
562
+ const row = button.closest('tr');
563
+ const cells = row.querySelectorAll('td:not(:last-child)');
564
+ const editBtn = row.querySelector('.edit-btn');
565
+ const saveBtn = button;
566
+
567
+ // Don't process frequency selector cell for medication table
568
+ const editableCells = tableType === 'medication' ?
569
+ cells.slice(0, -1) : cells;
570
+
571
+ editableCells.forEach(cell => {
572
+ cell.contentEditable = "false";
573
+ cell.style.backgroundColor = "";
574
+ });
575
+
576
+ editBtn.style.display = "inline-block";
577
+ saveBtn.style.display = "none";
578
+
579
+ // Update the carePlanData based on the edited values
580
+ if (tableType === 'medication') {
581
+ const frequencySelect = row.querySelector('.frequency-select');
582
+ const frequency = frequencySelect ? parseInt(frequencySelect.value) : 1;
583
+
584
+ carePlanData.medication_schedule[index] = {
585
+ "Medication Name": cells[0].textContent.trim(),
586
+ "Dosage": cells[1].textContent.trim(),
587
+ "frequency": frequency
588
+ };
589
+ } else if (tableType === 'meal') {
590
+ carePlanData.meal_schedule[index] = {
591
+ Time: cells[0].textContent.trim(),
592
+ Meal: cells[1].textContent.trim(),
593
+ Details: cells[2].textContent.trim()
594
+ };
595
+ } else if (tableType === 'activity') {
596
+ carePlanData.activity_schedule[index] = {
597
+ Time: cells[0].textContent.trim(),
598
+ Activity: cells[1].textContent.trim(),
599
+ Duration: cells[2].textContent.trim(),
600
+ Notes: cells[3].textContent.trim()
601
+ };
602
+ }
603
+
604
+ // Regenerate timetables if they're already displayed
605
+ if (document.getElementById('timetableResult').innerHTML !== '') {
606
+ document.getElementById('generateTimetableBtn').click();
607
+ }
608
+ }
609
+
610
+ function updateLoadingStep(stepNumber) {
611
+ // Reset all steps
612
+ document.querySelectorAll('.step').forEach(step => {
613
+ step.classList.remove('active', 'completed');
614
+ });
615
+
616
+ // Mark completed steps
617
+ for (let i = 1; i < stepNumber; i++) {
618
+ document.getElementById(`step${i}`).classList.add('completed');
619
+ }
620
+
621
+ // Mark current step as active
622
+ document.getElementById(`step${stepNumber}`).classList.add('active');
623
+ }
624
+
625
+ document.getElementById('uploadForm').addEventListener('submit', async function(e) {
626
+ e.preventDefault();
627
+
628
+ const fileInput = document.getElementById('audioFile');
629
+ const loadingOverlay = document.getElementById('loadingOverlay');
630
+ const resultDiv = document.getElementById('carePlanResult');
631
+ const medicationTableSection = document.getElementById('medicationTableSection');
632
+ const mealScheduleSection = document.getElementById('mealScheduleSection');
633
+ const activityScheduleSection = document.getElementById('activityScheduleSection');
634
+
635
+ if (!fileInput.files.length) {
636
+ resultDiv.innerHTML = '<div class="alert alert-danger">Please select an audio file</div>';
637
+ return;
638
+ }
639
+
640
+ const formData = new FormData();
641
+ formData.append('audio', fileInput.files[0]);
642
+
643
+ loadingOverlay.style.display = 'flex';
644
+ resultDiv.innerHTML = '';
645
+ medicationTableSection.innerHTML = '';
646
+ mealScheduleSection.innerHTML = '';
647
+ activityScheduleSection.innerHTML = '';
648
+ updateLoadingStep(1);
649
+
650
+ try {
651
+ // Step 1: Upload started
652
+ updateLoadingStep(1);
653
+ await new Promise(resolve => setTimeout(resolve, 1000)); // Show upload step
654
+
655
+ // Step 2: Transcription
656
+ updateLoadingStep(2);
657
+ const response = await fetch('/generate_care_plan', {
658
+ method: 'POST',
659
+ body: formData
660
+ });
661
+
662
+ // Step 3: Generating plan
663
+ updateLoadingStep(3);
664
+ const data = await response.json();
665
+
666
+ if (data.error) {
667
+ resultDiv.innerHTML = `<div class="alert alert-danger">Error: ${data.error}</div>`;
668
+ } else {
669
+ // Store the care plan data globally
670
+ carePlanData = data;
671
+
672
+ // Display the care plan data
673
+ let html = `
674
+ <div class="care-plan-container">
675
+ <div class="alert alert-success">
676
+ <h2><i class="fas fa-check-circle"></i> Care Plan Generated Successfully</h2>
677
+ </div>
678
+ <div class="card mb-4">
679
+ <div class="card-header bg-info text-white">
680
+ <h3 class="mb-0">Patient Information</h3>
681
+ </div>
682
+ <div class="card-body">
683
+ <p><strong>Name:</strong> ${data.patient_name || 'N/A'}</p>
684
+ <p><strong>Age:</strong> ${data.patient_age || 'N/A'}</p>
685
+ <p><strong>Medical Condition:</strong> ${data.medical_condition || 'N/A'}</p>
686
+ </div>
687
+ </div>
688
+ `;
689
+
690
+ if (data.prescription_summary) {
691
+ html += `
692
+ <div class="card mb-4">
693
+ <div class="card-header bg-primary text-white">
694
+ <h3 class="mb-0">Prescription Summary</h3>
695
+ </div>
696
+ <div class="card-body">
697
+ <p>${data.prescription_summary}</p>
698
+ </div>
699
+ </div>
700
+ `;
701
+ }
702
+
703
+ // Transcription section
704
+ if (data.transcription) {
705
+ html += `
706
+ <div class="card mb-4">
707
+ <div class="card-header bg-secondary text-white">
708
+ <h3 class="mb-0">Audio Transcription</h3>
709
+ </div>
710
+ <div class="card-body">
711
+ <p>${data.transcription}</p>
712
+ </div>
713
+ </div>
714
+ `;
715
+ }
716
+
717
+ resultDiv.innerHTML = html;
718
+
719
+ // Create the medication table for setting frequencies
720
+ if (data.medication_schedule && data.medication_schedule.length > 0) {
721
+ medicationTableSection.innerHTML = createTable(
722
+ 'Medication Schedule',
723
+ ["Medication Name"],
724
+ data.medication_schedule,
725
+ true,
726
+ 'medication'
727
+ );
728
+ }
729
+
730
+ // Create the meal schedule table
731
+ if (data.meal_schedule && data.meal_schedule.length > 0) {
732
+ mealScheduleSection.innerHTML = createTable(
733
+ 'Meal Schedule',
734
+ ["Time", "Meal", "Details"],
735
+ data.meal_schedule,
736
+ true,
737
+ 'meal'
738
+ );
739
+ } else {
740
+ mealScheduleSection.innerHTML = createTable(
741
+ 'Meal Schedule',
742
+ ["Time", "Meal", "Details"],
743
+ [],
744
+ true,
745
+ 'meal'
746
+ );
747
+ }
748
+
749
+ // Create the activity schedule table
750
+ if (data.activity_schedule && data.activity_schedule.length > 0) {
751
+ activityScheduleSection.innerHTML = createTable(
752
+ 'Activity Schedule',
753
+ ["Time", "Activity", "Duration", "Notes"],
754
+ data.activity_schedule,
755
+ true,
756
+ 'activity'
757
+ );
758
+ } else {
759
+ activityScheduleSection.innerHTML = createTable(
760
+ 'Activity Schedule',
761
+ ["Time", "Activity", "Duration", "Notes"],
762
+ [],
763
+ true,
764
+ 'activity'
765
+ );
766
+ }
767
+
768
+ document.getElementById('generateTimetableBtn').style.display = 'block';
769
+ document.getElementById('notificationControls').style.display = 'block';
770
+ }
771
+ } catch (error) {
772
+ resultDiv.innerHTML = `<div class="alert alert-danger">Error: ${error.message}</div>`;
773
+ } finally {
774
+ loadingOverlay.style.display = 'none';
775
+ }
776
+ });
777
+
778
+ document.getElementById('generateTimetableBtn').addEventListener('click', function() {
779
+ const timetableResult = document.getElementById('timetableResult');
780
+
781
+ if (!carePlanData) {
782
+ timetableResult.innerHTML = '<div class="alert alert-warning">No data available</div>';
783
+ return;
784
+ }
785
+
786
+ // Get medication data from the tables
787
+ const medicationTable = document.getElementById('medicationTable');
788
+ let medications = [];
789
+
790
+ if (medicationTable) {
791
+ const rows = medicationTable.querySelectorAll('tbody tr');
792
+ medications = Array.from(rows).map(row => {
793
+ const cells = row.querySelectorAll('td');
794
+ const frequencySelect = row.querySelector('.frequency-select');
795
+ const frequency = frequencySelect ? parseInt(frequencySelect.value) : 1;
796
+
797
+ return {
798
+ name: cells[0].textContent.trim(),
799
+ dosage: cells[1].textContent.trim() || '1 tablet',
800
+ frequency: frequency
801
+ };
802
+ });
803
+ }
804
+
805
+ // Prepare data for schedule generation
806
+ const scheduleData = {
807
+ medicines: medications.map(med => ({
808
+ medication_name: med.name,
809
+ dosage: med.dosage,
810
+ frequency: med.frequency
811
+ })),
812
+ meal_schedule: carePlanData.meal_schedule || [],
813
+ activity_schedule: carePlanData.activity_schedule || []
814
+ };
815
+
816
+ allScheduleData = scheduleData;
817
+
818
+ // Send data to backend to generate schedule and set up notifications
819
+ fetch('/generate_schedule', {
820
+ method: 'POST',
821
+ headers: {
822
+ 'Content-Type': 'application/json'
823
+ },
824
+ body: JSON.stringify(scheduleData)
825
+ })
826
+ .then(response => response.json())
827
+ .then(data => {
828
+ if (data.error) {
829
+ timetableResult.innerHTML = `<div class="alert alert-danger">Error: ${data.error}</div>`;
830
+ return;
831
+ }
832
+
833
+ // Update notification status
834
+ const notificationStatus = document.getElementById('notificationStatus');
835
+ if (data.notifications_scheduled) {
836
+ notificationStatus.innerHTML = `
837
+ <div class="alert alert-success notification-status">
838
+ <i class="fas fa-check-circle"></i> WhatsApp notifications have been scheduled successfully.
839
+ </div>
840
+ `;
841
+ } else {
842
+ notificationStatus.innerHTML = `
843
+ <div class="alert alert-warning notification-status">
844
+ <i class="fas fa-exclamation-triangle"></i> WhatsApp notifications could not be scheduled.
845
+ </div>
846
+ `;
847
+ }
848
+
849
+ // Update medication schedule with the generated data
850
+ if (data.schedule_list && data.schedule_list.length > 0) {
851
+ carePlanData.medication_schedule = data.schedule_list;
852
+ }
853
+
854
+ // Generate daily timetable
855
+ let html = `
856
+ <div class="alert alert-success">
857
+ <h2><i class="fas fa-calendar-alt"></i> Daily Medication Schedule</h2>
858
+ </div>
859
+ `;
860
+
861
+ // Create daily schedule table
862
+ html += '<div class="table-responsive">';
863
+ html += '<table class="table table-bordered">';
864
+ html += '<thead class="table-primary"><tr><th>Time</th><th>Medications</th><th>Meal</th><th>Activities</th></tr></thead>';
865
+ html += '<tbody>';
866
+
867
+ // Create a combined schedule
868
+ let combinedSchedule = [];
869
+
870
+ // Add medication items
871
+ carePlanData.medication_schedule.forEach(med => {
872
+ combinedSchedule.push({
873
+ time: med['Scheduled Time'],
874
+ type: 'medication',
875
+ name: med['Medication Name'],
876
+ details: med['Dosage'],
877
+ meal: med['Meal']
878
+ });
879
+ });
880
+
881
+ // Add meal items
882
+ if (carePlanData.meal_schedule) {
883
+ carePlanData.meal_schedule.forEach(meal => {
884
+ combinedSchedule.push({
885
+ time: meal['Time'],
886
+ type: 'meal',
887
+ name: meal['Meal'],
888
+ details: meal['Details']
889
+ });
890
+ });
891
+ }
892
+
893
+ // Add activity items
894
+ if (carePlanData.activity_schedule) {
895
+ carePlanData.activity_schedule.forEach(activity => {
896
+ combinedSchedule.push({
897
+ time: activity['Time'],
898
+ type: 'activity',
899
+ name: activity['Activity'],
900
+ details: activity['Duration'],
901
+ notes: activity['Notes']
902
+ });
903
+ });
904
+ }
905
+
906
+ // Sort the combined schedule by time
907
+ combinedSchedule.sort((a, b) => {
908
+ // Convert time strings to Date objects for comparison
909
+ const timeA = new Date(`01/01/2023 ${a.time}`);
910
+ const timeB = new Date(`01/01/2023 ${b.time}`);
911
+ return timeA - timeB;
912
+ });
913
+
914
+ // Group items by time
915
+ const scheduledItems = {};
916
+ combinedSchedule.forEach(item => {
917
+ if (!scheduledItems[item.time]) {
918
+ scheduledItems[item.time] = {
919
+ medications: [],
920
+ meals: [],
921
+ activities: []
922
+ };
923
+ }
924
+
925
+ if (item.type === 'medication') {
926
+ scheduledItems[item.time].medications.push({
927
+ name: item.name,
928
+ details: item.details,
929
+ meal: item.meal
930
+ });
931
+ } else if (item.type === 'meal') {
932
+ scheduledItems[item.time].meals.push({
933
+ name: item.name,
934
+ details: item.details
935
+ });
936
+ } else if (item.type === 'activity') {
937
+ scheduledItems[item.time].activities.push({
938
+ name: item.name,
939
+ details: item.details,
940
+ notes: item.notes
941
+ });
942
+ }
943
+ });
944
+
945
+ // Create table rows
946
+ Object.keys(scheduledItems).sort((a, b) => {
947
+ const timeA = new Date(`01/01/2023 ${a}`);
948
+ const timeB = new Date(`01/01/2023 ${b}`);
949
+ return timeA - timeB;
950
+ }).forEach(time => {
951
+ const items = scheduledItems[time];
952
+
953
+ html += '<tr>';
954
+ html += `<td class="fw-bold">${time}</td>`;
955
+
956
+ // Medications column
957
+ html += '<td>';
958
+ if (items.medications.length > 0) {
959
+ items.medications.forEach(med => {
960
+ html += `<div class="mb-1"><strong>${med.name}</strong> (${med.details})</div>`;
961
+ });
962
+ } else {
963
+ html += '-';
964
+ }
965
+ html += '</td>';
966
+
967
+ // Meals column
968
+ html += '<td>';
969
+ if (items.meals.length > 0) {
970
+ items.meals.forEach(meal => {
971
+ html += `<div class="mb-1"><strong>${meal.name}</strong>`;
972
+ if (meal.details) {
973
+ html += `<br><small>${meal.details}</small>`;
974
+ }
975
+ html += '</div>';
976
+ });
977
+ } else if (items.medications.some(med => med.meal)) {
978
+ const meal = items.medications[0].meal;
979
+ html += `<div class="mb-1"><strong>${meal}</strong></div>`;
980
+ } else {
981
+ html += '-';
982
+ }
983
+ html += '</td>';
984
+
985
+ // Activities column
986
+ html += '<td>';
987
+ if (items.activities.length > 0) {
988
+ items.activities.forEach(activity => {
989
+ html += `<div class="mb-1"><strong>${activity.name}</strong> (${activity.details})`;
990
+ if (activity.notes) {
991
+ html += `<br><small>${activity.notes}</small>`;
992
+ }
993
+ html += '</div>';
994
+ });
995
+ } else {
996
+ html += '-';
997
+ }
998
+ html += '</td>';
999
+
1000
+ html += '</tr>';
1001
+ });
1002
+
1003
+ html += '</tbody></table></div>';
1004
+
1005
+ // Show download button
1006
+ html += `
1007
+ <div class="action-buttons">
1008
+ <button id="printScheduleBtn" class="btn btn-info">
1009
+ <i class="fas fa-print"></i> Print Schedule
1010
+ </button>
1011
+ </div>
1012
+ `;
1013
+
1014
+ timetableResult.innerHTML = html;
1015
+ document.getElementById('downloadExcelBtn').style.display = 'block';
1016
+
1017
+ // Add event listener for print button
1018
+ document.getElementById('printScheduleBtn').addEventListener('click', function() {
1019
+ window.print();
1020
+ });
1021
+ })
1022
+ .catch(error => {
1023
+ timetableResult.innerHTML = `<div class="alert alert-danger">Error: ${error.message}</div>`;
1024
+ });
1025
+ });
1026
+
1027
+ // Event listener for downloading Excel file
1028
+ document.getElementById('downloadExcelBtn').addEventListener('click', function() {
1029
+ if (!carePlanData) {
1030
+ alert('No data available for download');
1031
+ return;
1032
+ }
1033
+
1034
+ // Prepare the complete data for download
1035
+ const downloadData = {
1036
+ medication_schedule: carePlanData.medication_schedule || [],
1037
+ meal_schedule: carePlanData.meal_schedule || [],
1038
+ activity_schedule: carePlanData.activity_schedule || []
1039
+ };
1040
+
1041
+ // Request Excel file from server
1042
+ fetch('/download_schedule', {
1043
+ method: 'POST',
1044
+ headers: {
1045
+ 'Content-Type': 'application/json'
1046
+ },
1047
+ body: JSON.stringify(downloadData)
1048
+ })
1049
+ .then(response => {
1050
+ if (!response.ok) {
1051
+ throw new Error('Server error');
1052
+ }
1053
+ return response.blob();
1054
+ })
1055
+ .then(blob => {
1056
+ const url = window.URL.createObjectURL(blob);
1057
+ const a = document.createElement('a');
1058
+ a.style.display = 'none';
1059
+ a.href = url;
1060
+ a.download = 'daily_schedule.xlsx';
1061
+ document.body.appendChild(a);
1062
+ a.click();
1063
+ window.URL.revokeObjectURL(url);
1064
+ })
1065
+ .catch(error => {
1066
+ alert(`Error downloading the file: ${error.message}`);
1067
+ });
1068
+ });
1069
+
1070
+ // Listen for changes to notification toggle
1071
+ document.getElementById('enableNotifications').addEventListener('change', function() {
1072
+ const notificationStatus = document.getElementById('notificationStatus');
1073
+ if (this.checked) {
1074
+ notificationStatus.innerHTML = `
1075
+ <div class="alert alert-success notification-status">
1076
+ <i class="fas fa-check-circle"></i> WhatsApp notifications are enabled.
1077
+ </div>
1078
+ `;
1079
+ // Regenerate schedule to enable notifications
1080
+ if (allScheduleData && Object.keys(allScheduleData).length > 0) {
1081
+ document.getElementById('generateTimetableBtn').click();
1082
+ }
1083
+ } else {
1084
+ notificationStatus.innerHTML = `
1085
+ <div class="alert alert-warning notification-status">
1086
+ <i class="fas fa-bell-slash"></i> WhatsApp notifications are disabled.
1087
+ </div>
1088
+ `;
1089
+ }
1090
+ });
1091
+ </script>
1092
+ </body>
1093
+ </html>