arch-researcher/models.py
2026-04-27 23:44:22 +05:00

121 lines
4.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Доменные модели инструментария.
Все типы данных, которыми обмениваются модули системы.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from enum import Enum, auto
from pathlib import Path
from typing import Dict, List, Optional, Set
# ---------------------------------------------------------------------------
# Метрики
# ---------------------------------------------------------------------------
@dataclass
class FunctionMetrics:
"""Метрики одной функции / метода."""
name: str
file: str
line: int
ccn: int # Цикломатическая сложность (McCabe)
nloc: int # Строк кода без комментариев
params: int # Число параметров
language: str # "cpp" | "js"
@property
def is_complex(self) -> bool:
return self.ccn > 10
@property
def is_large(self) -> bool:
return self.nloc > 50
@dataclass
class ModuleMetrics:
"""Агрегированные метрики модуля (директории или файла)."""
name: str # Имя модуля (путь относительно корня)
files: List[str] = field(default_factory=list)
functions: List[FunctionMetrics] = field(default_factory=list)
language: str = "mixed" # "cpp" | "js" | "mixed"
# Агрегированные показатели (заполняются MetricsEngine)
total_nloc: int = 0
avg_ccn: float = 0.0
max_ccn: int = 0
total_functions: int = 0
heavy_functions_count: int = 0 # Функций с CCN > порога
heavy_functions_ratio: float = 0.0
# Граф зависимостей (заполняется DependencyAnalyzer)
coupling_out: int = 0 # Исходящая связность (fan-out)
coupling_in: int = 0 # Входящая связность (fan-in)
instability: float = 0.0 # C_out / (C_in + C_out), метрика Р. Мартина
has_cycles: bool = False # Участвует в циклической зависимости
# Зависимости
dependencies: Set[str] = field(default_factory=set) # Модули, от которых зависит этот
dependents: Set[str] = field(default_factory=set) # Модули, зависящие от этого
# ---------------------------------------------------------------------------
# Классификация риска и стратегия
# ---------------------------------------------------------------------------
class RiskLevel(Enum):
"""Уровень архитектурного риска модуля."""
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
CRITICAL = "critical"
class ModernizationStrategy(Enum):
"""Рекомендуемая стратегия модернизации (Lehman & Belady, 1985)."""
KEEP = auto() # Оставить без изменений
REFACTOR = auto() # Локальный рефакторинг (Fowler, 2018)
REENGINEER = auto() # Реинжиниринг архитектуры
REPLACE = auto() # Полная замена
@dataclass
class ModuleDecision:
"""Решение DecisionEngine по конкретному модулю."""
module_name: str
risk_level: RiskLevel
strategy: ModernizationStrategy
reasons: List[str] = field(default_factory=list) # Причины в человекочитаемом виде
priority: int = 0 # Приоритет в плане рефакторинга (1 = высший)
# ---------------------------------------------------------------------------
# Итоговый отчёт
# ---------------------------------------------------------------------------
@dataclass
class AnalysisReport:
"""Итоговый отчёт по всей системе."""
project_root: str
modules: Dict[str, ModuleMetrics] = field(default_factory=dict)
decisions: List[ModuleDecision] = field(default_factory=list)
dependency_cycles: List[List[str]] = field(default_factory=list)
# Системные агрегаты
total_files: int = 0
total_nloc: int = 0
total_functions: int = 0
avg_system_ccn: float = 0.0
system_risk_level: RiskLevel = RiskLevel.LOW
recommended_system_strategy: ModernizationStrategy = ModernizationStrategy.KEEP
@property
def critical_modules(self) -> List[ModuleDecision]:
return [d for d in self.decisions if d.risk_level == RiskLevel.CRITICAL]
@property
def high_risk_modules(self) -> List[ModuleDecision]:
return [d for d in self.decisions if d.risk_level in (RiskLevel.HIGH, RiskLevel.CRITICAL)]