#!/usr/bin/env python3
"""Пересобрать PR-срез сводного отчёта из реестра текстов и базы площадок.

Зачем. Раздел «Сводный отчёт» показывал состояние внешнего контура руками:
цифры вписывались в report.json один раз и отставали на несколько дней.
07.09.2026 панель говорила «две публикации на внешних площадках», когда их
было семь, а Smart-Lab, Audit-it и LiveJournal в таблице отсутствовали вовсе.

Источники правды:
    завод/реестр-текстов.json - сколько материалов вышло и на какой площадке
    завод/площадки.json       - заведён ли аккаунт, что с ним не так, dofollow

Что скрипт трогает: у каждой площадки поля «опубликовано», «статус»,
«ограничение», «ссылки», плюс KPI и строку состояния. Демо-поля (всё,
что с суффиксом _демо) не трогает: они нарисованные и показывают, как
раздел выглядит с данными за четыре месяца.

Порядок: после завод/реестр-текстов.py, до обновить-фолбэк.py.

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

import importlib.util
import json
import re
from collections import Counter
from pathlib import Path

КОРЕНЬ = Path(__file__).resolve().parent.parent.parent
РЕЕСТР = КОРЕНЬ / "завод" / "реестр-текстов.json"
ПЛОЩАДКИ = КОРЕНЬ / "завод" / "площадки.json"
ПРОСМОТРЫ_CSV = КОРЕНЬ / "завод" / "просмотры-публикаций.csv"
ОТЧЁТ = КОРЕНЬ / "панель" / "данные" / "report.json"
МЕТРИКА = КОРЕНЬ / "завод" / "метрика" / "история"


def _число(с):
    """Целое из ячейки таблицы: терпит пробелы, неразрывные пробелы, запятые."""
    return int(re.sub(r"[\s\u00a0,]", "", с))


def просмотры_публикаций():
    """Просмотры по публикациям из таблички `просмотры-публикаций.csv`.

    Одна строка = одна статья на площадке. Ключ - № статьи (как в панели).
    Возвращает {№: (просмотры, дата)}. Так вкладка «Статьи» получает число
    по каждой статье, а рейтинг площадок - сумму по площадке; многопубликационные
    площадки раскладываются корректно. Пустое поле - ещё не мерили. Числа площадки
    отдают только в своих кабинетах (API почти нет, Дзен - руками раз в неделю).
    """
    из_файла = {}
    if not ПРОСМОТРЫ_CSV.exists():
        return из_файла
    # файл заполняют руками, иногда через Excel: метка кодировки, кавычки,
    # запятая вместо точки с запятой. Разбор терпит это.
    for строка in ПРОСМОТРЫ_CSV.read_text(encoding="utf-8-sig").splitlines():
        строка = строка.strip()
        if not строка or строка.startswith("#"):
            continue
        разделитель = ";" if ";" in строка else ("\t" if "\t" in строка else ",")
        части = [ч.strip().strip('"').strip() for ч in строка.split(разделитель)]
        ключ = части[0]
        if not ключ.isdigit():
            continue  # шапка «№;…» или пустой ключ
        число = части[3] if len(части) > 3 else ""
        дата = части[4] if len(части) > 4 else ""
        if not число:
            # Числа нет. Если вместо даты стоит текст - это пометка о том,
            # что счётчика у площадки не существует («Работаем!» на Lemmy).
            # Она попадёт в клетку вместо прочерка: прочерк читается
            # как «забыли замерить», а тут мерить нечего.
            if дата and not дата[:1].isdigit():
                из_файла[int(ключ)] = (None, дата)
            continue
        try:
            из_файла[int(ключ)] = (_число(число), дата or None)
        except ValueError:
            print(f"  просмотры: не число у №{ключ}: {число!r}, пропускаю")
    return из_файла


# в реестре площадка пишется так, как её называет шапка материала,
# в базе - так, как она называет себя сама
СИНОНИМЫ = {"audit-it.ru": "Audit-it", "смарт-лаб": "Smart-Lab",
            "живой журнал": "LiveJournal", "вс.ру": "vc.ru"}

# В отчёт идут только площадки, где аккаунт заведён или работа начата.
# Отбор по началу статуса, а не по вхождению слова: «заведено» сидит
# подстрокой внутри «не заведено», и поиск по вхождению затаскивает
# в отчёт всю разведку из 47 строк вместо тринадцати рабочих.
НАЧАЛО_РАБОТЫ = ("работает", "заведено", "аккаунт", "регистрация начата",
                 "отложено", "отправлено")


def имя_базы(площадка):
    """Имя, под которым площадка лежит в базе.

    В шапке материала её подписывают с уточнением - «Sostav (SBlogs)»,
    «Работаем! (rabotaem.app)», - а в базе имя короткое. Без отбрасывания
    скобок 14.09.2026 карточка не находилась, и отчёт писал, что тип ссылок
    у этих площадок не проверен, хотя у «Работаем!» стоит dofollow.
    """
    площадка = re.sub(r"\s*\([^)]*\)\s*$", "", str(площадка or "")).strip()
    return СИНОНИМЫ.get(площадка.lower(), площадка)


def короткий_статус(строка):
    """Первое слово статуса из базы: «работает», «заведено», «отложено»."""
    низ = строка.lower()
    if низ.startswith("работает") or "опубликован" in низ[:40]:
        return "работает"
    if "не заведено" in низ:
        return "не заведено"
    if "отложено" in низ:
        return "отложено"
    return "заведено"


def причина(площадка):
    """Одна строка о том, что мешает: её читает оператор, а не машина.

    Из статуса срезается только начальное слово-метка («работает - …»):
    оно уже стоит в своей колонке. Резать по первому дефису нельзя,
    в статусах живут адреса публикаций, и smart-lab.ru рвётся пополам.
    """
    статус = (площадка.get("статус") or "").strip()
    хвост = re.sub(r"^(работает|заведено|отложено|отправлено|отклонено)\s*[-\u2013\u2014:]\s*",
                   "", статус, flags=re.IGNORECASE)
    хвост = хвост.strip(" .")
    if len(хвост) <= 200:
        return хвост
    # режем по границе слова: иначе обрывается адрес публикации,
    # а он в этой колонке единственная кликабельная зацепка
    return хвост[:200].rsplit(" ", 1)[0] + "..."


def ссылки(площадка):
    """Передаёт ли площадка вес.

    Три состояния, а не два: «не проверено» это честный ответ, а поставить
    там nofollow значит соврать в отчёте. Метку ставим только после ручной
    проверки на своей публикации, и она записана в поле «ссылки_seo».
    """
    # Метка живёт в своём поле «ссылки_вес» и ставится руками после замера.
    # Разбирать её из свободного текста ненадёжно: «внешние ссылки без nofollow»
    # (Работаем!, вес передаётся) и «Без nofollow только соцсети площадки»
    # (Workspace, вес не передаётся) содержат одну и ту же подстроку
    # и означают противоположное
    метка = (площадка.get("ссылки_вес") or "").strip().lower()
    if метка in ("dofollow", "nofollow", "ссылок нет", "не проверено"):
        return метка
    return "не проверено"


def кратко_заголовок(т, предел=54):
    т = re.sub(r"\s+", " ", str(т or "")).strip()
    return т[:предел] + ("…" if len(т) > предел else "")


# Слова, по которым узнаём материал, ожидающий решения чужой редакции.
# «Отклонён» сюда не попадает: там ждать уже нечего.
# Слова, которыми в статусе записано «материал у редакции, ответа нет».
# Формулировки пишутся руками и разнятся: 24.09.2026 Searchengines и Т-Бизнес
# секреты не попали в счёт, потому что у них стояло «ОТПРАВЛЕНО НА ПРОВЕРКУ»,
# а в списке такого не было. Держим все встречающиеся написания и ловим
# пропуски правилом в панель/проверка-панели.py.
ЖДЁМ = ("модерац", "отправлено в редакцию", "отправлено на проверку",
        "на проверке", "ждёт ответа", "ждём ответа", "лежит в ожидании",
        "ждем ответа")


def на_модерации():
    """Материалы, которые отправлены и ждут решения площадки.

    Руководитель 14.09.2026: в сводном отчёте видно только опубликованное,
    а тексты, лежащие у чужих редакций, не видны вовсе - хотя это тоже
    результат работы и по ним ждут ответа.
    """
    итог = []
    if not РЕЕСТР.exists():
        return итог
    р = json.loads(РЕЕСТР.read_text(encoding="utf-8"))
    for т in р["тексты"]:
        if т.get("вид") != "внешняя площадка":
            continue
        с = str(т.get("статус") or "").lower()
        if "отклон" in с or "опубликовано" in с:
            continue
        if any(сл in с for сл in ЖДЁМ):
            итог.append((имя_базы(т.get("площадка") or ""), т.get("заголовок") or "",
                         т.get("статус") or ""))
    return sorted(итог)


# Наши собственные источники: заход в кабинет Метрики и витрина, с которой мы
# сами открываем статьи. Переходом читателя это не является.
НЕ_ПЕРЕХОД = {"metrika.yandex.ru", "smm.prfabrika.ru", "webmaster.yandex.ru"}

# Домены, на которых живут и наши площадки, и чужие сервисы. Поддомены таких
# доменов площадке не засчитываем.
ОБЩИЕ_ДОМЕНЫ = {"yandex.ru", "vk.com", "vk.ru", "t.me", "dzen.ru"}


def без_www(домен):
    """Снять префикс www. Раньше стояло .lstrip(\"www.\"), а он режет символы,
    а не строку: workspace.ru превращался в orkspace.ru (22.09.2026)."""
    return домен[4:] if домен.startswith("www.") else домен


def переходы_по_площадкам(база):
    """{имя площадки: визитов с неё} из последнего снимка Метрики.

    Метрика отдаёт домен страницы, с которой человек пришёл; площадки в базе
    записаны адресами. Сопоставляем по домену, с учётом поддоменов: переход
    с news.pressfeed.ru засчитывается площадке pressfeed.ru.

    Заведено 15.09.2026: до этого колонка «Переходов на портал» в сводном
    отчёте была пустой у всех площадок - данные просто не забирались, хотя
    Метрика их считает.
    """
    снимки = sorted(МЕТРИКА.glob("*.json")) if МЕТРИКА.exists() else []
    if not снимки:
        return {}
    последний = json.loads(снимки[-1].read_text(encoding="utf-8"))
    визиты = {}
    for строка in последний.get("переходы_с_сайтов") or []:
        домен = без_www(str(строка.get("домен") or "").lower())
        if домен and домен not in НЕ_ПЕРЕХОД:
            визиты[домен] = визиты.get(домен, 0) + int(строка.get("визиты") or 0)
    if not визиты:
        return {}

    свои = {}
    for имя, карточка in база.items():
        адрес = str(карточка.get("url") or "")
        домен = без_www(re.sub(r"^https?://", "", адрес).split("/")[0].lower())
        if not домен or "frantlas" in домен:
            continue
        # Поддомены засчитываем только своим площадкам: news.pressfeed.ru это
        # Pressfeed. Но у «Яндекс Кью» в карточке стоит yandex.ru, и по суффиксу
        # ей доставались визиты с metrika.yandex.ru - то есть наши собственные
        # заходы в Метрику показывались переходом с площадки (22.09.2026).
        # Домены общего пользования сопоставляем только точно.
        точно = домен in ОБЩИЕ_ДОМЕНЫ
        сумма = sum(в for д, в in визиты.items()
                    if д == домен or (not точно and д.endswith("." + домен)))
        if сумма:
            свои[имя] = сумма
    return свои


def главная():
    # Общий модуль признаков грузим первым: и «вышло», и «есть аккаунт»
    # живут в нём, а нужны они в разных местах сборки.
    спец = importlib.util.spec_from_file_location(
        "таблица_площадок", КОРЕНЬ / "завод" / "таблица-площадок.py")
    таб = importlib.util.module_from_spec(спец)
    спец.loader.exec_module(таб)

    реестр = json.loads(РЕЕСТР.read_text(encoding="utf-8"))
    база = {п["имя"]: п for п in json.loads(ПЛОЩАДКИ.read_text(encoding="utf-8"))["площадки"]}
    отчёт = json.loads(ОТЧЁТ.read_text(encoding="utf-8"))
    pr = отчёт["pr"]

    # Просмотры теперь ведём по публикациям (№ статьи), а рейтинг площадок
    # собираем как сумму по площадке. Читаем статьи заранее: по ним же
    # № статьи связывается с её площадкой.
    статьи_путь = ОТЧЁТ.parent / "articles.json"
    статьи = json.loads(статьи_путь.read_text(encoding="utf-8")) if статьи_путь.exists() else {"строки": []}
    по_публикациям = просмотры_публикаций()          # {№: (просмотры, дата)}
    агрегат = {}                                       # {имя площадки: [сумма, дата]}
    for стр in статьи["строки"]:
        n = стр.get("№")
        if n not in по_публикациям:
            continue
        имя = имя_базы(стр.get("площадка") or "")
        в, дт = по_публикациям[n]
        if в is None:
            continue  # у площадки нет счётчика просмотров, суммировать нечего
        су = агрегат.setdefault(имя, [0, None])
        су[0] += в
        су[1] = max(су[1], дт) if (су[1] and дт) else (дт or су[1])

    # Признак «вышло» один на все скрипты и живёт в общем модуле: он
    # разбирает и регистр статуса, и явное поле «видно_читателю».
    вышло = Counter(имя_базы(т["площадка"]) for т in реестр["тексты"]
                    if таб.вышло_читателю(т))

    # Сколько вышедших материалов площадки ведут на портал. Поле берётся
    # из шапки текста (ссылки_на_портал) через реестр. Раньше «со ссылкой»
    # в сводном отчёте всегда показывал ноль: значение ставилось при создании
    # строки и больше никем не пересчитывалось, поэтому первая же публикация
    # со ссылкой - Smart-Lab 15.09.2026 - в отчёт не попала.
    со_ссылкой = Counter(имя_базы(т["площадка"]) for т in реестр["тексты"]
                         if таб.вышло_читателю(т) and т.get("ссылка_на_портал"))

    # Кого показываем: где есть публикации плюс где заведён аккаунт.
    # Признак берём из поля «аккаунт» базы площадок, а не из первого слова
    # статуса: по слову в таблицу попали VK Статьи и VK-сообщество, где
    # регистрации не было и решение отложено, а E-xecutive с заведённым
    # профилем и статьёй на модерации в таблицу не попал (17.09.2026).
    # Имена площадок приходят из двух мест: из реестра текстов - короткие
    # («HF»), из базы - с уточнением («HF (hf.ru)»). Без приведения к одному
    # виду 24.09.2026 в отчёте висели обе строки: одна с публикациями, вторая
    # пустая, «0 публикаций · - на статью».
    показываем = set(вышло)
    for имя, п in база.items():
        if имя == "Портал frantlas.ru":
            continue
        # Снятую площадку не показываем, даже если аккаунт на ней заведён:
        # Pressfeed отклонили 21.09.2026, а строка висела с нулём публикаций,
        # потому что аккаунт остался (24.09.2026).
        if таб.снята(п) and not вышло.get(имя_базы(имя)):
            continue
        if str(п.get("аккаунт") or "").startswith(("заведён", "общий")):
            показываем.add(имя_базы(имя))

    переходы = переходы_по_площадкам(база)
    строки = {с["площадка"]: с for с in pr["площадки"]}
    образец = {к: (0 if isinstance(v, int) else None) for к, v in pr["площадки"][0].items()}
    добавлено, изменено = [], []

    for имя in sorted(показываем, key=lambda и: (-вышло[и], и)):
        п = база.get(имя, {})
        строка = строки.get(имя)
        if строка is None:
            строка = dict(образец)
            строка.update({к: v for к, v in pr["площадки"][0].items() if к.endswith("_демо")})
            строка.update({"площадка": имя, "демо": False, "просмотры": None,
                           "со_ссылкой": 0})
            pr["площадки"].append(строка)
            строки[имя] = строка
            добавлено.append(имя)

        было = (строка.get("опубликовано"), строка.get("статус"))
        строка["опубликовано"] = вышло[имя]
        строка["со_ссылкой"] = со_ссылкой[имя]
        строка["статус"] = короткий_статус(п.get("статус", "заведено"))
        строка["автопостинг"] = ("есть, публикуем скриптом" if п.get("автопостинг")
                                 else строка.get("автопостинг") or "нет API, только руками")
        строка["ограничение"] = причина(п) or строка.get("ограничение")
        строка["ссылки"] = ссылки(п)
        # Просмотры отражают ровно табличку: вписано - ставим, нет - прочерк.
        # Убрал(а) строку из CSV - число уходит из панели, а не залипает.
        _агр = агрегат.get(имя)
        строка["просмотры"], строка["замерено"] = (_агр[0], _агр[1]) if _агр else (None, None)
        # Переходы на портал с этой площадки: из Метрики, по домену источника.
        # Ноль и «не считали» - разные вещи, поэтому площадка без единого
        # перехода получает 0, а не None: Метрика её просто не видела.
        строка["переходы"] = переходы.get(имя, 0) if переходы else None
        if было != (строка["опубликовано"], строка["статус"]) and имя not in добавлено:
            изменено.append(f'{имя}: {было[0]} → {строка["опубликовано"]}, {было[1]} → {строка["статус"]}')

    # Карточку «переходов на портал» руководитель снял 23.09.2026. Цифра была
    # верной, но бесполезной: Метрика видит переход, только если человек кликнул
    # по ссылке, а часть площадок (Smart-Lab, «Работаем!») закрывает ссылки
    # редиректом и источник теряется. Переходы по площадкам остались в таблице
    # ниже, где рядом видно, у кого вообще стоит ссылка.
    pr["kpi"] = [k for k in pr.get("kpi", [])
                 if "переход" not in str(k.get("метка") or "").lower()]

    # разведанные, но не заведённые площадки из отчёта убираем: они попадали
    # сюда при широком отборе и создавали ряд пустых строк
    лишние = [с["площадка"] for с in pr["площадки"]
              if not с.get("демо") and с["площадка"] not in показываем]
    pr["площадки"] = [с for с in pr["площадки"]
                      if с.get("демо") or с["площадка"] in показываем]
    pr["площадки"].sort(key=lambda с: (-(с.get("опубликовано") or 0), с["площадка"]))

    всего = sum(вышло.values())
    # Аккаунты считаем по полю «аккаунт» в базе площадок, а не по первому
    # слову статуса. Слово ошибалось в обе стороны (17.09.2026): засчитывало
    # VK Статьи и VK-сообщество, где стоит «отложено» и регистрации не было,
    # и пропускало E-xecutive со статусом «РАБОТАЕМ: профиль заведён» -
    # в отборе было «работает», но не «работаем». Журнал Pressfeed
    # открывается аккаунтом самого Pressfeed: две карточки, один вход.
    # Признак аккаунта считает одна общая функция из завод/таблица-площадок.py:
    # поле заполняют по-разному, и своя копия проверки здесь уже разъезжалась
    # с перечнем площадок (21.09.2026).
    # Отклонённые площадки в счёт аккаунтов не идут: работы там нет, а
    # аккаунт когда-то заводили. После снятия Pressfeed 21.09.2026 он остался
    # в списке заведённых, и счётчик вырос вместо того чтобы уменьшиться.
    с_аккаунтом = sorted(и for и, п in база.items()
                         if таб.есть_аккаунт({**п, "публикаций_факт": вышло.get(таб.имя_площадки(и), вышло.get(и, 0))})
                         and п.get("аккаунт") != "общий"
                         and not таб.снята(п))
    общие = sorted(и for и, п in база.items() if п.get("аккаунт") == "общий")
    аккаунтов = len(с_аккаунтом)
    # Коротко с 02.10.2026: список всех площадок под счётчиком растягивал
    # карточку на полэкрана, он есть на вкладке «Площадки».
    подпись_аккаунтов = "список - на вкладке «Площадки»" + (
        f"; {', '.join(общие)} - общий вход" if общие else "")
    вес = sorted(и for и in вышло if ссылки(база.get(и, {})) == "dofollow")
    ждут = на_модерации()
    # «Внешних ссылок» - сколько вышедших материалов ведут на портал. Раньше
    # показатель стоял нулём и не пересчитывался вовсе: первая публикация
    # со ссылкой (Smart-Lab, 15.09.2026) в отчёт так и не попала.
    ссылок_всего = sum(со_ссылкой.values())
    где_ссылки = ", ".join(f"{и}: {к}" for и, к in со_ссылкой.most_common() if к)
    топ_вышло = вышло.most_common()
    для_kpi = {"статей опубликовано": (всего, ", ".join(f"{и}: {к}" for и, к in топ_вышло[:5])
                                       + (f" и ещё {len(топ_вышло) - 5}" if len(топ_вышло) > 5 else "")),
               "аккаунтов заведено": (аккаунтов, подпись_аккаунтов),
               "внешних ссылок": (
                   ссылок_всего,
                   (где_ссылки + ". Первые 2-3 материала на площадке без ссылок")
                   if ссылок_всего else
                   "прогрев: первые 2-3 материала на площадке без ссылок"),
               "ждут ответа редакции": (
                   len(ждут),
                   ", ".join(sorted({пл for пл, _, _ in ждут})) or "ничего не ждём")}
    метки = {k["метка"] for k in pr["kpi"]}
    if "ждут ответа редакции" not in метки:
        # показатель заведён 14.09.2026: до этого отправленное в редакции
        # нигде не отображалось. Ставим рядом с опубликованным.
        место = next((i for i, k in enumerate(pr["kpi"])
                      if k["метка"] == "статей опубликовано"), -1) + 1
        pr["kpi"].insert(место, {"метка": "ждут ответа редакции", "значение": 0,
                                 "демо": False, "комментарий": ""})
    for k in pr["kpi"]:
        if k["метка"] in для_kpi:
            k["значение"], k["комментарий"] = для_kpi[k["метка"]]
        # Заголовки ждущих материалов на карточке не выводим (компактный вид,
        # 02.10.2026), но держим полным списком: по нему вечерняя проверка
        # ищет материалы, выпавшие из отчёта.
        if k["метка"] == "ждут ответа редакции":
            k["список"] = [f"{пл} - {заг}" for пл, заг, _ in ждут]

    # текст пустого рейтинга тоже с цифрой: стояло «две публикации есть»,
    # когда их было семь
    pr["рейтинг_пусто"] = (
        f"Рейтинг строится по просмотрам, а счётчиков на площадках у нас нет: "
        f"публикаций {всего}, но цифры по ним площадки отдают только в своём "
        f"интерфейсе. Порядок площадок в списке ниже задан числом публикаций, "
        f"а не результатом.")
    # про «остальных» пишем раздельно: закрытая ссылка и незамеренная это
    # разные вещи, и сваливать их в одну фразу значит выдавать догадку за замер
    закрыто = sorted(и for и in вышло if ссылки(база.get(и, {})) == "nofollow")
    без_замера = sorted(и for и in вышло if ссылки(база.get(и, {})) == "не проверено")
    ждёт_фраза = f" Ждут ответа редакций: {len(ждут)}." if ждут else ""
    # Коротко с 02.10.2026: имена площадок по типу ссылки есть на вкладке
    # «Площадки», здесь только счёт.
    хвост_кратко = []
    if вес:
        хвост_кратко.append(f"вес передают: {', '.join(вес)}")
    if закрыто:
        хвост_кратко.append(f"nofollow: {len(закрыто)}")
    if без_замера:
        хвост_кратко.append(f"ссылка не проверена: {len(без_замера)}")
    pr["состояние"] = (f"Публикаций: {всего} на {len(вышло)} площадках. "
                       + (("; ".join(хвост_кратко) + ".")[:1].upper() + ("; ".join(хвост_кратко) + ".")[1:] if хвост_кратко
                          else "Площадок с dofollow пока нет.")
                       + ждёт_фраза)
    ОТЧЁТ.write_text(json.dumps(отчёт, ensure_ascii=False, indent=1), encoding="utf-8")

    # Просмотры по публикациям - прямо в строки вкладки «Статьи»: у каждой
    # внешней статьи своё число из таблички. Внешние строки без числа сбрасываем
    # (не залипают), свой счётчик портала не трогаем.
    проставлено = 0
    for стр in статьи["строки"]:
        пл_raw = стр.get("площадка") or ""
        if "портал" in пл_raw.lower() or "frantlas" in пл_raw.lower():
            continue  # портал ведёт свой счётчик, он проставляется отдельно
        данные_публ = по_публикациям.get(стр.get("№"))
        стр.pop("просмотры_нота", None)
        стр.pop("отклик", None)          # прежняя колонка «Голоса / комм.», убрана 14.09.2026
        if данные_публ and данные_публ[0] is not None:
            стр["просмотры"] = данные_публ[0]
            проставлено += 1
        elif данные_публ:
            # числа нет и не будет: в клетке стоит пометка вместо прочерка
            стр["просмотры"] = None
            стр["просмотры_нота"] = данные_публ[1]
        else:
            стр["просмотры"] = None
    if статьи_путь.exists():
        статьи_путь.write_text(json.dumps(статьи, ensure_ascii=False, indent=1), encoding="utf-8")
        print(f"просмотры проставлены статьям: {проставлено}")

    print(f"публикаций: {всего}, площадок в отчёте: {аккаунтов}")
    for и in добавлено:
        print("  добавлена:", и)
    for и in лишние:
        print("  убрана:", и)
    for с in изменено:
        print("  ", с)


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