rajkhanke commited on
Commit
45b8ea0
·
verified ·
1 Parent(s): 330f2be

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +541 -337
app.py CHANGED
@@ -17,506 +17,656 @@ from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, Tabl
17
 
18
  app = Flask(__name__)
19
 
20
- app.secret_key = '688ed745a74bdd7ac238f5b50f4104fb87d6774b8b0a4e06e7e18ac5ed0fa31c'
 
 
21
  upload_base = os.getenv('UPLOAD_DIR', './uploads')
22
- upload_folder = os.path.join(upload_base, 'uploads')
23
 
24
  app.config['UPLOAD_FOLDER'] = upload_folder
25
  os.makedirs(upload_folder, exist_ok=True)
26
 
27
  # Twilio Configuration
28
- ACCOUNT_SID = 'AC490e071f8d01bf0df2f03d086c788d87'
29
- AUTH_TOKEN = '224b23b950ad5a4052aba15893fdf083'
30
- TWILIO_FROM = 'whatsapp:+14155238886'
31
- TWILIO_TO = 'whatsapp:+917559355282' # Hardcoded number as requested
 
32
 
33
  # Initialize Twilio client
34
- twilio_client = Client(ACCOUNT_SID, AUTH_TOKEN)
 
 
 
 
 
35
 
36
  # Gemini API Configuration
37
- genai.configure(api_key="AIzaSyD54ejbjVIVa-F3aD_Urnp8m1EFLUGR__I")
 
 
 
 
 
 
 
 
 
 
38
 
39
  generation_config = {
40
- "temperature": 1,
41
- "top_p": 0.95,
42
- "top_k": 40,
43
- "max_output_tokens": 8192,
44
  }
45
 
46
- model = genai.GenerativeModel(
47
- model_name="gemini-2.0-flash",
48
- generation_config=generation_config,
49
- )
 
 
 
 
 
 
 
 
 
 
 
 
50
 
51
- # Patient database (in-memory for demo, use a real database in production)
52
- patients_db = {}
53
 
 
 
 
54
 
55
  def extract_text_from_pdf(pdf_file):
56
  """Extract text from uploaded PDF file"""
57
  try:
 
 
58
  pdf_reader = PyPDF2.PdfReader(pdf_file)
59
  text = ""
60
  for page in pdf_reader.pages:
61
- text += page.extract_text()
 
 
62
  return text.strip()
63
  except Exception as e:
64
  print(f"Error extracting PDF text: {e}")
65
  return ""
66
 
67
-
68
  def extract_care_plan_format(pdf_text):
69
- """Extract the care plan format from PDF text"""
70
- try:
71
- # Find sections and their content in the PDF using regex
72
- sections = re.findall(
73
- r'([A-Z][A-Z\s]+)[:|\n]((?:(?!\n[A-Z][A-Z\s]+[:|\n]).)*)',
74
- pdf_text,
75
- re.DOTALL
76
- )
77
-
78
- if sections:
79
- format_template = ""
80
- for section, content in sections:
81
- format_template += f"{section.strip()}:\n"
82
- # Extract bullet points if they exist and remove any asterisks
83
- bullets = re.findall(r'[-•*]\s*(.*?)(?=[-•*]|\n|$)', content)
84
- if bullets:
85
- for bullet in bullets:
86
- format_template += f"- {bullet.strip()}\n"
87
- format_template += "\n"
88
- return format_template
89
- return None
90
- except Exception as e:
91
- print(f"Error extracting format: {e}")
92
  return None
93
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
 
95
  def determine_patient_status(original_plan, updated_plan, feedback):
96
  """Determine patient status based on care plan comparison and feedback"""
97
- # Keywords indicating condition worsening
 
 
 
 
 
98
  emergency_keywords = [
99
- "severe chest pain", "heart attack", "shortness of breath",
100
- "dizziness", "loss of consciousness", "extreme pain",
101
  "sudden weakness", "confusion", "slurred speech", "severe headache",
102
- "difficulty breathing", "severe shortness of breath", "wheezing",
103
  "severe abdominal pain", "persistent vomiting", "uncontrolled bleeding",
104
  "severe allergic reaction", "anaphylaxis", "immediate medical attention",
105
  "emergency", "call 911", "urgent care", "hospital", "critical", "ambulance",
106
  "collapsed", "unconscious", "unresponsive", "stroke", "seizure", "convulsion",
107
- "suffocating", "not breathing", "blue lips", "blue face", "cardiac arrest"
 
 
108
  ]
109
-
110
  deteriorating_keywords = [
111
  "worsening", "increased pain", "not improving", "deteriorating",
112
  "elevated", "higher", "more frequent", "concerning", "monitor closely",
113
  "decline", "decreased function", "less able", "more difficult", "worse",
114
  "getting worse", "aggravated", "intensified", "escalating", "degrading",
115
- "falling", "weakening", "relapse", "recurrence", "regressing", "not responding",
116
  "increased symptoms", "more severe", "progressing", "progressive", "complicated",
117
- "concerning development", "adverse change", "unstable", "needs attention"
 
118
  ]
119
-
120
  improvement_keywords = [
121
  "improving", "better", "reduced", "lower", "less pain", "increased function",
122
  "healing", "recovery", "progress", "stable", "maintained", "consistent",
123
  "well-controlled", "responsive", "good progress", "getting better", "positive",
124
  "improved", "enhancement", "advancement", "resolving", "resolved", "recovering",
125
  "normalized", "normal range", "responding well", "responding positively",
126
- "effective treatment", "successful treatment", "managed well", "under control"
 
127
  ]
128
-
129
- original_plan_lower = original_plan.lower() if original_plan else ""
130
- updated_plan_lower = updated_plan.lower()
131
- feedback_lower = feedback.lower()
132
-
133
- # Check for emergency keywords first - higher priority in feedback
134
  if any(keyword in feedback_lower for keyword in emergency_keywords):
135
  return "emergency"
136
-
137
- # Check for emergency keywords in updated plan
138
  if any(keyword in updated_plan_lower for keyword in emergency_keywords):
139
  return "emergency"
140
-
141
  # Check for deteriorating keywords
142
  if any(keyword in feedback_lower for keyword in deteriorating_keywords):
143
  return "deteriorating"
144
-
145
- # Further check for deteriorating indicators in updated plan
146
  if any(keyword in updated_plan_lower for keyword in deteriorating_keywords):
147
  return "deteriorating"
148
-
149
  # Check for improvement keywords
150
  if any(keyword in feedback_lower for keyword in improvement_keywords):
151
  return "improving"
152
-
153
- # Further check for improvement indicators in updated plan
154
  if any(keyword in updated_plan_lower for keyword in improvement_keywords):
155
  return "improving"
156
-
157
- # Default to stable if no clear indicators
 
 
 
 
 
 
 
158
  return "stable"
159
 
160
 
161
  def generate_care_plan_pdf(patient_info, care_plan_text, status):
162
  """Generate a PDF of the care plan with improved styling"""
163
  buffer = io.BytesIO()
164
-
165
  # Create a PDF document
166
- doc = SimpleDocTemplate(buffer, pagesize=letter)
 
 
 
167
  styles = getSampleStyleSheet()
168
-
169
  # Create custom styles
170
  title_style = ParagraphStyle(
171
  'Title',
172
  parent=styles['Heading1'],
173
- fontSize=20,
174
  alignment=1, # Center alignment
175
- spaceAfter=20,
176
- textColor=colors.navy
177
  )
178
-
179
  heading_style = ParagraphStyle(
180
  'Heading',
181
  parent=styles['Heading2'],
182
- fontSize=14,
183
- spaceAfter=10,
184
- spaceBefore=15,
185
- textColor=colors.darkblue
186
  )
187
-
188
  normal_style = ParagraphStyle(
189
  'Normal',
190
  parent=styles['Normal'],
191
  fontSize=11,
192
- spaceAfter=5,
193
- leading=14
 
194
  )
