Compare commits

..

30 commits
master ... dev

Author SHA1 Message Date
ipu
0b0181a711 set effectiveDate type to Any 2025-10-13 17:57:17 +03:00
ipu
c3135e1549 new flow; remove plans from request 2025-10-10 09:59:41 +03:00
ipu
7178369f47 remove welcome page from storage 2025-10-10 09:59:26 +03:00
ipu
f873b7e710 debug estimation request 2025-10-03 23:09:35 +03:00
ipu
25f091d02c new estimation data model 2025-10-03 23:03:06 +03:00
ipu
13d9a76bed new estimation data model 2025-10-03 22:53:17 +03:00
ipu
9b1a6340e8 do not save page on /initialize 2025-10-03 22:13:01 +03:00
ipu
dd1b79f7e7 add show_page hook 2025-10-03 20:26:17 +03:00
ipu
41fd8e3d15 first_visit logic 2025-10-02 21:13:14 +03:00
ipu
0e33063c99 add isFirstVisit 2025-10-02 20:04:27 +03:00
ipu
280611e86f fix alia greeting 2025-10-02 16:46:16 +03:00
ipu
dbf468b586 fix initialize with empty application 2025-10-02 13:18:55 +03:00
ipu
7eb8136cd4 remove 6.5 tier 2025-10-01 17:29:49 +03:00
ipu
b77be4c250 fix name context 2025-10-01 14:38:03 +03:00
ipu
37920ef259 add name to context 2025-10-01 14:32:20 +03:00
ipu
0e6cb8bbd0 add applicant names to prompt 2025-09-29 18:46:52 +03:00
ipu
e0de48a66a add initialize endpoint 2025-09-29 18:09:44 +03:00
ipu
52c7292d94 fix applicant_id 2025-09-24 23:01:59 +03:00
ipu
8cfa3762b8 fix medication frequency (one time dose) 2025-09-24 15:46:07 +03:00
ipu
3ff38c9e86 fix medication frequency.description 2025-09-24 15:44:52 +03:00
ipu
75186b1b70 Update medication frequencies 2025-09-24 15:41:31 +03:00
ipu
ffb4750717 fix default uid in chat 2025-09-22 20:25:56 +03:00
ipu
d8d38ab132 add base64 decode for context.application 2025-09-22 16:33:59 +03:00
ipu
b2eba19fb1 add user_session 2025-09-19 12:09:26 +03:00
ipu
9b4a6a3ad5 fix effectiveDate typo 2025-09-18 13:49:25 +03:00
ipu
d61aac5813 send application info to prompt 2025-09-17 12:49:23 +03:00
ipu
538ea04aa5 add context.application to chat 2025-09-15 20:30:00 +03:00
ipu
0d79803fea limit compare_plans to 3 2025-09-15 15:19:45 +03:00
ipu
137dd66e24 make page_id lower 2025-09-15 14:43:36 +03:00
ipu
808d1f6d26 update uninsurable issues 2025-09-11 22:05:18 +03:00
8 changed files with 606 additions and 48 deletions

View file

@ -0,0 +1,45 @@
"""add user_session
Revision ID: 31359fcda8a7
Revises: 57f67bce2bec
Create Date: 2025-09-19 12:03:40.032535
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '31359fcda8a7'
down_revision: Union[str, Sequence[str], None] = '57f67bce2bec'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('user_session',
sa.Column('id', sa.BigInteger(), nullable=False),
sa.Column('user_id', sa.String(), nullable=True),
sa.Column('session_id', sa.String(), nullable=True),
sa.PrimaryKeyConstraint('id')
)
with op.batch_alter_table('user_session', schema=None) as batch_op:
batch_op.create_index(batch_op.f('ix_user_session_id'), ['id'], unique=False)
batch_op.create_index(batch_op.f('ix_user_session_user_id'), ['user_id'], unique=False)
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('user_session', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_user_session_user_id'))
batch_op.drop_index(batch_op.f('ix_user_session_id'))
op.drop_table('user_session')
# ### end Alembic commands ###

View file

@ -0,0 +1,36 @@
"""add user page
Revision ID: 4324550d9c83
Revises: 31359fcda8a7
Create Date: 2025-10-03 16:08:13.898990
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '4324550d9c83'
down_revision: Union[str, Sequence[str], None] = '31359fcda8a7'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('user_session', schema=None) as batch_op:
batch_op.add_column(sa.Column('page_id', sa.String(), nullable=True))
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('user_session', schema=None) as batch_op:
batch_op.drop_column('page_id')
# ### end Alembic commands ###

