File size: 14,075 Bytes
cf59d7b
b6ac253
 
 
 
cf59d7b
b6ac253
 
 
 
bd95f58
b6ac253
 
 
 
bd95f58
cf59d7b
 
b6ac253
 
fae50cb
459fd06
 
b6ac253
bd95f58
cf59d7b
b6ac253
cf59d7b
b6ac253
fae50cb
 
b6ac253
 
bd95f58
b6ac253
 
 
fae50cb
459fd06
 
 
 
bd95f58
 
cf59d7b
b6ac253
9417746
b6ac253
1e9f854
 
 
 
 
b6ac253
 
73445ca
1e9f854
 
b6ac253
1e9f854
b6ac253
 
1e9f854
b6ac253
 
9f1ce89
fae50cb
bd95f58
fae50cb
bd95f58
 
 
 
cf59d7b
bd95f58
cf59d7b
fae50cb
 
cf59d7b
fae50cb
bd95f58
fae50cb
 
bd95f58
fae50cb
bd95f58
 
fae50cb
 
 
 
bd95f58
cf59d7b
fae50cb
b6ac253
fae50cb
b6ac253
bd95f58
fae50cb
 
bd95f58
 
 
fae50cb
 
 
cf59d7b
fae50cb
 
 
cf59d7b
bd95f58
fae50cb
cf59d7b
bd95f58
cf59d7b
bd95f58
fae50cb
 
cf59d7b
 
 
bd95f58
cf59d7b
bd95f58
fae50cb
cf59d7b
 
fae50cb
bd95f58
cf59d7b
bd95f58
fae50cb
cf59d7b
 
 
bd95f58
fae50cb
 
459fd06
 
fae50cb
 
bd95f58
 
 
459fd06
fae50cb
 
bd95f58
 
 
 
b6ac253
bd95f58
b6ac253
bd95f58
b6ac253
 
bd95f58
 
 
b6ac253
bd95f58
 
 
 
b6ac253
9417746
bd95f58
b6ac253
 
 
bd95f58
 
 
 
 
 
 
459fd06
cf59d7b
 
b6ac253
 
bd95f58
 
 
cf59d7b
bd95f58
 
 
 
b6ac253
bd95f58
b6ac253
 
9417746
bd95f58
 
 
 
 
 
 
 
459fd06
bd95f58
 
 
 
 
 
 
 
 
459fd06
bd95f58
 
459fd06
bd95f58
 
 
 
 
 
 
 
459fd06
bd95f58
 
 
 
 
 
 
 
 
 
88810b9
 
cf59d7b
 
459fd06
 
 
88810b9
cf59d7b
88810b9
 
 
bd95f58
b6ac253
 
 
 
bd95f58
 
 
b6ac253
bd95f58
 
 
 
 
459fd06
bd95f58
 
 
 
 
9417746
bd95f58
 
 
 
 
 
 
 
459fd06
b6ac253
 
bd95f58
 
459fd06
 
 
 
 
 
 
bd95f58
 
459fd06
 
 
 
 
 
 
 
 
 
 
 
bd95f58
459fd06
 
 
 
 
 
bd95f58
 
 
459fd06
bd95f58
 
 
 
459fd06
bd95f58
 
b6ac253
 
 
bd95f58
 
 
 
 
 
 
 
459fd06
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bd95f58
 
459fd06
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bd95f58
 
459fd06
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bd95f58
 
 
 
 
 
 
 
 
 
 
 
 
b6ac253
bd95f58
b6ac253
459fd06
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
import os
import json
import logging
import tempfile
import re
from dotenv import load_dotenv
from flask import Flask, request, jsonify, render_template, send_file
from groq import Groq
import google.generativeai as genai
import pandas as pd
from datetime import datetime, date, time
from apscheduler.schedulers.background import BackgroundScheduler
from twilio.rest import Client
import io

# Load environment variables
load_dotenv()

app = Flask(__name__)

# Configure API keys (keep as is)
GROQ_API_KEY = os.getenv('GROQ_API_KEY', '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')
GEMINI_API_KEY = os.getenv('GEMINI_API_KEY', 'AIzaSyCizcswP6vlKDMdB3HRAtVi2JbifOpbPvA')

