#!/usr/bin/env python3
"""Два разреза позиций для операторского экрана: от статьи и от ключа.

Устройство снято с эталона PR Factory (MyBiUSTY, /seo-positions, сверено
17.09.2026). Там экран оператора, а не клиента: заказчик видит только сводный
отчёт, поэтому здесь можно держать все состояния, включая пустые и неудобные.

Сверху разрез «от статьи»: пара «статья + ключ» с позицией, разложенная по
состояниям - стоим в выдаче, замер не удался, проверено и не нашли, не мерили
ни разу. Главное правило эталона, которое мы повторяем: «проверено и не нашли»
и «не мерили» - разные вещи, и валить их в один ноль нельзя.

Снизу разрез «от ключа»: по каким кластерам ядра мы вообще где-то стоим,
и какие кластеры не закрыты ни одной статьёй.

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

import json
import sys
from pathlib import Path

ДАННЫЕ = Path(__file__).resolve().parent
КОРЕНЬ = ДАННЫЕ.parent.parent
ПОЗИЦИИ = ДАННЫЕ / "positions.json"
ЯДРО = ДАННЫЕ / "semantics.json"
РЕЕСТР = КОРЕНЬ / "завод" / "реестр-текстов.json"
ЗАПРОСЫ = КОРЕНЬ / "семантика" / "запросы-площадок.json"


def имя_базы(площадка):
    return (площадка or "").split("(")[0].strip()


def пары_от_статьи(строки, реестр, запланированные):
    """Пара «страница + запрос» с её состоянием."""
    итог = []
    for с in строки:
        if с.get("демо"):
            continue
        адрес = с.get("страница") or ""
        если_адрес = adres_ok = bool(адрес) and адрес != "—"
        # Разрез «от статьи» держит только пары, у которых страница известна.
        # Выгрузка Вебмастера отдаёт запросы без страницы, и в этом разрезе
        # им не место: они целиком уходят в разрез «от ключа» (17.09.2026).
        if not если_адрес:
            continue
        состояние = ("сбой" if с.get("сбой")
                     else "в выдаче" if с.get("позиция")
                     else "не нашли")
        итог.append({
            "запрос": с.get("запрос"),
            "позиция": с.get("позиция"),
            "страница": адрес if если_адрес else None,
            "заголовок": реестр.get(адрес, {}).get("заголовок") or "",
            "площадка": с.get("площадка") or ("портал" if с.get("контур") == "SEO" else ""),
            "контур": с.get("контур"),
            "глубина": 100 if с.get("контур") == "PR" else None,
            "проверено": с.get("замер"),
            "состояние": состояние,
        })
    # Публикации, по которым замера не было вовсе: их нет в positions.json,
    # но они есть в списке запросов площадок. Ноль им ставить нельзя.
    известные = {(п["страница"], п["запрос"]) for п in итог if п["страница"]}
    for адрес, данные in запланированные.items():
        for запрос in данные["запросы"]:
            if (адрес, запрос) in известные:
                continue
            итог.append({
                "запрос": запрос, "позиция": None, "страница": адрес,
                "заголовок": реестр.get(адрес, {}).get("заголовок") or данные.get("имя") or "",
                "площадка": данные.get("площадка") or "", "контур": "PR",
                "глубина": None, "проверено": None, "состояние": "не мерили",
            })
    порядок = {"в выдаче": 0, "сбой": 1, "не нашли": 2, "не мерили": 3}
    итог.sort(key=lambda п: (порядок[п["состояние"]], п["позиция"] or 999))
    return итог


def разрез_по_ядру(ядро, строки):
    """Кластеры ядра: сколько ключей, по скольким стоим, лучшая позиция."""
    нашлись = {}
    for с in строки:
        if с.get("демо") or not с.get("позиция"):
            continue
        имя = (с.get("запрос") or "").strip().lower()
        если = нашлись.get(имя)
        if если is None or с["позиция"] < если:
            нашлись[имя] = с["позиция"]

    по_категориям, без_статьи = {}, []
    for к in ядро.get("строки", []):
        фразы = [(ф.get("фраза") or "").strip().lower() for ф in (к.get("фразы") or [])]
        свои = [нашлись[ф] for ф in фразы if ф in нашлись]
        имя = к.get("категория") or "Без категории"
        гр = по_категориям.setdefault(имя, {"категория": имя, "ключей": 0,
                                            "в_выдаче": 0, "лучшая": None})
        гр["ключей"] += len(фразы)
        гр["в_выдаче"] += len(свои)
        if свои:
            лучшая = min(свои)
            гр["лучшая"] = лучшая if гр["лучшая"] is None else min(гр["лучшая"], лучшая)
        есть_страница = (к.get("страница") or "не создана") != "не создана"
        if not есть_страница:
            без_статьи.append({"код": к.get("код"), "кластер": к.get("кластер"),
                               "категория": имя, "показы": к.get("показы"),
                               "волна": к.get("волна")})
    строки_ядра = sorted(по_категориям.values(), key=lambda г: -г["ключей"])
    без_статьи.sort(key=lambda к: -(к["показы"] or 0))
    return строки_ядра, без_статьи


def главная():
    сухой = "--сухой" in sys.argv
    д = json.loads(ПОЗИЦИИ.read_text(encoding="utf-8"))
    ядро = json.loads(ЯДРО.read_text(encoding="utf-8")) if ЯДРО.exists() else {}

    реестр = {}
    if РЕЕСТР.exists():
        for т in json.loads(РЕЕСТР.read_text(encoding="utf-8"))["тексты"]:
            if т.get("ссылка"):
                реестр[т["ссылка"]] = {"заголовок": т.get("заголовок") or ""}

    запланированные = {}
    if ЗАПРОСЫ.exists():
        з = json.loads(ЗАПРОСЫ.read_text(encoding="utf-8"))
        список = з.get("публикации") if isinstance(з, dict) else з
        for п in (список or []):
            адрес = п.get("адрес") or п.get("ссылка")
            if адрес:
                запланированные[адрес] = {"запросы": п.get("запросы") or [],
                                          "площадка": имя_базы(п.get("площадка")),
                                          "имя": п.get("заголовок") or ""}

    пары = пары_от_статьи(д.get("строки", []), реестр, запланированные)
    по_ядру, без_статьи = разрез_по_ядру(ядро, д.get("строки", []))

    состояния = {}
    for п in пары:
        состояния[п["состояние"]] = состояния.get(п["состояние"], 0) + 1
    страниц_всего = len({п["страница"] for п in пары if п["страница"]})
    страниц_в_выдаче = len({п["страница"] for п in пары
                            if п["страница"] and п["состояние"] == "в выдаче"})
    ключей_ядра = sum(г["ключей"] for г in по_ядру)
    ключей_в_выдаче = sum(г["в_выдаче"] for г in по_ядру)

    д["разрезы"] = {
        "пары": пары,
        "по_ядру": по_ядру,
        "без_статьи": без_статьи,
        "итоги": {
            "страниц_всего": страниц_всего,
            "страниц_в_выдаче": страниц_в_выдаче,
            "пар_всего": len(пары),
            "в_топ10": sum(1 for п in пары if п["позиция"] and п["позиция"] <= 10),
            "ключей_ядра": ключей_ядра,
            "ключей_в_выдаче": ключей_в_выдаче,
            "кластеров_без_статьи": len(без_статьи),
            "состояния": состояния,
            "последний_замер": max((п["проверено"] for п in пары if п["проверено"]),
                                   default=None),
        },
    }
    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"страниц в выдаче {и['страниц_в_выдаче']} из {и['страниц_всего']}, "
          f"ключей ядра в выдаче {и['ключей_в_выдаче']} из {и['ключей_ядра']}, "
          f"кластеров без статьи {и['кластеров_без_статьи']}")
    return 0


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