From 400d9922bbeb538fb2bbadd574ebedbdd45ca838 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 18 Sep 2026 14:21:06 +0300 Subject: [PATCH] =?UTF-8?q?docs:=20=D1=81=D0=BE=D1=81=D1=82=D0=BE=D1=8F?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D1=84=D0=B0=D0=B7=D1=8B=202=20=D0=BF?= =?UTF-8?q?=D0=BE=D1=81=D0=BB=D0=B5=20=D0=B0=D0=BB=D0=BB=D0=BE=D0=BA=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D0=B8,=20API=20=D0=B8=20=D1=8D=D0=BA=D1=80=D0=B0?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2=20=D0=BF=D0=BE=D1=80=D1=82=D1=84=D0=B5=D0=BB?= =?UTF-8?q?=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Плюс два ограничения, которые стоили времени: AssetClass нельзя выставлять наружу енумом из-за значения index, и сектор не заполнен ни у одного инструмента — его нет в ответе GetInstrumentBy, нужны потиповые Shares/Bonds. --- AGENTS.md | 18 ++++++++++++++---- docs/ai/architecture.md | 15 +++++++++++---- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 68f0bae..2f9a5ab 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -49,7 +49,10 @@ just revision "msg" # новая alembic-миграция из изме - **Аналитика читает только `event.status = confirmed`.** Snapshot-таблицы — для сверки. - **Сырые данные (`raw_*`) append-only и идемпотентны** по стабильному id источника. - `openapi/openapi.json` и `app/packages/api_client` — генерируются, но коммитятся; - после изменения роутов запускай `just gen-client`. + после изменения роутов запускай `just gen-client`. В схемах API не выставляй наружу + `AssetClass`: одно из его значений — `index`, а Dart-енум не может иметь члена с таким + именем (конфликт с `Enum.index`), и сгенерированный клиент перестаёт компилироваться. + Стабильные ключи (`asset_class`, `price_status`, `bucket`, `period`) идут строками. - Секреты только в `.env` (в `.gitignore`). Реальные отчёты брокеров — в `backend/tests/fixtures/reports/raw/` (в `.gitignore`); в git только обезличенные фикстуры. - Коммиты — Conventional Commits на русском (`feat(ledger): …`), автор — только пользователь. @@ -68,9 +71,14 @@ just revision "msg" # новая alembic-миграция из изме - `analytics/valuation` — дневная серия стоимости и холдинги по scope (`all`, `account:`, `portfolio:`), сверка со снапшотами брокера; `analytics/returns` — XIRR и TWR по периодам (`1m 3m 6m ytd 1y 3y all`) плюс XIRR на инструмент; `pricing/prices.py` — цена на - дату с протяжкой вперёд и порогом устаревания 10 дн. + дату с протяжкой вперёд и порогом устаревания 10 дн.; +- `analytics/allocation` — разрезы по классу актива, сектору, стране и валюте; каждый + покрывает один и тот же итог (бумаги + кэш), поэтому веса любого из них дают единицу; +- API `/analytics/*`, `/events`, `/instruments/{id}` и экраны «Портфель» (позиции + + аллокация) и карточка инструмента. -Осталось: `analytics/allocation`, корпоративные действия, API и экраны позиций. +Осталось: `ledger/matching.py` + `account_link`, корпоративные действия, бэкфилл истории +цен MOEX. Известные расхождения derived vs брокерский снапшот по позициям (5 шт, не ошибки разбора): редомициляция FIVE→X5, MGNT, HEAD, внебиржевая SIBN6P4. Без цен два инструмента — `SIBN6P4` @@ -78,4 +86,6 @@ just revision "msg" # новая alembic-миграция из изме счетах 46, 48 и 52 (+6313, +56447, +146 ₽) — это неполнота леджера T-Invest, а не оценки: счёт 51 сходится точно. История цен тонкая (большинство облигаций — только с апреля 2026, TBRU@ — с 14.09.2026), поэтому TWR пропускает 32 дня и сообщает об этом в -`metric_data_quality`. План фаз — в `docs/ai/plan.md`. +`metric_data_quality`. Сектор не заполнен ни у одного инструмента: `sources/tinvest` читает +`GetInstrumentBy`, в ответе которого этого поля нет — нужны потиповые `Shares`/`Bonds`. +План фаз — в `docs/ai/plan.md`. diff --git a/docs/ai/architecture.md b/docs/ai/architecture.md index 0a887ed..ffe0609 100644 --- a/docs/ai/architecture.md +++ b/docs/ai/architecture.md @@ -50,8 +50,8 @@ ## Аналитика (`analytics/`, `pricing/{fx,prices}.py`, `metrics/refresh.py`) Шаги регистрируются в `analytics/__init__.py` и выполняются `refresh_all` по порядку: -`fx → classify → lots → valuation → returns → networth → cashflow → spending → runway → -quality` (фаза 2 вставит income и allocation после returns). Запуск: +`fx → classify → lots → valuation → returns → allocation → networth → cashflow → spending → +runway → quality` (фаза 4 вставит income и rebalance после allocation). Запуск: worker после синка с `changed=True`, `fintracker metrics refresh`, `POST /metrics/refresh`, `POST /rules/apply`. Каждая `metric_*` таблица пересобирается целиком. Net worth считается от текущего `account.balance` назад по транзакциям; конвертация — `FxTable` по дате @@ -63,7 +63,10 @@ worker после синка с `changed=True`, `fintracker metrics refresh`, `P `portfolio:`. `returns` читает только `metric_portfolio_value_daily`: XIRR по внешним потокам и терминальной стоимости, TWR цепочкой `V_t / (V_{t-1} + F_t)`. Покупка бумаги без цены трактуется как вывод из оцениваемого портфеля (`unvalued_flow_rub`), иначе дыра в -данных читалась бы как обвал. +данных читалась бы как обвал. `allocation` режет тот же итог четырьмя способами (класс, +сектор, страна, валюта), включая кэш в каждый разрез: четыре диаграммы одного портфеля +обязаны быть одного размера. Бакет — стабильный ключ (`cash`, `unknown`, код страны), язык +живёт в клиенте. Деньги в JSON — `Money` (`api/schemas/common.py`), всегда позиционная строка. @@ -73,7 +76,11 @@ worker после синка с `changed=True`, `fintracker metrics refresh`, `P - Ошибки — RFC 7807 (`application/problem+json`), см. `api/errors.py`. - Auth: `POST /auth/login` → access JWT (1 ч) + refresh (30 д, хранится хэшем, ротируется); login rate-limited в памяти (`api/ratelimit.py`). -- Деньги в JSON — строки. +- Деньги в JSON — строки; енумы моделей наружу отдаются не всегда: `asset_class` идёт + строкой, потому что значение `index` ломает генератор Dart-клиента. +- Аналитика инвестиций читает только `metric_*` — на запрос ничего не считается. + Параметр `scope` (`all | account: | portfolio:`) резолвится через + `analytics/valuation.account_scopes`, неизвестный scope — 404, а не пустой график. ## Клиент (`app/`) Flutter, Riverpod, go_router, fl_chart; клиент `app/packages/api_client` генерируется