Package: beancount-interpolate
InterpolateFour plugins for double-entry accounting system Beancount to interpolate transactions by generating additional entries over time.They are:recur: dublicates all entry postings over timesplit: dublicates all entry postings over time at fraction of valuedepr: generates new entries to depreciate target asset/liability posting over given periodspread: generate new entries to allocate P&L of target income/expense posting over given periodThese plugins are triggered by adding metadata or tags to source entries. It's safe to disable at any time. All plugins share the same parser that can set maximal period, custom starting date and minimal step by either number or keyword.You can use these to define recurring transactions, account for depreciation, smooth transactions over time and make graphs less zig-zag.Thisdepris not yet compatible with any accounting standards. For tax-compatible yearly depreciation take a look at thispluginby Alok Parlikar under MIT license. All contributions to improvedeprare welcome.Installpip3 install beancount_interpolate --userOr copy to path used for python. For example,$HOME/.local/lib/python3.7/site-packages/beancount_interpolate/*would do on Debian. If in doubt, look wherebeancountfolder is and copy next to it.Details: SpreadProblemLet's say John has only two activities10 EUR daily food expenses300 EUR net monthly salary (day 15 in next month)If we plot John's wealth, it would look like zig-zag and be negative between -150 EUR (right after salary) and -450 EUR (right before), that is big and unstable error for your current wealth. And there could be more incomes/expenses with various 'periods'. In most complicated case John would have multiple income/expense sources with various lenght periods that are unsynchronised, but John wants dashboard with graph of correct daily report on his wealth.Possible solutions:Do nothing and ackowledge the 0-300 EUR error. IMO Is ok only if error is relatively small.Do nothing and restrict yourself to do analysis at regular intervals when error is smallest (for example right after salary) and apply known corrections (for example, +150 EUR because it's already middle of month). IMO it is ok only if all periods are synchronised.Record 10 EUR income to cash daily. But that violates 'after fact' principle that IMO is more important and IMO that's too much effort and transaction spam.Record 10 EUR income to 'work token' assets daily but IMO that's still too much effort and transaction spam.Let plugin do No.4 for you. (This repository)How to useEnable the plugin (see available options below).plugin "beancount_interpolate.spread"Add meta or tags to your transactions. All folllowing transactions does the same.; Explicit.
2016-06-15 * "The Company" "Simplest salary entry"
    Income:TheCompany:NetSalary     -310.00 EUR
        spreadAfter: "Month @ 2016-05-01"
    Assets:MyBank:Checking           310.00 EUR

; Transaction meta applies to all Income/Expense postings if they don't have their own.
2016-06-15 * "The Company" "Simplest salary entry"
    spreadAfter: "Month @ 2016-05-01"
    Income:TheCompany:NetSalary     -310.00 EUR
    Assets:MyBank:Checking           310.00 EUR

; Use spreadBefore if that reads better in your case.
2016-06-15 * "The Company" "Simplest salary entry"
    spreadBefore: "Month @ 2016-05-31"
    Income:TheCompany:NetSalary     -310.00 EUR
    Assets:MyBank:Checking           310.00 EUR

; Use default period.
2016-06-15 * "The Company" "Simplest salary entry"
    spreadAfter: "2016-05-01"
    Income:TheCompany:NetSalary     -310.00 EUR
    Assets:MyBank:Checking           310.00 EUR

; Use date of transaction.
2016-06-15 * "The Company" "Simplest salary entry"
    spreadAfter: "Month"
    Income:TheCompany:NetSalary     -310.00 EUR
    Assets:MyBank:Checking           310.00 EUR

; Use date of transaction and default period. Beware the different transaction date.
2016-05-01 * "The Company" "Simplest salary entry" #spreadAfter
    Income:TheCompany:NetSalary     -310.00 EUR
    Assets:MyBank:Checking           310.00 EURWhat happensFirst, plugin edits the original transaction like this:2016-05-01 * "The Company" "Simplest salary entry (Generated by interpolate-spread)" #spread
    Liabilities:Current:TheCompany:NetSalary       -310.00 EUR
    Assets:MyBank:Checking                          310.00 EURSecond, plugin inserts 30 or 31 transactions from 2016-05-01 to 2016-05-31 like this:2016-05-xx * "The Company" "Simplest salary entry (Generated by interpolate-spread)" #spread
    Income:TheCompany:NetSalary                     -10.00 EUR
    Liabilities:Current:TheCompany:NetSalary         10.00 EURNotesThe content of meta tag consists of 5 parts, every of them optional:amountx, a whole number. Defaults to 1.period, a case-insensitive keyword "day[s]", "week[s]", "month[s]" or "year[s]". Defaults to "day".start date, prefixed with@sign. Defaults to transaction date.step multiplierm, a whole number. Defaults to 1.stepsize, can be either keyword from as period. Defaults to "day".Summary:[x] [period] [@ YYYY-MM[-DD]] [/ [m] [step]]Details: RecurProblemYou want to make recurring entry every X days until forever (or some Y days have passed). Recur will duplicate tagged entry for you!How to useEnable the plugin (see available options below).plugin "beancount_interpolate.recur"Details: SplitProblemIn fact, the argumentation is the same as inspread, but the only difference is that different usecases needs a bit different treatment. For example, our John want to set aside money for savings daily. To account for it nicely,spreadwon't do - we don't need anyLiabilities:Current:...accounts generated for us. A simple duplication will do here.How to useEnable the plugin (see available options below).plugin "beancount_interpolate.split"Add meta or tags to your transactions. All folllowing transactions does the same.; Explicit.
2016-01-01 * "Me" "Set aside money for savings"
    split: "Year"
    Assets:MyBank:Checking          -365.00 EUR
    Assets:MyBank:Savings            365.00 EUR

; In fact, original date doesn't matter here, as original entry will be deleted.
2016-06-15 * "Me" "Set aside money for savings"
    splitAfter: "Year @ 2016-01-01"
    Assets:MyBank:Checking          -365.00 EUR
    Assets:MyBank:Savings            365.00 EUR


; Use can also use tags.
2016-01-01 * "Me" "Set aside money for savings" #split
    Assets:MyBank:Checking          -365.00 EUR
    Assets:MyBank:Savings            365.00 EURWhat happensFirst, plugin deletes the original transaction.Second, plugin inserts transactions every day until today, included.2016-01-01 * "Me" "Set aside money for savings (Generated by interpolate-split)" #split
    Assets:MyBank:Checking            -1.00 EUR
    Assets:MyBank:Savings              1.00 EUR

2016-01-02 * "Me" "Set aside money for savings (Generated by interpolate-split)" #split
    Assets:MyBank:Checking            -1.00 EUR
    Assets:MyBank:Savings              1.00 EUR

...Details: DepreciateProblemDepreciate technically is the same as spread but from other way around. But practically, you would like to have different settings for your short-term spreads and long-term depreciations.How to useEnable the plugin (see available options below).plugin "beancount_interpolate.depr"Add meta or tags to your transactions. All folllowing transactions does the same.; Explicit.
2016-06-15 * "CornerStore" "Bought new Laptop to do beancounting"
    Assets:Fixed:PC                  199.00 EUR
        depr: "Year @ 2016-06-15"
    Assets:MyBank:Checking          -199.00 EUR

; Transaction meta applies to all Assets:Fixed postings if depr is entry-wide.
2016-06-15 * "CornerStore" "Bought new Laptop to do beancounting"
    depr: "Year @ 2016-06-15"
    Assets:Fixed:PC                  199.00 EUR
    Assets:MyBank:Checking          -199.00 EUR


; Use default period (Year).
2016-06-15 * "CornerStore" "Bought new Laptop to do beancounting"
    depr: "Year"
    Assets:Fixed:PC                  199.00 EUR
    Assets:MyBank:Checking          -199.00 EUR

