rajkhanke commited on
Commit
ff78ca3
·
verified ·
1 Parent(s): 1208a04

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +282 -301
app.py CHANGED
@@ -14,81 +14,74 @@ from reportlab.pdfgen import canvas
14
  from reportlab.lib import colors
15
  from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
16
  from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle
17
- import traceback # Import traceback for better error logging
18
 
19
  app = Flask(__name__)
20
 
21
  # Use a strong, unique secret key
22
  app.secret_key = os.getenv('SECRET_KEY', '688ed745a74bdd7ac238f5b50f4104fb87d6774b8b0a4e06e7e18ac5ed0fa31c') # CHANGE THIS IN PRODUCTION
23
 
24
- # Configure upload folder (not strictly used for persistent storage in this demo)
25
  upload_base = os.getenv('UPLOAD_DIR', './uploads')
26
  upload_folder = os.path.join(upload_base, 'pdfs') # Changed folder name for clarity
27
 
28
  app.config['UPLOAD_FOLDER'] = upload_folder
29
- os.makedirs(upload_folder, exist_ok=True) # Ensure upload directory exists
30
 
31
  # Twilio Configuration
32
  # IMPORTANT: Use environment variables for sensitive information like SID and Auth Token
33
  ACCOUNT_SID = os.getenv('TWILIO_ACCOUNT_SID', 'AC490e071f8d01bf0df2f03d086c788d87')
34
  AUTH_TOKEN = os.getenv('TWILIO_AUTH_TOKEN', '224b23b950ad5a4052aba15893fdf083')
35
- TWILIO_FROM = os.getenv('TWILIO_FROM_NUMBER', 'whatsapp:+14155238886') # Twilio Sandbox number example
36
  TWILIO_TO = os.getenv('TWILIO_TO_NUMBER', 'whatsapp:+917559355282') # Hardcoded number as requested
37
 
38
  # Initialize Twilio client
39
- twilio_client = None
40
  try:
41
- if ACCOUNT_SID and AUTH_TOKEN:
42
- twilio_client = Client(ACCOUNT_SID, AUTH_TOKEN)
43
- # Optional: Verify account SID is valid (can raise error if not)
44
- # twilio_client.api.account.fetch()
45
- print("Twilio client initialized successfully.")
46
- else:
47
- print("TWILIO_ACCOUNT_SID or TWILIO_AUTH_TOKEN not set. WhatsApp sending disabled.")
48
  except Exception as e:
49
  print(f"Error initializing Twilio client: {e}")
50
- traceback.print_exc()
51
- twilio_client = None # Ensure client is None if initialization fails
52
-
53
 
54
  # Gemini API Configuration
55
  # IMPORTANT: Use environment variables for sensitive information like API keys
56
- GENAI_API_KEY = os.getenv('GENAI_API_KEY', "AIzaSyD54ejbjVIVa-F3aD_Urnp8m1EFLUGR__I") # CHANGE THIS KEY - Use your actual key
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  model = None
58
- if GENAI_API_KEY:
59
  try:
60
- genai.configure(api_key=GENAI_API_KEY)
61
- # Simple test to check connectivity (optional, can slow down startup)
62
- # list(genai.list_models())
63
- print("Gemini API configured successfully.")
64
-
65
- generation_config = {
66
- "temperature": 0.8,
67
- "top_p": 0.9,
68
- "top_k": 30,
69
- "max_output_tokens": 4096,
70
- }
71
- # Using a recommended stable model alias
72
  model = genai.GenerativeModel(
73
- model_name="gemini-1.5-flash-latest",
74
  generation_config=generation_config,
75
  )
76
- # Optional: Test model generation (can slow down startup)
77
  # model.generate_content("Hello")
78
  print(f"Using Gemini model: {model.model_name}")
79
-
80
  except Exception as e:
81
- print(f"Error configuring or initializing Gemini API/model: {e}")
82
- traceback.print_exc()
83
- model = None # Ensure model is None if configuration or initialization fails
84
- else:
85
- print("GENAI_API_KEY not set. AI generation disabled.")
86
 
87
 
88
- # Patient database (in-memory for DEMO ONLY - DATA WILL BE LOST ON SERVER RESTART)
89
- # Replace with a database (SQLite, PostgreSQL, etc.) for production use.
90
  patients_db = {}
91
- print("Patient database initialized (in-memory). Data will be lost on server restart.")
92
 
93
  def extract_text_from_pdf(pdf_file):
94
  """Extract text from uploaded PDF file"""
@@ -104,37 +97,38 @@ def extract_text_from_pdf(pdf_file):
104
  return text.strip()
