Преминете към основното съдържание

Скриптови Работни Процеси с Beancount и Fava

Beancount (инструмент за счетоводство в обикновен текст със двойно записване) и Fava (неговият уеб интерфейс) са изключително разширяеми и могат да бъдат управлявани чрез скриптове. Техният дизайн позволява да автоматизирате финансови задачи, да генерирате персонализирани отчети и да настройвате сигнали чрез писане на Python скриптове. По думите на един потребител: “Много ми харесва, че данните ми са в толкова удобен формат и че мога да автоматизирам нещата до насита. Няма API като файл на вашия диск; лесно е да се интегрира с него.” Това ръководство ще ви преведе през създаването на скриптови работни процеси – от лесна за начинаещи автоматизация до разширени Fava плъгини.

Първи стъпки: Изпълнение на Beancount като Python скрипт

scriptable-workflows

Преди да се задълбочите в конкретни задачи, уверете се, че имате инсталиран Beancount (например чрез pip install beancount). Тъй като Beancount е написан на Python, можете да го използвате като библиотека във вашите собствени скриптове. Общият подход е:

  • Заредете вашата Beancount счетоводна книга: Използвайте зареждащото устройство на Beancount, за да анализирате .beancount файла в Python обекти. Например:

    from beancount import loader
    entries, errors, options_map = loader.load_file("myledger.beancount")
    if errors:
    print("Errors:", errors)

    Това ви дава списък с entries (транзакции, салда и т.н.) и options_map с метаданни. Всички ваши сметки, транзакции и салда вече са достъпни в кода.

  • Използвайте Beancount Query Language (BQL): Вместо ръчно итериране, можете да изпълнявате SQL-подобни заявки върху данните. Например, за да получите общите разходи по месеци, можете да използвате API за заявки:

    from beancount.query import query
    q = query.Query(entries, options_map)
    result = q.query("SELECT month, sum(position) WHERE account ~ 'Expenses' GROUP BY month")
    print(result)

    Това използва Query системата на Beancount за агрегиране на данни. (Всъщност, това е подобно на това, което прави командата bean-query, но тук я използвате в скрипт.) Всъщност, авторът на Beancount отбелязва, че можете да заредите файла и да извикате run_query() директно чрез Python API, избягвайки необходимостта да извиквате външни команди в цикъл.

  • Настройте структура на проекта: Организирайте вашите скриптове заедно с вашата счетоводна книга. Общо оформление е да имате директории за импортери (за извличане/анализиране на външни данни), отчети или заявки (за скриптове за анализ) и документи (за съхранение на изтеглени извлечения). Например, един потребител поддържа:

    • importers/ – персонализирани Python скриптове за импортиране (с тестове),
    • queries/ – скриптове за генериране на отчети (изпълними чрез python3 queries/...),
    • documents/ – изтеглени банкови CSV/PDF файлове, организирани по сметка.

С тази настройка можете да изпълнявате скриптове ръчно (например python3 queries/cash_flow.py) или да ги планирате (чрез cron или task runner), за да автоматизирате вашия работен процес.

Автоматизиране на Задачи за Съгласуване

Съгласуване означава да се уверите, че вашата счетоводна книга съвпада с външни записи (банкови извлечения, отчети за кредитни карти и т.н.). Обикновеният текстов формат на Beancount и Python API правят възможно автоматизирането на голяма част от този процес.

Импортиране и Съпоставяне на Транзакции (Начинаещи)

За начинаещи, препоръчителният подход е да използвате импортър плъгини на Beancount. Вие пишете малък Python клас, следващ импортър протокола на Beancount, за да анализирате даден формат (CSV, OFX, PDF и т.н.) и да генерирате транзакции. След това използвайте командата bean-extract или скрипт, за да приложите тези импортери:

  • Напишете импортер (Python клас с методи като identify(), extract()) за CSV формата на вашата банка. Документацията на Beancount предоставя ръководство и примери.
  • Използвайте bean-extract в скрипт или Makefile (като примера justfile), за да анализирате нови извлечения. Например, един работен процес изпълнява bean-extract върху всички файлове в ~/Downloads и извежда транзакциите във временен файл.
  • Ръчно прегледайте и копирайте транзакциите от временния файл във вашата основна счетоводна книга, след което изпълнете bean-check, за да се уверите, че салда съвпадат.