# Initialize clients
groq_client = Groq(api_key=GROQ_API_KEY)
genai.configure(api_key=GEMINI_API_KEY)
MODEL_NAME = "gemini-1.5-flash"

# Initialize scheduler with local timezone to ensure correct timing
scheduler = BackgroundScheduler(timezone='Asia/Kolkata')
scheduler.start()

# Logger setup
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# Twilio configuration (keep as is)
ACCOUNT_SID = 'AC490e071f8d01bf0df2f03d086c788d87'
AUTH_TOKEN = '224b23b950ad5a4052aba15893fdf083'
TWILIO_FROM = 'whatsapp:+14155238886'
PATIENT_PHONE = 'whatsapp:+917559355282'

# Initialize Twilio client
twilio_client = Client(ACCOUNT_SID, AUTH_TOKEN)


def send_whatsapp_message(message):
    """Send WhatsApp notification"""
    # Format the message with WhatsApp-compatible formatting
    # Use emojis and formatting to make the feedback link stand out
    feedback_message = message + "\n\nπŸ”„ Need help or plan not working?\nπŸ‘‰ *FEEDBACK*: https://rajkhanke-patient-feedback-system.hf.space/"
    
    try:
        msg = twilio_client.messages.create(
            from_=TWILIO_FROM,
            body=feedback_message,
            to=PATIENT_PHONE
        )
        logger.info(f"WhatsApp notification sent: {msg.sid}")
        return True
    except Exception as e:
        logger.error(f"Failed to send WhatsApp notification: {str(e)}")
        return False


# Predefined descriptive slots with time objects
DESCRIPTIVE_SLOTS = {
    'Morning':   time(hour=8, minute=0),
    'Afternoon': time(hour=13, minute=0),
    'Evening':   time(hour=18, minute=0),
    'Night':     time(hour=21, minute=0),
}

def parse_time_slot(time_str):
    """
    Parse time strings like "hh:mm AM/PM" or descriptive slots.
    Returns a time object or None.
    """
    # Try "hh:mm AM/PM"
    try:
        dt = datetime.strptime(time_str, '%I:%M %p')
        return dt.time()
    except ValueError:
        # Fallback to descriptor map
        slot = DESCRIPTIVE_SLOTS.get(time_str.title())
        if slot:
            return slot
        logger.warning(f"Unrecognized time format: '{time_str}'")
        return None


def schedule_notifications(schedule_data):
    """Schedule WhatsApp notifications."""
    try:
        # Clear existing jobs
        scheduler.remove_all_jobs()

        # Sections to schedule
        sections = [
            ('medication_schedule', 'med',   {'time': 'Scheduled Time', 'fields': ['Meal', 'Medication Name', 'Dosage']}),
            ('meal_schedule',       'meal',  {'time': 'Time',           'fields': ['Meal', 'Details']}),
            ('activity_schedule',   'activity', {'time': 'Time',       'fields': ['Activity', 'Duration', 'Notes']}),
        ]

        for section, prefix, key_map in sections:
            for item in schedule_data.get(section, []):
                time_str = item.get(key_map['time'], '')
                t = parse_time_slot(time_str)
                if not t:
                    continue

                # Build message
                if section == 'medication_schedule':
                    message = (
                        f"πŸ”” Medication Reminder\n"
                        f"Time: {time_str}\n"
                        f"Meal: {item.get('Meal', '')}\n"
                        f"Medication: {item.get('Medication Name', '')}\n"
                        f"Dosage: {item.get('Dosage', 'As prescribed')}"
                    )
                elif section == 'meal_schedule':
                    message = (
                        f"🍽 Meal Reminder\n"
                        f"Time: {time_str}\n"
                        f"Meal: {item.get('Meal', '')}\n"
                        f"Details: {item.get('Details', '')}"
                    )
                else:
                    message = (
                        f"πŸƒ Activity Reminder\n"
                        f"Time: {time_str}\n"
                        f"Activity: {item.get('Activity', '')}\n"
                        f"Duration: {item.get('Duration', '')}\n"
                        f"Notes: {item.get('Notes', '')}"
                    )

                # Generate unique job ID
                safe_name = re.sub(r"[^A-Za-z0-9_]+", "_", item.get(key_map['fields'][0], ''))
                safe_time = time_str.replace(':', '').replace(' ', '')
                job_id = f"{prefix}_{safe_name}_{safe_time}"

                # Schedule the job
                scheduler.add_job(
                    send_whatsapp_message,
                    trigger='cron',
                    args=[message],
                    hour=t.hour,
                    minute=t.minute,
                    id=job_id,
                    replace_existing=True
                )

        return True

    except Exception as e:
        logger.error(f"Failed to schedule notifications: {e}")
        return False



