Tobi-ewl commited on
Commit
207cb6b
·
verified ·
1 Parent(s): 01b0b43

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +110 -293
app.py CHANGED
@@ -528,7 +528,7 @@ elif modus == "Upload csv-Datei":
528
 
529
  st.session_state["annuitaeten_gesamt_batch"] = annuitaeten_gesamt
530
  st.session_state["annuitaeten_objekt_map_batch"] = annuitaeten_objekt_map
531
-
532
  do_calc = st.button("Batch-Berechnung starten")
533
  if do_calc:
534
  try:
@@ -540,9 +540,6 @@ elif modus == "Upload csv-Datei":
540
  df_out[name] = ""
541
  df_out[hname] = ""
542
 
543
- annuitaeten_gesamt_batch = []
544
- annuitaeten_objekt_map = {}
545
-
546
  for idx, row in df_input.iterrows():
547
  try:
548
  nutzflaeche = row["Wohnflaeche"]
@@ -564,16 +561,18 @@ elif modus == "Upload csv-Datei":
564
  "Fixkosten_O+M", "Preisänderungsfaktor_O+M", "Emissionsänderungsfaktor", "Förderung"]:
565
  df[col] = df[col].astype(str).str.replace(",", ".").str.replace("|", ".")
566
  df[col] = pd.to_numeric(df[col], errors='coerce')
 
567
  batch_user_vals = st.session_state.get("batch_user_values")
568
  if batch_user_vals is not None:
 
569
  preview_heizsys_names = {preview_df.loc[x, "Name"]: x for x in preview_df.index}
570
  for pidx in batch_user_vals:
571
  sysname = preview_df.loc[pidx, "Name"]
 
572
  df_idx = df[df["Name"] == sysname].index
573
  if not df_idx.empty:
574
  for key in batch_user_vals[pidx]:
575
  df.at[df_idx[0], key] = batch_user_vals[pidx][key]
576
-
577
  energiebedarf = energiebedarf_spezifisch
578
  df["Energiebedarf"] = df.apply(lambda rowh: calculate_energiebedarf(
579
  energiebedarf, nutzflaeche, rowh["Effizienz"]), axis=1)
@@ -592,315 +591,133 @@ elif modus == "Upload csv-Datei":
592
  df["Annuität_NS"] = 0
593
  df["Annuität"] = df["Annuität_NK"] + df["Annuität_NV"] + df["Annuität_NB"] + df["Annuität_NS"]
594
  df = df.sort_values("Annuität")
595
-
596
  df_anni = df[[
597
  "Name", "Annuität_NK", "Annuität_NV", "Annuität_NB", "Annuität"
598
  ]].copy()
599
  df_anni["Objekt-ID"] = row.get("Objekt-ID", idx)
600
  annuitaeten_gesamt.append(df_anni)
601
  annuitaeten_objekt_map[str(row.get("Objekt-ID", idx))] = df_anni
602
-
603
  for j in range(min(len(df), max_count)):
604
  df_out.at[idx, annuität_colnames[j]] = int(df.iloc[j]["Annuität"])
605
  df_out.at[idx, hsystem_colnames[j]] = df.iloc[j]["Name"]
606
  except Exception as e:
607
  st.warning(f"Objekt-ID {row.get('Objekt-ID', idx)}: {e}")
