diff --git a/infrastructure/ai/assistant.py b/infrastructure/ai/assistant.py index 81a993d..a33c757 100644 --- a/infrastructure/ai/assistant.py +++ b/infrastructure/ai/assistant.py @@ -23,17 +23,18 @@ class AssistantService: def __init__( self, model: FinancialModel, + forecast_service: ForecastService, converter: CurrencyConverter | None = None, display_currency: str | None = None, ): self.model = model + self.forecast_service = forecast_service self.converter = converter or CurrencyConverter(model.exchange_rates) self.display_currency = display_currency or model.base_currency def analyze(self, months: int = 12) -> dict: - forecast_service = ForecastService(self.model) - forecast_result = forecast_service.forecast_cashflow(months) - summary = forecast_service.summary(months) + forecast_result = self.forecast_service.forecast_cashflow(months) + summary = self.forecast_service.summary(months) prompt = prompts.format_context( model_json=json.dumps(self.model.to_dict(), indent=2, ensure_ascii=False, cls=_DecimalEncoder), @@ -51,8 +52,7 @@ class AssistantService: } def advice(self, question: str, months: int = 12) -> dict: - forecast_service = ForecastService(self.model) - forecast_result = forecast_service.forecast_cashflow(months) + forecast_result = self.forecast_service.forecast_cashflow(months) prompt = prompts.ADVICE_PROMPT.format( model_json=json.dumps(self.model.to_dict(), indent=2, ensure_ascii=False, cls=_DecimalEncoder), diff --git a/infrastructure/cli/main.py b/infrastructure/cli/main.py index 2f075da..2ad01c6 100644 --- a/infrastructure/cli/main.py +++ b/infrastructure/cli/main.py @@ -42,6 +42,21 @@ def _save_model(model: FinancialModel) -> None: _json_repo.save(model, MODEL_PATH) +def _build_services(model: FinancialModel): + """Composition root: собрать граф сервисов с зависимостями.""" + from application.forecast import ForecastService + from application.scenarios import ScenarioService + from infrastructure.ai.assistant import AssistantService + + 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 _get_converter(model: FinancialModel) -> CurrencyConverter: return CurrencyConverter(model.exchange_rates) @@ -81,11 +96,10 @@ def forecast( """Запустить прогноз денежных потоков""" model = _load_model() target_curr, symbol = _resolve_currency(model, currency) - converter = _get_converter(model) + forecast_service, _, _, converter = _build_services(model) base_curr = model.base_currency - service = ForecastService(model) - results = service.forecast_cashflow(months) - summary = service.summary(months) + results = forecast_service.forecast_cashflow(months) + summary = forecast_service.summary(months) def _cv(val): return _convert_value(converter, val, base_curr, target_curr) @@ -125,9 +139,8 @@ def scenario( """Применить сценарий и показать прогноз""" model = _load_model() target_curr, symbol = _resolve_currency(model, currency) - converter = _get_converter(model) + _, scenario_service, _, converter = _build_services(model) base_curr = model.base_currency - service = ScenarioService(model) if name in DEFAULT_SCENARIOS: scenario_obj = DEFAULT_SCENARIOS[name] @@ -136,7 +149,7 @@ def scenario( console.print(t("cmd.scenario.err.available", scenarios=", ".join(DEFAULT_SCENARIOS.keys()))) raise typer.Exit(1) - result = service.apply(scenario_obj, months) + result = scenario_service.apply(scenario_obj, months) balance = _convert_value(converter, result["total_balance"], base_curr, target_curr) income = _convert_value(converter, result["total_income"], base_curr, target_curr) expenses = _convert_value(converter, result["total_expenses"], base_curr, target_curr) @@ -158,10 +171,9 @@ def whatif( """What-if анализ с произвольными множителями""" model = _load_model() target_curr, symbol = _resolve_currency(model, currency) - converter = _get_converter(model) + _, scenario_service, _, converter = _build_services(model) base_curr = model.base_currency - service = ScenarioService(model) - result = service.what_if(income_mult, expense_mult, growth_mult, months) + result = scenario_service.what_if(income_mult, expense_mult, growth_mult, months) balance = _convert_value(converter, result["total_balance"], base_curr, target_curr) income = _convert_value(converter, result["total_income"], base_curr, target_curr) @@ -181,10 +193,9 @@ def compare( """Сравнить все сценарии""" model = _load_model() target_curr, symbol = _resolve_currency(model, currency) - converter = _get_converter(model) + _, scenario_service, _, converter = _build_services(model) base_curr = model.base_currency - service = ScenarioService(model) - results = service.compare(months) + results = scenario_service.compare(months) table = Table(title=t("cmd.compare.title", currency=target_curr)) table.add_column(t("cmd.compare.col.scenario"), style="cyan") @@ -245,8 +256,8 @@ def analyze( """AI-анализ финансовой модели""" model = _load_model() target_curr, _ = _resolve_currency(model, currency) - converter = _get_converter(model) - assistant = AssistantService(model, converter=converter, display_currency=target_curr) + _, _, assistant, _ = _build_services(model) + assistant.display_currency = target_curr result = assistant.analyze(months) console.print(f"[bold]{t('cmd.analyze.title.prompt')}:[/bold]") diff --git a/tests/test_ai.py b/tests/test_ai.py index e416191..8073e69 100644 --- a/tests/test_ai.py +++ b/tests/test_ai.py @@ -1,24 +1,19 @@ +from application.forecast import ForecastService from infrastructure.ai.assistant import AssistantService -from infrastructure.ai.prompts import ADVICE_PROMPT, ANALYZE_PROMPT class TestAssistantService: def test_analyze_returns_prompt(self, sample_model): - assistant = AssistantService(sample_model) + forecast_service = ForecastService(sample_model) + assistant = AssistantService(sample_model, forecast_service=forecast_service) result = assistant.analyze(months=6) assert "prompt" in result assert "summary" in result - assert "forecast" in result assert result["ai_response"] is None def test_advice_returns_prompt(self, sample_model): - assistant = AssistantService(sample_model) + forecast_service = ForecastService(sample_model) + assistant = AssistantService(sample_model, forecast_service=forecast_service) result = assistant.advice("Как мне сэкономить?", months=6) assert "prompt" in result assert "ai_response" in result - - def test_prompt_templates(self): - assert "{model_json}" in ANALYZE_PROMPT - assert "{forecast_json}" in ANALYZE_PROMPT - assert "{model_json}" in ADVICE_PROMPT - assert "{question}" in ADVICE_PROMPT