diff --git a/.gitignore b/.gitignore index 87218c0..cb7b304 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ node_modules backend/__pycache__ backend/.env + +__pycache__/ +*.pyc diff --git a/backend/API/chatHistory.json b/backend/API/chatHistory.json new file mode 100644 index 0000000..1b194c3 --- /dev/null +++ b/backend/API/chatHistory.json @@ -0,0 +1,10 @@ +{ + "session_id" : { + "query" : "some string in format", + "history": [ + { "role": "user", "parts": ["Hello"] }, + { "role": "model", "parts": ["Hi there! How can I help you today?"] }, + { "role": "user", "parts": ["Tell me about my plan benefits."] } + ] + } +} \ No newline at end of file diff --git a/backend/API/root.py b/backend/API/root.py index ab98ef5..f039688 100644 --- a/backend/API/root.py +++ b/backend/API/root.py @@ -12,12 +12,15 @@ from ..Chatbot.chatbot import get_chatbot_response import asyncio +from uuid import uuid4 # for getting session id + + app = FastAPI() from fastapi.middleware.cors import CORSMiddleware app.add_middleware( - CORSMiddleware, # type: ignore + CORSMiddleware, #type: ignore allow_origins=["http://localhost:5173"], # Allow frontend in React to connect allow_credentials=True, @@ -31,24 +34,30 @@ history = {} +chat_history = {} + @app.get("/") def root(): return {"FastAPI Running!!!!!"} +@app.get("/session") +def get_session_id(): + session_id = str(uuid4()) + chat_history[session_id] = {} # Optional: initialize chat history + return {"sessionId" : session_id} -@app.post("/chat/message") -async def send_message(data: ChatBotMessage): - response = get_chatbot_response(data.message, history) +@app.post("/chat/message/{session_id}") +async def send_message(session_id: str, data: ChatBotMessage): + response = get_chatbot_response(data.message, chat_history[session_id]) return {"received": data.message, "response": response} -@app.post("/form/submit") -async def upload_pdfs(form_data: str = Form(...), +@app.post("/form/submit/{session_id}") +async def upload_pdfs(session_id: str, form_data: str = Form(...), files: List[UploadFile] = File(...)): - # Process form data into dictionary - form_dict = json.loads(form_data) + form_dict = json.loads(form_data) # Check against Pydantic Model user_input = UserInputForm(**form_dict) user_input = user_input.model_dump() @@ -66,7 +75,7 @@ async def upload_pdfs(form_data: str = Form(...), # upload to s3 and textract # { "name": filename, "text": "TEXT RESULTS " } results = await upload_and_extract(plans) - print(results) + # The weights: weight = user_input['weights'] @@ -80,10 +89,8 @@ async def upload_pdfs(form_data: str = Form(...), user_input['zip_code'] = location['zip_code'] user_input['city'] = location['city'] user_input['state'] = location['state'] - user_input['household_size'] = coverage['household_size'] - user_input['individual_bool'] = (coverage['household_size'] == 1) - weights = {k: v / 10 for k, v in weight.items()} + weights = {k: v/10 for k, v in weight.items()} # The premiums: async def process_plan(plan_name: str, plan_content: str, @@ -105,8 +112,11 @@ async def process_plan(plan_name: str, plan_content: str, # Run all tasks concurrently results = await asyncio.gather(*tasks) + to_frontend = [] + prompt = "" + # Store the results in the history for name, unweighted_scores, weighted_scores, total_score, short_summary, plan_content in results: # {'file_name': 'weighted_scores: dict, 'total_score': float, 'text': str} @@ -118,9 +128,18 @@ async def process_plan(plan_name: str, plan_content: str, to_frontend.append({ "name": name, - "weightedScores": unweighted_scores, + "weightedScores": weighted_scores, "totalScore": total_score, "shortSummary": short_summary }) + prompt += f"\n\nPlan Name: {name}\nTotal Score: {total_score}\nPlan Text: {short_summary}\n" + + prompt += ("Please justify the ranking of each plan based on its strengths and weaknesses using the information above " + "Explain each plan clearly, highlighting what makes it better or worse compared to the others.\n") + + session_history = chat_history.setdefault(session_id, {}) + session_history["prompt"] = prompt + return to_frontend + diff --git a/backend/Chatbot/chatbot.py b/backend/Chatbot/chatbot.py index ae7d18b..8bd4a48 100644 --- a/backend/Chatbot/chatbot.py +++ b/backend/Chatbot/chatbot.py @@ -6,23 +6,16 @@ load_dotenv() api_key = os.getenv("GEMINI_API_KEY") -def get_chatbot_response(question: str, history: dict) -> str: - client = genai.Client(api_key=api_key) - # Combine plan textract stuff - combined_plans_text = "" - for key, value in history.items(): - combined_plans_text += f"---\nPlan: {key}\n\n{value['text']}\n" +def get_chatbot_response(question: str, session_history: dict) -> str: + client = genai.Client(api_key=api_key) + query = session_history["prompt"] # Prompting type - prompt = f"""You are a healthcare plan assistant. Only use the text provided to answer user questions. - - Below is the text for multiple plans: - {combined_plans_text} - + query += f"""You are a healthcare plan assistant. Only use the text provided to answer user questions. Answer the following question ONLY from the text above: Question: {question} @@ -30,7 +23,7 @@ def get_chatbot_response(question: str, history: dict) -> str: response = client.models.generate_content( model="gemini-2.0-flash", - contents=[prompt], + contents=[query], ) # 5) Return the text response diff --git a/frontend/.gitignore b/frontend/.gitignore index 883e79e..f0b1ec5 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -44,3 +44,7 @@ amplifytools.xcconfig .secret-* **.sample #amplify-do-not-edit-end + + +__pycache__/ +*.pyc diff --git a/frontend/src/components/Chatbot.tsx b/frontend/src/components/Chatbot.tsx index f4e3b82..1734f69 100644 --- a/frontend/src/components/Chatbot.tsx +++ b/frontend/src/components/Chatbot.tsx @@ -16,8 +16,10 @@ const Chatbot: React.FC = ({messages, setMessages}) => { setMessages((prev) => [...prev, { message: input, sender: "user" }]); try { + // get the session id + const sessionId = localStorage.getItem("sessionId"); // Replace with your AI API (e.g., OpenAI, Rasa, Dialogflow, etc.) - const response = await fetch("http://127.0.0.1:8000/chat/message", { + const response = await fetch("http://127.0.0.1:8000/chat/message/"+sessionId, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ message: input }), diff --git a/frontend/src/pages/Input/Input.tsx b/frontend/src/pages/Input/Input.tsx index edab3f7..6a13913 100644 --- a/frontend/src/pages/Input/Input.tsx +++ b/frontend/src/pages/Input/Input.tsx @@ -9,7 +9,7 @@ import Address from "../../components/form/Address"; import BudgetInfo from "../../components/form/BudgetInfo"; import UploadPdfs from "../../components/form/UploadPdfs"; import Rankings from "../../components/form/Rankings"; -import { sendInputData } from "../sendInputAPI"; +import {getSessionID, sendInputData} from "../sendInputAPI"; import { validateContactInfo, validateAddress, @@ -36,6 +36,14 @@ const Input: React.FC = ({ results, setResults }) => { } setLoading(true); const { files, costs, ...formDataWithoutFilesAndCosts } = formData; + + setLoading(false); + setHasSubmittedInput(true); + // get session id and store it + const sessionId = await getSessionID(); + localStorage.setItem("sessionId", sessionId); + + // success = true, if form upload worked someone handle that.. const results = await sendInputData( formDataWithoutFilesAndCosts, files, diff --git a/frontend/src/pages/Ranking/Rankings.tsx b/frontend/src/pages/Ranking/Rankings.tsx new file mode 100644 index 0000000..12a920f --- /dev/null +++ b/frontend/src/pages/Ranking/Rankings.tsx @@ -0,0 +1,196 @@ +import React, {useState} from "react"; +import {useNavigate} from "react-router-dom"; +import {useLocation} from "react-router-dom"; +import {styled} from "@mui/material/styles"; +import Box from "@mui/material/Box"; +import Grid from "@mui/material/Grid"; +import Typography from "@mui/material/Typography"; +import Slider from "@mui/material/Slider"; +import MuiInput from "@mui/material/Input"; +import Select from "@mui/material/Select"; +import MenuItem from "@mui/material/MenuItem"; +import {SelectChangeEvent} from "@mui/material/Select"; +import {ResultsProps} from "../../App"; + +import {getSessionID, sendInputData} from "../sendInputAPI.ts"; +import {getResults} from "../resultsAPI.ts"; +import styles from "./rankings.module.css"; +import {useFlow} from "../../context/FlowContext.tsx"; + +const Input = styled(MuiInput)` + width: 42px; +`; + +const marks = [ + {value: 1, label: "1"}, + {value: 2, label: "2"}, + {value: 3, label: "3"}, + {value: 4, label: "4"}, + {value: 5, label: "5"}, + {value: 6, label: "6"}, + {value: 7, label: "7"}, + {value: 8, label: "8"}, + {value: 9, label: "9"}, + {value: 10, label: "10"}, +]; + +const rankingItems = [ + "Affordability", + "Coverage of Personal Health Concerns", + "Plan Flexibility", + "Coverage of All Benefits", + "Geographic Coverage", + "Coverage in Emergencies", + "Convenience of Accessing Benefits", +]; + +const rankingDescriptions = [ + "Overall cost of the plan, including premiums, deductibles, and out-of-pocket expenses.", + "How well the plan covers your specific health needs, including pre-existing conditions and ongoing treatments.", + "Flexibility in choosing healthcare providers, specialists, and treatment options.", + "Comprehensive coverage of all necessary medical services, including preventive care and specialist visits.", + "Availability of the plan in your geographic area and its network of providers.", + "Coverage for emergency situations, including out-of-network care.", + "Ease of accessing benefits, including online services, customer support, and appointment scheduling.", +]; + +const Rankings: React.FC = ({results, setResults}) => { + const navigate = useNavigate(); + const location = useLocation(); + const {formData} = location.state || {}; + const {files} = location.state || {}; + const {planCost} = location.state || {}; + const {setHasCompletedRankings} = useFlow(); + + const [rankings, setRankings] = React.useState<{ + [key: string]: number | string; + }>(Object.fromEntries(rankingItems.map((item) => [item, 1]))); + + const [selectedOption, setSelectedOption] = useState(""); + const [dropdownError, setDropdownError] = useState(false); + + const handleDropdownChange = (event: SelectChangeEvent) => { + setSelectedOption(event.target.value); + setDropdownError(false); + }; + + // Handle slider change + const handleSliderChange = + (category: string) => (event: Event, newValue: number | number[]) => { + setRankings((prev) => ({ + ...prev, + [category]: newValue as number, + })); + }; + + //Handle Submit + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + // Check if dropdown is selected + if (!selectedOption) { + setDropdownError(true); + return; // Stop submission + } + setHasCompletedRankings(true); + const fullUserData = {...formData, ...rankings, selectedOption}; + + + navigate("/results"); + + + }; + + return ( +
+ +

