Requirements for the budget application.
Requirement 1: Basics
Requirement 1.1
The application infrastructure is defined in CDK (TypeScript). The application itself is implemented as an API written in TypeScript, with a frontend in HTML and CSS and a little bit of JavaScript where needed. The business logic is implemented in the “backend” lambda, which includes page rendering. JavaScript in the browser is used for presentation only.
Requirement 1.2
The application is serverless, using the following AWS services to run:
- Lambda to implement application logic and auth
- DynamoDB in on-demand pricing mode for storage
- S3 for assets such as CSS, favicon and presentational JavaScript.
- CloudFront for TLS and caching (if needed)
Requirement 2: System Entities
System entities are split into three groups:
- “Timeless” entities
- Events
- Reconstructible entities
Requirement 2.1: Timeless entities
“Timeless” entities pop into existence when they are needed (for example when another entity needs to refer to them). They may have metadata associated with them, but they can never be delete once they are referred to. For simplicity, they can never be deleted (to avoid having to keep reference counts).
Examples of timeless entities: accounts, pots, plans, categories, goals.
Requirement 2.2: Events
Events have a strict order and are generally immutable apart from changes that are required to maintain the order. Event order never changes and for each event it’s always possible to say which event comes after it.
Examples of events: transaction creation event, transaction update event, transaction deletion event.
Requirement 2.3: Reconstructible entities
Reconstructible entities can be deleted and reconstructed from other entities. This sounds quite abstract, but in reality there are two such entites: transactions and snapshots.
Transactions can be constructed from a stream of events.
Snapshots can be constructed from a stream of transactions.
Requirement 2.4: Metadata
Metadata is attached to entities and is versioned (with a timestamp). For example, a transaction description or a plan name are metadata that can be changed but previous versions can be looked at.
Note the implications: A transaction may refer to a category (a timeless entity) whose current metadata is “name=food”. The category’s metadata can be updated to “name=supermarket”. This change will be reflected immediately in every transaction that uses this category. It doesn’t matter if the transaction was created before the category’s metadata was changed. But it is possible to query the metadata’s previous versions to see that it used to be “name=food” at some point (also when it changed).
Requirement 3: Timeless Entities
Requirement 3.1: Accounts
Requirement 3.1.1
An account is an entity with an account id (uuid) and metadata. The metadata includes:
- Account name
- Optional notes (freetext)
An account can be the source or the destination of a transaction. An account will have a balance that is derived from a stream of transaction. The balance is not stored in the account and is not part of the account, it is simply a derived property that is associated with the account. The balance can be positive, zero or negative without limitation.
Requirement 3.1.2
The user can add an account.
Requirement 3.1.3
The user can update the account’s metadata.
Requirement 3.2: Pots
A pot is an entity with a pot id (uuid) and metadata. It belongs to an account, and so also holds the account id. The pot metadata includes:
- The pot’s name
- Optional notes (freetext)
A pot belongs to an account. An account may have zero or more pots.
Requirement 3.2.1
A pot can be the source or the destination of a transaction.
A pot will have a balance that is derived from a stream of transaction. The balance is not stored in the pot and is not part of the pot, it is simply a derived property that is associated with the pot.
Requirement 3.2.2
The balance can be positive or zero but may not be negative. A pot is an abstract allocation of funds, and a negative sum cannot be allocated. If a transaction removes an amount that is greater that the amount currently associated with the pot, that amount becomes zero, and the rest of the transaction’s amount is taken directly from the holding account (which can be associated with a negative balance).
Requirement 3.2.3
A transaction can transfer an amount between pots. The same rule applies. For example, if Pot A is associated with 100 and Pot B is associated with 200, and the transaction moves 400 from Pot B to Pot A, then 200 is take from Pot B (it cannot be associated with a negative balance), and the rest is taken directly from account. The total in the account is 300, so after the transaction Pot A is associated with 500 (100 + 400) and the account is associated with -200. Overall the total is still 300, but the allocation shows that 200 has been “over-allocated” to Pot A.
Requirement 3.2.4
The user can add a pot to an account.
Requirement 3.2.5
The user can update the pot’s metadata.
Requirement 3.3: Plans
A plan is an event/transaction generator. The plan has an id (a uuid). Its metadata includes
- The plan’s name
- The plan’s version
- Optional notes (freetext)
- Hidden (boolean)
- The transaction template:
- amount
- account details
- transaction’s metadata (category, description)
- Start date
- Schedule
- Optional end date
- Optional last realised date
Requirement 3.3.1: Plan Schedule
The schedule can be one-off (in which case the start date is the plan’s date) or recurring, with various rules, at a minimum:
- Weekly on a given weekday, e.g. every Monday
- Monthly on a given month day, e.g. every 10th
The monthly schedule is a bit tricky, for example if the day is 31, then it will take place on:
- 31st January
- 28th February (or 29th on a leap year)
- 31st March
- 30th April etc.
In addition, it can be “the Friday before the date, if it falls on a weekend”, “the Monday after the date, if it falls on a weekend” or even more complicated, taking bank holidays and national holidays into account. However, at a minimum it will support plain monthly schedule (without weekend / holidays).
Requirement 3.3.2: Last Realised Date
The last realised date of a plan tells use when to generate events from. For example, if the last realised date is 2025-12-10 and the schedule is monthly on the 20th of the month, then the next event the plan generates is on 2025-12-20. If we try to generate all the events before 2025-12-16, then we will get an empty list, but the “last realised” date will be updated to 2025-12-16.
Requirement 3.3.3: Transactions and Events
The plan can generate both events and transactions. Events are generated when the plan is realised for a date, and are then appended to the event log. Transactions are generated for information only, and are not persisted.
Generating transactions is used for budget predictions. For example, it should be possible to see all the planned transactions for a specific month, two years in the future, and also the impact on balances of accounts and pots, and whether goals will be achieved.
Requirement 3.3.4
The user can create a new plan.
Requirement 3.3.5
The user can update the plans’s metadata.
Requirement 3.3.6
Plans are realised regularly in order to keep accounts and pots up to date. Events generated from plans include the plan id and the plan version. Transactions that are created from those events also include the plan id and the plan version. It is possible to update such transactions (for example, amend their actual date vs. planned date, or actual amount vs. planned amount) using new events. It is possible to compare the transaction to the planned transaction at any time (even if the plan has changed since the transaction was created), as it includes the plan id and the plan version.
Requirement 3.4: Categories
A category has an id (a uuid) and metadata:
- The category’s name (“the category”)
- Optional notes (freetext)
Requirement 3.4.1
The user can create a new category.
Requirement 3.4.2
The user can update the category’s metadata.
Requirement 3.5: Goals
A goal has an id (a uuid). It is associated with a pot. A pot may be associated with zero or one goal.
The goal also has the following metadata:
- The goal’s name
- The goal’s target amount
- An optional goal date
Requirement 3.5.1
Based on the goal’s target amount, the pot’s associated balance and the goal’s date, it is possible to calculate the required amount per unit of time to reach the goal. For example, if the pot is associated with 900 and the goal is 1500 in four month’s time, then a monthly addition of 150 is required to reach the goal. Is it possible to tell whether the goal has been achieved (the pot is assoicated with a sum >= the goal) and whether it has been achieved on time.
Requirement 4: Events
The data model and behaviour is based on the “event source” model, with respect to transactions. Events are strictly ordered, and can be used to reconstruct a stream of transactions.
The system does not model metadata changes as event as they are applied immediately. For example, changing an account name applies immediately everywhere, whereas if it were modelled as an event it would have only applied to transactions that were created after the name change.
There are three event types:
- Create transaction event
- Update transaction event
- Delete transaction event
Each event has an event creation timestamp, used for strictly ordering the events in the order they were created. An event created on 2025-02-01 may be a transaction creation event for a transaction with a date of 2025-01-09. They do not have to match.
An event has an event id (a ULID).
Requirement 4.1: Create Event
Transaction creation events include:
- “Core” transaction information:
- Date
- Amount
- Account information
- Transaction metadata
- Category id
- Description
The core information affects account balance calaculations. Metadata does not.
Requirement 4.2: Update Event
Transaction update events include:
- Transaction id
- Core transaction information
Updates do not touch the metadata which is considered “timeless”.
Requirement 4.3: Delete Event
Transaction delete events include:
- Transaction id
Deletes also delete the metadata, to keep things clean.
Requirement 5: Transactions
A transaction models the movement of an amount of money between entities (accounts or pot). In addition a transaction id (a uuid), transactions have the following core data:
- Amount
- Date
- Change information
Change information includes the change type: income, expense, transfer. Depending on the type, it also includes a source, a destination or both. Source and destination can be an account or pot. It is not possible to transfer between the same entity as both source and destination.
Requirement 5.1
There is some ambiguity with date. Transactions are not ordered if they have the same date. It is not possible to say which one happens first. This may lead to some issues, because not all transactions are commutative. For example, if pot A has 200 and there are two transactions on the same day, one income of 300 and one expense of 300, the result will differ according to the order we apply the transactions. For now this will stay ambiguous, additional rules may be added later.
Requirement 5.2
Transactions are reconstructed from a stream of events. We will obtain the exact same transactions if we process the same stream of events.
Requirement 6: States
A state is the result of “applying” a transaction to the previous state. The initial state of the system is one where all balances of all entities (accounts and pots) are zero.
For example, applying an income transaction of 100 for account X on the initial state produces a new state where a balance of 100 is associated with account X.
Requirement 6.1: Dates
Transactions are applied to states in date order. For the same date there could be multiple transactions, in which case they are applied in a consistent order. The “natural” order is by creation timestamp (a transaction that is created later is applied later). However, this is arbitrary, because transaction date may change, and so an “old” transaction which is originally applied last on its original date may get a new date, where it will be the first transaction to be applied.
Requirement 6.2: Snapshots
A state can be stored as a snapshot. If transactions that are applied before the state’s date don’t change, it is possible to use the snapshot as a base for later transactions. This can serve as a caching mechanism to avoid applying all the transactions from the earliest one.
Requirement 6.2.1: Invalidation
If a transaction that was used to construct the state is changed in any way, that invalidates all snapshots with a date equal to or later than that transaction.
While it may be possible to avoid this invalidation by carefully considering the impact of the change (for example, if it’s a date change that is “harmless”), for simplicity all “potentially impacted” snapshots are invalidated.
Requirement 6.2.2
If a transaction’s date changes, the earlier of the old date and the new date must be considered for snapshot invalidation.
Requirement 7: User Experience
The application serves HTML pages to the user. This section is under-specified on purpose, as requirements are vague and multiple iterations will be required to find what works.
Requirement 7.1: Accounts, Pots and Goals
Requirement 7.1.1
The application will serve a page that allows the user to see the accounts, the pots and the goals (if any), including names, dates, descriptions and notes.
The user will be able to edit any piece of metadata on the page and submit it.
Requirement 7.2: Transactions
Requirement 7.2.1
The application will serve a page that shows the transactions for a given year and month. The page will default to the current year and month, but will have naviation buttons which will request a new page for the given year and month. For example, if the user clicks on the “next month” button, it will request the page with parameters that specify the year and month.
Requirement 7.2.2
The page is specific to an account (and optionally pot). Dropdown menus will allow the user to select a different account and/or pot. The year and month settings will be the same when a different account/pot is selected
Requirement 7.2.3
The transactions will be displayed as a list, ordered by date and creation timestamp. A running balance for the selected account/pot will be included in the list, as well as starting and ending balance.
Requirement 7.2.4
The transaction page will allow the user to submit a new transaction, including:
- Amount
- Date
- Change information (source/destination accounts/pots)
- Category
- Description
Requirement 7.3: Categories
Requirement 7.3.1
The application will serve a page that allows the user to see the list of categories with an edit icon next to each category name and notes.
Requirement 7.3.2
The application will serve a page that allows the user to edit a category name and notes, and submit the change. The application will then serve the category list as in 7.3.1.
Requirement 7.4: Plans
Requirement 7.4.1
The application will serve a page with the list of active plans. The user will be able to edit any plan on the page and submit the page.
Requirement 7.5: Calendar View
Requirement 7.5.1
The application will serve a page, similar to the transaction page, only where the data is presented on a monthly calendar for the selected year, month and account/pot. The running total will be shown on the calendar as well as starting balance and ending balance.
The user will be able to see additional information for any day in the calendar: A list of all the transactions for that day with all the details.