diff --git a/conservancy_beancount/reports/rewrite.py b/conservancy_beancount/reports/rewrite.py index 40cfda5731d495abb1633d5a9a3a6d292c58cfb4..31177a193a6641d6855054228c78daa0a117e6d0 100644 --- a/conservancy_beancount/reports/rewrite.py +++ b/conservancy_beancount/reports/rewrite.py @@ -1,4 +1,130 @@ -"""rewrite.py - Post rewriting for financial reports""" +"""rewrite.py - Post rewriting for financial reports + +Introduction +------------ + +There are some kinds of posting metadata that's too impractical to write when +you enter it the books. For example, the ``expense-type`` of employee payroll +is usually determined by the employee's records or estimate at the end of the +year. It isn't known when payroll expenses are posted throughout the year, and +then there's too many of them to go back and code it manually. + +Rewrite rules solve this problem. They provide a mechanism to make safe, bulk +transformations to postings just after they're loaded and before they're +reported. They let you fill in the gaps between the data in the books and +different reporting requirements. + +Most reporting tools load rewrite rules written in YAML, so the examples in +this documentation are written that way. (If you're developing reporting tools, +note RewriteRule accepts a native Python dictionary.) One typical rule looks +like:: + + if: + - SUBJECT OP VALUE + [- SUBJECT2 OP2 VALUE2 + - …] + action1: + - SUBJECT OP VALUE + [- SUBJECT2 OP2 VALUE2 + - …] + [action2: + - … + …] + +A ruleset, as in a YAML file, is just an array of hashes like this. + +Conditions and Actions +---------------------- + +The hash must have at least two keys. One of them must be ``if``, and its value +is an array of condition strings. The rest can have any name you like and are +actions. Each action transforms an original posting that matched the ``if`` +conditions and yields a new posting from it. The value is an array of action +strings. Conditions and actions are written the same way; +conditions just use test operators, while actions use assignment operators. + +Subjects +-------- + +There are two kinds of subjects, attributes and metadata. + +Attributes start with a ``.`` and access data directly on the posting line, +or from the parent transaction line. You can use these attributes: + + ================ ======================================================= + Name Description + ================ ======================================================= + ``.account`` The name of the account on the posting line + ---------------- ------------------------------------------------------- + ``.date`` The date of the posting's transaction. When you work on + a date, write the value in ISO ``YYYY-MM-DD`` format. + ---------------- ------------------------------------------------------- + ``.number`` The number part of the posting's position; + i.e., the amount without the currency. + ================ ======================================================= + +Any other string is a metadata key. As usual, if a condition tries to read +metadata that does not exist on the posting, it will fall back to checking the +transaction. Metadata values are always treated as strings. NOTE: This means +comparisons against non-string metadata values, like dates and amounts, might +not work the way you want. + +Condition Operators +------------------- + +Conditions can always use Python's basic comparison operators: +``== != < <= > >=``. You can also use the following: + + ================ ======================================================= + Name Description + ================ ======================================================= + ``.account in`` The value is parsed as a space-separated list of + account names. The condition matches when the posting's + account is any of those named accounts, or any of their + respective subaccounts. + ================ ======================================================= + +Action Operators +---------------- + +You can set ``.account`` and any metadata with ``=``. Values are always treated +as strings. + +You can also transform the posting's number using ``.number *= NUMBER``. This +is mainly used to divide the posting's amount across multiple actions in one +rule. + +Execution +--------- + +When rewrite rules are applied to postings, the first rule whose condition +matches "wins." When a source posting matches a rule's conditions, its actions +are applied, and the transformed posting(s) replace the source posting. +No more rewrite rules are considered for either the original source posting +or the transformed posting(s). + +Validations +----------- + +Rewrite rules are validated to help ensure that you don't break the fundamental +accounting equation, Equity = Assets - Liabilities. + +* If an action assigns to ``.account``, there must also be a condition to check + that the ``.account`` is in the same category, using ``==`` or ``in``. + You cannot change an Asset into a Liability or Equity, and so on. + +* All actions in a rewrite rule must multiply ``.number`` by a total of 1. + (Actions that don't explicitly multiply the number are understood to + multiply it by 1.) For example, a rewrite rule can have two actions that + each multiply the number by .5, or one by .8 and the other by .2. It + cannot have two actions that each multiply the number by 1, or .3, + etc. Otherwise, the different postings of a transaction would not balance. + +* You cannot assign to ``.date`` at all. Otherwise, you might separate postings + of the same transaction in time, and the accounting equation would not hold + during the time gap. + +""" # Copyright © 2020 Brett Smith # # This program is free software: you can redistribute it and/or modify