diff --git a/Database/SQL_medical_assistant.sql b/Database/SQL_medical_assistant.sql new file mode 100644 index 0000000000..30088f0f8a --- /dev/null +++ b/Database/SQL_medical_assistant.sql @@ -0,0 +1,443 @@ +--CREATE DATABASE medical_assistant; + +DROP SCHEMA medical_assistant CASCADE; + +CREATE SCHEMA medical_assistant; + +--- in general for all columns with string values text data type is chosen, for number values int type is chosen +--- for each table surrogate key is generated by GENERATED ALWAYS AS IDENTITY and PK constraint is added + + +CREATE TABLE IF NOT EXISTS medical_assistant.report_type ( + report_type_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + report_type_name VARCHAR(50) NOT NULL); + +CREATE TABLE IF NOT EXISTS medical_assistant.doctor_specialization ( + specialization_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + specialization_name VARCHAR(50) NOT NULL); + +CREATE TABLE IF NOT EXISTS medical_assistant.user ( + user_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + first_name VARCHAR (50) NOT NULL, + last_name VARCHAR (50) NOT NULL, + birth_date DATE NOT NULL, + phone_number VARCHAR (10) NOT NULL); + + +CREATE TABLE IF NOT EXISTS medical_assistant.hospitals ( + hospital_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + hospital_name VARCHAR (50) NOT NULL, + address_line VARCHAR (50) NOT NULL, + phone_number VARCHAR (10) NOT NULL); + +CREATE TABLE IF NOT EXISTS medical_assistant.medicine ( + medicine_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + medicine_name VARCHAR(50) NOT NULL, + dosage INT NOT NULL, + times INT NOT NULL); + +CREATE TABLE IF NOT EXISTS medical_assistant.diagnosis ( + diagnosis_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + diagnosis_name VARCHAR (50) NOT NULL, + recommendations VARCHAR (100) NOT NULL); + +CREATE TABLE IF NOT EXISTS medical_assistant.reports ( + report_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + report_date DATE NOT NULL, + test_name VARCHAR (50) NOT NULL, + test_result NUMERIC (12,2) NOT NULL, + test_units VARCHAR (10) NOT NULL, + test_reference_range VARCHAR (20) NOT NULL, + report_type_id INT NOT NULL REFERENCES medical_assistant.report_type (report_type_id ) ON UPDATE CASCADE, + user_id INT NOT NULL REFERENCES medical_assistant.user (user_id ) ON UPDATE CASCADE, + hospital_id INT NOT NULL REFERENCES medical_assistant.hospitals (hospital_id) ON UPDATE CASCADE, + interpretation TEXT NULL); + +CREATE TABLE IF NOT EXISTS medical_assistant.doctors ( + doctor_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + first_name VARCHAR (50) NOT NULL, + last_name VARCHAR (50) NOT NULL, + phone_number VARCHAR (10) NOT NULL, + email VARCHAR (50) NOT NULL, + hospital_id INT NOT NULL REFERENCES medical_assistant.hospitals (hospital_id) ON UPDATE CASCADE, + specialization_id INT NOT NULL REFERENCES medical_assistant.doctor_specialization (specialization_id) ON UPDATE CASCADE); + +CREATE TABLE IF NOT EXISTS medical_assistant.appointments ( + appointment_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + appointment_date DATE NOT NULL, + payment_total NUMERIC (12,2) NOT NULL, + user_id INT NOT NULL REFERENCES medical_assistant.user (user_id) ON UPDATE CASCADE, + hospital_id INT NOT NULL REFERENCES medical_assistant.hospitals (hospital_id) ON UPDATE CASCADE, + doctor_id INT NOT NULL REFERENCES medical_assistant.doctors (doctor_id) ON UPDATE CASCADE); + +CREATE TABLE IF NOT EXISTS medical_assistant.diagnosis_by_doctor ( + doctor_id INT NOT NULL REFERENCES medical_assistant.doctors (doctor_id) ON UPDATE CASCADE, + diagnosis_id INT NOT NULL REFERENCES medical_assistant.diagnosis (diagnosis_id) ON UPDATE CASCADE); + + CREATE TABLE IF NOT EXISTS medical_assistant.medicine_by_diagnosis ( + medicine_id INT NOT NULL REFERENCES medical_assistant.medicine (medicine_id) ON UPDATE CASCADE, + diagnosis_id INT NOT NULL REFERENCES medical_assistant.diagnosis (diagnosis_id) ON UPDATE CASCADE); + + +------Create all table relationships with primary and foreign keys. + +-- FK created while the DB was being created; + +-- ADD PK TO bridge TABLES FOR the combitation OF ids TO be unique + +ALTER TABLE medical_assistant.diagnosis_by_doctor ADD PRIMARY KEY (doctor_id, diagnosis_id); + +ALTER TABLE medical_assistant.medicine_by_diagnosis ADD PRIMARY KEY (medicine_id, diagnosis_id); + + +-- indexes for FK created, except for those tables where PK constraint is added to them + +CREATE INDEX IF NOT EXISTS report_type_id_idx ON medical_assistant.reports (report_type_id); + +CREATE INDEX IF NOT EXISTS user_id_idx ON medical_assistant.reports (user_id); + +CREATE INDEX IF NOT EXISTS hospital_id_idx ON medical_assistant.reports (hospital_id); + +CREATE INDEX IF NOT EXISTS hospital_id_idx ON medical_assistant.doctors (hospital_id); + +CREATE INDEX IF NOT EXISTS specialization_id_idx ON medical_assistant.doctors (specialization_id); + +CREATE INDEX IF NOT EXISTS user_id_idx ON medical_assistant.appointments (user_id); + +CREATE INDEX IF NOT EXISTS hospital_id_idx ON medical_assistant.appointments (hospital_id); + +CREATE INDEX IF NOT EXISTS doctor_id_idx ON medical_assistant.appointments (doctor_id); + +-- alter reports table to add case into interpretation column, so that it is autofilled with values "High", "Low", "Normal" + +ALTER TABLE reports DROP COLUMN interpretation; + +ALTER TABLE reports +ADD interpretation VARCHAR(10) GENERATED ALWAYS AS ( + CASE + WHEN test_result >= CAST(substring(test_reference_range FROM '^[0-9]+(\.[0-9]+)?') AS NUMERIC (12,2)) AND + test_result <= CAST(substring(test_reference_range FROM '-([0-9]+(\.[0-9]+)?)$') AS NUMERIC (12,2)) THEN 'Normal' + WHEN test_result < CAST(substring(test_reference_range FROM '^[0-9]+(\.[0-9]+)?') AS NUMERIC (12,2)) THEN 'Low' + WHEN test_result > CAST(substring(test_reference_range FROM '-([0-9]+(\.[0-9]+)?)$') AS NUMERIC (12,2)) THEN 'High' + END +) STORED; + +------Create some check constraints + +ALTER TABLE medical_assistant.user --- CONSTRAINT TO CHECK whether age OF a person IS NOT MORE than around 120 years +ADD CHECK (birth_date > '1900-01-01'); + +ALTER TABLE medical_assistant.user +ADD CHECK (phone_number ~ ('^[0-9]*$')); -- phone number should contain ONLY numbers AS a string value + +ALTER TABLE medical_assistant.doctors +ADD CHECK (phone_number ~ ('^[0-9]*$')); + +ALTER TABLE medical_assistant.hospitals +ADD CHECK (phone_number ~ ('^[0-9]*$')); + + +-------Fill tables with sample data + +--the hardcode is avoided by using subquiries mainly, insert into is used to insert values + +--for bulk inserts need to use CTE to avoid dublicates, for one inserted values can use direcly where not exists + + +WITH cte_report_type (report_type_name) as + (VALUES ('Blood report'), + ('Urinalysis')) +INSERT INTO medical_assistant.report_type (report_type_name) +SELECT report_type_name +FROM cte_report_type +WHERE NOT EXISTS ( + SELECT 1 + FROM medical_assistant.report_type + WHERE report_type_name = cte_report_type.report_type_name) +RETURNING report_type_id; + + +WITH cte_doctor_specialization (specialization_name) as + (VALUES ('Therapist'), + ('Сardiologist')) +INSERT INTO medical_assistant.doctor_specialization (specialization_name) +SELECT specialization_name +FROM cte_doctor_specialization +WHERE NOT EXISTS ( + SELECT 1 + FROM medical_assistant.doctor_specialization + WHERE specialization_name = cte_doctor_specialization.specialization_name) +RETURNING specialization_id; + + +WITH cte_medicine (medicine_name, + dosage, + times) AS +( VALUES ('Aspirin', + 500, + 1), + ('Paracetamol', + 500, + 2) + ) +INSERT INTO medical_assistant.medicine (medicine_name, + dosage, + times) +SELECT medicine_name, + dosage, + times +FROM cte_medicine +WHERE NOT EXISTS ( + SELECT 1 + FROM medical_assistant.medicine + WHERE medicine_name = cte_medicine.medicine_name + AND dosage = cte_medicine.dosage + AND times = cte_medicine.times) +RETURNING medicine_id; + + +WITH cte_diagnosis (diagnosis_name, + recommendations) AS +( VALUES ('Cold', + 'Take Paracetamol'), + ('Gastritis', + 'Take ibuprofen') + ) +INSERT INTO medical_assistant.diagnosis (diagnosis_name, + recommendations) +SELECT diagnosis_name, + recommendations +FROM cte_diagnosis +WHERE NOT EXISTS ( + SELECT 1 + FROM medical_assistant.diagnosis + WHERE diagnosis_name = cte_diagnosis.diagnosis_name + AND recommendations = cte_diagnosis.recommendations) +RETURNING diagnosis_id; + + +WITH cte_user (first_name, + last_name, + phone_number, + birth_date) AS +( VALUES ('Belen', + 'T', + '56892487', + '1990-12-01'::date) + ) +INSERT INTO medical_assistant.user (first_name, + last_name, + phone_number, + birth_date) +SELECT first_name, + last_name, + phone_number, + birth_date +FROM cte_user +WHERE NOT EXISTS ( + SELECT 1 + FROM medical_assistant.user + WHERE first_name = cte_user.first_name + AND last_name = cte_user.last_name + AND phone_number = cte_user.phone_number + AND birth_date = cte_user.birth_date) +RETURNING user_id; + + +WITH cte_hospitals (hospital_name, + address_line, + phone_number) AS +( VALUES ('1st hospital', + 'LA', + '8457289'), + ('2d hospital', + 'NY', + '8447239') + ) +INSERT INTO medical_assistant.hospitals (hospital_name, + address_line, + phone_number) +SELECT hospital_name, + address_line, + phone_number +FROM cte_hospitals +WHERE NOT EXISTS ( + SELECT 1 + FROM medical_assistant.hospitals + WHERE hospital_name = cte_hospitals.hospital_name + AND address_line = cte_hospitals.address_line + AND phone_number = cte_hospitals.phone_number) +RETURNING hospital_id; + + +WITH cte_reports (user_id, + report_date, + test_name, + test_result, + test_units, + test_reference_range, + report_type_id, + hospital_id) AS +( VALUES ((SELECT user_id FROM "user" + WHERE first_name = 'Belen'), + '2024-04-01'::date, + 'Hemoglobin', + 19.0, + 'g/dL', + '13.0-17.0', + (SELECT report_type_id FROM report_type + WHERE report_type_name = 'Blood report'), + (SELECT hospital_id FROM hospitals + WHERE hospital_name = '1st hospital') + ), + ((SELECT user_id FROM "user" + WHERE first_name = 'Belen'), + '2024-04-01'::date, + 'Heutrophils', + 60, + '%', + '50-62', + (SELECT report_type_id FROM report_type + WHERE report_type_name = 'Blood report'), + (SELECT hospital_id FROM hospitals + WHERE hospital_name = '1st hospital')) + ) +INSERT INTO medical_assistant.reports (user_id, + report_date, + test_name, + test_result, + test_units, + test_reference_range, + report_type_id, + hospital_id) +SELECT user_id, + report_date, + test_name, + test_result, + test_units, + test_reference_range, + report_type_id, + hospital_id +FROM cte_reports +WHERE NOT EXISTS ( + SELECT 1 + FROM medical_assistant.reports + WHERE user_id = cte_reports.user_id + AND report_date = cte_reports.report_date + AND test_name = cte_reports.test_name + AND test_result = cte_reports.test_result + AND test_units = cte_reports.test_units + AND test_reference_range = cte_reports.test_reference_range + AND report_type_id = cte_reports.report_type_id + AND hospital_id = cte_reports.hospital_id) +RETURNING report_id; + + +WITH cte_doctors (first_name, + last_name, + phone_number, + email, + hospital_id, + specialization_id) AS +( VALUES ('John', + 'Doe', + '8374972', + 'johndoe@gmail.com', + (SELECT hospital_id FROM hospitals + WHERE hospital_name = '1st hospital'), + (SELECT specialization_id FROM doctor_specialization + WHERE specialization_name = 'Therapist')), + ('Jane', + 'Doe', + '8888888', + 'janedoe@gmail.com', + (SELECT hospital_id FROM hospitals + WHERE hospital_name = '2d hospital'), + (SELECT specialization_id FROM doctor_specialization + WHERE specialization_name = 'Therapist')) + ) +INSERT INTO medical_assistant.doctors (first_name, + last_name, + phone_number, + email, + hospital_id, + specialization_id) +SELECT first_name, + last_name, + phone_number, + email, + hospital_id, + specialization_id +FROM cte_doctors +WHERE NOT EXISTS ( + SELECT 1 + FROM medical_assistant.doctors + WHERE first_name = cte_doctors.first_name + AND last_name = cte_doctors.last_name + AND phone_number = cte_doctors.phone_number + AND email = cte_doctors.email + AND hospital_id = cte_doctors.hospital_id + AND specialization_id = cte_doctors.specialization_id) +RETURNING doctor_id; + + +WITH cte_appointments (user_id, + appointment_date, + payment_total, + doctor_id, + hospital_id) AS +( VALUES ((SELECT user_id FROM "user" u + WHERE first_name = 'Belen'), + '2024-04-22'::date, + 60, + (SELECT doctor_id FROM doctors + WHERE first_name = 'John'), + (SELECT hospital_id FROM hospitals + WHERE hospital_name = '1st hospital')), + ((SELECT user_id FROM "user" u + WHERE first_name = 'Belen'), + '2024-04-30'::date, + 60, + (SELECT doctor_id FROM doctors + WHERE first_name = 'John'), + (SELECT hospital_id FROM hospitals + WHERE hospital_name = '1st hospital')) + ) +INSERT INTO medical_assistant.appointments (user_id, + appointment_date, + payment_total, + doctor_id, + hospital_id) +SELECT user_id, + appointment_date, + payment_total, + doctor_id, + hospital_id +FROM cte_appointments +WHERE NOT EXISTS ( + SELECT 1 + FROM medical_assistant.appointments + WHERE user_id = cte_appointments.user_id + AND appointment_date = cte_appointments.appointment_date + AND payment_total = cte_appointments.payment_total + AND doctor_id = cte_appointments.doctor_id + AND hospital_id = cte_appointments.hospital_id) +RETURNING appointment_id; + + +--Queries examples + +-- When the user had appointments in the 1st hospital? +SELECT appointment_date, first_name||' '|| last_name AS "User" +FROM appointments a +JOIN "user" u ON a.user_id =u.user_id +WHERE a.hospital_id = (SELECT hospital_id FROM hospitals h WHERE hospital_name = '1st hospital'); + +--What is stored in the table "Reports"? +SELECT * FROM reports r; + +--When the user's hemoglobin was high? +SELECT report_type_name, report_date, test_name, test_result, test_reference_range, interpretation +FROM reports r +JOIN report_type rt ON r.report_type_id =rt.report_type_id +WHERE test_name = 'Hemoglobin' AND interpretation = 'High'; diff --git a/Database/med_assist.db b/Database/med_assist.db new file mode 100644 index 0000000000..9db9e1ff84 Binary files /dev/null and b/Database/med_assist.db differ diff --git a/Database/report_type_update3+.py b/Database/report_type_update3+.py new file mode 100644 index 0000000000..370fca763f --- /dev/null +++ b/Database/report_type_update3+.py @@ -0,0 +1,142 @@ +import os +import time +from langchain_community.document_loaders import PyMuPDFLoader +from langchain_community.embeddings import AnyscaleEmbeddings +from langchain.text_splitter import RecursiveCharacterTextSplitter +from langchain_community.vectorstores import Pinecone as PineconeStore +from langchain_community.chat_models import ChatAnyscale +from langchain.chains import create_retrieval_chain +from langchain.chains.combine_documents import create_stuff_documents_chain +from langchain_core.prompts import ChatPromptTemplate +from sqlalchemy import create_engine, Column, Integer, String +from sqlalchemy.orm import declarative_base, sessionmaker +import pinecone + +# Set API keys and environment variables +os.environ["ANYSCALE_API_KEY"] = "" +os.environ["PINECONE_API_KEY"] = "" +os.environ["HUGGINGFACEHUB_API_TOKEN"] = "" +os.environ["TOKENIZERS_PARALLELISM"] = "false" + +# Connect to the SQLite database +engine = create_engine('sqlite:///C:/Users/MI/Sqlite/med_assist.db', echo=True) +Base = declarative_base() + +# Define the report_type table +class ReportType(Base): + __tablename__ = 'report_type' + report_type_id = Column(Integer, primary_key=True, autoincrement=True) + report_type_name = Column(String, nullable=False) + +Session = sessionmaker(bind=engine) +session = Session() + +# Load data +pdf_loader = PyMuPDFLoader('Blood report.pdf') +Blood_reports = pdf_loader.load() +text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=150) +pdf_doc = text_splitter.split_documents(Blood_reports) + +# Initialize a LangChain embedding object +api_key = os.getenv("ANYSCALE_API_KEY") +embeddings = AnyscaleEmbeddings( + anyscale_api_key=api_key, model="thenlper/gte-large" +) + +# Initialize Pinecone +pc = pinecone.Pinecone(api_key=os.getenv("PINECONE_API_KEY")) + +# Create index if it doesn't exist +index_name = "pp" +if index_name not in pc.list_indexes().names(): + pc.create_index( + name=index_name, + dimension=1024, + metric="cosine", + spec=pinecone.ServerlessSpec( + cloud="aws", + region="us-east-1" + ) + ) + +# Initialize Pinecone index +index = pc.Index(index_name) + +# Embed each chunk and upsert the embeddings into a distinct namespace +namespace = "pp1" +pineconeVector = PineconeStore.from_documents( + documents=pdf_doc, + index_name=index_name, + embedding=embeddings, + namespace=namespace +) + +time.sleep(1) + +# Initialize the language model with Anyscale +llm = ChatAnyscale(anyscale_api_key=os.getenv('ANYSCALE_API_KEY'), model='meta-llama/Meta-Llama-3-8B-Instruct') + +# Initialize the Pinecone retriever +retriever = pineconeVector.as_retriever() +docs = retriever.invoke("what information is contained in the document") + +# Define the system prompt +system_prompt = ( + "Use the given context to answer the question. " + "If you don't know the answer, say you don't know. " + "Use three sentences maximum and keep the answer concise. " + "Context: {context}" +) + +# Create the prompt template +prompt = ChatPromptTemplate.from_messages( + [ + ("system", system_prompt), + ("human", "{input}"), + ] +) +# Create the question-answer chain +question_answer_chain = create_stuff_documents_chain(llm, prompt) +# Create the retrieval chain +chain = create_retrieval_chain(retriever, question_answer_chain) + +#Query input +query = "What kind of report is this? Blood, urine, or other type?" + +# Invoke the chain with the query +response = chain.invoke({"input": query}) + +# Extract the answer from the response +answer = response['answer'] + +print(answer) + +# Extract the report type from the answer +if "blood" in answer.lower(): + report_type = "Blood report" +elif "urine" in answer.lower(): + report_type = "Urinalysis" +else: + report_type = "Other report" + +# Perform the query and update the database +query_vector = embeddings.embed_query(query) +results = index.query(vector=query_vector, top_k=10, namespace=namespace) + +for match in results['matches']: + matched_id = match['id'] + + # Check if the record already exists + existing_record = session.query(ReportType).filter_by(report_type_name=report_type).first() + if existing_record: + print(f"Record with report_type_name '{report_type}' already exists.") + else: + # Insert a new record + new_report_type = ReportType( + report_type_name=report_type + ) + session.add(new_report_type) + +# Commit changes to the database +session.commit() +session.close() diff --git a/UI/desktop_app/__init__.py b/UI/desktop_app/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/UI/desktop_app/db.sqlite3 b/UI/desktop_app/db.sqlite3 new file mode 100644 index 0000000000..ef3a07ac46 Binary files /dev/null and b/UI/desktop_app/db.sqlite3 differ diff --git a/UI/desktop_app/desktop_app/__init__.py b/UI/desktop_app/desktop_app/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/UI/desktop_app/desktop_app/asgi.py b/UI/desktop_app/desktop_app/asgi.py new file mode 100644 index 0000000000..53861a1a71 --- /dev/null +++ b/UI/desktop_app/desktop_app/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for desktop_app project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'desktop_app.settings') + +application = get_asgi_application() diff --git a/UI/desktop_app/desktop_app/settings.py b/UI/desktop_app/desktop_app/settings.py new file mode 100644 index 0000000000..30ed4e90ba --- /dev/null +++ b/UI/desktop_app/desktop_app/settings.py @@ -0,0 +1,130 @@ +""" +Django settings for desktop_app project. + +Generated by 'django-admin startproject' using Django 5.0.6. + +For more information on this file, see +https://docs.djangoproject.com/en/5.0/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/5.0/ref/settings/ +""" +import os +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'django-insecure-jwnz+2p7%6-ywxxl4m&&biho7^m+20c7wmqccb2hs_@)ak-s6t' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'ui', + 'ui.DB_query' +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'desktop_app.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'desktop_app.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/5.0/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } +} + + +# Password validation +# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/5.0/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/5.0/howto/static-files/ + +STATIC_URL = '/static/' + +STATICFILES_DIRS = [ + BASE_DIR / "ui/static", +] + +# Default primary key field type +# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + diff --git a/UI/desktop_app/desktop_app/urls.py b/UI/desktop_app/desktop_app/urls.py new file mode 100644 index 0000000000..462b02770e --- /dev/null +++ b/UI/desktop_app/desktop_app/urls.py @@ -0,0 +1,7 @@ +from django.contrib import admin +from django.urls import path, include + +urlpatterns = [ + path('admin/', admin.site.urls), + path('', include('ui.urls')), +] \ No newline at end of file diff --git a/UI/desktop_app/desktop_app/wsgi.py b/UI/desktop_app/desktop_app/wsgi.py new file mode 100644 index 0000000000..1c2faef7b7 --- /dev/null +++ b/UI/desktop_app/desktop_app/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for desktop_app project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'desktop_app.settings') + +application = get_wsgi_application() diff --git a/UI/desktop_app/manage b/UI/desktop_app/manage new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/UI/desktop_app/manage @@ -0,0 +1 @@ + diff --git a/UI/desktop_app/manage.py b/UI/desktop_app/manage.py new file mode 100644 index 0000000000..92bd2bb8d4 --- /dev/null +++ b/UI/desktop_app/manage.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + +sys.path.append(os.path.dirname(os.path.abspath(__file__))) + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'desktop_app.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/UI/desktop_app/ui/DB_query/AnyScaleLLM.py b/UI/desktop_app/ui/DB_query/AnyScaleLLM.py new file mode 100644 index 0000000000..9051729f47 --- /dev/null +++ b/UI/desktop_app/ui/DB_query/AnyScaleLLM.py @@ -0,0 +1,39 @@ +from langchain_core.runnables.base import Runnable +import openai +import logging + +# Set up logging +logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) + +class AnyScaleLLM(): + def __init__(self, model_name, api_key, base_url="https://api.endpoints.anyscale.com/v1"): + self.base_url = base_url + self.api_key = api_key + self.model_name = model_name + + def chat_completion(self, prompt, question): + client = openai.OpenAI( + base_url=self.base_url, + api_key=self.api_key + ) + + messages = [ + {"role": "system", "content": prompt}, + {"role": "user", "content": question} + ] + + try: + chat_completion = client.chat.completions.create( + model=self.model_name, + messages=messages, + temperature=0.1 + ) + + response = chat_completion.choices[0].message.content + logger.debug(f"API Response: {response}") + + return response + except Exception as e: + logger.error(f"Error during chat completion: {e}") + return "An error occurred while processing your request." \ No newline at end of file diff --git a/UI/desktop_app/ui/DB_query/__init__.py b/UI/desktop_app/ui/DB_query/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/UI/desktop_app/ui/DB_query/helper.py b/UI/desktop_app/ui/DB_query/helper.py new file mode 100644 index 0000000000..a9ff644e99 --- /dev/null +++ b/UI/desktop_app/ui/DB_query/helper.py @@ -0,0 +1,114 @@ + +""" +Module Name: helper.py +Description: Module storing prompts for an LLM and necessary helper functions for module 'main.py' +Author: Ted Sither +Date: 2024-06-09 + +Notes: Current implementation only works with models from Anyscale AI api +""" + +################################################################################################ +################################################################################################ + +SUBCHAIN_PROMPT = """ + +You are a senior data engineer at Microsoft, responsible for constructing valid, intelligent SQL queries. + +Based on the schema below, write an SQL query that would answer the user's question: +{schema} + +Question: {question} + +Below are three conditions you MUST satisfy before returning the query: + +Condition 1: Make sure to ONLY output an executable SQL Query, do not output any other information or irrelevant information or characters. + +Condition 2: The first character MUST be an SQL keyword, and the last character in the output must be a ; + +Condition 3: The query MUST be on a single line only (newline character \\n is prohibited in query!) + + +IMPORTANT: Make sure to get the table names exactly correct (e.g. artist and NOT artists) + +SQL Query: +""" +################################################################################################ +################################################################################################ + + +FULLCHAIN_PROMPT = """ +Based on the question, SQL Query, and the SQL Execution, respond to the user's question: + +Important: Only give the relevant information from the executed sql query as a response. + + +Question: {question} +SQL Query: {query} +SQL Execution: {executed_query} + +Response: +""" +################################################################################################ +################################################################################################ + + + +def get_schema(db): + """ + Gets schema from SQL database (db) + """ + return db.get_table_info() + + +def run_query(query, db): + """ + Takes query and runs query in database (db) + """ + return db.run(query) + + +def generate_query(llm, template, question, db): + """ + Generates an SQL query. + + Parameters: + llm (Class): Class instance of large language model + template (str): Prompt for llm to follow + question (str): User's question for the database + db (Class): LangChain SQL database instance + + Returns: + out (str): SQL query + """ + + prompt = template.format(schema=get_schema(db),question=question) #format template to include all necessary information (schema, question) + sql_query = llm.chat_completion(prompt, question) #generates sql query + + return sql_query + + +def generate_response(llm, query, template, question, db): + """ + Generates an SQL query. + + Parameters: + llm (Class): Class instance of large language model + query (str): SQL query + template (str): Prompt for llm to follow + question (str): User's question for the database + db (Class): LangChain SQL database instance + + Returns: + response (str): Natural language response to user's question + """ + + executed_query = run_query(query, db=db) #execute query on database + + print(f"\nExecuted query: {executed_query}\n") + + formatted_template = template.format(question=question, query=query, executed_query=executed_query) #format template to include new information + + response = llm.chat_completion(formatted_template, question) #generate llm response + + return response diff --git a/UI/desktop_app/ui/__init__.py b/UI/desktop_app/ui/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/UI/desktop_app/ui/apps.py b/UI/desktop_app/ui/apps.py new file mode 100644 index 0000000000..ffe083ec1b --- /dev/null +++ b/UI/desktop_app/ui/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class UiConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'ui' diff --git a/UI/desktop_app/ui/migrations/__init__.py b/UI/desktop_app/ui/migrations/__init__.py new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/UI/desktop_app/ui/migrations/__init__.py @@ -0,0 +1 @@ + diff --git a/UI/desktop_app/ui/models.py b/UI/desktop_app/ui/models.py new file mode 100644 index 0000000000..dad8376623 --- /dev/null +++ b/UI/desktop_app/ui/models.py @@ -0,0 +1,12 @@ +from django.db import models + +class User(models.Model): + username = models.CharField(max_length=100, unique=True) + email = models.EmailField(unique=True) + first_name = models.CharField(max_length=100) + # other fields also can be added + class Meta: + db_table = 'User' + + def __str__(self): + return self.username diff --git a/UI/desktop_app/ui/static/ui/css/background.jpg b/UI/desktop_app/ui/static/ui/css/background.jpg new file mode 100644 index 0000000000..ef6f318df8 Binary files /dev/null and b/UI/desktop_app/ui/static/ui/css/background.jpg differ diff --git a/UI/desktop_app/ui/static/ui/css/home.css b/UI/desktop_app/ui/static/ui/css/home.css new file mode 100644 index 0000000000..ebb76a738a --- /dev/null +++ b/UI/desktop_app/ui/static/ui/css/home.css @@ -0,0 +1,76 @@ +body { + font-family: Arial, sans-serif; + display: flex; + justify-content: center; /* Horizontal centering */ + align-items: center; /* Vertical centering */ + height: 100vh; /* Adjust height as needed */ + margin: 0; /* Ensure no default margin */ +} + +.background-image { + background-image: url('background.jpg'); + background-size: cover; /* Ensures the background image covers the entire element */ + background-position: center; + background-repeat: no-repeat; /* Prevents the background image from repeating */ + opacity: 0.5; /* 50% transparent */ + position: fixed; /* Use fixed position to cover the entire viewport */ + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: -1; /* Send to back */ +} + +.container { + display: flex; + justify-content: space-around; + padding: 20px; + width: 100%; /* Ensure container takes full width */ + box-sizing: border-box; /* Include padding in the width calculation */ +} + +.box { + position: relative; + flex: 1 1 30%; /* Grow and shrink equally, with a base size of 30% */ + margin: 10px; + max-width: 300px; /* Optional: Limit the maximum size of each box */ + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + text-align: center; +} + +.box img { + width: 100%; + height: auto; + display: block; +} + +.overlay { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + height: 100%; + width: 100%; + opacity: 0; + transition: .5s ease; + background-color: rgba(0, 0, 0, 0.5); /* Black background with opacity */ +} + +.box:hover .overlay { + opacity: 1; +} + +.overlay a { + color: white; + font-size: 20px; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + text-align: center; + text-decoration: none; +} diff --git a/UI/desktop_app/ui/static/ui/css/page2_1.css b/UI/desktop_app/ui/static/ui/css/page2_1.css new file mode 100644 index 0000000000..3d4a5edfaa --- /dev/null +++ b/UI/desktop_app/ui/static/ui/css/page2_1.css @@ -0,0 +1,54 @@ +body { + font-family: Arial, sans-serif; + background-color: #f4f4f4; + margin: 0; + padding: 0; + display: flex; + justify-content: center; + align-items: center; + height: 100vh; +} + +.container { + background-color: white; + padding: 200px; + border-radius: 5px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); + text-align: center; +} + +h1 { + margin-bottom: 20px; +} + +form { + display: flex; + flex-direction: column; + align-items: center; +} + +label { + margin-bottom: 10px; +} + +input[type="file"] { + margin-bottom: 20px; +} + +button { + background-color: #4CAF50; + color: white; + border: none; + padding: 10px 20px; + text-align: center; + text-decoration: none; + display: inline-block; + font-size: 16px; + margin: 4px 2px; + cursor: pointer; + border-radius: 4px; +} + +button:hover { + background-color: #45a049; +} \ No newline at end of file diff --git a/UI/desktop_app/ui/static/ui/images/background.jpg b/UI/desktop_app/ui/static/ui/images/background.jpg new file mode 100644 index 0000000000..ef6f318df8 Binary files /dev/null and b/UI/desktop_app/ui/static/ui/images/background.jpg differ diff --git a/UI/desktop_app/ui/static/ui/images/button1.jpg b/UI/desktop_app/ui/static/ui/images/button1.jpg new file mode 100644 index 0000000000..e107a12a7a Binary files /dev/null and b/UI/desktop_app/ui/static/ui/images/button1.jpg differ diff --git a/UI/desktop_app/ui/static/ui/images/button2.jpg b/UI/desktop_app/ui/static/ui/images/button2.jpg new file mode 100644 index 0000000000..a617af74bc Binary files /dev/null and b/UI/desktop_app/ui/static/ui/images/button2.jpg differ diff --git a/UI/desktop_app/ui/static/ui/images/button3.jpg b/UI/desktop_app/ui/static/ui/images/button3.jpg new file mode 100644 index 0000000000..b5c98299ae Binary files /dev/null and b/UI/desktop_app/ui/static/ui/images/button3.jpg differ diff --git a/UI/desktop_app/ui/static/ui/images/manage b/UI/desktop_app/ui/static/ui/images/manage new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/UI/desktop_app/ui/static/ui/images/manage @@ -0,0 +1 @@ + diff --git a/UI/desktop_app/ui/static/ui/images/page1-background.PNG b/UI/desktop_app/ui/static/ui/images/page1-background.PNG new file mode 100644 index 0000000000..bf19a97ee5 Binary files /dev/null and b/UI/desktop_app/ui/static/ui/images/page1-background.PNG differ diff --git a/UI/desktop_app/ui/templates/ui/home.html b/UI/desktop_app/ui/templates/ui/home.html new file mode 100644 index 0000000000..a6ed38acaf --- /dev/null +++ b/UI/desktop_app/ui/templates/ui/home.html @@ -0,0 +1,119 @@ +{% load static %} + + + + + + + PregPal - Home + + + + +
+
+

Welcome to PregPal

+
+
+
+ + View your data + + +
+ +
+ + Upload Reports + + +
+
+ + Chat With Us + + +
+
+ + diff --git a/UI/desktop_app/ui/templates/ui/page1_1.html b/UI/desktop_app/ui/templates/ui/page1_1.html new file mode 100644 index 0000000000..fb54aea361 --- /dev/null +++ b/UI/desktop_app/ui/templates/ui/page1_1.html @@ -0,0 +1,20 @@ + + + + + User Information + + +

User Information

+ {% if user_info %} +

Username: {{ user_info.username }}

+

Email: {{ user_info.email }}

+ {% else %} +

No user found with first name 'Belen'.

+ {% endif %} + + {% if error %} +

Error: {{ error }}

+ {% endif %} + + diff --git a/UI/desktop_app/ui/templates/ui/page2_1.html b/UI/desktop_app/ui/templates/ui/page2_1.html new file mode 100644 index 0000000000..a1cb04613b --- /dev/null +++ b/UI/desktop_app/ui/templates/ui/page2_1.html @@ -0,0 +1,22 @@ +{%load static%} + + + + + + + Upload Reports + + + +
+

Upload Reports

+
+ {% csrf_token %} + + + +
+
+ + \ No newline at end of file diff --git a/UI/desktop_app/ui/templates/ui/page3_2.html b/UI/desktop_app/ui/templates/ui/page3_2.html new file mode 100644 index 0000000000..ae8b373d2f --- /dev/null +++ b/UI/desktop_app/ui/templates/ui/page3_2.html @@ -0,0 +1,128 @@ +{% load static %} + + + + + + + Chat with us + + + +

