added new project to communuty section.
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user