608
-
609
- # Im Session-State speichern!
610
- st.session_state["annuitaeten_gesamt_batch"] = annuitaeten_gesamt
611
- st.session_state["annuitaeten_objekt_map_batch"] = annuitaeten_objekt_map
612
-
613
- # (Optional: Platz-1-Übersicht, Mapping, weitere Batch-Funktionen ...)
614
- st.success("Berechnung abgeschlossen!")
615
- except Exception as e:
616
- st.error(f"Fehler beim Einlesen/Berechnen: {e}")
617
-
618
- # --- Batch-Mittelwert-Grafik/Nachbereitung ---
619
-
620
- if st.session_state["annuitaeten_gesamt_batch"]:
621
- df_alle = pd.concat(st.session_state["annuitaeten_gesamt_batch"], ignore_index=True)
622
- df_mittel = df_alle.groupby("Name")[["Annuität_NK", "Annuität_NV", "Annuität_NB", "Annuität"]].mean(numeric_only=True).reset_index()
623
- df_stacked = df_mittel.melt(
624
- id_vars="Name",
625
- value_vars=["Annuität_NK", "Annuität_NV", "Annuität_NB"],
626
- var_name="Kostenart",
627
- value_name="Wert"
628
- )
629
- df_stacked["Kostenart"] = df_stacked["Kostenart"].replace({
630
- "Annuität_NK": "Kapitalgebundene Kosten",
631
- "Annuität_NV": "Bedarfsgebundene Kosten",
632
- "Annuität_NB": "Betriebsgebundene Kosten"
633
- })
634
- kostenart_order = ["Kapitalgebundene Kosten", "Bedarfsgebundene Kosten", "Betriebsgebundene Kosten"]
635
- df_stacked["Kostenart"] = pd.Categorical(df_stacked["Kostenart"], categories=kostenart_order, ordered=True)
636
- color_scale = alt.Scale(domain=kostenart_order, range=["#00386c", "#004c93", "#0069c8"])
637
- sortierte_names = df_mittel.sort_values("Annuität")["Name"]
638
-
639
- st.markdown("### Mittlere annualisierte Kosten pro Heizsystem (Batch-Durchschnitt)")
640
- st.altair_chart(
641
- alt.Chart(df_stacked)
642
- .mark_bar()
643
- .encode(
644
- x=alt.X("Wert:Q", title="mittlere annualisierte Kosten (€)", stack="zero"),
645
- y=alt.Y("Name:N", title="Heizsystem", sort=list(sortierte_names)),
646
- color=alt.Color("Kostenart:N", scale=color_scale, title="Kostenart"),
647
- tooltip=["Name", "Kostenart", "Wert"]
648
- ).properties(height=350, width=650),
649
- use_container_width=True
650
- )
651
-
652
- # Download der Batch-CSV
653
- csv_buffer = io.StringIO()
654
- df_out.to_csv(csv_buffer, sep=";", index=False)
655
- csv_bytes = csv_buffer.getvalue().encode("utf-8")
656
- st.download_button(
657
- "Download Ergebnis-CSV",
658
- csv_bytes,
659
- file_name="heizsysteme_batch_ergebnis.csv",
660
- mime="text/csv"
661
- )
662
-
663
- # Einzelobjekt-Detailauswertung:
664
- if st.session_state["annuitaeten_objekt_map_batch"] and len(st.session_state["annuitaeten_objekt_map_batch"]) > 0:
665
- st.markdown("### Einzelanalyse eines Objekts (wie Einzel-Darstellung)")
666
- objekt_ids = list(st.session_state["annuitaeten_objekt_map_batch"].keys())
667
- col1, col2 = st.columns([5, 2])
668
- with col1:
669
- auswahl = st.selectbox(
670
- "Objekt für Detaildarstellung auswählen", objekt_ids, key="batch_detail_sbox",
671
- index=objekt_ids.index(st.session_state["selected_objekt_id_batch_detail"])
672
- if st.session_state["selected_objekt_id_batch_detail"] in objekt_ids else 0
673
- )
674
- with col2:
675
- if st.button("Kostenvergleich für ausgewähltes Objekt anzeigen", key="batch_detail_show_btn"):
676
- st.session_state["selected_objekt_id_batch_detail"] = auswahl
677
-
678
- show_id = st.session_state["selected_objekt_id_batch_detail"]
679
- if show_id and show_id in st.session_state["annuitaeten_objekt_map_batch"]:
680
- df_einzel = st.session_state["annuitaeten_objekt_map_batch"][show_id]
681
- df_stacked = df_einzel[["Name", "Annuität_NK", "Annuität_NV", "Annuität_NB"]].melt(
682
- id_vars="Name",
683
- value_vars=["Annuität_NK", "Annuität_NV", "Annuität_NB"],
684
- var_name="Kostenart",
685
- value_name="Wert"
686
- )
687
- df_stacked["Kostenart"] = df_stacked["Kostenart"].replace({
688
- "Annuität_NK": "Kapitalgebundene Kosten",
689
- "Annuität_NV": "Bedarfsgebundene Kosten",
690
- "Annuität_NB": "Betriebsgebundene Kosten"
691
- })
692
- kostenart_order = ["Kapitalgebundene Kosten", "Bedarfsgebundene Kosten", "Betriebsgebundene Kosten"]
693
- df_stacked["Kostenart"] = pd.Categorical(df_stacked["Kostenart"], categories=kostenart_order, ordered=True)
694
- color_scale = alt.Scale(domain=kostenart_order, range=["#00386c", "#004c93", "#0069c8"])
695
- sortierte_names = df_einzel.sort_values("Annuität")["Name"]
696
-
697
- st.markdown(f"#### Annualisierte Kosten für Objekt {show_id}")
698
- st.altair_chart(
699
- alt.Chart(df_stacked)
700
- .mark_bar()
701
- .encode(
702
- x=alt.X("Wert:Q", title="Annualisierte Kosten (€)", stack="zero"),
703
- y=alt.Y("Name:N", title="Heizsystem", sort=list(sortierte_names)),
704
- color=alt.Color("Kostenart:N", scale=color_scale, title="Kostenart"),
705
- tooltip=["Name", "Kostenart", "Wert"]
706
- ).properties(height=350, width=650),
707
- use_container_width=True
708
- )
709
-
710
- st.markdown("---")
711
- st.caption("Berechnung nach VDI 2067, Heizsystemauswahl gemäß DIN EN 15378.")
712
-
713
-
714
-
715
- # do_calc = st.button("Batch-Berechnung starten")
716
- # if do_calc:
717
- # try:
718
- # df_out = df_input.copy()
719
- # max_count = 8
720
- # annuität_colnames = [f"Heizsystem_{i+1}" for i in range(max_count)]
721
- # hsystem_colnames = [f"Heizsystem_Name_{i+1}" for i in range(max_count)]
722
- # for name, hname in zip(annuität_colnames, hsystem_colnames):
723
- # df_out[name] = ""
724
- # df_out[hname] = ""
725
-
726
- # for idx, row in df_input.iterrows():
727
- # try:
728
- # nutzflaeche = row["Wohnflaeche"]
729
- # baujahr = int(row["Baujahr"])
730
- # gesamtbedarf = row.get("Gesamtwaermebedarf", "")
731
- # spezbedarf = row.get("spezifischer Waermebedarf", "")
732
- # if pd.notnull(gesamtbedarf) and gesamtbedarf != '' and float(gesamtbedarf) > 0 and nutzflaeche > 0:
733
- # energiebedarf_spezifisch = float(gesamtbedarf) / float(nutzflaeche)
734
- # else:
735
- # energiebedarf_spezifisch = float(spezbedarf)
736
- # df = finde_passende_heizsysteme(energiebedarf_spezifisch, baujahr, nutzflaeche, szen_kurz)
737
- # df.columns = [
738
- # "Name", "Leistung", "Investitionskosten", "Betriebsdauer", "Effizienz", "Emissionen",
739
- # "Preisänderungsfaktor_Inv", "Betriebskosten", "Preisänderungsfaktor_Bedarf",
740
- # "Fixkosten_O+M", "Preisänderungsfaktor_O+M", "Emissionsänderungsfaktor", "Förderung"
741
- # ]
742
- # for col in ["Investitionskosten", "Betriebsdauer", "Effizienz", "Emissionen",
743
- # "Preisänderungsfaktor_Inv", "Betriebskosten", "Preisänderungsfaktor_Bedarf",
744
- # "Fixkosten_O+M", "Preisänderungsfaktor_O+M", "Emissionsänderungsfaktor", "Förderung"]:
745
- # df[col] = df[col].astype(str).str.replace(",", ".").str.replace("|", ".")
746
- # df[col] = pd.to_numeric(df[col], errors='coerce')
747
- # # Wenn Batch-global-Werte gesetzt sind, anwenden:
748
- # batch_user_vals = st.session_state.get("batch_user_values")
749
- # if batch_user_vals is not None:
750
- # # Mapping via Name zur Sicherheit
751
- # preview_heizsys_names = {preview_df.loc[x, "Name"]: x for x in preview_df.index}
752
- # for pidx in batch_user_vals:
753
- # sysname = preview_df.loc[pidx, "Name"]
754
- # # Suche nach dem Namen im aktuellen DF
755
- # df_idx = df[df["Name"] == sysname].index
756
- # if not df_idx.empty:
757
- # for key in batch_user_vals[pidx]:
758
- # df.at[df_idx[0], key] = batch_user_vals[pidx][key]
759
- # energiebedarf = energiebedarf_spezifisch
760
- # df["Energiebedarf"] = df.apply(lambda rowh: calculate_energiebedarf(
761
- # energiebedarf, nutzflaeche, rowh["Effizienz"]), axis=1)
762
- # df["Annuität_NK"] = df.apply(
763
- # lambda rowh: calculate_annuity_nk(rowh["Förderung"], rowh["Investitionskosten"], zinssatz, rowh["Betriebsdauer"],
764
- # beobachtungszeitraum, rowh["Preisänderungsfaktor_Inv"]), axis=1)
765
- # df["Annuität_NV"] = df.apply(
766
- # lambda rowh: calculate_annuity_nv(
767
- # float(rowh["Betriebskosten"]) - float(rowh["Emissionen"]) * emission_cost, rowh["Energiebedarf"], zinssatz,
768
- # rowh["Preisänderungsfaktor_Bedarf"], beobachtungszeitraum,
769
- # emission_cost, rowh["Emissionen"], preisaenderungsfaktor_emission, rowh["Emissionsänderungsfaktor"]
770
- # ), axis=1)
771
- # df["Annuität_NB"] = df.apply(
772
- # lambda rowh: calculate_annuity_nb(rowh["Leistung"], rowh["Fixkosten_O+M"], rowh["Preisänderungsfaktor_O+M"],
773
- # zinssatz, beobachtungszeitraum), axis=1)
774
- # df["Annuität_NS"] = 0
775
- # df["Annuität"] = df["Annuität_NK"] + df["Annuität_NV"] + df["Annuität_NB"] + df["Annuität_NS"]
776
- # df = df.sort_values("Annuität")
777
- # df_anni = df[[
778
- # "Name", "Annuität_NK", "Annuität_NV", "Annuität_NB", "Annuität"
779
- # ]].copy()
780
- # df_anni["Objekt-ID"] = row.get("Objekt-ID", idx)
781
- # annuitaeten_gesamt.append(df_anni)
782
- # annuitaeten_objekt_map[str(row.get("Objekt-ID", idx))] = df_anni
783
-
784
- # for j in range(min(len(df), max_count)):
785
- # df_out.at[idx, annuität_colnames[j]] = int(df.iloc[j]["Annuität"])
786
- # df_out.at[idx, hsystem_colnames[j]] = df.iloc[j]["Name"]
787
- # except Exception as e:
788
- # st.warning(f"Objekt-ID {row.get('Objekt-ID', idx)}: {e}")
789
 
