Naar hoofdinhoud springen

Hoe Python-scripts Beancount en Fava automatiseren

Beancount en Fava blijven scriptbaar: gebruik Python om rapporten, saldi en aangepaste workflows tegen uw grootboek te automatiseren.

Beancount (een plain-text dubbel boekhoudprogramma) en Fava (de webinterface ervan) zijn zeer uitbreidbaar en scriptbaar. Hun ontwerp stelt je in staat om financiële taken te automatiseren, aangepaste rapporten te genereren en waarschuwingen in te stellen door Python-scripts te schrijven. In de woorden van een gebruiker: "Ik vind het echt fijn om mijn gegevens in zo'n handig formaat te hebben, en ik vind het fijn dat ik dingen naar hartenlust kan automatiseren. Er is geen API zoals een bestand op je schijf; het is makkelijk om mee te integreren." Deze gids loopt door het creëren van scriptbare workflows—van beginnersvriendelijke automatisering tot geavanceerde Fava-plugins.

Verken een live voorbeeld-ledger:

Example Ledger in een nieuw tabblad openen

Begin met de bea opdrachtregel​

Voordat je Python schrijft, controleer of bea de taak al doet. Het valideert de ledger, voert BQL-query's uit, produceert de vier financiële rapporten en importeert bankexporten, en de globale --json-optie verandert elk daarvan in een parseerbare envelop die je shell naar jq kan pipen. De exitcodes vormen het contract waarop een geplande taak vertakt, dus cron of CI heeft helemaal geen loaderscript nodig. Zie automatiseer boekhouding met bea voor de target-resolutie, de envelop en het vertakken op exitcodes, en kom hier terug wanneer je een aangepaste berekening nodig hebt die de CLI niet biedt.

Aan de slag: Beancount uitvoeren als een Python-script​

Voor de onderstaande aangepaste Python-scripts installeer je de scripting-bibliotheken (pip install beancount beanquery beangulp). De bea-commandoworkflows gebruiken in plaats daarvan de managed engine; volg de CLI quick start om die te installeren. Omdat Beancount in Python is geschreven, kun je het als een bibliotheek gebruiken in je eigen scripts. De onderstaande scripts zijn uitgevoerd met Beancount 3.2.3, beanquery 0.2.0 en beangulp 0.2.0. De algemene aanpak is:

  • Laad je Beancount-ledger: Gebruik Beancount's loader om het .beancount-bestand te parsen naar Python-objecten. Bijvoorbeeld:

    from beancount import loader
    entries, errors, options = loader.load_file("myledger.beancount")
    if errors:
        for error in errors:
            print(error)
        raise SystemExit(1)

    De loader retourneert entries en fouten samen. Een ongebalanceerd of ongeldig bestand retourneert nog steeds entries, dus controleer errors en stop voordat je de gegevens vertrouwt. Al je rekeningen, transacties en saldi zijn nu toegankelijk in code.

  • Maak gebruik van Beancount Query Language (BQL): In plaats van handmatig te itereren, kun je SQL-achtige query's op de gegevens uitvoeren. Query's staan in het aparte beanquery-pakket. Er is geen beancount.query-module in Beancount 3.2.3. Om bijvoorbeeld de totale uitgaven per maand te krijgen, verbind je de geladen entries en voer je de query direct uit:

    import beanquery
     
    conn = beanquery.connect("beancount:", entries=entries, errors=errors, options=options)
    cur = conn.execute(
        "SELECT year, month, sum(position) WHERE account ~ 'Expenses' GROUP BY year, month"
    )
    for row in cur.fetchall():
        print(row)

    Dit gebruikt beanquery om gegevens te aggregeren. Het is dezelfde engine achter bea query, maar hier roep je het aan in een script. Dat voorkomt dat je in een loop een externe opdracht via de shell aanroept.

  • Zet een projectstructuur op: Organiseer je scripts naast je ledger. Een veelvoorkomende indeling is mappen voor importers (om externe gegevens op te halen/parsen), reports of queries (voor analysescripts) en documents (om gedownloade afschriften op te slaan). Een gebruiker houdt bijvoorbeeld:

    • importers/ – aangepaste Python-importscripts (met tests),
    • queries/ – scripts om rapporten te genereren (uitvoerbaar via python3 queries/...),
    • documents/ – gedownloade bank-CSV's/PDF's georganiseerd per rekening.

Met deze opzet kun je scripts handmatig uitvoeren (bijv. python3 queries/cash_flow.py) of plannen (via cron of een task runner) om je workflow te automatiseren.

Taken voor reconciliatie automatiseren​