218
prompt-v3.md Normal file
View file

@ -0,0 +1,218 @@
# Personality
You are Alia, a friendly and helpful virtual insurance assistant. You never assume the user knows insurance jargon. You always explain things clearly, like youre talking to a smart friend. Keep responses short, positive, and polite. Your answers are specific to the users question and the plans offered. Reassure users that the application process is easy, fast, and secure.
Provide clear, concise, accurate, and informative answers using current and expert knowledge. Refrain from addressing and answering on prohibited topics, regardless of whether the question appears related to insurance or a specific plan. Do not lose your personality.
Before processing any question, check if it falls under prohibited themes (health conditions, legal, tax, HR, medical, off-topic). If it does, respond only with the appropriate prohibited theme message, slightly tailored to reflect the users specific question (e.g., mention the specific health condition or topic raised). Do not provide any other information.
---
# Prohibited Themes
* **Health Condition Questions**
* If the question is about being **over 65**, respond:
> “Youre welcome to complete an application! Once youre 65, your options for major medical plans are usually limited because Medicare becomes the primary program. But you may still be able to see quotes for other products like dental, vision, or supplemental plans. And if we cant provide a quote, well guide you toward the right resources so you always know your options.”
* For all other health conditions (e.g., smoking, weight loss medication, warfarin):
* Reassure that users can still apply.
* Clarify that eligibility is determined during the application.
* Emphasize that if a quote isnt possible, alternatives will be suggested.
* Keep responses friendly, warm, and in line with the “better interaction” examples.
* **Tax-Related Questions**
> “Questions about \[tax-related term] are better suited for a legal or HR expert. Im here to help you understand your plan and coverage—let me know if you'd like help with that!”
* **Legal or HR-Type Questions**
> “Questions about \[legal/HR topic] are better suited for a legal or HR expert. Im here to help you understand your plan and coverage—let me know if you'd like help with that!”
* **Medical Advice Questions**
> “I'm really glad you're being proactive about your health, especially regarding \[medical topic]! Since I cant give medical advice, I recommend checking with your doctor to get the best guidance.”
* **General Not My Expertise**
> “Thats a great question about \[topic]! Its a bit outside what I can help with, but Im here for anything related to your insurance plans or benefits.”
* **Totally Off-Topic / Programming / Self-Harm / Politics**
> “Hmm, \[topic] is a little outside my wheelhouse! But if you have any questions about your health or insurance plans, Id love to help.”
* **Offering Next Steps**
> “While I cant advise on \[topic] directly, Id be happy to help you find the right contact or share plan details that might help.”
---
# Notes
## Application & Onboarding
* **Application process:**
* Emphasize that it is easy, fast, and flexible.
* Most users complete it in just a few minutes.
* Users can pause and resume anytime; progress is saved automatically.
* Reassure users that youre available to guide them through each step.
* **Avoid confusion:**
* Do not continue conversations that collect application data.
* Do not ask questions like *“Ready to get started?”*—this may imply the chat is part of the application itself.
* Instead, guide users to interact directly with the form and offer support for questions on each page.
* **Who is OneHealth?**
* OneHealth is a licensed health insurance provider offering **exclusive, premium private insurance plans** not available anywhere else.
* Not ACA Marketplace, faith-based, or MEC.
* Plans are available **year-round, in all 50 states**, with coverage from trusted carriers (e.g., **Cigna, Guardian, Equitable**).
* Tailored for gig workers, freelancers, and small businesses (<50 employees).
* **Plan legitimacy:**
* Confirm that plans are **real, high-quality, ACA-compliant alternatives** sourced from trusted carriers.
* Premium plans are not always the cheapest — emphasize quality and stability.
* **Comparison to Obamacare:**
* Clarify that plans are private and not part of the federal Marketplace.
* They offer **year-round enrollment**, curated provider networks, and potentially lower rates — while still being compliant and comprehensive.
* **Eligibility:**
* Anyone can apply, but acceptance is based on underwriting.
* Eligibility is determined by the **Personal Health Questionnaire (PHQ)**.
* If a user doesnt qualify, never provide reasons. Say only: *“we could not offer you a plan at this time.”*
* Ancillary products (dental, vision, accident, critical illness, hospital indemnity) are open to everyone without a PHQ.
* **Data privacy:**
* All user data is protected with **bank-level encryption** and **HIPAA-compliant systems**.
* Data is never sold.
* It is only shared with selected insurance carriers when necessary for enrollment.
* **Post-application:**
* Applications are reviewed for eligibility and pricing.
* Some plans may offer **instant approval and coverage**.
* **Dependents:** Maximum of **5 children plus a spouse**.
* **Nicotine use:** Does **not** automatically deny coverage. Eligibility is application-based.
* **Medications:** Encourage accuracy; if info is unknown, reassure progress is still saved. Never say “denied automatically.”
* **Minor procedures (e.g., mole removal):** Often too minor to list; reassure omission usually means its not required.
---
## Plan Selection & Recommendations
* **Provider Fit:**
* Current health plans use the **Cigna network**.
* Options range from high-deductible to low-deductible PPOs.
* Networks are curated — may not include every provider. Always be transparent.
* **Prescription Coverage:**
* All health plans cover prescriptions.
* For routine, low-cost prescriptions: most plans fit.
* For specialty or brand-only meds: recommend richer coverage like **Cigna PPO 1000**.
* If a drug is not covered, clearly flag this.
* **Budget & Risk Trade-off:**
* Alia should always explain trade-offs:
* “Lower premium but higher deductible = lower monthly costs, but more if you need care.”
* “Higher premium but lower deductible = higher monthly costs, but more predictable bills.”
* **Usage Scenarios:**
* **Minimal care:** leaner health plan + ancillary (accident).
* **Moderate care:** mid-tier with co-pays.
* **Heavy care:** richer PPOs like **Cigna PPO 1000 or 1500**.
* **Family vs. Individual:**
* Highlight family deductibles, out-of-pocket max, and pediatric benefits when dependents are added.
* **Add-ons:**
* **Dental:** recommend if kids or user asks about cleanings/teeth.
* **Vision:** recommend for glasses, contacts, or annual exams.
* **Accident / Critical Illness / Hospital Indemnity:**
* Especially valuable for high-deductible health plans.
* Always disclose: *“Hospital Indemnity is supplemental insurance and not a substitute for major medical coverage.”*
* **Recommendation Style:**
* Name the plan clearly (**Cigna PPO 1000**).
* Explain *why* it may fit (doctor access, Rx coverage, predictable costs).
* Point out trade-offs (high vs. low deductible).
* Use examples and side-by-side comparisons where helpful.
* Plans and prices should be in **bold**.
---
## Returning & Rejected Users
* **Returning Users:**
* Greet warmly: “Welcome back! Ready to pick up where you left off?”
* Remind them of last step if available.
* If >30 days: PHQ must be redone. Reassure its quick.
* If repeated abandons: switch from nudging → supporting (e.g., “Is there something I can explain that might help?”).
* **Rejected / DTQd Users:**
* Stay empathetic, neutral. Never give specific reasons.
* Say: *“we could not offer you a plan at this time.”*
* Immediately pivot to ancillary products (dental, vision, accident, etc.).
* Optionally suggest ACA Marketplace link if appropriate.
* Invite return after 90 days.
---
## Payment
* **Methods:** Only **ACH** (no credit cards).
* **Bank connection issues:** Suggest retrying credentials or later attempt.
* Progress is always saved.
---
# Style Guidelines
* Plan-specific, actionable answers.
* Avoid jargon—use plain English.
* Use short paragraphs and bullet points.
* Never guess or assume.
* Stay warm, positive, not robotic.
* Personalize for health condition mentions.
* If rejected, only show ancillary plans.
* Always emphasize **data security, privacy, and flexibility**.
---
# Tool Usage Instructions
* **show\_plans** → when user wants to see one or more plans (include plan IDs).
* **compare\_plans** → when user wants a side-by-side comparison (include plan IDs).
* **update\_applicants** → when user updates applicant info (id: 0 = self, 1 = spouse, 2+ = children).
Use `get_plan_list()` to fetch available plans and `get_plan_by_id(plan_id)` for details (only if user is not rejected).
---
# Output Schema
```json
{
"answer": "string - the natural language reply to the user",
"show_plans": [],
"compare_plans": [],
"update_applicants": []
}
```
Important Output Rules:
- Always include "answer" (string) — this is the text the user will read.
- Include hooks when the user asks for plans or comparing of plans (show me plan ..., compare ... plan to ..., etc.).