790
- # heizsystem_namensmap = {
791
- # "Ölheizung": "Oelheizung",
792
- # "Luft-Wasser Wärmepumpe": "Luft-Wasser Waermepumpe",
793
- # "Sole-Wasser Wärmepumpe": "Sole-Wasser Waermepumpe",
794
- # "Wasser-Wasser Wärmepumpe": "Wasser-Wasser Waermepumpe",
795
- # }
796
- # for col in hsystem_colnames:
797
- # df_out[col] = df_out[col].replace(heizsystem_namensmap)
798
 
799
- # st.success("Berechnung abgeschlossen!")
800
 
801
- # if annuitaeten_gesamt:
802
- # # Verbinde alle Einzel-DFs zu einem großen Table
803
- # df_alle = pd.concat(annuitaeten_gesamt, ignore_index=True)
804
- # # Mittelwerte pro System
805
- # df_mittel = df_alle.groupby("Name")[["Annuität_NK", "Annuität_NV", "Annuität_NB", "Annuität"]].mean(numeric_only=True).reset_index()
806
 
807
- # # Für gestapeltes Balkendiagramm (wie im Einzel-Modus)
808
- # df_stacked = df_mittel.melt(
809
- # id_vars="Name",
810
- # value_vars=["Annuität_NK", "Annuität_NV", "Annuität_NB"],
811
- # var_name="Kostenart",
812
- # value_name="Wert"
813
- # )
814
- # df_stacked["Kostenart"] = df_stacked["Kostenart"].replace({
815
- # "Annuität_NK": "Kapitalgebundene Kosten",
816
- # "Annuität_NV": "Bedarfsgebundene Kosten",
817
- # "Annuität_NB": "Betriebsgebundene Kosten"
818
- # })
819
- # kostenart_order = ["Kapitalgebundene Kosten", "Bedarfsgebundene Kosten", "Betriebsgebundene Kosten"]
820
- # df_stacked["Kostenart"] = pd.Categorical(df_stacked["Kostenart"], categories=kostenart_order, ordered=True)
821
- # color_scale = alt.Scale(domain=kostenart_order, range=["#00386c", "#004c93", "#0069c8"])
822
 
