Split infrastructure/cli/: - main.py (330 LOC) → 9 files in infrastructure/cli/commands/: * init.py, forecast.py, scenario.py, whatif.py, compare.py * import_xlsx.py, export_xlsx.py, analyze.py, info.py - config.py (428 LOC) → commands/config_cmd.py (15 sub-commands) - main.py → app.py (entry point: registers all commands) - New: paths.py (MODEL_PATH, DATA_DIR) - New: services.py (composition root + helpers: load_model, save_model, build_services, resolve_currency, convert_value, fmt_amount) - cli/__init__.py (empty) удалён Each command module: <100 LOC. pyproject.toml: cf = infrastructure.cli.app:app tests/test_cli.py: import обновлён на infrastructure.cli.app CLI behavior идентично, все команды работают. Tests: 67/67 pass.
61 lines
2.0 KiB
Python
61 lines
2.0 KiB
Python
"""Composition root + общие хелперы для CLI-команд."""
|
|
from __future__ import annotations
|
|
|
|
from decimal import Decimal
|
|
|
|
from rich.console import Console
|
|
|
|
from application.forecast import ForecastService
|
|
from application.scenarios import ScenarioService
|
|
from domain import CurrencyConverter, FinancialModel
|
|
from infrastructure.ai.assistant import AssistantService
|
|
from infrastructure.cli.paths import MODEL_PATH
|
|
from infrastructure.repositories import JsonFileRepository
|
|
|
|
|
|
console = Console()
|
|
|
|
_json_repo = JsonFileRepository()
|
|
|
|
|
|
def load_model() -> FinancialModel:
|
|
if MODEL_PATH.exists():
|
|
return _json_repo.load(MODEL_PATH)
|
|
return FinancialModel()
|
|
|
|
|
|
def save_model(model: FinancialModel) -> None:
|
|
_json_repo.save(model, MODEL_PATH)
|
|
|
|
|
|
def build_services(model: FinancialModel) -> tuple[ForecastService, ScenarioService, AssistantService, CurrencyConverter]:
|
|
"""Composition root: собрать граф сервисов с зависимостями."""
|
|
converter = CurrencyConverter(model.exchange_rates)
|
|
forecast_service = ForecastService(model)
|
|
scenario_service = ScenarioService(model)
|
|
assistant_service = AssistantService(
|
|
model, forecast_service=forecast_service, converter=converter
|
|
)
|
|
return forecast_service, scenario_service, assistant_service, converter
|
|
|
|
|
|
def resolve_currency(model: FinancialModel, currency: str | None) -> tuple[str, str]:
|
|
target = currency or model.base_currency
|
|
converter = CurrencyConverter(model.exchange_rates)
|
|
symbol = converter.get_symbol(target)
|
|
return target, symbol
|
|
|
|
|
|
def convert_value(
|
|
converter: CurrencyConverter, amount: Decimal, from_curr: str, to_curr: str
|
|
) -> Decimal:
|
|
if to_curr == from_curr:
|
|
return amount
|
|
return converter.convert(amount, from_curr, to_curr)
|
|
|
|
|
|
def fmt_amount(amount: Decimal | float, currency: str, symbol: str) -> str:
|
|
if isinstance(amount, Decimal):
|
|
return f"{symbol}{amount:,.2f}"
|
|
return f"{symbol}{amount:,.2f}"
|