lloza7 commited on
Commit
84580c9
·
verified ·
1 Parent(s): f461233

Delete thicc

Browse files
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)