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

8.2 KiB
Raw Blame History

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)?