Reconciliatie betekent ervoor zorgen dat je ledger overeenkomt met externe gegevens (bankafschriften, creditcardoverzichten, enz.). Beancount's plain-text ledger en Python-API maken het mogelijk om een groot deel van dit proces te automatiseren.

Transacties importeren en koppelen (beginner)​

Voor beginners is de aanbevolen aanpak om importers uit het aparte beangulp-pakket te gebruiken. Beancount 3 verwijderde de v2 ingest-module en het extract-commando. Je schrijft een kleine Python-klasse die beangulp.Importer subclasset om een bepaald formaat (CSV, OFX, PDF, enz.) te parsen en transacties te produceren. Registreer het in een kort ingest-script en voer het vervolgens uit via bea ingest in de managed engine:

  • Schrijf een importer (een Python-klasse met de methoden identify(), account() en extract()) voor het CSV-formaat van je bank.
  • Voeg een ingest-script toe dat je importers registreert. bea ingest voert de identify-, extract- en archive-commando's van het script uit. Eén workflow voert bijvoorbeeld extract uit op alle bestanden in ~/Downloads en schrijft transacties naar een tijdelijk bestand.
  • Controleer handmatig en kopieer transacties uit het tijdelijke bestand naar je hoofd-ledger, en voer dan bea check uit om te controleren of de saldi kloppen.

Een minimaal voorbeeld: een statement.csv met kolommen date,description,amount, geparseerd door deze importer (checking_importer.py):

import csv
import datetime
from beancount.core import data
from beancount.core.amount import Amount
from beancount.core.number import D
import beangulp
 
 
class CheckingImporter(beangulp.Importer):
    def identify(self, filepath: str) -> bool:
        return filepath.endswith("statement.csv")
 
    def account(self, filepath: str) -> str:
        return "Assets:Bank:Checking"
 
    def extract(self, filepath: str, existing):
        entries = []
        with open(filepath, newline="") as f:
            for row in csv.DictReader(f):
                date = datetime.date.fromisoformat(row["date"])
                amount = Amount(D(row["amount"]), "USD")
                meta = data.new_metadata(filepath, 0)
                entries.append(
                    data.Transaction(
                        meta, date, "*", None, row["description"],
                        data.EMPTY_SET, data.EMPTY_SET, [
                            data.Posting("Expenses:Food:Groceries", amount,
                                         None, None, None, None),
                            data.Posting("Assets:Bank:Checking",
                                         Amount(-amount.number, "USD"),
                                         None, None, None, None),
                        ]))
        return entries

Het ingest-script (ingest.py) koppelt het:

from checking_importer import CheckingImporter
from beangulp import Ingest
 
ingest = Ingest([CheckingImporter()])
 
if __name__ == "__main__":
    ingest()

Voer het uit op een gedownload bestand. Er zijn geen inloggegevens nodig voor een lokale CSV. Installeer eerst de systeembibliotheek libmagic. Het eenmalige enable-commando downloadt Beangulp in de managed engine:

bea engine enable beangulp
bea ingest identify --config ingest.py statement.csv
bea ingest extract --config ingest.py statement.csv -o new.beancount

identify rapporteert checking_importer.CheckingImporter voor het bestand. extract schrijft de transacties in Beancount-formaat:

2024-01-08 * "Grocery Store"
  Expenses:Food:Groceries   120.00 USD
  Assets:Bank:Checking     -120.00 USD

Controleer new.beancount, kopieer de entries naar je hoofd-ledger en voer bea check uit.

Sla de importer over voor een eenmalige conversie

Je hoeft geen importer te schrijven om één enkel afschrift te converteren. Plak het bestand in de CSV naar Beancount-converter, of gebruik OFX & QIF naar Beancount voor .ofx-, .qfx- en .qif-downloads. Beide werken volledig in je browser, dus het afschrift verlaat je machine nooit.

Hoewel dit proces nog steeds een controle stap bevat, wordt veel van het saaie werk van het parsen en formatteren van entries geautomatiseerd. Importer-scripts kunnen ook automatisch categorieën toewijzen en zelfs balance-assertions instellen (verklaringen van verwachte saldi) om discrepanties op te sporen. Na het importeren zou je bijvoorbeeld een regel kunnen hebben zoals 2025-04-30 balance Assets:Bank:Checking 1234.56 USD die het eindsaldo vaststelt. Wanneer je bea check uitvoert, zal Beancount verifiëren dat al deze balance assertions correct zijn, en eventuele fouten markeren als transacties ontbreken of gedupliceerd zijn. Dit is een best practice: genereer automatisch balance assertions voor elke afschriftperiode, zodat de computer niet-gereconcilieerde verschillen voor je kan opsporen.

Aangepaste Reconciliatiescripts (Gemiddeld niveau)​

Voor meer controle kun je een aangepast Python-script schrijven om een banktransactielijst (CSV of via API) te vergelijken met je ledger-entries:

  1. Lees de externe gegevens: Parseer het CSV-bestand van de bank met Python's csv-module (of Pandas). Normaliseer de gegevens naar een lijst van transacties, elk met bijvoorbeeld een datum, bedrag en omschrijving.
  2. Laad ledger-transacties: Gebruik loader.load_file zoals eerder getoond om alle ledger-entries te krijgen. Filter deze lijst op de rekening van interesse (bijv. je betaalrekening) en mogelijk het datumbereik van het afschrift.
  3. Vergelijk en vind discrepanties:
  • Controleer voor elke externe transactie of er een identieke entry in de ledger bestaat (match op datum en bedrag, misschien omschrijving). Als die niet wordt gevonden, markeer deze dan als "nieuw" en geef deze mogelijk uit als een Beancount-geformatteerde transactie die je kunt controleren.
  • Identificeer omgekeerd eventuele ledger-entries op die rekening die niet in de externe bron voorkomen – dit kunnen invoerfouten zijn of transacties die nog niet bij de bank zijn verwerkt.
  1. Geef resultaten weer: Druk een rapport af of maak een nieuw .beancount-fragment met de ontbrekende transacties.

Een community-script genaamd reconcile.py doet precies dit: gegeven een Beancount-bestand en een input-CSV, print het een lijst van nieuwe transacties die geïmporteerd moeten worden, evenals eventuele bestaande ledger-posten die niet in de input voorkomen (mogelijk een teken van verkeerde classificatie). Met zo'n script kan maandelijkse reconciliatie zo simpel zijn als het uitvoeren ervan en vervolgens de voorgestelde transacties aan je ledger toevoegen. Een Beancount-gebruiker merkt op dat ze "elke maand een reconciliatieproces op alle rekeningen uitvoeren" en een groeiende verzameling Python-code gebruiken om een groot deel van het handmatige werk bij het importeren en reconciliëren van gegevens te elimineren.

Tip: Maak tijdens reconciliatie gebruik van Beancount's hulpmiddelen voor nauwkeurigheid:

  • Gebruik balance assertions zoals vermeld, om geautomatiseerde controles op rekeningsaldi te hebben.
  • Gebruik indien gewenst de pad-directive, die automatisch balancerende entries kan invoegen voor kleine afrondingsverschillen (gebruik met voorzichtigheid).
  • Schrijf unit tests voor je importer of reconciliatielogica (Beancount biedt testhelpers). Eén workflow hield bijvoorbeeld in: een voorbeeld-CSV nemen, failing tests schrijven met verwachte transacties, en vervolgens de importer implementeren totdat alle tests slaagden. Dit zorgt ervoor dat je importscript correct werkt voor verschillende gevallen.

Aangepaste rapporten en samenvattingen genereren​

Hoewel Fava veel standaardrapporten biedt (winst-en-verliesrekening, balans, enz.), kun je aangepaste rapporten maken met scripts. Deze variëren van eenvoudige console-uitvoer tot rijk geformatteerde bestanden of grafieken.

Gegevens opvragen voor rapporten (Beginner)​

Op basisniveau kun je de Beancount Query Language (BQL) gebruiken om samenvattingsgegevens op te halen en deze af te drukken of op te slaan. Bijvoorbeeld:

  • Cashflowsamenvatting: Gebruik een query om de netto cashflow te berekenen. "Cashflow" kan worden gedefinieerd als de verandering in saldo van bepaalde rekeningen over een periode. Met BQL zou je kunnen doen:

    SELECT year, month, sum(position)
    WHERE account ~ 'Income' OR account ~ 'Expenses'
    GROUP BY year, month

    Dit netto-eert alle inkomsten- en uitgavenposten per maand. Filter met ~ en een reguliere expressie: LIKE is een syntaxfout in beanquery 0.2.0. Postings dragen position, niet amount. Elke rij bevat één Inventory, dus elke valuta wordt afzonderlijk vermeld in plaats van omgerekend. Inkomsten komen negatief binnen en uitgaven positief. Je kunt dit uitvoeren via bea query of via de eerder getoonde beanquery Python-API, en vervolgens het resultaat formatteren.

  • Categorie-uitgavenrapport: Vraag de totale uitgaven per categorie op:

    SELECT account, sum(position)
    WHERE account ~ 'Expenses'
    GROUP BY account
    ORDER BY sum(position) ASC

    Dit levert een tabel op met uitgaven per categorie. Elk totaal is een Inventory in de oorspronkelijke valuta. Wikkel het aggregaat niet in round(): er is geen round(inventory, int)-functie, dus round(sum(position), 2) compileert niet. Je kunt meerdere query's in een script uitvoeren en de resultaten uitvoeren als tekst, CSV of zelfs JSON voor verdere verwerking.

Een gebruiker vond het "triviaal" om financiële gegevens te analyseren met Fava of met scripts, en noemde dat ze één Python-script gebruiken om gegevens uit Beancount te halen via de Query Language en die vervolgens in een Pandas DataFrame te zetten om een aangepast rapport voor te bereiden. Je zou bijvoorbeeld maandelijkse totalen met een query kunnen ophalen en vervolgens Pandas/Matplotlib gebruiken om een cashflowgrafiek over de tijd te tekenen. De combinatie van BQL en data-science-bibliotheken stelt je in staat rapporten te bouwen die verder gaan dan wat Fava standaard biedt.

Geavanceerde rapportage (grafieken, prestaties, enz.)​

Voor geavanceerdere behoeften kunnen je scripts statistieken berekenen zoals beleggingsrendement of visuele output creëren:

  • Beleggingsrendement (IRR/XIRR): Omdat je ledger alle cashflows bevat (aankopen, verkopen, dividenden), kun je rendementspercentages van portefeuilles berekenen. Je zou bijvoorbeeld een script kunnen schrijven dat transacties van je beleggingsrekeningen filtert en vervolgens het interne rendement (Internal Rate of Return) berekent. Er zijn bibliotheken (of formules) om IRR te berekenen op basis van cashflowgegevens. Sommige door de community ontwikkelde Fava-extensies (zoals PortfolioSummary of fava_investor) doen precies dit, en berekenen IRR en andere statistieken voor beleggingsportefeuilles. Als script kun je een IRR-functie (van NumPy of je eigen) gebruiken op de reeks stortingen/opnames plus eindwaarde.

  • Meerperiodige of aangepaste statistieken: Wil je een rapport van je spaarquote (verhouding van spaargeld tot inkomen) per maand? Een Python-script kan de ledger laden, alle inkomstenrekeningen en alle uitgavenrekeningen optellen, en dan spaargeld = inkomen - uitgaven en het percentage berekenen. Dit kan een mooie tabel opleveren of zelfs een HTML/Markdown-rapport genereren voor je administratie.

  • Visualisatie: Je kunt grafieken genereren buiten Fava. Gebruik bijvoorbeeld matplotlib of altair in een script om een netto vermogen in de tijd-grafiek te maken met ledgergegevens. Omdat de ledger alle historische saldi heeft (of je kunt ze opbouwen door entries te itereren), kun je tijdreeksgrafieken produceren. Sla deze grafieken op als afbeeldingen of interactieve HTML. (Als je de voorkeur geeft aan visuals in de app, zie het gedeelte over Fava-extensies hieronder voor het toevoegen van grafieken binnen Fava.)

Uitvoeropties: Bepaal hoe je het rapport wilt leveren:

  • Voor eenmalige analyses is printen naar het scherm of opslaan naar een CSV/Excel-bestand wellicht voldoende.
  • Voor dashboards kun je overwegen een HTML-bestand te genereren met de gegevens (mogelijk met een templating-bibliotheek zoals Jinja2 of zelfs gewoon Markdown schrijven) dat je in een browser kunt openen.
  • Je kunt ook integreren met Jupyter Notebooks voor een interactieve rapportageomgeving, hoewel dat meer voor verkenning dan voor automatisering is.

Melding Triggeren vanuit Je Grootboek​

Een ander krachtig gebruik van scriptbare workflows is het instellen van waarschuwingen op basis van voorwaarden in je financiële gegevens. Omdat je ledger regelmatig wordt bijgewerkt (en toekomstgedateerde items kan bevatten zoals aankomende rekeningen of budgetten), kun je deze met een script scannen en op de hoogte worden gesteld van belangrijke gebeurtenissen.

Waarschuwingen bij Laag Rekeningsaldo​

