From 808d1f6d260779136902ab0901c3ef49bd40f758 Mon Sep 17 00:00:00 2001 From: ipu Date: Thu, 11 Sep 2025 22:05:18 +0300 Subject: [PATCH 01/30] update uninsurable issues --- src/services/estimation_service_v2.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/services/estimation_service_v2.py b/src/services/estimation_service_v2.py index 22829e6..7616a5c 100644 --- a/src/services/estimation_service_v2.py +++ b/src/services/estimation_service_v2.py @@ -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): @@ -459,7 +469,8 @@ class EstimationService: plan_coverage = self.get_plan_coverage(plans[0]) rx_spend = 0 - for applicant_id, applicant in enumerate(applicants): + for applicant in applicants: + applicant_id = applicant.applicant applicant_review_reasons = [] applicant_dtq_reasons = [] applicant_accept_reasons = [] From 137dd66e24ac812f04f72beabee65fa896da0912 Mon Sep 17 00:00:00 2001 From: ipu Date: Mon, 15 Sep 2025 14:43:36 +0300 Subject: [PATCH 02/30] make page_id lower --- src/api/v1/router.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/v1/router.py b/src/api/v1/router.py index 13e262a..a3c9a8b 100644 --- a/src/api/v1/router.py +++ b/src/api/v1/router.py @@ -14,7 +14,7 @@ async def insurance_chat(request: models.InsuranceChatRequest): try: current_page = 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) result = await chat_service.process_insurance_chat( message=request.message, From 0d79803feac5eb2f529ac58b4a72304777e23172 Mon Sep 17 00:00:00 2001 From: ipu Date: Mon, 15 Sep 2025 15:19:45 +0300 Subject: [PATCH 03/30] limit compare_plans to 3 --- src/services/chat_service.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/services/chat_service.py b/src/services/chat_service.py index ad4be67..f33c0d3 100644 --- a/src/services/chat_service.py +++ b/src/services/chat_service.py @@ -166,6 +166,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) From 538ea04aa51e5409b8d03315cd00b0f72f361b29 Mon Sep 17 00:00:00 2001 From: ipu Date: Mon, 15 Sep 2025 20:30:00 +0300 Subject: [PATCH 04/30] add context.application to chat --- src/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/models.py b/src/models.py index d00ef07..2bc517b 100644 --- a/src/models.py +++ b/src/models.py @@ -86,6 +86,7 @@ class EstimationResponse(BaseModel): class InsuranceChatContext(BaseModel): page: str + application: dict | None = None class InsuranceChatRequest(BaseModel): userId: str | int | None = None From d61aac581368fe74cd215246b3c8038e7293ca68 Mon Sep 17 00:00:00 2001 From: ipu Date: Wed, 17 Sep 2025 12:49:23 +0300 Subject: [PATCH 05/30] send application info to prompt --- src/api/v1/router.py | 6 ++++++ src/services/chat_service.py | 13 ++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/api/v1/router.py b/src/api/v1/router.py index a3c9a8b..7520acf 100644 --- a/src/api/v1/router.py +++ b/src/api/v1/router.py @@ -16,11 +16,17 @@ async def insurance_chat(request: models.InsuranceChatRequest): if request.context and 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 + result = await chat_service.process_insurance_chat( message=request.message, session_id=request.session_id, uid=str(request.userId), current_page=current_page, + application=application, ) return models.InsuranceChatResponse( diff --git a/src/services/chat_service.py b/src/services/chat_service.py index f33c0d3..321d0c9 100644 --- a/src/services/chat_service.py +++ b/src/services/chat_service.py @@ -135,7 +135,7 @@ 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 process_insurance_chat(self, message: str, session_id: Optional[str] = None, uid: Optional[int] = None, current_page: Optional[str] = None, application: Optional[dict] = None) -> Dict[str, Any]: """Process an insurance chat request""" try: if not session_id: @@ -148,6 +148,17 @@ class ChatService: 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("effective_date", None): + effective_date = application.get("phq", {}).get("effective_date", None) + instructions += f"Plan effective date: {effective_date}" + if current_page: instructions += f"\n\n# User now is currently on page: {current_page}" From 9b4a6a3ad5ec7b6d0d64519b841ed467ae03ce91 Mon Sep 17 00:00:00 2001 From: ipu Date: Thu, 18 Sep 2025 13:49:25 +0300 Subject: [PATCH 06/30] fix effectiveDate typo --- src/services/chat_service.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/chat_service.py b/src/services/chat_service.py index 321d0c9..0aefdf7 100644 --- a/src/services/chat_service.py +++ b/src/services/chat_service.py @@ -155,8 +155,8 @@ class ChatService: for p in application["plans"]: plans_info += f"{str(p)}\n" instructions += plans_info - if application.get("phq", {}).get("effective_date", None): - effective_date = application.get("phq", {}).get("effective_date", None) + if application.get("phq", {}).get("effectiveDate", None): + effective_date = application.get("phq", {}).get("effectiveDate", None) instructions += f"Plan effective date: {effective_date}" if current_page: From b2eba19fb1df5f8f658faf27472e980bbd5fcc5a Mon Sep 17 00:00:00 2001 From: ipu Date: Fri, 19 Sep 2025 12:09:26 +0300 Subject: [PATCH 07/30] add user_session --- ...9_19_1203-31359fcda8a7_add_user_session.py | 45 +++++++++++++++++++ src/database.py | 8 ++++ src/services/chat_service.py | 37 ++++++++++++--- 3 files changed, 83 insertions(+), 7 deletions(-) create mode 100644 alembic/versions/2025_09_19_1203-31359fcda8a7_add_user_session.py diff --git a/alembic/versions/2025_09_19_1203-31359fcda8a7_add_user_session.py b/alembic/versions/2025_09_19_1203-31359fcda8a7_add_user_session.py new file mode 100644 index 0000000..5ea0d00 --- /dev/null +++ b/alembic/versions/2025_09_19_1203-31359fcda8a7_add_user_session.py @@ -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 ### diff --git a/src/database.py b/src/database.py index 19c461c..8cbb5f1 100644 --- a/src/database.py +++ b/src/database.py @@ -34,5 +34,13 @@ 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) + + engine = create_engine(settings.DATABASE_URL) Session = sessionmaker(autocommit=False, autoflush=False, bind=engine) diff --git a/src/services/chat_service.py b/src/services/chat_service.py index 0aefdf7..705a0a1 100644 --- a/src/services/chat_service.py +++ b/src/services/chat_service.py @@ -7,6 +7,7 @@ from src.models import ApplicantParam, ChatHook, PlansParam, InsuranceChatContex from .session_service import session_service from ..api.v1.models import Source, HistoryItem from ..config import settings +from ..database import Session, UserSession class ChatService: @@ -134,15 +135,37 @@ 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, application: Optional[dict] = None) -> Dict[str, Any]: + + + async def get_user_session(self, uid: str) -> str | None: + with Session() as session: + session = session.query(UserSession).filter(UserSession.user_id == uid).first() + if not session: + return None + return 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 process_insurance_chat(self, message: str, session_id: Optional[str] = None, uid: Optional[str] = None, current_page: Optional[str] = None, application: Optional[dict] = 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) + await self.create_user_session(uid, session_id) + else: + session_id = await session_service.create_session(agent_id=settings.TALESTORM_AGENT_ID) + instructions = "" if uid: From d8d38ab1326fabafbaaa88e33ed5e1bed3bf2588 Mon Sep 17 00:00:00 2001 From: ipu Date: Mon, 22 Sep 2025 16:33:59 +0300 Subject: [PATCH 08/30] add base64 decode for context.application --- src/api/v1/router.py | 5 +++++ src/models.py | 1 + 2 files changed, 6 insertions(+) diff --git a/src/api/v1/router.py b/src/api/v1/router.py index 7520acf..bb64d39 100644 --- a/src/api/v1/router.py +++ b/src/api/v1/router.py @@ -1,3 +1,6 @@ +import base64 +import json + from fastapi import APIRouter, HTTPException from src.services.estimation_service_v2 import EstimationService @@ -20,6 +23,8 @@ async def insurance_chat(request: models.InsuranceChatRequest): 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, diff --git a/src/models.py b/src/models.py index 2bc517b..c19b876 100644 --- a/src/models.py +++ b/src/models.py @@ -87,6 +87,7 @@ class EstimationResponse(BaseModel): class InsuranceChatContext(BaseModel): page: str application: dict | None = None + applicationDTO: str | None = None class InsuranceChatRequest(BaseModel): userId: str | int | None = None From ffb475071768ed9e388df3392427141bb505d14e Mon Sep 17 00:00:00 2001 From: ipu Date: Mon, 22 Sep 2025 20:25:56 +0300 Subject: [PATCH 09/30] fix default uid in chat --- src/api/v1/router.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/v1/router.py b/src/api/v1/router.py index bb64d39..ed83c4e 100644 --- a/src/api/v1/router.py +++ b/src/api/v1/router.py @@ -29,7 +29,7 @@ async def insurance_chat(request: models.InsuranceChatRequest): 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, ) From 75186b1b70dcaef7fa3cee26dc82e6da33180be3 Mon Sep 17 00:00:00 2001 From: ipu Date: Wed, 24 Sep 2025 15:41:31 +0300 Subject: [PATCH 10/30] Update medication frequencies --- prompt-v3.md | 218 ++++++++++++++++++++++++++ src/models.py | 1 + src/services/estimation_service_v2.py | 15 +- 3 files changed, 232 insertions(+), 2 deletions(-) create mode 100644 prompt-v3.md diff --git a/prompt-v3.md b/prompt-v3.md new file mode 100644 index 0000000..3688372 --- /dev/null +++ b/prompt-v3.md @@ -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 you’re talking to a smart friend. Keep responses short, positive, and polite. Your answers are specific to the user’s 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 user’s 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: + > “You’re welcome to complete an application! Once you’re 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 can’t provide a quote, we’ll 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 isn’t 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. I’m 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. I’m 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 can’t give medical advice, I recommend checking with your doctor to get the best guidance.” + +* **General Not My Expertise** + > “That’s a great question about \[topic]! It’s a bit outside what I can help with, but I’m 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, I’d love to help.” + +* **Offering Next Steps** + > “While I can’t advise on \[topic] directly, I’d 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 you’re 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 doesn’t 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 it’s 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 it’s quick. + * If repeated abandons: switch from nudging → supporting (e.g., “Is there something I can explain that might help?”). + +* **Rejected / DTQ’d 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.). \ No newline at end of file diff --git a/src/models.py b/src/models.py index c19b876..aa6427d 100644 --- a/src/models.py +++ b/src/models.py @@ -25,6 +25,7 @@ class Medication(BaseModel): rxcui: str dosage: str frequency: str + frequencyDescription: str description: str class IssueDetail(BaseModel): diff --git a/src/services/estimation_service_v2.py b/src/services/estimation_service_v2.py index 7616a5c..2847f34 100644 --- a/src/services/estimation_service_v2.py +++ b/src/services/estimation_service_v2.py @@ -385,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 == ["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 From 3ff38c9e867f05b598531ee47b216ca70ff9222b Mon Sep 17 00:00:00 2001 From: ipu Date: Wed, 24 Sep 2025 15:44:52 +0300 Subject: [PATCH 11/30] fix medication frequency.description --- src/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models.py b/src/models.py index aa6427d..75bdbc0 100644 --- a/src/models.py +++ b/src/models.py @@ -25,7 +25,7 @@ class Medication(BaseModel): rxcui: str dosage: str frequency: str - frequencyDescription: str + frequencyDescription: str = '' description: str class IssueDetail(BaseModel): From 8cfa3762b80ce9445ee4d7d78900b9f116c9b4db Mon Sep 17 00:00:00 2001 From: ipu Date: Wed, 24 Sep 2025 15:46:07 +0300 Subject: [PATCH 12/30] fix medication frequency (one time dose) --- src/services/estimation_service_v2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/estimation_service_v2.py b/src/services/estimation_service_v2.py index 2847f34..21d0c96 100644 --- a/src/services/estimation_service_v2.py +++ b/src/services/estimation_service_v2.py @@ -387,7 +387,7 @@ class EstimationService: month_times = 120 elif medication.frequency in ["Weekly", "Once weekly"]: month_times = 4 - elif medication.frequency == ["Monthly", "One-time dose"]: + elif medication.frequency in ["Monthly", "One-time dose"]: month_times = 1 elif medication.frequency == "Every other day": month_times = 15 From 52c7292d94abea1053652aea4c658313b439515c Mon Sep 17 00:00:00 2001 From: ipu Date: Wed, 24 Sep 2025 23:01:59 +0300 Subject: [PATCH 13/30] fix applicant_id --- src/services/estimation_service_v2.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/services/estimation_service_v2.py b/src/services/estimation_service_v2.py index 21d0c96..6d920a0 100644 --- a/src/services/estimation_service_v2.py +++ b/src/services/estimation_service_v2.py @@ -480,8 +480,7 @@ class EstimationService: plan_coverage = self.get_plan_coverage(plans[0]) rx_spend = 0 - for applicant in applicants: - applicant_id = applicant.applicant + for applicant_id, applicant in enumerate(applicants): applicant_review_reasons = [] applicant_dtq_reasons = [] applicant_accept_reasons = [] From e0de48a66ab8a04982157cc6c7059021fd3f6691 Mon Sep 17 00:00:00 2001 From: ipu Date: Mon, 29 Sep 2025 18:09:44 +0300 Subject: [PATCH 14/30] add initialize endpoint --- src/api/v1/router.py | 15 ++++++++ src/models.py | 8 +++++ src/services/chat_service.py | 70 +++++++++++++++++++++++++++++++++++- 3 files changed, 92 insertions(+), 1 deletion(-) diff --git a/src/api/v1/router.py b/src/api/v1/router.py index ed83c4e..1009f5b 100644 --- a/src/api/v1/router.py +++ b/src/api/v1/router.py @@ -46,6 +46,21 @@ 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()) + + result = await chat_service.initialize_chat(str(request.userId), application) + 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""" diff --git a/src/models.py b/src/models.py index 75bdbc0..684250c 100644 --- a/src/models.py +++ b/src/models.py @@ -96,6 +96,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 diff --git a/src/services/chat_service.py b/src/services/chat_service.py index 705a0a1..c88ecd9 100644 --- a/src/services/chat_service.py +++ b/src/services/chat_service.py @@ -8,6 +8,14 @@ from .session_service import session_service from ..api.v1.models import Source, HistoryItem from ..config import settings from ..database import Session, UserSession +from datetime import datetime + +# vars: name +INIT_MESSAGES = [ + "Hi. I’m Alia, your personal benefits assistant! If you have a question at any point in the enrollment process, just ask, and I’ll try to get you an answer.", + "Hi, {name}! Welcome back! As usual, I’m 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: @@ -125,6 +133,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""" @@ -153,6 +173,51 @@ class ChatService: session.add(user_session) session.commit() + async def initialize_chat(self, uid: str, application): + 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 + + try: + name = application["applicants"][0]["firstName"] + except: + return { + "session_id": session_id, + "answer": INIT_MESSAGES[0], + } + + last_message = self.get_last_chat_message_date(session_id) + if not last_message: + return { + "session_id": session_id, + "answer": INIT_MESSAGES[0], + } + + applicant = application["applicants"][0] + 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 { + "session_id": session_id, + "answer": INIT_MESSAGES[2].format(name=name) + } + + return { + "session_id": session_id, + "answer": INIT_MESSAGES[1].format(name=name) + } + + + + + + + + + + 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) -> Dict[str, Any]: """Process an insurance chat request""" @@ -162,7 +227,10 @@ class ChatService: 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) - await self.create_user_session(uid, session_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) From 0e6cb8bbd0cfeed0aaef500146a2eb260883b370 Mon Sep 17 00:00:00 2001 From: ipu Date: Mon, 29 Sep 2025 18:46:52 +0300 Subject: [PATCH 15/30] add applicant names to prompt --- src/services/chat_service.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/services/chat_service.py b/src/services/chat_service.py index c88ecd9..559411a 100644 --- a/src/services/chat_service.py +++ b/src/services/chat_service.py @@ -249,6 +249,13 @@ class ChatService: 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}" From 37920ef259a43eee7a0ac6f03297dec82ac32e1e Mon Sep 17 00:00:00 2001 From: ipu Date: Wed, 1 Oct 2025 14:32:20 +0300 Subject: [PATCH 16/30] add name to context --- src/api/v1/router.py | 6 +++++- src/models.py | 5 +++++ src/services/chat_service.py | 18 +++++------------- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/api/v1/router.py b/src/api/v1/router.py index 1009f5b..e1b2bef 100644 --- a/src/api/v1/router.py +++ b/src/api/v1/router.py @@ -54,7 +54,11 @@ async def init_chat(request: models.InitializeChatRequest): elif request.context and request.context.applicationDTO: application = json.loads(base64.b64decode(request.context.applicationDTO).decode()) - result = await chat_service.initialize_chat(str(request.userId), application) + name = None + if request.context and request.context.name: + name = request.context.name + + result = await chat_service.initialize_chat(str(request.userId), application, name.first_name) return models.InitializeChatResponse( session_id=result["session_id"], answer=result["answer"], diff --git a/src/models.py b/src/models.py index 684250c..d3e8089 100644 --- a/src/models.py +++ b/src/models.py @@ -85,10 +85,15 @@ class EstimationResponse(BaseModel): details: EstimationDetails 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 class InsuranceChatRequest(BaseModel): userId: str | int | None = None diff --git a/src/services/chat_service.py b/src/services/chat_service.py index 559411a..57f2bff 100644 --- a/src/services/chat_service.py +++ b/src/services/chat_service.py @@ -1,14 +1,14 @@ 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 from .session_service import session_service from ..api.v1.models import Source, HistoryItem from ..config import settings from ..database import Session, UserSession -from datetime import datetime # vars: name INIT_MESSAGES = [ @@ -173,7 +173,7 @@ class ChatService: session.add(user_session) session.commit() - async def initialize_chat(self, uid: str, application): + async def initialize_chat(self, uid: str, application, name): 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) @@ -183,7 +183,8 @@ class ChatService: pass try: - name = application["applicants"][0]["firstName"] + if not name: + name = application["applicants"][0]["firstName"] except: return { "session_id": session_id, @@ -210,15 +211,6 @@ class ChatService: } - - - - - - - - - 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) -> Dict[str, Any]: """Process an insurance chat request""" try: From b77be4c25030cc9106688045fa3c0b92e2066bb6 Mon Sep 17 00:00:00 2001 From: ipu Date: Wed, 1 Oct 2025 14:38:03 +0300 Subject: [PATCH 17/30] fix name context --- src/api/v1/router.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/v1/router.py b/src/api/v1/router.py index e1b2bef..e145e9c 100644 --- a/src/api/v1/router.py +++ b/src/api/v1/router.py @@ -56,9 +56,9 @@ async def init_chat(request: models.InitializeChatRequest): name = None if request.context and request.context.name: - name = request.context.name + name = request.context.name.first_name - result = await chat_service.initialize_chat(str(request.userId), application, name.first_name) + result = await chat_service.initialize_chat(str(request.userId), application, name) return models.InitializeChatResponse( session_id=result["session_id"], answer=result["answer"], From 7eb8136cd4239ab098fa2aa745716ffce2845ee0 Mon Sep 17 00:00:00 2001 From: ipu Date: Wed, 1 Oct 2025 17:29:49 +0300 Subject: [PATCH 18/30] remove 6.5 tier --- src/services/estimation_service_v2.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/services/estimation_service_v2.py b/src/services/estimation_service_v2.py index 6d920a0..553c85f 100644 --- a/src/services/estimation_service_v2.py +++ b/src/services/estimation_service_v2.py @@ -415,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: From dbf468b5860edf355ada355568bc162acc8b958a Mon Sep 17 00:00:00 2001 From: ipu Date: Thu, 2 Oct 2025 13:18:55 +0300 Subject: [PATCH 19/30] fix initialize with empty application --- src/services/chat_service.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/services/chat_service.py b/src/services/chat_service.py index 57f2bff..e563ca3 100644 --- a/src/services/chat_service.py +++ b/src/services/chat_service.py @@ -198,7 +198,13 @@ class ChatService: "answer": INIT_MESSAGES[0], } - applicant = application["applicants"][0] + try: + applicant = application["applicants"][0] + except: + return { + "session_id": session_id, + "answer": 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 { "session_id": session_id, From 280611e86fa282d55cb67acb372ddb66c592d21a Mon Sep 17 00:00:00 2001 From: ipu Date: Thu, 2 Oct 2025 16:46:16 +0300 Subject: [PATCH 20/30] fix alia greeting --- src/services/chat_service.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/services/chat_service.py b/src/services/chat_service.py index e563ca3..9f636ae 100644 --- a/src/services/chat_service.py +++ b/src/services/chat_service.py @@ -191,7 +191,8 @@ class ChatService: "answer": INIT_MESSAGES[0], } - last_message = self.get_last_chat_message_date(session_id) + last_message = await self.get_last_chat_message_date(session_id) + print(f"{uid}; {session_id}; {last_message}") if not last_message: return { "session_id": session_id, From 0e33063c99d36eca2baa1b53c87b3907ebbf4eab Mon Sep 17 00:00:00 2001 From: ipu Date: Thu, 2 Oct 2025 20:04:27 +0300 Subject: [PATCH 21/30] add isFirstVisit --- src/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/models.py b/src/models.py index d3e8089..b49bc92 100644 --- a/src/models.py +++ b/src/models.py @@ -94,6 +94,7 @@ class InsuranceChatContext(BaseModel): application: dict | None = None applicationDTO: str | None = None name: UserNameContext | None = None + isFirstVisit: bool = True class InsuranceChatRequest(BaseModel): userId: str | int | None = None From 41fd8e3d1504332e4584942d48bc37f97fece8cf Mon Sep 17 00:00:00 2001 From: ipu Date: Thu, 2 Oct 2025 21:13:14 +0300 Subject: [PATCH 22/30] first_visit logic --- src/api/v1/router.py | 5 ++++- src/services/chat_service.py | 6 ++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/api/v1/router.py b/src/api/v1/router.py index e145e9c..c0f96c2 100644 --- a/src/api/v1/router.py +++ b/src/api/v1/router.py @@ -57,8 +57,11 @@ async def init_chat(request: models.InitializeChatRequest): 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 - result = await chat_service.initialize_chat(str(request.userId), application, name) + result = await chat_service.initialize_chat(str(request.userId), application, name, first_visit) return models.InitializeChatResponse( session_id=result["session_id"], answer=result["answer"], diff --git a/src/services/chat_service.py b/src/services/chat_service.py index 9f636ae..7bfa83d 100644 --- a/src/services/chat_service.py +++ b/src/services/chat_service.py @@ -173,7 +173,7 @@ class ChatService: session.add(user_session) session.commit() - async def initialize_chat(self, uid: str, application, name): + async def initialize_chat(self, uid: str, application, name, first_visit): 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) @@ -191,9 +191,7 @@ class ChatService: "answer": INIT_MESSAGES[0], } - last_message = await self.get_last_chat_message_date(session_id) - print(f"{uid}; {session_id}; {last_message}") - if not last_message: + if first_visit: return { "session_id": session_id, "answer": INIT_MESSAGES[0], From dd1b79f7e760a60635ff5cda7c101f7a5799ad66 Mon Sep 17 00:00:00 2001 From: ipu Date: Fri, 3 Oct 2025 20:26:17 +0300 Subject: [PATCH 23/30] add show_page hook --- ...5_10_03_1608-4324550d9c83_add_user_page.py | 36 +++++++ src/api/v1/router.py | 8 +- src/database.py | 1 + src/models.py | 5 +- src/services/chat_service.py | 102 +++++++++++------- 5 files changed, 113 insertions(+), 39 deletions(-) create mode 100644 alembic/versions/2025_10_03_1608-4324550d9c83_add_user_page.py diff --git a/alembic/versions/2025_10_03_1608-4324550d9c83_add_user_page.py b/alembic/versions/2025_10_03_1608-4324550d9c83_add_user_page.py new file mode 100644 index 0000000..2f2ca3d --- /dev/null +++ b/alembic/versions/2025_10_03_1608-4324550d9c83_add_user_page.py @@ -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 ### diff --git a/src/api/v1/router.py b/src/api/v1/router.py index c0f96c2..646442c 100644 --- a/src/api/v1/router.py +++ b/src/api/v1/router.py @@ -16,6 +16,7 @@ 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 = str(request.context.page).lower() current_page = await get_page_description(page_id) @@ -32,6 +33,7 @@ async def insurance_chat(request: models.InsuranceChatRequest): uid=str(request.userId) if request.userId else None, current_page=current_page, application=application, + page_id=page_id ) return models.InsuranceChatResponse( @@ -61,7 +63,11 @@ async def init_chat(request: models.InitializeChatRequest): if request.context: first_visit = request.context.isFirstVisit - result = await chat_service.initialize_chat(str(request.userId), application, name, first_visit) + 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"], diff --git a/src/database.py b/src/database.py index 8cbb5f1..0b6e9ef 100644 --- a/src/database.py +++ b/src/database.py @@ -40,6 +40,7 @@ class UserSession(Base): 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) diff --git a/src/models.py b/src/models.py index b49bc92..c0ee547 100644 --- a/src/models.py +++ b/src/models.py @@ -125,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 diff --git a/src/services/chat_service.py b/src/services/chat_service.py index 7bfa83d..3585217 100644 --- a/src/services/chat_service.py +++ b/src/services/chat_service.py @@ -4,7 +4,7 @@ from typing import Dict, Any, List, Optional import httpx -from src.models import ApplicantParam, ChatHook, PlansParam +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 @@ -86,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: @@ -145,7 +154,6 @@ class ChatService: 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: @@ -159,10 +167,10 @@ class ChatService: async def get_user_session(self, uid: str) -> str | None: with Session() as session: - session = session.query(UserSession).filter(UserSession.user_id == uid).first() - if not session: + user_session = session.query(UserSession).filter(UserSession.user_id == uid).first() + if not user_session: return None - return session.session_id + return user_session.session_id async def create_user_session(self, uid: str, session_id: str): with Session() as session: @@ -173,50 +181,62 @@ class ChatService: session.add(user_session) session.commit() - async def initialize_chat(self, uid: str, application, name, first_visit): - 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 + 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 + def get_welcome_message(self, application, name, first_visit): try: if not name: name = application["applicants"][0]["firstName"] except: - return { - "session_id": session_id, - "answer": INIT_MESSAGES[0], - } + return INIT_MESSAGES[0] if first_visit: - return { - "session_id": session_id, - "answer": INIT_MESSAGES[0], - } + return INIT_MESSAGES[0] try: applicant = application["applicants"][0] except: - return { - "session_id": session_id, - "answer": INIT_MESSAGES[2].format(name=name) - } + 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 { - "session_id": session_id, - "answer": INIT_MESSAGES[2].format(name=name) - } + 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": INIT_MESSAGES[1].format(name=name) + "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) -> Dict[str, Any]: + 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 or not await session_service.validate_session(session_id): @@ -224,13 +244,14 @@ class ChatService: 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 + 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) + old_page_id = await self.update_user_page(uid, page_id) instructions = "" if uid: @@ -265,6 +286,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( @@ -283,7 +305,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, From 9b1a6340e84010b1585ef668fb52d2060e879829 Mon Sep 17 00:00:00 2001 From: ipu Date: Fri, 3 Oct 2025 22:13:01 +0300 Subject: [PATCH 24/30] do not save page on /initialize --- src/services/chat_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/chat_service.py b/src/services/chat_service.py index 3585217..7ef7d0d 100644 --- a/src/services/chat_service.py +++ b/src/services/chat_service.py @@ -219,7 +219,7 @@ class ChatService: await self.create_user_session(uid, session_id) except: pass - await self.update_user_page(uid, page_id) + # await self.update_user_page(uid, page_id) welcome_msg = self.get_welcome_message(application, name, first_visit) From 13d9a76bedcd03965b54b6f39d5500c74015ef45 Mon Sep 17 00:00:00 2001 From: ipu Date: Fri, 3 Oct 2025 22:53:17 +0300 Subject: [PATCH 25/30] new estimation data model --- src/api/v1/router.py | 2 +- src/models.py | 6 ++++-- src/services/estimation_service_v2.py | 14 ++++++++++---- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/api/v1/router.py b/src/api/v1/router.py index 646442c..81537c0 100644 --- a/src/api/v1/router.py +++ b/src/api/v1/router.py @@ -85,7 +85,7 @@ async def estimate(request: models.EstimationRequest): ) 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, request.plans, request.coverage) return estimation_response diff --git a/src/models.py b/src/models.py index c0ee547..7cff934 100644 --- a/src/models.py +++ b/src/models.py @@ -17,7 +17,7 @@ class Applicant(BaseModel): class Plan(BaseModel): id: int - priceId: int + priceId: int | None = None class Medication(BaseModel): applicant: int @@ -60,6 +60,7 @@ class Address(BaseModel): class EstimationRequest(BaseModel): userId: str | int | None = Field(None, description="Unique identifier") + coverage: int = 1 applicants: List[Applicant] plans: List[Plan] phq: PHQ @@ -69,7 +70,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 diff --git a/src/services/estimation_service_v2.py b/src/services/estimation_service_v2.py index 553c85f..2f7da6c 100644 --- a/src/services/estimation_service_v2.py +++ b/src/services/estimation_service_v2.py @@ -458,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, plans: list[Plan], plan_coverage: int): estimation_results = [] is_review = False review_reasons = [] @@ -478,7 +478,9 @@ class EstimationService: base_tier = tier break - plan_coverage = self.get_plan_coverage(plans[0]) + if not plan_coverage: + plan_coverage = self.get_plan_coverage(plans[0]) + rx_spend = 0 for applicant_id, applicant in enumerate(applicants): applicant_review_reasons = [] @@ -559,7 +561,7 @@ class EstimationService: message=final_reason, ) ) - + plan_price_id = self.get_plan_price(plans[0], base_tier, plan_coverage) if is_dtq: @@ -570,6 +572,7 @@ class EstimationService: dtq=is_dtq, reason=reason, price_id=plan_price_id, + tier=f"Tier {base_tier.value}", ), results=estimation_results ) @@ -582,6 +585,7 @@ class EstimationService: dtq=is_dtq, reason=reason, price_id=plan_price_id, + tier=f"Tier {base_tier.value}", ), results=estimation_results ) @@ -597,6 +601,7 @@ class EstimationService: dtq=True, reason=reason, price_id=plan_price_id, + tier=f"Tier {base_tier.value}", ), results=estimation_results ) @@ -614,6 +619,7 @@ class EstimationService: dtq=is_dtq, reason=reason, price_id=plan_price_id, + tier=f"Tier {base_tier.value}", ), results=estimation_results ) @@ -625,7 +631,7 @@ class EstimationService: dtq=is_dtq, reason=reason, price_id=plan_price_id, + tier=f"Tier {base_tier.value}", ), results=estimation_results ) - \ No newline at end of file From 25f091d02c68b4575d64f5a3178fb3d09da74f82 Mon Sep 17 00:00:00 2001 From: ipu Date: Fri, 3 Oct 2025 23:03:06 +0300 Subject: [PATCH 26/30] new estimation data model --- src/models.py | 4 ++-- src/services/estimation_service_v2.py | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/models.py b/src/models.py index 7cff934..71037d0 100644 --- a/src/models.py +++ b/src/models.py @@ -70,7 +70,7 @@ class EstimationRequest(BaseModel): class EstimationDetails(BaseModel): dtq: bool reason: str - price_id: int = -1 + # price_id: int = -1 tier: str class EstimationResult(BaseModel): @@ -85,7 +85,7 @@ class EstimationResult(BaseModel): class EstimationResponse(BaseModel): status: str details: EstimationDetails - results: List[EstimationResult] + # results: List[EstimationResult] class UserNameContext(BaseModel): first_name: str diff --git a/src/services/estimation_service_v2.py b/src/services/estimation_service_v2.py index 2f7da6c..a5cecee 100644 --- a/src/services/estimation_service_v2.py +++ b/src/services/estimation_service_v2.py @@ -571,10 +571,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: @@ -584,10 +584,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) @@ -600,10 +600,10 @@ 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: @@ -618,10 +618,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) @@ -630,8 +630,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 ) From f873b7e710084954139874c7f4341d94c1a136f2 Mon Sep 17 00:00:00 2001 From: ipu Date: Fri, 3 Oct 2025 23:09:35 +0300 Subject: [PATCH 27/30] debug estimation request --- src/api/v1/router.py | 8 ++++++-- src/models.py | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/api/v1/router.py b/src/api/v1/router.py index 81537c0..8aa867b 100644 --- a/src/api/v1/router.py +++ b/src/api/v1/router.py @@ -78,12 +78,16 @@ async def init_chat(request: models.InitializeChatRequest): 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) + if not request.plans: + request.plans = list() + estimation_service = EstimationService() estimation_response = await estimation_service.estimate_insurance(request.applicants, request.phq, request.plans, request.coverage) diff --git a/src/models.py b/src/models.py index 71037d0..d8bfe05 100644 --- a/src/models.py +++ b/src/models.py @@ -62,7 +62,7 @@ class EstimationRequest(BaseModel): userId: str | int | None = Field(None, description="Unique identifier") coverage: int = 1 applicants: List[Applicant] - plans: List[Plan] + plans: List[Plan] | None = None phq: PHQ income: float address: Address From 7178369f4775320670ed4bf71f3cd11ea2f96eb1 Mon Sep 17 00:00:00 2001 From: ipu Date: Fri, 10 Oct 2025 09:59:26 +0300 Subject: [PATCH 28/30] remove welcome page from storage --- src/services/chat_service.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/services/chat_service.py b/src/services/chat_service.py index 7ef7d0d..7d83e16 100644 --- a/src/services/chat_service.py +++ b/src/services/chat_service.py @@ -190,6 +190,12 @@ class ChatService: 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: @@ -251,7 +257,10 @@ class ChatService: else: session_id = await session_service.create_session(agent_id=settings.TALESTORM_AGENT_ID) - old_page_id = await self.update_user_page(uid, page_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: From c3135e1549efdcdf70a0b56764ceb9746f6fd1b1 Mon Sep 17 00:00:00 2001 From: ipu Date: Fri, 10 Oct 2025 09:59:41 +0300 Subject: [PATCH 29/30] new flow; remove plans from request --- src/api/v1/router.py | 24 +++++++++++++++++++++--- src/models.py | 2 -- src/services/estimation_service_v2.py | 9 +++------ 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/api/v1/router.py b/src/api/v1/router.py index 8aa867b..09c1122 100644 --- a/src/api/v1/router.py +++ b/src/api/v1/router.py @@ -85,11 +85,29 @@ async def estimate(request: models.EstimationRequest): ) print("estimation request: ", request) - if not request.plans: - request.plans = list() + + 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, request.coverage) + estimation_response = await estimation_service.estimate_insurance(request.applicants, request.phq, coverage) return estimation_response diff --git a/src/models.py b/src/models.py index d8bfe05..3f56a35 100644 --- a/src/models.py +++ b/src/models.py @@ -60,9 +60,7 @@ class Address(BaseModel): class EstimationRequest(BaseModel): userId: str | int | None = Field(None, description="Unique identifier") - coverage: int = 1 applicants: List[Applicant] - plans: List[Plan] | None = None phq: PHQ income: float address: Address diff --git a/src/services/estimation_service_v2.py b/src/services/estimation_service_v2.py index a5cecee..a9f3552 100644 --- a/src/services/estimation_service_v2.py +++ b/src/services/estimation_service_v2.py @@ -458,7 +458,7 @@ class EstimationService: return None - async def estimate_insurance(self, applicants: list[Applicant], phq: PHQ, plans: list[Plan], plan_coverage: int): + async def estimate_insurance(self, applicants: list[Applicant], phq: PHQ, plan_coverage: int): estimation_results = [] is_review = False review_reasons = [] @@ -478,9 +478,6 @@ class EstimationService: base_tier = tier break - if not plan_coverage: - plan_coverage = self.get_plan_coverage(plans[0]) - rx_spend = 0 for applicant_id, applicant in enumerate(applicants): applicant_review_reasons = [] @@ -562,7 +559,7 @@ class EstimationService: ) ) - 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) @@ -609,7 +606,7 @@ class EstimationService: 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) From 0b0181a711927b509ae8fbf933013457f14098b5 Mon Sep 17 00:00:00 2001 From: ipu Date: Mon, 13 Oct 2025 17:57:17 +0300 Subject: [PATCH 30/30] set effectiveDate type to Any --- src/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/models.py b/src/models.py index 3f56a35..97383d8 100644 --- a/src/models.py +++ b/src/models.py @@ -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): @@ -44,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")