Delete thicc
Browse files- thicc/.DS_Store +0 -0
- thicc/ai_logic/config.py +0 -121
- thicc/ai_logic/intent_parser.py +0 -80
- thicc/ai_logic/rag_service.py +0 -307
- thicc/app.py +0 -164
- thicc/data/.DS_Store +0 -0
- thicc/data/data_loader.py +0 -31
- thicc/data/hospitals/cedars_sinai_medical_center_data.csv +0 -8
- thicc/data/hospitals/cleveland_clinic_data.csv +0 -6
- thicc/data/hospitals/houston_methodist_hospital_data.csv +0 -8
- thicc/data/hospitals/johns_hopkins_hospital_data.csv +0 -8
- thicc/data/hospitals/massachusetts_general_hospital_data.csv +0 -8
- thicc/data/hospitals/mayo_clinic_data.csv +0 -8
- thicc/data/hospitals/newyork_presbyterian_hospital_data.csv +0 -8
- thicc/data/hospitals/northwestern_memorial_hospital_data.csv +0 -8
- thicc/data/hospitals/ucla_medical_center_data.csv +0 -8
- thicc/data/hospitals/ucsf_medical_center_data.csv +0 -8
- thicc/insurance/cost_estimator.py +0 -13
- thicc/insurance/coverage_explainer.py +0 -248
- thicc/insurance/plans.py +0 -14
thicc/.DS_Store
DELETED
|
Binary file (6.15 kB)
|
|
|
thicc/ai_logic/config.py
DELETED
|
@@ -1,121 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
Configuration for LLM and RAG components.
|
| 3 |
-
Supports both OpenAI and Ollama (local) providers.
|
| 4 |
-
"""
|
| 5 |
-
import os
|
| 6 |
-
from pathlib import Path
|
| 7 |
-
from typing import Optional, Literal
|
| 8 |
-
|
| 9 |
-
# Load environment variables from .env file if it exists
|
| 10 |
-
try:
|
| 11 |
-
from dotenv import load_dotenv
|
| 12 |
-
env_path = os.environ.get("THICC_ENV", Path(os.getcwd()) / ".env")
|
| 13 |
-
if env_path.exists():
|
| 14 |
-
print(f"Loading environment variables from {env_path}")
|
| 15 |
-
load_dotenv(env_path)
|
| 16 |
-
except ImportError:
|
| 17 |
-
pass # python-dotenv not installed, will use system environment variables
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
# LLM Provider Selection
|
| 21 |
-
# Supported: openai, ollama, llama_cpp
|
| 22 |
-
LLM_PROVIDER: Literal["openai", "ollama", "llama_cpp"] = os.getenv("LLM_PROVIDER", "llama_cpp").lower()
|
| 23 |
-
|
| 24 |
-
# OpenAI Configuration
|
| 25 |
-
OPENAI_API_KEY: Optional[str] = os.getenv("OPENAI_API_KEY")
|
| 26 |
-
OPENAI_MODEL: str = os.getenv("OPENAI_MODEL", "gpt-4o-mini") # or "gpt-3.5-turbo"
|
| 27 |
-
|
| 28 |
-
# Ollama Configuration
|
| 29 |
-
OLLAMA_BASE_URL: str = os.getenv("OLLAMA_BASE_URL", "http://localhost:11434")
|
| 30 |
-
OLLAMA_MODEL: str = os.getenv("OLLAMA_MODEL", "llama3.2") # or "mistral", "phi3", etc.
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
# llama.cpp Configuration (OpenAI-compatible server)
|
| 34 |
-
LLAMA_CPP_API_BASE: str = os.getenv("LLAMA_CPP_API_BASE", "http://192.168.0.28:8012/v1")
|
| 35 |
-
LLAMA_CPP_MODEL: str = os.getenv("LLAMA_CPP_MODEL", "llama-2-7b-chat")
|
| 36 |
-
|
| 37 |
-
# Embedding Configuration
|
| 38 |
-
EMBEDDING_PROVIDER: Literal["openai", "ollama", "local"] = os.getenv("EMBEDDING_PROVIDER", "local").lower()
|
| 39 |
-
OPENAI_EMBEDDING_MODEL: str = os.getenv("EMBEDDING_MODEL", "text-embedding-3-small")
|
| 40 |
-
OLLAMA_EMBEDDING_MODEL: str = os.getenv("OLLAMA_EMBEDDING_MODEL", "nomic-embed-text")
|
| 41 |
-
LOCAL_EMBEDDING_MODEL: str = os.getenv("LOCAL_EMBEDDING_MODEL", "all-MiniLM-L6-v2")
|
| 42 |
-
|
| 43 |
-
# RAG Configuration
|
| 44 |
-
VECTOR_STORE_PERSIST_DIR: str = os.getenv("VECTOR_STORE_DIR", "./chroma_db")
|
| 45 |
-
CHUNK_SIZE: int = 500
|
| 46 |
-
CHUNK_OVERLAP: int = 50
|
| 47 |
-
|
| 48 |
-
# LLM Temperature (0.0 = deterministic, 1.0 = creative)
|
| 49 |
-
TEMPERATURE: float = float(os.getenv("TEMPERATURE", "0.3"))
|
| 50 |
-
|
| 51 |
-
def validate_config():
|
| 52 |
-
"""Validate that required configuration is present based on provider."""
|
| 53 |
-
if LLM_PROVIDER == "openai":
|
| 54 |
-
if not OPENAI_API_KEY:
|
| 55 |
-
raise ValueError(
|
| 56 |
-
"LLM_PROVIDER is set to 'openai' but OPENAI_API_KEY environment variable is not set. "
|
| 57 |
-
"Please set it or change LLM_PROVIDER to 'ollama' or 'llama_cpp'."
|
| 58 |
-
)
|
| 59 |
-
elif LLM_PROVIDER == "ollama":
|
| 60 |
-
# Check if Ollama is accessible
|
| 61 |
-
try:
|
| 62 |
-
import requests
|
| 63 |
-
response = requests.get(f"{OLLAMA_BASE_URL}/api/tags", timeout=2)
|
| 64 |
-
if response.status_code != 200:
|
| 65 |
-
raise ValueError(
|
| 66 |
-
f"Ollama server not accessible at {OLLAMA_BASE_URL}. "
|
| 67 |
-
"Please start Ollama with: ollama serve"
|
| 68 |
-
)
|
| 69 |
-
except Exception as e:
|
| 70 |
-
raise ValueError(
|
| 71 |
-
f"Cannot connect to Ollama at {OLLAMA_BASE_URL}. "
|
| 72 |
-
f"Please start Ollama server with: ollama serve\n"
|
| 73 |
-
f"Error: {e}"
|
| 74 |
-
)
|
| 75 |
-
elif LLM_PROVIDER == "llama_cpp":
|
| 76 |
-
# Check if llama.cpp server is accessible
|
| 77 |
-
try:
|
| 78 |
-
import requests
|
| 79 |
-
response = requests.get(f"{LLAMA_CPP_API_BASE}/models", timeout=2)
|
| 80 |
-
if response.status_code != 200:
|
| 81 |
-
raise ValueError(
|
| 82 |
-
f"llama.cpp server not accessible at {LLAMA_CPP_API_BASE}. "
|
| 83 |
-
"Please start llama.cpp with OpenAI API compatibility."
|
| 84 |
-
)
|
| 85 |
-
except Exception as e:
|
| 86 |
-
raise ValueError(
|
| 87 |
-
f"Cannot connect to llama.cpp at {LLAMA_CPP_API_BASE}. "
|
| 88 |
-
f"Please start llama.cpp server with OpenAI API compatibility.\n"
|
| 89 |
-
f"Error: {e}"
|
| 90 |
-
)
|
| 91 |
-
else:
|
| 92 |
-
raise ValueError(
|
| 93 |
-
f"Invalid LLM_PROVIDER: {LLM_PROVIDER}. Must be 'openai', 'ollama', or 'llama_cpp'."
|
| 94 |
-
)
|
| 95 |
-
|
| 96 |
-
def get_provider_info() -> dict:
|
| 97 |
-
"""Get information about the current LLM provider configuration."""
|
| 98 |
-
if LLM_PROVIDER == "openai":
|
| 99 |
-
llm_model = OPENAI_MODEL
|
| 100 |
-
url = None
|
| 101 |
-
elif LLM_PROVIDER == "ollama":
|
| 102 |
-
llm_model = OLLAMA_MODEL
|
| 103 |
-
url = OLLAMA_BASE_URL
|
| 104 |
-
print(f"Ollama URL: {url}")
|
| 105 |
-
elif LLM_PROVIDER == "llama_cpp":
|
| 106 |
-
llm_model = LLAMA_CPP_MODEL
|
| 107 |
-
url = LLAMA_CPP_API_BASE
|
| 108 |
-
else:
|
| 109 |
-
llm_model = None
|
| 110 |
-
url = None
|
| 111 |
-
return {
|
| 112 |
-
"llm_provider": LLM_PROVIDER,
|
| 113 |
-
"llm_model": llm_model,
|
| 114 |
-
"embedding_provider": EMBEDDING_PROVIDER,
|
| 115 |
-
"embedding_model": (
|
| 116 |
-
LOCAL_EMBEDDING_MODEL if EMBEDDING_PROVIDER == "local"
|
| 117 |
-
else OLLAMA_EMBEDDING_MODEL if EMBEDDING_PROVIDER == "ollama"
|
| 118 |
-
else OPENAI_EMBEDDING_MODEL
|
| 119 |
-
),
|
| 120 |
-
"provider_url": url,
|
| 121 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
thicc/ai_logic/intent_parser.py
DELETED
|
@@ -1,80 +0,0 @@
|
|
| 1 |
-
import os
|
| 2 |
-
from typing import Optional
|
| 3 |
-
|
| 4 |
-
import pandas as pd
|
| 5 |
-
|
| 6 |
-
from .rag_service import get_rag_service
|
| 7 |
-
from insurance.coverage_explainer import CoverageExplainer
|
| 8 |
-
|
| 9 |
-
USE_LLM = os.getenv("USE_LLM", "true").lower() == "true"
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
def parse_intent_simple(user_input: str, services_data: pd.DataFrame) -> Optional[str]:
|
| 13 |
-
"""Simple rule-based intent parsing with coverage question support."""
|
| 14 |
-
user_input = user_input.lower()
|
| 15 |
-
|
| 16 |
-
# Check coverage questions first (not really used now, but harmless)
|
| 17 |
-
if CoverageExplainer.identify_coverage_question(user_input):
|
| 18 |
-
return "coverage_explanation"
|
| 19 |
-
|
| 20 |
-
# Check for help/list requests
|
| 21 |
-
if any(word in user_input for word in ["help", "support", "services", "available"]):
|
| 22 |
-
return "list_services"
|
| 23 |
-
|
| 24 |
-
# Service keyword matching - match service descriptions mentioned in the user input
|
| 25 |
-
res = services_data[
|
| 26 |
-
services_data["description"].apply(
|
| 27 |
-
lambda desc: desc.lower() in user_input
|
| 28 |
-
)
|
| 29 |
-
]
|
| 30 |
-
|
| 31 |
-
if not res.empty:
|
| 32 |
-
return res.iloc[0]["intent"]
|
| 33 |
-
|
| 34 |
-
return None
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
def parse_intent_with_llm(
|
| 38 |
-
user_input: str,
|
| 39 |
-
services_data: pd.DataFrame,
|
| 40 |
-
hospital_name: str = "Unknown Hospital",
|
| 41 |
-
) -> Optional[str]:
|
| 42 |
-
"""LLM-powered intent parsing with coverage question detection."""
|
| 43 |
-
|
| 44 |
-
# Coverage questions bypass RAG (though app.py already handles these first)
|
| 45 |
-
if CoverageExplainer.identify_coverage_question(user_input):
|
| 46 |
-
return "coverage_explanation"
|
| 47 |
-
|
| 48 |
-
try:
|
| 49 |
-
rag_service = get_rag_service()
|
| 50 |
-
rag_service.initialize_vector_store(
|
| 51 |
-
services_data,
|
| 52 |
-
hospital_name,
|
| 53 |
-
force_reload=False,
|
| 54 |
-
)
|
| 55 |
-
intent = rag_service.parse_intent_with_llm(user_input, services_data)
|
| 56 |
-
return intent
|
| 57 |
-
|
| 58 |
-
except Exception as e:
|
| 59 |
-
print(f"Error in LLM parsing: {e}. Falling back to simple parsing.")
|
| 60 |
-
return parse_intent_simple(user_input, services_data)
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
def parse_intent(
|
| 64 |
-
user_input: str,
|
| 65 |
-
services_data: pd.DataFrame,
|
| 66 |
-
hospital_name: str = "Unknown Hospital",
|
| 67 |
-
use_llm: Optional[bool] = None,
|
| 68 |
-
) -> Optional[str]:
|
| 69 |
-
"""
|
| 70 |
-
Main intent parser - detects service requests or coverage questions.
|
| 71 |
-
Returns: service intent, "list_services", "coverage_explanation", or None.
|
| 72 |
-
Note: coverage questions are already handled in app.py before this is called.
|
| 73 |
-
"""
|
| 74 |
-
should_use_llm = use_llm if use_llm is not None else USE_LLM
|
| 75 |
-
|
| 76 |
-
if should_use_llm:
|
| 77 |
-
return parse_intent_with_llm(user_input, services_data, hospital_name)
|
| 78 |
-
else:
|
| 79 |
-
print("Using simple parsing.")
|
| 80 |
-
return parse_intent_simple(user_input, services_data)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
thicc/ai_logic/rag_service.py
DELETED
|
@@ -1,307 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
RAG (Retrieval-Augmented Generation) service for healthcare cost information.
|
| 3 |
-
|
| 4 |
-
This module provides semantic search capabilities over hospital services data
|
| 5 |
-
using vector embeddings and LLM-powered query understanding.
|
| 6 |
-
|
| 7 |
-
Supports both OpenAI and Ollama (local) LLM providers.
|
| 8 |
-
"""
|
| 9 |
-
import os
|
| 10 |
-
from typing import List, Dict, Optional, Tuple
|
| 11 |
-
import pandas as pd
|
| 12 |
-
from pathlib import Path
|
| 13 |
-
import textwrap
|
| 14 |
-
|
| 15 |
-
from langchain_chroma import Chroma
|
| 16 |
-
from langchain_text_splitters import RecursiveCharacterTextSplitter
|
| 17 |
-
from langchain_core.documents import Document
|
| 18 |
-
from langchain_core.prompts import ChatPromptTemplate
|
| 19 |
-
|
| 20 |
-
from . import config
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
def _get_llm():
|
| 24 |
-
"""Get the appropriate LLM based on provider configuration."""
|
| 25 |
-
if config.LLM_PROVIDER == "openai":
|
| 26 |
-
from langchain_openai import ChatOpenAI
|
| 27 |
-
return ChatOpenAI(
|
| 28 |
-
model=config.OPENAI_MODEL,
|
| 29 |
-
temperature=config.TEMPERATURE,
|
| 30 |
-
openai_api_key=config.OPENAI_API_KEY
|
| 31 |
-
)
|
| 32 |
-
elif config.LLM_PROVIDER == "ollama":
|
| 33 |
-
from langchain_ollama import ChatOllama
|
| 34 |
-
return ChatOllama(
|
| 35 |
-
model=config.OLLAMA_MODEL,
|
| 36 |
-
temperature=config.TEMPERATURE,
|
| 37 |
-
base_url=config.OLLAMA_BASE_URL
|
| 38 |
-
)
|
| 39 |
-
else:
|
| 40 |
-
raise ValueError(f"Unsupported LLM provider: {config.LLM_PROVIDER}")
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
def _get_embeddings():
|
| 44 |
-
"""Get the appropriate embeddings based on provider configuration."""
|
| 45 |
-
if config.EMBEDDING_PROVIDER == "openai":
|
| 46 |
-
from langchain_openai import OpenAIEmbeddings
|
| 47 |
-
return OpenAIEmbeddings(
|
| 48 |
-
model=config.OPENAI_EMBEDDING_MODEL,
|
| 49 |
-
openai_api_key=config.OPENAI_API_KEY
|
| 50 |
-
)
|
| 51 |
-
elif config.EMBEDDING_PROVIDER == "ollama":
|
| 52 |
-
from langchain_ollama import OllamaEmbeddings
|
| 53 |
-
return OllamaEmbeddings(
|
| 54 |
-
model=config.OLLAMA_EMBEDDING_MODEL,
|
| 55 |
-
base_url=config.OLLAMA_BASE_URL
|
| 56 |
-
)
|
| 57 |
-
elif config.EMBEDDING_PROVIDER == "local":
|
| 58 |
-
from langchain_huggingface import HuggingFaceEmbeddings
|
| 59 |
-
return HuggingFaceEmbeddings(
|
| 60 |
-
model_name=config.LOCAL_EMBEDDING_MODEL,
|
| 61 |
-
model_kwargs={'device': 'cpu'},
|
| 62 |
-
encode_kwargs={'normalize_embeddings': True}
|
| 63 |
-
)
|
| 64 |
-
else:
|
| 65 |
-
raise ValueError(f"Unsupported embedding provider: {config.EMBEDDING_PROVIDER}")
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
class HealthcareRAGService:
|
| 69 |
-
"""
|
| 70 |
-
RAG service for healthcare cost information retrieval and intent parsing.
|
| 71 |
-
|
| 72 |
-
This service creates vector embeddings of hospital services and uses
|
| 73 |
-
semantic search to find relevant services based on user queries.
|
| 74 |
-
|
| 75 |
-
Supports multiple LLM providers: OpenAI and Ollama (local).
|
| 76 |
-
"""
|
| 77 |
-
|
| 78 |
-
def __init__(self, persist_directory: str = config.VECTOR_STORE_PERSIST_DIR):
|
| 79 |
-
"""
|
| 80 |
-
Initialize the RAG service.
|
| 81 |
-
|
| 82 |
-
Args:
|
| 83 |
-
persist_directory: Directory to persist the vector store
|
| 84 |
-
"""
|
| 85 |
-
config.validate_config()
|
| 86 |
-
self.persist_directory = persist_directory
|
| 87 |
-
self.embeddings = _get_embeddings()
|
| 88 |
-
self.llm = _get_llm()
|
| 89 |
-
self.vector_store: Optional[Chroma] = None
|
| 90 |
-
|
| 91 |
-
def create_documents_from_services(
|
| 92 |
-
self,
|
| 93 |
-
services_df: pd.DataFrame,
|
| 94 |
-
hospital_name: str
|
| 95 |
-
) -> List[Document]:
|
| 96 |
-
"""
|
| 97 |
-
Convert service data into LangChain Documents with metadata.
|
| 98 |
-
|
| 99 |
-
Args:
|
| 100 |
-
services_df: DataFrame containing service information
|
| 101 |
-
hospital_name: Name of the hospital
|
| 102 |
-
|
| 103 |
-
Returns:
|
| 104 |
-
List of Document objects
|
| 105 |
-
"""
|
| 106 |
-
documents = []
|
| 107 |
-
|
| 108 |
-
for _, row in services_df.iterrows():
|
| 109 |
-
content = textwrap.dedent(f"""
|
| 110 |
-
Service: {row['description']}
|
| 111 |
-
Intent: {row['intent']}
|
| 112 |
-
Hospital: {hospital_name}
|
| 113 |
-
Gross Charge: ${row['gross_charge']}
|
| 114 |
-
Negotiated Rate: ${row['negotiated_rate']}
|
| 115 |
-
|
| 116 |
-
This service provides {row['description'].lower()} at {hospital_name}.
|
| 117 |
-
Common queries: {row['intent'].replace('_', ' ')}
|
| 118 |
-
""")
|
| 119 |
-
|
| 120 |
-
metadata = {
|
| 121 |
-
"intent": row['intent'],
|
| 122 |
-
"description": row['description'],
|
| 123 |
-
"gross_charge": float(row['gross_charge']),
|
| 124 |
-
"negotiated_rate": float(row['negotiated_rate']),
|
| 125 |
-
"hospital": hospital_name,
|
| 126 |
-
}
|
| 127 |
-
|
| 128 |
-
documents.append(Document(page_content=content, metadata=metadata))
|
| 129 |
-
|
| 130 |
-
return documents
|
| 131 |
-
|
| 132 |
-
def initialize_vector_store(
|
| 133 |
-
self,
|
| 134 |
-
services_df: pd.DataFrame,
|
| 135 |
-
hospital_name: str,
|
| 136 |
-
force_reload: bool = False
|
| 137 |
-
):
|
| 138 |
-
"""
|
| 139 |
-
Initialize or reload the vector store with service data.
|
| 140 |
-
|
| 141 |
-
Args:
|
| 142 |
-
services_df: DataFrame containing service information
|
| 143 |
-
hospital_name: Name of the hospital
|
| 144 |
-
force_reload: If True, recreate the vector store even if it exists
|
| 145 |
-
"""
|
| 146 |
-
# Create documents from services data
|
| 147 |
-
documents = self.create_documents_from_services(services_df, hospital_name)
|
| 148 |
-
|
| 149 |
-
# Check if we should use existing vector store
|
| 150 |
-
if not force_reload and os.path.exists(self.persist_directory):
|
| 151 |
-
try:
|
| 152 |
-
self.vector_store = Chroma(
|
| 153 |
-
persist_directory=self.persist_directory,
|
| 154 |
-
embedding_function=self.embeddings
|
| 155 |
-
)
|
| 156 |
-
# Add new documents to existing store
|
| 157 |
-
self.vector_store.add_documents(documents)
|
| 158 |
-
return
|
| 159 |
-
except Exception as e:
|
| 160 |
-
print(f"Could not load existing vector store: {e}. Creating new one.")
|
| 161 |
-
|
| 162 |
-
# Create new vector store
|
| 163 |
-
self.vector_store = Chroma.from_documents(
|
| 164 |
-
documents=documents,
|
| 165 |
-
embedding=self.embeddings,
|
| 166 |
-
persist_directory=self.persist_directory
|
| 167 |
-
)
|
| 168 |
-
|
| 169 |
-
def parse_intent_with_llm(
|
| 170 |
-
self,
|
| 171 |
-
user_query: str,
|
| 172 |
-
available_services: pd.DataFrame
|
| 173 |
-
) -> Optional[str]:
|
| 174 |
-
"""
|
| 175 |
-
Use LLM with RAG to parse user intent and match to available services.
|
| 176 |
-
|
| 177 |
-
Args:
|
| 178 |
-
user_query: The user's natural language query
|
| 179 |
-
available_services: DataFrame of available services
|
| 180 |
-
|
| 181 |
-
Returns:
|
| 182 |
-
The matched intent string or None if no match found
|
| 183 |
-
"""
|
| 184 |
-
if self.vector_store is None:
|
| 185 |
-
raise ValueError("Vector store not initialized. Call initialize_vector_store first.")
|
| 186 |
-
|
| 187 |
-
# Perform semantic search to find relevant services
|
| 188 |
-
search_results = self.vector_store.similarity_search_with_score(user_query, k=3)
|
| 189 |
-
context_services = []
|
| 190 |
-
for doc, score in search_results:
|
| 191 |
-
context_services.append({
|
| 192 |
-
"description": doc.metadata["description"],
|
| 193 |
-
"intent": doc.metadata["intent"],
|
| 194 |
-
"relevance_score": score
|
| 195 |
-
})
|
| 196 |
-
|
| 197 |
-
# LLM Prompt for intent parsing
|
| 198 |
-
prompt_template = ChatPromptTemplate.from_messages([
|
| 199 |
-
(
|
| 200 |
-
"system",
|
| 201 |
-
textwrap.dedent("""You are a healthcare assistant helping users find medical services.
|
| 202 |
-
Your task is to understand the user's intent and match it to one of the available services.
|
| 203 |
-
|
| 204 |
-
Available services from semantic search:
|
| 205 |
-
{context}
|
| 206 |
-
|
| 207 |
-
Instructions:
|
| 208 |
-
1. Analyze the user's query carefully
|
| 209 |
-
2. Match it to the most relevant service from the context
|
| 210 |
-
3. Return ONLY the intent string (e.g., "mri_information", "xray_information")
|
| 211 |
-
4. If the user is asking for general help or a list of services, return "list_services"
|
| 212 |
-
5. If no service matches well, return "unknown"
|
| 213 |
-
|
| 214 |
-
Return only the intent string, nothing else.
|
| 215 |
-
""")
|
| 216 |
-
),
|
| 217 |
-
("user", "{query}")
|
| 218 |
-
])
|
| 219 |
-
|
| 220 |
-
context_text = "\n".join([
|
| 221 |
-
f"- {s['description']} (intent: {s['intent']}, relevance: {s['relevance_score']:.3f})"
|
| 222 |
-
for s in context_services
|
| 223 |
-
])
|
| 224 |
-
|
| 225 |
-
messages = prompt_template.format_messages(
|
| 226 |
-
context=context_text,
|
| 227 |
-
query=user_query
|
| 228 |
-
)
|
| 229 |
-
response = self.llm.invoke(messages)
|
| 230 |
-
intent = response.content.strip()
|
| 231 |
-
|
| 232 |
-
if intent == "list_services" or intent == "unknown":
|
| 233 |
-
return intent if intent == "list_services" else None
|
| 234 |
-
|
| 235 |
-
if intent in available_services["intent"].values:
|
| 236 |
-
return intent
|
| 237 |
-
|
| 238 |
-
# Try partial matching intents
|
| 239 |
-
for service_intent in available_services["intent"].values:
|
| 240 |
-
if intent.lower() in service_intent.lower() or service_intent.lower() in intent.lower():
|
| 241 |
-
return service_intent
|
| 242 |
-
|
| 243 |
-
return None
|
| 244 |
-
|
| 245 |
-
def get_conversational_response(
|
| 246 |
-
self,
|
| 247 |
-
user_query: str,
|
| 248 |
-
service_info: Optional[Dict] = None,
|
| 249 |
-
plan_name: str = "No Insurance"
|
| 250 |
-
) -> str:
|
| 251 |
-
"""
|
| 252 |
-
Generate a natural, conversational response using the LLM.
|
| 253 |
-
|
| 254 |
-
Args:
|
| 255 |
-
user_query: User's original query
|
| 256 |
-
service_info: Information about the matched service (if any)
|
| 257 |
-
plan_name: User's insurance plan name
|
| 258 |
-
|
| 259 |
-
Returns:
|
| 260 |
-
Natural language response
|
| 261 |
-
"""
|
| 262 |
-
if service_info is None:
|
| 263 |
-
prompt_template = ChatPromptTemplate.from_messages([
|
| 264 |
-
(
|
| 265 |
-
"system",
|
| 266 |
-
textwrap.dedent("""
|
| 267 |
-
You are a helpful healthcare cost estimator assistant.
|
| 268 |
-
The user asked about a service we don't have information for.
|
| 269 |
-
Politely let them know and suggest they ask about available services.
|
| 270 |
-
""")
|
| 271 |
-
),
|
| 272 |
-
("user", "{query}")
|
| 273 |
-
])
|
| 274 |
-
messages = prompt_template.format_messages(query=user_query)
|
| 275 |
-
else:
|
| 276 |
-
prompt_template = ChatPromptTemplate.from_messages([
|
| 277 |
-
(
|
| 278 |
-
"system",
|
| 279 |
-
textwrap.dedent("""You are a helpful healthcare cost assistant.
|
| 280 |
-
Provide a clear, friendly response about the service cost.
|
| 281 |
-
|
| 282 |
-
Service Information:
|
| 283 |
-
- Description: {description}
|
| 284 |
-
- Estimated Cost: ${cost:.2f}
|
| 285 |
-
- Insurance Plan: {plan}
|
| 286 |
-
- Gross Charge: ${gross_charge}
|
| 287 |
-
- Negotiated Rate: ${negotiated_rate}
|
| 288 |
-
|
| 289 |
-
Provide a natural, conversational response that includes the estimated cost and any relevant details.""")
|
| 290 |
-
),
|
| 291 |
-
("user", "{query}")
|
| 292 |
-
])
|
| 293 |
-
messages = prompt_template.format_messages(
|
| 294 |
-
description=service_info.get("description", "Unknown"),
|
| 295 |
-
cost=service_info.get("estimated_cost", 0),
|
| 296 |
-
plan=plan_name,
|
| 297 |
-
gross_charge=service_info.get("gross_charge", 0),
|
| 298 |
-
negotiated_rate=service_info.get("negotiated_rate", 0),
|
| 299 |
-
query=user_query
|
| 300 |
-
)
|
| 301 |
-
|
| 302 |
-
response = self.llm.invoke(messages)
|
| 303 |
-
return response.content.strip()
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
def get_rag_service() -> HealthcareRAGService:
|
| 307 |
-
return HealthcareRAGService()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
thicc/app.py
DELETED
|
@@ -1,164 +0,0 @@
|
|
| 1 |
-
import gradio as gr
|
| 2 |
-
from ai_logic.intent_parser import parse_intent
|
| 3 |
-
from data.data_loader import list_services, load_services_data, HOSPITALS
|
| 4 |
-
from insurance import plans, cost_estimator
|
| 5 |
-
from insurance.coverage_explainer import CoverageExplainer
|
| 6 |
-
|
| 7 |
-
# UI dropdowns
|
| 8 |
-
plan_dropdown = gr.Dropdown(
|
| 9 |
-
choices=list(plans.SAMPLE_PLANS.keys()) + ["No Insurance"],
|
| 10 |
-
value="PPO",
|
| 11 |
-
label="Select plan",
|
| 12 |
-
)
|
| 13 |
-
|
| 14 |
-
hospital_dropdown = gr.Dropdown(
|
| 15 |
-
choices=list(HOSPITALS.keys()),
|
| 16 |
-
value=list(HOSPITALS.keys())[0],
|
| 17 |
-
label="Select a Hospital",
|
| 18 |
-
)
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
def respond(message, history, plan_name: str, hospital_name: str) -> str:
|
| 22 |
-
"""Main response function - handles cost estimation and coverage explanations."""
|
| 23 |
-
|
| 24 |
-
# --- 1) Coverage questions first ---
|
| 25 |
-
if CoverageExplainer.identify_coverage_question(message):
|
| 26 |
-
# Figure out which term (deductible, copay, coinsurance, etc.)
|
| 27 |
-
term = CoverageExplainer.get_matching_term(message)
|
| 28 |
-
|
| 29 |
-
if term:
|
| 30 |
-
# Explain the specific term (NOT the whole message)
|
| 31 |
-
explanation = CoverageExplainer.explain_term(term)
|
| 32 |
-
|
| 33 |
-
# Add plan-specific context if a sample plan is selected
|
| 34 |
-
if plan_name != "No Insurance" and plan_name in plans.SAMPLE_PLANS:
|
| 35 |
-
plan = plans.SAMPLE_PLANS[plan_name]
|
| 36 |
-
plan_details = {
|
| 37 |
-
"deductible": plan.deductible,
|
| 38 |
-
"copay": plan.copay,
|
| 39 |
-
"coinsurance": plan.coinsurance,
|
| 40 |
-
}
|
| 41 |
-
explanation += "\n\n---\n\n"
|
| 42 |
-
explanation += CoverageExplainer.format_plan_coverage_summary(
|
| 43 |
-
plan_name, plan_details
|
| 44 |
-
)
|
| 45 |
-
|
| 46 |
-
return explanation
|
| 47 |
-
else:
|
| 48 |
-
# If we can't match a specific term, give the full coverage explainer
|
| 49 |
-
return CoverageExplainer.explain_all_terms()
|
| 50 |
-
|
| 51 |
-
# --- 2) Service cost estimation path ---
|
| 52 |
-
hospital_data_path = HOSPITALS.get(hospital_name)
|
| 53 |
-
services_data = load_services_data(hospital_data_path)
|
| 54 |
-
requested_info = parse_intent(message, services_data, hospital_name=hospital_name)
|
| 55 |
-
|
| 56 |
-
if requested_info is None or requested_info == "list_services":
|
| 57 |
-
services_list = list_services(services_data)
|
| 58 |
-
response = (
|
| 59 |
-
"**Available services:**\n"
|
| 60 |
-
+ "\n".join(f"• {service}" for service in services_list)
|
| 61 |
-
)
|
| 62 |
-
response += (
|
| 63 |
-
"\n\n💡 **Tip**: You can ask me about insurance terms like "
|
| 64 |
-
"'What is a deductible?' or 'Explain coinsurance'."
|
| 65 |
-
)
|
| 66 |
-
return response
|
| 67 |
-
|
| 68 |
-
service_data = services_data[
|
| 69 |
-
services_data["intent"].str.contains(requested_info, case=False, na=False)
|
| 70 |
-
]
|
| 71 |
-
|
| 72 |
-
if service_data.empty:
|
| 73 |
-
return (
|
| 74 |
-
"Sorry, no information found for your request.\n\nYou can:\n"
|
| 75 |
-
"• Ask about available services\n"
|
| 76 |
-
"• Ask about insurance terms (e.g., 'What is a copay?')\n"
|
| 77 |
-
"• Get cost estimates for specific procedures"
|
| 78 |
-
)
|
| 79 |
-
|
| 80 |
-
service_description = service_data.iloc[0]["description"]
|
| 81 |
-
price = service_data.iloc[0]["negotiated_rate"]
|
| 82 |
-
|
| 83 |
-
# Map dropdown choice -> InsurancePlan object
|
| 84 |
-
if plan_name == "No Insurance":
|
| 85 |
-
plan = plans.NO_INSURANCE_PLAN
|
| 86 |
-
else:
|
| 87 |
-
plan = plans.SAMPLE_PLANS.get(plan_name, plans.NO_INSURANCE_PLAN)
|
| 88 |
-
|
| 89 |
-
cost = cost_estimator.estimate_cost(price, plan, deductible_met=True)
|
| 90 |
-
|
| 91 |
-
# --- 3) Format response with cost breakdown ---
|
| 92 |
-
|
| 93 |
-
# Special handling for No Insurance so messaging isn't confusing
|
| 94 |
-
if plan_name == "No Insurance":
|
| 95 |
-
response = f"""**Cost Estimate for {service_description}**
|
| 96 |
-
|
| 97 |
-
• Hospital: {hospital_name}
|
| 98 |
-
• Insurance Plan: {plan_name}
|
| 99 |
-
• Estimated Cost: **${cost:.2f}**
|
| 100 |
-
|
| 101 |
-
Because you selected **No Insurance**, this demo assumes you pay the full negotiated rate.
|
| 102 |
-
|
| 103 |
-
💡 **Understanding your cost**:
|
| 104 |
-
• Negotiated rate: ${price:.2f}
|
| 105 |
-
• Your insurance covers: $0.00
|
| 106 |
-
• You pay: ${cost:.2f}
|
| 107 |
-
|
| 108 |
-
If you want to see how deductibles, copays, and coinsurance work, switch to a sample plan above and ask something like:
|
| 109 |
-
• "What is a deductible?"
|
| 110 |
-
• "Explain coinsurance"
|
| 111 |
-
"""
|
| 112 |
-
return response
|
| 113 |
-
|
| 114 |
-
# For actual plans
|
| 115 |
-
response = f"""**Cost Estimate for {service_description}**
|
| 116 |
-
|
| 117 |
-
• Hospital: {hospital_name}
|
| 118 |
-
• Insurance Plan: {plan_name}
|
| 119 |
-
• Estimated Cost: **${cost:.2f}**
|
| 120 |
-
|
| 121 |
-
This estimate assumes your deductible has been met.
|
| 122 |
-
|
| 123 |
-
💡 **Understanding your cost**:
|
| 124 |
-
• Negotiated rate: ${price:.2f}
|
| 125 |
-
• Your insurance covers: ${price - cost:.2f}
|
| 126 |
-
• You pay: ${cost:.2f}"""
|
| 127 |
-
|
| 128 |
-
# Explain payment type
|
| 129 |
-
if plan.copay and cost == plan.copay:
|
| 130 |
-
response += (
|
| 131 |
-
f"\n\n*You're paying a fixed copay of ${plan.copay:.2f} for this service.*"
|
| 132 |
-
)
|
| 133 |
-
elif plan.coinsurance and plan.coinsurance > 0:
|
| 134 |
-
response += (
|
| 135 |
-
f"\n\n*You're paying {plan.coinsurance*100:.0f}% coinsurance "
|
| 136 |
-
f"({plan.coinsurance*100:.0f}% of ${price:.2f}).*"
|
| 137 |
-
)
|
| 138 |
-
|
| 139 |
-
response += (
|
| 140 |
-
"\n\n**Need help?** Ask me 'What is coinsurance?' "
|
| 141 |
-
"or any other insurance term!"
|
| 142 |
-
)
|
| 143 |
-
|
| 144 |
-
return response
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
# Gradio interface (no examples table)
|
| 148 |
-
demo = gr.ChatInterface(
|
| 149 |
-
fn=respond,
|
| 150 |
-
title="THICC Cost Chatbot - Now with Coverage Explanations! 🏥",
|
| 151 |
-
description="""Get healthcare cost estimates and understand your insurance coverage.
|
| 152 |
-
|
| 153 |
-
**What you can ask:**
|
| 154 |
-
• Cost estimates: "How much does an MRI cost?"
|
| 155 |
-
• Coverage terms: "What is a deductible?" or "Explain coinsurance"
|
| 156 |
-
• Available services: "What services are available?"
|
| 157 |
-
""",
|
| 158 |
-
type="messages",
|
| 159 |
-
additional_inputs=[plan_dropdown, hospital_dropdown],
|
| 160 |
-
additional_inputs_accordion="Tell us about your insurance plan and hospital",
|
| 161 |
-
)
|
| 162 |
-
|
| 163 |
-
if __name__ == "__main__":
|
| 164 |
-
demo.launch()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
thicc/data/.DS_Store
DELETED
|
Binary file (6.15 kB)
|
|
|
thicc/data/data_loader.py
DELETED
|
@@ -1,31 +0,0 @@
|
|
| 1 |
-
import pandas as pd
|
| 2 |
-
from pathlib import Path
|
| 3 |
-
|
| 4 |
-
# Directory of this file: .../thicc/data
|
| 5 |
-
BASE_DIR = Path(__file__).resolve().parent
|
| 6 |
-
|
| 7 |
-
HOSPITALS = {
|
| 8 |
-
"Mayo Clinic": BASE_DIR / "hospitals" / "mayo_clinic_data.csv",
|
| 9 |
-
"Cleveland Clinic": BASE_DIR / "hospitals" / "cleveland_clinic_data.csv",
|
| 10 |
-
"Johns Hopkins Hospital": BASE_DIR / "hospitals" / "johns_hopkins_hospital_data.csv",
|
| 11 |
-
"Massachusetts General Hospital": BASE_DIR / "hospitals" / "massachusetts_general_hospital_data.csv",
|
| 12 |
-
"UCLA Medical Center": BASE_DIR / "hospitals" / "ucla_medical_center_data.csv",
|
| 13 |
-
"Cedars-Sinai Medical Center": BASE_DIR / "hospitals" / "cedars_sinai_medical_center_data.csv",
|
| 14 |
-
"NewYork-Presbyterian Hospital": BASE_DIR / "hospitals" / "newyork_presbyterian_hospital_data.csv",
|
| 15 |
-
"Northwestern Memorial Hospital": BASE_DIR / "hospitals" / "northwestern_memorial_hospital_data.csv",
|
| 16 |
-
"UCSF Medical Center": BASE_DIR / "hospitals" / "ucsf_medical_center_data.csv",
|
| 17 |
-
"Houston Methodist Hospital": BASE_DIR / "hospitals" / "houston_methodist_hospital_data.csv",
|
| 18 |
-
}
|
| 19 |
-
|
| 20 |
-
def load_services_data(path) -> pd.DataFrame:
|
| 21 |
-
"""Load price data from a CSV file."""
|
| 22 |
-
df = pd.read_csv(path)
|
| 23 |
-
return df[[
|
| 24 |
-
"intent",
|
| 25 |
-
"description",
|
| 26 |
-
"gross_charge",
|
| 27 |
-
"negotiated_rate",
|
| 28 |
-
]]
|
| 29 |
-
|
| 30 |
-
def list_services(services_data: pd.DataFrame) -> list[str]:
|
| 31 |
-
return services_data["description"].tolist()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
thicc/data/hospitals/cedars_sinai_medical_center_data.csv
DELETED
|
@@ -1,8 +0,0 @@
|
|
| 1 |
-
intent,description,gross_charge,negotiated_rate
|
| 2 |
-
"mri_information","MRI",1300,1080
|
| 3 |
-
"xray_information","X-Ray",2180,1780
|
| 4 |
-
"general_consultation","General Consultation",1620,1320
|
| 5 |
-
"neurosurgery_information","Neurosurgery",20000,17000
|
| 6 |
-
"pain_management_information","Pain Management",2500,2000
|
| 7 |
-
"endocrinology_information","Endocrinology",3700,3100
|
| 8 |
-
"bariatric_surgery_information","Bariatric Surgery",15000,12000
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
thicc/data/hospitals/cleveland_clinic_data.csv
DELETED
|
@@ -1,6 +0,0 @@
|
|
| 1 |
-
intent,description,gross_charge,negotiated_rate
|
| 2 |
-
"mri_information","MRI",1320,1100
|
| 3 |
-
"xray_information","X-Ray",2200,1800
|
| 4 |
-
"general_consultation","General Consultation",1550,1250
|
| 5 |
-
"cardiac_surgery_information","Cardiac Surgery",15000,12000
|
| 6 |
-
"dialysis_information","Dialysis",4000,3200
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
thicc/data/hospitals/houston_methodist_hospital_data.csv
DELETED
|
@@ -1,8 +0,0 @@
|
|
| 1 |
-
intent,description,gross_charge,negotiated_rate
|
| 2 |
-
"mri_information","MRI",1310,1090
|
| 3 |
-
"xray_information","X-Ray",2130,1730
|
| 4 |
-
"general_consultation","General Consultation",1610,1310
|
| 5 |
-
"transplant_surgery_information","Transplant Surgery",48000,40000
|
| 6 |
-
"wound_care_information","Wound Care",1800,1400
|
| 7 |
-
"hyperbaric_therapy_information","Hyperbaric Therapy",3500,3000
|
| 8 |
-
"occupational_therapy_information","Occupational Therapy",2100,1700
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
thicc/data/hospitals/johns_hopkins_hospital_data.csv
DELETED
|
@@ -1,8 +0,0 @@
|
|
| 1 |
-
intent,description,gross_charge,negotiated_rate
|
| 2 |
-
"mri_information","MRI",1400,1150
|
| 3 |
-
"xray_information","X-Ray",2050,1600
|
| 4 |
-
"general_consultation","General Consultation",1700,1400
|
| 5 |
-
"organ_transplant_information","Organ Transplant",50000,42000
|
| 6 |
-
"pediatric_care_information","Pediatric Care",3000,2500
|
| 7 |
-
"sleep_disorder_clinic_information","Sleep Disorder Clinic",2100,1700
|
| 8 |
-
"infectious_disease_information","Infectious Disease",4200,3500
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
thicc/data/hospitals/massachusetts_general_hospital_data.csv
DELETED
|
@@ -1,8 +0,0 @@
|
|
| 1 |
-
intent,description,gross_charge,negotiated_rate
|
| 2 |
-
"mri_information","MRI",1280,1050
|
| 3 |
-
"xray_information","X-Ray",2150,1750
|
| 4 |
-
"general_consultation","General Consultation",1650,1350
|
| 5 |
-
"fertility_treatment_information","Fertility Treatment",12000,10000
|
| 6 |
-
"oncology_information","Oncology",8000,7000
|
| 7 |
-
"burn_unit_information","Burn Unit",6000,5000
|
| 8 |
-
"speech_therapy_information","Speech Therapy",1800,1400
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
thicc/data/hospitals/mayo_clinic_data.csv
DELETED
|
@@ -1,8 +0,0 @@
|
|
| 1 |
-
intent,description,gross_charge,negotiated_rate
|
| 2 |
-
"mri_information","MRI",1234,1000
|
| 3 |
-
"xray_information","X-Ray",2100,1700
|
| 4 |
-
"general_consultation","General Consultation",1600,1300
|
| 5 |
-
"sleep_medicine_information","Sleep Medicine",2200,1800
|
| 6 |
-
"rheumatology_information","Rheumatology",3500,2900
|
| 7 |
-
"geriatrics_information","Geriatrics",2500,2000
|
| 8 |
-
"nutrition_counseling_information","Nutrition Counseling",1200,900
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
thicc/data/hospitals/newyork_presbyterian_hospital_data.csv
DELETED
|
@@ -1,8 +0,0 @@
|
|
| 1 |
-
intent,description,gross_charge,negotiated_rate
|
| 2 |
-
"mri_information","MRI",1370,1130
|
| 3 |
-
"xray_information","X-Ray",2120,1720
|
| 4 |
-
"general_consultation","General Consultation",1680,1380
|
| 5 |
-
"psychiatric_care_information","Psychiatric Care",4000,3500
|
| 6 |
-
"rehabilitation_information","Rehabilitation",2200,1800
|
| 7 |
-
"neonatal_care_information","Neonatal Care",5000,4200
|
| 8 |
-
"speech_pathology_information","Speech Pathology",2100,1700
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
thicc/data/hospitals/northwestern_memorial_hospital_data.csv
DELETED
|
@@ -1,8 +0,0 @@
|
|
| 1 |
-
intent,description,gross_charge,negotiated_rate
|
| 2 |
-
"mri_information","MRI",1290,1060
|
| 3 |
-
"xray_information","X-Ray",2170,1770
|
| 4 |
-
"general_consultation","General Consultation",1630,1330
|
| 5 |
-
"dermatology_information","Dermatology",1200,900
|
| 6 |
-
"allergy_treatment_information","Allergy Treatment",1100,850
|
| 7 |
-
"urology_information","Urology",2700,2200
|
| 8 |
-
"pulmonology_information","Pulmonology",3500,2900
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
thicc/data/hospitals/ucla_medical_center_data.csv
DELETED
|
@@ -1,8 +0,0 @@
|
|
| 1 |
-
intent,description,gross_charge,negotiated_rate
|
| 2 |
-
"mri_information","MRI",1350,1120
|
| 3 |
-
"xray_information","X-Ray",2250,1850
|
| 4 |
-
"general_consultation","General Consultation",1580,1280
|
| 5 |
-
"sports_medicine_information","Sports Medicine",3500,3000
|
| 6 |
-
"plastic_surgery_information","Plastic Surgery",9000,7500
|
| 7 |
-
"immunology_information","Immunology",3200,2700
|
| 8 |
-
"gastroenterology_information","Gastroenterology",4100,3500
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
thicc/data/hospitals/ucsf_medical_center_data.csv
DELETED
|
@@ -1,8 +0,0 @@
|
|
| 1 |
-
intent,description,gross_charge,negotiated_rate
|
| 2 |
-
"mri_information","MRI",1390,1160
|
| 3 |
-
"xray_information","X-Ray",2190,1790
|
| 4 |
-
"general_consultation","General Consultation",1690,1390
|
| 5 |
-
"aids_hiv_care_information","AIDS/HIV Care",7000,6000
|
| 6 |
-
"genetic_counseling_information","Genetic Counseling",2500,2000
|
| 7 |
-
"transgender_health_information","Transgender Health",6000,5000
|
| 8 |
-
"integrative_medicine_information","Integrative Medicine",3200,2700
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
thicc/insurance/cost_estimator.py
DELETED
|
@@ -1,13 +0,0 @@
|
|
| 1 |
-
from .plans import InsurancePlan
|
| 2 |
-
|
| 3 |
-
def estimate_cost(price: float, plan: InsurancePlan | None = None, deductible_met: bool = False) -> float:
|
| 4 |
-
price = float(price)
|
| 5 |
-
|
| 6 |
-
if plan is None or getattr(plan, "plan_name", "") == "No Insurance":
|
| 7 |
-
return price # No insurance, full price
|
| 8 |
-
|
| 9 |
-
if deductible_met:
|
| 10 |
-
# If deductible is met, only copay and coinsurance apply
|
| 11 |
-
return plan.copay or (price * plan.coinsurance)
|
| 12 |
-
|
| 13 |
-
return min(price, plan.deductible) + plan.copay
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
thicc/insurance/coverage_explainer.py
DELETED
|
@@ -1,248 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
Coverage Explanations Module - Provides insurance term explanations.
|
| 3 |
-
"""
|
| 4 |
-
|
| 5 |
-
class CoverageExplainer:
|
| 6 |
-
"""Explains insurance coverage terms in user-friendly language."""
|
| 7 |
-
|
| 8 |
-
COVERAGE_TERMS = {
|
| 9 |
-
"deductible": {
|
| 10 |
-
"brief": "The amount you pay before insurance starts covering costs",
|
| 11 |
-
"detailed": """A deductible is the amount you must pay out-of-pocket for covered healthcare services before your insurance plan starts to pay.
|
| 12 |
-
|
| 13 |
-
**Example**: If you have a $1,000 deductible:
|
| 14 |
-
• You pay the first $1,000 of covered services yourself
|
| 15 |
-
• After meeting your deductible, you typically pay only copays or coinsurance
|
| 16 |
-
• Deductibles reset yearly
|
| 17 |
-
|
| 18 |
-
**Important**: Some services like preventive care may be covered before you meet your deductible.""",
|
| 19 |
-
"keywords": ["deductible", "deductibles", "yearly amount", "before insurance pays"],
|
| 20 |
-
},
|
| 21 |
-
|
| 22 |
-
"copay": {
|
| 23 |
-
"brief": "A fixed amount you pay for a covered service",
|
| 24 |
-
"detailed": """A copay (copayment) is a fixed amount you pay for a covered healthcare service, usually at the time of service.
|
| 25 |
-
|
| 26 |
-
**Example**:
|
| 27 |
-
• Doctor visit: $25 copay
|
| 28 |
-
• Specialist visit: $50 copay
|
| 29 |
-
• Prescription: $10 copay
|
| 30 |
-
|
| 31 |
-
**Key Points**:
|
| 32 |
-
• Copays are typically the same regardless of the actual cost
|
| 33 |
-
• You usually pay copays even after meeting your deductible
|
| 34 |
-
• Different services have different copay amounts""",
|
| 35 |
-
"keywords": ["copay", "copayment", "fixed amount", "flat fee"],
|
| 36 |
-
},
|
| 37 |
-
|
| 38 |
-
"coinsurance": {
|
| 39 |
-
"brief": "Your percentage share of costs after deductible",
|
| 40 |
-
"detailed": """Coinsurance is your share of the costs of a covered healthcare service, calculated as a percentage of the allowed amount for the service.
|
| 41 |
-
|
| 42 |
-
**Example**: With 20% coinsurance:
|
| 43 |
-
• Insurance pays 80% of covered costs
|
| 44 |
-
• You pay 20% of covered costs
|
| 45 |
-
• This applies AFTER you meet your deductible
|
| 46 |
-
|
| 47 |
-
**Real Scenario**:
|
| 48 |
-
MRI costs $1,000 (after deductible met)
|
| 49 |
-
• Insurance pays: $800 (80%)
|
| 50 |
-
• You pay: $200 (20%)""",
|
| 51 |
-
"keywords": ["coinsurance", "percentage", "percent of cost", "cost sharing", "80/20", "70/30"],
|
| 52 |
-
},
|
| 53 |
-
|
| 54 |
-
"out_of_network": {
|
| 55 |
-
"brief": "Higher costs for providers not in your insurance network",
|
| 56 |
-
"detailed": """Out-of-network refers to healthcare providers who haven't contracted with your insurance company to provide services at negotiated rates.
|
| 57 |
-
|
| 58 |
-
**Penalties and Higher Costs**:
|
| 59 |
-
• Higher deductibles (often double in-network amounts)
|
| 60 |
-
• Higher coinsurance (40-50% vs 20-30%)
|
| 61 |
-
• No negotiated rates (you may pay full price)
|
| 62 |
-
• Balance billing (provider can bill you the difference)
|
| 63 |
-
|
| 64 |
-
**Example Cost Difference**:
|
| 65 |
-
Same procedure:
|
| 66 |
-
• In-network: You pay $500 (20% coinsurance)
|
| 67 |
-
• Out-of-network: You pay $2,000 (50% coinsurance + balance billing)
|
| 68 |
-
|
| 69 |
-
**Tip**: Always check if a provider is in-network before receiving care, except in emergencies.""",
|
| 70 |
-
"keywords": [
|
| 71 |
-
"out of network",
|
| 72 |
-
"out-of-network",
|
| 73 |
-
"network penalties",
|
| 74 |
-
"provider network",
|
| 75 |
-
"in-network",
|
| 76 |
-
"network",
|
| 77 |
-
],
|
| 78 |
-
},
|
| 79 |
-
|
| 80 |
-
"out_of_pocket_maximum": {
|
| 81 |
-
"brief": "The most you'll pay in a year for covered services",
|
| 82 |
-
"detailed": """The out-of-pocket maximum is the most you have to pay for covered services in a plan year. After you reach this amount, your insurance pays 100% of covered services.
|
| 83 |
-
|
| 84 |
-
**What Counts**:
|
| 85 |
-
✓ Deductibles
|
| 86 |
-
✓ Copayments
|
| 87 |
-
✓ Coinsurance
|
| 88 |
-
|
| 89 |
-
**What Doesn't Count**:
|
| 90 |
-
✗ Monthly premiums
|
| 91 |
-
✗ Out-of-network costs (usually)
|
| 92 |
-
✗ Non-covered services
|
| 93 |
-
|
| 94 |
-
**Example**: $8,000 out-of-pocket maximum
|
| 95 |
-
Once you've paid $8,000 in deductibles, copays, and coinsurance, your insurance covers 100% for the rest of the year.""",
|
| 96 |
-
"keywords": [
|
| 97 |
-
"out of pocket maximum",
|
| 98 |
-
"out-of-pocket maximum",
|
| 99 |
-
"max",
|
| 100 |
-
"maximum",
|
| 101 |
-
"yearly limit",
|
| 102 |
-
"annual limit",
|
| 103 |
-
"oop max",
|
| 104 |
-
],
|
| 105 |
-
},
|
| 106 |
-
|
| 107 |
-
"premium": {
|
| 108 |
-
"brief": "Monthly payment to maintain insurance coverage",
|
| 109 |
-
"detailed": """Your premium is the amount you pay for your health insurance every month to maintain coverage, regardless of whether you use services.
|
| 110 |
-
|
| 111 |
-
**Key Points**:
|
| 112 |
-
• Due monthly whether you use healthcare or not
|
| 113 |
-
• Doesn't count toward deductible or out-of-pocket maximum
|
| 114 |
-
• Higher premiums often mean lower deductibles/copays
|
| 115 |
-
• Employer may pay part of your premium
|
| 116 |
-
|
| 117 |
-
**Example Monthly Premiums**:
|
| 118 |
-
• Individual: $450/month
|
| 119 |
-
• Family: $1,200/month""",
|
| 120 |
-
"keywords": ["premium", "monthly payment", "monthly cost", "insurance payment"],
|
| 121 |
-
},
|
| 122 |
-
|
| 123 |
-
"prior_authorization": {
|
| 124 |
-
"brief": "Insurance approval needed before certain services",
|
| 125 |
-
"detailed": """Prior authorization (preauthorization) means your insurance company must approve a service before you receive it for the service to be covered.
|
| 126 |
-
|
| 127 |
-
**Common Services Requiring Authorization**:
|
| 128 |
-
• MRI/CT scans
|
| 129 |
-
• Surgery
|
| 130 |
-
• Expensive medications
|
| 131 |
-
• Specialist referrals (HMO plans)
|
| 132 |
-
|
| 133 |
-
**Important**:
|
| 134 |
-
• Without authorization, insurance may deny coverage
|
| 135 |
-
• Your doctor typically handles the authorization
|
| 136 |
-
• Can take days to weeks for approval
|
| 137 |
-
• Emergency services don't require prior authorization""",
|
| 138 |
-
"keywords": [
|
| 139 |
-
"prior authorization",
|
| 140 |
-
"preauthorization",
|
| 141 |
-
"pre-approval",
|
| 142 |
-
"authorization",
|
| 143 |
-
"approval needed",
|
| 144 |
-
],
|
| 145 |
-
},
|
| 146 |
-
}
|
| 147 |
-
|
| 148 |
-
@classmethod
|
| 149 |
-
def explain_term(cls, term: str) -> str | None:
|
| 150 |
-
"""Get detailed explanation for a specific coverage term."""
|
| 151 |
-
term_lower = term.lower().strip()
|
| 152 |
-
|
| 153 |
-
for key, info in cls.COVERAGE_TERMS.items():
|
| 154 |
-
if key in term_lower or any(keyword in term_lower for keyword in info["keywords"]):
|
| 155 |
-
return info["detailed"]
|
| 156 |
-
|
| 157 |
-
return None
|
| 158 |
-
|
| 159 |
-
@classmethod
|
| 160 |
-
def get_brief_explanation(cls, term: str) -> str | None:
|
| 161 |
-
"""Get brief explanation for a coverage term."""
|
| 162 |
-
term_lower = term.lower().strip()
|
| 163 |
-
|
| 164 |
-
for key, info in cls.COVERAGE_TERMS.items():
|
| 165 |
-
if key in term_lower or any(keyword in term_lower for keyword in info["keywords"]):
|
| 166 |
-
return info["brief"]
|
| 167 |
-
|
| 168 |
-
return None
|
| 169 |
-
|
| 170 |
-
@classmethod
|
| 171 |
-
def explain_all_terms(cls) -> str:
|
| 172 |
-
"""Return explanations for all coverage terms."""
|
| 173 |
-
explanations = ["**Understanding Your Insurance Coverage**\n"]
|
| 174 |
-
|
| 175 |
-
for term, info in cls.COVERAGE_TERMS.items():
|
| 176 |
-
title = term.replace("_", " ").title()
|
| 177 |
-
explanations.append(f"**{title}**")
|
| 178 |
-
explanations.append(info["detailed"])
|
| 179 |
-
explanations.append("")
|
| 180 |
-
|
| 181 |
-
return "\n".join(explanations)
|
| 182 |
-
|
| 183 |
-
@classmethod
|
| 184 |
-
def identify_coverage_question(cls, user_input: str) -> bool:
|
| 185 |
-
"""Check if user is asking about coverage terms."""
|
| 186 |
-
user_input_lower = user_input.lower()
|
| 187 |
-
|
| 188 |
-
# General coverage question patterns
|
| 189 |
-
general_terms = [
|
| 190 |
-
"what is",
|
| 191 |
-
"what's",
|
| 192 |
-
"explain",
|
| 193 |
-
"how does",
|
| 194 |
-
"how do",
|
| 195 |
-
"tell me about",
|
| 196 |
-
"help me understand",
|
| 197 |
-
"coverage",
|
| 198 |
-
"insurance terms",
|
| 199 |
-
"insurance work",
|
| 200 |
-
]
|
| 201 |
-
|
| 202 |
-
# Check for general questions with coverage terms
|
| 203 |
-
if any(term in user_input_lower for term in general_terms):
|
| 204 |
-
for term_info in cls.COVERAGE_TERMS.values():
|
| 205 |
-
if any(keyword in user_input_lower for keyword in term_info["keywords"]):
|
| 206 |
-
return True
|
| 207 |
-
|
| 208 |
-
# Direct term mentions
|
| 209 |
-
for term_info in cls.COVERAGE_TERMS.values():
|
| 210 |
-
if any(keyword in user_input_lower for keyword in term_info["keywords"]):
|
| 211 |
-
return True
|
| 212 |
-
|
| 213 |
-
return False
|
| 214 |
-
|
| 215 |
-
@classmethod
|
| 216 |
-
def get_matching_term(cls, user_input: str) -> str | None:
|
| 217 |
-
"""Extract which coverage term the user is asking about."""
|
| 218 |
-
user_input_lower = user_input.lower()
|
| 219 |
-
|
| 220 |
-
for term_key, term_info in cls.COVERAGE_TERMS.items():
|
| 221 |
-
for keyword in term_info["keywords"]:
|
| 222 |
-
if keyword in user_input_lower:
|
| 223 |
-
return term_key
|
| 224 |
-
|
| 225 |
-
return None
|
| 226 |
-
|
| 227 |
-
@classmethod
|
| 228 |
-
def format_plan_coverage_summary(cls, plan_name: str, plan_details: dict) -> str:
|
| 229 |
-
"""Format a summary of coverage for a specific plan."""
|
| 230 |
-
deductible = plan_details.get("deductible", 0)
|
| 231 |
-
copay = plan_details.get("copay", 0)
|
| 232 |
-
coinsurance = plan_details.get("coinsurance", 0) * 100
|
| 233 |
-
|
| 234 |
-
return f"""**Your {plan_name} Plan Coverage**
|
| 235 |
-
|
| 236 |
-
**Deductible**: ${deductible:,.0f}
|
| 237 |
-
• You pay this amount first before insurance helps
|
| 238 |
-
|
| 239 |
-
**Copay**: ${copay:.0f}
|
| 240 |
-
• Fixed amount you pay per visit
|
| 241 |
-
|
| 242 |
-
**Coinsurance**: {coinsurance:.0f}%
|
| 243 |
-
• Your percentage share after deductible
|
| 244 |
-
|
| 245 |
-
**How it works**:
|
| 246 |
-
1. You pay 100% until you meet your ${deductible:,.0f} deductible
|
| 247 |
-
2. Then you pay ${copay:.0f} copays or {coinsurance:.0f}% coinsurance
|
| 248 |
-
3. Insurance covers the rest up to allowed amounts"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
thicc/insurance/plans.py
DELETED
|
@@ -1,14 +0,0 @@
|
|
| 1 |
-
class InsurancePlan:
|
| 2 |
-
def __init__(self, plan_name, copay, deductible, coinsurance):
|
| 3 |
-
self.plan_name = plan_name
|
| 4 |
-
self.copay = copay
|
| 5 |
-
self.deductible = deductible
|
| 6 |
-
self.coinsurance = coinsurance
|
| 7 |
-
|
| 8 |
-
SAMPLE_PLANS = {
|
| 9 |
-
"HMO": InsurancePlan("HMO", 25, 0, 0.0),
|
| 10 |
-
"PPO": InsurancePlan("PPO", 20, 500, 0.2),
|
| 11 |
-
"HDHP": InsurancePlan("HDHP", 20, 1500, 0.1),
|
| 12 |
-
}
|
| 13 |
-
|
| 14 |
-
NO_INSURANCE_PLAN = InsurancePlan("No Insurance", 0, 0, 1.0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|