rajkhanke commited on
Commit
7973a55
·
verified ·
1 Parent(s): 3728745

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +360 -141
app.py CHANGED
@@ -15,6 +15,7 @@ from reportlab.pdfgen import canvas
15
  from reportlab.lib import colors
16
  from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
17
  from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle
 
18
 
19
  app = Flask(__name__, instance_path='/tmp') # Explicitly set instance_path to a writable location
20
 
@@ -73,9 +74,9 @@ os.makedirs(upload_folder, exist_ok=True)
73
 
74
  # Twilio Configuration
75
  # IMPORTANT: Use environment variables for sensitive information like SID and Auth Token
76
- ACCOUNT_SID = os.getenv('TWILIO_ACCOUNT_SID', 'AC490e071f8d01bf0df2f03d086c788d87')
77
- AUTH_TOKEN = os.getenv('TWILIO_AUTH_TOKEN', '224b23b950ad5a4052aba15893fdf083')
78
- TWILIO_FROM = os.getenv('TWILIO_FROM_NUMBER', 'whatsapp:+14155238886')
79
  TWILIO_TO = os.getenv('TWILIO_TO_NUMBER', 'whatsapp:+917559355282') # Hardcoded number as requested
80
 
81
  # Initialize Twilio client
@@ -93,7 +94,7 @@ else:
93
 
94
  # Gemini API Configuration
95
  # IMPORTANT: Use environment variables for sensitive information like API keys
96
- GENAI_API_KEY = os.getenv('GENAI_API_KEY', "AIzaSyD54ejbjVIVa-F3aD_Urnp8m1EFLUGR__I") # CHANGE THIS KEY
97
  model = None
98
  if GENAI_API_KEY:
99
  try:
@@ -108,7 +109,7 @@ if GENAI_API_KEY:
108
  }
109
 
110
  model = genai.GenerativeModel(
111
- model_name="gemini-1.5-flash-latest",
112
  generation_config=generation_config,
113
  )
114
  print(f"Using Gemini model: {model.model_name}")
@@ -128,10 +129,12 @@ def extract_text_from_pdf(pdf_file):
128
  text = ""
129
  if pdf_reader.is_encrypted:
130
  try:
 
131
  pdf_reader.decrypt('')
132
- except Exception as e:
133
- print(f"PDF is encrypted and cannot be decrypted: {e}")
134
- return "[PDF Content Unavailable: File is encrypted]"
 
135
 
136
  for page in pdf_reader.pages:
137
  page_text = page.extract_text()
@@ -145,28 +148,38 @@ def extract_text_from_pdf(pdf_file):
145
 
146
  def extract_care_plan_format(pdf_text):
147
  """Extract a general format template from PDF text by identifying common section headers."""
148
- if not pdf_text or "[No readable text found" in pdf_text or "[Error extracting PDF text" in pdf_text:
149
  return None
150
 
151
- sections = re.findall(
152
- r'^([A-Z][A-Z\s]*):(?:\s*\n)?((?:(?!\n[A-Z][A-Z\s]*:).*\n?)*)',
153
- pdf_text,
154
- re.MULTILINE | re.DOTALL
155
- )
156
-
157
- if not sections:
158
- potential_headers = re.findall(r'^[A-Z][A-Za-z\s,]*[.:]?$', pdf_text, re.MULTILINE)
159
- if potential_headers:
160
- format_template = "\n".join([f"{header.strip()}:" for header in set(potential_headers) if header.strip()]) + "\n"
161
- print(f"Extracted potential headers (fallback): {list(set(potential_headers))}")
 
 
162
  return format_template if format_template.strip() else None
 
163
  print("No sections or potential headers found in PDF.")
164
  return None
165
 
 
 
 
 
 
 
 
166
  format_template = ""
167
- for section_title, _ in sections:
168
- format_template += f"{section_title.strip()}:\n- [Details]\n"
169
- print(f"Extracted sections: {[s[0].strip() for s in sections]}")
170
  return format_template if format_template.strip() else None
171
 
172
 
@@ -175,7 +188,7 @@ def determine_patient_status(original_plan, updated_plan, feedback):
175
 
176
  feedback_lower = feedback.lower()
177
  original_plan_lower = original_plan.lower() if original_plan else ""
178
- updated_plan_lower = updated_plan.lower()
179
 
180
  emergency_keywords = [
181
  "severe chest pain", "heart attack", "sudden shortness of breath",
@@ -187,7 +200,9 @@ def determine_patient_status(original_plan, updated_plan, feedback):
187
  "collapsed", "unconscious", "unresponsive", "stroke", "seizure", "convulsion",
188
  "suffocating", "not breathing", "blue lips", "blue face", "cardiac arrest",
189
  "high fever", "signs of shock", "severe dehydration", "acute change",
190
- "unstable vitals", "rapidly worsening", "can't breathe", "chest tight"
 
 
191
  ]
192
 
193
  deteriorating_keywords = [
@@ -199,7 +214,9 @@ def determine_patient_status(original_plan, updated_plan, feedback):
199
  "increased symptoms", "more severe", "progressing", "progressive", "complicated",
200
  "adverse change", "unstable", "needs attention", "spike in", "significant increase",
201
  "new symptoms", "trouble with", "reduced appetite", "difficulty sleeping",
202
- "tired all the time", "much weaker", "feeling worse"
 
 
203
  ]
204
 
205
  improvement_keywords = [
@@ -210,9 +227,12 @@ def determine_patient_status(original_plan, updated_plan, feedback):
210
  "normalized", "normal range", "responding well", "responding positively",
211
  "effective treatment", "successful treatment", "managed well", "under control",
212
  "symptoms decreased", "feeling stronger", "better sleep", "increased appetite",
213
- "pain decreased", "more energy", "walking further"
 
 
214
  ]
215
 
 
216
  if any(keyword in feedback_lower for keyword in emergency_keywords):
217
  return "emergency"
218
  if any(keyword in feedback_lower for keyword in deteriorating_keywords):
@@ -220,14 +240,29 @@ def determine_patient_status(original_plan, updated_plan, feedback):
220
  if any(keyword in feedback_lower for keyword in improvement_keywords):
221
  return "improving"
222
 
223
- if updated_plan_lower != original_plan_lower:
 
 
 
 
 
 
 
 
 
 
 
224
  if any(keyword in updated_plan_lower for keyword in emergency_keywords):
225
- return "emergency"
 
 
226
  if any(keyword in updated_plan_lower for keyword in deteriorating_keywords):
227
- return "deteriorating"
228
  if any(keyword in updated_plan_lower for keyword in improvement_keywords):
229
- return "improving"
 
230
 
 
231
  return "stable"
232
 
233
 
@@ -241,30 +276,45 @@ def generate_care_plan_pdf(patient_info, care_plan_text, status):
241
 
242
  styles = getSampleStyleSheet()
243
 
 
244
  title_style = ParagraphStyle(
245
  'Title',
246
  parent=styles['Heading1'],
247
- fontSize=22, alignment=1, spaceAfter=25, textColor=colors.HexColor("#4e73df")
 
248
  )
249
 
250
  heading_style = ParagraphStyle(
251
  'Heading',
252
  parent=styles['Heading2'],
253
- fontSize=15, spaceAfter=8, spaceBefore=18, textColor=colors.HexColor("#1cc88a")
 
254
  )
255
 
256
  normal_style = ParagraphStyle(
257
  'Normal',
258
  parent=styles['Normal'],
259
- fontSize=11, spaceAfter=6, leading=14, textColor=colors.HexColor("#5a5c69")
 
260
  )
261
 
262
  bullet_style = ParagraphStyle(
263
  'Bullet',
264
  parent=styles['Normal'],
265
- fontSize=11, spaceAfter=3, leftIndent=20, leading=14, bulletIndent=10, textColor=colors.HexColor("#5a5c69")
 
266
  )
267
 
 
 
 
 
 
 
 
 
 
 
268
  status_colors = {
269
  'emergency': colors.HexColor("#e74a3b"),
270
  'deteriorating': colors.HexColor("#f6c23e"),
@@ -274,11 +324,11 @@ def generate_care_plan_pdf(patient_info, care_plan_text, status):
274
  }
275
 