105
  except Exception as e:
106
  print(f"Error extracting PDF text: {e}")
107
- traceback.print_exc()
108
  return ""
109
 
110
  def extract_care_plan_format(pdf_text):
111
  """Extract a general format template from PDF text by identifying common section headers."""
112
  if not pdf_text:
113
- print("No PDF text provided for format extraction.")
114
  return None
115
 
116
- # Improved regex to find lines that look like headers
117
- # Looks for lines starting with 1+ uppercase letters (possibly with spaces)
118
- # followed by a colon, optionally with a dot or other punctuation, end of line
119
- # Example: ASSESSMENT: or DAILY CARE PLAN: or MEDICATIONS.
120
- potential_headers = re.findall(r'^[A-Z][A-Z\s]*(?:[:.]\s*)?\n?', pdf_text, re.MULTILINE)
 
 
 
121
 
122
- if not potential_headers:
123
- print("No potential headers found in PDF text.")
 
 
 
 
 
 
 
124
  return None # No recognizable format found
125
 
126
- # Use a set to get unique headers, clean them up
127
- cleaned_headers = sorted(list(set([h.strip(' :.\n') for h in potential_headers if h.strip()])))
128
-
129
- if not cleaned_headers:
130
- print("Cleaned headers list is empty.")
131
- return None
132
-
133
  format_template = ""
134
- for header in cleaned_headers:
135
- format_template += f"{header.strip()}:\n- [Details based on feedback and previous plan]\n" # Use a placeholder bullet structure
136
- print(f"Extracted format template based on headers: {cleaned_headers}")
137
- return format_template.strip() if format_template.strip() else None
138
 
139
 
140
  def determine_patient_status(original_plan, updated_plan, feedback):
@@ -145,75 +139,69 @@ def determine_patient_status(original_plan, updated_plan, feedback):
145
  updated_plan_lower = updated_plan.lower()
146
 
147
  # Comprehensive keywords indicating condition worsening or emergency
148
- # Added more specific medical terms
149
  emergency_keywords = [
150
  "severe chest pain", "heart attack", "sudden shortness of breath",
151
- "difficulty breathing", "severe difficulty breathing", "air hunger", "gasping", "wheezing",
152
- "loss of consciousness", "syncope", "extreme pain", "unbearable pain", "10/10 pain",
153
- "sudden weakness", "paralysis", "numbness", "confusion", "sudden confusion", "disorientation",
154
- "slurred speech", "sudden slurred speech", "drooping face", # Stroke symptoms
155
- "severe headache", "sudden severe headache", "thunderclap headache",
156
- "severe abdominal pain", "acute abdomen", "persistent vomiting", "vomiting blood", "hematemesis",
157
- "uncontrolled bleeding", "heavy bleeding", "hemorrhage",
158
- "severe allergic reaction", "anaphylaxis", "swelling face", "swollen tongue",
159
- "immediate medical attention", "emergency", "call 911", "urgent care", "hospital", "critical", "ambulance",
160
- "collapsed", "unconscious", "unresponsive", "stroke", "seizure", "convulsion", "status epilepticus",
161
- "not breathing", "apnea", "blue lips", "cyanosis", "blue face", "cardiac arrest", "no pulse",
162
- "high fever", "fever over 104f", "signs of shock", "low blood pressure", "rapid heart rate",
163
- "severe dehydration", "unable to keep fluids down", "acute change", "rapidly worsening",
164
- "unstable vitals", "chest pressure", "radiating pain", "vision loss", "sudden vision change"
165
  ]
166
 
167
  deteriorating_keywords = [
168
- "worsening", "increased pain", "pain getting worse", "not improving", "no improvement", "deteriorating",
169
- "elevated", "higher", "levels rising", "more frequent", "happening more often", "concerning", "cause for concern",
170
- "decline", "decreased function", "less able", "more difficult", "worse", "getting worse",
171
- "aggravated", "intensified", "escalating", "progressing", "progressive", "regressing", "setback",
172
- "not responding", "treatment ineffective", "increased symptoms", "symptoms worsening", "more severe",
173
- "complicated", "adverse change", "unstable", "needs attention", "spike in", "significant increase",
174
- "new symptoms", "trouble with", "difficulty with", "reduced appetite", "poor appetite",
175
- "difficulty sleeping", "insomnia", "increased fatigue", "more tired", "losing weight", "significant weight loss",
176
- "swelling increasing", "redness spreading", "discharge increasing", "uncontrolled", "lab values changing negatively"
177
  ]
178
 
