Follow-up to T3 (Decimal migration): display layer still hardcodes :,.2f everywhere, which is wrong for JPY (0 dp) and 3-dp currencies like BHD/KWD/OMR. T8 = display-only refactor: - CURRENCY_DECIMALS + CurrencyConverter.format_money() in domain/ - replace 14 :,.2f literals across infrastructure/cli/ - rework 2 i18n templates that bake the format into the message string - extend tests/test_currency.py with JPY/BHD/USD/CHF cases Locked decisions (HALF_UP, ',' separator, suffix for unknown codes, internal precision untouched) recorded in req-T8.json for the executing node.
155 lines
8.2 KiB
Markdown
155 lines
8.2 KiB
Markdown
# 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`)?
|