823
- # # Heizsysteme nach mittlerer Gesamtkosten sortieren
824
- # sortierte_names = df_mittel.sort_values("Annuität")["Name"]
825
 
826
- # st.markdown("### Mittlere annualisierte Kosten pro Heizsystem (Batch-Durchschnitt)")
827
- # st.altair_chart(
828
- # alt.Chart(df_stacked)
829
- # .mark_bar()
830
- # .encode(
831
- # x=alt.X("Wert:Q", title="mittlere annualisierte Kosten (€)", stack="zero"),
832
- # y=alt.Y("Name:N", title="Heizsystem", sort=list(sortierte_names)),
833
- # color=alt.Color("Kostenart:N", scale=color_scale, title="Kostenart"),
834
- # tooltip=["Name", "Kostenart", "Wert"]
835
- # ).properties(height=350, width=650),
836
- # use_container_width=True
837
- # )
838
 
839
- # csv_buffer = io.StringIO()
840
- # df_out.to_csv(csv_buffer, sep=";", index=False)
841
- # csv_bytes = csv_buffer.getvalue().encode("utf-8")
842
- # st.download_button(
843
- # "Download Ergebnis-CSV",
844
- # csv_bytes,
845
- # file_name="heizsysteme_batch_ergebnis.csv",
846
- # mime="text/csv"
847
- # )
848
 
