#!/usr/bin/env python3
"""Разрезы расхода на платные API: по процессам, по дням, последние вызовы.

Сделано по образцу PR Factory (/costs, сверено 17.09.2026). Там на каждый
вызов модели пишется строка, и из неё собираются три разреза. У нас до этого
в панели лежали только итоги по сервису: «OpenRouter 138 запросов, 23 рубля».
На вопрос «во что обошёлся конкретный прогон» ответить было нечем.

Источник - завод/расход/журнал.csv, одна строка = один прогон или вызов.
Курс для пересчёта долларов в рубли лежит в этом же скрипте и подписан датой:
выдумывать курс на день списания панель не должна, но и молчать про рубли
нельзя - руководитель считает бюджет в рублях.

Запуск: python3 панель/данные/обновить-расход.py [--сухой]
"""

import json
import sys
from collections import defaultdict
from pathlib import Path

ДАННЫЕ = Path(__file__).resolve().parent
КОРЕНЬ = ДАННЫЕ.parent.parent
ЖУРНАЛ = КОРЕНЬ / "завод" / "расход" / "журнал.csv"
РАСХОД = ДАННЫЕ / "apicost.json"
КУРС = 92.0            # рублей за доллар, сверен 17.09.2026
ПОСЛЕДНИХ = 20

ИМЕНА = {
    "geo-замер": "Замер ИИ-цитируемости",
    "частотность-ядра": "Замер частотности ядра",
    "позиции-площадок": "Позиции публикаций на площадках",
    "интент-по-выдаче": "Проверка интента по выдаче",
}


def строки_журнала():
    если = []
    if not ЖУРНАЛ.exists():
        return если
    for строка in ЖУРНАЛ.read_text(encoding="utf-8-sig").splitlines():
        строка = строка.strip()
        if not строка or строка.startswith("#") or строка.startswith("когда;"):
            continue
        ч = (строка.split(";") + [""] * 10)[:10]
        когда, процесс, сервис, модель, вызовов, вход, выход, стоимость, валюта, нота = ч
        число = lambda з: int(з) if з.strip().isdigit() else None
        деньги = None
        if стоимость.strip():
            try:
                деньги = float(стоимость.replace(",", "."))
            except ValueError:
                деньги = None
        в_рублях = None
        if деньги is not None:
            в_рублях = деньги * КУРС if валюта.strip().upper() == "USD" else деньги
        если.append({
            "когда": когда.strip(), "дата": когда.strip()[:10],
            "процесс": процесс.strip(), "имя_процесса": ИМЕНА.get(процесс.strip(), процесс.strip()),
            "сервис": сервис.strip(), "модель": модель.strip(),
            "вызовов": число(вызовов) or 0,
            "вход": число(вход), "выход": число(выход),
            "стоимость": деньги, "валюта": валюта.strip().upper() or "RUB",
            "рублей": round(в_рублях, 2) if в_рублях is not None else None,
            "нота": нота.strip(),
        })
    если.sort(key=lambda с: с["когда"], reverse=True)
    return если


def свод(строки, ключ, имя_поля):
    группы = defaultdict(lambda: {"вызовов": 0, "вход": 0, "выход": 0, "рублей": 0.0,
                                  "без_цены": 0})
    for с in строки:
        г = группы[с[ключ]]
        г["вызовов"] += с["вызовов"]
        г["вход"] += с["вход"] or 0
        г["выход"] += с["выход"] or 0
        if с["рублей"] is None:
            г["без_цены"] += 1
        else:
            г["рублей"] += с["рублей"]
    итог = []
    for имя, г in группы.items():
        итог.append({
            имя_поля: имя,
            "вызовов": г["вызовов"],
            "вход": г["вход"] or None,
            "выход": г["выход"] or None,
            "рублей": round(г["рублей"], 2),
            "на_вызов": round(г["рублей"] / г["вызовов"], 3) if г["вызовов"] else None,
            "без_цены": г["без_цены"],
        })
    итог.sort(key=lambda г: -г["рублей"])
    return итог


def главная():
    сухой = "--сухой" in sys.argv
    строки = строки_журнала()
    д = json.loads(РАСХОД.read_text(encoding="utf-8"))

    всего_рублей = round(sum(с["рублей"] or 0 for с in строки), 2)
    без_цены = sum(1 for с in строки if с["рублей"] is None)
    разрезы = {
        "по_процессам": свод(строки, "имя_процесса", "процесс"),
        "по_дням": sorted(свод(строки, "дата", "дата"), key=lambda г: г["дата"], reverse=True),
        "по_сервисам": свод(строки, "сервис", "сервис"),
        "последние": строки[:ПОСЛЕДНИХ],
        "итоги": {
            "рублей": всего_рублей,
            "вызовов": sum(с["вызовов"] for с in строки),
            "прогонов": len(строки),
            "без_цены": без_цены,
            "курс": КУРС,
            "первый": строки[-1]["дата"] if строки else None,
            "последний": строки[0]["дата"] if строки else None,
        },
        "оговорка": (
            f"Доллары пересчитаны по курсу {КУРС:.0f} ₽, он вписан в скрипт и сверен "
            "17.09.2026: курс на день списания панель не знает, поэтому цифра в рублях - "
            "оценка, а не выписка. "
            + (f"У {без_цены} строк цены нет: прогон был, а сумма по нему не сохранилась. "
               "Такие строки в итог не идут и помечены прочерком."
               if без_цены else "Цена известна по всем строкам.")),
        "источник": "завод/расход/журнал.csv",
    }
    д["разрезы"] = разрезы

    # Итоги по сервису в старой таблице тоже пересобираем из журнала:
    # раньше они стояли руками и разъезжались с реальностью.
    по_сервисам = {г["сервис"]: г for г in разрезы["по_сервисам"]}
    for с in д["строки"]:
        г = по_сервисам.get(с["сервис"])
        if г:
            с["запросов"] = г["вызовов"]
            с["расход"] = round(г["рублей"])

    if сухой:
        print("сухой прогон:", json.dumps(разрезы["итоги"], ensure_ascii=False))
        return 0
    РАСХОД.write_text(json.dumps(д, ensure_ascii=False, indent=1) + "\n", encoding="utf-8")
    и = разрезы["итоги"]
    print(f"строк журнала: {и['прогонов']}, вызовов: {и['вызовов']}, "
          f"потрачено: {и['рублей']} ₽, без цены: {и['без_цены']}")
    for г in разрезы["по_процессам"]:
        print(f"  {г['процесс']:<34} {г['вызовов']:>4} вызовов  {г['рублей']:>8.2f} ₽")
    return 0


if __name__ == "__main__":
    sys.exit(главная())
