from __future__ import annotations import argparse import json from pathlib import Path from typing import Any import yaml ROOT = Path(__file__).resolve().parents[1] DEFAULT_START_LINKS = ROOT / "plugins" / "1c" / "rag" / "official-docs" / "start-links.json" DEFAULT_SOURCES = ROOT / "plugins" / "1c" / "rag" / "official-docs" / "sources.yaml" DEFAULT_OUTPUT = ROOT / "reports" / "1c-official-docs-start-coverage.json" def load_json(path: Path) -> dict[str, Any]: return json.loads(path.read_text(encoding="utf-8-sig")) if path.exists() else {} def load_yaml(path: Path) -> dict[str, Any]: data = yaml.safe_load(path.read_text(encoding="utf-8")) return data if isinstance(data, dict) else {} def check_coverage(start_links_path: Path, sources_path: Path) -> dict[str, Any]: start_links = load_json(start_links_path) sources_config = load_yaml(sources_path) source_urls = {str(item.get("url") or "") for item in sources_config.get("sources") or []} candidates = start_links.get("start_links") or [] rows = [] by_category: dict[str, dict[str, int]] = {} for item in candidates: category = str(item.get("category") or "unknown") active = str(item.get("url") or "") in source_urls by_category.setdefault(category, {"candidates": 0, "active": 0, "inactive": 0}) by_category[category]["candidates"] += 1 by_category[category]["active" if active else "inactive"] += 1 rows.append({**item, "active": active}) inactive = [item for item in rows if not item["active"]] counts = { "sources": len(source_urls), "candidates": len(rows), "active_candidates": sum(1 for item in rows if item["active"]), "inactive_candidates": len(inactive), } findings = [] required_categories = { "dev_section", "dev_section_index", "developer_glossary", "development_standards", "methodical_support", "platform_doc", } for category in sorted(required_categories): stats = by_category.get(category) or {} if stats.get("active", 0) == 0: findings.append({"severity": "error", "message": f"no active source for required category {category}"}) return { "schema": "onec_its_start_link_coverage.v1", "passed": not any(item["severity"] == "error" for item in findings), "start_links": str(start_links_path), "sources": str(sources_path), "counts": counts, "by_category": dict(sorted(by_category.items())), "findings": findings, "inactive_samples": inactive[:50], } def main() -> int: parser = argparse.ArgumentParser(description="Compare discovered 1C:ITS start links with active sources.yaml seeds.") parser.add_argument("--start-links", type=Path, default=DEFAULT_START_LINKS) parser.add_argument("--sources", type=Path, default=DEFAULT_SOURCES) parser.add_argument("--output", type=Path, default=DEFAULT_OUTPUT) parser.add_argument("--print", action="store_true", dest="print_report") args = parser.parse_args() report = check_coverage(args.start_links, args.sources) args.output.parent.mkdir(parents=True, exist_ok=True) args.output.write_text(json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") if args.print_report: print(json.dumps(report, ensure_ascii=False, indent=2)) return 0 if report["passed"] else 1 if __name__ == "__main__": raise SystemExit(main())