feat: add tests for import polling, login page, money utilities, and query keys
- Implemented tests for import polling functionality, ensuring correct behavior for active and terminal import statuses. - Created tests for the login page, validating user interactions and error handling. - Added unit tests for money utility functions, covering parsing, formatting, and summation of monetary values. - Developed tests for query keys, ensuring proper scoping and invalidation roots. chore: setup testing environment and configuration - Added setup file for testing with Vitest and Testing Library. - Configured TypeScript and Vite for the frontend project. - Created Docker and Nginx configurations for local development and production environments. - Established a PostgreSQL initialization script for setting up application roles. docs: add README and environment configuration for infra - Included README for the infra directory, detailing local development setup and service descriptions. - Created an example environment configuration file for local development with placeholders for sensitive information.
This commit is contained in:
@@ -0,0 +1,277 @@
|
||||
# План MVP финансового трекера
|
||||
|
||||
## Цель
|
||||
|
||||
Собрать закрытую hosted beta личного zero-based бюджета. Пользователь по приглашению создаёт личное финансовое пространство, учитывает RUB-счета и кредитки, планирует календарный месяц, импортирует выписки пяти поддержанных банковских контрактов, разбирает неоднозначности, закрывает период и видит детерминированную аналитику.
|
||||
|
||||
Критический пользовательский путь:
|
||||
|
||||
1. Принять приглашение, выбрать RU/EN и создать личное пространство.
|
||||
2. Создать дебетовый, наличный или кредитный счёт с начальной позицией.
|
||||
3. Создать месячный план из ожидаемых доходов и назначений, при необходимости скопировав прошлый месяц.
|
||||
4. Загрузить выписку, подтвердить привязку к счёту, исправить критические поля, разрешить дубли/переводы и атомарно применить импорт.
|
||||
5. Категоризировать расходы, распределить фактически поступившие деньги, покрыть обязательные дефициты и закрыть месяц.
|
||||
6. Проверить счета, бюджет, операции, импорт и четыре отчёта; экспортировать либо удалить свои данные.
|
||||
|
||||
## Зафиксированный объём
|
||||
|
||||
- Один личный `Financial Space` на пользователя в UI; схема сразу поддерживает membership с ролями `owner` и `member`.
|
||||
- Полная видимость данных внутри пространства; счёт принадлежит ровно одному пространству.
|
||||
- RUB only. Все суммы хранятся целым числом копеек, без `float`.
|
||||
- Счета MVP: дебетовые/текущие, наличные, кредитные.
|
||||
- Импорт-контракты: Альфа debit XLSX, Альфа credit XLSX, Ozon debit PDF, Ozon credit PDF, Рокетбанк debit PDF.
|
||||
- Ozon и Рокетбанк: только PDF с извлекаемым текстом, без OCR. Альфа: только XLSX, без XLS.
|
||||
- Бюджет: календарный месяц, план и обеспечение разделены, ожидаемый доход можно планировать до поступления.
|
||||
- Категории: плоский список; одна операция имеет не более одной бюджетной категории.
|
||||
- Два типа категории: месячная категория и постоянный накопительный конверт.
|
||||
- Отчёты: расходы по категориям; план/факт; доходы/расходы/cash flow; остатки активов и долг по кредиткам.
|
||||
- Полностью адаптивный RU/EN web для desktop и mobile.
|
||||
- Масштаб beta: до 100 пользователей, 50 000 подтверждённых операций на пространство, 10 000 строк и 20 МБ на импорт.
|
||||
|
||||
## Вне MVP
|
||||
|
||||
- AI-категоризация, AI-аналитика и анализ договоров кредиток.
|
||||
- Семейные приглашения, передача владения, выход из пространства и совместный UI.
|
||||
- Полный расчёт льготного периода, процентов и тарифов кредиток; кредитка MVP — баланс/долг и резерв.
|
||||
- Мультивалютность, вклады, кредиты кроме кредиток, инвестиции, цели с суммой/сроком.
|
||||
- Split одной операции между несколькими категориями и иерархия категорий.
|
||||
- OCR, сканы, XLS, credit-выписка Рокетбанка, банковские API и автоматическая синхронизация.
|
||||
- Движок регулярных платежей; вместо него есть явное копирование плана прошлого месяца.
|
||||
- Публичная регистрация, биллинг и 2FA.
|
||||
|
||||
## Доменный язык
|
||||
|
||||
При реализации создать корневой `CONTEXT.md` в формате skill `domain-modeling` и перенести туда только следующие краткие определения, без технических деталей.
|
||||
|
||||
- **Пользователь**: подтверждённая личность, которая входит в приложение. Не называть финансовым счётом или владельцем операций.
|
||||
- **Финансовое пространство**: единая граница владения счетами, операциями, бюджетами и справочниками.
|
||||
- **Участие**: связь пользователя с финансовым пространством и ролью `owner` либо `member`.
|
||||
- **Финансовый счёт**: место учёта собственных денег либо долга. Карта является банковским инструментом/атрибутом, а не отдельным синонимом счёта.
|
||||
- **Операция**: пользовательский финансовый факт: расход, доход, внутренний перевод или возврат.
|
||||
- **Проводка**: изменение остатка одного внутреннего финансового счёта в рамках операции.
|
||||
- **Внутренний перевод**: одна операция с двумя противоположными проводками по собственным счетам; не доход и не расход.
|
||||
- **Возврат**: отдельная операция, полностью или частично связанная с исходным расходом; не доход.
|
||||
- **Импорт выписки**: загруженный документ и процесс его проверки/применения.
|
||||
- **Попытка разбора**: версионированный результат конкретного parser adapter для одного документа.
|
||||
- **Исходная запись**: неизменяемый извлечённый банковский факт с происхождением в документе.
|
||||
- **Получатель**: нормализованное пространство-локальное имя магазина, сервиса или человека с алиасами исходных описаний.
|
||||
- **Бюджетный период**: календарный месяц в часовом поясе пользователя со статусом open/closed.
|
||||
- **Общий пул**: фактически доступные, но ещё не распределённые деньги пространства; может быть отрицательным.
|
||||
- **Плановое назначение**: доля текущих и ожидаемых денег, которую пользователь намерен направить категории.
|
||||
- **Обеспечение**: фактически поступившие деньги, перемещённые из общего пула в категорию или резерв.
|
||||
- **Месячная категория**: назначение расходов, остаток которого при закрытии сворачивается в общий пул.
|
||||
- **Накопительный конверт**: виртуальный обеспеченный резерв без обязательной цели/срока, переносимый между месяцами.
|
||||
- **Резерв кредитки**: постоянный системный резерв конкретной кредитки для будущего погашения долга.
|
||||
- **Ожидаемый доход**: плановое поступление с датой, суммой и источником, отдельно сопоставляемое фактическим доходам.
|
||||
- **Источник дохода**: необязательная классификация происхождения фактического или ожидаемого поступления.
|
||||
|
||||
## Архитектура
|
||||
|
||||
### Репозиторий и процессы
|
||||
|
||||
- Монорепозиторий: `backend/`, `frontend/`, `infra/`, корневые `CONTEXT.md`, `docs/adr/` и setup-документация.
|
||||
- Backend: FastAPI, Pydantic 2, синхронный SQLAlchemy 2 + psycopg, Alembic, PostgreSQL.
|
||||
- Worker: Celery + Redis. HTTP-процесс не разбирает файлы; загрузка возвращает import ID, клиент опрашивает активное состояние через TanStack Query и прекращает polling в terminal state.
|
||||
- Frontend: React + TypeScript + Vite, React Router, TanStack Query, React Hook Form + Zod, i18next; OpenAPI-generated API client.
|
||||
- Локально: Docker Compose с API, worker, frontend/reverse proxy, PostgreSQL, Redis, MinIO и SMTP test sink.
|
||||
- Production: provider-neutral OCI images, PostgreSQL, Redis, S3-compatible storage и SMTP; фактическая площадка, база, объекты и backups обязаны находиться в РФ.
|
||||
- Пакеты фиксируются `uv.lock` и `pnpm-lock.yaml`; секреты не попадают в `VITE_*` и Git.
|
||||
|
||||
### Backend-модули
|
||||
|
||||
- `identity`: invitations, users, password reset, sessions, locale/timezone.
|
||||
- `spaces`: financial spaces, memberships, roles, RLS context.
|
||||
- `ledger`: accounts, operations, postings, transfers, refunds, voiding, balances.
|
||||
- `budgeting`: periods, plans, expected income, funding, categories, envelopes, credit reserves, close/reopen.
|
||||
- `imports`: encrypted documents, parser adapters/runs, source records, reconciliation, deduplication, atomic apply/revert.
|
||||
- `categorization`: payees, aliases, MCC/bank mappings, user rules and suggestion provenance.
|
||||
- `analytics`: read queries/snapshots for the four fixed reports.
|
||||
- `exports`: asynchronous ZIP exports and space deletion workflow.
|
||||
- `audit`: append-only domain/security events without raw financial payloads.
|
||||
|
||||
Модули общаются через application services внутри одного процесса/БД. Не создавать микросервисы, event bus или абстрактные AI-provider interfaces в MVP.
|
||||
|
||||
### ADR после scaffold
|
||||
|
||||
Создать только ADR, которые сохраняют труднообратимый контекст:
|
||||
|
||||
1. `0001-financial-space-ownership-and-rls.md`: данные принадлежат пространству; полная видимость; application checks + PostgreSQL RLS.
|
||||
2. `0002-journal-and-budget-reserves.md`: журнал операций/проводок, отдельные план/обеспечение, переносимые envelopes/credit reserves, не event sourcing и не полная двойная запись.
|
||||
3. `0003-versioned-statement-imports.md`: неизменяемое происхождение, версионированные parse runs, атомарное применение/отмена и консервативная дедупликация.
|
||||
4. `0004-provider-neutral-encrypted-data-boundary.md`: размещение в РФ, S3-neutral envelope encryption и отсутствие внешних AI-вызовов.
|
||||
|
||||
## Модель данных и инварианты
|
||||
|
||||
### Identity и tenancy
|
||||
|
||||
- `users`, `invitations`, `sessions`, `financial_spaces`, `memberships`, `audit_events`.
|
||||
- Регистрация только по одноразовому email invite; пароль хешируется Argon2id. Email нормализован и уникален без учёта регистра.
|
||||
- Непрозрачный session token хранится в cookie `HttpOnly`, `Secure`, `SameSite=Lax`; в БД хранится только его hash. Ротация после login/reset, revoke one/all sessions, rate limits и CSRF token для изменяющих запросов.
|
||||
- В MVP invitation создаёт пространство и единственного owner. В БД всегда должен оставаться минимум один owner.
|
||||
- Все финансовые таблицы имеют `space_id`; составные FK запрещают cross-space ссылки.
|
||||
- API проверяет membership, затем внутри транзакции выполняет `SET LOCAL app.space_id`; RLS повторно ограничивает запрос. Worker получает IDs, заново проверяет принадлежность и устанавливает тот же RLS context. Bypass role доступна только migrations и audited break-glass tooling.
|
||||
- Для конкурентно изменяемых budget/operation/import aggregates использовать version column и `409 Conflict`, а для close/apply/revert — row locks и одну DB transaction.
|
||||
|
||||
### Ledger
|
||||
|
||||
- `accounts`: `cash | debit | credit`, opening date/amount, bank code, masked product identifier, keyed fingerprint, status `active | archived`.
|
||||
- Остаток не изменяется напрямую: opening position + confirmed, non-voided postings. Asset balance положительный; credit balance отражает долг по единой документированной sign convention.
|
||||
- `operations`: `expense | income | transfer | refund`; status `confirmed | voided`; positive amount; `occurred_on`; optional `posted_on`; optional category/income source/payee/refund parent; audit/version.
|
||||
- `postings`: одна для external income/expense/refund, две равные и противоположные для internal transfer. Погашение кредитки — internal transfer и не создаёт второй расход.
|
||||
- Бюджет и расходная аналитика используют `occurred_on`. Остаток/сверка используют `posted_on`, когда он есть, иначе `occurred_on`. Если выписка содержит одну дату, parser записывает только `occurred_on`, не выдумывая вторую.
|
||||
- Операция имеет максимум одну категорию. Income source необязателен и отсутствие источника не блокирует close.
|
||||
- Ошибочная операция voided с причиной; реальный возврат создаётся отдельно. Изменение/void операции закрытого месяца требует каскадного reopen.
|
||||
- Refund sum по связанной покупке не должна бесконтрольно превышать исходный расход; неоднозначный/избыточный остаток требует ручной классификации.
|
||||
- Счёт архивируется только с нулевым остатком и сохраняется в истории; удалить можно только никогда не использованный счёт.
|
||||
|
||||
### Budget
|
||||
|
||||
- `budget_periods`, `plan_assignments`, `expected_incomes`, `expected_income_matches`, `funding_movements`, `period_closures`.
|
||||
- Плановый остаток = opening projected pool + expected incomes - plan assignments. Он может быть положительным/отрицательным: UI предупреждает, но не блокирует сохранение или close.
|
||||
- Actual funding выполняется вручную после поступления денег; UI предлагает недообеспеченные назначения. Expected income может быть закрыт несколькими фактическими incomes и показывает amount/date variance.
|
||||
- Фактический доход сразу увеличивает general pool независимо от наличия income source.
|
||||
- Debit/cash expense уменьшает funded category/envelope; uncategorized expense уменьшает системный `Unallocated` deficit/general availability и блокирует close.
|
||||
- Credit expense увеличивает debt. Обеспеченная часть расхода перемещается из category/envelope в постоянный credit reserve; необеспеченная часть остаётся видимым category debt. Uncategorized credit expense остаётся unallocated/unfunded debt.
|
||||
- Credit payment сначала уменьшает credit reserve, затем general pool; pool может стать отрицательным. Initial credit debt финансируется пользователем вручную.
|
||||
- Обычная monthly category при close сворачивает положительный или отрицательный остаток в general pool и начинает следующий период с нуля.
|
||||
- Savings envelope переносит баланс. Его отрицательный баланс блокирует close до покрытия.
|
||||
- Credit reserve переносится до платежа. Refund на кредитку уменьшает debt; связанный излишек резерва освобождается в текущий pool.
|
||||
- Refund в том же открытом месяце восстанавливает категорию покупки. Refund после закрытия исходного месяца пополняет текущий general pool и не переписывает closed budget fact.
|
||||
- Close блокируют: imports без допустимого reconciliation confirmation, unresolved financial fields/dedup/transfer decisions, expenses без category и отрицательные savings envelopes. Plan imbalance, expected-income variance и underfunded credit debt дают warnings, но не блокируют.
|
||||
- Reopen старого периода показывает impacted periods, атомарно открывает его и все последующие и пересчитывает preliminary pools; повторный close выполняется последовательно.
|
||||
- `Copy previous month` копирует plan assignments и expected-income templates, сдвигает ожидаемую дату на следующий месяц с clamp к последнему дню, но не копирует факты, matches или funding.
|
||||
|
||||
### Categories и локализация
|
||||
|
||||
- `categories`: `monthly | savings_envelope`, `system_key` nullable, `custom_name/name_override` nullable, archived flag.
|
||||
- Standard category отображается через RU/EN translation key. После rename сохраняется override, который больше не переводится. Custom category всегда хранит одно пользовательское имя.
|
||||
- Использованная category только архивируется; merge и bulk reclassification вне MVP.
|
||||
- `income_sources` — отдельный плоский пользовательский справочник; неизвестный источник разрешён и в отчёте группируется как unclassified.
|
||||
- Locale хранится в user profile. API возвращает machine-readable error codes; React и email templates переводят RU/EN. User timezone configurable, default `Europe/Moscow`; формат суммы/даты следует locale.
|
||||
|
||||
### Categorization
|
||||
|
||||
- `payees`, `payee_aliases`, `categorization_rules`, versioned MCC dictionary и bank-category mappings.
|
||||
- Parser извлекает raw description, MCC и bank category, но не назначает пользовательскую category.
|
||||
- Приоритет: explicit operation choice > confirmed payee rule > MCC suggestion > bank category suggestion > uncategorized.
|
||||
- Payee rule автоматически применяется к будущим операциям пространства и сохраняет provenance; MCC/bank match остаётся предложением до подтверждения.
|
||||
|
||||
### Import
|
||||
|
||||
- `statement_documents`, `parse_runs`, `source_records`, `source_record_operation_links`, `dedup_candidates`, reconciliation metadata.
|
||||
- Файл проходит magic/MIME/size check, hash и quotas до сохранения. Не сохранять полный извлечённый текст документа в БД: persist только allowlisted statement metadata/source fields и page/row coordinates.
|
||||
- Содержимое шифруется до S3 через AES-GCM envelope encryption: случайный DEK на файл, encrypted DEK и nonce metadata в БД, KEK в secret manager. Random object keys не содержат PII. KEK rotation rewraps DEKs.
|
||||
- XLSX reader: `openpyxl` в `read_only=True`, `data_only=True`, `keep_links=False`, с `defusedxml`, ZIP quotas и явным close.
|
||||
- PDF reader: `pdfplumber`; table/text settings принадлежат конкретному adapter contract. Worker non-root, без исходящей сети, с CPU/memory/time limits.
|
||||
- Adapter interface: content detection + parser version + `StatementDraft` с bank/product/account fingerprint, period, currency, balances/control totals и source rows (external ID, dates, signed amount/direction, description, payee hints, MCC, bank category, location, field warnings).
|
||||
- State machine: uploaded -> queued -> processing -> review_required/ready -> confirmed либо failed; reparse создаёт новый run, старый не изменяет.
|
||||
- Критические blockers: currency, amount, direction, occurred date, target account, arithmetic mismatch. Описательные uncertainty отправляются в categorization queue.
|
||||
- Первая account mapping подтверждается пользователем; stable bank fingerprint далее применяется автоматически. Conflict блокирует импорт.
|
||||
- Reconciliation: при наличии balances/control totals требуется точное равенство. Если формат не даёт полной проверки, только явный `confirmed_without_full_reconciliation` с причиной/audit.
|
||||
- Dedupe автоматически связывает stable bank ID или единственное полное однозначное совпадение. Повторяющиеся одинаковые кандидаты остаются отдельными до решения; приоритет — не потерять реальную операцию.
|
||||
- Internal transfer matcher использует amount/currency/time/account candidates; связывает только однозначную пару, остальные подтверждает пользователь.
|
||||
- Confirm применяет весь run одной DB transaction: создаёт только новые operations/postings, а duplicates получают additional source links. Ни одна строка не влияет на ledger до confirm.
|
||||
- Reparse создаёт новый run и diff; пользовательские правки не перезаписываются. Apply проходит полную сверку и dedupe заново.
|
||||
- Revert confirmed import показывает diff, требует cascade reopen affected periods, атомарно удаляет созданные этим import postings/operations и снимает только source links у ранее существовавших duplicates. Файл/audit остаётся до отдельного удаления.
|
||||
- Source file хранится до explicit deletion. После удаления сохраняются allowlisted source records, checksum и normalized operations. Удаление пространства уничтожает всё по lifecycle ниже.
|
||||
|
||||
## UI и API
|
||||
|
||||
- Same-origin `/api/v1` REST; OpenAPI schema генерирует frontend client. Mutations принимают idempotency key там, где browser retry может повторить side effect.
|
||||
- Главная навигация: Accounts, Budget, Operations, Review Queue, Imports, Analytics, Settings/Data.
|
||||
- Desktop использует data-dense tables для operations/import review; mobile показывает те же действия карточками и пошаговыми dialogs, без обязательного horizontal scroll.
|
||||
- Operations: server pagination, date/account/type/category/payee/status filters, search, manual income/expense/transfer/refund, void and audit view.
|
||||
- Budget: month switcher, preliminary/closed badge, planned/funded/spent/available columns, pool, envelopes, card reserves/debt, expected income matching, close/reopen/copy flows.
|
||||
- Import: upload/progress, adapter/account detection, field warnings, reconciliation summary, duplicate/transfer decisions, atomic confirm/revert/reparse diff.
|
||||
- Analytics:
|
||||
- category spend uses purchase occurrence period; linked late refund adjusts original purchase analytics but not closed budget fact;
|
||||
- cash flow uses actual posting/refund period (`posted_on`, fallback `occurred_on`), excludes internal transfers/opening positions and identifies refund separately;
|
||||
- plan/fact uses budget-effective `occurred_on` and immutable closure snapshots for closed periods;
|
||||
- balances/debt use posting timeline and confirmed non-voided postings.
|
||||
- Любой chart имеет доступную табличную альтернативу; keyboard/focus/error states и contrast соответствуют WCAG AA.
|
||||
|
||||
## Безопасность, данные и эксплуатация
|
||||
|
||||
- TLS only; private S3 bucket; encrypted PostgreSQL/Redis/backups; secrets from environment/secret manager; structured logs redact filename, description, payee, account identifiers, email and amounts.
|
||||
- Нет support UI для просмотра финансов. Break-glass использует отдельную роль, обязательную reason, short-lived grant и append-only audit; обычный admin управляет invites/health без содержимого пространств.
|
||||
- Uploaded files никогда не исполняются. Ограничить decompression, pages, cells, rows, parser time and worker memory; quarantine failed files; одинаковый file hash делает enqueue/apply idempotent.
|
||||
- Celery tasks idempotent, `acks_late`, prefetch 1, bounded retry/backoff и terminal failure. DB unique constraints защищают от duplicate task side effects.
|
||||
- Self-service export создаётся worker как short-lived encrypted ZIP с CSV + JSON для accounts, operations, categories, budgets, expected incomes and audit-relevant provenance; download URL одноразовый/короткоживущий.
|
||||
- Delete space: 30-day cancellation window, пространство немедленно блокируется; затем hard-delete rows/objects и crypto-shred file keys. Backups хранятся 30 дней; disaster restore повторно применяет durable deletion ledger до открытия сервиса.
|
||||
- Observability provider-neutral: health/readiness, queue depth, parser duration/failure by adapter version, reconciliation outcomes, DB latency and error codes. Метрики/трейсы не содержат финансовый payload.
|
||||
- До реальных приглашений провести юридическую проверку обработки персональных/финансовых данных и локализации в РФ; задокументировать privacy/retention/incident procedures.
|
||||
|
||||
## Последовательность реализации
|
||||
|
||||
1. **Scaffold и доменная документация**
|
||||
- Создать monorepo layout, locked backend/frontend dependencies, lint/type/test commands, Docker Compose и CI.
|
||||
- Добавить `CONTEXT.md` и четыре ADR выше; дополнить `.gitignore` для private fixtures, object data, secrets и generated exports.
|
||||
- Поднять PostgreSQL/Redis/MinIO/test SMTP; добавить Alembic naming convention и transactional migrations.
|
||||
|
||||
2. **Identity, sessions и tenant boundary**
|
||||
- Реализовать admin invite, registration, login/logout/reset, server sessions, CSRF, locale/timezone.
|
||||
- Создавать personal space + owner membership атомарно.
|
||||
- Включить RLS policies, request/worker transaction context, composite tenant constraints и negative cross-tenant tests.
|
||||
|
||||
3. **Ledger vertical slice**
|
||||
- Реализовать accounts/opening position, operations/postings, sign convention, manual income/expense/transfer/refund, voiding and archive.
|
||||
- Добавить transactional domain services, optimistic locking and balance queries/snapshots.
|
||||
- Построить Accounts/Operations UI и property tests для сохранения сумм/переводов/долга.
|
||||
|
||||
4. **Budget vertical slice**
|
||||
- Реализовать periods, plan assignments, expected incomes/matches, manual funding, monthly categories, envelopes and credit reserves.
|
||||
- Закодировать debit/credit/refund/uncategorized effects, close gates, closure snapshot, cascade reopen and copy-plan behavior.
|
||||
- Построить Budget UI с предупреждениями plan imbalance/underfunded card и invariant/property tests.
|
||||
|
||||
5. **Безопасное import ядро**
|
||||
- Реализовать upload validation, envelope encryption/S3 lifecycle, Celery state machine, adapter contract, reconciliation, account mapping and versioned parse runs.
|
||||
- Реализовать conservative dedupe, transfer matching, atomic confirm, reparse diff, revert import and source deletion.
|
||||
- Добавить synthetic parser inputs в Git и отдельный `PRIVATE_FIXTURES_DIR` test command; сами реальные документы никогда не коммитить.
|
||||
|
||||
6. **Пять bank adapter contracts**
|
||||
- Последовательно добавить Alfa debit/credit XLSX, Ozon debit/credit PDF, Rocketbank debit PDF.
|
||||
- Для каждого выделить detection signatures, page/sheet parser, control totals and adapter-specific warnings.
|
||||
- Adapter не считается включённым в beta, пока не проходит минимум два приватных периода своего contract и malformed/duplicate/overlap cases.
|
||||
|
||||
7. **Categorization и review queue**
|
||||
- Добавить standard/custom category localization, income sources, payees/aliases, MCC and bank maps, precedence/provenance.
|
||||
- Построить unified Review Queue для critical import issues, duplicate/transfer decisions и uncategorized expenses.
|
||||
- Проверить, что parser не знает пользовательские категории, а confirmed payee rules не пересекают spaces.
|
||||
|
||||
8. **Analytics**
|
||||
- Реализовать четыре read models/queries с правильными occurred/posted/refund semantics и server-side ranges/pagination.
|
||||
- Добавить charts + accessible tables; закрытые budget facts читать из closure snapshots, balances — из ledger.
|
||||
- Проверить агрегаты на generated 50k-operation space и reconcile totals с ledger.
|
||||
|
||||
9. **Data controls и hardening**
|
||||
- Реализовать async export, 30-day delete/cancel, object/key purge, backup deletion ledger and restore test.
|
||||
- Добавить break-glass tooling/audit, redaction, rate limits, parser sandbox limits, key rotation and security headers.
|
||||
- Завершить responsive RU/EN flows, accessibility and browser E2E.
|
||||
|
||||
10. **Staging и закрытая beta**
|
||||
- Собрать provider-neutral images, one-shot migrations, same-origin reverse proxy, health checks, backup/restore runbook and alerts.
|
||||
- Сначала прогнать synthetic data и private parser suite; затем legal/privacy gate и ограниченные email invites.
|
||||
- Включать bank adapters независимо после contract gate; не заявлять отсутствующие форматы.
|
||||
|
||||
## Проверка готовности
|
||||
|
||||
- Backend: Ruff/format, static typing, pytest unit/integration against real PostgreSQL/Redis/S3-compatible test services, Hypothesis domain properties.
|
||||
- Database: Alembic upgrade from empty DB, downgrade where safe, constraints/RLS policies, tenant escape tests, concurrent close/apply conflict tests.
|
||||
- Parsers: synthetic CI tests plus mandatory private E2E command for five contracts; malformed/oversized/encrypted/scanned/wrong-bank files fail with stable diagnostics.
|
||||
- Frontend: TypeScript, lint, Vitest + Testing Library, i18n key parity, accessibility checks.
|
||||
- E2E Playwright on desktop and mobile viewport covers invite/login, account setup, plan/funding, manual operations, import review/confirm, categorization, close/reopen, analytics, export and delete cancellation.
|
||||
- Financial properties: transfer sums zero; no double-counted card payment; balances equal opening + postings; import confirm/revert is idempotent; category/pool/envelope/reserve transitions conserve the defined available-resource equation; closed periods never change without explicit cascade reopen.
|
||||
- Analytics fixtures verify late refund on two axes, unknown income source, uncategorized expense, posting-vs-occurrence dates and archived entities.
|
||||
- Performance gate at agreed beta scale; import remains asynchronous and all operation/report lists remain paginated.
|
||||
- Disaster test restores a backup, reapplies completed deletion ledger and proves purged spaces/keys remain inaccessible.
|
||||
|
||||
## Основные риски и gates
|
||||
|
||||
- Private fixtures are an external prerequisite: an adapter cannot be declared supported without them, even if synthetic tests pass.
|
||||
- Bank layout drift must create a new adapter/parser version and review diff, never silently reinterpret historical data.
|
||||
- RLS context leakage through pooled connections/tasks is a critical test target; use transaction-local settings and fail closed when context is absent.
|
||||
- Parsing untrusted PDF/XLSX is isolated and resource-limited; library success alone is not a security boundary.
|
||||
- Envelope encryption protects object storage but is not zero-knowledge: compromised running API/worker + KEK can decrypt files; break-glass, secret isolation and audit remain necessary.
|
||||
- Russian data-location and consent requirements are release gates requiring qualified legal review, not assumptions encoded only in infrastructure.
|
||||
|
||||
Неразрешённых продуктовых решений для MVP не осталось. Репозиторий пуст, поэтому migration path для существующих данных не требуется.
|
||||
Reference in New Issue
Block a user