Перейти до вмісту

SRS — {{Project / Feature name}}

Цей контент ще не доступний вашою мовою.

One-line tagline: what this system is, in 12 words or fewer.


Why does this system exist? What problem does it solve, for whom? One short paragraph (3–6 sentences). No marketing language. No solution wording — describe the gap, not the fix.


RoleWhoGoalInfluence on requirements
Product OwnerFinal FR/NFR sign-off
Primary user (Persona A)”I want to track my spending without opening an app”Drives UX latency NFRs
Secondary user (Persona B)
OperatorSelf-hosting adminKeep bot running, restore from backupDrives ops/observability NFRs

Personas — 1 short paragraph each: context, motivation, pain, technical literacy. Avoid stereotypes; describe behavior, not demographics.


  • (deferred to v0.2 — see Roadmap)
  • (non-goal — never)

Explicit Out of Scope is what makes a scope statement useful. “Не написано — не точно” — погано; “Не написано і явно виключено” — добре.


TermDefinition
AccountA logical wallet (cash / card / deposit) belonging to a user.
TransactionA single movement of money: expense, income, or transfer.
LedgerAppend-only store of double-entry transfers.

Short prose: 4–8 lines describing the core entities and their relationships.

PlantUML Diagram

If any entity has non-trivial states, draw it. Skip otherwise.

PlantUML Diagram

Format: user stories with BDD acceptance criteria. Each story has a stable ID FR-<MODULE>-<NN>. Acceptance criterion has its own ID AC-<FR-ID>-<n>. Every IF criterion has a paired ELSE / error code. Use must / shall consistently — pick one and stick to it.

FR-EXP-01 — Add an expense from a free-text message

Section titled “FR-EXP-01 — Add an expense from a free-text message”

As a Persona-A user I want to type “250 кафе” into the bot chat so that my expense is recorded with the right category without opening any UI.

Acceptance criteria:

  • AC-FR-EXP-01-1Given the user is authenticated When they send a message matching ^\s*(\d+([.,]\d{1,2})?)\s+(.+)$ Then the system MUST persist a Transaction of kind expense with the parsed amount in minor units, current timestamp, and category resolved by alias-match (case-insensitive).
  • AC-FR-EXP-01-2Given the user sends a message that does not match the format When the system parses it Then it MUST reply with ERR_PARSE_001 and the example “1500 метро”, and MUST NOT persist anything.
  • AC-FR-EXP-01-3Given no category alias matches the input When the parser resolves the category Then it MUST fall back to the system category other and the reply MUST flag this fallback so the user can correct it.

FR-LDG-01 — Transfer between own accounts

Section titled “FR-LDG-01 — Transfer between own accounts”

As a user with two accounts I want to record a transfer “1500 з картки на готівку” so that my account balances stay consistent.

Acceptance criteria:

  • AC-FR-LDG-01-1Given a parsed transfer with valid source and destination accounts When the system records it Then it MUST create exactly one ledger entry that debits source and credits destination atomically. Partial-write of one side without the other MUST NOT be observable.
  • AC-FR-LDG-01-2Given source and destination are the same account When the system validates the transfer Then it MUST reject with ERR_LDG_002 and persist nothing.
  • AC-FR-LDG-01-3Given source account would go below min_balance after the transfer When the validation runs Then the system MUST reject with ERR_LDG_003.

Add as many FR sections as needed. Group by module (EXP, LDG, BUD, RPT, AUTH, …).


Numbers, not adjectives. “Fast” → “P95 ≤ 500ms”. If you can’t measure it, don’t write it.

  • NFR-PERF-01 — P95 latency from inbound Telegram update to bot reply MUST be ≤ 800 ms at MVP load (≤ 5 rps per pod).
  • NFR-REL-01 — Ledger writes MUST be atomic across debit+credit; no partially-applied transfers.
  • NFR-REL-02 — Bot MUST recover within 30 s after PostgreSQL or TigerBeetle restart, without losing inbound updates (Telegram redelivers via long polling).
  • NFR-SEC-01 — Each user can only read/write their own data; account isolation enforced at query level (every query filtered by user_id).
  • NFR-SEC-02 — Telegram bot token, DB credentials, TigerBeetle cluster-id MUST come from env / secret store; never committed.
  • NFR-SEC-03 — No PII or transaction amounts in logs at INFO level.
  • NFR-OBS-01 — Every inbound update gets a request_id correlated through logs, DB queries, and ledger calls.
  • NFR-OBS-02 — Metrics exported in Prometheus format: bot_messages_total{kind=…}, ledger_transfer_duration_seconds, db_pool_in_use.
  • NFR-MNT-01 — Ledger interface MUST allow swapping the storage backend (TigerBeetle ↔ Postgres double-entry) without changing call sites in domain code.

  • C-01 — Telegram is the only UI for MVP. No web, no mobile.
  • C-02 — Self-hosted on a single VPS for MVP-1; no managed services beyond Telegram itself.
  • C-03 — Python 3.12+, aiogram 3.x.
  • A-01 — Users tolerate 30-second outages during deploys (not a payment system).
  • A-02 — TigerBeetle 0.16+ Python client is stable enough for personal-scale traffic.
  • A-03 — Users self-onboard without admin intervention from MVP-2 onwards.

If an assumption later turns out false → it becomes a Risk in Appendix C.


InterfaceDirectionProtocolPurposeNotes
Telegram Bot APIin/outHTTPS long-pollingUser messagingToken in env; rate limits 30 msg/sec/user
PostgreSQLoutTCP/5432, asyncpgSystem of record (users, accounts, categories, txn metadata)Pool size 10
TigerBeetleoutTCP/3000, native clientLedger (atomic double-entry transfers, balances)Cluster id pinned in env

Each external interface that has a contract gets its own document under docs/contracts/ (one OpenAPI / one client-spec file per integration).


PhaseScope additionTrigger to start
MVP-1Single user (you + partner), Tier-2 featuresnow
MVP-2Self-onboarding, multi-tenant isolation, simple recurring transactionswhen ≥ 3 external users want in
MVP-3Multi-currency, FX revaluation, goals, CSV import/exportwhen MVP-2 stable for 1 month
v1Paid tier, advanced reports, web companionlater

PlantUML Diagram

PlantUML for precision; embed rendered PNG.

Mermaid sequence; embed.


Concrete column-level spec for each entity. Implementer builds DDL from this.

EntityFieldTypeFormatRequiredDefaultMin/MaxNotes
UseridUUIDuuid-v4yesgen_random_uuid()PK
Usertelegram_idbigintint64yesUK
Transactionamount_minorbigintint64yes≥ 0minor units; e.g. kopecks
TransactioncurrencytextISO 4217yesuser.default_currency3 chars

IDDescriptionLikelihoodImpactMitigation
R-01TigerBeetle Python client breaking changes pre-1.0MMWrap behind ledger interface (NFR-MNT-01)
R-02Telegram rate-limits during burst of importsLMQueue + backoff
IDQuestionAffectsOwner
OQ-01Multi-currency in MVP-1 or deferred?Domain model, FR-EXPPO

VersionDateChangeAuthor
0.1.0YYYY-MM-DDInitial draft@zipsybok