SRS — {{Project / Feature name}}
One-line tagline: what this system is, in 12 words or fewer.
1. Purpose
Section titled “1. Purpose”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.
2. Stakeholders & Personas
Section titled “2. Stakeholders & Personas”| Role | Who | Goal | Influence on requirements |
|---|---|---|---|
| Product Owner | … | … | Final 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) | … | … | … |
| Operator | Self-hosting admin | Keep bot running, restore from backup | Drives ops/observability NFRs |
Personas — 1 short paragraph each: context, motivation, pain, technical literacy. Avoid stereotypes; describe behavior, not demographics.
3. Scope
Section titled “3. Scope”In Scope (this version)
Section titled “In Scope (this version)”- …
- …
Out of Scope (explicitly deferred)
Section titled “Out of Scope (explicitly deferred)”- … (deferred to v0.2 — see Roadmap)
- … (non-goal — never)
Explicit
Out of Scopeis what makes a scope statement useful. “Не написано — не точно” — погано; “Не написано і явно виключено” — добре.
4. Glossary
Section titled “4. Glossary”| Term | Definition |
|---|---|
| Account | A logical wallet (cash / card / deposit) belonging to a user. |
| Transaction | A single movement of money: expense, income, or transfer. |
| Ledger | Append-only store of double-entry transfers. |
| … | … |
5. Domain Model
Section titled “5. Domain Model”5.1 Entities (overview)
Section titled “5.1 Entities (overview)”Short prose: 4–8 lines describing the core entities and their relationships.
5.2 ERD
Section titled “5.2 ERD”5.3 Lifecycle / state diagrams
Section titled “5.3 Lifecycle / state diagrams”If any entity has non-trivial states, draw it. Skip otherwise.
6. Functional Requirements
Section titled “6. Functional Requirements”Format: user stories with BDD acceptance criteria. Each story has a stable ID
FR-<MODULE>-<NN>. Acceptance criterion has its own IDAC-<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-1 — Given the user is authenticated When they send a message matching
^\s*(\d+([.,]\d{1,2})?)\s+(.+)$Then the system MUST persist a Transaction of kindexpensewith the parsed amount in minor units, current timestamp, and category resolved by alias-match (case-insensitive). - AC-FR-EXP-01-2 — Given the user sends a message that does not match the format When the system parses it Then it MUST reply with
ERR_PARSE_001and the example “1500 метро”, and MUST NOT persist anything. - AC-FR-EXP-01-3 — Given no category alias matches the input When the parser resolves the category Then it MUST fall back to the system category
otherand 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-1 — Given 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-2 — Given source and destination are the same account When the system validates the transfer Then it MUST reject with
ERR_LDG_002and persist nothing. - AC-FR-LDG-01-3 — Given source account would go below
min_balanceafter the transfer When the validation runs Then the system MUST reject withERR_LDG_003.
Add as many FR sections as needed. Group by module (
EXP,LDG,BUD,RPT,AUTH, …).
7. Non-Functional Requirements
Section titled “7. Non-Functional Requirements”Numbers, not adjectives. “Fast” → “P95 ≤ 500ms”. If you can’t measure it, don’t write it.
7.1 Performance
Section titled “7.1 Performance”- NFR-PERF-01 — P95 latency from inbound Telegram update to bot reply MUST be ≤ 800 ms at MVP load (≤ 5 rps per pod).
7.2 Reliability & Availability
Section titled “7.2 Reliability & Availability”- 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).
7.3 Security & Privacy
Section titled “7.3 Security & Privacy”- 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.
7.4 Observability
Section titled “7.4 Observability”- NFR-OBS-01 — Every inbound update gets a
request_idcorrelated 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.
7.5 Maintainability / Portability
Section titled “7.5 Maintainability / Portability”- NFR-MNT-01 — Ledger interface MUST allow swapping the storage backend (TigerBeetle ↔ Postgres double-entry) without changing call sites in domain code.
8. Constraints & Assumptions
Section titled “8. Constraints & Assumptions”Constraints
Section titled “Constraints”- 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.
Assumptions
Section titled “Assumptions”- 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.
9. External Interfaces
Section titled “9. External Interfaces”| Interface | Direction | Protocol | Purpose | Notes |
|---|---|---|---|---|
| Telegram Bot API | in/out | HTTPS long-polling | User messaging | Token in env; rate limits 30 msg/sec/user |
| PostgreSQL | out | TCP/5432, asyncpg | System of record (users, accounts, categories, txn metadata) | Pool size 10 |
| TigerBeetle | out | TCP/3000, native client | Ledger (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).
10. Roadmap (post-MVP)
Section titled “10. Roadmap (post-MVP)”| Phase | Scope addition | Trigger to start |
|---|---|---|
| MVP-1 | Single user (you + partner), Tier-2 features | now |
| MVP-2 | Self-onboarding, multi-tenant isolation, simple recurring transactions | when ≥ 3 external users want in |
| MVP-3 | Multi-currency, FX revaluation, goals, CSV import/export | when MVP-2 stable for 1 month |
| v1 | Paid tier, advanced reports, web companion | later |
Appendix A — Architecture Diagrams
Section titled “Appendix A — Architecture Diagrams”A.1 C4 Level 1 (System Context)
Section titled “A.1 C4 Level 1 (System Context)”A.2 C4 Level 2 (Containers)
Section titled “A.2 C4 Level 2 (Containers)”PlantUML for precision; embed rendered PNG.
A.3 Sequence — “Add expense”
Section titled “A.3 Sequence — “Add expense””Mermaid sequence; embed.
Appendix B — Data Dictionary
Section titled “Appendix B — Data Dictionary”Concrete column-level spec for each entity. Implementer builds DDL from this.
| Entity | Field | Type | Format | Required | Default | Min/Max | Notes |
|---|---|---|---|---|---|---|---|
| User | id | UUID | uuid-v4 | yes | gen_random_uuid() | — | PK |
| User | telegram_id | bigint | int64 | yes | — | — | UK |
| Transaction | amount_minor | bigint | int64 | yes | — | ≥ 0 | minor units; e.g. kopecks |
| Transaction | currency | text | ISO 4217 | yes | user.default_currency | 3 chars | |
| … | … |
Appendix C — Risks & Open Questions
Section titled “Appendix C — Risks & Open Questions”| ID | Description | Likelihood | Impact | Mitigation |
|---|---|---|---|---|
| R-01 | TigerBeetle Python client breaking changes pre-1.0 | M | M | Wrap behind ledger interface (NFR-MNT-01) |
| R-02 | Telegram rate-limits during burst of imports | L | M | Queue + backoff |
Open Questions
Section titled “Open Questions”| ID | Question | Affects | Owner |
|---|---|---|---|
| OQ-01 | Multi-currency in MVP-1 or deferred? | Domain model, FR-EXP | PO |
Appendix D — Changelog
Section titled “Appendix D — Changelog”| Version | Date | Change | Author |
|---|---|---|---|
| 0.1.0 | YYYY-MM-DD | Initial draft | @zipsybok |