View file

@ -1,3 +1,6 @@
import base64
import json
from fastapi import APIRouter, HTTPException
from src.services.estimation_service_v2 import EstimationService
@ -13,14 +16,24 @@ async def insurance_chat(request: models.InsuranceChatRequest):
"""Handle insurance chat requests"""
try:
current_page = None
page_id = None
if request.context and request.context.page:
page_id = request.context.page
page_id = str(request.context.page).lower()
current_page = await get_page_description(page_id)
application = None
if request.context and request.context.application:
application = request.context.application
elif request.context and request.context.applicationDTO:
application = json.loads(base64.b64decode(request.context.applicationDTO).decode())
result = await chat_service.process_insurance_chat(
message=request.message,
session_id=request.session_id,
uid=str(request.userId),
uid=str(request.userId) if request.userId else None,
current_page=current_page,
application=application,
page_id=page_id
)
return models.InsuranceChatResponse(
@ -35,18 +48,66 @@ async def insurance_chat(request: models.InsuranceChatRequest):
# raise e
raise HTTPException(status_code=500, detail=f"Error processing chat request: {str(e)}")
@router.post("/initialize", response_model=models.InitializeChatResponse)
async def init_chat(request: models.InitializeChatRequest):
application = None
if request.context and request.context.application:
application = request.context.application
elif request.context and request.context.applicationDTO:
application = json.loads(base64.b64decode(request.context.applicationDTO).decode())
name = None
if request.context and request.context.name:
name = request.context.name.first_name
first_visit = True
if request.context:
first_visit = request.context.isFirstVisit
page_id = None
if request.context and request.context.page:
page_id = str(request.context.page).lower()
result = await chat_service.initialize_chat(str(request.userId), application, name, first_visit, page_id)
return models.InitializeChatResponse(
session_id=result["session_id"],
answer=result["answer"],
)
@router.post("/estimation", response_model=models.EstimationResponse)
async def estimate(request: models.EstimationRequest):
"""Handle insurance estimation requests"""
try:
if not request.applicants or not request.plans:
if not request.applicants:
raise HTTPException(
status_code=400,
detail="Missing required applicants or plans"
)
print("estimation request: ", request)
has_primary = False
has_spouse = False
has_dependents = False
for applicant in request.applicants:
if applicant.applicant == 1:
has_primary = True
elif applicant.applicant == 2:
has_spouse = True
elif applicant.applicant == 3:
has_dependents = True
if has_primary and not has_spouse and not has_dependents:
coverage = 1
elif has_primary and has_spouse and not has_dependents:
coverage = 2
elif has_primary and not has_spouse and has_dependents:
coverage = 3
else:
coverage = 4
estimation_service = EstimationService()
estimation_response = await estimation_service.estimate_insurance(request.applicants, request.phq, request.plans)
estimation_response = await estimation_service.estimate_insurance(request.applicants, request.phq, coverage)
return estimation_response

View file

@ -34,5 +34,14 @@ class Webpage(Base):
description = Column(String)
class UserSession(Base):
__tablename__ = "user_session"
id = Column(BigInteger, primary_key=True, index=True)
user_id = Column(String, index=True)
session_id = Column(String)
page_id = Column(String, default=None, nullable=True)
engine = create_engine(settings.DATABASE_URL)
Session = sessionmaker(autocommit=False, autoflush=False, bind=engine)

View file

@ -1,5 +1,5 @@
from pydantic import BaseModel, Field
from typing import Optional, List
from typing import Optional, List, Any
from datetime import date
class Applicant(BaseModel):
@ -17,7 +17,7 @@ class Applicant(BaseModel):
class Plan(BaseModel):
id: int
priceId: int
priceId: int | None = None
class Medication(BaseModel):
applicant: int
@ -25,6 +25,7 @@ class Medication(BaseModel):
rxcui: str
dosage: str
frequency: str
frequencyDescription: str = ''
description: str
class IssueDetail(BaseModel):
@ -43,12 +44,12 @@ class PHQ(BaseModel):
treatment: bool
invalid: bool
pregnancy: bool
effectiveDate: date
disclaimer: bool
signature: str
medications: List[Medication]
issues: List[Issue]
conditions: List[Condition]
effectiveDate: Any = None
class Address(BaseModel):
address1: Optional[str] = Field("", description="Address line 1")
@ -60,7 +61,6 @@ class Address(BaseModel):
class EstimationRequest(BaseModel):
userId: str | int | None = Field(None, description="Unique identifier")
applicants: List[Applicant]
plans: List[Plan]
phq: PHQ
income: float
address: Address
@ -68,7 +68,8 @@ class EstimationRequest(BaseModel):
class EstimationDetails(BaseModel):
dtq: bool
reason: str
price_id: int
# price_id: int = -1
tier: str
class EstimationResult(BaseModel):
name: str
@ -82,10 +83,18 @@ class EstimationResult(BaseModel):
class EstimationResponse(BaseModel):
status: str
details: EstimationDetails
results: List[EstimationResult]
# results: List[EstimationResult]
class UserNameContext(BaseModel):
first_name: str
last_name: str
class InsuranceChatContext(BaseModel):
page: str
application: dict | None = None
applicationDTO: str | None = None
name: UserNameContext | None = None
isFirstVisit: bool = True
class InsuranceChatRequest(BaseModel):
userId: str | int | None = None
@ -93,6 +102,14 @@ class InsuranceChatRequest(BaseModel):
session_id: str | None = Field(None, description="Chat session ID")
context: InsuranceChatContext | None = None
class InitializeChatRequest(BaseModel):
userId: str | int
context: InsuranceChatContext | None = None
class InitializeChatResponse(BaseModel):
session_id: str
answer: str
class Source(BaseModel):
plan_name: str
chunk_number: int
@ -108,9 +125,12 @@ class PlansParam(BaseModel):
class ApplicantParam(BaseModel):
applicants: list[Applicant]
class PageParam(BaseModel):
page: str
class ChatHook(BaseModel):
tool: str
params: PlansParam | ApplicantParam
params: PlansParam | ApplicantParam | PageParam
class AIChatResponse(BaseModel):
answer: str

View file

@ -1,12 +1,21 @@
import json
from datetime import datetime
from typing import Dict, Any, List, Optional
import httpx
from src.models import ApplicantParam, ChatHook, PlansParam, InsuranceChatContext
from src.models import ApplicantParam, ChatHook, PlansParam, PageParam
from .session_service import session_service
from ..api.v1.models import Source, HistoryItem
from ..config import settings
from ..database import Session, UserSession
# vars: name
INIT_MESSAGES = [
"Hi. Im Alia, your personal benefits assistant! If you have a question at any point in the enrollment process, just ask, and Ill try to get you an answer.",
"Hi, {name}! Welcome back! As usual, Im here if you have any questions!",
"Hi, {name}! Welcome back! Do you want to pick up where you left off and complete your PHQ?",
]
class ChatService:
@ -77,7 +86,16 @@ class ChatService:
"chat_session_id": session_id,
"message": f"I'm sorry, I'm experiencing technical difficulties. Please try again later. Error: {str(e)}"
}
async def add_message_to_history(self, session_id: str, message: list):
async with await self.get_client() as client:
response = await client.post(
"/messages/",
params={"chat_session_id": session_id,},
json={"content": message}
)
return response.json()
async def get_chat_history(self, session_id: str) -> List[HistoryItem]:
"""Get chat history for a session and format it properly"""
async with await self.get_client() as client:
@ -124,7 +142,18 @@ class ChatService:
# raise e
print(f"Error getting chat history: {e}")
return []
async def get_last_chat_message_date(self, session_id: str):
async with await self.get_client() as client:
try:
response = await client.get("/chat/", params={"chat_session_id": session_id, "limit": 1})
resp_json = response.json()
msg_date = resp_json[0]["created_at"]
return datetime.fromisoformat(msg_date)
except:
return None
def _extract_sources_from_response(self, response_text: str) -> List[Source]:
"""Extract sources from RAG search results if available"""
# This is a placeholder - in a real implementation, you would:
@ -134,20 +163,127 @@ class ChatService:
# For now, return empty list - this would be populated when RAG is implemented
return []
async def process_insurance_chat(self, message: str, session_id: Optional[str] = None, uid: Optional[int] = None, current_page: Optional[str] = None) -> Dict[str, Any]:
async def get_user_session(self, uid: str) -> str | None:
with Session() as session:
user_session = session.query(UserSession).filter(UserSession.user_id == uid).first()
if not user_session:
return None
return user_session.session_id
async def create_user_session(self, uid: str, session_id: str):
with Session() as session:
user_session = UserSession(
user_id=uid,
session_id=session_id,
)
session.add(user_session)
session.commit()
async def update_user_page(self, uid: str, page_id: str | None):
with Session() as session:
user_session = session.query(UserSession).filter(UserSession.user_id == uid).first()
old_page_id = user_session.page_id
user_session.page_id = page_id
session.add(user_session)
session.commit()
return old_page_id
async def get_user_page(self, uid: str):
with Session() as session:
user_session = session.query(UserSession).filter(UserSession.user_id == uid).first()
old_page_id = user_session.page_id
return old_page_id
def get_welcome_message(self, application, name, first_visit):
try:
if not name:
name = application["applicants"][0]["firstName"]
except:
return INIT_MESSAGES[0]
if first_visit:
return INIT_MESSAGES[0]
try:
applicant = application["applicants"][0]
except:
return INIT_MESSAGES[2].format(name=name)
if not applicant.get("gender") or not applicant.get("dob") or applicant.get("dob") == "-01-" or not applicant.get("weight") or applicant.get("heightFt") is None or applicant.get("heightIn") is None:
return INIT_MESSAGES[2].format(name=name)
return INIT_MESSAGES[1].format(name=name)
async def initialize_chat(self, uid: str, application, name, first_visit, page_id):
session_id = await self.get_user_session(uid)
if not session_id or not await session_service.validate_session(session_id):
session_id = await session_service.create_session(agent_id=settings.TALESTORM_AGENT_ID)
try:
await self.create_user_session(uid, session_id)
except:
pass
# await self.update_user_page(uid, page_id)
welcome_msg = self.get_welcome_message(application, name, first_visit)
msg_history_item = [
{'parts': [{'content': 'Hi', 'part_kind': 'user-prompt'}],'kind': 'request'},
{'parts': [{'content': welcome_msg, 'part_kind': 'text'}], 'kind': 'response'}
]
await self.add_message_to_history(session_id, msg_history_item)
return {
"session_id": session_id,
"answer": welcome_msg
}
async def process_insurance_chat(self, message: str, session_id: Optional[str] = None, uid: Optional[str] = None, current_page: Optional[str] = None, application: Optional[dict] = None, page_id = None) -> Dict[str, Any]:
"""Process an insurance chat request"""
try:
if not session_id:
session_id = await session_service.create_session(agent_id=settings.TALESTORM_AGENT_ID)
elif not await session_service.validate_session(session_id):
session_id = await session_service.create_session(agent_id=settings.TALESTORM_AGENT_ID)
if not session_id or not await session_service.validate_session(session_id):
if uid:
session_id = await self.get_user_session(uid)
if not session_id or not await session_service.validate_session(session_id):
session_id = await session_service.create_session(agent_id=settings.TALESTORM_AGENT_ID)
try:
await self.create_user_session(uid, session_id)
except:
pass
else:
session_id = await session_service.create_session(agent_id=settings.TALESTORM_AGENT_ID)
if page_id.lower() != 'welcome':
old_page_id = await self.update_user_page(uid, page_id)
else:
old_page_id = await self.get_user_page(uid)
instructions = ""
if uid:
user_state = await self.get_user_state(uid)
instructions += f"\n\n# User Information\nApplication state (None means that application was not sent or pending):\n{user_state}"
if application:
instructions += f"\n\n# User Application Info\n"
if application.get("plans", []):
plans_info = "User plans:\n"
for p in application["plans"]:
plans_info += f"{str(p)}\n"
instructions += plans_info
if application.get("phq", {}).get("effectiveDate", None):
effective_date = application.get("phq", {}).get("effectiveDate", None)
instructions += f"Plan effective date: {effective_date}"
applicants = application.get("applicants", [])
if applicants:
applicants_info = "\nApplicants:"
for n, applicant in enumerate(applicants, 1):
user_name = applicant["firstName"]
applicants_info += f"\n{n}. {user_name}"
instructions += applicants_info
if current_page:
instructions += f"\n\n# User now is currently on page: {current_page}"
@ -159,6 +295,7 @@ class ChatService:
compare_plans = ai_response.get("compare_plans")
show_plans = ai_response.get("show_plans")
update_applicants = ai_response.get("update_applicants")
show_page = ai_response.get("show_page")
hooks = []
if update_applicants:
hooks.append(ChatHook(
@ -166,6 +303,8 @@ class ChatService:
params=ApplicantParam(applicants=update_applicants)
))
elif compare_plans:
if len(compare_plans) > 3:
compare_plans = compare_plans[:3]
hooks.append(ChatHook(
tool="compare_plans",
params=PlansParam(plans=compare_plans)
@ -175,7 +314,13 @@ class ChatService:
tool="show_plans",
params=PlansParam(plans=show_plans)
))
elif show_page and old_page_id:
hooks.append(ChatHook(
tool="show_page",
params=PageParam(page=old_page_id)
))
return {
"session_id": session_id,
"answer": ai_message,

View file

@ -55,7 +55,7 @@ BASE_TIERS = {
}
HEIGHT_WEIGHT_TABLE = {
(4, 2): (54, 60, 125, 151),
(4, 2): (54, 60, 125, 150),
(4, 3): (56, 63, 130, 156),
(4, 4): (59, 65, 136, 162),
(4, 5): (60, 68, 141, 169),
@ -286,6 +286,13 @@ UNINSURABLE_MEDICATIONS = [
"zoledronic acid"
]
UNINSURABLE_ISSUES = [
"alcohol", "alzheimer_disease", "chronic_pulmonary_disorder", "copd", "hepatitis", "liver_disorder",
"myasthenia_gravis", "organ_transplant", "other_cognitive", "nebulizer", "aids", "als", "amputation",
"cancer", "cirrhosis", "emphysema", "multiple_sclerosis", "nervous_disorder", "osteoarthritis",
"parkinson_disease", "senile_dementia", "systemic_lupus",
]
class EstimationService:
def __init__(self):
self.base_url = settings.TALESTORM_API_BASE_URL
@ -333,6 +340,8 @@ class EstimationService:
return True, "Applicant is pregnant"
for issue in phq.issues:
if issue.key in UNINSURABLE_ISSUES:
return True, f"Uninsurable issue: {issue.key}"
if issue.key == "surgery":
for detail in issue.details:
if detail.key == "not_performed":
@ -342,7 +351,8 @@ class EstimationService:
if condition.key in uninsurable_conditions:
return True, f"Applicant have uninsurable condition: {condition.key}"
# issues would be checked by AI
# issues would be partially checked by AI
return False, ""
def calculate_age(self, born):
@ -375,12 +385,23 @@ class EstimationService:
month_times = 90
elif medication.frequency == "Four times daily":
month_times = 120
elif medication.frequency == "Weekly":
elif medication.frequency in ["Weekly", "Once weekly"]:
month_times = 4
elif medication.frequency == "Monthly":
elif medication.frequency in ["Monthly", "One-time dose"]:
month_times = 1
elif medication.frequency == "Every other day":
month_times = 15
elif medication.frequency == "Three times per week":
month_times = 12
elif medication.frequency == "Every 2 weeks":
month_times = 2
elif medication.frequency == "Every X hours":
try:
freq = int(medication.frequencyDescription)
month_times = (24/freq) * 30
except:
month_times = 1
review_reasons.append(f"Unclear frequency of drug {medication.name}: Every X hours; X is not defined.")
else:
review_reasons.append(f"Unclear frequency of drug {medication.name}: {medication.frequency}")
month_times = 1
@ -394,14 +415,14 @@ class EstimationService:
def get_tier(self, coverage: int, rx_spend: float) -> tuple[Tier | None, str | None]:
tiers = [
Tier.TIER_1, Tier.TIER_15, Tier.TIER_2, Tier.TIER_25, Tier.TIER_3, Tier.TIER_35,
Tier.TIER_4, Tier.TIER_45, Tier.TIER_5, Tier.TIER_55, Tier.TIER_6, Tier.TIER_65, Tier.TIER_7
Tier.TIER_4, Tier.TIER_45, Tier.TIER_5, Tier.TIER_55, Tier.TIER_6, Tier.TIER_7
]
rates = {
1: [122, 127, 133, 139, 145, 151, 158, 164, 172, 182, 196, 209, 222],
2: [243, 253, 265, 277, 290, 302, 315, 327, 343, 364, 390, 417, 444],
3: [219, 228, 239, 249, 261, 272, 283, 295, 309, 327, 351, 376, 400],
4: [365, 379, 397, 415, 435, 452, 472, 491, 514, 545, 585, 626, 666]
1: [122, 127, 133, 139, 145, 151, 158, 164, 172, 182, 196, 222],
2: [243, 253, 265, 277, 290, 302, 315, 327, 343, 364, 390, 444],
3: [219, 228, 239, 249, 261, 272, 283, 295, 309, 327, 351, 400],
4: [365, 379, 397, 415, 435, 452, 472, 491, 514, 545, 585, 666],
}
if coverage not in rates:
@ -437,7 +458,7 @@ class EstimationService:
return None
async def estimate_insurance(self, applicants: list[Applicant], phq: PHQ, plans: list[Plan]):
async def estimate_insurance(self, applicants: list[Applicant], phq: PHQ, plan_coverage: int):
estimation_results = []
is_review = False
review_reasons = []
@ -457,7 +478,6 @@ class EstimationService:
base_tier = tier
break
plan_coverage = self.get_plan_coverage(plans[0])
rx_spend = 0
for applicant_id, applicant in enumerate(applicants):
applicant_review_reasons = []
@ -538,8 +558,8 @@ class EstimationService:
message=final_reason,
)
)
plan_price_id = self.get_plan_price(plans[0], base_tier, plan_coverage)
# plan_price_id = self.get_plan_price(plans[0], base_tier, plan_coverage)
if is_dtq:
reason = "\n".join(dtq_reasons)
@ -548,9 +568,10 @@ class EstimationService:
details=EstimationDetails(
dtq=is_dtq,
reason=reason,
price_id=plan_price_id,
# price_id=plan_price_id,
tier=f"Tier {base_tier.value}",
),
results=estimation_results
# results=estimation_results
)
if is_review:
@ -560,9 +581,10 @@ class EstimationService:
details=EstimationDetails(
dtq=is_dtq,
reason=reason,
price_id=plan_price_id,
# price_id=plan_price_id,
tier=f"Tier {base_tier.value}",
),
results=estimation_results
# results=estimation_results
)
new_tier, tier_reason = self.get_tier(plan_coverage, rx_spend)
@ -575,15 +597,16 @@ class EstimationService:
details=EstimationDetails(
dtq=True,
reason=reason,
price_id=plan_price_id,
# price_id=plan_price_id,
tier=f"Tier {base_tier.value}",
),
results=estimation_results
# results=estimation_results
)
if new_tier > base_tier:
base_tier = new_tier
plan_price_id = self.get_plan_price(plans[0], base_tier, plan_coverage)
# plan_price_id = self.get_plan_price(plans[0], base_tier, plan_coverage)
if base_tier is not None:
reason = "\n".join(accept_reasons)
@ -592,9 +615,10 @@ class EstimationService:
details=EstimationDetails(
dtq=is_dtq,
reason=reason,
price_id=plan_price_id,
# price_id=plan_price_id,
tier=f"Tier {base_tier.value}",
),
results=estimation_results
# results=estimation_results
)
else:
reason = "\n".join(dtq_reasons)
@ -603,8 +627,8 @@ class EstimationService:
details=EstimationDetails(
dtq=is_dtq,
reason=reason,
price_id=plan_price_id,
# price_id=plan_price_id,
tier=f"Tier {base_tier.value}",
),
results=estimation_results
# results=estimation_results
)