Въпреки че този процес все още включва стъпка за преглед, голяма част от досадната работа по анализиране и форматиране на записи е автоматизирана. Импортър скриптовете могат също автоматично да присвояват категории и дори да задават потвърждения за баланс (декларации за очаквани салда), за да уловят несъответствия. Например, след импортиране, може да имате ред като 2025-04-30 balance Assets:Bank:Checking 1234.56 USD, който потвърждава затварящото салдо. Когато изпълните bean-check, Beancount ще провери дали всички тези потвърждения за баланс са правилни и ще маркира всички грешки, ако транзакции липсват или са дублирани. Това е най-добра практика: автоматично генерирайте потвърждения за баланс за всеки период на извлечение, за да позволите на компютъра да забележи несъгласувани разлики вместо вас.

Персонализирани Скриптове за Съгласуване (Средно ниво)

За повече контрол можете да напишете персонализиран Python скрипт, за да сравните списъка с транзакции на банката (CSV или чрез API) с вашите записи в счетоводната книга:

  1. Прочетете външните данни: Анализирайте CSV файла на банката, използвайки Python модула csv (или Pandas). Нормализирайте данните в списък с транзакции, например всяка с дата, сума и описание.
  2. Заредете транзакциите от счетоводната книга: Използвайте loader.load_file, както е показано по-рано, за да получите всички записи от счетоводната книга. Филтрирайте този списък до сметката, която ви интересува (например вашата чекова сметка) и може би датата на извлечението.
  3. Сравнете и намерете несъответствия:
  • За всяка външна транзакция проверете дали съществува идентичен запис в счетоводната книга (съвпадение по дата и сума, може би описание). Ако не бъде намерен, го маркирайте като “нов” и евентуално го изведете като транзакция, форматирана за Beancount, за да я прегледате.
  • Обратно, идентифицирайте всички записи в счетоводната книга в тази сметка, които не се появяват във външния източник – това може да са грешки при въвеждане на данни или транзакции, които не са били осчетоводени от банката.
  1. Изведете резултатите: Отпечатайте отчет или създайте нов .beancount фрагмент с липсващите транзакции.

Като пример, скрипт от общността, наречен reconcile.py, прави точно това: като се има предвид Beancount файл и входен CSV, той отпечатва списък с нови транзакции, които трябва да бъдат импортирани, както и всички съществуващи записи в счетоводната книга, които не са във входящите данни (потенциално признак за неправилна класификация). С такъв скрипт месечното съгласуване може да бъде толкова просто, колкото да го изпълните и след това да добавите предложените транзакции към вашата счетоводна книга. Един потребител на Beancount отбелязва, че “извършва процес на съгласуване на всички сметки всеки месец” и използва нарастваща колекция от Python код, за да елиминира голяма част от ръчната работа при импортиране и съгласуване на данни.

Съвет: По време на съгласуването използвайте инструментите на Beancount за точност:

  • Използвайте потвърждения за баланс, както споменахме, за да имате автоматизирани проверки на салдата по сметките.
  • Използвайте директивата pad, ако желаете, която може автоматично да вмъква балансиращи записи за малки разлики при закръгляне (използвайте с повишено внимание).
  • Напишете unit тестове за вашата импортър или логика за съгласуване (Beancount предоставя помощни средства за тестване). Например, един работен процес включва вземане на примерен CSV, писане на неуспешни тестове с очаквани транзакции, след което внедряване на импортера, докато всички тестове преминат. Това гарантира, че вашият скрипт за импортиране работи правилно за различни случаи.

