77993d0ea0
Build pitch-deck / build-push-deploy (push) Failing after 24s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 5m28s
CI / test-python-voice (push) Successful in 4m0s
CI / test-bqas (push) Successful in 32s
Generiert pro Szenario (Wandeldarlehen 200k/Bear/Bull, 1 Mio Base/Bear/Bull) ein .xlsx mit 10 Tabs (Dashboard, Kunden, Umsatzerlöse, Personalkosten, Investitionen, Materialaufwand, Betriebliche Aufwendungen, Liquidität, GuV, Formelübersicht). Editierbare Eingaben bleiben rohe Werte; abgeleitete Zellen werden zu echten Excel-Formeln über Tabs hinweg, sodass das Bearbeiten von Inputs Personal/Opex/Liquidität/GuV neu berechnet. Dashboard-Tab fasst Jahres-KPIs zusammen und enthält fünf Charts (Umsatz/Material/Personal/EBIT YoY, Jahresüberschuss YoY, Liquidität, Headcount, Personalkosten monatlich). Run: PG_CONN=... pitch-deck/scripts/export-finanzplan.sh Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
146 lines
5.0 KiB
Python
146 lines
5.0 KiB
Python
"""
|
|
Post-process Finanzplan Excel exports: attach charts to the Dashboard sheet
|
|
and move Dashboard to be the first tab.
|
|
|
|
Run after export-finanzplan-excel.ts:
|
|
python3 scripts/add-charts.py
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
from openpyxl import load_workbook
|
|
from openpyxl.chart import BarChart, LineChart, Reference
|
|
from openpyxl.chart.label import DataLabelList
|
|
from openpyxl.utils import get_column_letter
|
|
|
|
EXPORTS_DIR = Path(__file__).resolve().parent.parent / "exports"
|
|
|
|
|
|
def column_count_until_empty(ws, start_col: int, header_row: int) -> int:
|
|
"""Count consecutive non-empty cells starting at (header_row, start_col)."""
|
|
c = start_col
|
|
while ws.cell(row=header_row, column=c).value not in (None, ""):
|
|
c += 1
|
|
return c - start_col
|
|
|
|
|
|
def add_charts_to_workbook(path: Path) -> None:
|
|
wb = load_workbook(path)
|
|
if "Dashboard" not in wb.sheetnames:
|
|
print(f" skip {path.name}: no Dashboard tab")
|
|
return
|
|
ws = wb["Dashboard"]
|
|
|
|
# --- Chart 1: YoY Revenue / Material / Personnel / EBIT ---
|
|
# Source: rows 3..9 (3=year headers, rows 4=Umsatz, 5=Material, 6=Personal, 9=EBIT)
|
|
# categories: B3:F3 (years), data: rows 4,5,6,9
|
|
cat_ref = Reference(ws, min_col=2, max_col=6, min_row=3, max_row=3)
|
|
chart1 = BarChart()
|
|
chart1.type = "col"
|
|
chart1.style = 11
|
|
chart1.title = "Umsatz / Material / Personal / EBIT — YoY"
|
|
chart1.y_axis.title = "EUR"
|
|
chart1.x_axis.title = "Jahr"
|
|
for r in (4, 5, 6, 9):
|
|
data_ref = Reference(ws, min_col=1, max_col=6, min_row=r, max_row=r)
|
|
chart1.add_data(data_ref, titles_from_data=True, from_rows=True)
|
|
chart1.set_categories(cat_ref)
|
|
chart1.height = 9
|
|
chart1.width = 18
|
|
ws.add_chart(chart1, "H3")
|
|
|
|
# --- Chart 2: Jahresueberschuss YoY (row 11) ---
|
|
chart2 = BarChart()
|
|
chart2.type = "col"
|
|
chart2.style = 13
|
|
chart2.title = "Jahresüberschuss — YoY"
|
|
chart2.y_axis.title = "EUR"
|
|
chart2.x_axis.title = "Jahr"
|
|
data_ref = Reference(ws, min_col=1, max_col=6, min_row=11, max_row=11)
|
|
chart2.add_data(data_ref, titles_from_data=True, from_rows=True)
|
|
chart2.set_categories(cat_ref)
|
|
chart2.height = 9
|
|
chart2.width = 18
|
|
chart2.dataLabels = DataLabelList(showVal=True)
|
|
ws.add_chart(chart2, "H22")
|
|
|
|
# --- Chart 3: Liquidity monthly (row 16, months from col B) ---
|
|
# Determine the last month column by scanning row 15 (month labels)
|
|
n_months = column_count_until_empty(ws, 2, 15)
|
|
last_col = 1 + n_months # col 2..(1+n_months)
|
|
|
|
chart3 = LineChart()
|
|
chart3.title = "Liquidität (monatlich)"
|
|
chart3.y_axis.title = "EUR"
|
|
chart3.x_axis.title = "Monat"
|
|
chart3.style = 12
|
|
cat_months = Reference(ws, min_col=2, max_col=last_col, min_row=15, max_row=15)
|
|
data_liq = Reference(ws, min_col=1, max_col=last_col, min_row=16, max_row=16)
|
|
chart3.add_data(data_liq, titles_from_data=True, from_rows=True)
|
|
chart3.set_categories(cat_months)
|
|
chart3.height = 9
|
|
chart3.width = 24
|
|
ws.add_chart(chart3, "H41")
|
|
|
|
# --- Chart 4: Headcount (row 20) ---
|
|
chart4 = LineChart()
|
|
chart4.title = "Headcount (monatlich)"
|
|
chart4.y_axis.title = "Personen"
|
|
chart4.x_axis.title = "Monat"
|
|
chart4.style = 10
|
|
data_hc = Reference(ws, min_col=1, max_col=last_col, min_row=20, max_row=20)
|
|
chart4.add_data(data_hc, titles_from_data=True, from_rows=True)
|
|
chart4.set_categories(cat_months)
|
|
chart4.height = 9
|
|
chart4.width = 24
|
|
ws.add_chart(chart4, "H60")
|
|
|
|
# --- Chart 5: Personalkosten total monthly (row 24) ---
|
|
chart5 = LineChart()
|
|
chart5.title = "Personalkosten total (monatlich)"
|
|
chart5.y_axis.title = "EUR"
|
|
chart5.x_axis.title = "Monat"
|
|
chart5.style = 13
|
|
data_pers = Reference(ws, min_col=1, max_col=last_col, min_row=24, max_row=24)
|
|
chart5.add_data(data_pers, titles_from_data=True, from_rows=True)
|
|
chart5.set_categories(cat_months)
|
|
chart5.height = 9
|
|
chart5.width = 24
|
|
ws.add_chart(chart5, "H79")
|
|
|
|
# --- Move Dashboard to be the first sheet ---
|
|
idx = wb.sheetnames.index("Dashboard")
|
|
if idx != 0:
|
|
# openpyxl uses _sheets internal list; reorder by index.
|
|
wb._sheets.insert(0, wb._sheets.pop(idx))
|
|
# Also put the Formelübersicht (docs) tab at the end if present
|
|
for docs_name in ("Formelübersicht", "Formulas"):
|
|
if docs_name in wb.sheetnames:
|
|
fidx = wb.sheetnames.index(docs_name)
|
|
wb._sheets.append(wb._sheets.pop(fidx))
|
|
break
|
|
# Make Dashboard the active sheet on open
|
|
wb.active = 0
|
|
|
|
wb.save(path)
|
|
print(f" charts added to {path.name}")
|
|
|
|
|
|
def main() -> None:
|
|
if not EXPORTS_DIR.exists():
|
|
print(f"no exports dir at {EXPORTS_DIR}")
|
|
sys.exit(1)
|
|
files = sorted(EXPORTS_DIR.glob("Finanzplan-*.xlsx"))
|
|
if not files:
|
|
print("no Finanzplan-*.xlsx files found in exports/")
|
|
sys.exit(1)
|
|
for f in files:
|
|
print(f"Processing {f.name}")
|
|
add_charts_to_workbook(f)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|