""" arma_modlist_tools.reporter ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Build and persist a report of mods that are required by the modlists but absent from the file server. The report includes a ``group`` field per missing mod so downstream tools (``sync_missing.py``) know exactly where to place it when it becomes available on the server, without needing to re-read ``comparison.json``. Typical usage:: from arma_modlist_tools.reporter import build_missing_report, save_missing_report report = build_missing_report(comparison, server_index) save_missing_report(report, cfg.missing_report) """ from __future__ import annotations import json from datetime import datetime, timezone from pathlib import Path def build_missing_report(comparison: dict, server_index: dict) -> dict: """ Cross-reference every mod in *comparison* against *server_index* and return a report of mods that are not on the server. :param comparison: Dict as returned by :func:`~arma_modlist_tools.compare.compare_presets`. :param server_index: Dict as returned by :func:`~arma_modlist_tools.fetcher.build_server_index`. :returns: Report dict:: { "generated_at": "2026-04-07T12:00:00+00:00", "total_mods": 80, "on_server": 2, "missing": 78, "missing_mods": [ { "name": "CBA_A3", "steam_id": "450814997", "url": "https://steamcommunity.com/...", "group": "shared" }, ... ] } """ by_steam_id: dict = server_index.get("by_steam_id", {}) by_name: dict = server_index.get("by_name", {}) from .fetcher import _normalize_name # reuse existing helper def _on_server(mod: dict) -> bool: if mod.get("steam_id") and mod["steam_id"] in by_steam_id: return True return _normalize_name(mod.get("name", "")) in by_name # Flatten all mods with their group label all_mods: list[tuple[dict, str]] = [] for mod in comparison["shared"]["mods"]: all_mods.append((mod, "shared")) for preset_name, data in comparison["unique"].items(): for mod in data["mods"]: all_mods.append((mod, preset_name)) missing_mods = [] on_server_count = 0 for mod, group in all_mods: if _on_server(mod): on_server_count += 1 else: missing_mods.append({ "name": mod["name"], "steam_id": mod.get("steam_id"), "url": mod.get("url"), "group": group, }) return { "generated_at": datetime.now(timezone.utc).isoformat(), "total_mods": len(all_mods), "on_server": on_server_count, "missing": len(missing_mods), "missing_mods": missing_mods, } def save_missing_report(report: dict, path: Path) -> None: """Write *report* as indented JSON to *path*, creating parent dirs as needed.""" path.parent.mkdir(parents=True, exist_ok=True) path.write_text(json.dumps(report, indent=2, ensure_ascii=False), encoding="utf-8")