Генериране на Персонализирани Отчети и Обобщения

Въпреки че Fava предоставя много стандартни отчети (Отчет за Доходите, Баланс и т.н.), можете да създадете персонализирани отчети, използвайки скриптове. Те могат да варират от прости изходи на конзолата до богато форматирани файлове или диаграми.

Заявки за Данни за Отчети (Начинаещи)

На основно ниво можете да използвате Beancount Query Language (BQL), за да получите обобщени данни и да ги отпечатате или запазите. Например:

  • Обобщение на Паричния Поток: Използвайте заявка, за да изчислите нетния паричен поток. “Паричен поток” може да се дефинира като промяна в салдото на определени сметки за определен период. Използвайки BQL, можете да направите:

    SELECT year, month, sum(amount)
    WHERE account LIKE 'Income:%' OR account LIKE 'Expenses:%'
    GROUP BY year, month

    Това ще нетира всички приходи и разходи по месеци. Можете да изпълните това чрез bean-query CLI или чрез Python API (query.Query, както е показано по-рано) и след това да форматирате резултата.

  • Отчет за Разходите по Категории: Заявете общите разходи за всяка категория:

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

    Това дава таблица с разходите по категории. Можете да изпълнявате множество заявки в скрипт и да извеждате резултатите като текст, CSV или дори JSON за по-нататъшна обработка.

Един потребител установи, че е “тривиално” да анализира финансови данни с Fava или със скриптове, като посочи, че използва един Python скрипт, за да извлече данни от Beancount чрез Query Language и след това да ги постави в Pandas DataFrame, за да подготви персонализиран отчет. Например, можете да извлечете месечни суми със заявка и след това да използвате Pandas/Matplotlib, за да начертаете диаграма на паричния поток във времето. Комбинацията от BQL и библиотеки за наука за данни ви позволява да изграждате отчети извън това, което Fava предлага по подразбиране.

Разширено Отчитане (Диаграми, Производителност и т.н.)

За по-разширени нужди, вашите скриптове могат да изчисляват показатели като инвестиционна производителност или да създават визуални изходи:

  • Инвестиционна Производителност (IRR/XIRR): Тъй като вашата счетоводна книга съдържа всички парични потоци (купувания, продажби, дивиденти), можете да изчислите нивата на възвръщаемост на портфолиото. Например, можете да напишете скрипт, който филтрира транзакциите на вашите инвестиционни сметки и след това изчислява Вътрешната Норма на Възвръщаемост. Има библиотеки (или формули) за изчисляване на IRR, като се имат предвид данните за паричните потоци. Някои Fava разширения, разработени от общността (като PortfolioSummary или fava_investor), правят точно това, изчислявайки IRR и други показатели за инвестиционни портфейли. Като скрипт, можете да използвате IRR функция (от NumPy или ваша собствена) върху поредицата от вноски/тегления плюс крайната стойност.

  • Многопериодни или Персонализирани Показатели: Искате отчет за вашия процент на спестяване (съотношение на спестяванията към доходите) всеки месец? Python скрипт може да зареди счетоводната книга, да сумира всички сметки за Доходи и всички сметки за Разходи, след което да изчисли спестявания = доходи - разходи и процента. Това може да изведе хубава таблица или дори да генерира HTML/Markdown отчет за вашите записи.

  • Визуализация: Можете да генерирате диаграми извън Fava. Например, използвайте matplotlib или altair в скрипт, за да създадете диаграма на нетната стойност във времето, използвайки данни от счетоводната книга. Тъй като счетоводната книга има всички исторически салда (или можете да ги натрупате чрез итериране на записи), можете да създавате графики на времеви серии. Запазете тези диаграми като изображения или интерактивен HTML. (Ако предпочитате визуални ефекти в приложението, вижте раздела за Fava разширения по-долу за добавяне на диаграми във Fava.)

Опции за Изход: Решете как да предоставите отчета:

  • За еднократен анализ може да е достатъчно да отпечатате на екрана или да запазите в CSV/Excel файл.
  • За табла за управление обмислете генерирането на HTML файл с данните (евентуално използвайки библиотека за шаблони като Jinja2 или дори просто писане на Markdown), който можете да отворите в браузър.
  • Можете също да се интегрирате с Jupyter Notebooks за интерактивна среда за отчитане, въпреки че това е повече за проучване, отколкото за автоматизация.

Задействане на Сигнали от Вашата Счетоводна Книга

Друго мощно използване на скриптови работни процеси е настройването на сигнали въз основа на условия във вашите финансови данни. Тъй като вашата счетоводна книга се актуализира редовно (и може да включва елементи с бъдеща дата като предстоящи сметки или бюджети), можете да я сканирате със скрипт и да бъдете уведомени за важни събития.

Предупреждения за Нисък Баланс по Сметката

За да избегнете овърдрафти или да поддържате минимален баланс, може да искате сигнал, ако някоя сметка (например чекова или спестовна) падне под праг. Ето как можете да внедрите това:

  1. Определете текущите салда: След като заредите entries чрез зареждащото устройство, изчислете последния баланс на сметките, които ви интересуват. Можете да направите това чрез агрегиране на записи или използване на заявка. Например, използвайте BQL заявка за баланса на конкретна сметка:

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

    Това връща текущия баланс на тази сметка (сума на всички нейни записи). Като алтернатива, използвайте вътрешните функции на Beancount за изграждане на баланс. Например:

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

    След това извлечете числената стойност (например balance.get_currency_units('USD') може да даде Decimal). Въпреки това, използването на заявката е по-просто в повечето случаи.

  2. Проверете прага: Сравнете баланса с предварително зададения от вас лимит. Ако е под, задействайте сигнал.

  3. Задействайте известие: Това може да бъде толкова просто, колкото отпечатване на предупреждение в конзолата, но за истински сигнали можете да изпратите имейл или push известие. Можете да се интегрирате с имейл (чрез smtplib) или услуга като IFTTT или Slack’s webhook API, за да изпратите сигнала. Например:

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

    (Внедрете send_email с данните за вашия имейл сървър.)

Чрез ежедневно изпълнение на този скрипт (чрез cron job или Windows Task Scheduler) ще получавате проактивни предупреждения. Тъй като той използва счетоводната книга, той може да вземе предвид всички транзакции, включително тези, които току-що сте добавили.

Предстоящи Крайни Срокове за Плащане

Ако използвате Beancount за проследяване на сметки или крайни срокове, можете да маркирате бъдещи плащания и да имате скриптове, които да ви напомнят. Два начина да представите предстоящи задължения в Beancount:

  • Събития: Beancount поддържа event директива за произволни датирани бележки. Например:

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

    Това не засяга салдата, но записва дата с етикет. Скрипт може да сканира entries за Event записи, където Event.type == "BillDue" (или всеки персонализиран тип, който изберете) и да провери дали датата е в рамките на, да речем, следващите 7 дни от днес. Ако е така, задействайте сигнал (имейл, известие или дори изскачащ прозорец).

  • Бъдещи Транзакции: Някои хора въвеждат бъдещи транзакции (датирани в бъдещето) за неща като планирани плащания. Те няма да се показват в салдата, докато датата не мине (освен ако не изпълнявате отчети към бъдещи дати). Скрипт може да търси транзакции, датирани в близко бъдеще, и да ги изброи.

Използвайки тези, можете да създадете скрипт “tickler”, който, когато бъде изпълнен, извежда списък със задачи или сметки, които скоро ще бъдат дължими. Интегрирайте се с API като Google Calendar или мениджър на задачи, ако искате автоматично да създавате напомняния там.

Откриване на Аномалии

