7 changed files with 458 additions and 0 deletions
@ -0,0 +1 @@
|
||||
"Your google auth credentials." |
@ -0,0 +1,35 @@
|
||||
import os |
||||
from google.oauth2.credentials import Credentials |
||||
from google_auth_oauthlib.flow import InstalledAppFlow |
||||
from google.auth.transport.requests import Request |
||||
from googleapiclient.discovery import build # Add this import |
||||
|
||||
SCOPES = ["https://www.googleapis.com/auth/calendar.events"] |
||||
|
||||
def authenticate_google_calender(): |
||||
creds = None |
||||
token_path = r"C:\Users\Legion\Desktop\projects\medical_prescription_to_google_calender\token.json" |
||||
|
||||
if os.path.exists(token_path): |
||||
creds = Credentials.from_authorized_user_file(token_path, SCOPES) |
||||
|
||||
if not creds or not creds.valid: |
||||
if creds and creds.expired and creds.refresh_token: |
||||
creds.refresh(Request()) |
||||
else: |
||||
flow = InstalledAppFlow.from_client_secrets_file(r"C:\Users\Legion\Desktop\projects\medical_prescription_to_google_calender\credentials.json", SCOPES) |
||||
creds = flow.run_local_server(port=0) |
||||
|
||||
with open(token_path, "w") as token_file: |
||||
token_file.write(creds.to_json()) |
||||
|
||||
# Build and return the service instead of just credentials |
||||
try: |
||||
service = build('calendar', 'v3', credentials=creds) |
||||
return service |
||||
except Exception as e: |
||||
print(f"Error building service: {e}") |
||||
return None |
||||
|
||||
if __name__ == "__main__": |
||||
authenticate_google_calender() |
@ -0,0 +1,64 @@
|
||||
from googleapiclient.discovery import build |
||||
from calendar_auth import authenticate_google_calender |
||||
from parsing_json import format_calendar_events |
||||
from datetime import datetime, timedelta |
||||
|
||||
def create_event(service, event_details): |
||||
"""Creates an event in Google Calendar.""" |
||||
try: |
||||
event = service.events().insert(calendarId='primary', body=event_details).execute() |
||||
print(f"Event created: {event.get('htmlLink')}") |
||||
except Exception as e: |
||||
print(f"Error creating event: {str(e)}") |
||||
|
||||
def convert_time_to_24hr(time_str): |
||||
"""Converts time from '10:30 am' format to '10:30:00'""" |
||||
if time_str and time_str.lower() != 'none': |
||||
try: |
||||
parsed_time = datetime.strptime(time_str, '%I:%M %p') |
||||
return parsed_time.strftime('%H:%M:%S') |
||||
except ValueError: |
||||
return '09:00:00' |
||||
return '09:00:00' |
||||
|
||||
def convert_to_gcal_events(formatted_events): |
||||
"""Converts formatted events into Google Calendar's format.""" |
||||
gcal_events = [] |
||||
|
||||
for event in formatted_events: |
||||
gcal_event = { |
||||
'summary': event['summary'], |
||||
'reminders': { |
||||
'useDefault': False, |
||||
'overrides': [{'method': 'popup', 'minutes': 10}] |
||||
} |
||||
} |
||||
|
||||
# Check if it's an all-day event (has 'date') or timed event (has 'dateTime') |
||||
if 'date' in event['start']: |
||||
# All-day event (like tests and follow-ups) |
||||
gcal_event['start'] = { |
||||
'date': event['start']['date'], |
||||
'timeZone': 'Asia/Kolkata' |
||||
} |
||||
gcal_event['end'] = { |
||||
'date': event['end']['date'], |
||||
'timeZone': 'Asia/Kolkata' |
||||
} |
||||
else: |
||||
# Timed event (like medicine schedules) |
||||
start_dt = datetime.strptime(event['start']['dateTime'], '%Y-%m-%dT%H:%M:%S') |
||||
end_dt = start_dt + timedelta(minutes=30) |
||||
|
||||
gcal_event['start'] = { |
||||
'dateTime': start_dt.isoformat(), |
||||
'timeZone': 'Asia/Kolkata' |
||||
} |
||||
gcal_event['end'] = { |
||||
'dateTime': end_dt.isoformat(), |
||||
'timeZone': 'Asia/Kolkata' |
||||
} |
||||
|
||||
gcal_events.append(gcal_event) |
||||
|
||||
return gcal_events |
@ -0,0 +1,26 @@
|
||||
from ocr import * |
||||
from calendar_auth import * |
||||
from create_calender_events import * |
||||
from parsing_json import * |
||||
from preprocess import * |
||||
|
||||
image_path = r"C:\Users\Legion\Desktop\projects\medical_prescription_to_google_calender\test_data\prescription_page-0001.jpg" |
||||
|
||||
extracted_text = extract_text_from_image(image_path=image_path) |
||||
print(extracted_text) |
||||
cleaned_text = clean_text(extracted_text) |
||||
print(cleaned_text) |
||||
structured_data = preprocess_extracted_text(cleaned_text) |
||||
print(structured_data) |
||||
final_structured_data = process_dates(structured_data) |
||||
print(final_structured_data) |
||||
formatted_calender_events = format_calendar_events(final_structured_data) |
||||
print(formatted_calender_events) |
||||
validated_events = [validate_event(event) for event in formatted_calender_events] |
||||
for event in validated_events[:5]: |
||||
print(json.dumps(event, indent=2)) |
||||
service = authenticate_google_calender() |
||||
gcal_events = convert_to_gcal_events(validated_events) |
||||
|
||||
for event in gcal_events: |
||||
create_event(service, event) |
@ -0,0 +1,71 @@
|
||||
import os |
||||
from openai import OpenAI |
||||
from dotenv import load_dotenv |
||||
import base64 |
||||
from PIL import Image |
||||
import re |
||||
|
||||
load_dotenv() |
||||
|
||||
openai_api_key = os.getenv("OPENAI_API_KEY") |
||||
|
||||
MODEL = "gpt-4o" |
||||
|
||||
openai = OpenAI() |
||||
|
||||
def encode_image(image_path): |
||||
with open(image_path, "rb") as image_file: |
||||
return base64.b64encode(image_file.read()).decode("utf-8") |
||||
|
||||
def extract_text_from_image(image_path): |
||||
response = openai.chat.completions.create( |
||||
model = MODEL, |
||||
max_tokens = 1000, |
||||
messages=[ |
||||
{ |
||||
"role": "system", "content": """You are an OCR assistant that extracts text from medical |
||||
prescription images. Extract all the text exactly as it |
||||
appears in the prescription image. Dont include images. Only |
||||
extract text.""" |
||||
}, |
||||
{ |
||||
"role": "user", |
||||
"content": [ |
||||
{ |
||||
"type": "text", |
||||
"text": "Extract text from this image: " |
||||
}, |
||||
{ |
||||
"type": "image_url", |
||||
"image_url": { |
||||
"url": f"data:image/jpeg;base64,{encode_image(image_path)}" |
||||
} |
||||
} |
||||
] |
||||
} |
||||
] |
||||
) |
||||
return response.choices[0].message.content |
||||
|
||||
import re |
||||
|
||||
def clean_text(text): |
||||
# Remove all hyphens |
||||
text = re.sub(r'-', ' ', text) |
||||
|
||||
# Remove excessive non-word characters but keep necessary punctuation |
||||
text = re.sub(r'[^\w\s.,()%/]', '', text) |
||||
|
||||
# Remove multiple spaces and ensure single spaces |
||||
text = re.sub(r'\s+', ' ', text) |
||||
|
||||
# Replace multiple newlines with a single newline |
||||
text = re.sub(r'\n+', '\n', text) |
||||
|
||||
# Ensure spacing around punctuation marks |
||||
text = re.sub(r'([.,])([^\s])', r'\1 \2', text) |
||||
|
||||
return text.strip() |
||||
|
||||
|
||||
|
@ -0,0 +1,120 @@
|
||||
import json |
||||
import re |
||||
from datetime import datetime, timedelta |
||||
|
||||
# Default number of days to schedule indefinitely recurring events (1 year) |
||||
DEFAULT_DURATION_DAYS = 365 |
||||
|
||||
# Function to assign a default time for general terms like "before breakfast", etc. |
||||
def assign_time(timing): |
||||
time_mappings = { |
||||
"random": "09:00 AM", |
||||
"daily": "09:00 AM", |
||||
"before breakfast": "07:00 AM", |
||||
"after breakfast": "08:30 AM", |
||||
"before lunch": "12:00 PM", |
||||
"after lunch": "01:30 PM", |
||||
"before dinner": "07:00 PM", |
||||
"after dinner": "08:30 PM", |
||||
} |
||||
return time_mappings.get(timing.lower(), timing) |
||||
|
||||
# Function to extract the recurrence pattern |
||||
def get_recurrence_interval(timing): |
||||
""" Extracts interval days from 'every X days', 'once a week', or 'once a month'. """ |
||||
timing = timing.lower().strip() |
||||
|
||||
if "every alternate day" in timing: |
||||
return 2 # Every other day (every 2 days) |
||||
elif match := re.search(r"every (\d+) days", timing): |
||||
return int(match.group(1)) # Extract number of days |
||||
elif "once a week" in timing: |
||||
return 7 # Every 7 days (once a week) |
||||
elif "once a month" in timing: |
||||
return "monthly" # Special case for monthly scheduling |
||||
elif timing in ["daily", "every day"]: |
||||
return 1 # Every day |
||||
else: |
||||
return None # Not a recurring event |
||||
|
||||
# Function to convert AM/PM time format to 24-hour format |
||||
def convert_to_24hr(time_str): |
||||
return datetime.strptime(time_str, "%I:%M %p").strftime("%H:%M") |
||||
|
||||
# Function to generate Google Calendar events |
||||
def format_calendar_events(processed_data): |
||||
events = [] |
||||
start_date = datetime.today().date() |
||||
|
||||
# Medicines |
||||
if "medicines" in processed_data: |
||||
for med in processed_data["medicines"]: |
||||
if med.get("name"): |
||||
event_time = assign_time(med.get("timing", "09:00 AM")) |
||||
interval_days = get_recurrence_interval(med["timing"]) |
||||
|
||||
# If no interval, assume daily (default behavior) |
||||
if interval_days is None: |
||||
interval_days = 1 |
||||
|
||||
# Generate events for 1 year if no duration is given |
||||
event_date = start_date |
||||
for _ in range(365 if interval_days != "monthly" else 12): |
||||
if interval_days == "monthly": |
||||
event_date = (event_date.replace(day=1) + timedelta(days=32)).replace(day=1) # Jump to the next month |
||||
else: |
||||
event_date += timedelta(days=interval_days) |
||||
|
||||
event = { |
||||
"summary": f"Take {med['name']} ({med.get('dosage', 'Dosage not specified')})", |
||||
"start": { |
||||
"dateTime": f"{event_date.isoformat()}T{convert_to_24hr(event_time)}:00", |
||||
"timeZone": "Asia/Kolkata" |
||||
}, |
||||
"end": { |
||||
"dateTime": f"{event_date.isoformat()}T{convert_to_24hr(event_time)}:59", |
||||
"timeZone": "Asia/Kolkata" |
||||
} |
||||
} |
||||
events.append(event) |
||||
|
||||
# Tests |
||||
if "tests" in processed_data: |
||||
for test in processed_data["tests"]: |
||||
if test.get("name") and test.get("dueDate"): # Use 'dueDate' instead of 'date' |
||||
event = { |
||||
"summary": f"Medical Test: {test['name']}", |
||||
"start": {"date": test["dueDate"]}, # Fix here |
||||
"end": {"date": test["dueDate"]}, # Fix here |
||||
"timeZone": "Asia/Kolkata" |
||||
} |
||||
events.append(event) |
||||
|
||||
|
||||
# Follow-ups |
||||
if "follow_ups" in processed_data: |
||||
for follow_up in processed_data["follow_ups"]: |
||||
if follow_up.get("date"): |
||||
event = { |
||||
"summary": "Doctor Follow-up Appointment", |
||||
"start": {"date": follow_up["date"]}, |
||||
"end": {"date": follow_up["date"]}, |
||||
"timeZone": "Asia/Kolkata" |
||||
} |
||||
events.append(event) |
||||
|
||||
return events |
||||
|
||||
# Function to validate events before sending to Google Calendar |
||||
def validate_event(event): |
||||
required_fields = { |
||||
"summary": "Untitled Event", |
||||
"start": {"dateTime": datetime.today().isoformat(), "timeZone": "Asia/Kolkata"}, |
||||
"end": {"dateTime": (datetime.today() + timedelta(minutes=30)).isoformat(), "timeZone": "Asia/Kolkata"} |
||||
} |
||||
|
||||
for field, default_value in required_fields.items(): |
||||
if field not in event or event[field] is None: |
||||
event[field] = default_value |
||||
|
||||
return event |
@ -0,0 +1,141 @@
|
||||
import os |
||||
from openai import OpenAI |
||||
from dotenv import load_dotenv |
||||
import json |
||||
from datetime import datetime, timedelta |
||||
|
||||
load_dotenv() |
||||
|
||||
openai_api = os.getenv("OPENAI_API_KEY") |
||||
MODEL = "gpt-4o-mini" |
||||
openai = OpenAI() |
||||
|
||||
system_prompt = """You are a medical assistant that processes prescription text. |
||||
Your goal is to extract medicines, tests, and follow-ups in a structured JSON format. |
||||
|
||||
### **Instructions:** |
||||
- Extract **medicines**, **dosages**, and **timings** if available. |
||||
- **Convert vague timings** into precise values: |
||||
- **Before breakfast** → `07:30 AM` |
||||
- **After lunch** → `02:00 PM` |
||||
- **Before dinner** → `07:00 PM` |
||||
- **After dinner** → `10:00 PM` |
||||
- **30 minutes before breakfast** → `07:00 AM` |
||||
- If **"daily"** is mentioned without a time, **assign a logical time** between **08:00 AM - 10:00 PM**. |
||||
- If the prescription says **"every alternate day"**, return `"interval": 2` instead of just `"daily"`. |
||||
|
||||
### **Tests & Follow-ups:** |
||||
- Extract **medical tests** and their required dates. |
||||
- Convert relative times (e.g., `"after 3 months"`) into **exact calendar dates**, using the prescription date. |
||||
- If the prescription date is missing, use today's date. |
||||
- Follow-up should **only be included if required**, not just for general check-ups. |
||||
|
||||
### **Output Format:** |
||||
Return **only valid JSON**, structured as follows: |
||||
|
||||
{ |
||||
"medicines": [ |
||||
{ |
||||
"name": "<Medicine Name>", |
||||
"dosage": "<Dosage>", |
||||
"timing": "<Time>", |
||||
"interval": <Interval in days (if applicable)> |
||||
} |
||||
], |
||||
"tests": [ |
||||
{ |
||||
"name": "<Test Name>", |
||||
"date": "<YYYY-MM-DD>" |
||||
} |
||||
], |
||||
"follow_ups": [ |
||||
{ |
||||
"date": "<YYYY-MM-DD>" |
||||
} |
||||
] |
||||
} |
||||
""" |
||||
|
||||
def clean_json_string(json_str): |
||||
"""Clean and validate JSON string before parsing.""" |
||||
try: |
||||
start = json_str.find('{') |
||||
end = json_str.rfind('}') + 1 |
||||
if start >= 0 and end > 0: |
||||
json_str = json_str[start:end] |
||||
|
||||
# Remove any extra whitespace |
||||
json_str = json_str.strip() |
||||
|
||||
# Attempt to parse the JSON |
||||
return json.loads(json_str) |
||||
except json.JSONDecodeError as e: |
||||
print(f"Failed to parse JSON. Raw response:\n{json_str}") |
||||
print(f"Error: {str(e)}") |
||||
return None |
||||
|
||||
def preprocess_extracted_text(extracted_text): |
||||
"""Calls GPT-4o-mini to process prescription text into structured JSON.""" |
||||
try: |
||||
response = openai.chat.completions.create( |
||||
model=MODEL, |
||||
messages=[ |
||||
{ |
||||
"role": "system", |
||||
"content": system_prompt, |
||||
}, |
||||
{ |
||||
"role": "user", |
||||
"content": f"Process this prescription and return ONLY valid JSON:\n\n{extracted_text}" |
||||
} |
||||
], |
||||
temperature=0.3 # Lower temperature for more consistent JSON output |
||||
) |
||||
|
||||
# Get the response content |
||||
content = response.choices[0].message.content |
||||
|
||||
# Clean and parse the JSON |
||||
parsed_data = clean_json_string(content) |
||||
|
||||
if parsed_data is None: |
||||
return { |
||||
"medicines": [], |
||||
"tests": [], |
||||
"follow_ups": [] |
||||
} |
||||
|
||||
return parsed_data |
||||
|
||||
except Exception as e: |
||||
print(f"Error in API call or processing: {str(e)}") |
||||
return { |
||||
"medicines": [], |
||||
"tests": [], |
||||
"follow_ups": [] |
||||
} |
||||
|
||||
def process_dates(data): |
||||
"""Adjusts test dates and follow-up based on the prescription date or today's date.""" |
||||
try: |
||||
# Extract prescription date (if available) or use today's date |
||||
prescription_date = datetime.strptime("02 JANUARY 2025", "%d %B %Y").date() |
||||
|
||||
# Process test dates |
||||
for test in data.get("tests", []): |
||||
if isinstance(test, dict) and "date" not in test and "after_months" in test: |
||||
test_date = prescription_date + timedelta(days=test["after_months"] * 30) |
||||
test["date"] = test_date.strftime("%Y-%m-%d") |
||||
|
||||
# Process follow-up dates |
||||
follow_ups = data.get("follow_ups", []) |
||||
for follow_up in follow_ups: |
||||
if isinstance(follow_up, dict) and "date" not in follow_up and "after_months" in follow_up: |
||||
follow_up_date = prescription_date + timedelta(days=follow_up["after_months"] * 30) |
||||
follow_up["date"] = follow_up_date.strftime("%Y-%m-%d") |
||||
|
||||
return data |
||||
|
||||
except Exception as e: |
||||
print(f"Error processing dates: {str(e)}") |
||||
return data |
Loading…
Reference in new issue