179
  improvement_keywords = [
180
- "improving", "better", "feeling better", "reduced", "lower", "less pain", "pain decreased",
181
- "increased function", "easier to move", "healing", "recovery", "progress", "stable", "maintained", "consistent",
182
- "well-controlled", "under control", "responsive", "responding well", "good progress", "getting better", "positive change",
183
- "improved", "enhancement", "advancement", "resolving", "resolved", "clearing up", "recovering",
184
- "normalized", "normal range", "responding positively", "effective treatment", "successful treatment",
185
- "managed well", "symptoms decreased", "less severe symptoms", "feeling stronger", "better sleep", "increased appetite",
186
- "weight stable", "swelling decreasing", "redness decreasing", "discharge decreasing", "lab values improving",
187
- "more energy", "easier to breathe"
188
  ]
189
 
190
- # Check for emergency keywords first (highest priority)
191
- # Prioritize feedback first, then the generated plan text
192
  if any(keyword in feedback_lower for keyword in emergency_keywords):
193
- print("Emergency keywords found in feedback.")
194
  return "emergency"
 
195
  if any(keyword in updated_plan_lower for keyword in emergency_keywords):
196
- print("Emergency keywords found in updated plan.")
197
  return "emergency"
198
 
199
  # Check for deteriorating keywords
200
  if any(keyword in feedback_lower for keyword in deteriorating_keywords):
201
- print("Deteriorating keywords found in feedback.")
202
  return "deteriorating"
 
203
  if any(keyword in updated_plan_lower for keyword in deteriorating_keywords):
204
- print("Deteriorating keywords found in updated plan.")
205
  return "deteriorating"
206
 
207
  # Check for improvement keywords
208
  if any(keyword in feedback_lower for keyword in improvement_keywords):
209
- print("Improvement keywords found in feedback.")
210
  return "improving"
 
211
  if any(keyword in updated_plan_lower for keyword in improvement_keywords):
212
- print("Improvement keywords found in updated plan.")
213
  return "improving"
214
 
215
- # Default to stable if no clear indicators are found
216
- print("No clear status indicators found. Defaulting to stable.")
 
 
 
 
 
 
217
  return "stable"
218
 
219
 
@@ -235,8 +223,7 @@ def generate_care_plan_pdf(patient_info, care_plan_text, status):
235
  fontSize=22, # Slightly larger title
236
  alignment=1, # Center alignment
237
  spaceAfter=25, # More space after title
238
- textColor=colors.HexColor("#4e73df"), # Primary color
239
- fontName='Helvetica-Bold'
240
  )
241
 
242
  heading_style = ParagraphStyle(
@@ -245,8 +232,7 @@ def generate_care_plan_pdf(patient_info, care_plan_text, status):
245
  fontSize=15, # Slightly larger headings
246
  spaceAfter=8, # Less space after heading for tighter look
247
  spaceBefore=18, # More space before heading
248
- textColor=colors.HexColor("#1cc88a"), # Secondary color
249
- fontName='Helvetica-Bold'
250
  )
251
 
252
  normal_style = ParagraphStyle(
@@ -255,8 +241,7 @@ def generate_care_plan_pdf(patient_info, care_plan_text, status):
255
  fontSize=11,
256
  spaceAfter=6, # More space after paragraphs
257
  leading=14,
258
- textColor=colors.HexColor("#5a5c69"), # Dark color
259
- fontName='Helvetica'
260
  )
261
 
262
  bullet_style = ParagraphStyle(
@@ -267,9 +252,7 @@ def generate_care_plan_pdf(patient_info, care_plan_text, status):
267
  leftIndent=20,
268
  leading=14,
269
  bulletIndent=10, # Space between bullet and text
270
- textColor=colors.HexColor("#5a5c69"), # Dark color
271
- fontName='Helvetica',
272
- bulletText='•' # Explicitly set bullet character
273
  )
274
 
275
  # Status color mapping
@@ -277,8 +260,7 @@ def generate_care_plan_pdf(patient_info, care_plan_text, status):
277
  'emergency': colors.HexColor("#e74a3b"), # Danger color
278
  'deteriorating': colors.HexColor("#f6c23e"), # Warning color
279
  'improving': colors.HexColor("#1cc88a"), # Success color
280
- 'stable': colors.HexColor("#36b9cc"), # Info color
281
- 'unknown': colors.HexColor("#5a5c69") # Dark grey color
282
  }
283
 