195
-
196
  bullet_style = ParagraphStyle(
197
  'Bullet',
198
  parent=styles['Normal'],
199
  fontSize=11,
200
  spaceAfter=3,
201
  leftIndent=20,
202
- leading=14
 
 
203
  )
204
-
205
  # Status color mapping
206
  status_colors = {
207
- 'emergency': colors.red,
208
- 'deteriorating': colors.orange,
209
- 'improving': colors.green,
210
- 'stable': colors.blue
211
  }
212
-
213
- status_style = ParagraphStyle(
214
- 'Status',
215
- parent=styles['Heading2'],
216
- fontSize=14,
217
- spaceBefore=10,
218
- spaceAfter=10,
219
- textColor=status_colors.get(status, colors.black)
220
- )
221
-
222
  # Build the document content
223
  story = []
224
-
225
  # Title
226
  story.append(Paragraph("Patient Care Plan", title_style))
227
- story.append(Spacer(1, 10))
228
-
229
  # Status indicator
230
- status_text = {
231
  'emergency': "EMERGENCY - IMMEDIATE ACTION REQUIRED",
232
  'deteriorating': "HIGH RISK - Condition Deteriorating",
233
  'improving': "LOW RISK - Condition Improving",
234
- 'stable': "STABLE - Maintain Current Care"
 
235
  }
236
-
237
- story.append(Paragraph(status_text.get(status, "Status: Unknown"), status_style))
238
- story.append(Spacer(1, 15))
239
-
 
 
240
  # Patient Information Table
241
  patient_data = [
242
- ["Patient Name:", patient_info.get('name', 'N/A')],
243
- ["Age:", patient_info.get('age', 'N/A')],
244
- ["Gender:", patient_info.get('gender', 'N/A')],
245
- ["Generated Date:", datetime.now().strftime("%Y-%m-%d %H:%M")]
246
  ]
247
-
248
- patient_table = Table(patient_data, colWidths=[120, 300])
249
  patient_table.setStyle(TableStyle([
250
- ('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
251
  ('BACKGROUND', (0, 0), (0, -1), colors.lightgrey),
252
- ('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'),
253
- ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
 
 
 
254
  ('LEFTPADDING', (0, 0), (-1, -1), 6),
255
  ('RIGHTPADDING', (0, 0), (-1, -1), 6),
256
- ('TOPPADDING', (0, 0), (-1, -1), 4),
257
- ('BOTTOMPADDING', (0, 0), (-1, -1), 4),
258
  ]))
259
-
260
  story.append(patient_table)
261
- story.append(Spacer(1, 20))
262
-
263
  # Care Plan Content
264
  story.append(Paragraph("Care Plan Details:", heading_style))
265
  story.append(Spacer(1, 10))
266
-
267
- # Process care plan text by sections
268
- sections = re.findall(r'([A-Z][A-Z\s]+):(.*?)(?=\n[A-Z][A-Z\s]+:|$)', care_plan_text, re.DOTALL)
269
-
270
- if not sections:
271
- # If no sections found, add the entire text as a paragraph
272
- story.append(Paragraph(care_plan_text, normal_style))
273
- else:
274
- for section_title, section_content in sections:
275
- story.append(Paragraph(section_title + ":", heading_style))
276
-
277
- # Process bullet points if they exist
278
- bullet_points = re.findall(r'[-•]\s*(.*?)(?=[-•]|\n|$)', section_content.strip())
279
-
280
- if bullet_points:
281
- for point in bullet_points:
282
- if point.strip():
283
- story.append(Paragraph(f"• {point.strip()}", bullet_style))
284
- else:
285
- # If no bullet points, add the content as a paragraph
286
- cleaned_content = section_content.strip()
287
- if cleaned_content:
288
- story.append(Paragraph(cleaned_content, normal_style))
289
-
290
- story.append(Spacer(1, 5))
291
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
292
  # Build the PDF
293
  doc.build(story)
294
  buffer.seek(0)
295
  return buffer
296
 
297
 
298
- def send_whatsapp_care_plan(patient_name, care_plan_text, status):
299
  """Send care plan via WhatsApp using Twilio with improved formatting"""
 
 
 
 
300
  try:
301
- # Status emoji
302
  status_emoji = {
303
  'emergency': "🚨 EMERGENCY",
304
  'deteriorating': "⚠️ HIGH RISK",
305
  'improving': "✅ IMPROVING",
306
  'stable': "🟦 STABLE"
307
  }
308
-
309
- # Format message for WhatsApp with improved structure
310
- message = f"*Care Plan Update for {patient_name}*\n\n"
311
- message += f"*Status: {status_emoji.get(status, 'Unknown')}*\n\n"
312
-
313
- # Extract and format sections
314
- sections = re.findall(r'([A-Z][A-Z\s]+):(.*?)(?=\n[A-Z][A-Z\s]+:|$)', care_plan_text, re.DOTALL)
315
-
316
- if not sections:
317
- # If no sections found, add the entire text
318
- message += care_plan_text
319
- else:
320
- for section_title, section_content in sections:
321
- message += f"*{section_title}:*\n"
322
-
323
- # Process bullet points if they exist
324
- bullet_points = re.findall(r'[-•]\s*(.*?)(?=[-•]|\n|$)', section_content.strip())
325
-
326
- if bullet_points:
327
- for point in bullet_points:
328
- if point.strip():
329
- message += f"• {point.strip()}\n"
330
- else:
331
- # If no bullet points, add the content as a paragraph
332
- cleaned_content = section_content.strip()
333
- if cleaned_content:
334
- message += f"{cleaned_content}\n"
335
-
336
- message += "\n"
337
-
338
  # Send WhatsApp message using hardcoded number
339
- twilio_client.messages.create(
340
  from_=TWILIO_FROM,
341
  body=message,
342
  to=TWILIO_TO
343
  )
 
344
  return True
345
  except Exception as e:
346
  print(f"Error sending WhatsApp message: {e}")
 
347
  return False
348
 
349
 
350
  @app.route('/')
351
  def index():
 
 
 
 
 
 
352
  return render_template('index.html')
353
 
354
 
355
  @app.route('/switch_role', methods=['POST'])
356
  def switch_role():
357
  role = request.form.get('role')
358
- session['role'] = role
359
- return jsonify({'success': True, 'role': role})
 
 
360
 
361
 
362
  @app.route('/doctor_dashboard')
363
  def doctor_dashboard():
 
 
364
  return render_template('doctor_dashboard.html')
365
 
366
 
 
 
367
  @app.route('/patient_details/<patient_id>')
368
  def patient_details(patient_id):
369
- patient = patients_db.get(patient_id)
370
- if not patient:
371
- return redirect('/doctor_dashboard')
372
- return render_template('doctor_dashboard.html', selected_patient=patient)
 
 
 
 
 
 
373
 
374
 
375
  @app.route('/submit_feedback', methods=['POST'])
376
  def submit_feedback():
 
 
 
377
  try:
378
- # Get patient information
379
- name = request.form.get('name', '')
380
- age = request.form.get('age', '')
381
- gender = request.form.get('gender', '')
382
  feedback = request.form.get('feedback', '')
383
-
384
  care_plan_text = ""
385
  care_plan_format = None
386
-
 
387
  if 'care_plan_pdf' in request.files:
388
  pdf_file = request.files['care_plan_pdf']
389
- if pdf_file.filename != '':
 
390
  care_plan_text = extract_text_from_pdf(pdf_file)
391
- care_plan_format = extract_care_plan_format(care_plan_text)
392
-
 
 
393
  # If no format is found in the PDF, use a default format
394
- if not care_plan_format:
 
395
  care_plan_format = """
 
 
 
 
 
396
  ASSESSMENT:
397
- - [Assessment details]
 
398
  DAILY CARE PLAN:
399
  Morning:
400
- - [Morning activities]
401
  Afternoon:
402
- - [Afternoon activities]
403
  Evening:
404
- - [Evening activities]
 
 
 
405
  MEDICATIONS:
406
- - [Medication details]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
407
  ADDITIONAL RECOMMENDATIONS:
408
- - [Recommendations]
 
409
  FOLLOW-UP:
410
- - [Follow-up details]
411
  """
412
-
413
- # Define emergency keywords to check for severe symptoms
414
- emergency_keywords = [
415
- # Cardiovascular
 
416
  "severe chest pain", "heart attack", "shortness of breath",
417
  "dizziness", "loss of consciousness", "extreme pain",
418
-
419
- # Neurological
420
  "sudden weakness", "confusion", "slurred speech", "severe headache",
421
-
422
- # Respiratory
423
- "difficulty breathing", "severe shortness of breath", "wheezing", "respiratory distress",
424
-
425
- # Gastrointestinal
426
  "severe abdominal pain", "persistent vomiting", "uncontrolled bleeding",
427
-
428
- # Others
429
- "severe allergic reaction", "anaphylaxis"
 
 
 
430
  ]
431
-
432
  feedback_lower = feedback.lower()
433
- is_emergency = any(keyword in feedback_lower for keyword in emergency_keywords)
434
-
435
- if is_emergency:
436
- # If emergency symptoms are detected, instruct immediate emergency response
437
- emergency_message = (
 
 
 
 
 
 
 
 
438
  "ASSESSMENT:\n"
439
- "- Emergency symptoms detected. Immediate medical attention required.\n\n"
440
  "EMERGENCY ACTION PLAN:\n"
441
- "- Call emergency services immediately at 104/108/109/112.\n"
442
  "- Do not delay seeking medical help.\n"
443
- "- Do not eat or drink anything until evaluated by medical professionals.\n\n"
 
 
 
 
444
  "FOLLOW-UP:\n"
445
- "- Immediate hospitalization may be required.\n"
446
- "- Contact your primary physician as soon as possible.\n"
 
447
  )
448
-
449
- # Create a patient record with emergency status
450
- patient_id = str(uuid.uuid4())
451
- patients_db[patient_id] = {
452
- 'id': patient_id,
453
- 'name': name,
454
- 'age': age,
455
- 'gender': gender,
456
- 'feedback': feedback,
457
- 'original_plan': care_plan_text,
458
- 'updated_plan': emergency_message,
459
- 'status': 'emergency',
460
- 'timestamp': datetime.now().strftime("%Y-%m-%d %H:%M:%S")
461
- }
462
-
463
- # Generate PDF for emergency instructions
464
- patient_info = {
465
- 'name': name,
466
- 'age': age,
467
- 'gender': gender
468
- }
469
- pdf_buffer = generate_care_plan_pdf(patient_info, emergency_message, 'emergency')
470
- pdf_base64 = base64.b64encode(pdf_buffer.getvalue()).decode('utf-8')
471
-
472
- # Send emergency WhatsApp message
473
- send_whatsapp_care_plan(name, emergency_message, 'emergency')
474
-
475
- return jsonify({
476
- 'success': True,
477
- 'updated_plan': emergency_message,
478
- 'patient_id': patient_id,
479
- 'status': 'emergency',
480
- 'pdf_data': pdf_base64
481
- })
482
-
483
- # Prepare a prompt for Gemini AI for a perfect, attractively formatted day care plan.
484
- prompt = f"""
485
  Patient Information:
486
  Name: {name}
487
  Age: {age}
488
  Gender: {gender}
489
- Patient Care Plan Update Request:
490
- Current Symptoms and Feedback: {feedback}
491
- Current Care Plan (extracted from PDF): {care_plan_text}
492
- Based on the patient's feedback and current care plan, please provide an updated, perfect daily care plan in the exact following format:
 
 
 
 
 
 
 
 
 
 
 
 
493
  {care_plan_format}
494
- Please ensure the following:
495
- - The response is well-structured with clear section headings.
496
- - Use bullet points for list items.
497
- - Format the text attractively and professionally.
498
- - Be specific with recommendations based on the patient's feedback.
499
- - Include specific times for medications if applicable.
500
- - Provide clear follow-up instructions.
501
  """
502
- # Get response from Gemini AI
503
- response = model.generate_content(prompt)
504
- # Remove any asterisk symbols from the response text
505
- updated_plan = response.text.replace("*", "")
506
-
507
- # Determine patient status
508
- status = determine_patient_status(care_plan_text, updated_plan, feedback)
509
-
510
- # Generate PDF for downloading
511
- patient_info = {
512
- 'name': name,
513
- 'age': age,
514
- 'gender': gender
515
- }
516
- pdf_buffer = generate_care_plan_pdf(patient_info, updated_plan, status)
517
- pdf_base64 = base64.b64encode(pdf_buffer.getvalue()).decode('utf-8')
518
-
519
- # Create a new patient record or update existing
 
 
 
 
 
520
  patient_id = str(uuid.uuid4())
521
  patients_db[patient_id] = {
522
  'id': patient_id,
@@ -524,77 +674,118 @@ Please ensure the following:
524
  'age': age,
525
  'gender': gender,
526
  'feedback': feedback,
527
- 'original_plan': care_plan_text,
528
- 'updated_plan': updated_plan,
529
  'status': status,
530
  'timestamp': datetime.now().strftime("%Y-%m-%d %H:%M:%S")
531
  }
532
-
 
 
 
 
 
 
 
 
 
 
 
 
533
  # Send care plan via WhatsApp
534
- whatsapp_sent = send_whatsapp_care_plan(name, updated_plan, status)
535
-
 
 
 
 
 
 
536
  return jsonify({
537
  'success': True,
538
- 'updated_plan': updated_plan,
539
  'pdf_data': pdf_base64,
540
- 'patient_id': patient_id,
541
  'status': status,
542
  'whatsapp_sent': whatsapp_sent
543
  })
544
-
545
  except Exception as e:
546
- print(f"Error: {str(e)}")
 
 
 
547
  return jsonify({
548
  'success': False,
549
- 'error': str(e)
550
- })
551
 
552
 
553
  @app.route('/download_pdf/<patient_id>')
554
  def download_pdf(patient_id):
 
 
 
555
  try:
556
- if patient_id not in patients_db:
557
- return "Patient not found", 404
558
-
559
- patient = patients_db[patient_id]
560
-
561
- # Use the patient's stored information to generate the PDF
562
- pdf_buffer = generate_care_plan_pdf({
563
- 'name': patient['name'],
564
- 'age': patient['age'],
565
- 'gender': patient['gender']
566
- }, patient['updated_plan'], patient['status'])
567
-
 
 
 
 
 
 
 
568
  pdf_buffer.seek(0) # Ensure buffer is at the start
569
-
 
 
 
 
 
570
  return send_file(
571
  pdf_buffer,
572
  as_attachment=True,
573
- download_name=f"care_plan_{patient['name'].replace(' ', '_')}.pdf",
574
  mimetype='application/pdf'
575
  )
576
  except Exception as e:
577
- print(f"PDF Download Error: {str(e)}")
 
 
578
  return f"Error generating PDF: {str(e)}", 500
579
 
580
 
581
  @app.route('/get_emergency_notifications')
582
  def get_emergency_notifications():
583
  # Filter patients with emergency status
584
- emergency_patients = [p for p in patients_db.values() if p['status'] == 'emergency']
585
-
 
 
 
586
  notifications = []
587
  for patient in emergency_patients:
588
  notifications.append({
589
  'patient_id': patient['id'],
590
- 'patient_name': patient['name'],
591
- 'patient_age': patient['age'],
592
- 'patient_gender': patient['gender'],
593
- 'patient_feedback': patient['feedback'],
594
- 'timestamp': patient['timestamp'],
595
- 'status': patient['status']
596
  })
597
-
598
  return jsonify({
599
  'success': True,
600
  'notifications': notifications
@@ -604,22 +795,35 @@ def get_emergency_notifications():
604
  @app.route('/get_patients')
605
  def get_patients():
606
  # Return all patients for the doctor dashboard
 
 
 
 
 
607
  return jsonify({
608
  'success': True,
609
- 'patients': list(patients_db.values())
610
  })
611
 
612
 
613
  @app.route('/get_patient/<patient_id>')
614
  def get_patient(patient_id):
615
- if patient_id not in patients_db:
616
- return jsonify({'success': False, 'error': 'Patient not found'})
617
-
 
 
 
 
 
 
 
618
  return jsonify({
619
  'success': True,
620
- 'patient': patients_db[patient_id]
621
  })
622
 
623
 
624
  if __name__ == '__main__':
625
- app.run(debug=True)
 
 
17
 
18
  app = Flask(__name__)
19
 
20
+ # Use a strong, unique secret key
21
+ app.secret_key = os.getenv('SECRET_KEY', '688ed745a74bdd7ac238f5b50f4104fb87d6774b8b0a4e06e7e18ac5ed0fa31c') # CHANGE THIS IN PRODUCTION
22
+
23
  upload_base = os.getenv('UPLOAD_DIR', './uploads')
24
+ upload_folder = os.path.join(upload_base, 'pdfs') # Changed folder name for clarity
25
 
26
  app.config['UPLOAD_FOLDER'] = upload_folder
27
  os.makedirs(upload_folder, exist_ok=True)
28
 
29
  # Twilio Configuration
30
+ # IMPORTANT: Use environment variables for sensitive information like SID and Auth Token
31
+ ACCOUNT_SID = os.getenv('TWILIO_ACCOUNT_SID', 'AC490e071f8d01bf0df2f03d086c788d87')
32
+ AUTH_TOKEN = os.getenv('TWILIO_AUTH_TOKEN', '224b23b950ad5a4052aba15893fdf083')
33
+ TWILIO_FROM = os.getenv('TWILIO_FROM_NUMBER', 'whatsapp:+14155238886')
34
+ TWILIO_TO = os.getenv('TWILIO_TO_NUMBER', 'whatsapp:+917559355282') # Hardcoded number as requested
35
 
36
  # Initialize Twilio client
37
+ try:
38
+ twilio_client = Client(ACCOUNT_SID, AUTH_TOKEN)
39
+ print("Twilio client initialized successfully.")
40
+ except Exception as e:
41
+ print(f"Error initializing Twilio client: {e}")
42
+ twilio_client = None # Handle cases where Twilio initialization fails
43
 
44
  # Gemini API Configuration
45
+ # IMPORTANT: Use environment variables for sensitive information like API keys
46
+ GENAI_API_KEY = os.getenv('GENAI_API_KEY', "AIzaSyD54ejbjVIVa-F3aD_Urnp8m1EFLUGR__I") # CHANGE THIS KEY
47
+ try:
48
+ genai.configure(api_key=GENAI_API_KEY)
49
+ # Simple test to check connectivity
50
+ list(genai.list_models())
51
+ print("Gemini API configured successfully.")
52
+ except Exception as e:
53
+ print(f"Error configuring Gemini API: {e}")
54
+ genai = None # Handle cases where API configuration fails
55
+
56
 
57
  generation_config = {
58
+ "temperature": 0.8, # Slightly reduced temp for more stable output
59
+ "top_p": 0.9, # Adjusted top_p
60
+ "top_k": 30, # Adjusted top_k
61
+ "max_output_tokens": 4096, # Reduced token limit slightly if needed, or increase if plans are long
62
  }
63
 
64
+ # Use a more capable model if available and affordable
65
+ # "gemini-1.5-pro" or "gemini-1.5-flash" are better choices than gemini-2.0-flash if 2.0 means an older preview
66
+ # Let's assume gemini-2.0-flash is a valid, accessible model alias
67
+ model = None
68
+ if genai:
69
+ try:
70
+ model = genai.GenerativeModel(
71
+ model_name="gemini-1.5-flash-latest", # Recommended model alias
72
+ generation_config=generation_config,
73
+ )
74
+ # Test model generation
75
+ # model.generate_content("Hello")
76
+ print(f"Using Gemini model: {model.model_name}")
77
+ except Exception as e:
78
+ print(f"Error initializing Gemini model: {e}")
79
+ model = None # Handle cases where model initialization fails
80
 
 
 
81
 
82
+ # Patient database (in-memory for demo - WILL BE RESET ON SERVER RESTART)
83
+ # For persistence, replace this with a database (SQLite, PostgreSQL, etc.)
84
+ patients_db = {}
85
 
86
  def extract_text_from_pdf(pdf_file):
87
  """Extract text from uploaded PDF file"""
88
  try:
89
+ # Ensure file pointer is at the beginning
90
+ pdf_file.seek(0)
91
  pdf_reader = PyPDF2.PdfReader(pdf_file)
92
  text = ""
93
  for page in pdf_reader.pages:
94
+ page_text = page.extract_text()
95
+ if page_text:
96
+ text += page_text + "\n" # Add newline between pages
97
  return text.strip()
98
  except Exception as e:
99
  print(f"Error extracting PDF text: {e}")
100
  return ""
101
 
 
102
  def extract_care_plan_format(pdf_text):
103
+ """Extract a general format template from PDF text by identifying common section headers."""
104
+ if not pdf_text:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  return None
106
 
107
+ # Look for lines that seem like headers (e.g., capitalized words ending with :, followed by content)
108
+ # This regex is an attempt to capture common patterns but might not be perfect for all PDFs.
109
+ # It looks for uppercase words (potentially with spaces), followed by a colon, then captures subsequent lines until another similar pattern or end.
110
+ sections = re.findall(
111
+ r'^([A-Z][A-Z\s]*):(?:\s*\n)?((?:(?!\n[A-Z][A-Z\s]*:).*\n?)*)',
112
+ pdf_text,
113
+ re.MULTILINE | re.DOTALL
114
+ )
115
+
116
+ if not sections:
117
+ # Fallback: If strict section matching fails, try to find prominent lines as potential headers
118
+ potential_headers = re.findall(r'^[A-Z][A-Za-z\s,]*[.:]?$', pdf_text, re.MULTILINE)
119
+ if potential_headers:
120
+ # Just list the potential headers as a basic format guess
121
+ format_template = "\n".join([f"{header.strip()}:" for header in set(potential_headers) if header.strip()]) + "\n"
122
+ print(f"Extracted potential headers (fallback): {list(set(potential_headers))}")
123
+ return format_template if format_template.strip() else None
124
+ print("No sections or potential headers found.")
125
+ return None # No recognizable format found
126
+
127
+ format_template = ""
128
+ for section_title, _ in sections:
129
+ format_template += f"{section_title.strip()}:\n- [Details]\n" # Use a placeholder bullet structure
130
+ print(f"Extracted sections: {[s[0].strip() for s in sections]}")
131
+ return format_template if format_template.strip() else None
132
+
133
 
134
  def determine_patient_status(original_plan, updated_plan, feedback):
135
  """Determine patient status based on care plan comparison and feedback"""
136
+
137
+ feedback_lower = feedback.lower()
138
+ original_plan_lower = original_plan.lower() if original_plan else ""
139
+ updated_plan_lower = updated_plan.lower()
140
+
141
+ # Comprehensive keywords indicating condition worsening or emergency
142
  emergency_keywords = [
143
+ "severe chest pain", "heart attack", "sudden shortness of breath",
144
+ "difficulty breathing", "loss of consciousness", "extreme pain",
145
  "sudden weakness", "confusion", "slurred speech", "severe headache",
 
146
  "severe abdominal pain", "persistent vomiting", "uncontrolled bleeding",
147
  "severe allergic reaction", "anaphylaxis", "immediate medical attention",
148
  "emergency", "call 911", "urgent care", "hospital", "critical", "ambulance",
149
  "collapsed", "unconscious", "unresponsive", "stroke", "seizure", "convulsion",
150
+ "suffocating", "not breathing", "blue lips", "blue face", "cardiac arrest",
151
+ "high fever", "signs of shock", "severe dehydration", "acute change",
152
+ "unstable vitals", "rapidly worsening"
153
  ]
154
+
155
  deteriorating_keywords = [
156
  "worsening", "increased pain", "not improving", "deteriorating",
157
  "elevated", "higher", "more frequent", "concerning", "monitor closely",
158
  "decline", "decreased function", "less able", "more difficult", "worse",
159
  "getting worse", "aggravated", "intensified", "escalating", "degrading",
160
+ "weakening", "relapse", "recurrence", "regressing", "not responding",
161
  "increased symptoms", "more severe", "progressing", "progressive", "complicated",
162
+ "adverse change", "unstable", "needs attention", "spike in", "significant increase",
163
+ "new symptoms", "trouble with", "reduced appetite", "difficulty sleeping"
164
  ]
165
+
166
  improvement_keywords = [
167
  "improving", "better", "reduced", "lower", "less pain", "increased function",
168
  "healing", "recovery", "progress", "stable", "maintained", "consistent",
169
  "well-controlled", "responsive", "good progress", "getting better", "positive",
170
  "improved", "enhancement", "advancement", "resolving", "resolved", "recovering",
171
  "normalized", "normal range", "responding well", "responding positively",
172
+ "effective treatment", "successful treatment", "managed well", "under control",
173
+ "symptoms decreased", "feeling stronger", "better sleep", "increased appetite"
174
  ]
175
+
176
+ # Check for emergency keywords first (highest priority), primarily in feedback as it's direct input
 
 
 
 
177
  if any(keyword in feedback_lower for keyword in emergency_keywords):
178
  return "emergency"
179
+ # Then check updated plan, as AI might interpret feedback and include emergency terms
 
180
  if any(keyword in updated_plan_lower for keyword in emergency_keywords):
181
  return "emergency"
182
+
183
  # Check for deteriorating keywords
184
  if any(keyword in feedback_lower for keyword in deteriorating_keywords):
185
  return "deteriorating"
186
+ # Check updated plan for deteriorating indicators
 
187
  if any(keyword in updated_plan_lower for keyword in deteriorating_keywords):
188
  return "deteriorating"
189
+
190
  # Check for improvement keywords
191
  if any(keyword in feedback_lower for keyword in improvement_keywords):
192
  return "improving"
193
+ # Check updated plan for improvement indicators
 
194
  if any(keyword in updated_plan_lower for keyword in improvement_keywords):
195
  return "improving"
196
+
197
+ # Compare original vs updated plan length/content as a weak signal (optional, can be removed if unreliable)
198
+ # if len(updated_plan) < len(original_plan) * 0.8 and any(k in updated_plan_lower for k in improvement_keywords):
199
+ # return "improving"
200
+ # if len(updated_plan) > len(original_plan) * 1.2 and any(k in updated_plan_lower for k in deteriorating_keywords):
201
+ # return "deteriorating"
202
+
203
+
204
+ # Default to stable if no clear indicators of change are found
205
  return "stable"
206
 
207
 
208
  def generate_care_plan_pdf(patient_info, care_plan_text, status):
209
  """Generate a PDF of the care plan with improved styling"""
210
  buffer = io.BytesIO()
211
+
212
  # Create a PDF document
213
+ doc = SimpleDocTemplate(buffer, pagesize=letter,
214
+ leftMargin=72, rightMargin=72,
215
+ topMargin=72, bottomMargin=72) # Add margins
216
+
217
  styles = getSampleStyleSheet()
218
+
219
  # Create custom styles
220
  title_style = ParagraphStyle(
221
  'Title',
222
  parent=styles['Heading1'],
223
+ fontSize=22, # Slightly larger title
224
  alignment=1, # Center alignment
225
+ spaceAfter=25, # More space after title
226
+ textColor=colors.HexColor("#4e73df") # Primary color
227
  )
228
+
229
  heading_style = ParagraphStyle(
230
  'Heading',
231
  parent=styles['Heading2'],
232
+ fontSize=15, # Slightly larger headings
233
+ spaceAfter=8, # Less space after heading for tighter look
234
+ spaceBefore=18, # More space before heading
235
+ textColor=colors.HexColor("#1cc88a") # Secondary color
236
  )
237
+
238
  normal_style = ParagraphStyle(
239
  'Normal',
240
  parent=styles['Normal'],
241
  fontSize=11,
242
+ spaceAfter=6, # More space after paragraphs
243
+ leading=14,
244
+ textColor=colors.HexColor("#5a5c69") # Dark color
245
  )
246
+
247
  bullet_style = ParagraphStyle(
248
  'Bullet',
249
  parent=styles['Normal'],
250
  fontSize=11,
251
  spaceAfter=3,
252
  leftIndent=20,
253
+ leading=14,
254
+ bulletIndent=10, # Space between bullet and text
255
+ textColor=colors.HexColor("#5a5c69") # Dark color
256
  )
257
+
258
  # Status color mapping
259
  status_colors = {
260
+ 'emergency': colors.HexColor("#e74a3b"), # Danger color
261
+ 'deteriorating': colors.HexColor("#f6c23e"), # Warning color
262
+ 'improving': colors.HexColor("#1cc88a"), # Success color
263
+ 'stable': colors.HexColor("#36b9cc") # Info color
264
  }
265
+
266
+ status_text_styles = {
267
+ 'emergency': ParagraphStyle('StatusEmergency', parent=styles['Heading2'], fontSize=16, spaceBefore=10, spaceAfter=15, textColor=status_colors['emergency'], alignment=1, fontName='Helvetica-Bold'),
268
+ 'deteriorating': ParagraphStyle('StatusDeteriorating', parent=styles['Heading2'], fontSize=15, spaceBefore=10, spaceAfter=15, textColor=status_colors['deteriorating'], alignment=1, fontName='Helvetica-Bold'),
269
+ 'improving': ParagraphStyle('StatusImproving', parent=styles['Heading2'], fontSize=15, spaceBefore=10, spaceAfter=15, textColor=status_colors['improving'], alignment=1, fontName='Helvetica-Bold'),
270
+ 'stable': ParagraphStyle('StatusStable', parent=styles['Heading2'], fontSize=15, spaceBefore=10, spaceAfter=15, textColor=status_colors['stable'], alignment=1, fontName='Helvetica-Bold'),
271
+ 'unknown': ParagraphStyle('StatusUnknown', parent=styles['Heading2'], fontSize=15, spaceBefore=10, spaceAfter=15, textColor=colors.black, alignment=1, fontName='Helvetica-Bold'),
272
+ }
273
+
274
+
275
  # Build the document content
276
  story = []
277
+
278
  # Title
279
  story.append(Paragraph("Patient Care Plan", title_style))
280
+
 
281
  # Status indicator
282
+ status_map_text = {
283
  'emergency': "EMERGENCY - IMMEDIATE ACTION REQUIRED",
284
  'deteriorating': "HIGH RISK - Condition Deteriorating",
285
  'improving': "LOW RISK - Condition Improving",
286
+ 'stable': "STABLE - Maintain Current Care",
287
+ 'unknown': "Status: Unknown"
288
  }
289
+ current_status_text = status_map_text.get(status, 'unknown')
290
+ current_status_style = status_text_styles.get(status, status_text_styles['unknown'])
291
+
292
+ story.append(Paragraph(current_status_text, current_status_style))
293
+
294
+
295
  # Patient Information Table
296
  patient_data = [
297
+ [Paragraph("<b>Patient Name:</b>", normal_style), Paragraph(patient_info.get('name', 'N/A'), normal_style)],
298
+ [Paragraph("<b>Age:</b>", normal_style), Paragraph(patient_info.get('age', 'N/A'), normal_style)],
299
+ [Paragraph("<b>Gender:</b>", normal_style), Paragraph(patient_info.get('gender', 'N/A'), normal_style)],
300
+ [Paragraph("<b>Generated Date:</b>", normal_style), Paragraph(datetime.now().strftime("%Y-%m-%d %H:%M"), normal_style)]
301
  ]
302
+
303
+ patient_table = Table(patient_data, colWidths=[150, 360]) # Adjusted column widths
304
  patient_table.setStyle(TableStyle([
 
305
  ('BACKGROUND', (0, 0), (0, -1), colors.lightgrey),
306
+ ('TEXTCOLOR', (0, 0), (0, -1), colors.black),
307
+ ('ALIGN', (0, 0), (-1, -1), 'LEFT'),
308
+ ('VALIGN', (0, 0), (-1, -1), 'TOP'),
309
+ ('INNERGRID', (0, 0), (-1, -1), 0.25, colors.grey), # Lighter grid
310
+ ('BOX', (0, 0), (-1, -1), 0.25, colors.grey),
311
  ('LEFTPADDING', (0, 0), (-1, -1), 6),
312
  ('RIGHTPADDING', (0, 0), (-1, -1), 6),
313
+ ('TOPPADDING', (0, 0), (-1, -1), 6), # Increased padding
314
+ ('BOTTOMPADDING', (0, 0), (-1, -1), 6), # Increased padding
315
  ]))
316
+
317
  story.append(patient_table)
318
+ story.append(Spacer(1, 25)) # More space after table
319
+
320
  # Care Plan Content
321
  story.append(Paragraph("Care Plan Details:", heading_style))
322
  story.append(Spacer(1, 10))
323
+
324
+ # Process care plan text by lines or sections for better formatting
325
+ lines = care_plan_text.strip().split('\n')
326
+ current_section = []
327
+ is_bullet_section = False
328
+
329
+ def add_section_to_story(section_lines, is_bullet):
330
+ if not section_lines:
331
+ return
332
+ text = "\n".join(section_lines).strip()
333
+ if not text:
334
+ return
335
+
336
+ # Attempt to identify potential sub-headings within content
337
+ sub_sections = re.split(r'\n(?=[A-Z][A-Z\s]*:)', text)
338
+
339
+ for i, sub_text in enumerate(sub_sections):
340
+ if i > 0: # Add space before subsequent sections
341
+ story.append(Spacer(1, 8))
342
+
343
+ # Check if the sub_text starts with a potential sub-heading
344
+ header_match = re.match(r'([A-Z][A-Z\s]*):', sub_text)
345
+ if header_match:
346
+ header_title = header_match.group(1).strip()
347
+ content_after_header = sub_text[header_match.end():].strip()
348
+ story.append(Paragraph(header_title + ":", heading_style)) # Use heading style for sub-sections too
349
+
350
+ # Process content after the potential header
351
+ content_lines = content_after_header.split('\n')
352
+ sub_is_bullet = any(re.match(r'^\s*[-•*]', line) for line in content_lines)
353
+
354
+ if sub_is_bullet:
355
+ sub_bullet_points = re.findall(r'^\s*[-•*]\s*(.*?)$', content_after_header, re.MULTILINE)
356
+ if sub_bullet_points:
357
+ for point in sub_bullet_points:
358
+ if point.strip():
359
+ story.append(Paragraph(f"• {point.strip()}", bullet_style))
360
+ else: # Fallback if regex misses some bullets
361
+ for line in content_lines:
362
+ cleaned_line = re.sub(r'^\s*[-•*]\s*', '', line).strip()
363
+ if cleaned_line:
364
+ story.append(Paragraph(f"• {cleaned_line}", bullet_style))
365
+
366
+ else: # Not a bulleted sub-section
367
+ cleaned_content = content_after_header.strip()
368
+ if cleaned_content:
369
+ story.append(Paragraph(cleaned_content, normal_style))
370
+
371
+ else: # If no sub-heading found at the start, treat as a single block of content
372
+ if is_bullet:
373
+ bullet_points = re.findall(r'^\s*[-•*]\s*(.*?)$', text, re.MULTILINE)
374
+ if bullet_points:
375
+ for point in bullet_points:
376
+ if point.strip():
377
+ story.append(Paragraph(f"• {point.strip()}", bullet_style))
378
+ else: # Fallback if regex misses some bullets
379
+ for line in section_lines:
380
+ cleaned_line = re.sub(r'^\s*[-•*]\s*', '', line).strip()
381
+ if cleaned_line:
382
+ story.append(Paragraph(f"• {cleaned_line}", bullet_style))
383
+ else:
384
+ story.append(Paragraph(text, normal_style))
385
+
386
+ for line in lines:
387
+ stripped_line = line.strip()
388
+ # Check if line is a potential section header
389
+ header_match = re.match(r'^([A-Z][A-Z\s]*):$', stripped_line)
390
+
391
+ if header_match:
392
+ # Add previous section before starting a new one
393
+ add_section_to_story(current_section, is_bullet_section)
394
+ story.append(Paragraph(stripped_line, heading_style)) # Add the new header
395
+ current_section = [] # Reset current section content
396
+ is_bullet_section = False # Assume new section is not bulleted initially
397
+ elif stripped_line.startswith('-') or stripped_line.startswith('•') or stripped_line.startswith('*'):
398
+ # Line is a bullet point, add it to the current section content
399
+ current_section.append(line)
400
+ is_bullet_section = True # Mark the current section as potentially bulleted
401
+ else:
402
+ # Line is regular content, add it to the current section
403
+ current_section.append(line)
404
+
405
+ # Add the last section
406
+ add_section_to_story(current_section, is_bullet_section)
407
+
408
+ # Add footer or timestamp if desired
409
+ story.append(Spacer(1, 20))
410
+ footer_style = ParagraphStyle('Footer', parent=styles['Normal'], fontSize=9, alignment=1, textColor=colors.grey)
411
+ story.append(Paragraph(f"Generated by Patient Care Management System on {datetime.now().strftime('%Y-%m-%d')}", footer_style))
412
+
413
+
414
  # Build the PDF
415
  doc.build(story)
416
  buffer.seek(0)
417
  return buffer
418
 
419
 
420
+ def send_whatsapp_care_plan(patient_info, care_plan_text, status):
421
  """Send care plan via WhatsApp using Twilio with improved formatting"""
422
+ if not twilio_client:
423
+ print("Twilio client not initialized. Cannot send WhatsApp message.")
424
+ return False
425
+
426
  try:
427
+ # Status emoji mapping
428
  status_emoji = {
429
  'emergency': "🚨 EMERGENCY",
430
  'deteriorating': "⚠️ HIGH RISK",
431
  'improving': "✅ IMPROVING",
432
  'stable': "🟦 STABLE"
433
  }
434
+
435
+ # Format message for WhatsApp including patient details and the full plan text
436
+ message = f"*Care Plan Update*\n\n"
437
+ message += f"*Patient Name:* {patient_info.get('name', 'N/A')}\n"
438
+ message += f"*Age:* {patient_info.get('age', 'N/A')}\n"
439
+ message += f"*Gender:* {patient_info.get('gender', 'N/A')}\n"
440
+ message += f"*Status:* {status_emoji.get(status, 'Unknown')}\n\n"
441
+ message += f"*Care Plan Details:*\n{care_plan_text.strip()}" # Include the full plan text
442
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
443
  # Send WhatsApp message using hardcoded number
444
+ message = twilio_client.messages.create(
445
  from_=TWILIO_FROM,
446
  body=message,
447
  to=TWILIO_TO
448
  )
449
+ print(f"WhatsApp message sent, SID: {message.sid}")
450
  return True
451
  except Exception as e:
452
  print(f"Error sending WhatsApp message: {e}")
453
+ # Twilio error handling can be more specific (e.g., check e.status)
454
  return False
455
 
456
 
457
  @app.route('/')
458
  def index():
459
+ # Determine current role from session or default to patient
460
+ role = session.get('role', 'patient')
461
+ if role == 'doctor':
462
+ # If role is doctor, redirect to doctor dashboard
463
+ return redirect(url_for('doctor_dashboard'))
464
+ # Otherwise, render the patient index page
465
  return render_template('index.html')
466
 
467
 
468
  @app.route('/switch_role', methods=['POST'])
469
  def switch_role():
470
  role = request.form.get('role')
471
+ if role in ['patient', 'doctor']:
472
+ session['role'] = role
473
+ return jsonify({'success': True, 'role': role})
474
+ return jsonify({'success': False, 'error': 'Invalid role'}), 400
475
 
476
 
477
  @app.route('/doctor_dashboard')
478
  def doctor_dashboard():
479
+ # This route will render the dashboard template
480
+ # The patient list will be loaded via AJAX by the JavaScript
481
  return render_template('doctor_dashboard.html')
482
 
483
 
484
+ # This route is not strictly needed anymore as patient details are loaded via AJAX on doctor_dashboard
485
+ # Keeping it for backward compatibility or direct linking if desired, but AJAX is preferred.
486
  @app.route('/patient_details/<patient_id>')
487
  def patient_details(patient_id):
488
+ print(f"Attempting to load patient details page for ID: {patient_id}")
489
+ print(f"Current patients_db keys: {patients_db.keys()}")
490
+ patient = patients_db.get(patient_id)
491
+ if not patient:
492
+ print(f"Patient ID {patient_id} not found in patients_db.")
493
+ # Redirect to dashboard, perhaps with an error message
494
+ return redirect(url_for('doctor_dashboard', error="Patient not found"))
495
+ # Render the dashboard template, passing the specific patient ID
496
+ # The JS on doctor_dashboard.html will detect this ID in the URL and load the details
497
+ return render_template('doctor_dashboard.html', selected_patient_id=patient_id)
498
 
499
 
500
  @app.route('/submit_feedback', methods=['POST'])
501
  def submit_feedback():
502
+ if not model:
503
+ return jsonify({'success': False, 'error': 'AI model not initialized. Please check API key.'}), 500
504
+
505
  try:
506
+ # Get patient information from form
507
+ name = request.form.get('name', 'Unnamed Patient')
508
+ age = request.form.get('age', 'N/A')
509
+ gender = request.form.get('gender', 'N/A')
510
  feedback = request.form.get('feedback', '')
511
+
512
  care_plan_text = ""
513
  care_plan_format = None
514
+
515
+ # Handle PDF file upload
516
  if 'care_plan_pdf' in request.files:
517
  pdf_file = request.files['care_plan_pdf']
518
+ if pdf_file and pdf_file.filename != '':
519
+ # Read the PDF content from the file stream
520
  care_plan_text = extract_text_from_pdf(pdf_file)
521
+ if care_plan_text:
522
+ care_plan_format = extract_care_plan_format(care_plan_text)
523
+ print(f"Extracted text length: {len(care_plan_text)}. Format found: {care_plan_format is not None}")
524
+
525
  # If no format is found in the PDF, use a default format
526
+ if not care_plan_format or not care_plan_format.strip():
527
+ print("Using default care plan format.")
528
  care_plan_format = """
529
+ PATIENT INFORMATION:
530
+ - Name: [Patient Name]
531
+ - Age: [Age]
532
+ - Gender: [Gender]
533
+
534
  ASSESSMENT:
535
+ - [Summary of patient's current condition based on feedback and previous plan]
536
+
537
  DAILY CARE PLAN:
538
  Morning:
539
+ - [Morning activities/medications]
540
  Afternoon:
541
+ - [Afternoon activities/medications]
542
  Evening:
543
+ - [Evening activities/medications]
544
+ Night:
545
+ - [Night activities/medications/sleep instructions]
546
+
547
  MEDICATIONS:
548
+ - [List of medications, dosage, frequency, and time]
549
+
550
+ DIET AND HYDRATION:
551
+ - [Dietary recommendations and hydration goals]
552
+
553
+ PHYSICAL ACTIVITY/EXERCISE:
554
+ - [Recommended physical activities, duration, frequency]
555
+
556
+ SYMPTOM MANAGEMENT:
557
+ - [Instructions for managing specific symptoms, including what to do if they worsen]
558
+
559
+ RED FLAGS / WHEN TO SEEK HELP:
560
+ - [Clear instructions on symptoms requiring immediate medical attention (Emergency)]
561
+ - [Instructions on symptoms requiring contact with doctor/clinic (Urgent but not Emergency)]
562
+
563
  ADDITIONAL RECOMMENDATIONS:
564
+ - [Other relevant advice, e.g., rest, monitoring, specific precautions]
565
+
566
  FOLLOW-UP:
567
+ - [Details about next appointment or when to contact healthcare provider]
568
  """
569
+ else:
570
+ print("Using extracted care plan format.")
571
+
572
+ # Check for emergency symptoms FIRST in feedback
573
+ emergency_keywords_check = [
574
  "severe chest pain", "heart attack", "shortness of breath",
575
  "dizziness", "loss of consciousness", "extreme pain",
 
 
576
  "sudden weakness", "confusion", "slurred speech", "severe headache",
577
+ "difficulty breathing", "severe shortness of breath", "wheezing",
 
 
 
 
578
  "severe abdominal pain", "persistent vomiting", "uncontrolled bleeding",
579
+ "severe allergic reaction", "anaphylaxis", "immediate medical attention",
580
+ "emergency", "call 911", "urgent care", "hospital", "critical", "ambulance",
581
+ "collapsed", "unconscious", "unresponsive", "stroke", "seizure", "convulsion",
582
+ "suffocating", "not breathing", "blue lips", "blue face", "cardiac arrest",
583
+ "high fever", "signs of shock", "severe dehydration", "acute change",
584
+ "unstable vitals", "rapidly worsening"
585
  ]
586
+
587
  feedback_lower = feedback.lower()
588
+ is_emergency_feedback = any(keyword in feedback_lower for keyword in emergency_keywords_check)
589
+
590
+ generated_plan_text = ""
591
+ status = 'stable' # Default status
592
+
593
+ if is_emergency_feedback:
594
+ print("Emergency keywords detected in feedback.")
595
+ # If emergency symptoms are detected in feedback, generate emergency instructions
596
+ generated_plan_text = (
597
+ "PATIENT INFORMATION:\n"
598
+ f"- Name: {name}\n"
599
+ f"- Age: {age}\n"
600
+ f"- Gender: {gender}\n\n"
601
  "ASSESSMENT:\n"
602
+ f"- Emergency symptoms reported: {feedback}. Immediate medical attention required.\n\n"
603
  "EMERGENCY ACTION PLAN:\n"
604
+ "- Call emergency services immediately at 104/108/109/112 or your local emergency number.\n"
605
  "- Do not delay seeking medical help.\n"
606
+ "- If conscious, try to remain calm.\n"
607
+ "- Do not eat or drink anything until evaluated by medical professionals.\n"
608
+ "- Follow instructions from emergency responders.\n\n"
609
+ "RED FLAGS / WHEN TO SEEK HELP:\n"
610
+ "- *Any* worsening of reported emergency symptoms requires urgent escalation.\n\n"
611
  "FOLLOW-UP:\n"
612
+ "- Immediate hospitalization or urgent medical evaluation is necessary.\n"
613
+ "- Inform your primary physician as soon as medically stable.\n"
614
+ "- Review and update care plan only after emergency situation is resolved and evaluated by medical professionals.\n"
615
  )
616
+ status = 'emergency' # Set status to emergency
617
+
618
+ else:
619
+ # Prepare a prompt for Gemini AI for a perfect, attractively formatted day care plan.
620
+ # Include patient info directly in the prompt for context
621
+ prompt = f"""
622
+ You are a helpful assistant generating updated patient care plans.
623
+ Based on the patient's personal information, their current symptoms/feedback, and their previous care plan, create a NEW, comprehensive, and well-structured daily care plan.
624
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
625
  Patient Information:
626
  Name: {name}
627
  Age: {age}
628
  Gender: {gender}
629
+
630
+ Patient Feedback/Symptoms Update:
631
+ {feedback}
632
+
633
+ Previous Care Plan Details (if available):
634
+ {care_plan_text if care_plan_text else "No previous care plan provided."}
635
+
636
+ Instructions:
637
+ 1. Generate the updated care plan using the exact following format template. Fill in the details based on the patient's information, feedback, and integrate relevant elements from the previous plan if helpful.
638
+ 2. Be specific and actionable in your recommendations.
639
+ 3. Ensure the language is clear and easy to understand.
640
+ 4. Include realistic times for medications if applicable (e.g., "Take 1 tablet in the morning with food").
641
+ 5. Highlight important instructions or warnings, especially regarding symptom management and when to seek help.
642
+ 6. Do NOT include any introductory or concluding sentences outside the plan format itself (like "Here is the updated plan:" or "Let me know if you need further assistance."). Provide ONLY the structured plan content.
643
+
644
+ Care Plan Format Template:
645
  {care_plan_format}
 
 
 
 
 
 
 
646
  """
647
+ print("Sending prompt to AI model...")
648
+ print(f"Prompt:\n---\n{prompt}\n---")
649
+
650
+ # Get response from Gemini AI
651
+ try:
652
+ response = model.generate_content(prompt)
653
+ generated_plan_text = response.text.strip() # Get the text and strip leading/trailing whitespace
654
+ print(f"AI Response received. Length: {len(generated_plan_text)}")
655
+ print(f"AI Response:\n---\n{generated_plan_text}\n---")
656
+
657
+ # Determine patient status based on AI's output and feedback
658
+ status = determine_patient_status(care_plan_text, generated_plan_text, feedback)
659
+
660
+ except Exception as ai_error:
661
+ print(f"Error generating content from AI: {ai_error}")
662
+ return jsonify({
663
+ 'success': False,
664
+ 'error': f'Error generating care plan: {ai_error}'
665
+ }), 500
666
+
667
+ # --- Actions after plan generation (Emergency or AI) ---
668
+
669
+ # Store patient record (in-memory DB)
670
  patient_id = str(uuid.uuid4())
671
  patients_db[patient_id] = {
672
  'id': patient_id,
 
674
  'age': age,
675
  'gender': gender,
676
  'feedback': feedback,
677
+ 'original_plan': care_plan_text, # Store original text from PDF
678
+ 'updated_plan': generated_plan_text, # Store the generated plan text
679
  'status': status,
680
  'timestamp': datetime.now().strftime("%Y-%m-%d %H:%M:%S")
681
  }
682
+ print(f"Patient {patient_id} added to in-memory DB with status: {status}. Current DB keys: {patients_db.keys()}")
683
+
684
+
685
+ # Generate PDF for downloading using the stored data
686
+ patient_info_for_pdf = {
687
+ 'name': name,
688
+ 'age': age,
689
+ 'gender': gender
690
+ }
691
+ pdf_buffer = generate_care_plan_pdf(patient_info_for_pdf, generated_plan_text, status)
692
+ pdf_base64 = base64.b64encode(pdf_buffer.getvalue()).decode('utf-8')
693
+ print("PDF generated and base64 encoded.")
694
+
695
  # Send care plan via WhatsApp
696
+ patient_info_for_whatsapp = {
697
+ 'name': name,
698
+ 'age': age,
699
+ 'gender': gender
700
+ }
701
+ whatsapp_sent = send_whatsapp_care_plan(patient_info_for_whatsapp, generated_plan_text, status)
702
+ print(f"WhatsApp message attempt sent: {whatsapp_sent}")
703
+
704
  return jsonify({
705
  'success': True,
706
+ 'updated_plan': generated_plan_text, # Send back the generated text
707
  'pdf_data': pdf_base64,
708
+ 'patient_id': patient_id, # Send the generated patient ID
709
  'status': status,
710
  'whatsapp_sent': whatsapp_sent
711
  })
712
+
713
  except Exception as e:
714
+ print(f"An unexpected error occurred: {str(e)}")
715
+ # Log the traceback for debugging
716
+ import traceback
717
+ traceback.print_exc()
718
  return jsonify({
719
  'success': False,
720
+ 'error': f'An unexpected error occurred: {str(e)}'
721
+ }), 500
722
 
723
 
724
  @app.route('/download_pdf/<patient_id>')
725
  def download_pdf(patient_id):
726
+ print(f"Download requested for patient ID: {patient_id}")
727
+ print(f"Current patients_db keys: {patients_db.keys()}")
728
+
729
  try:
730
+ patient = patients_db.get(patient_id)
731
+
732
+ if not patient:
733
+ print(f"Patient ID {patient_id} not found in patients_db for download.")
734
+ return "Patient data not found (it may have been cleared). Please regenerate the plan.", 404
735
+
736
+ # Use the patient's stored information to regenerate the PDF content
737
+ patient_info_for_pdf = {
738
+ 'name': patient.get('name', 'N/A'),
739
+ 'age': patient.get('age', 'N/A'),
740
+ 'gender': patient.get('gender', 'N/A')
741
+ }
742
+ # Use the already determined status and updated plan text
743
+ pdf_buffer = generate_care_plan_pdf(
744
+ patient_info_for_pdf,
745
+ patient['updated_plan'],
746
+ patient['status']
747
+ )
748
+
749
  pdf_buffer.seek(0) # Ensure buffer is at the start
750
+
751
+ # Sanitize patient name for filename
752
+ safe_name = re.sub(r'[^a-zA-Z0-9_\-]', '', patient.get('name', 'patient')).lower()
753
+ download_name = f"care_plan_{safe_name}_{datetime.now().strftime('%Y%m%d')}.pdf"
754
+
755
+ print(f"Serving PDF for {patient_id} as {download_name}")
756
  return send_file(
757
  pdf_buffer,
758
  as_attachment=True,
759
+ download_name=download_name,
760
  mimetype='application/pdf'
761
  )
762
  except Exception as e:
763
+ print(f"PDF Download Error for ID {patient_id}: {str(e)}")
764
+ import traceback
765
+ traceback.print_exc()
766
  return f"Error generating PDF: {str(e)}", 500
767
 
768
 
769
  @app.route('/get_emergency_notifications')
770
  def get_emergency_notifications():
771
  # Filter patients with emergency status
772
+ emergency_patients = [p for p in patients_db.values() if p.get('status') == 'emergency']
773
+
774
+ # Sort by timestamp, newest first
775
+ emergency_patients.sort(key=lambda x: datetime.strptime(x['timestamp'], "%Y-%m-%d %H:%M:%S"), reverse=True)
776
+
777
  notifications = []
778
  for patient in emergency_patients:
779
  notifications.append({
780
  'patient_id': patient['id'],
781
+ 'patient_name': patient.get('name', 'N/A'),
782
+ 'patient_age': patient.get('age', 'N/A'),
783
+ 'patient_gender': patient.get('gender', 'N/A'),
784
+ 'patient_feedback': patient.get('feedback', 'No feedback recorded.'),
785
+ 'timestamp': patient.get('timestamp', 'N/A'),
786
+ 'status': patient.get('status', 'unknown')
787
  })
788
+
789
  return jsonify({
790
  'success': True,
791
  'notifications': notifications
 
795
  @app.route('/get_patients')
796
  def get_patients():
797
  # Return all patients for the doctor dashboard
798
+ # Sort by timestamp, newest first by default, or add sorting logic here
799
+ sorted_patients = sorted(patients_db.values(),
800
+ key=lambda x: datetime.strptime(x.get('timestamp', '1900-01-01 00:00:00'), "%Y-%m-%d %H:%M:%S"),
801
+ reverse=True)
802
+
803
  return jsonify({
804
  'success': True,
805
+ 'patients': list(sorted_patients)
806
  })
807
 
808
 
809
  @app.route('/get_patient/<patient_id>')
810
  def get_patient(patient_id):
811
+ print(f"API request for patient ID: {patient_id}")
812
+ print(f"Current patients_db keys: {patients_db.keys()}")
813
+
814
+ patient = patients_db.get(patient_id)
815
+
816
+ if not patient:
817
+ print(f"Patient ID {patient_id} not found in patients_db for API request.")
818
+ return jsonify({'success': False, 'error': 'Patient not found or data cleared.'}), 404
819
+
820
+ print(f"Found patient {patient_id}: {patient.get('name')}")
821
  return jsonify({
822
  'success': True,
823
+ 'patient': patient
824
  })
825
 
826
 
827
  if __name__ == '__main__':
828
+ # Use a more robust development server like Waitress or Gunicorn in production
829
+ app.run(debug=True, port=5000) # Explicitly set port