# -*- coding: utf-8 -*-
"""Copilot Studio の月額を、公式の請求レート（1回答・1アクションが何 Copilot クレジットか）と
架空の利用条件（月の対応件数・1件あたりの内訳）から計算する。
使い方: python calculator.py input.json [expected.json]
  expected.json は配布した条件の検算用。自社の値に書き換えたときは付けずに実行する（付けると期待値と一致せず exit 1 になる）。
外部パッケージ不要（Python 3.10 以上）。ネットワーク接続なし。

前提（input.json）:
  - rates: 機能ごとの消費クレジット（2026-10-08 Microsoft Learn「請求レートと管理」で確認した値）
  - pack: 25,000 クレジット / 月 のテナント単位パックと、その月額（円・公式価格ページの FAQ の値）
  - payg: 従量課金の 1 クレジットあたり米ドル（Azure 価格ページ）。円単価は公式ページで未取得のため為替で換算する
  - fx: 円換算に使う為替（3水準）
  - licensed_share: やり取りのうち Microsoft 365 Copilot ライセンス保持者が社内利用として行う割合（0〜1）。
    公式表では条件を満たす利用は「無料」扱いなので、その分はクレジットを消費しないものとして差し引く
  - enforcement_ratio: 前払い容量の何倍で強制措置（エージェント無効化）が掛かるか（公式: 125%）
  - patterns: 1件（1回のやり取り）あたりの機能の内訳。公式の課金例3つと、当メディアの仮定1つ
  - volumes: 月の対応件数
"""
import json
import math
import sys
from pathlib import Path


def credits_per_run(pattern, rates):
    total = 0.0
    for key, count in pattern.items():
        if key == "label":
            continue
        if key not in rates:
            raise SystemExit("unknown feature: " + key)
        if count < 0:
            raise SystemExit("negative count: " + key)
        total += rates[key] * count
    return total


def calc(inp):
    rates = inp["rates"]
    pack = inp["pack"]
    payg_usd = inp["payg"]["usd_per_credit"]
    fx_list = inp["fx"]["jpy_per_usd"]
    share = float(inp.get("licensed_share", 0.0))
    if not (0.0 <= share <= 1.0):
        raise SystemExit("licensed_share must be 0..1")
    rows = []
    for pkey, pat in inp["patterns"].items():
        per_run = credits_per_run(pat, rates)
        for vol in inp["volumes"]:
            credits = per_run * vol * (1.0 - share)
            packs = int(math.ceil(credits / pack["credits"])) if credits > 0 else 0
            pack_jpy = packs * pack["jpy_per_month"]
            payg_usd_total = credits * payg_usd
            payg_jpy = {str(fx): round(payg_usd_total * fx) for fx in fx_list}
            mid = fx_list[len(fx_list) // 2]
            cheaper = "payg" if payg_jpy[str(mid)] < pack_jpy else "pack"
            rows.append({
                "pattern": pkey, "label": pat["label"], "volume": vol,
                "credits_per_run": per_run, "credits_month": round(credits),
                "payg_usd": round(payg_usd_total, 2), "payg_jpy": payg_jpy,
                "packs": packs, "pack_jpy": pack_jpy,
                "pack_utilization_pct": (round(credits / (packs * pack["credits"]) * 100, 1) if packs else None),
                "enforcement_credits": (round(packs * pack["credits"] * inp["enforcement_ratio"]) if packs else None),
                "cheaper_at_mid_fx": cheaper,
            })
    breakeven = {str(fx): round(pack["jpy_per_month"] / (payg_usd * fx)) for fx in fx_list}
    return {"rows": rows, "breakeven_credits_per_pack": breakeven,
            "pack_jpy_per_credit": round(pack["jpy_per_month"] / pack["credits"], 4)}


def main():
    if len(sys.argv) < 2:
        raise SystemExit("usage: python calculator.py input.json [expected.json]")
    inp = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
    res = calc(inp)
    print(json.dumps(res, ensure_ascii=False, indent=1))
    if len(sys.argv) >= 3:
        exp = json.loads(Path(sys.argv[2]).read_text(encoding="utf-8"))
        if exp != res:
            print("MISMATCH with expected.json", file=sys.stderr)
            raise SystemExit(1)
        print("OK: matches expected.json", file=sys.stderr)


if __name__ == "__main__":
    main()