+ Rank the following factors on a scale from

+ Least Important (1) ยป Most Important (10) +

+
+ + {rankingItems.map((category, index) => ( + +

{category}

+

+ {rankingDescriptions[index]}

+ + + + + +
+ ))} + + {/* Dropdown Selection */} + + + Select your level of familiarity with healthcare + jargon. + + + + + {/* Submit Button */} + + + +
+
+ ); +}; + +export default Rankings; diff --git a/frontend/src/pages/sendInputAPI.ts b/frontend/src/pages/sendInputAPI.ts index a6c323b..08bc97a 100644 --- a/frontend/src/pages/sendInputAPI.ts +++ b/frontend/src/pages/sendInputAPI.ts @@ -64,7 +64,8 @@ function structureToJSON(data: FormDataInput, planCost: number[]) { async function sendInputData( data: FormDataInput, files: File[], - planCost: number[] + planCost: number[], + sessionId: string ) { console.log("Post request"); console.log("DATA CHECK:"); @@ -74,8 +75,7 @@ async function sendInputData( // add the user form data const jsonData = structureToJSON(data, planCost); - console.log("JSON DATA CHECK:"); - console.log(jsonData); + formData.append("form_data", JSON.stringify(jsonData)); // add all the uploaded files @@ -83,8 +83,9 @@ async function sendInputData( formData.append("files", file); formData.append("plan_cost", String(planCost[files.indexOf(file)])); }); + console.log("backend making request with sessionId:"+sessionId); const response = await axios.post( - "http://127.0.0.1:8000/form/submit", + "http://127.0.0.1:8000/form/submit/" + sessionId, formData ); console.log("Server response:", response.data); @@ -95,4 +96,18 @@ async function sendInputData( } } -export { sendInputData }; + +async function getSessionID() { + try { + const response = await axios.get("http://127.0.0.1:8000/session"); + console.log("GET SESSION ID:", response.data); + const data = response.data; + + return data["sessionId"]; + } catch (error) { + return null; + } +} + + +export { sendInputData, getSessionID}; diff --git a/package-lock.json b/package-lock.json index 5fc7a93..1e9c713 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,7 +4,6 @@ "requires": true, "packages": { "": { - "name": "forge-spring25-software", "dependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0",