# βœ… Function to Extract JSON from Response
def extract_json(text):
    json_match = re.search(r'\{.*\}', text, re.DOTALL)
    if json_match:
        return json_match.group(0)
    return None


# βœ… Function to Transcribe Audio Using Groq
def transcribe_audio(audio_file):
    try:
        temp_dir = tempfile.mkdtemp()
        temp_path = os.path.join(temp_dir, "temp_audio.mp3")
        audio_file.save(temp_path)

        # Transcribe audio
        with open(temp_path, "rb") as file:
            transcription = groq_client.audio.transcriptions.create(
                file=(temp_path, file.read()),
                model="whisper-large-v3-turbo",
                response_format="json",
                language="en",
                temperature=0.0
            )

        # Cleanup temporary files
        os.remove(temp_path)
        os.rmdir(temp_dir)

        logger.info(f"Transcription successful: {transcription.text[:100]}...")
        return transcription.text

    except Exception as e:
        logger.error(f"Transcription error: {str(e)}")
        return None


# βœ… Function to Generate Care Plan Using Gemini
def generate_care_plan(conversation_text):
    try:
        model = genai.GenerativeModel(MODEL_NAME)
        prompt = f"""
            Based on the following conversation transcript between a doctor and a patient, generate a structured care plan in *valid JSON format*.
            *JSON Format:*
            {{
                "medication_schedule": [{{"Medication Name": ""}}],
                "meal_schedule": [{{"Time": "", "Meal": "", "Details": ""}}],
                "activity_schedule": [{{"Time": "", "Activity": "", "Duration": "", "Notes": ""}}]
            }}
            *Fixed Meal Times:*
            - Breakfast: 7:00 AM
            - Snack: 10:00 AM
            - Lunch: 1:00 PM
            - Snack: 5:00 PM
            - Dinner: 8:00 PM
            Ensure medications align with meals. Adjust exercise based on health conditions.
            *Conversation Transcript:*
            {conversation_text}
            *Output Strictly JSON. Do NOT add explanations or extra text.*
        """

        response = model.generate_content(prompt)
        raw_response = response.text
        logger.info(f"Raw Gemini Response: {raw_response[:100]}...")

        json_text = extract_json(raw_response)
        if json_text:
            return json.loads(json_text)

        logger.error("No valid JSON found in response.")
        return create_default_care_plan()

    except Exception as e:
        logger.error(f"Care plan generation error: {str(e)}")
        return create_default_care_plan()


# βœ… Function to Create a Default Care Plan (Fallback)
def create_default_care_plan():
    return {
        "medication_schedule": [],
        "meal_schedule": [
            {"Time": "7:00 AM", "Meal": "Breakfast", "Details": ""},
            {"Time": "1:00 PM", "Meal": "Lunch", "Details": ""},
            {"Time": "8:00 PM", "Meal": "Dinner", "Details": ""}
        ],
        "activity_schedule": []
    }


# βœ… Route: Homepage
@app.route('/')
def index():
    return render_template('index.html')


# βœ… Route: Generate Care Plan
@app.route("/generate_care_plan", methods=["POST"])
def generate_care_plan_route():
    try:
        if 'audio' not in request.files:
            return jsonify({"error": "No audio file provided"}), 400

        audio_file = request.files['audio']
        if not audio_file:
            return jsonify({"error": "Empty audio file"}), 400

        transcript = transcribe_audio(audio_file)
        if not transcript:
            return jsonify({"error": "Transcription failed"}), 500

        care_plan = generate_care_plan(transcript)
        return jsonify(care_plan)

    except Exception as e:
        logger.error(f"Error in generate_care_plan_route: {str(e)}")
        return jsonify({"error": str(e)}), 500


