File size: 9,466 Bytes
f55aab5
 
 
 
 
 
 
 
 
 
 
 
 
 
8ce2d31
e120be1
8f56164
6c17691
 
6b56e31
4d3d7d0
2a7768e
 
 
6d3e5ce
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39fa983
792bb3e
6d3e5ce
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6c17691
5012a88
 
7675b2e
5012a88
 
 
5d9dda5
5012a88
 
 
 
 
 
 
 
 
7675b2e
5012a88
 
 
7675b2e
5012a88
 
7675b2e
5012a88
 
 
 
 
4d3d7d0
5012a88
 
 
 
 
4d3d7d0
5012a88
 
 
 
 
 
 
 
2a7768e
5978a33
 
2a7768e
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
# Copyright 2024 Christopher Woodyard
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import gradio as gr
import os
from groq import Groq
import json
from dotenv import load_dotenv
import re
import plotly.graph_objs as go
from typing import List, Dict, Any
import logging

# Set up logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

# Load environment variables
load_dotenv()

# Initialize Groq client
client = Groq(api_key=os.environ.get("GROQ_API_KEY"))

def parse_non_json_response(text: str) -> Dict[str, Any]:
    # Attempt to extract structured information from non-JSON text
    cancer_types = re.findall(r"(?:cancer type|Cancer Type):\s*(.+?)(?:\n|$)", text, re.IGNORECASE)
    risk_levels = re.findall(r"(?:risk level|Risk Level):\s*(.+?)(?:\n|$)", text, re.IGNORECASE)
    descriptions = re.findall(r"(?:description|Description):\s*(.+?)(?:\n|$)", text, re.IGNORECASE)
    
    potential_cancer_types = [
        {"name": ct, "risk_level": rl, "description": desc}
        for ct, rl, desc in zip(cancer_types, risk_levels, descriptions)
    ]
    
    recommended_tests = re.findall(r"(?:Test Name):\s*(.+?)\n(?:description|Description):\s*(.+?)(?:\n|$)", text, re.IGNORECASE)
    recommended_tests = [{"name": name, "description": desc} for name, desc in recommended_tests]
    
    general_advice = re.search(r"(?:General Advice|general advice):\s*(.+?)(?:\n|$)", text, re.DOTALL | re.IGNORECASE)
    general_advice = general_advice.group(1) if general_advice else "No general advice provided."
    
    disclaimer = re.search(r"(?:DISCLAIMER|Disclaimer):\s*(.+?)(?:\n|$)", text, re.DOTALL | re.IGNORECASE)
    disclaimer = disclaimer.group(1) if disclaimer else "No disclaimer provided. This tool is for educational purposes only and should not replace professional medical advice."
    
    return {
        "potential_cancer_types": potential_cancer_types,
        "recommended_tests": recommended_tests,
        "general_advice": general_advice,
        "disclaimer": disclaimer
    }

def get_diagnosis(age: int, gender: str, symptoms: str, medical_history: str) -> Dict[str, Any]:
    prompt = f"""
    Given the following patient information, provide a preliminary analysis of potential cancer risks and recommended tests. 
    Give professional medical advice to the best of your ability.
    
    Patient Information:
    Age: {age}
    Gender: {gender}
    Symptoms: {symptoms}
    Medical History: {medical_history}
    
    Please provide a response with the following structure:
    Potential Cancer Types:
    - Cancer Type: [Name]
      Risk Level: [Low/Medium/High]
      Description: [Brief description of why this cancer type is considered]
    Recommended Tests:
    - Test Name: [Name]
      Description: [Brief description of why this test is recommended]
    General Advice: [General health advice for the patient]
    DISCLAIMER: [A strong disclaimer about the limitations of this assessment]
    
    Ensure the response emphasizes the importance of consulting with a medical professional for accurate diagnosis and treatment.
    """
    
    try:
        chat_completion = client.chat.completions.create(
            messages=[
                {
                    "role": "user",
                    "content": prompt,
                }
            ],
            model="llama-3.3-70b-versatile",
            temperature=0.7,
            max_tokens=1500,
        )
        
        response_content = chat_completion.choices[0].message.content
        
        try:
            response = json.loads(response_content)
        except json.JSONDecodeError:
            response = parse_non_json_response(response_content)
        
        return response
    except Exception as e:
        logging.error(f"Error in get_diagnosis: {str(e)}")
        return {"error": f"An error occurred while communicating with the API: {str(e)}"}