Om roodstand te voorkomen of een minimumsaldo aan te houden, wil je misschien een waarschuwing als een rekening (bijv. betaal- of spaarrekening) onder een drempel zakt. Zo kun je dit implementeren:

  1. Bepaal huidige saldi: Bereken na het laden van entries via de loader het laatste saldo van de rekeningen van interesse. Je kunt dit doen door postings te aggregeren of een query te gebruiken. Gebruik bijvoorbeeld een BQL-query voor het saldo van een specifieke rekening:

    SELECT sum(position) WHERE account = 'Assets:Bank:Checking'

    Dit retourneert het huidige saldo van die rekening (som van alle postings). Als alternatief kun je Beancount's interne functies gebruiken om een balans op te stellen. Bijvoorbeeld:

    from beancount.core import realization
    tree = realization.realize(entries)
    acct = realization.get_or_create(tree, "Assets:Bank:Checking")
    balance = acct.balance  # an Inventory of commodities

    Geef alleen de entries door: de tweede parameter is min_accounts, niet de options-map. Extraheer vervolgens de numerieke waarde (bijv. balance.get_currency_units('USD') retourneert het Decimal-bedrag in USD). Net als bij een query-aggregaat houdt het saldo elke valuta afzonderlijk. Voor de meeste gevallen is het gebruik van de query echter eenvoudiger.

  2. Controleer de drempel: Vergelijk het saldo met je vooraf gedefinieerde limiet. Als het lager is, activeer een waarschuwing.

  3. Activeer een melding: Dit kan zo simpel zijn als het printen van een waarschuwing naar de console, maar voor echte waarschuwingen wil je misschien een e-mail of pushmelding sturen. Je kunt integreren met e-mail (via smtplib) of een dienst zoals IFTTT of Slack's webhook-API om de waarschuwing te pushen. Bijvoorbeeld:

    if balance < 1000:
        send_email("Low balance alert", f"Account XYZ balance is {balance}")

    (Implementeer send_email met je e-mailservergegevens.)

Door dit script dagelijks uit te voeren (via een cronjob of Windows Taakplanner), krijg je proactieve waarschuwingen. Omdat het de ledger gebruikt, kan het alle transacties meenemen, inclusief degene die je zojuist hebt toegevoegd.

Aankomende betalingsdeadlines​

Als je Beancount gebruikt om rekeningen of deadlines bij te houden, kun je toekomstige betalingen markeren en scripts je laten herinneren. Twee manieren om aankomende verplichtingen in Beancount weer te geven:

  • Events: Beancount ondersteunt een event-directive voor willekeurige gedateerde notities. Bijvoorbeeld:

    2025-05-10 event "BillDue" "Mortgage payment due"

    Dit beïnvloedt saldi niet, maar registreert een datum met een label. Een script kan entries scannen op Event-entries waar Event.type == "BillDue" (of een ander aangepast type dat je kiest) en controleren of de datum binnen bijvoorbeeld de komende 7 dagen vanaf vandaag valt. Zo ja, activeer een waarschuwing (e-mail, melding of zelfs een popup).

  • Toekomstige transacties: Sommige mensen voeren toekomstgedateerde transacties in (post-dated) voor zaken zoals geplande betalingen. Deze verschijnen niet in saldi tot de datum is gepasseerd (tenzij je rapporten uitvoert vanaf toekomstige datums). Een script kan zoeken naar transacties met een datum in de nabije toekomst en deze vermelden.

Hiermee kun je een "tickler"-script maken dat, wanneer uitgevoerd, een lijst van taken of binnenkort vervallende rekeningen uitvoert. Integreer met een API zoals Google Calendar of een taakbeheerder als je daar automatisch herinneringen wilt aanmaken.

Detectie van anomalieën​

Naast bekende drempels of datums kun je aangepaste waarschuwingen voor ongebruikelijke patronen scripten. Als een normaal maandelijkse uitgave bijvoorbeeld niet heeft plaatsgevonden (misschien ben je vergeten een rekening te betalen), of als de uitgaven van een categorie deze maand abnormaal hoog zijn, kan je script dit markeren. Dit omvat doorgaans het opvragen van recente gegevens en vergelijken met de geschiedenis (wat een geavanceerd onderwerp kan zijn – mogelijk met statistiek of ML).

In de praktijk vertrouwen veel gebruikers op reconciliatie om anomalieën (onverwachte transacties) op te sporen. Als je bankmeldingen ontvangt (zoals e-mails voor elke transactie), kun je die met een script parsen en automatisch aan Beancount toevoegen, of op zijn minst verifiëren dat ze zijn vastgelegd. Een enthousiasteling configureerde zelfs zijn bank om e-mails met transactiewaarschuwingen te sturen, met het plan om ze automatisch te parsen en aan de ledger toe te voegen. Dit soort event-driven waarschuwingen kan ervoor zorgen dat geen enkele transactie ongeregistreerd blijft.

Fava uitbreiden met aangepaste plugins en weergaven​

Fava is al scriptbaar via zijn extensiesysteem. Als je wilt dat je automatisering of rapporten direct in de webinterface integreren, kun je een Fava-extensie (ook wel plugin genoemd) in Python schrijven.