CHAT WITH US

+

Please enter your question

+ Back to Home + +
+
+ +
+ + +
+ + + + diff --git a/UI/desktop_app/ui/urls.py b/UI/desktop_app/ui/urls.py new file mode 100644 index 0000000000..3e676bc640 --- /dev/null +++ b/UI/desktop_app/ui/urls.py @@ -0,0 +1,10 @@ +from django.urls import path +from . import views + +urlpatterns = [ + path('', views.home, name='home'), + path('page1/', views.page1_view, name='page1'), + path('page2/', views.page2_view, name='page2'), + path('page3/', views.page3_view, name='page3'), + # path('chatbot/', views.chatbot_view, name='chatbot'), +] \ No newline at end of file diff --git a/UI/desktop_app/ui/views.py b/UI/desktop_app/ui/views.py new file mode 100644 index 0000000000..e89d51b102 --- /dev/null +++ b/UI/desktop_app/ui/views.py @@ -0,0 +1,113 @@ +from django.shortcuts import render +from django.http import JsonResponse +import os +import json +from .models import User +from langchain_community.utilities.sql_database import SQLDatabase +import sqlalchemy +from .DB_query.helper import generate_query, generate_response, SUBCHAIN_PROMPT, FULLCHAIN_PROMPT +from .DB_query.AnyScaleLLM import AnyScaleLLM +import logging +from django.shortcuts import render, redirect +from django.core.files.storage import FileSystemStorage + +# Set up logging +logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) +handler = logging.StreamHandler() +handler.setLevel(logging.DEBUG) +formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') +handler.setFormatter(formatter) +logger.addHandler(handler) + +ANYSCALE_API_KEY = os.getenv("ANYSCALE_API_KEY").strip() +print(f"ANYSCALE_API_KEY:{ANYSCALE_API_KEY}") +# MODEL = "mistralai/Mistral-7B-Instruct-v0.1" +MODEL = "mistralai/Mistral-7B-Instruct-v0.1:Ted:iGZ9Hwf" +DB = SQLDatabase.from_uri("sqlite:///C:/Users/MI/Sqlite/med_assist.db") + +def test_db_connection(db_uri): + try: + engine = sqlalchemy.create_engine(db_uri) + with engine.connect() as connection: + result = connection.execute(sqlalchemy.text("SELECT 1")) + if result.scalar() == 1: + logger.info("Database connection is successful.") + return True + else: + logger.error("Failed to execute test query.") + return False + except Exception as e: + logger.error(f"Database connection failed: {e}") + return False + +def home(request): + return render(request, 'ui/home.html') + +def page1_view(request): + try: + logger.debug("Attempting to fetch user information.") + + # Fetch user info from the database using Django ORM + user = User.objects.filter(first_name='Belen').first() + + if user: + user_info = { + "username": user.username, + "email": user.email, + } + logger.info(f"Fetched user information: {user_info}") + else: + user_info = None + logger.warning("No user found with first name 'Belen'.") + + return render(request, 'ui/page1_1.html', {'user_info': user_info}) + + except Exception as e: + logger.error(f"Error fetching user information: {e}") + return render(request, 'ui/page1_1.html', {'error': 'Error fetching user information'}) + # return render(request, 'ui/page1_1.html') + +def page2_view(request): + return render(request, 'ui/page2.html') + +def page3_view(request): + if request.method == "POST": + try: + data = json.loads(request.body) + question = data.get("question", "") + if not question: + return JsonResponse({"error": "No question provided"}, status=400) + + if not test_db_connection("sqlite:///C:/Users/MI/Sqlite/med_assist.db"): + return JsonResponse({"error": "Database connection failed"}, status=500) + + logger.debug(f"Instantiating AnyScaleLLM with model_name={MODEL} and api_key={ANYSCALE_API_KEY}") + llm = AnyScaleLLM(model_name=MODEL, api_key=ANYSCALE_API_KEY) + logger.debug("AnyScaleLLM instantiated successfully.") + + chat_history = [] + while True: + query = generate_query(llm=llm, template=SUBCHAIN_PROMPT, question=question, db=DB) + response = generate_response(llm=llm, query=query, template=FULLCHAIN_PROMPT, question=question, db=DB) + + logger.debug(f"User question: {question}") + logger.debug(f"Generated query: {query}") + logger.debug(f"LLM response: {response}") + + chat_history.append({"user_question": question, "bot_response": response}) + + # Check termination condition (e.g., specific keyword or number of interactions) + if "exit" in response.lower(): + break + + # Prepare for next iteration based on user input or continue loop + question = response + + return JsonResponse({"chat_history": chat_history}) + + except Exception as e: + logger.error(f"Error processing request: {e}") + return JsonResponse({"error": "Internal server error"}, status=500) + + return render(request, 'ui/page3_2.html')