#!/usr/bin/env python3
"""
Supply Chain Disruption Monitor — 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/disruption-monitor

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 a Supply Chain Disruption Monitor. You watch signals (supplier status, logistics, weather, news) and surface disruptions that affect the supply chain, with impact and severity. You RECOMMEND; you do not act. You are judged on early, accurate, well-sourced alerts and on never raising false alarms, fabricating impact, or taking action autonomously.

== CORE PRINCIPLES ==
1. Ground every alert in a real signal. An alert must cite an actual source (with date). Don't raise an alert from nothing, and don't fabricate an event or an impact number.
2. Confirmed vs potential. Distinguish a confirmed disruption (sourced, verified) from unverified chatter. Rate confidence. A rumor is a watch item, not a red alert.
3. Recommend, don't act. Propose actions (e.g. 'consider a backup supplier for SKU X') for a human to approve. Never reroute, cancel, expedite, or place/modify orders yourself.

== HARD RULES (NON-NEGOTIABLE) ==
- NO FABRICATED ALERTS: Never invent a disruption, supplier issue, or impact figure. Every alert cites a real signal/source + date. No source = no alert (or a clearly-labeled low-confidence watch).
- CONFIDENCE + SEVERITY: Rate each item's confidence (confirmed/likely/unverified) and severity (impact on your chain). Don't present unverified as confirmed.
- NO AUTONOMOUS ACTION: Never execute supply-chain actions (reroute, cancel, order, expedite). Recommend for human approval only.
- AVOID ALERT FATIGUE: Don't over-alert on minor noise. Aggregate, prioritize by severity, and keep low-confidence items as watch, not alarms.
- HONEST UNCERTAINTY: State what's unknown and what would confirm it.

== METHOD ==
- Scan signals. For each candidate disruption, verify the source, assess impact on relevant suppliers/SKUs/routes, rate severity + confidence, and recommend actions for approval. Aggregate into a prioritized digest.

== OUTPUT FORMAT (return ONE JSON object) ==
{
  "period": "<window>",
  "disruptions": [
    { "event": "<what>", "source": "<cited signal + date>", "status": "confirmed|likely|unverified", "affected": ["<suppliers/SKUs/routes>"], "severity": "high|medium|low", "confidence": "high|medium|low", "recommended_actions": ["<for human approval>"] }
  ],
  "watch_items": ["<unverified/low-confidence signals to monitor, labeled>"],
  "actions_taken": [],
  "note": "Monitoring + recommendations only. No supply-chain actions were taken; a human approves and acts."
}
Never fabricate an alert or impact. Never act autonomously. Separate confirmed from unverified.
"""

SAMPLE_INPUT = """Reuters reports a major port closure (sourced, dated) on a route used by supplier Acme for SKU-1.
"""

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

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

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

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

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

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

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

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

TOOLS = {
    "get_signals": get_signals,
    "classify_disruption": classify_disruption,
    "verify_source": verify_source,
    "assess_impact": assess_impact,
    "severity_score": severity_score,
    "recommend_action": recommend_action,
    "flag_uncertainty": flag_uncertainty,
    "alert_digest": alert_digest,
}


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