849
- # # --- Einzelobjekt-Grafik nach dem Batch-Durchschnitt (immer NACH Mittelwert und Download-Button!) ---
850
 
851
- # if annuitaeten_objekt_map and len(annuitaeten_objekt_map) > 0:
852
- # st.markdown("### Einzelanalyse eines Objekts (wie Einzel-Darstellung)")
853
 
854
- # objekt_ids = list(annuitaeten_objekt_map.keys())
855
- # # EIGENER Key, EIGENE Session-Variable nur für diese Auswahl!
856
- # col1, col2 = st.columns([5, 2])
857
- # with col1:
858
- # auswahl = st.selectbox(
859
- # "Objekt für Detaildarstellung auswählen", objekt_ids, key="batch_detail_sbox",
860
- # index=objekt_ids.index(st.session_state["selected_objekt_id_batch_detail"])
861
- # if st.session_state["selected_objekt_id_batch_detail"] in objekt_ids else 0
862
- # )
863
- # with col2:
864
- # if st.button("Kostenvergleich für ausgewähltes Objekt anzeigen", key="batch_detail_show_btn"):
865
- # st.session_state["selected_objekt_id_batch_detail"] = auswahl
866
 
867
- # # Nach dem Buttonklick bleibt das Diagramm sichtbar, bis ein neuer Klick gemacht wird
868
- # show_id = st.session_state["selected_objekt_id_batch_detail"]
869
- # if show_id and show_id in annuitaeten_objekt_map:
870
- # df_einzel = annuitaeten_objekt_map[show_id]
871
- # df_stacked = df_einzel[["Name", "Annuität_NK", "Annuität_NV", "Annuität_NB"]].melt(
872
- # id_vars="Name",
873
- # value_vars=["Annuität_NK", "Annuität_NV", "Annuität_NB"],
874
- # var_name="Kostenart",
875
- # value_name="Wert"
876
- # )
877
- # df_stacked["Kostenart"] = df_stacked["Kostenart"].replace({
878
- # "Annuität_NK": "Kapitalgebundene Kosten",
879
- # "Annuität_NV": "Bedarfsgebundene Kosten",
880
- # "Annuität_NB": "Betriebsgebundene Kosten"
881
- # })
882
- # kostenart_order = ["Kapitalgebundene Kosten", "Bedarfsgebundene Kosten", "Betriebsgebundene Kosten"]
883
- # df_stacked["Kostenart"] = pd.Categorical(df_stacked["Kostenart"], categories=kostenart_order, ordered=True)
884
- # color_scale = alt.Scale(domain=kostenart_order, range=["#00386c", "#004c93", "#0069c8"])
885
- # sortierte_names = df_einzel.sort_values("Annuität")["Name"]
886
 
