From 21f5fbeba61a203510276ad8b360b2576c004609 Mon Sep 17 00:00:00 2001 From: Andreas Gerstmayr Date: Sun, 30 Jun 2024 21:48:16 +0200 Subject: [PATCH] use ledger start/end date if date filter is greater than ledger time frame (#69) Adjust the dates in case the date filter is set to e.g. 2023-2024, however the ledger only contains data up to summer 2024. Without this, all averages in the dashboard are off, because of a wrong number of days between dateFirst and dateLast. --- README.md | 4 +-- src/fava_dashboards/__init__.py | 59 +++++++++++++++++++++++++-------- 2 files changed, 47 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 89f8932..22a6336 100644 --- a/README.md +++ b/README.md @@ -56,8 +56,8 @@ The following variables and functions are available: * `ext`: the Fava [`ExtensionContext`](https://github.com/beancount/fava/blob/main/frontend/src/extensions.ts) * `ext.api.get("query", {bql: "SELECT ..."}`: executes the specified BQL query * `panel`: the current (augmented) panel definition. The results of the BQL queries can be accessed with `panel.queries[i].result`. -* `ledger.dateFirst`: first date in the current date filter -* `ledger.dateLast`: last date in the current date filter +* `ledger.dateFirst`: start date of the current date filter, or first transaction date of the ledger +* `ledger.dateLast`: end date of the current date filter, or last transaction date of the ledger * `ledger.operatingCurrencies`: configured operating currencies of the ledger * `ledger.ccy`: shortcut for the first configured operating currency of the ledger * `ledger.accounts`: declared accounts of the ledger diff --git a/src/fava_dashboards/__init__.py b/src/fava_dashboards/__init__.py index 8ee23ca..51d379a 100644 --- a/src/fava_dashboards/__init__.py +++ b/src/fava_dashboards/__init__.py @@ -1,4 +1,4 @@ -from typing import Any, Dict +from typing import Any, Dict, List import datetime from dataclasses import dataclass from collections import namedtuple @@ -7,6 +7,9 @@ from beancount.core.inventory import Inventory # type: ignore from beanquery.query import run_query # type: ignore from fava.application import render_template_string +from fava.beans.abc import Directive +from fava.beans.abc import Price +from fava.beans.abc import Transaction from fava.context import g from fava.core import FavaLedger from fava.core.conversion import simple_units @@ -114,38 +117,66 @@ def process_panel(self, ctx: PanelCtx): self.process_jinja2(ctx) self.sanitize_panel(ctx) - def bootstrap(self, dashboard_id): - ext_config = self.read_ext_config() + def get_ledger_duration(self, entries: List[Directive]): + date_first = None + date_last = None + for entry in entries: + if isinstance(entry, Transaction): + date_first = entry.date + break + for entry in reversed(entries): + if isinstance(entry, (Transaction, Price)): + date_last = entry.date + break + if not date_first or not date_last: + raise FavaAPIError("no transaction found") + return (date_first, date_last) + + def get_ledger(self): operating_currencies = self.ledger.options["operating_currency"] - if len(operating_currencies) == 0: raise FavaAPIError("no operating currency specified in the ledger") - # pylint: disable=protected-access - if not g.filtered._date_first or not g.filtered._date_last: - raise FavaAPIError( - "cannot determine first/last day of ledger, is the ledger empty?" + + if g.filtered.date_range: + date_first = g.filtered.date_range.begin + date_last = g.filtered.date_range.end - datetime.timedelta(days=1) + + # Adjust the dates in case the date filter is set to e.g. 2023-2024, + # however the ledger only contains data up to summer 2024. + # Without this, all averages in the dashboard are off, + # because of a wrong number of days between dateFirst and dateLast. + ledger_date_first, ledger_date_last = self.get_ledger_duration( + self.ledger.all_entries ) + if not (date_last < ledger_date_first or date_first > ledger_date_last): + date_first = max(date_first, ledger_date_first) + date_last = min(date_last, ledger_date_last) + else: + # Use filtered ledger here, as another filter (e.g. tag filter) could be applied. + date_first, date_last = self.get_ledger_duration(g.filtered.entries) commodities = {c.currency: c for c in self.ledger.all_entries_by_type.Commodity} accounts = self.ledger.accounts - ledger = { - # pylint: disable=protected-access - "dateFirst": g.filtered._date_first, - # pylint: disable=protected-access - "dateLast": g.filtered._date_last - datetime.timedelta(days=1), + return { + "dateFirst": date_first, + "dateLast": date_last, "operatingCurrencies": operating_currencies, "ccy": operating_currencies[0], "accounts": accounts, "commodities": commodities, } + def bootstrap(self, dashboard_id): + ext_config = self.read_ext_config() + ledger = self.get_ledger() + dashboards_yaml = self.read_dashboards_yaml(ext_config.dashboards_path) dashboards = dashboards_yaml.get("dashboards", []) if not 0 <= dashboard_id < len(dashboards): raise FavaAPIError(f"invalid dashboard ID: {dashboard_id}") for panel in dashboards[dashboard_id].get("panels", []): - ctx = PanelCtx(ledger, self.ledger, panel) + ctx = PanelCtx(ledger=ledger, favaledger=self.ledger, panel=panel) try: self.process_panel(ctx) except Exception as ex: