fix: Grafana-Zeitfenster deckt Projektionshorizont ab (Szenario-Vergleich sichtbar)

Das Szenario-Vergleich-Panel zeichnet die Projektion (Zukunft, horizon_days ab
heute). Kein Panel nutzt $__timeFilter, daher begrenzt allein das
Dashboard-Zeitfenster die Anzeige. Mit time.to='now' wurde die gesamte Kurve ab
morgen abgeschnitten (nur ~4 Tage <= heute sichtbar, Y-Achse auf deren schmalen
Bereich fixiert). Fix: time.to='now+19M' deckt den 548-Tage-Horizont ab.
Regressionstest tests/test_grafana_dashboard.py haelt die Invariante fest.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-24 20:39:23 +02:00
parent dec1cb9075
commit 224baba326
2 changed files with 52 additions and 1 deletions

View File

@@ -8,7 +8,7 @@
"tags": ["finanzen"],
"time": {
"from": "now-1y",
"to": "now"
"to": "now+19M"
},
"refresh": "",
"panels": [

View File

@@ -0,0 +1,51 @@
"""Regressionstests fuer das provisionierte Grafana-Dashboard finanzen.json.
Hintergrund: Kein Panel nutzt $__timeFilter, jedes liefert alle Zeilen und
Grafana beschneidet auf das Dashboard-Zeitfenster. Das Szenario-Vergleich-Panel
zeichnet die Projektion (Zukunft, horizon_days ab heute). Steht 'to' auf 'now',
wird die gesamte Kurve ab morgen abgeschnitten (nur die ~4 Tage <= heute
sichtbar, Y-Achse auf deren schmalen Bereich fixiert). Dieser Test haelt fest,
dass 'to' weit genug in die Zukunft reicht.
"""
import json
import re
from pathlib import Path
from app.config import get_settings
DASHBOARD = Path(__file__).resolve().parent.parent / "grafana" / "dashboards" / "finanzen.json"
_UNIT_DAYS = {"y": 365, "M": 30, "w": 7, "d": 1, "h": 1 / 24}
def _relative_to_days(expr: str) -> float:
"""Grafana-Relativausdruck ('now', 'now+19M', 'now-1y') -> vorzeichen-
behafteter Tages-Offset relativ zu 'now' (Zukunft positiv)."""
expr = expr.strip()
if expr == "now":
return 0.0
m = re.fullmatch(r"now([+-])(\d+)([yMwdh])", expr)
if not m:
raise ValueError(f"Unerwarteter Grafana-Zeitausdruck: {expr!r}")
sign = 1 if m.group(1) == "+" else -1
return sign * int(m.group(2)) * _UNIT_DAYS[m.group(3)]
def _load() -> dict:
return json.loads(DASHBOARD.read_text())
def test_dashboard_has_szenario_panel():
titles = [p.get("title") for p in _load().get("panels", [])]
assert "Szenario-Vergleich" in titles
def test_time_window_covers_projection_horizon():
d = _load()
horizon = get_settings().horizon_days
to_days = _relative_to_days(d["time"]["to"])
assert to_days >= horizon, (
f"time.to reicht nur {to_days} Tage in die Zukunft, deckt aber den "
f"Projektionshorizont von {horizon} Tagen nicht ab -> Projektionskurve "
f"wird abgeschnitten"
)