# 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()