arch-researcher/json_reporter.py

107 lines
4.1 KiB
Python
Raw Permalink Normal View History

2026-04-27 18:44:22 +00:00
"""
JSON Reporter машинно-читаемый отчёт в формате JSON.
Подходит для интеграции с CI/CD конвейерами и внешними инструментами.
"""
import json
import logging
from dataclasses import asdict, is_dataclass
from datetime import datetime
from pathlib import Path
from typing import Any
from models import AnalysisReport, ModernizationStrategy, RiskLevel
logger = logging.getLogger(__name__)
def _serialize(obj: Any) -> Any:
"""Рекурсивный сериализатор для dataclasses, Enum и set."""
if is_dataclass(obj) and not isinstance(obj, type):
return {k: _serialize(v) for k, v in asdict(obj).items()}
if isinstance(obj, (RiskLevel, ModernizationStrategy)):
return obj.name
if isinstance(obj, set):
return sorted(obj)
if isinstance(obj, list):
return [_serialize(i) for i in obj]
if isinstance(obj, dict):
return {k: _serialize(v) for k, v in obj.items()}
return obj
class JsonReporter:
"""Формирует JSON-отчёт по результатам анализа."""
def write(self, report: AnalysisReport, output_dir: str) -> str:
"""
Записывает отчёт в файл report.json внутри output_dir.
:returns: путь к созданному файлу
"""
out = Path(output_dir)
out.mkdir(parents=True, exist_ok=True)
file_path = out / "report.json"
payload = {
"generated_at": datetime.now().isoformat(),
"project_root": report.project_root,
"summary": {
"total_files": report.total_files,
"total_nloc": report.total_nloc,
"total_functions": report.total_functions,
"avg_system_ccn": round(report.avg_system_ccn, 2),
"system_risk_level": report.system_risk_level.name,
"recommended_system_strategy": report.recommended_system_strategy.name,
"dependency_cycles_count": len(report.dependency_cycles),
},
"dependency_cycles": report.dependency_cycles,
"decisions": [
{
"module": d.module_name,
"risk_level": d.risk_level.name,
"strategy": d.strategy.name,
"priority": d.priority,
"reasons": d.reasons,
}
for d in report.decisions
],
"modules": {
name: {
"files": mod.files,
"language": mod.language,
"total_nloc": mod.total_nloc,
"total_functions": mod.total_functions,
"avg_ccn": round(mod.avg_ccn, 2),
"max_ccn": mod.max_ccn,
"heavy_functions_count": mod.heavy_functions_count,
"heavy_functions_ratio": round(mod.heavy_functions_ratio, 3),
"coupling_out": mod.coupling_out,
"coupling_in": mod.coupling_in,
"instability": round(mod.instability, 3),
"has_cycles": mod.has_cycles,
"dependencies": sorted(mod.dependencies),
"dependents": sorted(mod.dependents),
"functions": [
{
"name": f.name,
"file": f.file,
"line": f.line,
"ccn": f.ccn,
"nloc": f.nloc,
"params": f.params,
}
for f in sorted(mod.functions, key=lambda x: x.ccn, reverse=True)
],
}
for name, mod in report.modules.items()
},
}
with open(file_path, "w", encoding="utf-8") as fh:
json.dump(payload, fh, ensure_ascii=False, indent=2)
logger.info("JSON отчёт записан: %s", file_path)
return str(file_path)