Skip to content

Commit

Permalink
Merge pull request #29 from drym-org/debts-and-advances
Browse files Browse the repository at this point in the history
Modeling Debts and Advances
  • Loading branch information
countvajhula authored Oct 2, 2024
2 parents 2c568c3 + bc50f2c commit 90428f1
Show file tree
Hide file tree
Showing 25 changed files with 1,377 additions and 1,003 deletions.
15 changes: 7 additions & 8 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,9 @@ jobs:
strategy:
matrix:
py:
- "3.9"
- "pypy3.9"
- "3.11"
os:
- "macos-latest"
- "ubuntu-latest"
architecture:
- x64
name: "Python: ${{ matrix.py }}-${{ matrix.architecture }} on ${{ matrix.os }}"
Expand All @@ -29,9 +28,9 @@ jobs:
- name: Install dependencies
run: make build-for-test
- name: Run tests
run: make test-matrix
run: pytest
coverage:
runs-on: macos-latest
runs-on: ubuntu-latest
name: Report coverage
env:
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
Expand All @@ -42,21 +41,21 @@ jobs:
- name: Setup python
uses: actions/setup-python@v4
with:
python-version: 3.9
python-version: 3.11
architecture: x64
- name: Install dependencies
run: make build-for-test
- name: Report coverage
run: make cover-coveralls
lint:
runs-on: macos-latest
runs-on: ubuntu-latest
name: Lint the package
steps:
- uses: actions/checkout@v3
- name: Setup python
uses: actions/setup-python@v4
with:
python-version: 3.9
python-version: 3.11
architecture: x64
- name: Install dependencies
run: make build
Expand Down
1 change: 1 addition & 0 deletions .ignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
!/.github/
34 changes: 31 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,50 @@ You could do this in a virtual environment or at the system / user level if you
$ make build
```

You may also need to run:

```
$ make build-for-test
```

Together, these should ensure that you have all development dependencies so that the rest of the `make` targets should work.

# Running Tests

## Unit Tests

```
$ make test
```

## Debugging
## Integration Tests

```
$ pytest tests/integration
```

NOTE: We do have a `make` target for this:

```
$ make test-integration
```

But this does not work even though it runs a command identical to the above, due to some weird dependency issue with pytest. For now, just use the earlier command in the shell directly.

# Debugging

To debug a test execution using a step debugger, put this in the body of the test you'd like to debug:

```
import pudb; pudb.set_trace()
```

or simply,

```
import pudb; pu.db
```

Now, when you run `make test` (for example), it will put you in the debugger, allowing you to step through the execution of the code. Here are some things you can do:

* `n` - next
Expand All @@ -32,8 +62,6 @@ Now, when you run `make test` (for example), it will put you in the debugger, al

There's more handy stuff that you can do like setting breakpoints and visiting other modules. Press `?` to see all the options.

*Note*: the official docs for `pudb` say that we could also use `pu.db()` instead of `pudb.set_trace()`, but this doesn't seem to work.

# Linting

Linter:
Expand Down
22 changes: 0 additions & 22 deletions DEV.md

This file was deleted.

11 changes: 9 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ DOCS-PATH=docs

export PYTEST_DISABLE_PLUGIN_AUTOLOAD = 1
UNIT_TESTS_PATH = tests/unit
INTEGRATION_TESTS_PATH = tests/integration

help:
@echo "clean - remove all build, test, coverage and Python artifacts"
Expand All @@ -23,6 +24,7 @@ help:
@echo "lint - alias for lint-source"
@echo "black - run black auto-formatting on all code"
@echo "test-unit - run unit tests"
@echo "test-integration - run integration tests"
@echo "test - run specified tests, e.g.:"
@echo " make test DEST=tests/unit/my_module.py"
@echo " (defaults to unit tests if none specified)"
Expand Down Expand Up @@ -93,7 +95,12 @@ black:
test-unit:
python setup.py test --addopts $(UNIT_TESTS_PATH)

test-all: clean-test test-unit
# NOTE: does not work! Only works when this identical
# command is run directly at the command line
test-integration:
pytest $(INTEGRATION_TESTS_PATH)

test-all: clean-test test-unit test-integration

test:
ifdef DEST
Expand Down Expand Up @@ -146,4 +153,4 @@ sdist: clean
python setup.py sdist
ls -l dist

.PHONY: help build build-for-test docs clean clean-build clean-pyc clean-test lint-source lint-tests lint-all lint black test-unit test-all test test-stop test-debug test-matrix test-tldr test-wiki debug coverage cover-coveralls sdist
.PHONY: help build build-for-test docs clean clean-build clean-pyc clean-test lint-source lint-tests lint-all lint black test-unit test-integration test-all test test-stop test-debug test-matrix test-tldr test-wiki debug coverage cover-coveralls sdist
Binary file added docs/assets/img/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
80 changes: 16 additions & 64 deletions docs/oldabe.scrbl
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
#lang scribble/manual

@require[racket/runtime-path]

@title{Old Abe: The Accountant for All of your ABE Needs}

@(define-runtime-path logo-path "assets/img/logo.png")
@(if (file-exists? logo-path)
(image logo-path #:scale 1.0)
(printf "[WARNING] No ~a file found!~%" logo-path))

@table-of-contents[]

@section{Intro}
Expand All @@ -25,6 +32,8 @@ Old Abe considers precisely three inputs in doing all of its accounting, and it

@item{Attributions -- an association of contributor to percentage of value allocated from the value represented by the project as a whole.}

@item{Instruments -- an association of an instrument to percentage of value allocated from the value represented by the project as a whole.}

@item{Price -- a generic "fair market value" provided by the project to its users (Like many concepts, this concept named price has a distinct role in ABE from its traditional role in capitalism).}

@item{Valuation -- the assessed present value of the project as a whole.}
Expand All @@ -34,81 +43,24 @@ All of these are determined through the process of Dialectical Inheritance Attri

@section{Accounting Flows}

There are several actions ("accountable actions") pertaining to a project that trigger accounting by Old Abe. These are:
Current accounting flows are a mix of manual and automated actions. Old Abe is not directly connected to any financial systems, so its primary role is to run the accounting logic, keep track of any project investors, and tell the maintainer how much to pay project contributors. It is up to the maintainer to record incoming payments (@code{abe/payments/}) and outgoing payouts (@code{abe/payouts/}).

@itemlist[
#:style 'ordered

@item{Work is done for the project.}
@item{Recording a Payment - triggers a GitHub Action that runs the accounting logic (details below) and produces a report of all Outstanding Balances as a GitHub Issue. The maintainer can refer to the Issue to find out how much to pay.}

@item{A financial contribution is made to the project.}

@item{An appointed project representative fulfills a payout to a contributor.}

@item{A "fiat" change is made to one of the inputs, i.e. either attributions, price or valuation, which is typically a resolution by DIA.}
@item{Recording a Payout - triggers a GitHub Action that simply updates the Outstanding Balances issue to reflect the updated amounts owed.}

]

We will learn more about each of these, in turn.

@subsection{Work}

Work done could be either labor, capital, or ideas, as defined in the @hyperlink["https://github.com/drym-org/finance/blob/main/finance.md"]{ABE financial model}. Regardless of what kind of work it is, its appraisal takes the form of an "incoming attribution," which is an association of a set of contributors to percentage of value contributed, as judged in related to existing attribution allocations in the project.

Old Abe will account this by "renormalizing" the attributions to total to 100% after incorporating the fresh values.

TODO: flesh out

@subsection{Payment}

When a payment comes in, we first pay out any @tech{instruments}. Then, with the remaining amount, we pay project contributors.

TODO: flesh out

@subsection{Payout}

TODO: flesh out

@subsection{Fiat Change in Inputs}

TODO: flesh out, including backpropagation

@section{Modules}

The accounting flows mentioned earlier correspond to distinct modules that handle them.

@subsection{Money In}

This module handles incoming payments.

First it finds all payments that have not already been processed, that
is, which do not appear in the transactions file.

For each of these payments, it consults the current attributions for
the project, and does three things.

First, it figures out how much each person in the attributions file is
owed from this fresh payment, generating a transaction for each
stakeholder.

Second, it determines how much of the incoming payment can be
considered an "investment" by comparing the project price with the
total amount paid by this payer up to this point -- the excess, if
any, is investment.
When someone makes a payment to a project, Old Abe allocates portions of that payment to project contributors and creates a report that tells maintainers how much money the project owes to each individual contributor. We'll get deep into the weeds of how it does that in a moment. If the incoming payment represents an investment (that is, it brings the payer's total amount paid above the project price), the payer is considered a project contributor. The system adds them to the attributions file with a share equal to their investment (or increases their pre-existing attributive share). The project valuation is increased by the investment amount and all existing attributive shares are diluted so that percentage shares still add up 100%.

Third, it increases the current valuation by the investment amount
determined, and, at the same time, "dilutes" the attributions by
making the payer an attributive stakeholder with a share proportionate
to their incoming investment amount (or if the payer is already a
stakeholder, increases their existing share) in relation to the
valuation.
Now, let's talk about how Old Abe allocates an incoming payment.

@subsection{Money Out}
First, we pay off any processing fees (found in @code{instruments.txt}). These fees have fixed percentages that apply to every incoming payment and do not get diluted by investments. They are somewhat analogous to credit card fees. They go towards the Old Abe system itself and to those who have contributed to the DIA process for this project.

This module determines outstanding balances owed.
Next, we divide the remainder among the contributors in the attributions file. Ideally, this is as simple as dividing the amount according to each contributor's attributive share. However, sometimes certain contributors are temporarily unpayable (e.g. they might not have provided their payment information yet, etc.). In that case, we record the amount owed to that contributor as a "debt" so that the project can pay them later. To avoid having money sitting around in maintainers' accounts, we divide any amount left over among payable contributors, according to attributive share. Any amount we pay someone in excess of what we owed them originally, we record as an "advance." The idea here is that when someone eventually becomes payable, we can prioritize paying off the debt we owe them by allocating money to them first whenever a new payment comes in. Anyone who has been accumulating advances will receive a little less than their attributive share until their total advance amount has been "drawn down" and the scales have been balanced between debts and advances.

First, it reads all generated transactions from payments that have come in to
determine the amount owed to each contributor. Then, it looks at all recorded
payouts to see the total amounts that have already been paid out. It then
reports the difference of these values, by contributor, as the balance still
owed.
14 changes: 5 additions & 9 deletions entrypoint.sh
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#!/bin/sh -l
#!/bin/bash

# ensure that any errors encountered cause immediate
# termination with a non-zero exit code
set -e
set -eo pipefail

echo "PWD is: "
echo $(pwd)
Expand All @@ -23,21 +23,17 @@ echo "... done."

# Note that running this locally would cause your global
# git config to be modified
echo "Committing updated transactions and attributions back to repo..."
echo "Committing updated accounting records back to repo..."
git config --global user.email "[email protected]"
git config --global user.name "Old Abe"
git add abe/transactions.txt abe/attributions.txt abe/valuation.txt abe/itemized_payments.txt
git add abe/transactions.txt abe/attributions.txt abe/valuation.txt abe/itemized_payments.txt abe/advances.txt abe/debts.txt

set +e

git commit -m "Updated transactions and attributions"
git commit -m "Updated accounting records"
git fetch
git rebase origin/`git remote set-head origin -a | cut -d' ' -f4`
git push origin `git remote set-head origin -a | cut -d' ' -f4`
echo "... done."

set -e

echo "Running money_out script..."
echo balances=$(python -m oldabe.money_out) >> $GITHUB_OUTPUT
echo "... done."
34 changes: 34 additions & 0 deletions oldabe/accounting_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from decimal import Decimal

ROUNDING_TOLERANCE = Decimal("0.000001")


def get_rounding_difference(attributions):
"""
Get the difference of the total of the attributions from 1, which is
expected to occur due to finite precision. If the difference exceeds the
expected error tolerance, an error is signaled.
"""
total = _get_attributions_total(attributions)
difference = total - Decimal("1")
assert abs(difference) <= ROUNDING_TOLERANCE
return difference


def correct_rounding_error(attributions, incoming_attribution):
"""Due to finite precision, the Decimal module will round up or down
on the last decimal place. This could result in the aggregate value not
quite totaling to 1. This corrects that total by either adding or
subtracting the difference from the incoming attribution (by convention).
"""
difference = get_rounding_difference(attributions)
attributions[incoming_attribution.email] -= difference


def assert_attributions_normalized(attributions):
print(_get_attributions_total(attributions))
assert _get_attributions_total(attributions) == Decimal("1")


def _get_attributions_total(attributions):
return sum(attributions.values())
23 changes: 23 additions & 0 deletions oldabe/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import os
from decimal import Decimal

ACCOUNTING_ZERO = Decimal("0.01")

ABE_ROOT = './abe'
PAYOUTS_DIR = os.path.join(ABE_ROOT, 'payouts')
PAYMENTS_DIR = os.path.join(ABE_ROOT, 'payments')
NONATTRIBUTABLE_PAYMENTS_DIR = os.path.join(
ABE_ROOT, 'payments', 'nonattributable'
)

TRANSACTIONS_FILE = os.path.join(ABE_ROOT, 'transactions.txt')
DEBTS_FILE = os.path.join(ABE_ROOT, 'debts.txt')
ADVANCES_FILE = os.path.join(ABE_ROOT, 'advances.txt')
UNPAYABLE_CONTRIBUTORS_FILE = os.path.join(
ABE_ROOT, 'unpayable_contributors.txt'
)
ITEMIZED_PAYMENTS_FILE = os.path.join(ABE_ROOT, 'itemized_payments.txt')
PRICE_FILE = os.path.join(ABE_ROOT, 'price.txt')
VALUATION_FILE = os.path.join(ABE_ROOT, 'valuation.txt')
ATTRIBUTIONS_FILE = os.path.join(ABE_ROOT, 'attributions.txt')
INSTRUMENTS_FILE = os.path.join(ABE_ROOT, 'instruments.txt')
Loading

0 comments on commit 90428f1

Please sign in to comment.