arch-researcher/models.py

121 lines
4.9 KiB
Python
Raw Permalink Normal View History

2026-04-27 18:44:22 +00:00
"""
Доменные модели инструментария.
Все типы данных, которыми обмениваются модули системы.
"""
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)]