#!/usr/bin/env python3
"""
Insurance Policy Coverage Agent — runnable demo (Agent Blueprint from AgentKits)

This runs a REAL agent loop: it sends the kit's actual system prompt and a
realistic sample input to the model, prints the structured decision, and then
executes any proposed actions against MOCK tools (labelled [MOCK]).

The model/reasoning is real. The tools are stubs that return fake data and do
NOT touch any real system. Wiring the mock tools to your own integrations
(CRM, database, GitHub, etc.) is the final implementation step — see README.md.

Works with Anthropic OR OpenAI — it auto-detects whichever API key is set.

© 2026 AgentKits · www.agent-kits.com · Licensed CC BY 4.0 (attribution required)
Original blueprint: https://agent-kits.com/kit/policy-coverage-explainer

Setup (pick one):
    pip install anthropic   &&  export ANTHROPIC_API_KEY=sk-ant-...
    pip install openai      &&  export OPENAI_API_KEY=sk-...
Then:
    python run.py
"""
import os, json, sys

ANTHROPIC_MODEL = "claude-sonnet-4-6"
OPENAI_MODEL = "gpt-4o"

SYSTEM_PROMPT = """You are an Insurance Policy Coverage Agent. You explain what a policyholder's policy says, in plain language, grounded in the policy document. You explain coverage; you do NOT decide or guarantee claims. You are judged on accurate, cited, honest explanations and on never inventing coverage or promising a payout.

== CORE PRINCIPLES ==
1. Answer from the policy, with citations. Explain coverage only from the actual policy document, citing the section. If it's not in the policy or is ambiguous, say so — don't guess.
2. Coverage is not a claim decision. You explain what the policy says. Whether a specific claim is paid depends on the facts, investigation, and an adjuster. Never guarantee a claim outcome.
3. Honest about limits. Surface relevant exclusions, limits, deductibles, and conditions, not just the favorable parts.

== HARD RULES (NON-NEGOTIABLE) ==
- NO FABRICATED COVERAGE: Never invent coverage, limits, or terms not in the policy. Cite the section for what you state. Not in the policy = say so + route to a licensed agent.
- NO CLAIM GUARANTEES: Never say or imply a claim "will be paid" or "is covered" for a specific incident. Explain the policy language; the claim decision is separate (facts + adjuster).
- SURFACE EXCLUSIONS/LIMITS: Include relevant exclusions, deductibles, and conditions; don't cherry-pick favorable terms.
- NOT LEGAL/FINANCIAL ADVICE: You explain the policy; you don't give legal or financial advice or coverage determinations.
- ESCALATE: Disputes, claims, ambiguous coverage, and anything beyond explanation -> route to a licensed agent/claims.

== METHOD ==
- Find the relevant policy language for the question, explain it plainly, cite the section, surface limits/exclusions, and note where a claim decision or a licensed agent is needed.

== OUTPUT FORMAT (return ONE JSON object) ==
{
  "question": "<member question>",
  "covered_by_policy_doc": <bool>,
  "explanation": "<plain-language, FROM the policy>",
  "citation": "<policy section, or empty>",
  "limits_exclusions": ["<relevant deductibles/limits/exclusions>"],
  "claim_guarantee": "none — coverage explanation is not a claim decision",
  "route_to": "self_serve|licensed_agent|claims",
  "escalation": { "needed": <bool>, "reason": "<unclear/dispute/claim, or empty>" }
}
Never invent coverage. Never guarantee a claim. Cite the section; surface exclusions.
"""

SAMPLE_INPUT = """Member: 'Is windshield damage covered?' Policy comprehensive section covers glass with a $100 deductible.
"""

# ---- MOCK tools (stubs — replace with real integrations to deploy) ----
def get_question(args=None):
    print(f"  [MOCK] get_question(" + str(args) + ")")
    return {"status": "mock", "tool": "get_question"}

def policy_search(args=None):
    print(f"  [MOCK] policy_search(" + str(args) + ")")
    return {"status": "mock", "tool": "policy_search"}

def cite_section(args=None):
    print(f"  [MOCK] cite_section(" + str(args) + ")")
    return {"status": "mock", "tool": "cite_section"}

def check_coverage(args=None):
    print(f"  [MOCK] check_coverage(" + str(args) + ")")
    return {"status": "mock", "tool": "check_coverage"}

def explain_plainly(args=None):
    print(f"  [MOCK] explain_plainly(" + str(args) + ")")
    return {"status": "mock", "tool": "explain_plainly"}

def flag_unclear(args=None):
    print(f"  [MOCK] flag_unclear(" + str(args) + ")")
    return {"status": "mock", "tool": "flag_unclear"}

def no_claim_guarantee(args=None):
    print(f"  [MOCK] no_claim_guarantee(" + str(args) + ")")
    return {"status": "mock", "tool": "no_claim_guarantee"}

def escalate_to_agent(args=None):
    print(f"  [MOCK] escalate_to_agent(" + str(args) + ")")
    return {"status": "mock", "tool": "escalate_to_agent"}

TOOLS = {
    "get_question": get_question,
    "policy_search": policy_search,
    "cite_section": cite_section,
    "check_coverage": check_coverage,
    "explain_plainly": explain_plainly,
    "flag_unclear": flag_unclear,
    "no_claim_guarantee": no_claim_guarantee,
    "escalate_to_agent": escalate_to_agent,
}


def call_model(system, user):
    """Dispatch to whichever provider's key is configured."""
    if os.environ.get("ANTHROPIC_API_KEY"):
        try:
            from anthropic import Anthropic
        except ImportError:
            sys.exit("Run: pip install anthropic")
        msg = Anthropic().messages.create(
            model=ANTHROPIC_MODEL,
            max_tokens=1600,
            system=system,
            messages=[{"role": "user", "content": user}],
        )
        return "".join(b.text for b in msg.content if getattr(b, "type", "") == "text")
    if os.environ.get("OPENAI_API_KEY"):
        try:
            from openai import OpenAI
        except ImportError:
            sys.exit("Run: pip install openai")
        resp = OpenAI().chat.completions.create(
            model=OPENAI_MODEL,
            max_tokens=1600,
            messages=[
                {"role": "system", "content": system},
                {"role": "user", "content": user},
            ],
        )
        return resp.choices[0].message.content
    sys.exit("Set ANTHROPIC_API_KEY or OPENAI_API_KEY first.")


def extract_json(text):
    a, b = text.find("{"), text.rfind("}")
    if a == -1 or b == -1:
        return None
    try:
        return json.loads(text[a : b + 1])
    except json.JSONDecodeError:
        return None


def main():
    print("=" * 70)
    print("INPUT:\n" + SAMPLE_INPUT)
    print("=" * 70)

    text = call_model(SYSTEM_PROMPT, SAMPLE_INPUT)
    print("AGENT DECISION:\n" + text)
    print("=" * 70)

    data = extract_json(text)
    if not data:
        print("(No JSON object parsed — the model returned prose.)")
        return

    actions = data.get("actions") or []
    if actions:
        print("EXECUTING PROPOSED ACTIONS (mock):")
        for a in actions:
            name = a.get("tool")
            args = a.get("args", {})
            if name in TOOLS:
                TOOLS[name](args)
            else:
                print(f"  [skip] no mock tool for '{name}'")
    else:
        print("This blueprint returned a structured result (no side-effecting "
              "actions in this sample). Available mock tools: "
              + ", ".join(TOOLS.keys()))


if __name__ == "__main__":
    main()
