Änderung: Filterfunktionen costobjects.py implementiert
This commit is contained in:
@@ -6,7 +6,7 @@ select
|
|||||||
KOSTENOBJEKT as obj,
|
KOSTENOBJEKT as obj,
|
||||||
BEZEICHNUNG,
|
BEZEICHNUNG,
|
||||||
VERANTWORTLICHER,
|
VERANTWORTLICHER,
|
||||||
FELD_2_X30 as VORGESETZER,
|
FELD_2_X30 as vorgesetzter,
|
||||||
ZUORDNUNGSGRUPPE_1 as zgrp1,
|
ZUORDNUNGSGRUPPE_1 as zgrp1,
|
||||||
ZUORDNUNGSGRUPPE_2 as zgrp2,
|
ZUORDNUNGSGRUPPE_2 as zgrp2,
|
||||||
ZUORDNUNGSGRUPPE_3 as zgrp3,
|
ZUORDNUNGSGRUPPE_3 as zgrp3,
|
||||||
@@ -14,6 +14,9 @@ select
|
|||||||
ZUORDNUNGSGRUPPE_5 as zgrp5,
|
ZUORDNUNGSGRUPPE_5 as zgrp5,
|
||||||
ZUORDNUNGSGRUPPE_6 as zgrp6,
|
ZUORDNUNGSGRUPPE_6 as zgrp6,
|
||||||
FELD_1_X30 as fertigung,
|
FELD_1_X30 as fertigung,
|
||||||
OBJEKTGRUPPE as objgrp
|
OBJEKTGRUPPE as objgrp,
|
||||||
|
sysdate
|
||||||
from
|
from
|
||||||
pkos
|
pkos
|
||||||
|
where
|
||||||
|
objekttyp in ('01', '02')
|
||||||
|
|||||||
@@ -5,41 +5,149 @@ from data.db import load_data
|
|||||||
from auth_runtime import require_login
|
from auth_runtime import require_login
|
||||||
from ui.sidebar import build_sidebar, hide_sidebar_if_logged_out
|
from ui.sidebar import build_sidebar, hide_sidebar_if_logged_out
|
||||||
from auth import get_fullname_for_user
|
from auth import get_fullname_for_user
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
# hide_sidebar_if_logged_out()
|
|
||||||
|
|
||||||
st.set_page_config(page_title="Co-App Home", page_icon="🏠", layout="wide")
|
st.set_page_config(page_title="Co-App Home", page_icon="🏠", layout="wide")
|
||||||
|
|
||||||
authenticator = require_login()
|
authenticator = require_login()
|
||||||
st.session_state["authenticator"] = authenticator
|
st.session_state["authenticator"] = authenticator
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@st.cache_data
|
@st.cache_data
|
||||||
def cache_data():
|
def cache_data() -> pd.DataFrame:
|
||||||
return load_data("ora_kostenobjekte","oracle")
|
"""
|
||||||
|
Load and cache the base dataset for this page.
|
||||||
|
|
||||||
def render_report():
|
Streamlit reruns the script on every interaction; caching avoids
|
||||||
df = cache_data()
|
repeated I/O and makes filtering feel instant.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
df = load_data("ora_kostenobjekte", "oracle")
|
||||||
|
return df
|
||||||
|
except:
|
||||||
|
st.warning("Fehler beim Laden der Daten", icon="⚠️")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def sidebar_filters(df) -> dict:
|
||||||
|
"""
|
||||||
|
Render the sidebar UI and return the current filter selections.
|
||||||
|
|
||||||
|
This function should contain UI concerns only (widgets, layout),
|
||||||
|
and not data filtering logic, to keep the code maintainable.
|
||||||
|
"""
|
||||||
|
st.sidebar.header("Filter")
|
||||||
|
|
||||||
zgrp1_options = st.multiselect(
|
if st.sidebar.button("Refresh (Global)"):
|
||||||
"Zuordnungsgruppe1",
|
cache_data.clear()
|
||||||
["SPI", "KULA", "AT"]
|
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)
|
||||||
|
with col_s2:
|
||||||
|
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()))
|
||||||
|
|
||||||
|
return {"year": year, "filter_text": filter_text, "typ": typ, "obj": obj, "zgrp1": zgrp1, "zgrp2": zgrp2, "zgrp3": zgrp3}
|
||||||
|
|
||||||
year = 2026
|
|
||||||
|
|
||||||
df_actual_year = df[df["jahr"] == year]
|
|
||||||
df_actual_year = df_actual_year.sort_values(
|
|
||||||
by="obj",
|
|
||||||
key=lambda s: pd.to_numeric(s, errors="coerce")
|
|
||||||
)
|
|
||||||
|
|
||||||
if zgrp1_options:
|
|
||||||
df_view = df_actual_year[df_actual_year["zgrp1"].isin(zgrp1_options)]
|
|
||||||
else:
|
|
||||||
df_view = df_actual_year
|
|
||||||
|
|
||||||
st.dataframe(df_view, height=600, hide_index=True, width="stretch")
|
def build_mask(df: pd.DataFrame, sidebar_filter: dict) -> np.ndarray:
|
||||||
|
"""
|
||||||
|
Build a boolean mask based on filter selections.
|
||||||
|
|
||||||
|
The mask approach keeps the logic readable and makes it easy
|
||||||
|
to add more conditions later.
|
||||||
|
"""
|
||||||
|
|
||||||
|
mask = np.ones(len(df), dtype=bool)
|
||||||
|
|
||||||
|
filter_text = (sidebar_filter.get("filter_text") or "").strip()
|
||||||
|
if filter_text:
|
||||||
|
m1 = df["bezeichnung"].astype("string").str.contains(filter_text, case=False, na=False, regex=False)
|
||||||
|
m2 = df["verantwortlicher"].astype("string").str.contains(filter_text, case=False, na=False, regex=False)
|
||||||
|
m3 = df["vorgesetzter"].astype("string").str.contains(filter_text, case=False, na=False, regex=False)
|
||||||
|
mask &= (m1 | m2 | m3)
|
||||||
|
|
||||||
|
if sidebar_filter["year"]:
|
||||||
|
mask &= df["jahr"].eq(sidebar_filter["year"])
|
||||||
|
if sidebar_filter["typ"]:
|
||||||
|
mask &= df["typ"].eq(sidebar_filter["typ"])
|
||||||
|
if sidebar_filter["obj"]:
|
||||||
|
mask &= df["obj"].isin(sidebar_filter["obj"])
|
||||||
|
if sidebar_filter["zgrp1"]:
|
||||||
|
mask &= df["zgrp1"].isin(sidebar_filter["zgrp1"])
|
||||||
|
if sidebar_filter["zgrp2"]:
|
||||||
|
mask &= df["zgrp2"].isin(sidebar_filter["zgrp2"])
|
||||||
|
if sidebar_filter["zgrp3"]:
|
||||||
|
mask &= df["zgrp3"].isin(sidebar_filter["zgrp3"])
|
||||||
|
return mask
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def render_table(df):
|
||||||
|
"""
|
||||||
|
Render the result table.
|
||||||
|
|
||||||
|
Keep this function presentation-only: it should not modify data.
|
||||||
|
"""
|
||||||
|
st.dataframe(df, hide_index=True, width="stretch", height="stretch")
|
||||||
|
|
||||||
|
|
||||||
|
def page():
|
||||||
|
"""
|
||||||
|
Page entry point: orchestrates data loading, UI, filtering, and rendering.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# Data loading (cached)
|
||||||
|
# -----------------------------
|
||||||
|
df = cache_data()
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# UI (Sidebar)
|
||||||
|
# -----------------------------
|
||||||
|
sidebar_filter = sidebar_filters(df)
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# Business logic (filtering)
|
||||||
|
# -----------------------------
|
||||||
|
mask = build_mask(df, sidebar_filter)
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# 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]
|
||||||
|
render_table(df_display)
|
||||||
|
|
||||||
|
col1, col2 = st.columns(2)
|
||||||
|
data_as_of = df["sysdate"].max()
|
||||||
|
row_count = len(df_display)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
st.text(f"Datenstand: {data_as_of}")
|
||||||
|
st.text("Datenquelle: Oracle (PENTA)")
|
||||||
|
with col2:
|
||||||
|
st.markdown(f"<div style='text-align: right;'>Anzahl Zeilen: {row_count}</div>", unsafe_allow_html=True)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
df = render_report()
|
df = page()
|
||||||
Reference in New Issue
Block a user