# βœ… Route: Generate Medication Schedule
@app.route('/generate_schedule', methods=['POST'])
def generate_schedule():
    try:
        data = request.get_json()
        medicines = data.get("medicines", [])
        schedule_list = []

        meal_times = {
            3: [("Breakfast", "7:00 AM"), ("Lunch", "1:00 PM"), ("Dinner", "8:00 PM")],
            2: [("Breakfast", "7:00 AM"), ("Dinner", "8:00 PM")],
            1: [("Dinner", "8:00 PM")]
        }

        for med in medicines:
            medication_name = med.get("medication_name", "")
            frequency = int(med.get("frequency", 1))
            dosage = med.get("dosage", "")

            for meal, time in meal_times.get(frequency, []):
                schedule_list.append({
                    "Medication Name": medication_name,
                    "Dosage": dosage,
                    "Meal": meal,
                    "Scheduled Time": time
                })

        # Schedule notifications
        schedule_data = {
            'medication_schedule': schedule_list,
            'meal_schedule': data.get('meal_schedule', []),
            'activity_schedule': data.get('activity_schedule', [])
        }
        notifications_scheduled = schedule_notifications(schedule_data)

        return jsonify({
            "schedule_list": schedule_list,
            "notifications_scheduled": notifications_scheduled
        })

    except Exception as e:
        logger.error(f"Schedule generation error: {str(e)}")
        return jsonify({"error": str(e)}), 500


@app.route('/download_schedule', methods=['POST'])
def download_schedule():
    try:
        data = request.json

        # Create Excel writer object
        output = io.BytesIO()
        with pd.ExcelWriter(output, engine='xlsxwriter') as writer:
            workbook = writer.book

            # Convert medication schedule to DataFrame and write to Excel
            if data.get('medication_schedule'):
                med_df = pd.DataFrame(data['medication_schedule'])
                med_df = med_df.sort_values('Time') if 'Time' in med_df.columns else med_df
                med_df.to_excel(writer, sheet_name='Medication Schedule', index=False)

                worksheet = writer.sheets['Medication Schedule']
                header_format = workbook.add_format({
                    'bold': True,
                    'bg_color': '#3498db',
                    'font_color': 'white'
                })

                for col_num, value in enumerate(med_df.columns.values):
                    worksheet.write(0, col_num, value, header_format)
                    worksheet.set_column(col_num, col_num, 15)  # Set column width

            # Write meal schedule
            if data.get('meal_schedule'):
                meal_df = pd.DataFrame(data['meal_schedule'])
                meal_df = meal_df.sort_values('Time')
                meal_df.to_excel(writer, sheet_name='Meal Schedule', index=False)

                worksheet = writer.sheets['Meal Schedule']
                header_format = workbook.add_format({
                    'bold': True,
                    'bg_color': '#2ecc71',
                    'font_color': 'white'
                })

                for col_num, value in enumerate(meal_df.columns.values):
                    worksheet.write(0, col_num, value, header_format)
                    worksheet.set_column(col_num, col_num, 15)

            # Write activity schedule
            if data.get('activity_schedule'):
                activity_df = pd.DataFrame(data['activity_schedule'])
                activity_df = activity_df.sort_values('Time')
                activity_df.to_excel(writer, sheet_name='Activity Schedule', index=False)

                worksheet = writer.sheets['Activity Schedule']
                header_format = workbook.add_format({
                    'bold': True,
                    'bg_color': '#e74c3c',
                    'font_color': 'white'
                })

                for col_num, value in enumerate(activity_df.columns.values):
                    worksheet.write(0, col_num, value, header_format)
                    worksheet.set_column(col_num, col_num, 15)

        output.seek(0)
        return send_file(
            output,
            mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
            as_attachment=True,
            download_name='daily_schedule.xlsx'
        )

    except Exception as e:
        logger.error(f"Error generating Excel file: {str(e)}")
        return jsonify({"error": str(e)}), 500


# βœ… Run Flask App
if __name__ == '__main__':
    app.run(debug=True, threaded=True)