887
- # st.markdown(f"#### Annualisierte Kosten für Objekt {show_id}")
888
- # st.altair_chart(
889
- # alt.Chart(df_stacked)
890
- # .mark_bar()
891
- # .encode(
892
- # x=alt.X("Wert:Q", title="Annualisierte Kosten (€)", stack="zero"),
893
- # y=alt.Y("Name:N", title="Heizsystem", sort=list(sortierte_names)),
894
- # color=alt.Color("Kostenart:N", scale=color_scale, title="Kostenart"),
895
- # tooltip=["Name", "Kostenart", "Wert"]
896
- # ).properties(height=350, width=650),
897
- # use_container_width=True
898
- # )
899
-
900
- # except Exception as e:
901
- # st.error(f"Fehler beim Einlesen/Berechnen: {e}")
902
 
903
- # st.markdown("---")
904
- # st.caption("Berechnung nach VDI 2067, Heizsystemauswahl gemäß DIN EN 15378.")
905
- # else:
906
- # st.warning("Bitte laden Sie eine CSV-Datei hoch", icon="⚠️")
 
528
 
529
  st.session_state["annuitaeten_gesamt_batch"] = annuitaeten_gesamt
530
  st.session_state["annuitaeten_objekt_map_batch"] = annuitaeten_objekt_map
531
+
532
  do_calc = st.button("Batch-Berechnung starten")
533
  if do_calc:
534
  try:
 
540
  df_out[name] = ""
541
  df_out[hname] = ""
542
 
 
 
 
543
  for idx, row in df_input.iterrows():
544
  try:
545
  nutzflaeche = row["Wohnflaeche"]
 
561
  "Fixkosten_O+M", "Preisänderungsfaktor_O+M", "Emissionsänderungsfaktor", "Förderung"]:
562
  df[col] = df[col].astype(str).str.replace(",", ".").str.replace("|", ".")
563
  df[col] = pd.to_numeric(df[col], errors='coerce')
564
+ # Wenn Batch-global-Werte gesetzt sind, anwenden:
565
  batch_user_vals = st.session_state.get("batch_user_values")
566
  if batch_user_vals is not None:
567
+ # Mapping via Name zur Sicherheit
568
  preview_heizsys_names = {preview_df.loc[x, "Name"]: x for x in preview_df.index}
569
  for pidx in batch_user_vals:
570
  sysname = preview_df.loc[pidx, "Name"]
571
+ # Suche nach dem Namen im aktuellen DF
572
  df_idx = df[df["Name"] == sysname].index
573
  if not df_idx.empty:
574
  for key in batch_user_vals[pidx]:
575
  df.at[df_idx[0], key] = batch_user_vals[pidx][key]
 
576
  energiebedarf = energiebedarf_spezifisch
577
  df["Energiebedarf"] = df.apply(lambda rowh: calculate_energiebedarf(
578
  energiebedarf, nutzflaeche, rowh["Effizienz"]), axis=1)
 
591
  df["Annuität_NS"] = 0
592
  df["Annuität"] = df["Annuität_NK"] + df["Annuität_NV"] + df["Annuität_NB"] + df["Annuität_NS"]
