CoolFace
Apppublic

jaminy/SocialScience

sourceHugging Facemitupdated 10mo agoView on Hugging Face
0likes
app.py353 linesDownload Raw Back to root
1"""2Benefits Eligibility System3"""4 5import os6import json7import uuid8import re9from typing import Dict, List, Tuple10import gradio as gr11from openai import OpenAI12from dotenv import load_dotenv13from langchain_core.tools import tool14 15load_dotenv()16client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))17session_states = {}18 19# =========================================================================20# PLAIN FUNCTIONS (Used by Code)21# =========================================================================22 23def _extract_user_information(text: str) -> dict:24    """Extract structured information"""25    profile = {26        "income": None,27        "location": None,28        "ward": None,29        "family_size": None,30        "children_ages": []31    }32    33    text_lower = text.lower()34    35    # Income36    if match := re.search(r'\$?(\d{1,3}(?:,\d{3})*)\s*(?:per|/)?\s*(?:year|annually)', text_lower):37        profile['income'] = float(match.group(1).replace(',', ''))38    elif match := re.search(r'\$?(\d{1,3}(?:,\d{3})*)\s*(?:per|/)?\s*month', text_lower):39        profile['income'] = float(match.group(1).replace(',', '')) * 1240    elif match := re.search(r'(?:earning|making)\s*\$?(\d{1,3}(?:,\d{3})*)', text_lower):41        profile['income'] = float(match.group(1).replace(',', ''))42    43    # Location44    if any(x in text_lower for x in ['washington', 'dc', 'd.c.']):45        profile['location'] = 'Washington DC'46        if ward_match := re.search(r'ward\s*(\d)', text_lower):47            profile['ward'] = int(ward_match.group(1))48    49    # Family size50    if match := re.search(r'(\d+)\s*(?:kids|children)', text_lower):51        profile['family_size'] = int(match.group(1)) + 152    elif match := re.search(r'family\s*of\s*(\d+)', text_lower):53        profile['family_size'] = int(match.group(1))54    55    # Children ages56    for match in re.finditer(r'(?:ages?|aged)\s*(\d+)', text_lower):57        age = int(match.group(1))58        if 0 <= age <= 18:59            profile['children_ages'].append(age)60    61    return profile62 63 64def _calculate_snap_eligibility(income: float, family_size: int) -> dict:65    """Calculate SNAP eligibility"""66    FPL_BASE, FPL_INCREMENT = 15060, 538067    68    fpl = FPL_BASE + (family_size - 1) * FPL_INCREMENT69    threshold = fpl * 1.3070    eligible = income <= threshold71    72    benefits = {1: 291, 2: 535, 3: 766, 4: 973, 5: 1155}73    benefit = benefits.get(family_size, 1155)74    75    return {76        "program": "SNAP",77        "eligible": eligible,78        "estimated_monthly_benefit": round(benefit * 0.7) if eligible else 0,79        "income_threshold": round(threshold),80        "explanation": f"Income ${income:,.0f} vs ${threshold:,.0f} threshold (130% FPL)"81    }82 83 84def _calculate_medicaid_eligibility(income: float, family_size: int) -> dict:85    """Calculate Medicaid eligibility"""86    FPL_BASE, FPL_INCREMENT = 15060, 538087    88    fpl = FPL_BASE + (family_size - 1) * FPL_INCREMENT89    threshold = fpl * 2.1690    eligible = income <= threshold91    92    return {93        "program": "Medicaid",94        "eligible": eligible,95        "income_threshold": round(threshold),96        "explanation": f"DC Medicaid at 216% FPL: ${threshold:,.0f}"97    }98 99 100# =========================================================================101# @tool DECORATED VERSIONS (For AI Discovery)102# =========================================================================103 104@tool105def extract_user_information(text: str) -> dict:106    """Extract structured information from user's natural language.107    108    Args:109        text: User's description110    Returns:111        Structured profile dictionary112    """113    return _extract_user_information(text)114 115 116@tool117def calculate_snap_eligibility(income: float, family_size: int) -> dict:118    """Calculate SNAP eligibility using 130% FPL threshold.119    120    Args:121        income: Annual income in dollars122        family_size: Number of people in household123    Returns:124        Eligibility result with estimated benefit125    """126    return _calculate_snap_eligibility(income, family_size)127 128 129@tool130def calculate_medicaid_eligibility(income: float, family_size: int) -> dict:131    """Calculate Medicaid eligibility. DC uses 216% FPL.132    133    Args:134        income: Annual income135        family_size: Household size136    Returns:137        Eligibility result138    """139    return _calculate_medicaid_eligibility(income, family_size)140 141 142# =========================================================================143# AGENTS144# =========================================================================145 146class IntakeAgent:147    def process(self, user_query: str) -> Dict:148        # Use plain functions149        profile = _extract_user_information(user_query)150        151        # OpenAI call152        response = client.chat.completions.create(153            model="gpt-4-turbo-preview",154            temperature=0.7,155            messages=[156                {"role": "system", "content": "You are an empathetic intake specialist."},157                {"role": "user", "content": f"User: {user_query}\n\nProfile: {json.dumps(profile)}\n\nRespond warmly. Ask ONE follow-up if missing income, location, or family_size."}158            ]159        )160        161        return {162            "profile": profile,163            "response": response.choices[0].message.content,164            "ready": profile.get("income") and profile.get("family_size")165        }166 167 168class EligibilityAgent:169    def process(self, profile: Dict) -> Dict:170        results = []171        172        income = profile.get("income", 0)173        family_size = profile.get("family_size", 1)174        175        if income and family_size:176            results.append(_calculate_snap_eligibility(income, family_size))177            results.append(_calculate_medicaid_eligibility(income, family_size))178        179        response = client.chat.completions.create(180            model="gpt-4-turbo-preview",181            temperature=0.3,182            messages=[183                {"role": "system", "content": "Summarize eligibility encouragingly."},184                {"role": "user", "content": f"Results:\n{json.dumps(results, indent=2)}\n\nFocus on qualified programs."}185            ]186        )187        188        qualified = [r["program"] for r in results if r.get("eligible")]189        190        return {191            "qualified": qualified,192            "response": response.choices[0].message.content193        }194 195 196class ApplicationAgent:197    def process(self, qualified: List[str], profile: Dict) -> Dict:198        response = client.chat.completions.create(199            model="gpt-4-turbo-preview",200            temperature=0.5,201            messages=[202                {"role": "system", "content": "Create application guidance."},203                {"role": "user", "content": f"Programs: {qualified}\n\nCreate action plan:\n**THIS WEEK**: Apply online at dc.gov/access\nPhone: (202) 727-5355\nDocuments: Photo ID, Proof of income"}204            ]205        )206        207        return {"response": response.choices[0].message.content}208 209 210# =========================================================================211# MAIN SYSTEM212# =========================================================================213 214class BenefitsSystem:215    def __init__(self):216        self.intake = IntakeAgent()217        self.eligibility = EligibilityAgent()218        self.application = ApplicationAgent()219    220    def process(self, session_id: str, message: str) -> Tuple[str, Dict]:221        if session_id not in session_states:222            session_states[session_id] = {223                "stage": "intake",224                "intake_done": False,225                "eligibility_done": False226            }227        228        state = session_states[session_id]229        responses = []230        231        if not state["intake_done"]:232            result = self.intake.process(message)233            responses.append(("** Intake**", result["response"]))234            state["profile"] = result["profile"]235            236            if result["ready"]:237                state["intake_done"] = True238            else:239                return result["response"], state240        241        if state["intake_done"] and not state["eligibility_done"]:242            result = self.eligibility.process(state["profile"])243            responses.append(("** Eligibility**", result["response"]))244            state["qualified"] = result["qualified"]245            state["eligibility_done"] = True246        247        if state["eligibility_done"]:248            result = self.application.process(state["qualified"], state["profile"])249            responses.append(("** Application**", result["response"]))250            state["stage"] = "complete"251        252        final = "\n\n---\n\n".join([f"{t}\n\n{c}" for t, c in responses]) if len(responses) > 1 else responses[0][1]253        return final, state254 255 256# =========================================================================257# GRADIO UI - FIXED: Proper message format for chatbot258# =========================================================================259 260system = BenefitsSystem()261 262with gr.Blocks(title="Benefits System") as demo:263    gr.Markdown("#  Benefits Eligibility System\n\n**With @tool Decorators**")264    265    with gr.Row():266        with gr.Column(scale=2):267            # IMPORTANT: Gradio Chatbot expects list of tuples: [(user_msg, bot_msg), ...]268            chatbot = gr.Chatbot(269                label="Conversation",270                height=600271            )272            273            with gr.Row():274                msg = gr.Textbox(275                    label="Your situation",276                    placeholder="Single mom, 2 kids ages 4 and 7, $32k/year in DC",277                    lines=3,278                    scale=4279                )280                submit = gr.Button("Send", variant="primary", scale=1)281            clear = gr.Button(" New")282        283        with gr.Column(scale=1):284            session_id_display = gr.Textbox(label="Session", value="Not started", interactive=False)285            stage = gr.Textbox(label="Stage", value="Intake", interactive=False)286    287    session_state = gr.State(value=None)288    289    def process_message(message, chat_history, sess_id):290        """Process user message - returns properly formatted chat history"""291        if not message.strip():292            return "", chat_history, sess_id, "Intake"293        294        if not sess_id:295            sess_id = str(uuid.uuid4())296        297        try:298            # Process through system299            response, state = system.process(sess_id, message)300            301            # IMPORTANT: Gradio 4+ expects messages as dictionaries with 'role' and 'content'302            new_history = chat_history + [303                {"role": "user", "content": message},304                {"role": "assistant", "content": response}305            ]306            307            return "", new_history, sess_id, state["stage"].title()308            309        except Exception as e:310            error_msg = f"⚠️ Error: {str(e)}"311            new_history = chat_history + [312                {"role": "user", "content": message},313                {"role": "assistant", "content": error_msg}314            ]315            return "", new_history, sess_id, "Error"316    317    def clear_chat():318        """Clear conversation"""319        new_sess_id = str(uuid.uuid4())320        return [], new_sess_id, "Intake"321    322    # Event handlers323    msg.submit(324        process_message,325        inputs=[msg, chatbot, session_state],326        outputs=[msg, chatbot, session_state, stage]327    )328    329    submit.click(330        process_message,331        inputs=[msg, chatbot, session_state],332        outputs=[msg, chatbot, session_state, stage]333    )334    335    clear.click(336        clear_chat,337        outputs=[chatbot, session_state, stage]338    )339    340    session_state.change(341        lambda x: x[:8] + "..." if x else "Not started",342        inputs=[session_state],343        outputs=[session_id_display]344    )345    346    gr.Markdown("**Disclaimer**: AI guidance. Verify at [benefits.gov](https://benefits.gov)")347 348if __name__ == "__main__":349    if not os.getenv("OPENAI_API_KEY"):350        print("⚠️ OPENAI_API_KEY not found!")351    else:352        print(" Starting Benefits Eligibility System...")353        demo.launch(share=False, server_name="0.0.0.0")