rajkhanke commited on
Commit
7a94e14
·
verified ·
1 Parent(s): a9bd28c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +285 -272
app.py CHANGED
@@ -1,4 +1,5 @@
1
  from flask import Flask, render_template, request, jsonify, session, redirect, url_for, send_file
 
2
  import google.generativeai as genai
3
  import PyPDF2
4
  import os
@@ -20,6 +21,47 @@ app = Flask(__name__)
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', '/tmp/uploads')
24
  upload_folder = os.path.join(upload_base, 'pdfs')
25
 
@@ -36,54 +78,51 @@ TWILIO_FROM = os.getenv('TWILIO_FROM_NUMBER', 'whatsapp:+14155238886')
36
  TWILIO_TO = os.getenv('TWILIO_TO_NUMBER', 'whatsapp:+917559355282') # Hardcoded number as requested
37
 
38
  # Initialize Twilio client
39
- try:
40
- twilio_client = Client(ACCOUNT_SID, AUTH_TOKEN)
41
- print("Twilio client initialized successfully.")
42
- except Exception as e:
43
- print(f"Error initializing Twilio client: {e}")
44
- twilio_client = None # Handle cases where Twilio initialization fails
 
 
 
 
 
 
45
 
46
  # Gemini API Configuration
47
  # IMPORTANT: Use environment variables for sensitive information like API keys
48
  GENAI_API_KEY = os.getenv('GENAI_API_KEY', "AIzaSyD54ejbjVIVa-F3aD_Urnp8m1EFLUGR__I") # CHANGE THIS KEY
49
- try:
50
- genai.configure(api_key=GENAI_API_KEY)
51
- # Simple test to check connectivity
52
- list(genai.list_models())
53
- print("Gemini API configured successfully.")
54
- except Exception as e:
55
- print(f"Error configuring Gemini API: {e}")
56
- genai = None # Handle cases where API configuration fails
57
-
58
-
59
- generation_config = {
60
- "temperature": 0.8, # Slightly reduced temp for more stable output
61
- "top_p": 0.9, # Adjusted top_p
62
- "top_k": 30, # Adjusted top_k
63
- "max_output_tokens": 4096, # Reduced token limit slightly if needed, or increase if plans are long
64
- }
65
-
66
- # Use a more capable model if available and affordable
67
- # "gemini-1.5-pro" or "gemini-1.5-flash" are better choices than gemini-2.0-flash if 2.0 means an older preview
68
- # Let's assume gemini-2.0-flash is a valid, accessible model alias
69
  model = None
70
- if genai:
71
  try:
 
 
 
 
 
 
 
 
 
 
 
 
72
  model = genai.GenerativeModel(
73
  model_name="gemini-1.5-flash-latest", # Recommended model alias
74
  generation_config=generation_config,
75
  )
76
- # Test model generation
77
  # model.generate_content("Hello")
78
  print(f"Using Gemini model: {model.model_name}")
79
- except Exception as e:
80
- print(f"Error initializing Gemini model: {e}")
81
- model = None # Handle cases where model initialization fails
82
 
 
 
 
 
 
83
 
84
- # Patient database (in-memory for demo - WILL BE RESET ON SERVER RESTART)
85
- # For persistence, replace this with a database (SQLite, PostgreSQL, etc.)
86
- patients_db = {}
87
 
88
  def extract_text_from_pdf(pdf_file):
89
  """Extract text from uploaded PDF file"""
@@ -92,19 +131,28 @@ def extract_text_from_pdf(pdf_file):
92
  pdf_file.seek(0)
93
  pdf_reader = PyPDF2.PdfReader(pdf_file)
94
  text = ""
 
 
 
 
 
 
 
 
95
  for page in pdf_reader.pages:
96
  page_text = page.extract_text()
97
  if page_text:
98
  text += page_text + "\n" # Add newline between pages
99
- return text.strip()
100
  except Exception as e:
101
  print(f"Error extracting PDF text: {e}")
102
- return ""
 
103
 
104
  def extract_care_plan_format(pdf_text):
105
  """Extract a general format template from PDF text by identifying common section headers."""
106
- if not pdf_text:
107
- return None
108
 
109
  # Look for lines that seem like headers (e.g., capitalized words ending with :, followed by content)
110
  # This regex is an attempt to capture common patterns but might not be perfect for all PDFs.
@@ -123,7 +171,7 @@ def extract_care_plan_format(pdf_text):
123
  format_template = "\n".join([f"{header.strip()}:" for header in set(potential_headers) if header.strip()]) + "\n"
124
  print(f"Extracted potential headers (fallback): {list(set(potential_headers))}")
125
  return format_template if format_template.strip() else None
126
- print("No sections or potential headers found.")
127
  return None # No recognizable format found
128
 
129
  format_template = ""
@@ -141,6 +189,7 @@ def determine_patient_status(original_plan, updated_plan, feedback):
141
  updated_plan_lower = updated_plan.lower()
142
 
143
  # Comprehensive keywords indicating condition worsening or emergency
 
144
  emergency_keywords = [
145
  "severe chest pain", "heart attack", "sudden shortness of breath",
146
  "difficulty breathing", "loss of consciousness", "extreme pain",
@@ -151,7 +200,7 @@ def determine_patient_status(original_plan, updated_plan, feedback):
151
  "collapsed", "unconscious", "unresponsive", "stroke", "seizure", "convulsion",
152
  "suffocating", "not breathing", "blue lips", "blue face", "cardiac arrest",
153
  "high fever", "signs of shock", "severe dehydration", "acute change",
154
- "unstable vitals", "rapidly worsening"
155
  ]
156
 
157
  deteriorating_keywords = [
@@ -162,7 +211,8 @@ def determine_patient_status(original_plan, updated_plan, feedback):
162
  "weakening", "relapse", "recurrence", "regressing", "not responding",
163
  "increased symptoms", "more severe", "progressing", "progressive", "complicated",
164
  "adverse change", "unstable", "needs attention", "spike in", "significant increase",
165
- "new symptoms", "trouble with", "reduced appetite", "difficulty sleeping"
 
166
  ]
167
 
168
  improvement_keywords = [
@@ -172,36 +222,26 @@ def determine_patient_status(original_plan, updated_plan, feedback):
172
  "improved", "enhancement", "advancement", "resolving", "resolved", "recovering",
173
  "normalized", "normal range", "responding well", "responding positively",
174
  "effective treatment", "successful treatment", "managed well", "under control",
175
- "symptoms decreased", "feeling stronger", "better sleep", "increased appetite"
 
176
  ]
177
 
178
- # Check for emergency keywords first (highest priority), primarily in feedback as it's direct input
179
  if any(keyword in feedback_lower for keyword in emergency_keywords):
180
  return "emergency"
181
- # Then check updated plan, as AI might interpret feedback and include emergency terms
182
- if any(keyword in updated_plan_lower for keyword in emergency_keywords):
183
- return "emergency"
184
-
185
- # Check for deteriorating keywords
186
  if any(keyword in feedback_lower for keyword in deteriorating_keywords):
187
  return "deteriorating"
188
- # Check updated plan for deteriorating indicators
189
- if any(keyword in updated_plan_lower for keyword in deteriorating_keywords):
190
- return "deteriorating"
191
-
192
- # Check for improvement keywords
193
  if any(keyword in feedback_lower for keyword in improvement_keywords):
194
  return "improving"
195
- # Check updated plan for improvement indicators
196
- if any(keyword in updated_plan_lower for keyword in improvement_keywords):
197
- return "improving"
198
-
199
- # Compare original vs updated plan length/content as a weak signal (optional, can be removed if unreliable)
200
- # if len(updated_plan) < len(original_plan) * 0.8 and any(k in updated_plan_lower for k in improvement_keywords):
201
- # return "improving"
202
- # if len(updated_plan) > len(original_plan) * 1.2 and any(k in updated_plan_lower for k in deteriorating_keywords):
203
- # return "deteriorating"
204
 
 
 
 
 
 
 
 
 
205
 
206
  # Default to stable if no clear indicators of change are found
207
  return "stable"
@@ -297,19 +337,19 @@ def generate_care_plan_pdf(patient_info, care_plan_text, status):
297
  # Patient Information Table
298
  patient_data = [
299
  [Paragraph("<b>Patient Name:</b>", normal_style), Paragraph(patient_info.get('name', 'N/A'), normal_style)],
300
- [Paragraph("<b>Age:</b>", normal_style), Paragraph(patient_info.get('age', 'N/A'), normal_style)],
301
  [Paragraph("<b>Gender:</b>", normal_style), Paragraph(patient_info.get('gender', 'N/A'), normal_style)],
302
  [Paragraph("<b>Generated Date:</b>", normal_style), Paragraph(datetime.now().strftime("%Y-%m-%d %H:%M"), normal_style)]
303
  ]
304
 
305
  patient_table = Table(patient_data, colWidths=[150, 360]) # Adjusted column widths