276
  status_text_styles = {
277
- 'emergency': ParagraphStyle('StatusEmergency', parent=styles['Heading2'], fontSize=16, spaceBefore=10, spaceAfter=15, textColor=status_colors['emergency'], alignment=1, fontName='Helvetica-Bold'),
278
- 'deteriorating': ParagraphStyle('StatusDeteriorating', parent=styles['Heading2'], fontSize=15, spaceBefore=10, spaceAfter=15, textColor=status_colors['deteriorating'], alignment=1, fontName='Helvetica-Bold'),
279
- 'improving': ParagraphStyle('StatusImproving', parent=styles['Heading2'], fontSize=15, spaceBefore=10, spaceAfter=15, textColor=status_colors['improving'], alignment=1, fontName='Helvetica-Bold'),
280
- 'stable': ParagraphStyle('StatusStable', parent=styles['Heading2'], fontSize=15, spaceBefore=10, spaceAfter=15, textColor=status_colors['stable'], alignment=1, fontName='Helvetica-Bold'),
281
- 'unknown': ParagraphStyle('StatusUnknown', parent=styles['Heading2'], fontSize=15, spaceBefore=10, spaceAfter=15, textColor=colors.black, alignment=1, fontName='Helvetica-Bold'),
282
  }
283
 
284
  story = []
@@ -286,10 +336,10 @@ def generate_care_plan_pdf(patient_info, care_plan_text, status):
286
  story.append(Paragraph("Patient Care Plan", title_style))
287
 
288
  status_map_text = {
289
- 'emergency': "EMERGENCY - IMMEDIATE ACTION REQUIRED",
290
- 'deteriorating': "HIGH RISK - Condition Deteriorating",
291
- 'improving': "LOW RISK - Condition Improving",
292
- 'stable': "STABLE - Maintain Current Care",
293
  'unknown': "Status: Unknown"
294
  }
295
  current_status_text = status_map_text.get(status, 'unknown')
@@ -304,11 +354,13 @@ def generate_care_plan_pdf(patient_info, care_plan_text, status):
304
  [Paragraph("<b>Generated Date:</b>", normal_style), Paragraph(datetime.now().strftime("%Y-%m-%d %H:%M"), normal_style)]
305
  ]
306
 
307
- patient_table = Table(patient_data, colWidths=[150, 360])
 
