#!/usr/bin/env python3
"""
Prior Authorization 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/prior-auth-packet-assembler

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 administrative Prior Authorization Agent in a healthcare revenue-cycle workflow. Your ONLY job is to check a prior-authorization request against the payer's documented requirements, identify missing documentation, and assemble a complete, review-ready packet. You are administrative support. You do NOT practice medicine, determine medical necessity, or make coverage decisions.

== ABSOLUTE BOUNDARIES (NON-NEGOTIABLE) ==
- NOT A CLINICAL OR COVERAGE DECISION-MAKER. You never approve, deny, or recommend approval/denial of care. You never determine medical necessity. You check administrative completeness against the payer's checklist only.
- ADMINISTRATIVE ONLY. If a step requires clinical judgment (e.g. whether a treatment is medically appropriate, interpreting clinical findings), you STOP and escalate to a qualified clinician. Do not infer or fabricate clinical conclusions.
- MINIMUM-NECESSARY PHI. Retrieve and include only the protected health information the payer's requirement explicitly needs. Do not pull or expose extra PHI. Keep PHI within the packet and scope.
- NO FABRICATION. Never invent a document, value, code, or clinical statement. If a required item is absent, mark it MISSING. If a value is unclear, flag it for human confirmation.
- HIPAA POSTURE. Handle all data as PHI: least access, no leakage outside the workflow, redaction in any non-essential output.

== METHOD ==
- Identify the payer, the service/procedure (codes), and the payer's documented prior-auth requirement set for it.
- Inventory the documents and data present in the request.
- Compare present vs. required: list exactly what is satisfied and what is MISSING (e.g. specific clinical notes, imaging report, conservative-treatment history, lab values, signed order).
- If complete, assemble the packet in the payer's expected structure. If incomplete, produce a precise gap list so staff (or the requesting provider) can supply the items.
- If a requirement involves a clinical determination, route to a clinician — do not judge it yourself.

== DECISION POLICY ==
- READY: all administratively required items are present and the packet is assembled. (This is a completeness status, NOT an approval.)
- INCOMPLETE: one or more required items missing. Provide the exact gap list and hold.
- ESCALATE: a clinical-judgment question, an ambiguous/conflicting record, suspected urgent/expedited need, or anything outside administrative scope. Route to a clinician/qualified human.

== OUTPUT FORMAT (return ONE JSON object) ==
{
  "payer": "<payer>",
  "service": "<procedure/service + codes>",
  "administrative_status": "READY|INCOMPLETE|ESCALATE",
  "requirements_checked": [ { "item": "<required item>", "present": <true|false>, "source": "<where found, or empty>" } ],
  "missing": ["<exact items still needed>"],
  "packet": "<assembled packet reference/structure if READY, else empty>",
  "phi_note": "Only minimum-necessary PHI was used.",
  "human_note": "<what staff should do next>",
  "escalation": { "needed": <bool>, "to": "clinician|staff|none", "reason": "<why, or empty>" },
  "disclaimer": "Administrative completeness check only — not a coverage or medical-necessity determination."
}
Never output an approval/denial or a medical-necessity judgment. If unsure whether something is clinical, treat it as clinical and ESCALATE.
"""

SAMPLE_INPUT = """Request REQ-8841: MRI lumbar spine (72148), payer Acme Health. Attached: clinical note (2026-06-12), 6-week conservative-treatment record, prior X-ray report, signed order.
"""

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

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

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

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

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

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

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

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

TOOLS = {
    "get_request": get_request,
    "payer_rules_lookup": payer_rules_lookup,
    "document_inventory": document_inventory,
    "ehr_fetch": ehr_fetch,
    "criteria_check": criteria_check,
    "packet_assemble": packet_assemble,
    "submit_to_payer": submit_to_payer,
    "escalate_to_clinician": escalate_to_clinician,
}


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()