Hoe Fava-extensies werken: Een extensie is een Python-module die een klasse definieert die erft van fava.ext.FavaExtensionBase. Je registreert deze in je Beancount-bestand via een aangepaste optie. Als je bijvoorbeeld een bestand myextension.py hebt met een klasse MyAlerts(FavaExtensionBase), kun je deze inschakelen door aan je ledger toe te voegen:

1970-01-01 custom "fava-extension" "myextension"

Wanneer Fava laadt, importeert het die module en initialiseert je MyAlerts-klasse.

Extensies kunnen verschillende dingen doen:

  • Hooks: Ze kunnen inhaken op gebeurtenissen in Fava's levenscyclus. after_load_file() wordt bijvoorbeeld aangeroepen nadat de ledger is geladen. Je kunt dit gebruiken om controles uit te voeren of gegevens vooraf te berekenen. Als je de laag-saldo-controle binnen Fava wilde implementeren, zou after_load_file over rekeningsaldi kunnen itereren en waarschuwingen kunnen opslaan (hoewel het tonen ervan in de UI wat meer werk vereist, zoals het opwerpen van een FavaAPIError of het gebruik van Javascript om een melding te tonen).
  • Aangepaste rapporten/pagina's: Als je extensieklasse een report_title-attribuut instelt, voegt Fava een nieuwe pagina toe in de zijbalk. Vervolgens lever je een template (HTML/Jinja2) voor de inhoud van die pagina. Zo maak je volledig nieuwe weergaven, zoals een dashboard of samenvatting die Fava standaard niet heeft. De extensie kan alle gegevens verzamelen die nodig zijn (je hebt toegang tot self.ledger met alle entries, saldi, enz.) en vervolgens de template renderen.

De ingebouwde portfolio_list-extensie in Fava voegt bijvoorbeeld een pagina toe met je portefeuilleposities. Community-extensies gaan verder:

  • Dashboards: De fava-dashboards-plugin maakt het mogelijk aangepaste grafieken en panelen te definiëren (met bibliotheken zoals Apache ECharts). Het leest een YAML-configuratie van uit te voeren query's, voert deze uit via Beancount en genereert een dynamische dashboardpagina in Fava. In essentie combineert het Beancount-gegevens en een JavaScript-grafiekbibliotheek om interactieve visualisaties te produceren.
  • Portefeuilleanalyse: De PortfolioSummary-extensie (door gebruikers bijgedragen) berekent beleggingssamenvattingen (rekeningen groeperen, IRR berekenen, enz.) en toont deze in Fava's UI.
  • Transactiebeoordeling: Een andere extensie, fava-review, helpt bij het beoordelen van transacties in de loop van de tijd (bijv. om er zeker van te zijn dat je geen bonnetjes hebt gemist).

Om zelf een eenvoudige extensie te maken, begin je met het subclassen van FavaExtensionBase. Een minimale extensie die een pagina toevoegt, zou er bijvoorbeeld zo uit kunnen zien:

from fava.ext import FavaExtensionBase
 
class HelloReport(FavaExtensionBase):
    report_title = "Hello World"
 
    def __init__(self, ledger, config):
        super().__init__(ledger, config)
        # any initialization, perhaps parse config if provided
 
    def after_load_file(self):
        # (optional) run after ledger is loaded
        print("Ledger loaded with", len(self.ledger.entries), "entries")

Als je dit in hello.py plaatst en custom "fava-extension" "hello" aan je ledger toevoegt, zou Fava een nieuwe "Hello World"-pagina tonen (je hebt ook een templatebestand HelloReport.html nodig in een submap templates om de pagina-inhoud te definiëren, tenzij de extensie alleen hooks gebruikt). De template kan gegevens gebruiken die je aan de extensieklasse koppelt. Fava gebruikt Jinja2-templates, dus je zou je gegevens in een HTML-tabel of grafiek in die template kunnen renderen.

Opmerking: Fava's extensiesysteem is krachtig maar wordt als "onstabiel" beschouwd (onderhevig aan verandering). Het vereist enige bekendheid met webontwikkeling (HTML/JS) als je aangepaste pagina's maakt. Als je doel simpelweg is om scripts of analyses uit te voeren, is het misschien eenvoudiger om ze als externe scripts te houden. Gebruik Fava-extensies wanneer je een op maat gemaakte in-app ervaring voor je workflow wilt.

Integratie van Derde Partij API's en Gegevens​

Een van de voordelen van scriptbare workflows is de mogelijkheid om externe gegevens binnen te halen. Hier zijn veelvoorkomende integraties:

Voor gehoste waarderingsprijzen biedt Live Prices beheerde includes zonder een geplande prijsophaalscript. Kies ondersteunde activaparen en een quoteringsvaluta in de picker. De lokale op bestanden gebaseerde workflows hieronder blijven nuttig voor upstream Beancount, Fava en reproduceerbare rapporten. Een beheerde verversing maakt geen Git-commit in je ledger.

  • Wisselkoersen en grondstoffen: Upstream Beancount haalt prijzen niet zelf op, maar biedt een price-directive waarmee je koersen kunt opgeven. Je kunt het ophalen van deze prijzen automatiseren. Een script kan bijvoorbeeld een API (Yahoo Finance, Alpha Vantage, enz.) bevragen voor de laatste wisselkoers of aandelenkoers en een prijsentry aan je ledger toevoegen:

    2025-04-30 price BTC 30000 USD
    2025-04-30 price EUR 1.10 USD

    Er zijn tools zoals bea price, ondersteund door Beanprice in de managed engine, die dagelijkse koersen ophalen en deze in Beancount-formaat uitvoeren. Je kunt het eenmalig inschakelen met bea engine enable beanprice, en vervolgens bea price main.beancount plannen om elke nacht een prices.beancount-includebestand bij te werken. Of gebruik Python: bijvoorbeeld met de requests-bibliotheek om een API aan te roepen. Beancount's documentatie suggereert dat je voor publiek verhandelde activa "wat code kunt aanroepen die prijzen downloadt en de directives voor je uitschrijft." Met andere woorden, laat een script de opzoeking doen en de price-regels invoegen, in plaats van dat je het handmatig doet.

  • Aandelenportefeuillegegevens: Net als bij wisselkoersen kun je integreren met API's om gedetailleerde aandelen- of dividendgegevens op te halen. De Yahoo Finance API (of community-bibliotheken zoals yfinance) kan bijvoorbeeld historische gegevens voor een ticker ophalen. Een script zou je ledger kunnen bijwerken met maandelijkse prijshistorie voor elk aandeel dat je bezit, wat nauwkeurige historische rapporten van marktwaarde mogelijk maakt. Sommige aangepaste extensies (zoals fava_investor) halen zelfs prijsgegevens on-the-fly op voor weergave, maar het eenvoudigst is om prijzen regelmatig in de ledger te importeren.

  • Bank-API's (Open Banking/Plaid): In plaats van CSV's te downloaden, kun je API's gebruiken om transacties automatisch op te halen. Diensten zoals Plaid aggregeren bankrekeningen en bieden programmatische toegang tot transacties. In een geavanceerde opzet zou je een Python-script kunnen hebben dat Plaid's API gebruikt om dagelijks nieuwe transacties op te halen en naar een bestand op te slaan (of direct in de ledger te importeren). Een power-user bouwde een systeem waarbij Plaid hun importpijplijn voedt, waardoor hun boeken bijna automatisch worden. Ze merken op dat "niets je ervan weerhoudt om je aan te melden bij de Plaid API en hetzelfde lokaal te doen" – dat wil zeggen, je kunt een lokaal script schrijven om bankgegevens op te halen en vervolgens je Beancount-importerlogica gebruiken om deze naar ledger-entries te parsen. Sommige regio's hebben open banking-API's van banken; die kunnen op vergelijkbare wijze worden gebruikt.

  • Andere API's: Je kunt budgetteringstools integreren (geplande budgetten exporteren om te vergelijken met werkelijke cijfers in Beancount), of een OCR-API gebruiken om bonnetjes te lezen en automatisch aan transacties te koppelen. Omdat je scripts volledige toegang hebben tot Python's ecosysteem, kun je alles integreren van e-maildiensten (voor het verzenden van waarschuwingen) tot Google Sheets (bijv. een sheet bijwerken met maandelijkse financiële statistieken) tot berichtenapps (stuur jezelf een samenvattend rapport via een Telegram-bot).

Bij het gebruik van externe API's: beveilig je inloggegevens (gebruik omgevingsvariabelen of configuratiebestanden voor API-sleutels) en handel fouten (netwerkproblemen, API-storingen) op een correcte manier af in je scripts. Het is vaak verstandig om gegevens te cachen (bewaar bijvoorbeeld opgehaalde wisselkoersen zodat je dezelfde historische koers niet herhaaldelijk opvraagt).

Best practices voor modulaire, onderhoudbare scripts​