308
  patient_table.setStyle(TableStyle([
309
  ('BACKGROUND', (0, 0), (0, -1), colors.HexColor("#f2f2f2")),
310
- ('TEXTCOLOR', (0, 0), (0, -1), colors.black),
311
- ('ALIGN', (0, 0), (-1, -1), 'LEFT'),
 
312
  ('VALIGN', (0, 0), (-1, -1), 'TOP'),
313
  ('INNERGRID', (0, 0), (-1, -1), 0.25, colors.HexColor("#dddddd")),
314
  ('BOX', (0, 0), (-1, -1), 0.25, colors.HexColor("#dddddd")),
@@ -321,31 +373,48 @@ def generate_care_plan_pdf(patient_info, care_plan_text, status):
321
  story.append(patient_table)
322
  story.append(Spacer(1, 25))
323
 
 
 
 
 
 
 
 
 
 
324
  story.append(Paragraph("Care Plan Details:", heading_style))
325
  story.append(Spacer(1, 10))
326
 
 
327
  lines = care_plan_text.strip().split('\n')
328
  for line in lines:
329
  stripped_line = line.strip()
330
  if not stripped_line:
331
  continue
332
 
333
- header_match = re.match(r'^([A-Z][A-Z\s]*):$', stripped_line)
 
 
334
  if header_match:
335
- story.append(Spacer(1, 8))
336
- story.append(Paragraph(stripped_line, heading_style))
 
337
  elif stripped_line.startswith('-') or stripped_line.startswith('*') or stripped_line.startswith('•'):
338
- bullet_text = re.sub(r'^[-*•]\s*', '', line).strip()
 
339
  if bullet_text:
340
- story.append(Paragraph(f"• {bullet_text}", bullet_style))
 
341
  else:
 
342
  story.append(Paragraph("• ", bullet_style))
343
  else:
344
- story.append(Paragraph(line, normal_style))
 
345
 
346
  story.append(Spacer(1, 20))
347
- footer_style = ParagraphStyle('Footer', parent=styles['Normal'], fontSize=9, alignment=1, textColor=colors.grey)
348
- story.append(Paragraph(f"Generated by Patient Care Management System on {datetime.now().strftime('%Y-%m-%d')}", footer_style))
349
 
350
  try:
351
  doc.build(story)
@@ -354,6 +423,8 @@ def generate_care_plan_pdf(patient_info, care_plan_text, status):
354
  return buffer
355
  except Exception as e:
356
  print(f"Error building PDF: {e}")
 
 
357
  error_buffer = io.BytesIO()
358
  c = canvas.Canvas(error_buffer, pagesize=letter)
359
  c.drawString(100, 750, "Error Generating Care Plan PDF")
@@ -367,7 +438,11 @@ def send_whatsapp_care_plan(patient_info, care_plan_text, status):
367
  """Send care plan via WhatsApp using Twilio with improved formatting"""
368
  if not twilio_client:
369
  print("Twilio client not configured. Cannot send WhatsApp message.")
370
- return False
 
 
 
 
371
 
372
  try:
373
  status_emoji = {
@@ -378,15 +453,34 @@ def send_whatsapp_care_plan(patient_info, care_plan_text, status):
378
  'unknown': "⚪ Status: Unknown"
379
  }
380
 
 
381
  formatted_plan = care_plan_text.strip()
 
382
  formatted_plan = re.sub(r'\n{2,}', '\n\n', formatted_plan)
 
383
  formatted_plan = formatted_plan.replace('- ', '• ').replace('* ', '• ')
 
 
 
 
 
 
 
 
 
 
 
384
 
385
  message = f"*Care Plan Update*\n\n"
386
  message += f"*Patient Name:* {patient_info.get('name', 'N/A')}\n"
387
  message += f"*Age:* {patient_info.get('age', 'N/A')}\n"
388
  message += f"*Gender:* {patient_info.get('gender', 'N/A')}\n"
389
  message += f"*Status:* {status_emoji.get(status, 'Unknown')}\n\n"
 
 
 
 
 
390
  message += f"*Care Plan Details:*\n{formatted_plan}"
391
 
392
  print(f"Attempting to send WhatsApp message to {TWILIO_TO}...")
@@ -396,17 +490,19 @@ def send_whatsapp_care_plan(patient_info, care_plan_text, status):
396
  to=TWILIO_TO
397
  )
398
  print(f"WhatsApp message sent, SID: {message_sent.sid}")
399
- return True
400
  except Exception as e:
401
  print(f"Error sending WhatsApp message: {e}")
402
- return False
403
 
404
 
405
  @app.route('/')
406
  def index():
407
  role = session.get('role', 'patient')
 
408
  if role == 'doctor':
409
  return redirect(url_for('doctor_dashboard'))
 
410
  return render_template('index.html')
411
 
412
 
@@ -415,12 +511,20 @@ def switch_role():
415
  role = request.form.get('role')
416
  if role in ['patient', 'doctor']:
417
  session['role'] = role
418
- return jsonify({'success': True, 'role': role})
 
 
 
 
419
  return jsonify({'success': False, 'error': 'Invalid role'}), 400
420
 
421
 
422
  @app.route('/doctor_dashboard')
423
  def doctor_dashboard():
 
 
 
 
424
  return render_template('doctor_dashboard.html')
425
 
426
 
@@ -433,18 +537,21 @@ def submit_feedback():
433
  name = request.form.get('name', 'Unnamed Patient')
434
  age = request.form.get('age')
435
  gender = request.form.get('gender', 'N/A')
436
- feedback = request.form.get('feedback', '')
437
 
438
  if not name or not feedback:
439
  return jsonify({'success': False, 'error': 'Patient Name and Feedback are required.'}), 400
 
 
440
  if age:
441
  try:
442
  age = int(age)
443
  if age <= 0: raise ValueError("Age must be positive")
 
444
  except ValueError:
445
  return jsonify({'success': False, 'error': 'Invalid Age provided.'}), 400
446
  else:
447
- age = None
448
 
449
  care_plan_text = ""
450
  care_plan_format = None
@@ -452,16 +559,26 @@ def submit_feedback():
452
  if 'care_plan_pdf' in request.files:
453
  pdf_file = request.files['care_plan_pdf']
454
  if pdf_file and pdf_file.filename != '':
 
 
 
 
455
  care_plan_text = extract_text_from_pdf(pdf_file)
456
- if care_plan_text and "[No readable text found" not in care_plan_text and "[Error extracting PDF text" not in care_plan_text:
457
- care_plan_format = extract_care_plan_format(care_plan_text)
458
- print(f"Extracted text length: {len(care_plan_text)}. Format found: {care_plan_format is not None}")
 
 
 
 
 
459
  else:
460
  print("No PDF file uploaded or file is empty.")
461
 
462
 
 
463
  if not care_plan_format or not care_plan_format.strip():
464
- print("Using default care plan format.")
465
  care_plan_format = """
466
  PATIENT INFORMATION:
467
  - Name: [Patient Name]
@@ -471,41 +588,43 @@ ASSESSMENT:
471
  - [Summary of patient's current condition based on feedback and previous plan]
472
  DAILY CARE PLAN:
473
  Morning:
474
- - [Morning activities/medications]
475
  Afternoon:
476
- - [Afternoon activities/medications]
477
  Evening:
478
- - [Evening activities/medications]
479
  Night:
480
- - [Night activities/medications/sleep instructions]
481
  MEDICATIONS:
482
- - [List of medications, dosage, frequency, and time]
483
  DIET AND HYDRATION:
484
- - [Dietary recommendations and hydration goals]
485
  PHYSICAL ACTIVITY/EXERCISE:
486
- - [Recommended physical activities, duration, frequency]
487
  SYMPTOM MANAGEMENT:
488
- - [Instructions for managing specific symptoms, including what to do if they worsen]
489
  RED FLAGS / WHEN TO SEEK HELP:
490
- - [Clear instructions on symptoms requiring immediate medical attention (Emergency)]
491
- - [Instructions on symptoms requiring contact with doctor/clinic (Urgent but not Emergency)]
492
  ADDITIONAL RECOMMENDATIONS:
493
- - [Other relevant advice, e.g., rest, monitoring, specific precautions]
494
  FOLLOW-UP:
495
- - [Details about next appointment or when to contact healthcare provider]
496
- """
497
- else:
498
- print("Using extracted care plan format.")
499
 
 
500
  initial_status = determine_patient_status(care_plan_text, "", feedback)
501
 
502
 
503
  generated_plan_text = ""
504
- status = initial_status
505
 
 
 
506
  if status == 'emergency':
507
  print("Emergency status detected from feedback. Generating emergency plan.")
508
  generated_plan_text = (
 
509
  "PATIENT INFORMATION:\n"
510
  f"- Name: {name}\n"
511
  f"- Age: {age if age is not None else 'N/A'}\n"
@@ -513,39 +632,52 @@ FOLLOW-UP:
513
  "ASSESSMENT:\n"
514
  f"- Emergency symptoms reported: {feedback}. Immediate medical attention required.\n\n"
515
  "EMERGENCY ACTION PLAN:\n"
516
- "- Call emergency services immediately at 104/108/109/112 or your local emergency number.\n"
517
- "- Do not delay seeking medical help.\n"
518
- "- If conscious, try to remain calm.\n"
519
- "- Do not eat or drink anything until evaluated by medical professionals.\n"
520
- "- Follow instructions from emergency responders.\n\n"
 
521
  "RED FLAGS / WHEN TO SEEK HELP:\n"
522
- "- *Any* worsening of reported emergency symptoms requires urgent escalation.\n\n"
 
523
  "FOLLOW-UP:\n"
524
  "- Immediate hospitalization or urgent medical evaluation is necessary.\n"
525
- "- Inform your primary physician as soon as medically stable.\n"
526
- "- Review and update care plan only after emergency situation is resolved and evaluated by medical professionals.\n"
527
  )
 
 
 
528
 
529
- else:
530
  prompt = f"""
531
- You are a helpful assistant generating updated patient care plans.
532
- 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.
 
533
  Patient Information:
534
  Name: {name}
535
  Age: {age if age is not None else 'N/A'}
536
  Gender: {gender}
 
537
  Patient Feedback/Symptoms Update:
538
  {feedback}
 
539
  Previous Care Plan Details (if available):
540
- {care_plan_text if care_plan_text and "[No readable text found" not in care_plan_text and "[Error extracting PDF text" not in care_plan_text else "No previous care plan provided."}
 
541
  Instructions:
542
- 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.
543
- 2. Be specific and actionable in your recommendations.
544
- 3. Ensure the language is clear and easy to understand.
545
- 4. Include realistic times for medications if applicable (e.g., "Take 1 tablet in the morning with food").
546
- 5. Highlight important instructions or warnings, especially regarding symptom management and when to seek help.
547
- 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.
548
- 7. If the previous care plan text indicates it was unreadable or had errors, acknowledge that and generate a new plan based primarily on the feedback and patient info, using the format template.
 
 
 
 
549
  Care Plan Format Template:
550
  {care_plan_format}
551
  """
@@ -555,29 +687,41 @@ Care Plan Format Template:
555
  response = model.generate_content(prompt)
556
  generated_plan_text = response.text.strip()
557
 
 
558
  if generated_plan_text.startswith('```') and generated_plan_text.endswith('```'):
559
  generated_plan_text = generated_plan_text[3:-3].strip()
560
- if generated_plan_text.startswith('text'):
 
561
  generated_plan_text = generated_plan_text[4:].strip()
 
 
 
562
 
563
  print(f"AI Response received. Length: {len(generated_plan_text)}")
564
 
565
- status = determine_patient_status(care_plan_text, generated_plan_text, feedback)
 
 
 
 
 
 
 
 
566
 
567
  except Exception as ai_error:
568
  print(f"Error generating content from AI: {ai_error}")
 
569
  generated_plan_text = f"[Error generating updated plan from AI: {ai_error}]\n\n"
570
- if care_plan_text and "[No readable text found" not in care_plan_text and "[Error extracting PDF text" not in care_plan_text:
571
- generated_plan_text += "Showing original plan due to AI error:\n\n" + care_plan_text
572
- status = determine_patient_status(care_plan_text, care_plan_text, feedback)
 
573
  else:
574
  generated_plan_text += "No previous plan available."
575
- status = initial_status
576
-
577
- # Even if AI fails, we still create a record with the error/fallback plan
578
- # and the determined status (e.g., 'emergency' if feedback indicated).
579
- # Continue to store the patient record below.
580
 
 
581
 
582
  # Create and store patient record in the database
583
  new_patient = Patient(
@@ -585,46 +729,39 @@ Care Plan Format Template:
585
  age=age,
586
  gender=gender,
587
  feedback=feedback,
588
- original_plan=care_plan_text,
589
- updated_plan=generated_plan_text,
590
- status=status,
591
  timestamp=datetime.utcnow()
592
  )
593
  db.session.add(new_patient)
594
  db.session.commit()
595
  patient_id = new_patient.id
596
 
597
- print(f"Patient {patient_id} added to DB with status: {status}.")
598
 
599
  # Generate PDF for downloading using the stored data
600
- patient_info_for_pdf = {
601
- 'name': name,
602
- 'age': age,
603
- 'gender': gender
604
- }
605
- pdf_buffer = generate_care_plan_pdf(patient_info_for_pdf, generated_plan_text, status)
606
  pdf_base64 = base64.b64encode(pdf_buffer.getvalue()).decode('utf-8')
607
  print("PDF generated and base64 encoded.")
608
 
609
- # Send care plan via WhatsApp
610
- patient_info_for_whatsapp = {
611
- 'name': name,
612
- 'age': age,
613
- 'gender': gender
614
- }
615
- whatsapp_sent = send_whatsapp_care_plan(patient_info_for_whatsapp, generated_plan_text, status)
616
- print(f"WhatsApp message attempt sent: {whatsapp_sent}")
617
 
618
- # Return success response even if AI failed, but include error message
619
  return jsonify({
620
  'success': True, # Indicate that patient record was created/updated
621
- 'updated_plan': generated_plan_text,
622
  'pdf_data': pdf_base64,
623
  'patient_id': patient_id,
624
- 'status': status,
625
  'whatsapp_sent': whatsapp_sent,
626
- 'ai_error': not model # Indicate if AI was even attempted (model exists)
627
- or ("Error generating updated plan" in generated_plan_text) # Check if error message was inserted
628
  })
629
 
630
  except Exception as e:
@@ -637,6 +774,86 @@ Care Plan Format Template:
637
  'error': f'An unexpected server error occurred: {str(e)}'
638
  }), 500