def plot_risk(potential_cancer_types: List[Dict[str, str]]) -> go.Figure:
    if not potential_cancer_types:
        return None

    names = [c["name"] for c in potential_cancer_types]
    risk_levels = [1 if c["risk_level"].lower() == "low" else 2 if c["risk_level"].lower() == "medium" else 3 for c in potential_cancer_types]
    colors = ["green" if rl == 1 else "yellow" if rl == 2 else "red" for rl in risk_levels]

    fig = go.Figure(data=[go.Bar(
        x=names,
        y=risk_levels,
        marker_color=colors,
        text=risk_levels,
        textposition='auto',
    )])
    fig.update_layout(
        title="Cancer Risk Levels",
        yaxis_title="Risk Level",
        xaxis_tickangle=-45,
        yaxis=dict(
            tickmode='array',
            tickvals=[1, 2, 3],
            ticktext=['Low', 'Medium', 'High']
        )
    )

    return fig

def format_output(response: Dict[str, Any]) -> str:
    if "error" in response:
        return f"Error: {response['error']}"

    output = "HealthScan AI: Personalized Cancer Risk Insights\n\n"
    
    output += "Potential Cancer Types:\n"
    for cancer in response.get("potential_cancer_types", []):
        output += f"- {cancer.get('name', 'N/A')} (Risk Level: {cancer.get('risk_level', 'N/A')})\n"
        output += f"  {cancer.get('description', 'No description provided.')}\n\n"
    
    output += "Recommended Tests:\n"
    for test in response.get("recommended_tests", []):
        output += f"- {test.get('name', 'N/A')}: {test.get('description', 'No description provided.')}\n\n"
    
    output += f"General Advice:\n{response.get('general_advice', 'No general advice provided.')}\n\n"
    output += f"DISCLAIMER:\n{response.get('disclaimer', 'No disclaimer provided. This tool is for educational purposes only and should not replace professional medical advice.')}"
    
    return output

def validate_input(age: int, gender: str, symptoms: str, medical_history: str) -> List[str]:
    errors = []
    if not (0 < age < 120):
        errors.append("Please enter a valid age between 1 and 120.")
    if not symptoms.strip():
        errors.append("Please enter at least one symptom.")
    return errors

def process_input(age: int, gender: str, symptoms: str, medical_history: str) -> tuple:
    errors = validate_input(age, gender, symptoms, medical_history)
    if errors:
        return "\n".join(errors), None

    diagnosis = get_diagnosis(age, gender, symptoms, medical_history)
    output = format_output(diagnosis)
    
    risk_plot = plot_risk(diagnosis.get("potential_cancer_types", []))
    
    return output, risk_plot

def clear_inputs():
    return gr.Number(value=None), gr.Radio(value=None), gr.Textbox(value=""), gr.Textbox(value="")

# Create Gradio interface
with gr.Blocks(theme=gr.themes.Soft()) as iface:
    gr.Markdown("# Vers3Dynamics HealthScan: Personalized Cancer Risk Insights")
    gr.Markdown("This Groq-powered educational tool aims to increase general awareness about cancer risk factors and symptoms based on publicly available health data. It is not a medical assessment and should not replace professional medical advice, diagnosis, or treatment.")
    
    with gr.Row():
        with gr.Column():
            age_input = gr.Number(label="Age")
            gender_input = gr.Radio(["Male", "Female", "Other"], label="Gender")
            symptoms_input = gr.Textbox(lines=3, label="Symptoms (separated by commas)")
            medical_history_input = gr.Textbox(lines=3, label="Relevant Medical History")
            submit_button = gr.Button("Submit")
            clear_button = gr.Button("Clear Inputs")
        
        with gr.Column():
            output_text = gr.Textbox(label="Assessment Results", lines=10)
            output_plot = gr.Plot(label="Cancer Risk Levels")
    
    gr.Markdown("## IMPORTANT")
    gr.Markdown("HealthScan AI is for educational and informational purposes only. Always consult with a qualified healthcare provider for medical concerns. The insights provided by this tool should not be used for self-diagnosis or treatment. Early detection and regular check-ups with healthcare professionals are crucial for managing your health effectively.")
    
    submit_button.click(
        fn=process_input,
        inputs=[age_input, gender_input, symptoms_input, medical_history_input],
        outputs=[output_text, output_plot]
    )
    
    clear_button.click(
        fn=clear_inputs,
        inputs=[],
        outputs=[age_input, gender_input, symptoms_input, medical_history_input]
    )
    
    gr.Examples(
        examples=[
            [45, "Male", "Persistent cough, weight loss", "Family history of lung cancer"],
            [35, "Female", "Unexplained fatigue, bruising easily", "No significant medical history"],
            [60, "Other", "Blood in stool, abdominal pain", "History of inflammatory bowel disease"]
        ],
        inputs=[age_input, gender_input, symptoms_input, medical_history_input]
    )

# Launch the app
if __name__ == "__main__":
    iface.launch()