Source code for package_maximizer.analyzers.result_analyzer

"""
Result Analyzer - Анализатор результатов максимизации пакетов.
"""

from __future__ import annotations

import logging
from typing import TYPE_CHECKING, Any

from ..core.interfaces import ResultAnalyzer as ResultAnalyzerInterface

if TYPE_CHECKING:
    pass

logger = logging.getLogger(__name__)


[docs] class ResultAnalyzer(ResultAnalyzerInterface): """ Анализатор результатов максимизации пакетов. Предоставляет методы для анализа: - Сравнение установленных и предложенных пакетов - Статистика изменений - Выявление конфликтов и зависимостей """
[docs] def __init__(self) -> None: """ Инициализация анализатора. """ pass
[docs] def analyze( self, installed: list[str] | None, proposed: list[str] | None ) -> dict[str, Any]: """ Проанализировать результаты максимизации. Args: installed: Список установленных пакетов proposed: Список предложенных пакетов Returns: Словарь с результатами анализа """ result = { "summary": {}, "changes": {}, "statistics": {}, } # Преобразуем в множества для удобства installed_set = set(installed or []) proposed_set = set(proposed or []) # Статистика result["statistics"]["installed_count"] = len(installed_set) result["statistics"]["proposed_count"] = len(proposed_set) # Изменения to_install = proposed_set - installed_set to_remove = installed_set - proposed_set unchanged = installed_set & proposed_set result["changes"]["to_install"] = sorted(list(to_install)) result["changes"]["to_remove"] = sorted(list(to_remove)) result["changes"]["unchanged"] = sorted(list(unchanged)) result["changes"]["to_install_count"] = len(to_install) result["changes"]["to_remove_count"] = len(to_remove) result["changes"]["unchanged_count"] = len(unchanged) # Сводка result["summary"]["total_changes"] = len(to_install) + len(to_remove) result["summary"]["net_change"] = len(to_install) - len(to_remove) result["summary"]["change_percentage"] = ( (len(to_install) + len(to_remove)) / len(installed_set) * 100 if installed_set else 0.0 ) # Категоризация изменений result["summary"]["category"] = self._categorize_changes( len(to_install), len(to_remove), len(installed_set) ) return result
def _categorize_changes( self, to_install: int, to_remove: int, total_installed: int ) -> str: """ Категоризировать изменения по уровню. Args: to_install: Количество пакетов для установки to_remove: Количество пакетов для удаления total_installed: Общее количество установленных пакетов Returns: Категория изменений """ if total_installed == 0: return "fresh_install" if to_install > 0 else "empty" total_changes = to_install + to_remove percentage = (total_changes / total_installed) * 100 if percentage == 0: return "no_changes" elif percentage < 5: return "minor" elif percentage < 20: return "moderate" elif percentage < 50: return "significant" else: return "major"
[docs] def get_compatibility_matrix( self, proposed: list[str], conflict_graph: dict[str, list[str]] ) -> dict[str, Any]: """ Построить матрицу совместимости для предложенных пакетов. Args: proposed: Список предложенных пакетов conflict_graph: Граф конфликтов (пакет -> список конфликтующих) Returns: Матрица совместимости и статистика """ matrix = {} statistics = { "compatible_pairs": 0, "incompatible_pairs": 0, "total_pairs": 0, } # Создаем матрицу for pkg1 in proposed: matrix[pkg1] = {} for pkg2 in proposed: if pkg1 == pkg2: matrix[pkg1][pkg2] = True continue # Проверяем конфликты has_conflict = pkg2 in conflict_graph.get( pkg1, [] ) or pkg1 in conflict_graph.get(pkg2, []) compatible = not has_conflict matrix[pkg1][pkg2] = compatible if compatible: statistics["compatible_pairs"] += 1 else: statistics["incompatible_pairs"] += 1 statistics["total_pairs"] += 1 # Вычисляем процент совместимости statistics["compatibility_percentage"] = ( (statistics["compatible_pairs"] / statistics["total_pairs"]) * 100 if statistics["total_pairs"] > 0 else 100.0 ) return {"matrix": matrix, "statistics": statistics}
[docs] def get_dependency_analysis( self, proposed: list[str], dependency_graph: dict[str, list[str]] ) -> dict[str, Any]: """ Проанализировать зависимости предложенных пакетов. Args: proposed: Список предложенных пакетов dependency_graph: Граф зависимостей (пакет -> список зависимостей) Returns: Анализ зависимостей """ analysis = { "total_dependencies": 0, "satisfied_dependencies": 0, "unsatisfied_dependencies": 0, "dependency_chain": {}, "circular_dependencies": [], } proposed_set = set(proposed) # Подсчет зависимостей for pkg in proposed: deps = dependency_graph.get(pkg, []) analysis["total_dependencies"] += len(deps) for dep in deps: if dep in proposed_set: analysis["satisfied_dependencies"] += 1 else: analysis["unsatisfied_dependencies"] += 1 # Вычисление процента if analysis["total_dependencies"] > 0: analysis["satisfaction_percentage"] = ( analysis["satisfied_dependencies"] / analysis["total_dependencies"] * 100 ) else: analysis["satisfaction_percentage"] = 100.0 return analysis
[docs] def compare_solvers( self, results: dict[str, list[str]], reference: list[str] | None = None ) -> dict[str, Any]: """ Сравнить результаты разных солверов. Args: results: Словарь {имя_солвера: список_пакетов} reference: Опциональный эталонный список для сравнения Returns: Сравнительный анализ """ comparison: dict[str, Any] = { "solvers": list(results.keys()), "common_packages": [], "unique_packages": {}, "statistics": {}, } # Преобразуем в множества solver_sets = {name: set(pkgs) for name, pkgs in results.items()} # Общие пакеты if solver_sets: common = set.intersection(*solver_sets.values()) comparison["common_packages"] = sorted(list(common)) # Уникальные пакеты для каждого солвера for name, pkg_set in solver_sets.items(): others = set.union(*[s for n, s in solver_sets.items() if n != name]) unique = pkg_set - others comparison["unique_packages"][name] = sorted(list(unique)) # Статистика comparison["statistics"]["num_solvers"] = len(solver_sets) comparison["statistics"]["common_count"] = len(common) if common else 0 # Сравнение с эталоном if reference: reference_set = set(reference) comparison["reference_comparison"] = {} for name, pkg_set in solver_sets.items(): matches = len(pkg_set & reference_set) precision = matches / len(pkg_set) if pkg_set else 0 recall = matches / len(reference_set) if reference_set else 0 comparison["reference_comparison"][name] = { "matches": matches, "precision": precision * 100, "recall": recall * 100, "f1_score": ( 2 * (precision * recall) / (precision + recall) * 100 if (precision + recall) > 0 else 0 ), } return comparison