639
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
640
 
641
  @app.route('/download_pdf/<patient_id>')
642
  def download_pdf(patient_id):
@@ -649,13 +866,9 @@ def download_pdf(patient_id):
649
  print(f"Patient ID {patient_id} not found in database for download.")
650
  return "Patient data not found.", 404
651
 
652
- patient_info_for_pdf = {
653
- 'name': patient.name,
654
- 'age': patient.age,
655
- 'gender': patient.gender
656
- }
657
  pdf_buffer = generate_care_plan_pdf(
658
- patient_info_for_pdf,
659
  patient.updated_plan,
660
  patient.status
661
  )
@@ -681,9 +894,11 @@ def download_pdf(patient_id):
681
 
682
  @app.route('/get_emergency_notifications')
683
  def get_emergency_notifications():
 
684
  emergency_patients_query = Patient.query.filter_by(status='emergency').order_by(Patient.timestamp.desc())
685
 
686
- notifications = [p.to_dict() for p in emergency_patients_query.all()]
 
687
 
688
  return jsonify({
689
  'success': True,
@@ -694,6 +909,7 @@ def get_emergency_notifications():
694
  @app.route('/get_patients')
695
  def get_patients():
696
  all_patients_query = Patient.query.order_by(Patient.timestamp.desc())
 
697
  patients_list = [p.to_dict() for p in all_patients_query.all()]
698
  return jsonify({
699
  'success': True,
@@ -711,12 +927,13 @@ def get_patient(patient_id):
711
  return jsonify({'success': False, 'error': 'Patient not found.'}), 404
712
 
713
  print(f"Found patient {patient_id}: {patient.name}")
 
714
  return jsonify({
715
  'success': True,
716
  'patient': patient.to_dict()
717
  })
718
 
719
- # New route to handle patient deletion
720
  @app.route('/delete_patient/<patient_id>', methods=['DELETE'])
721
  def delete_patient(patient_id):
722
  print(f"Delete requested for patient ID: {patient_id}")
@@ -744,7 +961,9 @@ def delete_patient(patient_id):
744
 
745
 
746
  if __name__ == '__main__':
 
747
  with app.app_context():
748
  db.create_all()
749
  # Use a more robust development server like Waitress or Gunicorn in production
 
750
  app.run(debug=True)
 
15
  from reportlab.lib import colors
16
  from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
17
  from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle
18
+ from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER, TA_JUSTIFY
19
 
20
  app = Flask(__name__, instance_path='/tmp') # Explicitly set instance_path to a writable location
21
 
 
74
 
75
  # Twilio Configuration
76
  # IMPORTANT: Use environment variables for sensitive information like SID and Auth Token
77
+ ACCOUNT_SID = os.getenv('TWILIO_ACCOUNT_SID', 'AC490e071f8d01bf0df2f03d086c788d87') # Replace with your SID
78
+ AUTH_TOKEN = os.getenv('TWILIO_AUTH_TOKEN', '224b23b950ad5a4052aba15893fdf083') # Replace with your Auth Token
79
+ TWILIO_FROM = os.getenv('TWILIO_FROM_NUMBER', 'whatsapp:+14155238886') # Replace with your Twilio Sandbox or purchased number
80
  TWILIO_TO = os.getenv('TWILIO_TO_NUMBER', 'whatsapp:+917559355282') # Hardcoded number as requested
81
 
82
  # Initialize Twilio client
 
94
 
95
  # Gemini API Configuration
96
  # IMPORTANT: Use environment variables for sensitive information like API keys
97
+ GENAI_API_KEY = os.getenv('GENAI_API_KEY', "AIzaSyD54ejbjVIVa-F3aD_Urnp8m1EFLUGR__I") # Replace with your actual API Key
98
  model = None
99
  if GENAI_API_KEY:
100
  try:
 
109
  }
110
 
111
  model = genai.GenerativeModel(
112
+ model_name="gemini-1.5-flash-latest", # Consider gemini-1.5-pro-latest for potentially better quality
113
  generation_config=generation_config,
114
  )
115
  print(f"Using Gemini model: {model.model_name}")
 
129
  text = ""
130
  if pdf_reader.is_encrypted:
131
  try:
132
+ # Attempt decryption with a blank password first
133
  pdf_reader.decrypt('')
134
+ except Exception:
135
+ # If blank password fails, the PDF is likely password protected
136
+ print("PDF is encrypted and requires a password.")
137
+ return "[PDF Content Unavailable: File is encrypted and requires a password]"
138
 
139
  for page in pdf_reader.pages:
140
  page_text = page.extract_text()
 
148
 
149
  def extract_care_plan_format(pdf_text):
150
  """Extract a general format template from PDF text by identifying common section headers."""
151
+ if not pdf_text or "[No readable text found" in pdf_text or "[Error extracting PDF text" in pdf_text or "[PDF Content Unavailable" in pdf_text:
152
  return None
153
 
154
+ # Look for lines that seem like headers followed by a colon, possibly with content below
155
+ # Pattern: Start of line, followed by one or more uppercase letters or spaces, ending with a colon.
156
+ # Non-capturing group `(?:...)` ensures we match the pattern but don't store the content part separately.
157
+ # Use word boundaries \b to avoid matching things like "MEDICATION:" within a sentence.
158
+ potential_headers = re.findall(r'^\b([A-Z][A-Z\s]*)\b[ \t]*:', pdf_text, re.MULTILINE)
159
+
160
+ if not potential_headers:
161
+ # Fallback: Look for lines that start with a capital letter and seem like standalone headers
162
+ # (e.g., "Patient Information", "Assessment.")
163
+ fallback_headers = re.findall(r'^[A-Z][A-Za-z\s,]*[.:]?$', pdf_text, re.MULTILINE)
164
+ if fallback_headers:
165
+ print(f"Extracted potential headers (fallback): {list(set(fallback_headers))}")
166
+ format_template = "\n".join([f"{header.strip()}:" for header in sorted(list(set(fallback_headers))) if header.strip()]) # Sort for consistency
167
  return format_template if format_template.strip() else None
168
+
169
  print("No sections or potential headers found in PDF.")
170
  return None
171
 
172
+ # Use a set to get unique headers and sort them for consistency
173
+ unique_headers = sorted(list(set([h.strip() for h in potential_headers if h.strip()])))
174
+
175
+ if not unique_headers:
176
+ print("Extracted headers are empty after cleaning.")
177
+ return None
178
+
179
  format_template = ""
180
+ for section_title in unique_headers:
181
+ format_template += f"{section_title.strip()}:\n- [Details]\n" # Use a simple list format placeholder
182
+ print(f"Extracted sections: {unique_headers}")
183
  return format_template if format_template.strip() else None
184
 
185
 
 
188
 
189
  feedback_lower = feedback.lower()
190
  original_plan_lower = original_plan.lower() if original_plan else ""
191
+ updated_plan_lower = updated_plan.lower() if updated_plan else "" # Ensure it's not None
192
 
193
  emergency_keywords = [
194
  "severe chest pain", "heart attack", "sudden shortness of breath",
 
200
  "collapsed", "unconscious", "unresponsive", "stroke", "seizure", "convulsion",
201
  "suffocating", "not breathing", "blue lips", "blue face", "cardiac arrest",
202
  "high fever", "signs of shock", "severe dehydration", "acute change",
203
+ "unstable vitals", "rapidly worsening", "can't breathe", "chest tight",
204
+ "severe difficulty swallowing", "new onset paralysis", "severe burns",
205
+ "major trauma", "poisoning", "overdose", "suicidal thoughts" # Added some more critical terms
206
  ]
207
 
208
  deteriorating_keywords = [
 
214
  "increased symptoms", "more severe", "progressing", "progressive", "complicated",
215
  "adverse change", "unstable", "needs attention", "spike in", "significant increase",
216
  "new symptoms", "trouble with", "reduced appetite", "difficulty sleeping",
217
+ "tired all the time", "much weaker", "feeling worse", "consistent high blood pressure",
218
+ "uncontrolled blood sugar", "increased swelling", "difficulty walking",
219
+ "persistent cough", "unexplained weight loss" # Added more worsening signs
220
  ]
221
 
222
  improvement_keywords = [
 
227
  "normalized", "normal range", "responding well", "responding positively",
228
  "effective treatment", "successful treatment", "managed well", "under control",
229
  "symptoms decreased", "feeling stronger", "better sleep", "increased appetite",
230
+ "pain decreased", "more energy", "walking further", "blood pressure normal",
231
+ "blood sugar stable", "swelling reduced", "easier breathing", "cough improving",
232
+ "weight gain", "feeling like myself again" # Added more positive signs
233
  ]
234
 
235
+ # Check feedback first as it's the most direct update from the patient
236
  if any(keyword in feedback_lower for keyword in emergency_keywords):
237
  return "emergency"
238
  if any(keyword in feedback_lower for keyword in deteriorating_keywords):
 
240
  if any(keyword in feedback_lower for keyword in improvement_keywords):
241
  return "improving"
242
 
243
+ # If feedback wasn't definitive, look at the *change* in plan
244
+ # Compare original plan (if available) vs updated plan keywords
245
+ # This part is tricky with AI generation; sometimes the plan *describes* worsening symptoms
246
+ # while still being a 'stable' plan on *how to manage* it.
247
+ # Let's prioritize keywords in the feedback. Only if feedback is neutral,
248
+ # or if the *updated plan itself strongly mandates* a status change (e.g., it says "Monitor closely due to worsening..."),
249
+ # we might infer status from the updated plan *content* keywords.
250
+ # However, for this version, sticking primarily to feedback-driven status is safer.
251
+ # The AI *should* generate content reflecting the feedback anyway.
252
+ # We'll keep the check on the updated plan as a secondary indicator if feedback is neutral.
253
+
254
+ if updated_plan_lower and updated_plan_lower != original_plan_lower:
255
  if any(keyword in updated_plan_lower for keyword in emergency_keywords):
256
+ # This is less likely to be a *status* derived from the plan itself
257
+ # unless the plan is *instructing* emergency actions based on the feedback
258
+ return "emergency" # Re-confirm based on AI output if needed
259
  if any(keyword in updated_plan_lower for keyword in deteriorating_keywords):
260
+ return "deteriorating" # Re-confirm based on AI output if needed
261
  if any(keyword in updated_plan_lower for keyword in improvement_keywords):
262
+ return "improving" # Re-confirm based on AI output if needed
263
+
264
 
265
+ # Default if no specific keywords found
266
  return "stable"
267
 
268
 
 
276
 
277
  styles = getSampleStyleSheet()
278
 
279
+ # Custom Styles
280
  title_style = ParagraphStyle(
281
  'Title',
282
  parent=styles['Heading1'],
283
+ fontSize=22, alignment=TA_CENTER, spaceAfter=25, textColor=colors.HexColor("#4e73df"),
284
+ fontName='Helvetica-Bold' # Use bold font
285
  )
286
 
287
  heading_style = ParagraphStyle(
288
  'Heading',
289
  parent=styles['Heading2'],
290
+ fontSize=15, spaceAfter=8, spaceBefore=18, textColor=colors.HexColor("#1cc88a"),
291
+ fontName='Helvetica-Bold' # Use bold font
292
  )
293
 
294
  normal_style = ParagraphStyle(
295
  'Normal',
296
  parent=styles['Normal'],
297
+ fontSize=11, spaceAfter=6, leading=14, textColor=colors.HexColor("#5a5c69"),
298
+ alignment=TA_JUSTIFY # Justify text
299
  )
300
 
301
  bullet_style = ParagraphStyle(
302
  'Bullet',
303
  parent=styles['Normal'],
304
+ fontSize=11, spaceAfter=3, leftIndent=20, leading=14, bulletIndent=10, textColor=colors.HexColor("#5a5c69"),
305
+ alignment=TA_JUSTIFY # Justify text
306
  )
307
 
308
+ feedback_style = ParagraphStyle(
309
+ 'Feedback',
310
+ parent=normal_style,
311
+ spaceBefore=10, spaceAfter=10, backColor=colors.HexColor("#f8f9fc"),
312
+ borderWidth=0.5, borderColor=colors.HexColor("#dee2e6"), borderPadding=6,
313
+ borderRadius=5,
314
+ textColor=colors.HexColor("#212529") # Darker text for readability on light background
315
+ )
316
+
317
+
318
  status_colors = {
319
  'emergency': colors.HexColor("#e74a3b"),
320
  'deteriorating': colors.HexColor("#f6c23e"),
 
324
  }
325
 
326
  status_text_styles = {
327
+ 'emergency': ParagraphStyle('StatusEmergency', parent=styles['Heading2'], fontSize=16, spaceBefore=10, spaceAfter=15, textColor=status_colors['emergency'], alignment=TA_CENTER, fontName='Helvetica-Bold'),
328
+ 'deteriorating': ParagraphStyle('StatusDeteriorating', parent=styles['Heading2'], fontSize=15, spaceBefore=10, spaceAfter=15, textColor=status_colors['deteriorating'], alignment=TA_CENTER, fontName='Helvetica-Bold'),
329
+ 'improving': ParagraphStyle('StatusImproving', parent=styles['Heading2'], fontSize=15, spaceBefore=10, spaceAfter=15, textColor=status_colors['improving'], alignment=TA_CENTER, fontName='Helvetica-Bold'),
330
+ 'stable': ParagraphStyle('StatusStable', parent=styles['Heading2'], fontSize=15, spaceBefore=10, spaceAfter=15, textColor=status_colors['stable'], alignment=TA_CENTER, fontName='Helvetica-Bold'),
331
+ 'unknown': ParagraphStyle('StatusUnknown', parent=styles['Heading2'], fontSize=15, spaceBefore=10, spaceAfter=15, textColor=colors.black, alignment=TA_CENTER, fontName='Helvetica-Bold'),
332
  }
333
 
334
  story = []
 
336
  story.append(Paragraph("Patient Care Plan", title_style))
337
 
338
  status_map_text = {
339
+ 'emergency': "🚨 EMERGENCY - IMMEDIATE ACTION REQUIRED 🚨",
340
+ 'deteriorating': "⚠️ HIGH RISK - Condition Deteriorating ⚠️",
341
+ 'improving': "LOW RISK - Condition Improving",
342
+ 'stable': "🟦 STABLE - Maintain Current Care 🟦",
343
  'unknown': "Status: Unknown"
344
  }
345
  current_status_text = status_map_text.get(status, 'unknown')
 
354
  [Paragraph("<b>Generated Date:</b>", normal_style), Paragraph(datetime.now().strftime("%Y-%m-%d %H:%M"), normal_style)]
355
  ]
356
 
357
+ # Using a Table for patient info
358
+ patient_table = Table(patient_data, colWidths=[doc.width/3, doc.width/3*2]) # Divide width into 1/3 and 2/3
359
  patient_table.setStyle(TableStyle([
360
  ('BACKGROUND', (0, 0), (0, -1), colors.HexColor("#f2f2f2")),
361
+ ('TEXTCOLOR', (0, 0), (-1, -1), colors.black),
362
+ ('ALIGN', (0, 0), (0, -1), 'LEFT'), # Left align headers
363
+ ('ALIGN', (1, 0), (-1, -1), 'LEFT'), # Left align details
364
  ('VALIGN', (0, 0), (-1, -1), 'TOP'),
365
  ('INNERGRID', (0, 0), (-1, -1), 0.25, colors.HexColor("#dddddd")),
366
  ('BOX', (0, 0), (-1, -1), 0.25, colors.HexColor("#dddddd")),
 
373
  story.append(patient_table)
374
  story.append(Spacer(1, 25))
375
 
376
+ # Add Feedback Section (if available)
377
+ if patient_info.get('feedback'):
378
+ story.append(Paragraph("Patient Feedback:", heading_style))
379
+ story.append(Spacer(1, 5))
380
+ # Using Paragraph with the feedback_style to format the text
381
+ story.append(Paragraph(patient_info['feedback'].replace('\n', '<br/>'), feedback_style)) # Replace newline with <br/> for ReportLab
382
+ story.append(Spacer(1, 15))
383
+
384
+
385
  story.append(Paragraph("Care Plan Details:", heading_style))
386
  story.append(Spacer(1, 10))
387
 
388
+ # Process care plan text line by line
389
  lines = care_plan_text.strip().split('\n')
390
  for line in lines:
391
  stripped_line = line.strip()
392
  if not stripped_line:
393
  continue
394
 
395
+ # Check for section headers (e.g., "MEDICATIONS:", "ASSESSMENT:")
396
+ # Look for lines starting with one or more uppercase words followed by a colon
397
+ header_match = re.match(r'^([A-Z][A-Z\s]*)\b[ \t]*:', stripped_line)
398
  if header_match:
399
+ story.append(Spacer(1, 8)) # Add space before a new section
400
+ story.append(Paragraph(stripped_line, heading_style)) # Use heading style for sections
401
+ # Check for list items (starting with -, *, •)
402
  elif stripped_line.startswith('-') or stripped_line.startswith('*') or stripped_line.startswith('•'):
403
+ # Remove the bullet character and any leading space/tab
404
+ bullet_text = re.sub(r'^[-*•][ \t]*', '', line).strip()
405
  if bullet_text:
406
+ # Use the bullet style, replace newline with <br/> for formatted text
407
+ story.append(Paragraph(f"• {bullet_text.replace('\n', '<br/>')}", bullet_style))
408
  else:
409
+ # Handle cases with just a bullet point on a line
410
  story.append(Paragraph("• ", bullet_style))
411
  else:
412
+ # Handle regular paragraph text
413
+ story.append(Paragraph(line.strip().replace('\n', '<br/>'), normal_style)) # Use normal style, replace newline
414
 
415
  story.append(Spacer(1, 20))
416
+ footer_style = ParagraphStyle('Footer', parent=styles['Normal'], fontSize=9, alignment=TA_CENTER, textColor=colors.grey)
417
+ story.append(Paragraph(f"Generated by Patient Care Management System on {datetime.now().strftime('%Y-%m-%d %H:%M')}", footer_style))
418
 
419
  try:
420
  doc.build(story)
 
423
  return buffer
424
  except Exception as e:
425
  print(f"Error building PDF: {e}")
426
+ import traceback
427
+ traceback.print_exc()
428
  error_buffer = io.BytesIO()
429
  c = canvas.Canvas(error_buffer, pagesize=letter)
430
  c.drawString(100, 750, "Error Generating Care Plan PDF")
 
438
  """Send care plan via WhatsApp using Twilio with improved formatting"""
439
  if not twilio_client:
440
  print("Twilio client not configured. Cannot send WhatsApp message.")
441
+ return False, "Twilio client not configured."
442
+
443
+ if not TWILIO_TO or not TWILIO_FROM:
444
+ print("Twilio TO or FROM number not set.")
445
+ return False, "Twilio TO or FROM number not configured."
446
 
447
  try:
448
  status_emoji = {
 
453
  'unknown': "⚪ Status: Unknown"
454
  }
455
 
456
+ # Clean and format the plan text for WhatsApp
457
  formatted_plan = care_plan_text.strip()
458
+ # Replace multiple newlines with double newline for paragraphs
459
  formatted_plan = re.sub(r'\n{2,}', '\n\n', formatted_plan)
460
+ # Replace common list bullet formats with WhatsApp bullet
461
  formatted_plan = formatted_plan.replace('- ', '• ').replace('* ', '• ')
462
+ # Basic attempt to bold section headers - look for lines ending with a colon followed by a newline
463
+ # or a line starting with uppercase word(s) ending in a colon.
464
+ formatted_plan_lines = []
465
+ for line in formatted_plan.split('\n'):
466
+ stripped_line = line.strip()
467
+ if re.match(r'^[A-Z][A-Z\s]*\b[ \t]*:(\s|$)', stripped_line):
468
+ formatted_plan_lines.append(f"*{stripped_line}*")
469
+ else:
470
+ formatted_plan_lines.append(line)
471
+ formatted_plan = '\n'.join(formatted_plan_lines)
472
+
473
 
474
  message = f"*Care Plan Update*\n\n"
475
  message += f"*Patient Name:* {patient_info.get('name', 'N/A')}\n"
476
  message += f"*Age:* {patient_info.get('age', 'N/A')}\n"
477
  message += f"*Gender:* {patient_info.get('gender', 'N/A')}\n"
478
  message += f"*Status:* {status_emoji.get(status, 'Unknown')}\n\n"
479
+ # Add feedback if available
480
+ feedback_text = patient_info.get('feedback')
481
+ if feedback_text:
482
+ message += f"*Latest Feedback:*\n{feedback_text.strip()}\n\n"
483
+
484
  message += f"*Care Plan Details:*\n{formatted_plan}"
485
 
486
  print(f"Attempting to send WhatsApp message to {TWILIO_TO}...")
 
490
  to=TWILIO_TO
491
  )
492
  print(f"WhatsApp message sent, SID: {message_sent.sid}")
493
+ return True, "WhatsApp message sent successfully."
494
  except Exception as e:
495
  print(f"Error sending WhatsApp message: {e}")
496
+ return False, f"Error sending WhatsApp: {e}"
497
 
498
 
499
  @app.route('/')
500
  def index():
501
  role = session.get('role', 'patient')
502
+ # If doctor role is in session but user visits '/', redirect to doctor dashboard
503
  if role == 'doctor':
504
  return redirect(url_for('doctor_dashboard'))
505
+ # Otherwise, render patient index
506
  return render_template('index.html')
507
 
508
 
 
511
  role = request.form.get('role')
512
  if role in ['patient', 'doctor']:
513
  session['role'] = role
514
+ # Redirect to the appropriate page after switching
515
+ if role == 'doctor':
516
+ return redirect(url_for('doctor_dashboard'))
517
+ else:
518
+ return redirect(url_for('index')) # Redirect to patient home
519
  return jsonify({'success': False, 'error': 'Invalid role'}), 400
520
 
521
 
522
  @app.route('/doctor_dashboard')
523
  def doctor_dashboard():
524
+ # Ensure user is marked as doctor in session when accessing this page directly
525
+ # This handles cases where someone might type the URL directly
526
+ if session.get('role') != 'doctor':
527
+ session['role'] = 'doctor'
528
  return render_template('doctor_dashboard.html')
529
 
530
 
 
537
  name = request.form.get('name', 'Unnamed Patient')
538
  age = request.form.get('age')
539
  gender = request.form.get('gender', 'N/A')
540
+ feedback = request.form.get('feedback', '').strip() # Strip whitespace
541
 
542
  if not name or not feedback:
543
  return jsonify({'success': False, 'error': 'Patient Name and Feedback are required.'}), 400
544
+
545
+ # Basic sanitization/validation for age
546
  if age:
547
  try:
548
  age = int(age)
549
  if age <= 0: raise ValueError("Age must be positive")
550
+ if age > 200: raise ValueError("Age seems unreasonably high") # Sanity check
551
  except ValueError:
552
  return jsonify({'success': False, 'error': 'Invalid Age provided.'}), 400
553
  else:
554
+ age = None # Store as None if not provided
555
 
556
  care_plan_text = ""
557
  care_plan_format = None
 
559
  if 'care_plan_pdf' in request.files:
560
  pdf_file = request.files['care_plan_pdf']
561
  if pdf_file and pdf_file.filename != '':
562
+ # Check file extension
563
+ if not pdf_file.filename.lower().endswith('.pdf'):
564
+ return jsonify({'success': False, 'error': 'Invalid file type. Only PDF files are allowed.'}), 400
565
+
566
  care_plan_text = extract_text_from_pdf(pdf_file)
567
+
568
+ # If extraction resulted in an error message, set format to None
569
+ if "[Error extracting PDF text" in care_plan_text or "[No readable text found" in care_plan_text or "[PDF Content Unavailable" in care_plan_text:
570
+ care_plan_format = None
571
+ print(f"PDF text extraction failed or empty: {care_plan_text}")
572
+ else:
573
+ care_plan_format = extract_care_plan_format(care_plan_text)
574
+ print(f"Extracted text length: {len(care_plan_text)}. Format found: {care_plan_format is not None}")
575
  else:
576
  print("No PDF file uploaded or file is empty.")
577
 
578
 
579
+ # Define or get the format template
580
  if not care_plan_format or not care_plan_format.strip():
581
+ print("Using default care plan format as extraction failed or returned empty.")
582
  care_plan_format = """
583
  PATIENT INFORMATION:
584
  - Name: [Patient Name]
 
588
  - [Summary of patient's current condition based on feedback and previous plan]
589
  DAILY CARE PLAN:
590
  Morning:
591
+ - [Morning activities/medications/checks]
592
  Afternoon:
593
+ - [Afternoon activities/medications/checks]
594
  Evening:
595
+ - [Evening activities/medications/checks]
596
  Night:
597
+ - [Night activities/medications/sleep instructions/checks]
598
  MEDICATIONS:
599
+ - [List of medications, dosage, frequency, and time, including PRN (as needed) instructions]
600
  DIET AND HYDRATION:
601
+ - [Specific dietary recommendations (e.g., low sodium, diabetic), hydration goals]
602
  PHYSICAL ACTIVITY/EXERCISE:
603
+ - [Recommended physical activities, duration, frequency, limitations, and progression]
604
  SYMPTOM MANAGEMENT:
605
+ - [Detailed instructions for managing specific symptoms (e.g., pain, nausea, shortness of breath), non-pharmacological interventions]
606
  RED FLAGS / WHEN TO SEEK HELP:
607
+ - [Clear, actionable instructions on symptoms requiring immediate medical attention (Emergency - call 911/local emergency, go to ER)]
608
+ - [Instructions on symptoms requiring contact with doctor/clinic (Urgent but not Emergency - call office, visit clinic)]
609
  ADDITIONAL RECOMMENDATIONS:
610
+ - [Other relevant advice, e.g., rest, monitoring specific vital signs, wound care instructions, mental health support, caregiver tips]
611
  FOLLOW-UP:
612
+ - [Details about next scheduled appointment, or criteria for scheduling follow-up (e.g., "call if symptoms worsen significantly")]
613
+ """ # Enhanced default format
 
 
614
 
615
+ # Determine initial status based primarily on feedback
616
  initial_status = determine_patient_status(care_plan_text, "", feedback)
617
 
618
 
619
  generated_plan_text = ""
620
+ status = initial_status # Start with feedback-determined status
621
 
622
+ # Only generate AI plan if status isn't immediate emergency based on feedback
623
+ # If feedback triggers "emergency", the generated_plan_text is a fixed emergency plan.
624
  if status == 'emergency':
625
  print("Emergency status detected from feedback. Generating emergency plan.")
626
  generated_plan_text = (
627
+ "🚨 *EMERGENCY - IMMEDIATE ACTION REQUIRED* 🚨\n\n"
628
  "PATIENT INFORMATION:\n"
629
  f"- Name: {name}\n"
630
  f"- Age: {age if age is not None else 'N/A'}\n"
 
632
  "ASSESSMENT:\n"
633
  f"- Emergency symptoms reported: {feedback}. Immediate medical attention required.\n\n"
634
  "EMERGENCY ACTION PLAN:\n"
635
+ "- *Call emergency services immediately* at your local emergency number (e.g., 104/108/109/112).\n"
636
+ "- Do not delay seeking medical help. If possible, have someone stay with the patient.\n"
637
+ "- If conscious, help the patient into a comfortable position (e.g., upright for breathing difficulties, on back with legs elevated for shock).\n"
638
+ "- Do not give food or drink until evaluated by medical professionals.\n"
639
+ "- Prepare relevant medical history, medication list, and previous care plan if available for emergency responders.\n"
640
+ "- Follow *all* instructions from emergency responders.\n\n"
641
  "RED FLAGS / WHEN TO SEEK HELP:\n"
642
+ "- *Any* worsening of reported emergency symptoms requires urgent re-evaluation by medical professionals.\n"
643
+ "- Do not attempt to manage severe symptoms at home once emergency signs are present.\n\n"
644
  "FOLLOW-UP:\n"
645
  "- Immediate hospitalization or urgent medical evaluation is necessary.\n"
646
+ "- Inform your primary physician/care team as soon as medically stable.\n"
647
+ "- A new care plan will be developed after the emergency situation is resolved and evaluated by medical professionals.\n"
648
  )
649
+ # For emergency, the generated plan text *is* the final plan to save/send.
650
+ final_plan_to_save = generated_plan_text
651
+ final_status_to_save = 'emergency' # Explicitly keep emergency status
652
 
653
+ else: # Status is not emergency based on feedback, proceed with AI generation
654
  prompt = f"""
655
+ You are a helpful and highly structured AI assistant generating updated patient care plans.
656
+ Your task is to create a NEW, comprehensive, and well-structured daily care plan based on the provided patient's personal information, their current symptoms/feedback, and their previous care plan (if available).
657
+
658
  Patient Information:
659
  Name: {name}
660
  Age: {age if age is not None else 'N/A'}
661
  Gender: {gender}
662
+
663
  Patient Feedback/Symptoms Update:
664
  {feedback}
665
+
666
  Previous Care Plan Details (if available):
667
+ {care_plan_text if care_plan_text and "[No readable text found" not in care_plan_text and "[Error extracting PDF text" not in care_plan_text and "[PDF Content Unavailable" not in care_plan_text else "No previous care plan provided or could not be read."}
668
+
669
  Instructions:
670
+ 1. Generate the updated care plan strictly using the exact following format template.
671
+ 2. Populate each section of the template based on the patient's information, their *latest feedback/symptoms*, and integrate relevant, SAFE, and appropriate elements from the previous plan if they are still applicable and helpful given the feedback.
672
+ 3. Prioritize addressing the issues raised in the patient feedback.
673
+ 4. Be specific, actionable, and realistic in your recommendations (e.g., specify *what* activities, *when* to take medications).
674
+ 5. Ensure the language is clear, empathetic, and easy for a patient or caregiver to understand. Avoid overly technical jargon where possible.
675
+ 6. Include clear instructions in the "RED FLAGS / WHEN TO SEEK HELP" section, distinguishing between immediate emergency (e.g., call 911/local emergency) and urgent but non-emergency situations (e.g., call doctor's office).
676
+ 7. Do NOT include any introductory phrases (like "Here is the updated plan:", "Based on your feedback...") or concluding sentences outside the plan structure. Provide ONLY the structured content that fits within the template.
677
+ 8. If the previous care plan was unavailable or unreadable, create the plan based solely on the patient information and feedback, still following the template.
678
+ 9. Ensure the plan is medically sound and reflects standard care principles. If feedback indicates a significant change or potential issue, the ASSESSMENT and subsequent sections should clearly address this and recommend appropriate actions (like contacting their doctor for a re-evaluation, even if it's not an immediate emergency).
679
+ 10. If the feedback indicates significant improvement, the plan should reflect this (e.g., adjusting activity levels up, noting successful symptom management) while still including monitoring and red flags.
680
+
681
  Care Plan Format Template:
682
  {care_plan_format}
683
  """
 
687
  response = model.generate_content(prompt)
688
  generated_plan_text = response.text.strip()
689
 
690
+ # Remove markdown code block formatting if present
691
  if generated_plan_text.startswith('```') and generated_plan_text.endswith('```'):
692
  generated_plan_text = generated_plan_text[3:-3].strip()
693
+ # Remove language identifier if present (e.g., 'text', 'markdown')
694
+ if generated_plan_text.lower().startswith('text'):
695
  generated_plan_text = generated_plan_text[4:].strip()
696
+ elif generated_plan_text.lower().startswith('markdown'):
697
+ generated_plan_text = generated_plan_text[8:].strip()
698
+
699
 
700
  print(f"AI Response received. Length: {len(generated_plan_text)}")
701
 
702
+ # Re-evaluate status based on generated plan content in conjunction with feedback
703
+ # This is a secondary check. The primary status is from feedback.
704
+ # We only update status here if the *generated plan* explicitly uses keywords
705
+ # that might contradict or strongly reinforce the initial feedback-based status.
706
+ # However, to avoid AI hallucination changing status, let's stick to feedback-driven status for submission for now.
707
+ # The UI can highlight AI warnings if the plan mentions critical things the feedback didn't.
708
+ # For simplicity, the status saved to the DB will be the one determined *before* AI generation based on feedback.
709
+ final_plan_to_save = generated_plan_text
710
+ final_status_to_save = status # Use the status determined before AI call
711
 
712
  except Exception as ai_error:
713
  print(f"Error generating content from AI: {ai_error}")
714
+ # If AI fails, construct an error message plan
715
  generated_plan_text = f"[Error generating updated plan from AI: {ai_error}]\n\n"
716
+ if care_plan_text and "[No readable text found" not in care_plan_text and "[Error extracting PDF text" not in care_plan_text and "[PDF Content Unavailable" not in care_plan_text:
717
+ generated_plan_text += "Falling back to original plan if available:\n\n" + care_plan_text
718
+ # If falling back to original, status should reflect original plan/feedback, not just AI failure
719
+ final_status_to_save = determine_patient_status(care_plan_text, care_plan_text, feedback)
720
  else:
721
  generated_plan_text += "No previous plan available."
722
+ final_status_to_save = status # Keep the feedback-determined status
 
 
 
 
723
 
724
+ final_plan_to_save = generated_plan_text
725
 
726
  # Create and store patient record in the database
727
  new_patient = Patient(
 
729
  age=age,
730
  gender=gender,
731
  feedback=feedback,
732
+ original_plan=care_plan_text, # Store extracted text, could be error message
733
+ updated_plan=final_plan_to_save, # Store generated/fallback plan text
734
+ status=final_status_to_save, # Store determined status
735
  timestamp=datetime.utcnow()
736
  )
737
  db.session.add(new_patient)
738
  db.session.commit()
739
  patient_id = new_patient.id
740
 
741
+ print(f"Patient {patient_id} added to DB with status: {final_status_to_save}.")
742
 
743
  # Generate PDF for downloading using the stored data
744
+ # Note: We pass the patient object directly to the PDF generator for simplicity
745
+ # and to include feedback in the PDF
746
+ pdf_buffer = generate_care_plan_pdf(new_patient.to_dict(), new_patient.updated_plan, new_patient.status)
 
 
 
747
  pdf_base64 = base64.b64encode(pdf_buffer.getvalue()).decode('utf-8')
748
  print("PDF generated and base64 encoded.")
749
 
750
+ # Send care plan via WhatsApp (using the final saved data)
751
+ # Note: We pass the patient object directly to the WhatsApp function
752
+ whatsapp_sent, whatsapp_message = send_whatsapp_care_plan(new_patient.to_dict(), new_patient.updated_plan, new_patient.status)
753
+ print(f"WhatsApp message attempt sent: {whatsapp_sent}, message: {whatsapp_message}")
 
 
 
 
754
 
755
+ # Return success response, include relevant data
756
  return jsonify({
757
  'success': True, # Indicate that patient record was created/updated
758
+ 'updated_plan': new_patient.updated_plan, # Return the final saved plan
759
  'pdf_data': pdf_base64,
760
  'patient_id': patient_id,
761
+ 'status': new_patient.status, # Return the final determined status
762
  'whatsapp_sent': whatsapp_sent,
763
+ 'whatsapp_message': whatsapp_message,
764
+ 'ai_error': (not model) or ("Error generating updated plan" in new_patient.updated_plan) # Indicate if AI failed or was skipped
765
  })
766
 
767
  except Exception as e:
 
774
  'error': f'An unexpected server error occurred: {str(e)}'
775
  }), 500
776
 
777
+ # --- New routes for Doctor Dashboard actions ---
778
+
779
+ @app.route('/update_care_plan/<patient_id>', methods=['PUT'])
780
+ def update_care_plan(patient_id):
781
+ """Endpoint for doctor to save edited care plan text."""
782
+ try:
783
+ data = request.get_json()
784
+ if not data or 'updated_plan' not in data:
785
+ return jsonify({'success': False, 'error': 'Invalid data provided.'}), 400
786
+
787
+ updated_plan_text = data['updated_plan'].strip()
788
+
789
+ patient = Patient.query.get(patient_id)
790
+
791
+ if not patient:
792
+ print(f"Patient ID {patient_id} not found for update.")
793
+ return jsonify({'success': False, 'error': 'Patient not found.'}), 404
794
+
795
+ # Optional: Re-determine status based on the manually updated plan + existing feedback/original?
796
+ # For simplicity, we'll keep the existing status unless explicitly changed via another mechanism.
797
+ # If doctor edits to add critical info, the status should ideally be manually updated too.
798
+ # But for this request, just saving the text is sufficient.
799
+ # patient.status = determine_patient_status(patient.original_plan, updated_plan_text, patient.feedback) # This would make saving potentially change status
800
+
801
+ patient.updated_plan = updated_plan_text
802
+ db.session.commit()
803
+ print(f"Updated plan saved for patient ID: {patient_id}")
804
+
805
+ return jsonify({
806
+ 'success': True,
807
+ 'message': 'Care plan saved successfully.',
808
+ 'patient_id': patient_id,
809
+ # Optionally return the updated status if determined above
810
+ # 'status': patient.status
811
+ })
812
+
813
+ except Exception as e:
814
+ print(f"Error updating care plan for ID {patient_id}: {str(e)}")
815
+ import traceback
816
+ traceback.print_exc()
817
+ db.session.rollback()
818
+ return jsonify({'success': False, 'error': f'An error occurred while saving the plan: {str(e)}'}), 500
819
+
820
+
821
+ @app.route('/send_whatsapp_doctor/<patient_id>', methods=['POST'])
822
+ def send_whatsapp_doctor(patient_id):
823
+ """Endpoint for doctor to send WhatsApp message for a patient."""
824
+ try:
825
+ patient = Patient.query.get(patient_id)
826
+
827
+ if not patient:
828
+ print(f"Patient ID {patient_id} not found for WhatsApp send.")
829
+ return jsonify({'success': False, 'error': 'Patient not found.'}), 404
830
+
831
+ # Use the latest data from the database for the message
832
+ patient_info_for_whatsapp = patient.to_dict() # Gets name, age, gender, feedback etc.
833
+ care_plan_text_to_send = patient.updated_plan
834
+ status_to_send = patient.status
835
+
836
+ whatsapp_sent, whatsapp_message = send_whatsapp_care_plan(
837
+ patient_info_for_whatsapp,
838
+ care_plan_text_to_send,
839
+ status_to_send
840
+ )
841
+
842
+ if whatsapp_sent:
843
+ print(f"WhatsApp sent successfully for patient ID: {patient_id}")
844
+ return jsonify({'success': True, 'message': whatsapp_message})
845
+ else:
846
+ print(f"WhatsApp failed for patient ID: {patient_id} - {whatsapp_message}")
847
+ return jsonify({'success': False, 'error': whatsapp_message}), 500 # Use 500 for server-side error
848
+
849
+ except Exception as e:
850
+ print(f"Error triggering WhatsApp send for ID {patient_id}: {str(e)}")
851
+ import traceback
852
+ traceback.print_exc()
853
+ return jsonify({'success': False, 'error': f'An error occurred while sending WhatsApp: {str(e)}'}), 500
854
+
855
+
856
+ # --- Existing routes remain below ---
857
 
858
  @app.route('/download_pdf/<patient_id>')
859
  def download_pdf(patient_id):
 
866
  print(f"Patient ID {patient_id} not found in database for download.")
867
  return "Patient data not found.", 404
868
 
869
+ # Pass the full patient dict to PDF generator for feedback inclusion
 
 
 
 
870
  pdf_buffer = generate_care_plan_pdf(
871
+ patient.to_dict(),
872
  patient.updated_plan,
873
  patient.status
874
  )
 
894
 
895
  @app.route('/get_emergency_notifications')
896
  def get_emergency_notifications():
897
+ # Only include patients whose status is 'emergency'
898
  emergency_patients_query = Patient.query.filter_by(status='emergency').order_by(Patient.timestamp.desc())
899
 
900
+ # Only return basic info needed for the alert, not full patient details
901
+ notifications = [{'id': p.id, 'name': p.name, 'status': p.status} for p in emergency_patients_query.all()]
902
 
903
  return jsonify({
904
  'success': True,
 
909
  @app.route('/get_patients')
910
  def get_patients():
911
  all_patients_query = Patient.query.order_by(Patient.timestamp.desc())
912
+ # Include status in the dictionary representation
913
  patients_list = [p.to_dict() for p in all_patients_query.all()]
914
  return jsonify({
915
  'success': True,
 
927
  return jsonify({'success': False, 'error': 'Patient not found.'}), 404
928
 
929
  print(f"Found patient {patient_id}: {patient.name}")
930
+ # Return the full patient data including original and updated plans
931
  return jsonify({
932
  'success': True,
933
  'patient': patient.to_dict()
934
  })
935
 
936
+ # Route to handle patient deletion
937
  @app.route('/delete_patient/<patient_id>', methods=['DELETE'])
938
  def delete_patient(patient_id):
939
  print(f"Delete requested for patient ID: {patient_id}")
 
961
 
962
 
963
  if __name__ == '__main__':
964
+ # Create database tables if they don't exist within the application context
965
  with app.app_context():
966
  db.create_all()
967
  # Use a more robust development server like Waitress or Gunicorn in production
968
+ # For development, debug=True is fine
969
  app.run(debug=True)