Отвъд известни прагове или дати, можете да напишете скриптове за персонализирани сигнали за необичайни модели. Например, ако обикновено месечен разход не се е случил (може би сте забравили да платите сметка) или ако разходите за категория са необичайно високи този месец, вашият скрипт може да го маркира. Това обикновено включва заявка за скорошни данни и сравняване с историята (което може да бъде разширена тема – вероятно включваща статистика или ML).

На практика много потребители разчитат на съгласуване, за да уловят аномалии (неочаквани транзакции). Ако получавате банкови известия (като имейли за всяка транзакция), можете да ги анализирате със скрипт и автоматично да ги добавите към Beancount или поне да проверите дали са записани. Един ентусиаст дори е конфигурирал банката си да изпраща имейли с известия за транзакции, с плана да ги анализира и автоматично да ги добавя към счетоводната книга. Този вид задействан от събития сигнал може да гарантира, че нито една транзакция не остава незаписана.

Разширяване на Fava с Персонализирани Плъгини и Изгледи

Fava вече може да се управлява чрез скриптове чрез своята система за разширения. Ако искате вашата автоматизация или отчети да се интегрират директно в уеб интерфейса, можете да напишете Fava разширение (наречено още плъгин) в Python.

Как работят Fava разширенията: Разширението е Python модул, който дефинира клас, наследяващ от fava.ext.FavaExtensionBase. Регистрирате го във вашия Beancount файл чрез персонализирана опция. Например, ако имате файл myextension.py с клас MyAlerts(FavaExtensionBase), можете да го активирате, като добавите към вашата счетоводна книга:

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

Когато Fava се зареди, той ще импортира този модул и ще инициализира вашия клас MyAlerts.

Разширенията могат да правят няколко неща:

  • Куки: Те могат да се закачат към събития в жизнения цикъл на Fava. Например, after_load_file() се извиква след зареждане на счетоводната книга. Можете да използвате това, за да изпълните проверки или предварително да изчислите данни. Ако искате да внедрите проверката за нисък баланс вътре във Fava, after_load_file може да итерира през салдата по сметките и евентуално да съхранява предупреждения (въпреки че извеждането им в потребителския интерфейс може да изисква малко повече работа, като например повдигане на FavaAPIError или използване на Javascript за показване на известие).
  • Персонализирани Отчети/Страници: Ако вашият клас разширение зададе атрибут report_title, Fava ще добави нова страница в страничната лента за него. След това предоставяте шаблон (HTML/Jinja2) за съдържанието на тази страница. Ето как създавате изцяло нови изгледи, като например табло за управление или обобщение, които Fava няма по подразбиране. Разширението може да събере каквито данни са му необходими (имате достъп до self.ledger, който има всички записи, салда и т.н.) и след това да рендира шаблона.

Например, вграденото разширение portfolio_list във Fava добавя страница, изброяваща вашите позиции в портфолиото. Разширенията на общността отиват по-далеч:

  • Табла за управление: Плъгинът fava-dashboards позволява дефиниране на персонализирани диаграми и панели (използвайки библиотеки като Apache ECharts). Той чете YAML конфигурация на заявки за изпълнение, изпълнява ги чрез Beancount и генерира динамична страница с табло за управление във Fava. По същество той свързва Beancount данните и JavaScript библиотека за диаграми, за да създаде интерактивни визуализации.
  • Анализ на портфолио: Разширението PortfolioSummary (предоставено от потребители) изчислява инвестиционни обобщения (групиране на сметки, изчисляване на IRR и т.н.) и ги показва в потребителския интерфейс на Fava.
  • Преглед на транзакции: Друго разширение, fava-review, помага за преглед на транзакции във времето (например, за да се уверите, че не сте пропуснали никакви разписки).

За да създадете просто разширение сами, започнете, като наследите FavaExtensionBase. Например, минимално разширение, което добавя страница, може да изглежда така:

from fava.ext import FavaExtensionBase

class HelloReport(FavaExtensionBase):
report_title = "Hello World"

def __init__(self, ledger, config):
super().__init__(ledger, config)
# всяка инициализация, може би анализирайте конфигурацията, ако е предоставена

def after_load_file(self):
# (по избор) изпълнете след зареждане на счетоводната книга
print("Ledger loaded with", len(self.ledger.entries), "entries")

Ако поставите това в hello.py и добавите custom "fava-extension" "hello" към вашата счетоводна книга, Fava ще покаже нова страница "Hello World" (ще ви трябва и темплейтен файл HelloReport.html в подпапка templates, за да дефинирате съдържанието на страницата, освен ако разширението използва само куки). Шаблонът може да използва данни, които прикачвате към класа на разширението. Fava използва Jinja2 шаблони, така че можете да рендирате вашите данни в HTML таблица или диаграма в този шаблон.

Забележка: Разширителната система на Fava е мощна, но се счита за “нестабилна” (подлежи на промяна). Тя изисква известна степен на познаване на уеб разработката (HTML/JS), ако правите персонализирани страници. Ако вашата цел е просто да изпълнявате скриптове или анализи, може да е по-лесно да ги запазите като външни скриптове. Използвайте Fava разширения, когато искате персонализирано изживяване в приложението за вашия работен процес.

Интегриране на API на Трети Страни и Данни

Едно от предимствата на скриптовите работни процеси е възможността за извличане на външни данни. Ето често срещани интеграции:

  • Обменни Курсове и Суровини: Beancount не извлича автоматично цени по дизайн (за да запази отчетите детерминистични), но предоставя Price директива, за да предоставите курсове. Можете да автоматизирате извличането на тези цени. Например, скрипт може да заяви API (Yahoo Finance, Alpha Vantage и т.н.) за най-новия обменен курс или цена на акции и да добави запис за цена към вашата счетоводна книга:

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

    Има инструменти като bean-price (сега външен инструмент под егидата на Beancount), които извличат ежедневни котировки и ги извеждат във формат Beancount. Можете да планирате bean-price да се изпълнява всяка вечер, за да актуализирате включен файл prices.beancount. Или използвайте Python: например, с библиотеката requests, за да извикате API. Документацията на Beancount предполага, че за публично търгувани активи можете да “извикате код, който ще изтегли цените и ще изпише директивите за вас”. С други думи, оставете скрипт да извърши търсенето и да вмъкне редовете price, вместо вие да го правите ръчно.

  • Данни за Фондово Портфолио: Подобно на обменните курсове, можете да се интегрирате с API, за да извличате подробни данни за акции или дивиденти. Например, Yahoo Finance API (или библиотеки на общността като yfinance) може да извлече исторически данни за тикер. Скрипт може да актуализира вашата счетоводна книга с месечна история на цените за всяка акция, която притежавате, което позволява точни исторически отчети за пазарната стойност. Някои персонализирани разширения (като fava_investor) дори извличат данни за цените в движение за показване, но най-просто е редовно да импортирате цените в счетоводната книга.

  • Банкови API (Отворено Банкиране/Plaid): Вместо да изтегляте CSV файлове, можете да използвате API за автоматично извличане на транзакции. Услуги като Plaid агрегират банкови сметки и позволяват програмен достъп до транзакциите. В разширена настройка можете да имате Python скрипт, който използва Plaid API за ежедневно извличане на нови транзакции и ги запазва във файл (или директно импортира в счетоводната книга). Един опитен потребител е изградил система, в която Plaid се захранва в техния канал за импортиране, което прави книгите им почти автоматични. Те отбелязват, че “нищо не ви пречи да се регистрирате в Plaid API и да направите същото локално” – т.е. можете да напишете локален скрипт за получаване на банкови данни, след което да използвате вашата Beancount импортър логика, за да ги анализирате в записи в счетоводната книга. Някои региони имат API за отворено банкиране, предоставени от банки; те могат да бъдат използвани по подобен начин.

  • Други API: Можете да интегрирате инструменти за бюджетиране (експортиране на планирани бюджети за сравнение с действителните в Beancount) или да