Naarmate je scriptbare workflows uitbouwt, houd je code georganiseerd en robuust:

  • Modulariteit: Splits verschillende verantwoordelijkheden in verschillende scripts of modules. Heb bijvoorbeeld aparte scripts voor "gegevens importeren/reconciliëren" versus "rapportgeneratie" versus "waarschuwingen". Je kunt zelfs een klein Python-pakket voor je ledger maken met modules zoals ledger_import.py, ledger_reports.py, enz. Dit maakt elk onderdeel gemakkelijker te begrijpen en te testen.

  • Configuratie: Vermijd het hardcoderen van waarden. Gebruik een configuratiebestand of variabelen bovenaan het script voor zaken zoals rekeningnamen, drempels, API-sleutels, datumbereiken, enz. Dit maakt het gemakkelijk om aan te passen zonder de code diepgaand te wijzigen. Definieer bijvoorbeeld LOW_BALANCE_THRESHOLDS = {"Assets:Bank:Checking": 500, "Assets:Savings": 1000} bovenaan, en je waarschuwingsscript kan door dit dict itereren.

  • Testen: Behandel je financiële automatisering als mission-critical code – want dat is het! Schrijf tests voor complexe logica. Beancount biedt enkele testhelpers (intern gebruikt voor importertesting) die je kunt benutten om ledger-inputs te simuleren. Zelfs zonder geavanceerde frameworks kun je een dummy-CSV en verwachte output-transacties hebben en controleren of je importscript de juiste entries produceert. Als je pytest gebruikt, kun je deze tests eenvoudig integreren (zoals Alex Watt deed via een just test-opdracht die pytest omhult).

  • Versiebeheer: Houd je ledger en scripts onder versiebeheer (git). Dit geeft je niet alleen back-ups en geschiedenis, maar moedigt je ook aan om wijzigingen op een gecontroleerde manier aan te brengen. Je kunt releases van je "financiële scripts" taggen of verschillen bekijken bij het debuggen van een probleem. Sommige gebruikers houden zelfs hun financiële administratie in Git bij om veranderingen in de loop van de tijd te zien. Wees wel voorzichtig om gevoelige gegevens (zoals ruwe afschriftbestanden of API-sleutels) te negeren in je repository.

  • Documentatie: Documenteer je aangepaste workflows voor je toekomstige zelf. Een README in je repository die uitlegt hoe je de omgeving opzet, hoe je elk script uitvoert en wat elk doet, is onmisbaar nadat er maanden zijn verstreken. Geef ook commentaar in je code, vooral bij niet voor de hand liggende boekhoudlogica of API-interactie.

  • Onderhoud van Fava-plugins: Als je een Fava-extensie schrijft, houd deze dan eenvoudig. Fava kan veranderen, dus kleinere extensies met gerichte functionaliteit zijn gemakkelijker bij te werken. Vermijd het dupliceren van te veel logica – gebruik Beancount's query-engine of bestaande hulpfuncties waar mogelijk, in plaats van berekeningen hard te coderen die gevoelig kunnen zijn voor ledgerwijzigingen.

  • Beveiliging: Omdat je scripts mogelijk gevoelige gegevens verwerken en verbinding maken met externe diensten, behandel ze met zorg. Stel API-sleutels niet bloot en overweeg om je automatisering op een beveiligde machine uit te voeren. Als je een gehoste oplossing of cloud gebruikt (zoals het plannen van GitHub Actions of een server om Fava uit te voeren), zorg er dan voor dat je ledgergegevens in rust versleuteld zijn en dat je je comfortabel voelt bij de privacy-implicaties.

Door deze praktijken te volgen, zorg je ervoor dat je workflow betrouwbaar blijft, zelfs naarmate je financiën (en de tools zelf) evolueren. Je wilt scripts die je jaar na jaar kunt hergebruiken, met minimale aanpassingen.

Conclusie​

Beancount en Fava bieden een krachtig, flexibel platform waarmee technisch onderlegde gebruikers hun persoonlijke financiële administratie volledig kunnen aanpassen. Door Python-scripts te schrijven kun je vervelende taken zoals het reconciliëren van afschriften automatiseren, rijke rapporten produceren die op jouw behoeften zijn toegesneden, en je financiën met tijdige waarschuwingen in de gaten houden. We hebben een reeks voorbeelden behandeld van basis tot geavanceerd – beginnend met eenvoudige query's en CSV-imports, en gaand naar volwaardige Fava-plugins en integraties met externe API's. Begin bij de implementatie eenvoudig en bouw geleidelijk op. Zelfs een paar kleine automatiseringsscripts kunnen uren werk besparen en de nauwkeurigheid enorm verbeteren. En onthoud: omdat alles plain text en Python is, heb je volledige controle – je financiële systeem groeit met je mee en buigt naar je specifieke behoeften. Veel plezier met scripten!

Bronnen: De bovenstaande technieken zijn ontleend aan de Beancount-documentatie en ervaringen uit de community. Voor verder lezen zie Beancount's officiële documentatie, community-gidsen en blogs, en de Awesome Beancount-repository voor links naar nuttige plugins en tools.

Bron: https://beancount.io/nl/docs/Solutions/scriptable-workflows