Query account metadata : non-transaction directives (notes, prices, etc)?

218 views
Skip to first unread message

siri...@gmail.com

unread,
Apr 26, 2017, 11:07:14 AM4/26/17
to Beancount
Hello All,

Is there a way to query directives which are not transactions?
Currently I'm seeing this:

beancount> select * where type != "transaction"


(empty)


beancount>




The problem I'm trying to solve is invoice generation: want to store client-specific metadata (e.g.: address) in the ledger somewhere; then query them and have the results be the various fields of the generated invoice.

An example might be:

2017-03-01      note    Assets:Credits:CLIENT      "Via Lungolago 13, CH-6900 Lugano"      #address


And an example query might be:

SELECT last(narration) FROM (type = "note") AND ("address" in tags) WHERE account = "Assets:Credits:CLIENT"

... assuming that a note's text field is referred to as "narration".

This would have the advantage of being able to declare new addresses if the client moves.
The #address tag is so that other notes may exist without breaking this query.



I'm not sure if this is the way this problem should be solved, maybe a different approach is best?

Thank you very much,

Sirio

martin.andr...@gmail.com

unread,
Apr 26, 2017, 12:09:49 PM4/26/17
to Beancount, siri...@gmail.com
Metadata on the open directive might be useful? for example:

2017-03-01      open Assets:Credits:CLIENT
   address: ""
   telephone: ""
   contact
:""

and build the invoices with a python script.

I use this approach with one of my importers, my importers.config builds a list of all open directives with a given metadata (unique ID in the bill-autopay-system from), and passes this list to an importer that parses this ID.

Martin Blais

unread,
Apr 29, 2017, 3:31:45 PM4/29/17
to Beancount
Not at the moment.
In the next iteration of the SQL client design the virtual tables will be by type of directive.
That is, you will be able to do something like

  select * from balance;

and so on. That work hasn't even started yet, but I think that's a better strategy than what I built in the first version.



--
You received this message because you are subscribed to the Google Groups "Beancount" group.
To unsubscribe from this group and stop receiving emails from it, send an email to beancount+unsubscribe@googlegroups.com.
To post to this group, send email to bean...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/beancount/e0ef1c87-578f-4656-aeb7-d3cc9716352c%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

siri...@gmail.com

unread,
May 1, 2017, 11:08:58 AM5/1/17
to Beancount, siri...@gmail.com
Thank you very much for your answers.
In the end I took your advice and defined metadata on the account's Open directive:

2017-03-01      open Assets:Credits:CLIENT
   name: "Client's Company Name"
   address
: "1234 somewhere, someplace 0000 USA"

   
e-mail: "he...@adele.com"

And accessed the metadata in Python directly:

#!/usr/bin/env python3
from beancount.core import data
import re

def get_open_meta(entries, acct_regex):
   
"""
    Return a dictionary formed from combining the metadata dictionaries
        of all 'Open' statements for accounts matching 'acct_regex'.

    Will return an empty dicionary if no accounts matched.

    Done this say so that metadata from multiple related accounts can be gathered
        into one set, which can then be queried.

    """

    meta
= {}
   
for ent in entries:
       
if type(ent) == data.Open and re.match(acct_regex, ent.account):
            meta
.update(ent.meta)
   
return meta


# MAIN
if __name__ == "__main__" :
   
from beancount import loader
    entries
, errors, options = loader.load_file("somefile.beancount")
    meta
= get_open_meta(entries, '.*CLIENT') #client metadata

   
print("""
{0}
{1}
{2}"""
.format(meta['name'], #will error if 'name' not defined
             
' \n'.join(meta['address'].split('; ')), #allows multiple lines as 'line1; line2'
              meta
.get('e-mail')) #doesn't barf if e-mail isn't specified
   
)



On Wednesday, April 26, 2017 at 5:07:14 PM UTC+2, siri...@gmail.com wrote:

Martin Blais

unread,
May 2, 2017, 9:53:46 PM5/2/17
to Beancount
I love to see these custom solutions.
Really glad the API's working for you.

BTW, there's a function that will gather a mapping of the open/close directories here:
Might be useful (but it does look like you don't need it).

Cheers,



--
You received this message because you are subscribed to the Google Groups "Beancount" group.
To unsubscribe from this group and stop receiving emails from it, send an email to beancount+unsubscribe@googlegroups.com.
To post to this group, send email to bean...@googlegroups.com.

siri...@gmail.com

unread,
May 3, 2017, 5:19:00 AM5/3/17
to Beancount
Thank you sir :)

Good to know! There's a few useful utility functions in that file, thank you.

In the end, the whole thing simplifies to this:

from beancount.loader import load_file
from beancount.core import data
import re

# MAIN
if __name__ == "__main__" :


   
# parse beancount file
    entries
, errors, options = load_file("somefile.beancount")
   
# gather metadata from all account 'open' directives
    meta
= { k: v for ent in entries for k, v in ent.meta.items()
       
if type(ent) == data.Open and re.match('.*CLIENT', ent.account)
       
}

   
# print statement from earlier example goes here

... sorry for the kudgy code on the first go; I'm still thawing from having been deep in C+asm country for some time :P

best,

Sirio
To unsubscribe from this group and stop receiving emails from it, send an email to beancount+...@googlegroups.com.

To post to this group, send email to bean...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages