# Task Manifest **Session:** `metaagent-005` (initial) → `metaagent-006` (T8 pending) **Goal:** Серьёзный архитектурный рефактор **Date:** 2026-10-08 **Total tasks:** 8 (7 archived, 1 pending) --- ## T1: Реструктуризация в domain/application/infrastructure + version=1 **Зависимости:** — **Файлы:** domain/ (new), application/ (new), infrastructure/{cli,sync,ai}/ (new), tests/ **Что:** Ввести явные слои. Переместить пакеты. Добавить version: 1 в FinancialModel. **Acceptance:** - [ ] Структура domain/application/infrastructure создана - [ ] Все исходные файлы перемещены - [ ] Все импорты обновлены - [ ] `FinancialModel.to_dict()` → `{'version': 1, ...}` - [ ] `from_dict()` поддерживает v0 (без version) и v1 - [ ] pytest 63/63 --- ## T2: Pydantic v2 — миграция моделей **Зависимости:** T1 **Файлы:** domain/*.py, pyproject.toml, infrastructure/sync/excel_sync.py **Что:** `@dataclass` → `pydantic.BaseModel`. Удалить ручные `to_dict`/`from_dict`. **Acceptance:** - [ ] Все модели — `BaseModel` - [ ] Удалены ручные to_dict/from_dict - [ ] UUID в JSON как str - [ ] Валидация (balance >= 0, и т.п.) - [ ] Excel-sync адаптирован - [ ] pytest 63/63 --- ## T3: Decimal для денег **Зависимости:** T2 **Файлы:** domain/*.py, application/*.py, infrastructure/sync/excel_sync.py, infrastructure/cli/{main,config}.py **Что:** `float` → `Decimal` для всех monetary полей. Арифметика engine, форматирование. **Acceptance:** - [ ] monetary поля — `Decimal` - [ ] CurrencyConverter с Decimal - [ ] ForecastService арифметика — Decimal - [ ] Excel-sync читает числа как Decimal - [ ] rich.print форматирует Decimal (2 знака) - [ ] JSON-сериализация Decimal работает - [ ] pytest 63/63 --- ## T4: Repository pattern — ModelRepository **Зависимости:** T1 **Файлы:** application/repositories/model_repository.py, infrastructure/repositories/{json_file,excel}_repository.py, infrastructure/sync/excel_sync.py **Что:** `ModelRepository` Protocol + `JsonFileRepository` + `ExcelRepository` (адаптер над ExcelSync). **Acceptance:** - [ ] `ModelRepository` Protocol (load/save) - [ ] `JsonFileRepository` реализует Protocol - [ ] `ExcelRepository` реализует Protocol - [ ] `FinancialModel.save/load` удалены - [ ] Тесты на каждый репозиторий - [ ] pytest 63/63 --- ## T5: Dependency Injection в сервисах **Зависимости:** T4 **Файлы:** application/forecast.py, scenarios.py, infrastructure/ai/assistant.py, infrastructure/cli/{main,config}.py **Что:** Зависимости через конструктор. Composition root в CLI. **Acceptance:** - [ ] `ForecastService.__init__(model, repository, converter)` - [ ] ScenarioService, AssistantService — то же - [ ] Нет `new ForecastService()` внутри других сервисов - [ ] CLI собирает граф зависимостей - [ ] pytest 63/63 --- ## T6: Декомпозиция CLI **Зависимости:** T5 **Файлы:** infrastructure/cli/main.py → app.py, infrastructure/cli/commands/*.py, infrastructure/cli/{paths,services}.py **Что:** 8 команд в отдельных файлах. config.py → paths.py + services.py. **Acceptance:** - [ ] Каждая команда в cli/commands/*.py - [ ] cli/paths.py — только пути - [ ] cli/services.py — composition root - [ ] Размер файлов < 100-150 LOC - [ ] Поведение идентично - [ ] pytest 63/63 --- ## T7: Финальная валидация **Зависимости:** T6 **Файлы:** tests/, README.md, pyproject.toml **Что:** Прогнать pytest, ручная проверка CLI, обновить README. **Acceptance:** - [ ] pytest 63/63+ - [ ] cf init, cf forecast, cf scenario, cf compare — работают - [ ] cf import data.xlsx → cf export — round-trip - [ ] README.md обновлён под новую структуру --- ## T8: ISO 4217-aware форматирование денег (per-currency decimals) **Зависимости:** T3 (Decimal уже мигрирован) **Файлы:** `domain/currency.py`, `infrastructure/cli/services.py`, `infrastructure/cli/commands/{info,config_cmd}.py`, `infrastructure/cli/i18n.py`, `tests/test_currency.py`, `tests/test_i18n.py` **Контекст:** Миграция на Decimal (T3) устранила арифметические ошибки, но отображение денег осталось наивным: во всех местах захардкожен формат `:,.2f` / `:.2f`. Это даёт два бага: (1) JPY показывается как `¥1,234.00` вместо `¥1,234`; (2) BHD/KWD/OMR (3 знака) вообще не поддерживаются. Словарь `CURRENCY_SYMBOLS` в `domain/currency.py` объявляет 8 валют, но семантика ISO 4217 не реализована. **Что:** - Ввести `CURRENCY_DECIMALS: dict[str, int]` в `domain/currency.py` (минимум: JPY=0, BHD/KWD/OMR=3, остальные из поддерживаемого списка=2). - Добавить `CurrencyConverter.format_money(amount: Decimal, currency: str) -> str` — единая точка форматирования (символ + количество знаков). - Заменить все 14 захардкоженных `:,.2f` в `services.py`, `info.py`, `config_cmd.py` на вызов `format_money()` (или новый форматтер в i18n-шаблонах). - Переработать i18n-шаблоны `cmd.config.transaction_add.ok` и `cmd.config.recurring_add.ok` — убрать `{amount:.2f}`, передавать уже отформатированную строку. - Сохранить `_q()` в `forecast.py` для **сериализации прогноза** (JSON-результат должен быть стабильным), но не трогать внутреннюю арифметику — она остаётся на полной точности Decimal. - Граничный кейс: при конвертации `0.1 USD → JPY` сейчас даёт 8.07 JPY вместо ~7.86 JPY из-за `quantize("0.01")` в `convert()`. Решить — квантовать только при отображении, не при конверсии. **Acceptance:** - [ ] `format_money(Decimal("1234.567"), "JPY") == "¥1,235"` (округление банкирское или HALF_UP — зафиксировать) - [ ] `format_money(Decimal("1234.567"), "USD") == "$1,234.57"` - [ ] `format_money(Decimal("1.234"), "BHD") == "BHD 1.234"` (3 знака) - [ ] Ни одного литерала `:,.2f` / `:.2f` для денег в `infrastructure/cli/` (за исключением явных UNIT-тестов форматтера) - [ ] Новые тесты в `tests/test_currency.py`: по одному на JPY, BHD, USD, плюс round-trip `format_money(format_money(x))` стабилен - [ ] `pytest` зелёный, существующие 67 тестов не сломаны - [ ] `cf info` на модели с JPY-счётом показывает целые йены, без `.00` **Открытые вопросы (на согласование перед выполнением):** 1. Округление для отображения — `ROUND_HALF_UP` (банковское кругление) или `ROUND_HALF_EVEN` (banker's rounding)? По умолчанию предлагаю `HALF_UP` для совместимости с T3. 2. Формат `1,234` vs `1 234` (разделитель тысяч) — текущий `:,.2f` даёт запятую; оставляем? 3. Для валют без символа в `CURRENCY_SYMBOLS` (например CHF) — выводить код префиксом (`CHF 1,234.56`) или суффиксом (`1,234.56 CHF`)?