284
  status_text_styles = {
@@ -286,7 +268,7 @@ def generate_care_plan_pdf(patient_info, care_plan_text, status):
286
  'deteriorating': ParagraphStyle('StatusDeteriorating', parent=styles['Heading2'], fontSize=15, spaceBefore=10, spaceAfter=15, textColor=status_colors['deteriorating'], alignment=1, fontName='Helvetica-Bold'),
287
  'improving': ParagraphStyle('StatusImproving', parent=styles['Heading2'], fontSize=15, spaceBefore=10, spaceAfter=15, textColor=status_colors['improving'], alignment=1, fontName='Helvetica-Bold'),
288
  'stable': ParagraphStyle('StatusStable', parent=styles['Heading2'], fontSize=15, spaceBefore=10, spaceAfter=15, textColor=status_colors['stable'], alignment=1, fontName='Helvetica-Bold'),
289
- 'unknown': ParagraphStyle('StatusUnknown', parent=styles['Heading2'], fontSize=15, spaceBefore=10, spaceAfter=15, textColor=status_colors['unknown'], alignment=1, fontName='Helvetica-Bold'),
290
  }
291
 
292
 
@@ -339,58 +321,94 @@ def generate_care_plan_pdf(patient_info, care_plan_text, status):
339
  story.append(Paragraph("Care Plan Details:", heading_style))
340
  story.append(Spacer(1, 10))
341
 
342
- # Process care plan text by lines/paragraphs
343
- # Simple split by double newline to separate major blocks
344
- blocks = re.split(r'\n\s*\n', care_plan_text.strip())
345
-
346
- for block in blocks:
347
- lines = block.strip().split('\n')
348
- if not lines:
349
- continue
350
-
351
- first_line = lines[0].strip()
352
-
353
- # Check if the first line looks like a section header
354
- # Example: "ASSESSMENT:", "DAILY CARE PLAN:"
355
- if re.match(r'^[A-Z][A-Z\s]*:?\s*$', first_line):
356
- # Use heading style for the header
357
- story.append(Paragraph(first_line, heading_style))
358
- content_lines = lines[1:] # Rest of the lines are content
359
- story.append(Spacer(1, 5)) # Small space after header
360
-
361
- # Process content lines for bullets
362
- bullet_points = re.findall(r'^\s*[-•*]\s*(.*?)$', "\n".join(content_lines), re.MULTILINE)
363
-
364
- if bullet_points:
365
- for point in bullet_points:
366
- if point.strip():
367
- story.append(Paragraph(f"• {point.strip()}", bullet_style))
368
- else:
369
- # If no bullet points found, treat as regular paragraph content
370
- content_text = "\n".join(content_lines).strip()
371
- if content_text:
372
- story.append(Paragraph(content_text, normal_style))
373
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
374
  else:
375
- # If the block doesn't start with a header, treat the whole block as a paragraph
376
- content_text = "\n".join(lines).strip()
377
- if content_text:
378
- # Also check if this block contains bullets, format accordingly
379
- bullet_points = re.findall(r'^\s*[-•*]\s*(.*?)$', content_text, re.MULTILINE)
380
- if bullet_points:
381
- for point in bullet_points:
382
- if point.strip():
383
- story.append(Paragraph(f"• {point.strip()}", bullet_style))
384
- else:
385
- story.append(Paragraph(content_text, normal_style))
386
-
387
- story.append(Spacer(1, 12)) # Space between blocks
388
 
 
 
389
 
390
  # Add footer or timestamp if desired
391
  story.append(Spacer(1, 20))
392
- footer_style = ParagraphStyle('Footer', parent=styles['Normal'], fontSize=9, alignment=1, textColor=colors.grey, fontName='Helvetica')
393
- story.append(Paragraph(f"Generated by Patient Care Management System on {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", footer_style))
394
 
395
 
396
  # Build the PDF
@@ -404,9 +422,6 @@ def send_whatsapp_care_plan(patient_info, care_plan_text, status):
404
  if not twilio_client:
405
  print("Twilio client not initialized. Cannot send WhatsApp message.")
406
  return False
407
- if not TWILIO_FROM or not TWILIO_TO:
408
- print("TWILIO_FROM or TWILIO_TO number not set. Cannot send WhatsApp message.")
409
- return False
410
 
411
  try:
412
  # Status emoji mapping
@@ -414,8 +429,7 @@ def send_whatsapp_care_plan(patient_info, care_plan_text, status):
414
  'emergency': "🚨 EMERGENCY",
415
  'deteriorating': "⚠️ HIGH RISK",
416
  'improving': "✅ IMPROVING",
417
- 'stable': "🟦 STABLE",
418
- 'unknown': "❓ STATUS UNKNOWN"
419
  }
420
 
421
  # Format message for WhatsApp including patient details and the full plan text