; Use date of transaction.
2016-06-15 * "CornerStore" "Bought new Laptop to do beancounting"
    depr: "Month"
    Assets:Fixed:PC                  199.00 EUR
    Assets:MyBank:Checking          -199.00 EUR

; Use default period (Year) and default date (entry date).
2016-06-15 * "CornerStore" "Bought new Laptop to do beancounting"
    depr: "empty string or some nonsense"
    Assets:Fixed:PC                  199.00 EUR
    Assets:MyBank:Checking          -199.00 EUR

; Use default period (Year) and default date (entry date) using a tag.
2016-06-15 * "CornerStore" "Bought new Laptop to do beancounting" #depr
    Assets:Fixed:PC                  199.00 EUR
    Assets:MyBank:Checking          -199.00 EURWhat happensPlugin inserts lots of transactions starting from given date until end (or today) like this:2016-06-15 * "CornerStore" "Bought new Laptop to do beancounting (depr 1/365)" #depred
    Assets:Fixed:PC                  -00.55 EUR
    Expenses:Depreciation:PC          00.55 EUR

2016-06-16 * "CornerStore" "Bought new Laptop to do beancounting (depr 2/365)" #depred
    Assets:Fixed:PC                  -00.54 EUR
    Expenses:Depreciation:PC          00.54 EUROptionsIn Beancount, options are passed to plugins as second argument and may be multi-line.
Tobeancount_interpolateoptions have to be formatted as valid JSON.Options that applies to all four plugins:aliases_after- list of strings to look for in meta and tags.default_duration- integer or string of keyword that plugin will default duration to, if none was provided in mark.default_step- integer that plugin will default step to, if none was provided in mark.min_value- decimal that will be the minimal value of leg within created transactions.max_new_tx- integer that will be the maximal amount of newly created transactions.suffix- string appended to created transaction annotations. Two variables are n-th transaction and total amount of transactions.tag- string that as tag will be applied to all created transactions.Options that applies only tospreadanddepr. Forspreadnew transactions are created under account name whereaccount_expenseandaccount_incomeprefixes are swapped toaccount_assetsoraccount_liabprefixes respectively. Fordeprthat other way around:account_assetsandaccount_liabprefixes are swapped toaccount_expenseandaccount_incomeprefixes instead.account_incomeaccount_expensesaccount_assetsaccount_liabDefaultsHere are all available options and their default values. Options are passed as serialized object to the plugin.plugin "beancount_interpolate.recur" "{
    'aliases_after': ['recurAfter', 'recur'],
    'default_duration': 'Infinite',
    'default_step': 'Day',
    'default_method': 'SL',
    'min_value': 0.05,
    'max_new_tx': 9999,
    'suffix': ' (recur %d/%d)',
    'tag': 'recurred'
}"

plugin "beancount_interpolate.split" "{
    'aliases_after': ['splitAfter', 'split'],
    'default_duration': 'Month',
    'default_step': 'Day',
    'default_method': 'SL',
    'min_value': 0.05,
    'max_new_tx': 9999,
    'suffix': ' (split %d/%d)',
    'tag': 'splitted'
}"

plugin "beancount_interpolate.spread" "{
    'account_income': 'Income',
    'account_expenses': 'Expenses',
    'account_assets': 'Assets:Current',
    'account_liab': 'Liabilities:Current',
    'aliases_after': ['spreadAfter', 'spread'],
    'default_duration': 'Month',
    'default_step': 'Day',
    'default_method': 'SL',  # Straight Line
    'min_value': 0.05,  # cannot be smaller than 0.01
    'max_new_tx': 9999,
    'suffix': ' (spread %d/%d)',
    'tag': 'spreaded'
}"

