Files
nifodea/.agent/tasks/manifest.md
T
oqyude 9175d5c76b T8: define task — ISO 4217 per-currency decimals (req ready)
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.
2026-10-09 18:50:12 +03:00

155 lines
8.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`)?