@@ -423,27 +437,20 @@ def send_whatsapp_care_plan(patient_info, care_plan_text, status):
423
  message += f"*Patient Name:* {patient_info.get('name', 'N/A')}\n"
424
  message += f"*Age:* {patient_info.get('age', 'N/A')}\n"
425
  message += f"*Gender:* {patient_info.get('gender', 'N/A')}\n"
426
- message += f"*Status:* {status_emoji.get(status, 'unknown')}\n\n"
427
- message += f"*Care Plan Details:*\n\n{care_plan_text.strip()}" # Include the full plan text
428
 
429
- # Send WhatsApp message using the configured numbers
430
  message = twilio_client.messages.create(
431
  from_=TWILIO_FROM,
432
  body=message,
433
  to=TWILIO_TO
434
  )
435
- print(f"WhatsApp message sent successfully, SID: {message.sid}")
436
  return True
437
  except Exception as e:
438
  print(f"Error sending WhatsApp message: {e}")
439
- traceback.print_exc()
440
- # Log specific Twilio error details if available
441
- if hasattr(e, 'status'):
442
- print(f"Twilio API Error Status: {e.status}")
443
- if hasattr(e, 'code'):
444
- print(f"Twilio API Error Code: {e.code}")
445
- if hasattr(e, 'msg'):
446
- print(f"Twilio API Error Message: {e.msg}")
447
  return False
448
 
449
 
@@ -463,50 +470,44 @@ def switch_role():
463
  role = request.form.get('role')
464
  if role in ['patient', 'doctor']:
465
  session['role'] = role
466
- # Redirect to the appropriate page after switching role
467
- if role == 'doctor':
468
- return jsonify({'success': True, 'role': role, 'redirect': url_for('doctor_dashboard')})
469
- else: # patient
470
- return jsonify({'success': True, 'role': role, 'redirect': url_for('index')})
471
-
472
  return jsonify({'success': False, 'error': 'Invalid role'}), 400
473
 
474
 
475
  @app.route('/doctor_dashboard')
476
  def doctor_dashboard():
477
  # This route will render the dashboard template
478
- # The patient list and details will be loaded via AJAX by the JavaScript
479
  return render_template('doctor_dashboard.html')
480
 
481
 
482
- # This route is not strictly needed for displaying patient details anymore
483
- # as the doctor_dashboard.html handles it via AJAX based on URL parameter
484
- # Keeping it as a redirect target for potentially old links or direct access attempts
485
  @app.route('/patient_details/<patient_id>')
486
- def patient_details_redirect(patient_id):
487
- print(f"Redirecting to doctor_dashboard with patient ID: {patient_id}")
488
- # Redirect to the main dashboard route with the patient ID as a query parameter
489
- return redirect(url_for('doctor_dashboard', patient_id=patient_id))
 
 
 
 
 
 
 
490
 
491
 
492
  @app.route('/submit_feedback', methods=['POST'])
493
  def submit_feedback():
494
  if not model:
495
- print("AI model not initialized.")
496
- return jsonify({'success': False, 'error': 'AI model not initialized. Please check API key and configuration.'}), 500
497
 
498
  try:
499
  # Get patient information from form
500
- name = request.form.get('name', 'Unnamed Patient').strip()
501
- age = request.form.get('age', 'N/A').strip()
502
- gender = request.form.get('gender', 'N/A').strip()
503
- feedback = request.form.get('feedback', '').strip()
504
-
505
- if not feedback:
506
- return jsonify({'success': False, 'error': 'Feedback is required.'}), 400
507
- if not name or not age or gender == 'N/A':
508
- return jsonify({'success': False, 'error': 'Patient Name, Age, and Gender are required.'}), 400
509
-
510
 
511
  care_plan_text = ""
512
  care_plan_format = None
@@ -515,18 +516,13 @@ def submit_feedback():
515
  if 'care_plan_pdf' in request.files:
516
  pdf_file = request.files['care_plan_pdf']
517
  if pdf_file and pdf_file.filename != '':
518
- print(f"Processing uploaded PDF: {pdf_file.filename}")
519
  care_plan_text = extract_text_from_pdf(pdf_file)
520
  if care_plan_text:
521
  care_plan_format = extract_care_plan_format(care_plan_text)
522
- print(f"Extracted text length: {len(care_plan_text) if care_plan_text else 0}. Format found: {care_plan_format is not None}")
523
- if not care_plan_text:
524
- print("PDF text extraction failed.")
525
- # Optionally return an error or proceed without the PDF content
526
- # For now, we'll proceed, treating it like no PDF was uploaded.
527
 
