Spaces:
Sleeping
Sleeping
Update app.py
Browse files
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 |
-
|
| 791 |
-
|
| 792 |
-
|
| 793 |
-
|
| 794 |
-
|
| 795 |
-
|
| 796 |
-
|
| 797 |
-
|
| 798 |
|
| 799 |
-
|
| 800 |
|
| 801 |
-
|
| 802 |
-
|
| 803 |
-
|
| 804 |
-
|
| 805 |
-
|
| 806 |
|
| 807 |
-
|
| 808 |
-
|
| 809 |
-
|
| 810 |
-
|
| 811 |
-
|
| 812 |
-
|
| 813 |
-
|
| 814 |
-
|
| 815 |
-
|
| 816 |
-
|
| 817 |
-
|
| 818 |
-
|
| 819 |
-
|
| 820 |
-
|
| 821 |
-
|
| 822 |
|
| 823 |
-
|
| 824 |
-
|
| 825 |
|
| 826 |
-
|
| 827 |
-
|
| 828 |
-
|
| 829 |
-
|
| 830 |
-
|
| 831 |
-
|
| 832 |
-
|
| 833 |
-
|
| 834 |
-
|
| 835 |
-
|
| 836 |
-
|
| 837 |
-
|
| 838 |
|
| 839 |
-
|
| 840 |
-
|
| 841 |
-
|
| 842 |
-
|
| 843 |
-
|
| 844 |
-
|
| 845 |
-
|
| 846 |
-
|
| 847 |
-
|
| 848 |
|
| 849 |
-
|
| 850 |
|
| 851 |
-
|
| 852 |
-
|
| 853 |
|
| 854 |
-
|
| 855 |
-
|
| 856 |
-
|
| 857 |
-
|
| 858 |
-
|
| 859 |
-
|
| 860 |
-
|
| 861 |
-
|
| 862 |
-
|
| 863 |
-
|
| 864 |
-
|
| 865 |
-
|
| 866 |
|
| 867 |
-
|
| 868 |
-
|
| 869 |
-
|
| 870 |
-
|
| 871 |
-
|
| 872 |
-
|
| 873 |
-
|
| 874 |
-
|
| 875 |
-
|
| 876 |
-
|
| 877 |
-
|
| 878 |
-
|
| 879 |
-
|
| 880 |
-
|
| 881 |
-
|
| 882 |
-
|
| 883 |
-
|
| 884 |
-
|
| 885 |
-
|
| 886 |
|
| 887 |
-
|
| 888 |
-
|
| 889 |
-
|
| 890 |
-
|
| 891 |
-
|
| 892 |
-
|
| 893 |
-
|
| 894 |
-
|
| 895 |
-
|
| 896 |
-
|
| 897 |
-
|
| 898 |
-
|
| 899 |
-
|
| 900 |
-
|
| 901 |
-
|
| 902 |
|
| 903 |
-
|
| 904 |
-
|
| 905 |
-
|
| 906 |
-
|
|
|
|
| 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="⚠️")
|