593
  df = df.sort_values("Annuität")
 
594
  df_anni = df[[
595
  "Name", "Annuität_NK", "Annuität_NV", "Annuität_NB", "Annuität"
596
  ]].copy()
597
  df_anni["Objekt-ID"] = row.get("Objekt-ID", idx)
598
  annuitaeten_gesamt.append(df_anni)
599
  annuitaeten_objekt_map[str(row.get("Objekt-ID", idx))] = df_anni
600
+
601
  for j in range(min(len(df), max_count)):
602
  df_out.at[idx, annuität_colnames[j]] = int(df.iloc[j]["Annuität"])
603
  df_out.at[idx, hsystem_colnames[j]] = df.iloc[j]["Name"]
604
  except Exception as e:
605
  st.warning(f"Objekt-ID {row.get('Objekt-ID', idx)}: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
606
 
607
+ heizsystem_namensmap = {
608
+ "Ölheizung": "Oelheizung",
609
+ "Luft-Wasser Wärmepumpe": "Luft-Wasser Waermepumpe",
610
+ "Sole-Wasser Wärmepumpe": "Sole-Wasser Waermepumpe",
611
+ "Wasser-Wasser Wärmepumpe": "Wasser-Wasser Waermepumpe",
612
+ }
613
+ for col in hsystem_colnames:
614
+ df_out[col] = df_out[col].replace(heizsystem_namensmap)
615
 
616
+ st.success("Berechnung abgeschlossen!")
617
 
618
+ if annuitaeten_gesamt:
619
+ # Verbinde alle Einzel-DFs zu einem großen Table
620
+ df_alle = pd.concat(annuitaeten_gesamt, ignore_index=True)
621
+ # Mittelwerte pro System
622
+ df_mittel = df_alle.groupby("Name")[["Annuität_NK", "Annuität_NV", "Annuität_NB", "Annuität"]].mean(numeric_only=True).reset_index()
623
 
624
+ # Für gestapeltes Balkendiagramm (wie im Einzel-Modus)
625
+ df_stacked = df_mittel.melt(
626
+ id_vars="Name",
627
+ value_vars=["Annuität_NK", "Annuität_NV", "Annuität_NB"],
628
+ var_name="Kostenart",
629
+ value_name="Wert"
630
+ )
631
+ df_stacked["Kostenart"] = df_stacked["Kostenart"].replace({
632
+ "Annuität_NK": "Kapitalgebundene Kosten",
633
+ "Annuität_NV": "Bedarfsgebundene Kosten",
634
+ "Annuität_NB": "Betriebsgebundene Kosten"
635
+ })
636
+ kostenart_order = ["Kapitalgebundene Kosten", "Bedarfsgebundene Kosten", "Betriebsgebundene Kosten"]
637
+ df_stacked["Kostenart"] = pd.Categorical(df_stacked["Kostenart"], categories=kostenart_order, ordered=True)
638
+ color_scale = alt.Scale(domain=kostenart_order, range=["#00386c", "#004c93", "#0069c8"])
639
 
640
+ # Heizsysteme nach mittlerer Gesamtkosten sortieren
641
+ sortierte_names = df_mittel.sort_values("Annuität")["Name"]
642
 
643
+ st.markdown("### Mittlere annualisierte Kosten pro Heizsystem (Batch-Durchschnitt)")
644
+ st.altair_chart(
645
+ alt.Chart(df_stacked)
646
+ .mark_bar()
647
+ .encode(
648
+ x=alt.X("Wert:Q", title="mittlere annualisierte Kosten (€)", stack="zero"),
649
+ y=alt.Y("Name:N", title="Heizsystem", sort=list(sortierte_names)),
650
+ color=alt.Color("Kostenart:N", scale=color_scale, title="Kostenart"),
651
+ tooltip=["Name", "Kostenart", "Wert"]
652
+ ).properties(height=350, width=650),
653
+ use_container_width=True
654
+ )
655
 
656
+ csv_buffer = io.StringIO()
657
+ df_out.to_csv(csv_buffer, sep=";", index=False)
658
+ csv_bytes = csv_buffer.getvalue().encode("utf-8")
659
+ st.download_button(
660
+ "Download Ergebnis-CSV",
661
+ csv_bytes,
662
+ file_name="heizsysteme_batch_ergebnis.csv",
663
+ mime="text/csv"
664
+ )
665
 
666
+ # --- Einzelobjekt-Grafik nach dem Batch-Durchschnitt (immer NACH Mittelwert und Download-Button!) ---
667
 
668
+ if annuitaeten_objekt_map and len(annuitaeten_objekt_map) > 0:
669
+ st.markdown("### Einzelanalyse eines Objekts (wie Einzel-Darstellung)")
670
 
671
+ objekt_ids = list(annuitaeten_objekt_map.keys())
672
+ # EIGENER Key, EIGENE Session-Variable nur für diese Auswahl!
673
+ col1, col2 = st.columns([5, 2])
674
+ with col1:
675
+ auswahl = st.selectbox(
676
+ "Objekt für Detaildarstellung auswählen", objekt_ids, key="batch_detail_sbox",
677
+ index=objekt_ids.index(st.session_state["selected_objekt_id_batch_detail"])
678
+ if st.session_state["selected_objekt_id_batch_detail"] in objekt_ids else 0
679
+ )
680
+ with col2:
681
+ if st.button("Kostenvergleich für ausgewähltes Objekt anzeigen", key="batch_detail_show_btn"):
682
+ st.session_state["selected_objekt_id_batch_detail"] = auswahl
683
 
684
+ # Nach dem Buttonklick bleibt das Diagramm sichtbar, bis ein neuer Klick gemacht wird
685
+ show_id = st.session_state["selected_objekt_id_batch_detail"]
686
+ if show_id and show_id in annuitaeten_objekt_map:
687
+ df_einzel = annuitaeten_objekt_map[show_id]
688
+ df_stacked = df_einzel[["Name", "Annuität_NK", "Annuität_NV", "Annuität_NB"]].melt(
689
+ id_vars="Name",
690
+ value_vars=["Annuität_NK", "Annuität_NV", "Annuität_NB"],
691
+ var_name="Kostenart",
692
+ value_name="Wert"
693
+ )
694
+ df_stacked["Kostenart"] = df_stacked["Kostenart"].replace({
695
+ "Annuität_NK": "Kapitalgebundene Kosten",
696
+ "Annuität_NV": "Bedarfsgebundene Kosten",
697
+ "Annuität_NB": "Betriebsgebundene Kosten"
698
+ })
699
+ kostenart_order = ["Kapitalgebundene Kosten", "Bedarfsgebundene Kosten", "Betriebsgebundene Kosten"]
700
+ df_stacked["Kostenart"] = pd.Categorical(df_stacked["Kostenart"], categories=kostenart_order, ordered=True)
701
+ color_scale = alt.Scale(domain=kostenart_order, range=["#00386c", "#004c93", "#0069c8"])
702
+ sortierte_names = df_einzel.sort_values("Annuität")["Name"]
703
 
704
+ st.markdown(f"#### Annualisierte Kosten für Objekt {show_id}")
705
+ st.altair_chart(
706
+ alt.Chart(df_stacked)
707
+ .mark_bar()
708
+ .encode(
709
+ x=alt.X("Wert:Q", title="Annualisierte Kosten (€)", stack="zero"),
710
+ y=alt.Y("Name:N", title="Heizsystem", sort=list(sortierte_names)),
711
+ color=alt.Color("Kostenart:N", scale=color_scale, title="Kostenart"),
712
+ tooltip=["Name", "Kostenart", "Wert"]
713
+ ).properties(height=350, width=650),
714
+ use_container_width=True
715
+ )
716
+
717
+ except Exception as e:
718
+ st.error(f"Fehler beim Einlesen/Berechnen: {e}")
719
 
720
+ st.markdown("---")
721
+ st.caption("Berechnung nach VDI 2067, Heizsystemauswahl gemäß DIN EN 15378.")
722
+ else:
723
+ st.warning("Bitte laden Sie eine CSV-Datei hoch", icon="⚠️")