From 67170b8130d85d344495da23cb508117e93c74e0 Mon Sep 17 00:00:00 2001 From: knedlik Date: Sun, 1 Mar 2026 13:11:28 +0100 Subject: [PATCH] =?UTF-8?q?=C3=84nderung:=20parquet=5Fstore.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/app_db/app.db | Bin 69632 -> 69632 bytes app/data/parquet_store.py | 0 app/pages/costobjects.py | 53 ++++++++++++++---- app/tools/excel_export.py | 16 ++++++ app/tools/help_text.py | 5 ++ config/settings.py | 11 ++++ ...226_095800_add_col_dashname_dashboards.sql | 8 +++ 7 files changed, 83 insertions(+), 10 deletions(-) create mode 100644 app/data/parquet_store.py create mode 100644 app/tools/excel_export.py create mode 100644 app/tools/help_text.py create mode 100644 config/settings.py create mode 100644 migrations/20260226_095800_add_col_dashname_dashboards.sql diff --git a/app/app_db/app.db b/app/app_db/app.db index 51b0afea7298a9ced2630ff3841a7fff5d02dc52..87d9f115d0ce6ccfd13d7b185cb484bff36f9f46 100644 GIT binary patch delta 1120 zcmb7CO>7%Q6yAyL`X}C*l*+Q)DDt>%)LM$o+Ez?sm4>PjDx^{?DuhI-tnu!|yUltw z%O0Gz7{n{91s@~Eb@nPIeb$8JXjMEZkE$6?ZN zBWOjdSL#iwO|`UIYqBwkW3!C_SBqhD?B^kh)q4v0oqSI|A#amYq=^5)+xTm|ir>Ul zoKf$EHF{Iko{#D18DvSAss4>nI!tNr;q?2ltkNiakRZX@@$4W>q!K>^0mk-ie$Mv{yIv4r8 z$1^h)fASAq;?m%P8kPhAWxfjrNP0#x-R6}U2Du$3=4qGP3~(j-dJnLip5=1Rkv%4K zPEAKQ={r6(nGNuX$CP}T4j8BMsheF6YNiB&LHpIgV3xScg=bq(qIhabt|g3Crn2+blUvuiAamr*V65Kd1*+A` zkHRo;C{{o?yA!8cy_^Z&T`v`Nup=@<_uT5j<<<~Mk11%!v|Mg9_^p2^>APG)Fu1?= z^63}S1eE}xMz*3-?(m)&Slfr&nv6#){P4cOy8Q7MWCMvxP+8q2pUO=K->kh@0LuW{ zgGSqPmUO+g%@?^H$!)KnM(2ZazgY*VQen3&l+VRqbE1Zlu+o#2TCLJJmLTZ=fU&^o jj~Aq4^Qf!o7(t_t(hIp_aIJqh-Ib2N5QW2k`)K7q)%aE> delta 460 zcmZozz|ydQWrDOIF9QREAP~cV{6rmNU0w#gWJX?=I}8HM84Uc={JWVm_&)H?=PBdO z<2c0U%^u6q%zBpFhIIj#CTs3yLxDR?n=_aVn3y{?c^_jd zBctZ#SoY-{a=g4k4Ez`Qr}M|~tMI+xtKf6yW8*!-+rhVn*OOOhv!TKV9#&I6W^d!k ze0uVm&1BCp$?@`WG4Ma--^V|VzmVU9U!LzD-yOb9e9e4Oe0qFbn-v{i@NVYP`^(74 zv6;i5hL5wJnTNqxvXNDAa<5%9qv7NicJiA!?0-6l^UY!4;9bthcZdHp{}lds-Y&-bef7%}m2%EAOWMMWo{>{Z$P Izn`%I0GZuz6#xJL diff --git a/app/data/parquet_store.py b/app/data/parquet_store.py new file mode 100644 index 0000000..e69de29 diff --git a/app/pages/costobjects.py b/app/pages/costobjects.py index 6c8f40a..624e950 100644 --- a/app/pages/costobjects.py +++ b/app/pages/costobjects.py @@ -1,11 +1,13 @@ import streamlit as st import pandas as pd -from data.scriptloader import get_sql +#from data.scriptloader import get_sql from data.db import load_data from auth_runtime import require_login -from ui.sidebar import build_sidebar, hide_sidebar_if_logged_out -from auth import get_fullname_for_user +#from ui.sidebar import build_sidebar, hide_sidebar_if_logged_out +#from auth import get_fullname_for_user import numpy as np +from tools.excel_export import df_to_excel_bytes +from tools.help_text import get_help st.set_page_config(page_title="Co-App Home", page_icon="🏠", layout="wide") @@ -13,7 +15,17 @@ st.set_page_config(page_title="Co-App Home", page_icon="🏠", layout="wide") authenticator = require_login() st.session_state["authenticator"] = authenticator +st.markdown(""" + +""", unsafe_allow_html=True) +description = get_help("costobjects") +with st.expander(label="ℹ️ Hilfe / Hinweise", expanded=False): # ❓ + st.markdown(description) @st.cache_data def cache_data() -> pd.DataFrame: @@ -38,19 +50,32 @@ def sidebar_filters(df) -> dict: This function should contain UI concerns only (widgets, layout), and not data filtering logic, to keep the code maintainable. """ + + st.markdown(""" + + """, unsafe_allow_html=True) + st.sidebar.header("Filter") if st.sidebar.button("Refresh (Global)"): cache_data.clear() st.rerun() - year = st.sidebar.selectbox(label="Jahr", options=(2025, 2026), index=1) filter_text = st.sidebar.text_input(label="Textsuche", placeholder="Suche Objekt, Text, Verantwortlicher") col_s1, col_s2 = st.sidebar.columns(2) with col_s1: - typ = st.sidebar.selectbox(label="Typ", options=sorted(df["typ"].dropna().unique()), index=1) + year = st.selectbox(label="Jahr", options=(2025, 2026), index=1) with col_s2: - obj = st.sidebar.multiselect("obj", sorted(df["obj"].dropna().unique())) + typ = st.selectbox(label="Typ", options=sorted(df["typ"].dropna().unique()), index=1) + obj = st.sidebar.multiselect("obj", sorted(df["obj"].dropna().unique())) zgrp1 = st.sidebar.multiselect("ZGrp1", sorted(df["zgrp1"].dropna().unique())) zgrp2 = st.sidebar.multiselect("ZGrp2", sorted(df["zgrp2"].dropna().unique())) zgrp3 = st.sidebar.multiselect("ZGrp3", sorted(df["zgrp3"].dropna().unique())) @@ -100,9 +125,14 @@ def render_table(df): Keep this function presentation-only: it should not modify data. """ + st.markdown("### Übersicht Kostenobjekte") st.dataframe(df, hide_index=True, width="stretch", height="stretch") +def download_data(df): + df.to_excel(path) + + def page(): """ Page entry point: orchestrates data loading, UI, filtering, and rendering. @@ -129,10 +159,9 @@ def page(): # ----------------------------- # Presentation # ----------------------------- - - df = df.sort_values(by="obj", key=lambda s: pd.to_numeric(s, errors="coerce")) df_clean = df[df.columns[:-1]] # letzte Spalten entfernen (sysdate) df_display = df_clean.loc[mask] + df_display = df_display.sort_values(by="obj", key=lambda s: pd.to_numeric(s, errors="coerce")) render_table(df_display) col1, col2 = st.columns(2) @@ -141,11 +170,15 @@ def page(): with col1: st.text(f"Datenstand: {data_as_of}") - st.text("Datenquelle: Oracle (PENTA)") with col2: st.markdown(f"
Anzahl Zeilen: {row_count}
", unsafe_allow_html=True) - + st.download_button( + "📊 Excel-Datei herunterladen", + data=df_to_excel_bytes(df_display), + file_name="kostenobjekte", + mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + ) diff --git a/app/tools/excel_export.py b/app/tools/excel_export.py new file mode 100644 index 0000000..394e7eb --- /dev/null +++ b/app/tools/excel_export.py @@ -0,0 +1,16 @@ +import pandas as pd +import io + +def df_to_excel_bytes(df): + + """ + Convert a DataFrame to an in-memory XLSX file. + + Returns a BytesIO object suitable for st.download_button(data=...). + """ + + buffer = io.BytesIO() + with pd.ExcelWriter(buffer, engine="openpyxl") as writer: + df.to_excel(writer, index=False, sheet_name="Daten") + buffer.seek(0) + return buffer \ No newline at end of file diff --git a/app/tools/help_text.py b/app/tools/help_text.py new file mode 100644 index 0000000..8f9f51d --- /dev/null +++ b/app/tools/help_text.py @@ -0,0 +1,5 @@ +from app_db.app_db import get_value + +def get_help(dashboard :str): + helptext = get_value(f"select dash_description from dashboards where dash_name = '{dashboard}.py'") + return helptext diff --git a/config/settings.py b/config/settings.py new file mode 100644 index 0000000..6d42114 --- /dev/null +++ b/config/settings.py @@ -0,0 +1,11 @@ +from pathlib import Path + +BASE_DIR = Path(__file__).resolve().parents[1] + +STORAGE_DIR = BASE_DIR / "storage" +DB_DIR = STORAGE_DIR / "app_db" +DB_PATH = DB_DIR / "app.db" +DWH_DIR = STORAGE_DIR / "dwh" + + +print(BASE_DIR, DB_DIR) \ No newline at end of file diff --git a/migrations/20260226_095800_add_col_dashname_dashboards.sql b/migrations/20260226_095800_add_col_dashname_dashboards.sql new file mode 100644 index 0000000..d5b86cb --- /dev/null +++ b/migrations/20260226_095800_add_col_dashname_dashboards.sql @@ -0,0 +1,8 @@ +begin; + +alter table dashboards +add column dash_name text not null default ""; + +INSERT INTO schema_version (version) VALUES ('20260226_095800_add_col_dashname_dashboards'); + +COMMIT; \ No newline at end of file