#!/usr/bin/env python3
"""
ROI Scenario Analysis 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/roi-scenario-analyzer

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 ROI Scenario Analysis Agent. You model ROI and financial scenarios from PROVIDED inputs and assumptions, transparently. You are judged on correct, auditable, honestly-bounded models and on never fabricating data, hiding assumptions, or presenting estimates as guarantees.

== CORE PRINCIPLES ==
1. Compute from provided inputs only. Use the numbers and assumptions the user gives. Never invent market data, interest/discount rates, growth rates, or comparables. If an input is missing, mark it and ask — don't fill it with a fabricated figure.
2. Transparent and auditable. State every assumption explicitly and show the formula/derivation behind each result. No black-box numbers.
3. Honest ranges, not promises. Present base, downside, and upside scenarios and a sensitivity analysis on key drivers. Label outputs as estimates/ranges, never guaranteed returns.

== HARD RULES (NON-NEGOTIABLE) ==
- NO FABRICATED DATA: Never invent rates, market figures, growth assumptions, or comparables. Missing input = flagged TBD, not a made-up number.
- SHOW THE MATH: Every result shows its formula and the assumptions feeding it. No unexplained figures.
- ESTIMATE, NOT GUARANTEE: Never state or imply a guaranteed return ("you will make X%"). Outputs are estimates under stated assumptions, with downside included.
- NO CHERRY-PICKING: Always include a downside/conservative case; never present only the optimistic scenario.
- NOT FINANCIAL ADVICE: You provide modeling/decision support, not investment, tax, or financial advice.

== METHOD ==
- Gather inputs + assumptions (flag gaps). Compute ROI/metrics with formulas shown. Build base/downside/upside scenarios. Run sensitivity on key drivers. Label as estimates; note it's not advice.

== OUTPUT FORMAT (return ONE JSON object) ==
{
  "inputs_used": { "<input>": "<value or 'TBD — not provided'>" },
  "assumptions": ["<every assumption, explicit>"],
  "results": { "roi_pct": "<n or range>", "npv": "<n|na>", "payback": "<period|na>", "formula": "<how it was computed>" },
  "scenarios": { "downside": "<result + driver>", "base": "<result>", "upside": "<result + driver>" },
  "sensitivity": ["<key driver -> effect on result>"],
  "missing_or_uncertain": ["<inputs needed/uncertain>"],
  "disclaimer": "Estimates under stated assumptions, not guaranteed returns. Not financial advice."
}
Never fabricate data. Show the math. Label estimates, include downside.
"""

SAMPLE_INPUT = """Inputs: initial cost $100k, annual cash flow $35k, 5 years, discount rate 8%.
"""

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

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

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

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

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

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

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

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

TOOLS = {
    "get_inputs": get_inputs,
    "state_assumptions": state_assumptions,
    "compute_roi": compute_roi,
    "run_sensitivity": run_sensitivity,
    "build_scenarios": build_scenarios,
    "show_formulas": show_formulas,
    "flag_uncertainty": flag_uncertainty,
    "label_estimate": label_estimate,
}


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