528
-
529
- # If no format is found in the PDF, use a robust default format
530
  if not care_plan_format or not care_plan_format.strip():
531
  print("Using default care plan format.")
532
  care_plan_format = """
@@ -549,23 +545,23 @@ Night:
549
  - [Night activities/medications/sleep instructions]
550
 
551
  MEDICATIONS:
552
- - [List of medications, dosage, frequency, and time. Be specific if possible.]
553
 
554
  DIET AND HYDRATION:
555
  - [Dietary recommendations and hydration goals]
556
 
557
  PHYSICAL ACTIVITY/EXERCISE:
558
- - [Recommended physical activities, duration, frequency, precautions]
559
 
560
  SYMPTOM MANAGEMENT:
561
  - [Instructions for managing specific symptoms, including what to do if they worsen]
562
 
563
  RED FLAGS / WHEN TO SEEK HELP:
564
- - [Clear instructions on symptoms requiring IMMEDIATE (Emergency) medical attention - CALL 911/local emergency number]
565
- - [Instructions on symptoms requiring URGENT contact with doctor/clinic (not Emergency)]
566
 
567
  ADDITIONAL RECOMMENDATIONS:
568
- - [Other relevant advice, e.g., rest, monitoring, specific precautions, mental health]
569
 
570
  FOLLOW-UP:
571
  - [Details about next appointment or when to contact healthcare provider]
@@ -573,14 +569,29 @@ FOLLOW-UP:
573
  else:
574
  print("Using extracted care plan format.")
575
 
576
- # Determine initial status based on feedback alone for immediate emergency check
577
- initial_status = determine_patient_status(care_plan_text, "", feedback) # Pass "" for updated_plan for initial check
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
578
 
579
  generated_plan_text = ""
580
- status = initial_status # Start with status based on feedback
581
 
582
- if status == 'emergency':
583
- print("Emergency status detected from feedback. Generating emergency instructions.")
584
  # If emergency symptoms are detected in feedback, generate emergency instructions
585
  generated_plan_text = (
586
  "PATIENT INFORMATION:\n"
@@ -588,26 +599,25 @@ FOLLOW-UP:
588
  f"- Age: {age}\n"
589
  f"- Gender: {gender}\n\n"
590
  "ASSESSMENT:\n"
591
- f"- Emergency symptoms reported based on feedback: {feedback}. Immediate medical attention required.\n\n"
592
  "EMERGENCY ACTION PLAN:\n"
593
- "- Call emergency services immediately at 104/108/109/112 or your local emergency number (e.g., 911 in the US).\n"
594
- "- Do not delay seeking medical help. This is a medical emergency.\n"
595
- "- If conscious, try to remain calm and follow dispatcher instructions.\n"
596
- "- If patient is unconscious or not breathing, begin CPR if trained and instructed by emergency services.\n"
597
  "- Do not eat or drink anything until evaluated by medical professionals.\n"
598
- "- Have patient's medication list and medical history ready for emergency responders.\n\n"
599
  "RED FLAGS / WHEN TO SEEK HELP:\n"
600
- "- *Any* worsening of reported symptoms or development of new severe symptoms requires urgent re-evaluation by emergency medical services.\n\n"
601
  "FOLLOW-UP:\n"
602
  "- Immediate hospitalization or urgent medical evaluation is necessary.\n"
603
- "- Inform your primary physician/specialist as soon as medically stable.\n"
604
- "- Review and update this care plan *only* after the emergency situation is resolved and evaluated by medical professionals in a hospital setting.\n"
605
  )
606
- # Status is already 'emergency'
607
 
608
  else:
609
- print(f"Initial status is {status}. Proceeding with AI plan generation.")
610
- # Prepare a prompt for Gemini AI
611
  prompt = f"""
612
  You are a helpful assistant generating updated patient care plans.
613
  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.
@@ -620,41 +630,38 @@ Gender: {gender}
620
  Patient Feedback/Symptoms Update:
621
  {feedback}
622
 
623
- Previous Care Plan Details (if available, analyze this for context and structure, but provide an updated plan):
624
- {care_plan_text if care_plan_text else "No previous care plan provided. Generate a standard comprehensive daily plan based on patient info and feedback."}
625
 
626
  Instructions:
627
- 1. Generate the updated care plan using a structure similar to the following format template. Adapt section titles as appropriate based on the previous plan or standard medical practice, but ensure all key areas are covered.
628
- 2. Be specific, actionable, and medically sound in your recommendations. Avoid generic advice where possible.
629
- 3. Ensure the language is clear and easy to understand for a patient.
630
- 4. Include realistic times for medications if applicable.
631
- 5. Highlight important instructions or warnings, especially regarding symptom management and when to seek help (clearly state what requires immediate emergency services vs. contacting the doctor).
632
- 6. Do NOT include any introductory or concluding sentences outside the plan content itself. Provide ONLY the structured plan content.
633
- 7. Ensure the generated plan incorporates information from BOTH the feedback and the previous plan.
634
-
635
- Care Plan Format Guideline (Adapt structure as needed, cover these topics):
636
  {care_plan_format}
637
  """
638
  print("Sending prompt to AI model...")
639
- # print(f"Prompt:\n---\n{prompt}\n---") # Avoid printing full prompt in logs for privacy/length
640
 
641
  # Get response from Gemini AI
642
  try:
643
  response = model.generate_content(prompt)
644
  generated_plan_text = response.text.strip() # Get the text and strip leading/trailing whitespace
645
  print(f"AI Response received. Length: {len(generated_plan_text)}")
646
- # print(f"AI Response:\n---\n{generated_plan_text}\n---") # Avoid printing full response in logs
647
 
648
  # Determine patient status based on AI's output and feedback
649
  status = determine_patient_status(care_plan_text, generated_plan_text, feedback)
650
- print(f"Determined status after AI generation: {status}")
651
 
652
  except Exception as ai_error:
653
  print(f"Error generating content from AI: {ai_error}")
654
- traceback.print_exc()
655
  return jsonify({
656
  'success': False,
657
- 'error': f'Error generating care plan with AI: {ai_error}. Please check API key and model access.'
658
  }), 500
659
 
660
  # --- Actions after plan generation (Emergency or AI) ---
@@ -670,9 +677,9 @@ Care Plan Format Guideline (Adapt structure as needed, cover these topics):
670
  'original_plan': care_plan_text, # Store original text from PDF
671
  'updated_plan': generated_plan_text, # Store the generated plan text
672
  'status': status,
673
- 'timestamp': datetime.now().strftime("%Y-%m-%d %H:%M:%S") # Consistent timestamp format
674
  }
675
- print(f"Patient {patient_id} added to in-memory DB with status: {status}. Current DB keys: {list(patients_db.keys())}")
676
 
677
 
678
  # Generate PDF for downloading using the stored data
@@ -681,16 +688,11 @@ Care Plan Format Guideline (Adapt structure as needed, cover these topics):
681
  'age': age,
682
  'gender': gender
683
  }
684
- # Use the already determined status and updated plan text
685
- pdf_buffer = generate_care_plan_pdf(
686
- patient_info_for_pdf,
687
- generated_plan_text,
688
- status
689
- )
690
  pdf_base64 = base64.b64encode(pdf_buffer.getvalue()).decode('utf-8')
691
- print("PDF generated and base64 encoded for preview/download.")
692
 
693
- # Send care plan via WhatsApp (Text Only)
694
  patient_info_for_whatsapp = {
695
  'name': name,
696
  'age': age,
@@ -702,36 +704,34 @@ Care Plan Format Guideline (Adapt structure as needed, cover these topics):
702
  return jsonify({
703
  'success': True,
704
  'updated_plan': generated_plan_text, # Send back the generated text
705
- 'pdf_data': pdf_base64, # Send base64 PDF for preview on index.html
706
  'patient_id': patient_id, # Send the generated patient ID
707
  'status': status,
708
  'whatsapp_sent': whatsapp_sent
709
  })
710
 
711
  except Exception as e:
712
- print(f"An unexpected error occurred during submission: {str(e)}")
 
 
713
  traceback.print_exc()
714
  return jsonify({
715
  'success': False,
716
- 'error': f'An unexpected server error occurred: {str(e)}'
717
  }), 500
718
 
719
 
720
  @app.route('/download_pdf/<patient_id>')
721
  def download_pdf(patient_id):
722
  print(f"Download requested for patient ID: {patient_id}")
723
- # print(f"Current patients_db keys: {list(patients_db.keys())}") # Log DB state (can be noisy)
724
 
725
  try:
726
  patient = patients_db.get(patient_id)
727
 
728
  if not patient:
729
  print(f"Patient ID {patient_id} not found in patients_db for download.")
730
- # Check if it might be an older ID lost due to server restart
731
- # For this demo, we just report it's not found in the current state
732
- return "Patient data not found (it may have been cleared due to server restart). Please regenerate the plan.", 404
733
-
734
- print(f"Found patient {patient_id} for download: {patient.get('name')}")
735
 
736
  # Use the patient's stored information to regenerate the PDF content
737
  patient_info_for_pdf = {
@@ -742,16 +742,15 @@ def download_pdf(patient_id):
742
  # Use the already determined status and updated plan text
743
  pdf_buffer = generate_care_plan_pdf(
744
  patient_info_for_pdf,
745
- patient.get('updated_plan', 'No updated plan available.'),
746
- patient.get('status', 'unknown')
747
  )
748
 
749
  pdf_buffer.seek(0) # Ensure buffer is at the start
750
 
751
  # Sanitize patient name for filename
752
- patient_name = patient.get('name', 'patient')
753
- safe_name = re.sub(r'[^a-zA-Z0-9_\-]', '', patient_name.replace(' ', '_')).lower()
754
- download_name = f"care_plan_{safe_name}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf"
755
 
756
  print(f"Serving PDF for {patient_id} as {download_name}")
757
  return send_file(
@@ -762,6 +761,7 @@ def download_pdf(patient_id):
762
  )
763
  except Exception as e:
764
  print(f"PDF Download Error for ID {patient_id}: {str(e)}")
 
765
  traceback.print_exc()
766
  return f"Error generating PDF: {str(e)}", 500
767
 
@@ -772,13 +772,7 @@ def get_emergency_notifications():
772
  emergency_patients = [p for p in patients_db.values() if p.get('status') == 'emergency']
773
 
774
  # Sort by timestamp, newest first
775
- try:
776
- emergency_patients.sort(key=lambda x: datetime.strptime(x.get('timestamp', '1900-01-01 00:00:00'), "%Y-%m-%d %H:%M:%S"), reverse=True)
777
- except ValueError:
778
- print("Error sorting emergency patients by timestamp. Using unsorted list.")
779
- # If timestamp format is unexpected, use unsorted list or fall back to another key
780
- pass
781
-
782
 
783
  notifications = []
784
  for patient in emergency_patients:
@@ -801,21 +795,10 @@ def get_emergency_notifications():
801
  @app.route('/get_patients')
802
  def get_patients():
803
  # Return all patients for the doctor dashboard
804
- # Sort by status priority (Emergency first) then timestamp (newest first)
805
- status_priority = { 'emergency': 0, 'deteriorating': 1, 'improving': 2, 'stable': 3, 'unknown': 4 }
806
-
807
- try:
808
- sorted_patients = sorted(patients_db.values(),
809
- key=lambda x: (status_priority.get(x.get('status', 'unknown'), 4),
810
- datetime.strptime(x.get('timestamp', '1900-01-01 00:00:00'), "%Y-%m-%d %H:%M:%S")),
811
- reverse=True) # Reverse timestamp sort for newest first
812
- except ValueError:
813
- print("Error sorting patients by timestamp/status. Using unsorted list.")
814
- sorted_patients = list(patients_db.values()) # Fallback to unsorted
815
-
816
- # Reverse the status priority sort so 0 (Emergency) comes first
817
- sorted_patients = sorted(sorted_patients, key=lambda x: status_priority.get(x.get('status', 'unknown'), 4))
818
-
819
 
820
  return jsonify({
821
  'success': True,
@@ -826,16 +809,15 @@ def get_patients():
826
  @app.route('/get_patient/<patient_id>')
827
  def get_patient(patient_id):
828
  print(f"API request for patient ID: {patient_id}")
829
- # print(f"Current patients_db keys: {list(patients_db.keys())}") # Log DB state (can be noisy)
830
 
831
  patient = patients_db.get(patient_id)
832
 
833
  if not patient:
834
  print(f"Patient ID {patient_id} not found in patients_db for API request.")
835
- return jsonify({'success': False, 'error': 'Patient not found or data cleared (server restart).'}), 404
836
 
837
- print(f"Found patient {patient_id} for API request: {patient.get('name')}")
838
- # Return the full patient object
839
  return jsonify({
840
  'success': True,
841
  'patient': patient
@@ -844,5 +826,4 @@ def get_patient(patient_id):
844
 
845
  if __name__ == '__main__':
846
  # Use a more robust development server like Waitress or Gunicorn in production
847
- # For development, debug=True is fine
848
  app.run(debug=True, port=5000) # Explicitly set port
 
14
  from reportlab.lib import colors
15
  from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
16
  from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle
 
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"""
 
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):
 
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
 
 
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(
 
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(
 
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(
 
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
 
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 = {
 
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
 
 
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
 
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
 
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
 
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
 
 
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
 
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 = """
 
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]
 
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"
 
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.
 
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) ---
 
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
 
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,
 
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 = {
 
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(
 
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
 
 
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:
 
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,
 
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
 
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