306
  patient_table.setStyle(TableStyle([
307
- ('BACKGROUND', (0, 0), (0, -1), colors.lightgrey),
308
  ('TEXTCOLOR', (0, 0), (0, -1), colors.black),
309
  ('ALIGN', (0, 0), (-1, -1), 'LEFT'),
310
  ('VALIGN', (0, 0), (-1, -1), 'TOP'),
311
- ('INNERGRID', (0, 0), (-1, -1), 0.25, colors.grey), # Lighter grid
312
- ('BOX', (0, 0), (-1, -1), 0.25, colors.grey),
313
  ('LEFTPADDING', (0, 0), (-1, -1), 6),
314
  ('RIGHTPADDING', (0, 0), (-1, -1), 6),
315
  ('TOPPADDING', (0, 0), (-1, -1), 6), # Increased padding
@@ -324,88 +364,31 @@ def generate_care_plan_pdf(patient_info, care_plan_text, status):
324
  story.append(Spacer(1, 10))
325
 
326
  # Process care plan text by lines or sections for better formatting
 
 
327
  lines = care_plan_text.strip().split('\n')
328
- current_section = []
329
- is_bullet_section = False
330
-
331
- def add_section_to_story(section_lines, is_bullet):
332
- if not section_lines:
333
- return
334
- text = "\n".join(section_lines).strip()
335
- if not text:
336
- return
337
-
338
- # Attempt to identify potential sub-headings within content
339
- sub_sections = re.split(r'\n(?=[A-Z][A-Z\s]*:)', text)
340
-
341
- for i, sub_text in enumerate(sub_sections):
342
- if i > 0: # Add space before subsequent sections
343
- story.append(Spacer(1, 8))
344
-
345
- # Check if the sub_text starts with a potential sub-heading
346
- header_match = re.match(r'([A-Z][A-Z\s]*):', sub_text)
347
- if header_match:
348
- header_title = header_match.group(1).strip()
349
- content_after_header = sub_text[header_match.end():].strip()
350
- story.append(Paragraph(header_title + ":", heading_style)) # Use heading style for sub-sections too
351
-
352
- # Process content after the potential header
353
- content_lines = content_after_header.split('\n')
354
- sub_is_bullet = any(re.match(r'^\s*[-•*]', line) for line in content_lines)
355
-
356
- if sub_is_bullet:
357
- sub_bullet_points = re.findall(r'^\s*[-•*]\s*(.*?)$', content_after_header, re.MULTILINE)
358
- if sub_bullet_points:
359
- for point in sub_bullet_points:
360
- if point.strip():
361
- story.append(Paragraph(f"• {point.strip()}", bullet_style))
362
- else: # Fallback if regex misses some bullets
363
- for line in content_lines:
364
- cleaned_line = re.sub(r'^\s*[-•*]\s*', '', line).strip()
365
- if cleaned_line:
366
- story.append(Paragraph(f"• {cleaned_line}", bullet_style))
367
-
368
- else: # Not a bulleted sub-section
369
- cleaned_content = content_after_header.strip()
370
- if cleaned_content:
371
- story.append(Paragraph(cleaned_content, normal_style))
372
-
373
- else: # If no sub-heading found at the start, treat as a single block of content
374
- if is_bullet:
375
- bullet_points = re.findall(r'^\s*[-•*]\s*(.*?)$', text, re.MULTILINE)
376
- if bullet_points:
377
- for point in bullet_points:
378
- if point.strip():
379
- story.append(Paragraph(f"• {point.strip()}", bullet_style))
380
- else: # Fallback if regex misses some bullets
381
- for line in section_lines:
382
- cleaned_line = re.sub(r'^\s*[-•*]\s*', '', line).strip()
383
- if cleaned_line:
384
- story.append(Paragraph(f"• {cleaned_line}", bullet_style))
385
- else:
386
- story.append(Paragraph(text, normal_style))
387
-
388
  for line in lines:
389
  stripped_line = line.strip()
390
- # Check if line is a potential section header
391
- header_match = re.match(r'^([A-Z][A-Z\s]*):$', stripped_line)
392
 
 
 
393
  if header_match:
394
- # Add previous section before starting a new one
395
- add_section_to_story(current_section, is_bullet_section)
396
- story.append(Paragraph(stripped_line, heading_style)) # Add the new header
397
- current_section = [] # Reset current section content
398
- is_bullet_section = False # Assume new section is not bulleted initially
399
- elif stripped_line.startswith('-') or stripped_line.startswith('•') or stripped_line.startswith('*'):
400
- # Line is a bullet point, add it to the current section content
401
- current_section.append(line)
402
- is_bullet_section = True # Mark the current section as potentially bulleted
 
403
  else:
404
- # Line is regular content, add it to the current section
405
- current_section.append(line)
406
 
407
- # Add the last section
408
- add_section_to_story(current_section, is_bullet_section)
409
 
410
  # Add footer or timestamp if desired
411
  story.append(Spacer(1, 20))
@@ -414,15 +397,27 @@ def generate_care_plan_pdf(patient_info, care_plan_text, status):
414
 
415
 
416
  # Build the PDF
417
- doc.build(story)
418
- buffer.seek(0)
419
- return buffer
 
 
 
 
 
 
 
 
 
 
 
 
420
 
421
 
422
  def send_whatsapp_care_plan(patient_info, care_plan_text, status):
423
  """Send care plan via WhatsApp using Twilio with improved formatting"""
424
  if not twilio_client:
425
- print("Twilio client not initialized. Cannot send WhatsApp message.")
426
  return False
427
 
428
  try:
@@ -431,28 +426,39 @@ def send_whatsapp_care_plan(patient_info, care_plan_text, status):
431
  'emergency': "🚨 EMERGENCY",
432
  'deteriorating': "⚠️ HIGH RISK",
433
  'improving': "✅ IMPROVING",
434
- 'stable': "🟦 STABLE"
 
435
  }
436
 
 
 
 
 
 
 
 
 
437
  # Format message for WhatsApp including patient details and the full plan text
438
  message = f"*Care Plan Update*\n\n"
439
  message += f"*Patient Name:* {patient_info.get('name', 'N/A')}\n"
440
  message += f"*Age:* {patient_info.get('age', 'N/A')}\n"
441
  message += f"*Gender:* {patient_info.get('gender', 'N/A')}\n"
442
  message += f"*Status:* {status_emoji.get(status, 'Unknown')}\n\n"
443
- message += f"*Care Plan Details:*\n{care_plan_text.strip()}" # Include the full plan text
444
 
 
445
  # Send WhatsApp message using hardcoded number
446
- message = twilio_client.messages.create(
447
  from_=TWILIO_FROM,
448
  body=message,
449
  to=TWILIO_TO
450
  )
451
- print(f"WhatsApp message sent, SID: {message.sid}")
452
  return True
453
  except Exception as e:
454
  print(f"Error sending WhatsApp message: {e}")
455
  # Twilio error handling can be more specific (e.g., check e.status)
 
456
  return False
457
 
458
 
@@ -483,34 +489,30 @@ def doctor_dashboard():
483
  return render_template('doctor_dashboard.html')
484
 
485
 
486
- # This route is not strictly needed anymore as patient details are loaded via AJAX on doctor_dashboard
487
- # Keeping it for backward compatibility or direct linking if desired, but AJAX is preferred.
488
- @app.route('/patient_details/<patient_id>')
489
- def patient_details(patient_id):
490
- print(f"Attempting to load patient details page for ID: {patient_id}")
491
- print(f"Current patients_db keys: {patients_db.keys()}")
492
- patient = patients_db.get(patient_id)
493
- if not patient:
494
- print(f"Patient ID {patient_id} not found in patients_db.")
495
- # Redirect to dashboard, perhaps with an error message
496
- return redirect(url_for('doctor_dashboard', error="Patient not found"))
497
- # Render the dashboard template, passing the specific patient ID
498
- # The JS on doctor_dashboard.html will detect this ID in the URL and load the details
499
- return render_template('doctor_dashboard.html', selected_patient_id=patient_id)
500
-
501
-
502
  @app.route('/submit_feedback', methods=['POST'])
503
  def submit_feedback():
504
  if not model:
505
- return jsonify({'success': False, 'error': 'AI model not initialized. Please check API key.'}), 500
506
 
507
  try:
508
  # Get patient information from form
509
  name = request.form.get('name', 'Unnamed Patient')
510
- age = request.form.get('age', 'N/A')
511
  gender = request.form.get('gender', 'N/A')
512
  feedback = request.form.get('feedback', '')
513
 
 
 
 
 
 
 
 
 
 
 
 
 
514
  care_plan_text = ""
515
  care_plan_format = None
516
 
@@ -520,9 +522,12 @@ def submit_feedback():
520
  if pdf_file and pdf_file.filename != '':
521
  # Read the PDF content from the file stream
522
  care_plan_text = extract_text_from_pdf(pdf_file)
523
- if care_plan_text:
524
  care_plan_format = extract_care_plan_format(care_plan_text)
525
  print(f"Extracted text length: {len(care_plan_text)}. Format found: {care_plan_format is not None}")
 
 
 
526
 
527
  # If no format is found in the PDF, use a default format
528
  if not care_plan_format or not care_plan_format.strip():
@@ -532,10 +537,8 @@ PATIENT INFORMATION:
532
  - Name: [Patient Name]
533
  - Age: [Age]
534
  - Gender: [Gender]
535
-
536
  ASSESSMENT:
537
  - [Summary of patient's current condition based on feedback and previous plan]
538
-
539
  DAILY CARE PLAN:
540
  Morning:
541
  - [Morning activities/medications]
@@ -545,60 +548,39 @@ Evening:
545
  - [Evening activities/medications]
546
  Night:
547
  - [Night activities/medications/sleep instructions]
548
-
549
  MEDICATIONS:
550
  - [List of medications, dosage, frequency, and time]
551
-
552
  DIET AND HYDRATION:
553
  - [Dietary recommendations and hydration goals]
554
-
555
  PHYSICAL ACTIVITY/EXERCISE:
556
  - [Recommended physical activities, duration, frequency]
557
-
558
  SYMPTOM MANAGEMENT:
559
  - [Instructions for managing specific symptoms, including what to do if they worsen]
560
-
561
  RED FLAGS / WHEN TO SEEK HELP:
562
  - [Clear instructions on symptoms requiring immediate medical attention (Emergency)]
563
  - [Instructions on symptoms requiring contact with doctor/clinic (Urgent but not Emergency)]
564
-
565
  ADDITIONAL RECOMMENDATIONS:
566
  - [Other relevant advice, e.g., rest, monitoring, specific precautions]
567
-
568
  FOLLOW-UP:
569
  - [Details about next appointment or when to contact healthcare provider]
570
  """
571
  else:
572
  print("Using extracted care plan format.")
573
 
574
- # Check for emergency symptoms FIRST in feedback
575
- emergency_keywords_check = [
576
- "severe chest pain", "heart attack", "shortness of breath",
577
- "dizziness", "loss of consciousness", "extreme pain",
578
- "sudden weakness", "confusion", "slurred speech", "severe headache",
579
- "difficulty breathing", "severe shortness of breath", "wheezing",
580
- "severe abdominal pain", "persistent vomiting", "uncontrolled bleeding",
581
- "severe allergic reaction", "anaphylaxis", "immediate medical attention",
582
- "emergency", "call 911", "urgent care", "hospital", "critical", "ambulance",
583
- "collapsed", "unconscious", "unresponsive", "stroke", "seizure", "convulsion",
584
- "suffocating", "not breathing", "blue lips", "blue face", "cardiac arrest",
585
- "high fever", "signs of shock", "severe dehydration", "acute change",
586
- "unstable vitals", "rapidly worsening"
587
- ]
588
-
589
- feedback_lower = feedback.lower()
590
- is_emergency_feedback = any(keyword in feedback_lower for keyword in emergency_keywords_check)
591
 
592
  generated_plan_text = ""
593
- status = 'stable' # Default status
594
 
595
- if is_emergency_feedback:
596
- print("Emergency keywords detected in feedback.")
597
  # If emergency symptoms are detected in feedback, generate emergency instructions
598
  generated_plan_text = (
599
  "PATIENT INFORMATION:\n"
600
  f"- Name: {name}\n"
601
- f"- Age: {age}\n"
602
  f"- Gender: {gender}\n\n"
603
  "ASSESSMENT:\n"
604
  f"- Emergency symptoms reported: {feedback}. Immediate medical attention required.\n\n"
@@ -615,7 +597,7 @@ FOLLOW-UP:
615
  "- Inform your primary physician as soon as medically stable.\n"
616
  "- Review and update care plan only after emergency situation is resolved and evaluated by medical professionals.\n"
617
  )
618
- status = 'emergency' # Set status to emergency
619
 
620
  else:
621
  # Prepare a prompt for Gemini AI for a perfect, attractively formatted day care plan.
@@ -623,18 +605,14 @@ FOLLOW-UP:
623
  prompt = f"""
624
  You are a helpful assistant generating updated patient care plans.
625
  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.
626
-
627
  Patient Information:
628
  Name: {name}
629
- Age: {age}
630
  Gender: {gender}
631
-
632
  Patient Feedback/Symptoms Update:
633
  {feedback}
634
-
635
  Previous Care Plan Details (if available):
636
- {care_plan_text if care_plan_text else "No previous care plan provided."}
637
-
638
  Instructions:
639
  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.
640
  2. Be specific and actionable in your recommendations.
@@ -642,46 +620,71 @@ Instructions:
642
  4. Include realistic times for medications if applicable (e.g., "Take 1 tablet in the morning with food").
643
  5. Highlight important instructions or warnings, especially regarding symptom management and when to seek help.
644
  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.
645
-
646
  Care Plan Format Template:
647
  {care_plan_format}
648
  """
649
  print("Sending prompt to AI model...")
650
- print(f"Prompt:\n---\n{prompt}\n---")
651
 
652
  # Get response from Gemini AI
653
  try:
654
  response = model.generate_content(prompt)
655
  generated_plan_text = response.text.strip() # Get the text and strip leading/trailing whitespace
 
 
 
 
 
 
 
 
656
  print(f"AI Response received. Length: {len(generated_plan_text)}")
657
- print(f"AI Response:\n---\n{generated_plan_text}\n---")
 
658
 
659
- # Determine patient status based on AI's output and feedback
660
  status = determine_patient_status(care_plan_text, generated_plan_text, feedback)
661
 
662
  except Exception as ai_error:
663
  print(f"Error generating content from AI: {ai_error}")
 
 
 
 
 
 
 
 
 
664
  return jsonify({
665
  'success': False,
666
- 'error': f'Error generating care plan: {ai_error}'
667
- }), 500
 
 
 
 
 
668
 
669
  # --- Actions after plan generation (Emergency or AI) ---
670
 
671
- # Store patient record (in-memory DB)
672
- patient_id = str(uuid.uuid4())
673
- patients_db[patient_id] = {
674
- 'id': patient_id,
675
- 'name': name,
676
- 'age': age,
677
- 'gender': gender,
678
- 'feedback': feedback,
679
- 'original_plan': care_plan_text, # Store original text from PDF
680
- 'updated_plan': generated_plan_text, # Store the generated plan text
681
- 'status': status,
682
- 'timestamp': datetime.now().strftime("%Y-%m-%d %H:%M:%S")
683
- }
684
- print(f"Patient {patient_id} added to in-memory DB with status: {status}. Current DB keys: {patients_db.keys()}")
 
 
685
 
686
 
687
  # Generate PDF for downloading using the stored data
@@ -706,53 +709,56 @@ Care Plan Format Template:
706
  return jsonify({
707
  'success': True,
708
  'updated_plan': generated_plan_text, # Send back the generated text
709
- 'pdf_data': pdf_base64,
710
- 'patient_id': patient_id, # Send the generated patient ID
711
  'status': status,
712
  'whatsapp_sent': whatsapp_sent
713
  })
714
 
715
  except Exception as e:
716
- print(f"An unexpected error occurred: {str(e)}")
 
717
  # Log the traceback for debugging
718
  import traceback
719
  traceback.print_exc()
 
 
720
  return jsonify({
721
  'success': False,
722
- 'error': f'An unexpected error occurred: {str(e)}'
723
  }), 500
724
 
725
 
726
  @app.route('/download_pdf/<patient_id>')
727
  def download_pdf(patient_id):
728
  print(f"Download requested for patient ID: {patient_id}")
729
- print(f"Current patients_db keys: {patients_db.keys()}")
730
 
731
  try:
732
- patient = patients_db.get(patient_id)
 
733
 
734
  if not patient:
735
- print(f"Patient ID {patient_id} not found in patients_db for download.")
736
- return "Patient data not found (it may have been cleared). Please regenerate the plan.", 404
737
 
738
  # Use the patient's stored information to regenerate the PDF content
739
  patient_info_for_pdf = {
740
- 'name': patient.get('name', 'N/A'),
741
- 'age': patient.get('age', 'N/A'),
742
- 'gender': patient.get('gender', 'N/A')
743
  }
744
- # Use the already determined status and updated plan text
745
  pdf_buffer = generate_care_plan_pdf(
746
  patient_info_for_pdf,
747
- patient['updated_plan'],
748
- patient['status']
749
  )
750
 
751
  pdf_buffer.seek(0) # Ensure buffer is at the start
752
 
753
  # Sanitize patient name for filename
754
- safe_name = re.sub(r'[^a-zA-Z0-9_\-]', '', patient.get('name', 'patient')).lower()
755
- download_name = f"care_plan_{safe_name}_{datetime.now().strftime('%Y%m%d')}.pdf"
756
 
757
  print(f"Serving PDF for {patient_id} as {download_name}")
758
  return send_file(
@@ -765,28 +771,15 @@ def download_pdf(patient_id):
765
  print(f"PDF Download Error for ID {patient_id}: {str(e)}")
766
  import traceback
767
  traceback.print_exc()
768
- return f"Error generating PDF: {str(e)}", 500
769
 
770
 
771
  @app.route('/get_emergency_notifications')
772
  def get_emergency_notifications():
773
- # Filter patients with emergency status
774
- emergency_patients = [p for p in patients_db.values() if p.get('status') == 'emergency']
775
-
776
- # Sort by timestamp, newest first
777
- emergency_patients.sort(key=lambda x: datetime.strptime(x['timestamp'], "%Y-%m-%d %H:%M:%S"), reverse=True)
778
-
779
- notifications = []
780
- for patient in emergency_patients:
781
- notifications.append({
782
- 'patient_id': patient['id'],
783
- 'patient_name': patient.get('name', 'N/A'),
784
- 'patient_age': patient.get('age', 'N/A'),
785
- 'patient_gender': patient.get('gender', 'N/A'),
786
- 'patient_feedback': patient.get('feedback', 'No feedback recorded.'),
787
- 'timestamp': patient.get('timestamp', 'N/A'),
788
- 'status': patient.get('status', 'unknown')
789
- })
790
 
791
  return jsonify({
792
  'success': True,
@@ -796,36 +789,56 @@ def get_emergency_notifications():
796
 
797
  @app.route('/get_patients')
798
  def get_patients():
799
- # Return all patients for the doctor dashboard
800
- # Sort by timestamp, newest first by default, or add sorting logic here
801
- sorted_patients = sorted(patients_db.values(),
802
- key=lambda x: datetime.strptime(x.get('timestamp', '1900-01-01 00:00:00'), "%Y-%m-%d %H:%M:%S"),
803
- reverse=True)
 
 
 
 
 
804
 
805
  return jsonify({
806
  'success': True,
807
- 'patients': list(sorted_patients)
808
  })
809
 
810
 
811
  @app.route('/get_patient/<patient_id>')
812
  def get_patient(patient_id):
813
  print(f"API request for patient ID: {patient_id}")
814
- print(f"Current patients_db keys: {patients_db.keys()}")
815
 
816
- patient = patients_db.get(patient_id)
 
817
 
818
  if not patient:
819
- print(f"Patient ID {patient_id} not found in patients_db for API request.")
820
- return jsonify({'success': False, 'error': 'Patient not found or data cleared.'}), 404
821
 
822
- print(f"Found patient {patient_id}: {patient.get('name')}")
823
  return jsonify({
824
  'success': True,
825
- 'patient': patient
826
  })
827
 
 
 
 
 
 
 
 
 
 
 
 
 
828
 
829
  if __name__ == '__main__':
830
  # Use a more robust development server like Waitress or Gunicorn in production
831
- app.run(debug=True, port=5000) # Explicitly set port
 
 
 
 
1
  from flask import Flask, render_template, request, jsonify, session, redirect, url_for, send_file
2
+ from flask_sqlalchemy import SQLAlchemy # Import SQLAlchemy
3
  import google.generativeai as genai
4
  import PyPDF2
5
  import os
 
21
  # Use a strong, unique secret key
22
  app.secret_key = os.getenv('SECRET_KEY', '688ed745a74bdd7ac238f5b50f4104fb87d6774b8b0a4e06e7e18ac5ed0fa31c') # CHANGE THIS IN PRODUCTION
23
 
24
+ # Database Configuration
25
+ # Use SQLite for simplicity
26
+ app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv('DATABASE_URL', 'sqlite:///patients.db')
27
+ app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
28
+
29
+ db = SQLAlchemy(app)
30
+
31
+ # Define the Patient Model
32
+ class Patient(db.Model):
33
+ id = db.Column(db.String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
34
+ name = db.Column(db.String(100), nullable=False)
35
+ age = db.Column(db.Integer)
36
+ gender = db.Column(db.String(50))
37
+ feedback = db.Column(db.Text, nullable=False)
38
+ original_plan = db.Column(db.Text) # Store text from uploaded PDF
39
+ updated_plan = db.Column(db.Text, nullable=False) # Store generated plan text
40
+ status = db.Column(db.String(50), default='stable')
41
+ timestamp = db.Column(db.DateTime, default=datetime.utcnow)
42
+
43
+ def __repr__(self):
44
+ return f"<Patient {self.name} ({self.id})>"
45
+
46
+ def to_dict(self):
47
+ return {
48
+ 'id': self.id,
49
+ 'name': self.name,
50
+ 'age': self.age,
51
+ 'gender': self.gender,
52
+ 'feedback': self.feedback,
53
+ 'original_plan': self.original_plan,
54
+ 'updated_plan': self.updated_plan,
55
+ 'status': self.status,
56
+ 'timestamp': self.timestamp.strftime("%Y-%m-%d %H:%M:%S") if self.timestamp else None
57
+ }
58
+
59
+ # Create database tables if they don't exist
60
+ @app.before_request
61
+ def create_tables():
62
+ db.create_all()
63
+
64
+
65
  upload_base = os.getenv('UPLOAD_DIR', '/tmp/uploads')
66
  upload_folder = os.path.join(upload_base, 'pdfs')
67
 
 
78
  TWILIO_TO = os.getenv('TWILIO_TO_NUMBER', 'whatsapp:+917559355282') # Hardcoded number as requested
79
 
80
  # Initialize Twilio client
81
+ twilio_client = None
82
+ if ACCOUNT_SID and AUTH_TOKEN and TWILIO_FROM and TWILIO_TO:
83
+ try:
84
+ twilio_client = Client(ACCOUNT_SID, AUTH_TOKEN)
85
+ # Basic check to ensure credentials are valid (optional but good)
86
+ # twilio_client.api.accounts.get(ACCOUNT_SID)
87
+ print("Twilio client initialized successfully.")
88
+ except Exception as e:
89
+ print(f"Error initializing Twilio client (check SID/Token/Network): {e}")
90
+ else:
91
+ print("Twilio environment variables not set. WhatsApp sending disabled.")
92
+
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:
100
+ genai.configure(api_key=GENAI_API_KEY)
101
+ # Simple test to check connectivity
102
+ # list(genai.list_models()) # Can take time, maybe skip in prod startup
103
+ print("Gemini API configured successfully.")
104
+
105
+ generation_config = {
106
+ "temperature": 0.8, # Slightly reduced temp for more stable output
107
+ "top_p": 0.9, # Adjusted top_p
108
+ "top_k": 30, # Adjusted top_k
109
+ "max_output_tokens": 4096, # Reduced token limit slightly if needed, or increase if plans are long
110
+ }
111
+
112
  model = genai.GenerativeModel(
113
  model_name="gemini-1.5-flash-latest", # Recommended model alias
114
  generation_config=generation_config,
115
  )
116
+ # Test model generation (optional)
117
  # model.generate_content("Hello")
118
  print(f"Using Gemini model: {model.model_name}")
 
 
 
119
 
120
+ except Exception as e:
121
+ print(f"Error configuring Gemini API or loading model: {e}")
122
+ model = None # Handle cases where API configuration fails
123
+ else:
124
+ print("GENAI_API_KEY environment variable not set. AI generation disabled.")
125
 
 
 
 
126
 
127
  def extract_text_from_pdf(pdf_file):
128
  """Extract text from uploaded PDF file"""
 
131
  pdf_file.seek(0)
132
  pdf_reader = PyPDF2.PdfReader(pdf_file)
133
  text = ""
134
+ # Handle potential decryption errors
135
+ if pdf_reader.is_encrypted:
136
+ try:
137
+ pdf_reader.decrypt('') # Attempt decryption with empty password
138
+ except Exception as e:
139
+ print(f"PDF is encrypted and cannot be decrypted: {e}")
140
+ return "[PDF Content Unavailable: File is encrypted]" # Return a message indicating failure
141
+
142
  for page in pdf_reader.pages:
143
  page_text = page.extract_text()
144
  if page_text:
145
  text += page_text + "\n" # Add newline between pages
146
+ return text.strip() or "[No readable text found in PDF]" # Return placeholder if no text
147
  except Exception as e:
148
  print(f"Error extracting PDF text: {e}")
149
+ return f"[Error extracting PDF text: {e}]" # Include error in placeholder
150
+
151
 
152
  def extract_care_plan_format(pdf_text):
153
  """Extract a general format template from PDF text by identifying common section headers."""
154
+ if not pdf_text or "[No readable text found" in pdf_text or "[Error extracting PDF text" in pdf_text:
155
+ return None # Cannot extract format from empty or error text
156
 
157
  # Look for lines that seem like headers (e.g., capitalized words ending with :, followed by content)
158
  # This regex is an attempt to capture common patterns but might not be perfect for all PDFs.
 
171
  format_template = "\n".join([f"{header.strip()}:" for header in set(potential_headers) if header.strip()]) + "\n"
172
  print(f"Extracted potential headers (fallback): {list(set(potential_headers))}")
173
  return format_template if format_template.strip() else None
174
+ print("No sections or potential headers found in PDF.")
175
  return None # No recognizable format found
176
 
177
  format_template = ""
 
189
  updated_plan_lower = updated_plan.lower()
190
 
191
  # Comprehensive keywords indicating condition worsening or emergency
192
+ # Prioritize feedback keywords
193
  emergency_keywords = [
194
  "severe chest pain", "heart attack", "sudden shortness of breath",
195
  "difficulty breathing", "loss of consciousness", "extreme pain",
 
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
  ]
205
 
206
  deteriorating_keywords = [
 
211
  "weakening", "relapse", "recurrence", "regressing", "not responding",
212
  "increased symptoms", "more severe", "progressing", "progressive", "complicated",
213
  "adverse change", "unstable", "needs attention", "spike in", "significant increase",
214
+ "new symptoms", "trouble with", "reduced appetite", "difficulty sleeping",
215
+ "tired all the time", "much weaker", "feeling worse"
216
  ]
217
 
218
  improvement_keywords = [
 
222
  "improved", "enhancement", "advancement", "resolving", "resolved", "recovering",
223
  "normalized", "normal range", "responding well", "responding positively",
224
  "effective treatment", "successful treatment", "managed well", "under control",
225
+ "symptoms decreased", "feeling stronger", "better sleep", "increased appetite",
226
+ "pain decreased", "more energy", "walking further"
227
  ]
228
 
229
+ # Check feedback first
230
  if any(keyword in feedback_lower for keyword in emergency_keywords):
231
  return "emergency"
 
 
 
 
 
232
  if any(keyword in feedback_lower for keyword in deteriorating_keywords):
233
  return "deteriorating"
 
 
 
 
 
234
  if any(keyword in feedback_lower for keyword in improvement_keywords):
235
  return "improving"
 
 
 
 
 
 
 
 
 
236
 
237
+ # Fallback: If no strong indicator in feedback, analyze the updated plan if it exists and differs
238
+ if updated_plan_lower != original_plan_lower:
239
+ if any(keyword in updated_plan_lower for keyword in emergency_keywords):
240
+ return "emergency"
241
+ if any(keyword in updated_plan_lower for keyword in deteriorating_keywords):
242
+ return "deteriorating"
243
+ if any(keyword in updated_plan_lower for keyword in improvement_keywords):
244
+ return "improving"
245
 
246
  # Default to stable if no clear indicators of change are found
247
  return "stable"
 
337
  # Patient Information Table
338
  patient_data = [
339
  [Paragraph("<b>Patient Name:</b>", normal_style), Paragraph(patient_info.get('name', 'N/A'), normal_style)],
340
+ [Paragraph("<b>Age:</b>", normal_style), Paragraph(str(patient_info.get('age', 'N/A')), normal_style)], # Ensure age is string for Paragraph
341
  [Paragraph("<b>Gender:</b>", normal_style), Paragraph(patient_info.get('gender', 'N/A'), normal_style)],
342
  [Paragraph("<b>Generated Date:</b>", normal_style), Paragraph(datetime.now().strftime("%Y-%m-%d %H:%M"), normal_style)]
343
  ]
344
 
345
  patient_table = Table(patient_data, colWidths=[150, 360]) # Adjusted column widths
346
  patient_table.setStyle(TableStyle([
347
+ ('BACKGROUND', (0, 0), (0, -1), colors.HexColor("#f2f2f2")), # Lighter grey
348
  ('TEXTCOLOR', (0, 0), (0, -1), colors.black),
349
  ('ALIGN', (0, 0), (-1, -1), 'LEFT'),
350
  ('VALIGN', (0, 0), (-1, -1), 'TOP'),
351
+ ('INNERGRID', (0, 0), (-1, -1), 0.25, colors.HexColor("#dddddd")), # Lighter grid
352
+ ('BOX', (0, 0), (-1, -1), 0.25, colors.HexColor("#dddddd")),
353
  ('LEFTPADDING', (0, 0), (-1, -1), 6),
354
  ('RIGHTPADDING', (0, 0), (-1, -1), 6),
355
  ('TOPPADDING', (0, 0), (-1, -1), 6), # Increased padding
 
364
  story.append(Spacer(1, 10))
365
 
366
  # Process care plan text by lines or sections for better formatting
367
+ # This logic attempts to render text, sections, and bullet points reasonably.
368
+ # Complex formatting in the AI output might require more sophisticated parsing.
369
  lines = care_plan_text.strip().split('\n')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
370
  for line in lines:
371
  stripped_line = line.strip()
372
+ if not stripped_line:
373
+ continue # Skip empty lines
374
 
375
+ # Check for potential section headers (e.g., "SECTION TITLE:")
376
+ header_match = re.match(r'^([A-Z][A-Z\s]*):$', stripped_line)
377
  if header_match:
378
+ story.append(Spacer(1, 8)) # Space before new section
379
+ story.append(Paragraph(stripped_line, heading_style))
380
+ # Check for bullet points (handles -, *, •)
381
+ elif stripped_line.startswith('-') or stripped_line.startswith('*') or stripped_line.startswith('•'):
382
+ # Clean the bullet point text
383
+ bullet_text = re.sub(r'^[-*•]\s*', '', line).strip() # Use original line to preserve initial indentation
384
+ if bullet_text:
385
+ story.append(Paragraph(f"• {bullet_text}", bullet_style))
386
+ else: # Handle empty bullet points
387
+ story.append(Paragraph("• ", bullet_style))
388
  else:
389
+ # Treat as normal paragraph content
390
+ story.append(Paragraph(line, normal_style)) # Use original line to preserve indentation/spacing within paragraphs
391
 
 
 
392
 
393
  # Add footer or timestamp if desired
394
  story.append(Spacer(1, 20))
 
397
 
398
 
399
  # Build the PDF
400
+ try:
401
+ doc.build(story)
402
+ buffer.seek(0)
403
+ print("PDF generated successfully.")
404
+ return buffer
405
+ except Exception as e:
406
+ print(f"Error building PDF: {e}")
407
+ # Attempt to create a minimal error PDF
408
+ error_buffer = io.BytesIO()
409
+ c = canvas.Canvas(error_buffer, pagesize=letter)
410
+ c.drawString(100, 750, "Error Generating Care Plan PDF")
411
+ c.drawString(100, 735, f"Details: {str(e)}")
412
+ c.save()
413
+ error_buffer.seek(0)
414
+ return error_buffer
415
 
416
 
417
  def send_whatsapp_care_plan(patient_info, care_plan_text, status):
418
  """Send care plan via WhatsApp using Twilio with improved formatting"""
419
  if not twilio_client:
420
+ print("Twilio client not configured. Cannot send WhatsApp message.")
421
  return False
422
 
423
  try:
 
426
  'emergency': "🚨 EMERGENCY",
427
  'deteriorating': "⚠️ HIGH RISK",
428
  'improving': "✅ IMPROVING",
429
+ 'stable': "🟦 STABLE",
430
+ 'unknown': "⚪ Status: Unknown"
431
  }
432
 
433
+ # Basic sanitization for WhatsApp (avoid excessive newlines, etc.)
434
+ formatted_plan = care_plan_text.strip()
435
+ # Replace multiple newlines with fewer for cleaner WhatsApp message
436
+ formatted_plan = re.sub(r'\n{2,}', '\n\n', formatted_plan)
437
+ # Attempt to make bullet points more visible
438
+ formatted_plan = formatted_plan.replace('- ', '• ').replace('* ', '• ')
439
+
440
+
441
  # Format message for WhatsApp including patient details and the full plan text
442
  message = f"*Care Plan Update*\n\n"
443
  message += f"*Patient Name:* {patient_info.get('name', 'N/A')}\n"
444
  message += f"*Age:* {patient_info.get('age', 'N/A')}\n"
445
  message += f"*Gender:* {patient_info.get('gender', 'N/A')}\n"
446
  message += f"*Status:* {status_emoji.get(status, 'Unknown')}\n\n"
447
+ message += f"*Care Plan Details:*\n{formatted_plan}" # Include the full plan text
448
 
449
+ print(f"Attempting to send WhatsApp message to {TWILIO_TO}...")
450
  # Send WhatsApp message using hardcoded number
451
+ message_sent = twilio_client.messages.create(
452
  from_=TWILIO_FROM,
453
  body=message,
454
  to=TWILIO_TO
455
  )
456
+ print(f"WhatsApp message sent, SID: {message_sent.sid}")
457
  return True
458
  except Exception as e:
459
  print(f"Error sending WhatsApp message: {e}")
460
  # Twilio error handling can be more specific (e.g., check e.status)
461
+ # Example: If the 'To' number is not registered for WhatsApp
462
  return False
463
 
464
 
 
489
  return render_template('doctor_dashboard.html')
490
 
491
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
492
  @app.route('/submit_feedback', methods=['POST'])
493
  def submit_feedback():
494
  if not model:
495
+ return jsonify({'success': False, 'error': 'AI model not initialized. Please check API key or server logs.'}), 500
496
 
497
  try:
498
  # Get patient information from form
499
  name = request.form.get('name', 'Unnamed Patient')
500
+ age = request.form.get('age') # Get as string initially
501
  gender = request.form.get('gender', 'N/A')
502
  feedback = request.form.get('feedback', '')
503
 
504
+ # Validate required fields
505
+ if not name or not feedback:
506
+ return jsonify({'success': False, 'error': 'Patient Name and Feedback are required.'}), 400
507
+ if age:
508
+ try:
509
+ age = int(age) # Convert age to integer
510
+ if age <= 0: raise ValueError("Age must be positive")
511
+ except ValueError:
512
+ return jsonify({'success': False, 'error': 'Invalid Age provided.'}), 400
513
+ else:
514
+ age = None # Store as None if not provided or invalid
515
+
516
  care_plan_text = ""
517
  care_plan_format = None
518
 
 
522
  if pdf_file and pdf_file.filename != '':
523
  # Read the PDF content from the file stream
524
  care_plan_text = extract_text_from_pdf(pdf_file)
525
+ if care_plan_text and "[No readable text found" not in care_plan_text and "[Error extracting PDF text" not in care_plan_text:
526
  care_plan_format = extract_care_plan_format(care_plan_text)
527
  print(f"Extracted text length: {len(care_plan_text)}. Format found: {care_plan_format is not None}")
528
+ else:
529
+ print("No PDF file uploaded or file is empty.")
530
+
531
 
532
  # If no format is found in the PDF, use a default format
533
  if not care_plan_format or not care_plan_format.strip():
 
537
  - Name: [Patient Name]
538
  - Age: [Age]
539
  - Gender: [Gender]
 
540
  ASSESSMENT:
541
  - [Summary of patient's current condition based on feedback and previous plan]
 
542
  DAILY CARE PLAN:
543
  Morning:
544
  - [Morning activities/medications]
 
548
  - [Evening activities/medications]
549
  Night:
550
  - [Night activities/medications/sleep instructions]
 
551
  MEDICATIONS:
552
  - [List of medications, dosage, frequency, and time]
 
553
  DIET AND HYDRATION:
554
  - [Dietary recommendations and hydration goals]
 
555
  PHYSICAL ACTIVITY/EXERCISE:
556
  - [Recommended physical activities, duration, frequency]
 
557
  SYMPTOM MANAGEMENT:
558
  - [Instructions for managing specific symptoms, including what to do if they worsen]
 
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
  ADDITIONAL RECOMMENDATIONS:
563
  - [Other relevant advice, e.g., rest, monitoring, specific precautions]
 
564
  FOLLOW-UP:
565
  - [Details about next appointment or when to contact healthcare provider]
566
  """
567
  else:
568
  print("Using extracted care plan format.")
569
 
570
+ # Determine status BEFORE calling AI, especially for emergency
571
+ initial_status = determine_patient_status(care_plan_text, "", feedback) # Compare against empty updated plan initially
572
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
573
 
574
  generated_plan_text = ""
575
+ status = initial_status # Start with status determined from feedback
576
 
577
+ if status == 'emergency':
578
+ print("Emergency status detected from feedback. Generating emergency plan.")
579
  # If emergency symptoms are detected in feedback, generate emergency instructions
580
  generated_plan_text = (
581
  "PATIENT INFORMATION:\n"
582
  f"- Name: {name}\n"
583
+ f"- Age: {age if age is not None else 'N/A'}\n"
584
  f"- Gender: {gender}\n\n"
585
  "ASSESSMENT:\n"
586
  f"- Emergency symptoms reported: {feedback}. Immediate medical attention required.\n\n"
 
597
  "- Inform your primary physician as soon as medically stable.\n"
598
  "- Review and update care plan only after emergency situation is resolved and evaluated by medical professionals.\n"
599
  )
600
+ # Status remains 'emergency'
601
 
602
  else:
603
  # Prepare a prompt for Gemini AI for a perfect, attractively formatted day care plan.
 
605
  prompt = f"""
606
  You are a helpful assistant generating updated patient care plans.
607
  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.
 
608
  Patient Information:
609
  Name: {name}
610
+ Age: {age if age is not None else 'N/A'}
611
  Gender: {gender}
 
612
  Patient Feedback/Symptoms Update:
613
  {feedback}
 
614
  Previous Care Plan Details (if available):
615
+ {care_plan_text if care_plan_text and "[No readable text found" not in care_plan_text else "No previous care plan provided."}
 
616
  Instructions:
617
  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.
618
  2. Be specific and actionable in your recommendations.
 
620
  4. Include realistic times for medications if applicable (e.g., "Take 1 tablet in the morning with food").
621
  5. Highlight important instructions or warnings, especially regarding symptom management and when to seek help.
622
  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.
623
+ 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.
624
  Care Plan Format Template:
625
  {care_plan_format}
626
  """
627
  print("Sending prompt to AI model...")
628
+ # print(f"Prompt:\n---\n{prompt}\n---") # Avoid printing large prompts in logs
629
 
630
  # Get response from Gemini AI
631
  try:
632
  response = model.generate_content(prompt)
633
  generated_plan_text = response.text.strip() # Get the text and strip leading/trailing whitespace
634
+
635
+ # Clean up common AI formatting issues (e.g., extra ```)
636
+ if generated_plan_text.startswith('```') and generated_plan_text.endswith('```'):
637
+ generated_plan_text = generated_plan_text[3:-3].strip()
638
+ if generated_plan_text.startswith('text'): # Handle ```text...```
639
+ generated_plan_text = generated_plan_text[4:].strip()
640
+
641
+
642
  print(f"AI Response received. Length: {len(generated_plan_text)}")
643
+ # print(f"AI Response (first 200 chars):\n---\n{generated_plan_text[:200]}\n---") # Print truncated response
644
+
645
 
646
+ # Re-determine patient status based on feedback AND the generated updated plan
647
  status = determine_patient_status(care_plan_text, generated_plan_text, feedback)
648
 
649
  except Exception as ai_error:
650
  print(f"Error generating content from AI: {ai_error}")
651
+ # Fallback: Use initial status and provide an error message in the plan
652
+ generated_plan_text = f"[Error generating updated plan from AI: {ai_error}]\n\n"
653
+ if care_plan_text and "[No readable text found" not in care_plan_text:
654
+ generated_plan_text += "Showing original plan due to AI error:\n\n" + care_plan_text
655
+ status = determine_patient_status(care_plan_text, care_plan_text, feedback) # Re-evaluate status based on original plan + feedback
656
+ else:
657
+ generated_plan_text += "No previous plan available."
658
+ status = initial_status # Keep the initial status if no original plan
659
+
660
  return jsonify({
661
  'success': False,
662
+ 'error': f'Error generating care plan: {ai_error}',
663
+ 'fallback_plan': generated_plan_text, # Optionally send fallback plan
664
+ 'patient_info': {'name': name, 'age': age, 'gender': gender},
665
+ 'status': status,
666
+ 'original_plan': care_plan_text # Include original plan if any
667
+ }), 500 # Return 500 if AI fails, but provide some data
668
+
669
 
670
  # --- Actions after plan generation (Emergency or AI) ---
671
 
672
+ # Create and store patient record in the database
673
+ new_patient = Patient(
674
+ name=name,
675
+ age=age,
676
+ gender=gender,
677
+ feedback=feedback,
678
+ original_plan=care_plan_text, # Store original text
679
+ updated_plan=generated_plan_text, # Store the generated plan text
680
+ status=status,
681
+ timestamp=datetime.utcnow() # Use UTC time
682
+ )
683
+ db.session.add(new_patient)
684
+ db.session.commit()
685
+ patient_id = new_patient.id # Get the generated ID
686
+
687
+ print(f"Patient {patient_id} added to DB with status: {status}.")
688
 
689
 
690
  # Generate PDF for downloading using the stored data
 
709
  return jsonify({
710
  'success': True,
711
  'updated_plan': generated_plan_text, # Send back the generated text
712
+ 'pdf_data': pdf_base64, # Send base64 data for preview
713
+ 'patient_id': patient_id, # Send the generated patient ID for download
714
  'status': status,
715
  'whatsapp_sent': whatsapp_sent
716
  })
717
 
718
  except Exception as e:
719
+ # Catch any other unexpected errors
720
+ print(f"An unexpected error occurred during submission: {str(e)}")
721
  # Log the traceback for debugging
722
  import traceback
723
  traceback.print_exc()
724
+ # Rollback database session in case of error
725
+ db.session.rollback()
726
  return jsonify({
727
  'success': False,
728
+ 'error': f'An unexpected server error occurred: {str(e)}'
729
  }), 500
730
 
731
 
732
  @app.route('/download_pdf/<patient_id>')
733
  def download_pdf(patient_id):
734
  print(f"Download requested for patient ID: {patient_id}")
 
735
 
736
  try:
737
+ # Fetch patient from the database
738
+ patient = Patient.query.get(patient_id)
739
 
740
  if not patient:
741
+ print(f"Patient ID {patient_id} not found in database for download.")
742
+ return "Patient data not found.", 404
743
 
744
  # Use the patient's stored information to regenerate the PDF content
745
  patient_info_for_pdf = {
746
+ 'name': patient.name,
747
+ 'age': patient.age,
748
+ 'gender': patient.gender
749
  }
750
+ # Use the already determined status and updated plan text from the DB record
751
  pdf_buffer = generate_care_plan_pdf(
752
  patient_info_for_pdf,
753
+ patient.updated_plan, # Use the stored updated plan
754
+ patient.status # Use the stored status
755
  )
756
 
757
  pdf_buffer.seek(0) # Ensure buffer is at the start
758
 
759
  # Sanitize patient name for filename
760
+ safe_name = re.sub(r'[^a-zA-Z0-9_\-]', '', patient.name or 'patient').lower()
761
+ download_name = f"care_plan_{safe_name}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf" # Add timestamp to filename
762
 
763
  print(f"Serving PDF for {patient_id} as {download_name}")
764
  return send_file(
 
771
  print(f"PDF Download Error for ID {patient_id}: {str(e)}")
772
  import traceback
773
  traceback.print_exc()
774
+ return f"Error generating PDF for download: {str(e)}", 500
775
 
776
 
777
  @app.route('/get_emergency_notifications')
778
  def get_emergency_notifications():
779
+ # Filter patients with emergency status directly from the database
780
+ emergency_patients_query = Patient.query.filter_by(status='emergency').order_by(Patient.timestamp.desc())
781
+
782
+ notifications = [p.to_dict() for p in emergency_patients_query.all()]
 
 
 
 
 
 
 
 
 
 
 
 
 
783
 
784
  return jsonify({
785
  'success': True,
 
789
 
790
  @app.route('/get_patients')
791
  def get_patients():
792
+ # Return all patients for the doctor dashboard from the database
793
+ # Order by timestamp, newest first
794
+ all_patients_query = Patient.query.order_by(Patient.timestamp.desc())
795
+
796
+ # Convert SQLAlchemy objects to dictionaries
797
+ patients_list = [p.to_dict() for p in all_patients_query.all()]
798
+
799
+ # Manual sorting by status priority for display if needed, but JS handles this
800
+ # Or you can add complex ordering to the query if using PostgreSQL etc.
801
+ # For simplicity, JS handles sorting after getting the timestamp-ordered list.
802
 
803
  return jsonify({
804
  'success': True,
805
+ 'patients': patients_list
806
  })
807
 
808
 
809
  @app.route('/get_patient/<patient_id>')
810
  def get_patient(patient_id):
811
  print(f"API request for patient ID: {patient_id}")
 
812
 
813
+ # Fetch patient from the database
814
+ patient = Patient.query.get(patient_id)
815
 
816
  if not patient:
817
+ print(f"Patient ID {patient_id} not found in database for API request.")
818
+ return jsonify({'success': False, 'error': 'Patient not found.'}), 404
819
 
820
+ print(f"Found patient {patient_id}: {patient.name}")
821
  return jsonify({
822
  'success': True,
823
+ 'patient': patient.to_dict() # Return dictionary representation
824
  })
825
 
826
+ # Helper to serve PDF file directly (used by doctor dashboard PDF viewer)
827
+ # Note: This route is potentially less secure than storing files on disk and serving them
828
+ # or generating on demand. But for a demo, it works with the base64 data flow.
829
+ # A better approach for large PDFs or production would be saving the PDF server-side
830
+ # and serving a URL that points to the saved file, or using a dedicated PDF viewer lib.
831
+ # For this fix, let's stick to serving the *generated* PDF data via download link,
832
+ # and rely on the browser's embedding for the patient view preview on initial submit.
833
+ # Doctor dashboard will just show the text content in tabs.
834
+
835
+ # Removed the direct PDF view route as it's problematic.
836
+ # The doctor dashboard will display the text content in the tabs.
837
+
838
 
839
  if __name__ == '__main__':
840
  # Use a more robust development server like Waitress or Gunicorn in production
841
+ # Ensure database tables are created before running the app
842
+ with app.app_context():
843
+ db.create_all()
844
+ app.run(debug=True) # Explicitly set port