plugin "beancount_interpolate.depr" "{
    'account_income': 'Income:Appreciation',
    'account_expenses': 'Expenses:Depreciation',
    'account_assets': 'Assets:Fixed',
    'account_liab': 'Liabilities:Fixed',
    'aliases_after': ['deprAfter', 'depr'],
    'default_duration': 'Year',
    'default_step': 'Day',
    'min_value': 0.05,  # cannot be smaller than 0.01
    'max_new_tx': 9999,
    'suffix': ' (depr %d/%d)',
    'tag': 'depred'
}"Details:aliases_afterType: list of stringsIf any of aliases are found in transaction tag, transaction meta or posting meta, then the plugin will be applied.Details:default_durationType: integer or string (one of keywords: day, week, month, year, inf, infinite, max)Default duration to apply when one is not specified explicitly. Note that month and year keywords do not adapt to current period, but are simple constants (PR welcome!)Details:default_stepType: integerDefault step to apply when one is not specified explicitly. In fact, currently it is not possible to specify it explicitly on per-case basis (PR welcome!)Details:min_valueType: decimalRestrictions: no less than 0.01Minimal value of leg when for new created transactions. It will try to do so Here's example how it works:For example, you want to spread your groceries 10.00 USD over 7 days, but it apparently doesn't divide nicely. So,1st day would get allocated 1.43 USD but -0.001429 is kept aside (10.00/7=1.428571).2nd day would get allocated 1.43 USD but -0.002858 is kept aside (10.00/7=1.428571-0.001429=1.427142)3rd day would get allocated 1.43 USD but -0.004287 is kept aside (10.00/7=1.428571-0.002858=1.425713)4th day would get allocated1.42USD but +0.004284 is kept aside (10.00/7=1.428571-0.004287=1.424284)5th day would get allocated 1.43 USD but +0.002855 is kept aside (10.00/7=1.428571+0.004284=1.432855)6th day would get allocated 1.43 USD but +0.001426 is kept aside (10.00/7=1.428571+0.002855=1.431426)7th day would get allocated 1.43 USD and only pure rounding error is kept aside (10.00/7=1.428571+0.001426=1.429997)Themin_valueis required to be at least 0.01 by beancount, but I'd recommend to raise it even further to avoid "1-cent spam" that lowers readability of reports and impacts performance.There's an interesting behavior for transactions that are very small. If on any day the allocation is smaller bymin_valuefor any of legs, the transaction on that day is not generated and put aside in full. Thus, such small transactions tend to happen once in a while withmin_valueamount. Even more funny, if there are more than one posting with small amount, those postings keep their "put aside" values seperaly - it may happen that at some day there will be a transaction with both of postings, effectively giving double ofmin_value. It may be hard to those little postings in reports, and I doubt that anyone would care about them at all, the best place to look at is the source.Details:max_new_txType: integerCaps the max new transactions generated for one entry. By default set to 9999, looks like working but is not tested.Details:suffixType: stringSuffix is string appended to created transaction annotations. It has two variables within in the given order:n-th transactiontotal amount of transactions.Details:tagType: stringString that as tag will be applied to all created transactions.DevelopmentThe source contains five files - one per plugin and commons. Plugins have very similar structure in pairs: spread is similar to depreciate, and recur is similar to split.Please see Makefile and inline comments.Note: there's a branchsingle-plugin-refactorthat's a up-for-grabs WIP based on v2 by@benedictvh. See #8 #9, #12 for details.See AlsoSourceAuthorgittestsUpdatedPyPIDocsCommentbeancount_interpolate.deprAkuukis✅✅2021-01Readmeparallel_average_costFalsifian❌✅2021-03❌DiscussionThis plugin is intended to help when you need to calculate your capital gains in two different ways, and one of them is average-cost booking.auto_depreciationhktkzyx✅✅2020-03❌Docsamortize_overCary Kempston❌❌2017-09❌Discussionflexible_deprecationAlok Parlikar✅❌2016-06DocstringImproved by Dave Stephens (Discussion) and fixed by Akuukis (PR)split_transactionsJohann Klähn❌❌2015-06❌DiscussionFurthermore may look into the sources for inspiration of the relatedDepreciateForLedger.And of course love (in form of backlink) to theplaintextaccounting.org.