@tannerntannern/budgeteer

A specialized constraint solver for budget flows

Usage no npm install needed!

<script type="module">
  import tannerntannernBudgeteer from 'https://cdn.skypack.dev/@tannerntannern/budgeteer';
</script>

README

🎩 budgeteer

npm version min size

a specialized constraint solver for budget flows

Overview

Budgeteer allows you to effortlessly balance a budget without doing any monotonous work. It lets you define intuitive resource flows and automatically balance them for you. "Resource" is purposely non-specific here -- you can use this tool to balance a monetary budget, manage your time, track calories, or whatever you want.

Budgeteer is written in TypeScript and relies on kiwi.js to do constraint solving under the hood.

Check out the demo website to see it in action, or keep reading if you want to integrate the API yourself.

Installation

npm install @tannerntannern/budgeteer

or

yarn add @tannerntannern/budgeteer

Usage example

import { supply, pipe, consumer, solve } from '@tannerntannern/budgeteer';

// 1. Build a network
const wages = supply('Wages', 2500);
const checking = pipe('Checking');
const expenses = pipe('Expenses');

wages
    .supplies(700).to(consumer('Taxes'))
    .supplies(1200).to(checking)
    .suppliesAsMuchAsPossible().to(consumer('Savings'));

checking
    .suppliesAsMuchAsNecessary().to(expenses)
    .suppliesAsMuchAsPossible().to(consumer('Spending Money'));

consumer('Rent').consumes(900).from(expenses);
consumer('Groceries').consumes(200).from(expenses);

// 2. Balance the network and view results
const results = solve();

results.transfers.forEach((node1, node2, amount) => {
    console.log(`${node1.name} -- ${amount} --> ${node2.name}`);
});

Which will print:

Wages -- $700 --> Taxes
Wages -- $1200 --> Checking
Wages -- $600 --> Savings
Checking -- $1100 --> Expenses
Checking -- $100 --> Spending Money
Expenses -- $900 --> Rent
Expenses -- $200 --> Groceries

Notice how the unspecified values (savings, expenses, and spending) have all been calculated for you.

API

For more detailed information, see the generated docs.

Node Types

Budgeteer has three functions for modelling a resource network:

Function Description Example
supply(name, amount, multiplier?) Creates a supply node, from which other nodes can draw resources from Wages, savings interest
consumer(name) Creates a consumer node, which can draw resources from other nodes, but cannot provide Rent, grocery expenses
pipe(name) Creates a mixture between a supply and consumer node; it can both draw and provide resources, although the pipe must draw at least as much as it provides Bank accounts

Key Terms

Nodes that provide are "consumable," and nodes that can recieve supply are "supplyable." Thus, supply nodes are consumable, consumer nodes are supplyable, and pipe nodes are both.

Node Relationships

Relationships (i.e., flows) between nodes are established through a chainable API. Each flow requires two function calls: one that specifies how much, and another that specifies where. For example: income.supplies(1000).to(rent)

Consumable Node Relationships

All consumable nodes have the following methods, each one followed with a .to(<supplyable node>) call, similar to the example above:

Function Description
supplies(amount) Supplies a fixed amount to another node
suppliesAsMuchAsNecessary() Supplies only as much as the recieving node needs
suppliesAsMuchAsPossible() Supplies any remaining resources to another node

Supplyable Node Relationships

All supplyable nodes have the following methods, each one followed with a .from(<consumable node>) call. For example: rent.consumes(1000).from(wages):

Function Description
consumes(amount) Consumes a fixed amount from another node
consumesAsMuchAsNecessary() Consumes only as much as necessary from the supplying node
consumesAsMuchAsPossible() Consumes any remaining resources from the supplying node

Balancing the Network

To resolve the network use solve(), which takes the nodes created by the supply, consumer, and pipe functions, along with all the relationships established between them, and calculates the resulting balances and transfers.

solve() is called without arguments. If the network can't be balanced, it will throw an error. Otherwise, it will return an object with three data structures:

Property Description
allNodes An array of all the nodes that were created by the three node type functions
tranfers A TwoKeyMap (see the generated docs) that maps pairs of nodes to the amount transferred between them
balances An ES6 Map of the final balance at each node after all the consuming and supplying is over

Resetting the Network

If you want to clear all nodes and setup a new network, use the reset() function.

How the Math Works

I recently made a post that, among other things, talks in detail about how these function calls translate to mathematical constraints. If you're interested, here's a link.

Author

Tanner Nielsen tannerntannern@gmail.com

Website | GitHub