File size: 51,682 Bytes
250bbef
 
 
 
 
 
 
 
 
 
2c91609
250bbef
9f3d10b
 
 
 
 
 
 
72e9186
9f3d10b
c4147ff
78ba0dd
 
9f3d10b
 
78ba0dd
9f3d10b
250bbef
 
 
2c91609
250bbef
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2c91609
b9fdb44
 
 
 
 
 
 
 
 
 
 
250bbef
 
 
 
 
 
 
 
 
6c17f63
 
56f602e
 
6c17f63
250bbef
 
 
 
 
 
7ec8986
 
2c91609
83376ef
 
 
250bbef
 
 
2c91609
 
 
 
 
c4147ff
2c91609
 
250bbef
 
c4147ff
f069b3b
 
27f0c0e
65d6145
fa59c03
65d6145
f069b3b
c4147ff
250bbef
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6202611
250bbef
 
 
 
 
2c91609
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b2a9b94
 
 
2c91609
 
 
 
 
 
 
 
 
 
 
 
 
 
250bbef
 
 
 
 
 
 
 
2c91609
 
250bbef
2c91609
 
250bbef
2c91609
250bbef
2c91609
250bbef
2c91609
0520cab
2c91609
0520cab
2c91609
 
0520cab
 
 
 
 
 
 
 
2c91609
0520cab
 
2c91609
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42a18e4
 
 
 
 
 
 
e9f3a04
42a18e4
 
 
 
 
2c91609
 
 
 
 
 
 
 
 
 
fa59c03
 
2c91609
 
 
 
 
 
 
 
 
 
fa59c03
2c91609
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0520cab
2c91609
 
0520cab
2c91609
 
 
 
0520cab
 
2c91609
0520cab
 
 
 
2c91609
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0c5f94c
2c91609
 
 
 
0c5f94c
2c91609
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bab3d44
2c91609
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c5c4f2b
2c91609
254b590
 
946aa2e
 
 
 
 
254b590
946aa2e
 
 
 
 
 
 
 
9f3d10b
817b515
ec85001
817b515
 
9f3d10b
ec85001
9f3d10b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e6ffc42
59bd1b9
9f3d10b
 
 
 
 
 
 
 
 
 
 
 
 
4ca2317
9f3d10b
b41f3d8
 
 
 
 
 
 
 
 
 
 
9f3d10b
 
3ea9170
6c1a23d
9f3d10b
6c1a23d
 
 
 
 
 
9f3d10b
6c1a23d
9f3d10b
 
 
 
 
 
 
2c91609
9f3d10b
 
 
3ea9170
9f3d10b
 
 
 
52f0aed
 
 
 
 
 
 
 
 
 
 
 
 
9f3d10b
 
 
 
 
 
 
fa59c03
 
9f3d10b
 
fa59c03
9f3d10b
 
 
 
 
 
 
 
 
 
 
 
 
 
207cb6b
9f3d10b
 
 
 
00fb30d
b4e3d62
 
 
9f3d10b
 
 
8dfa984
9f3d10b
8dfa984
 
 
 
 
 
9f3d10b
8dfa984
9f3d10b
 
 
 
 
 
 
 
 
 
 
207cb6b
9f3d10b
 
207cb6b
9f3d10b
 
 
207cb6b
9f3d10b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b9fdb44
 
00fb30d
 
b9fdb44
 
 
 
 
b4e3d62
 
 
 
 
b9fdb44
 
 
 
9f3d10b
 
 
207cb6b
 
 
 
 
 
2eb3f2e
82341af
 
 
 
 
 
 
 
7008f14
00fb30d
82341af
 
2eb3f2e
 
 
 
ae4c690
2eb3f2e
 
 
82341af
 
 
a1e65ce
207cb6b
e13f91b
7ec8986
 
207cb6b
 
 
 
 
1aa7e89
83a3932
 
 
207cb6b
1aa7e89
 
eedd68e
 
 
 
 
 
 
 
 
 
 
8b94657
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1aa7e89
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c4ea622
1aa7e89
 
 
 
e6ffc42
 
 
 
 
 
bab3d44
e6ffc42
 
 
 
 
 
54b4872
e6ffc42
 
 
 
 
 
 
 
c4ea622
e6ffc42
1aa7e89
e4d4c6d
910da1b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bab3d44
910da1b
 
 
 
 
 
 
 
 
 
bab3d44
910da1b
 
 
 
 
 
 
 
 
 
9f3d10b
207cb6b
c4147ff
207cb6b
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
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
907
908
909
910
import streamlit as st
import pandas as pd
import altair as alt

from data import default
from find_heatingsystem import finde_passende_heizsysteme
from calculations_annuity import (
    calculate_energiebedarf, calculate_annuity_nk, calculate_annuity_nv,
    calculate_annuity_nb, calculate_annuity_ns
)
import io

# Passwortschutz-Block
if "auth_ok" not in st.session_state:
    st.session_state["auth_ok"] = False

if not st.session_state["auth_ok"]:
    st.title("🔒 Zugang geschützt")
    pw = st.text_input("Bitte Passwort eingeben", type="password")
    if pw == "KliWinBa25!":
        st.session_state["auth_ok"] = True
        st.success("Passwort korrekt! Klicken Sie auf 'Weiter'")
        if st.button("Weiter"):
            pass  # Seite baut sich automatisch neu auf, App wird ab jetzt dargestellt
    elif pw != "":
        st.error("Falsches Passwort.")
    st.stop()

# Funktion zum Laden der Szenario-Excel
def load_szenario_data(filepath):
    df = pd.read_csv(filepath, sep=";", encoding="latin1", skip_blank_lines=True)
    df = df.dropna(subset=["Szenario"])
    df = df.set_index("Szenario")
    return df

PRIMARY = "#004c93"
SECONDARY = "#8b3003"
BG_LIGHT = "#dfe4f2"
HILITE1 = "#c13f1a"
ALT1 = "#00386c"
ALT2 = "#0069c8"
ALT3 = "#0087ff"

st.set_page_config(page_title="KliWinBa – Wirtschaftlichkeitsrechner für Heizsysteme", layout="centered")

st.markdown(f"""
    <style>
        html, body, [class*="css"] {{
            font-family: Optima, 'Optima', 'Segoe UI', 'Arial', 'sans-serif' !important;
        }}
        .kliwinba-header {{
            font-family: Optima, 'Optima', 'Segoe UI', 'Arial', 'sans-serif';
            font-size: 36px;
            font-weight: 700;
            padding: 1.5rem 1rem 1rem 1rem;
            color: white;
            background: {PRIMARY};
            border-radius: 10px;
            letter-spacing: 1px;
            margin-bottom: 1.0rem;
            text-align: center;
        }}
        .kliwinba-header .subline {{
            display: block;
            font-size: 28px;
            font-weight: 400;
            margin-top: 0.4rem;
        }}
        .block-container {{
            background: {BG_LIGHT};
        }}
        h2, .stMarkdown h2 {{
            color: {SECONDARY};
            font-family: Optima, 'Optima', 'Segoe UI', 'Arial', 'sans-serif';
        }}
    </style>
    <div class="kliwinba-header">
        KliWinBa<br>
        <span class="subline">Wirtschaftlichkeitsrechner für Heizsysteme</span>
    </div>
""", unsafe_allow_html=True)

alle_heizsysteme = [
    "Luft-Wasser Wärmepumpe",
    "Wasser-Wasser Wärmepumpe",
    "Sole-Wasser Wärmepumpe",
    "Pelletheizung",
    "Holzhackschnitzelheizung",
    "Wasserstoffheizung",
    "Gasheizung",
    "Ölheizung",
]

if 'eingabedaten' not in st.session_state:
    st.session_state.eingabedaten = None
if "df_heizsysteme" not in st.session_state:
    st.session_state.df_heizsysteme = None
if "user_values" not in st.session_state:
    st.session_state.user_values = {}
if "heizlast_user" not in st.session_state:
    st.session_state.heizlast_user = None

if "selected_objekt_id" not in st.session_state:
    st.session_state["selected_objekt_id"] = None
if "selected_objekt_id_batch_detail" not in st.session_state:
    st.session_state["selected_objekt_id_batch_detail"] = None

submitted = False

if "szenario_bestaetigt" not in st.session_state:
    st.session_state.szenario_bestaetigt = False
if "szenario" not in st.session_state:
    st.session_state.szenario = None
    
annuitaeten_gesamt = []

if "annuitaeten_gesamt_batch" not in st.session_state:
    st.session_state["annuitaeten_gesamt_batch"] = []

szenario_df = load_szenario_data('Data/Szenario-Input.csv')
szenario_liste = list(szenario_df.index)

# Auswahl Manuell/Batch
modus = st.radio(
    "Berechnungsmodus",
    ("Manuelle Eingabe", "Upload csv-Datei"),
    horizontal=True,
    help="Wählen Sie 'Manuelle Eingabe' für die Berechnung eines einzelnen Gebäudes. Wählen Sie 'Upload csv-Datei', um mehrere Gebäude gleichzeitig zu berechnen."
)

with st.form("szenario_formular"):
    st.markdown("### Szenarienauswahl")
    st.info(
        """**Hinweis zu den Szenarien:**
    
    Im 'Ist-Zustand' entsprechen alle Kostenannahmen und Preise den heutigen.
    Im 'Mittleren Szenario' wird von einer Erhöhung des CO₂-Preises auf 180 €/t bis zum Jahr 2040 ausgegangen.
    Im 'Klimaschutz-Szenario' wird von einer Erhöhung des CO₂-Preises auf 300 €/t bis zum Jahr 2040 ausgegangen.
    Im Szenario 'Niedrige Klimaschutzambitionen' wird der CO₂-Preis auf 45 €/t gedeckelt.
    """
    )
    szenario = st.radio(
        "Szenarienauswahl",
        szenario_liste,
        index=None if st.session_state.szenario is None else (
            szenario_liste.index(st.session_state.szenario)
        ),
        key="radio_szenario"
    )
    szenario_bestaetigen = st.form_submit_button("Szenario bestätigen")
    if szenario_bestaetigen and not szenario:
        st.warning("Bitte wählen Sie zuerst ein Szenario aus!", icon="⚠️")

if szenario_bestaetigen:
    st.session_state.szenario_bestaetigt = True
    st.session_state.szenario = st.session_state.radio_szenario
    szenario = st.session_state.szenario
else:
    szenario = st.session_state.szenario

szenario_map = szenario_df['Kuerzel'].to_dict()
szen_kurz = szenario_map.get(szenario, "A")
if szenario in szenario_df.index:
    szen_values = szenario_df.loc[szenario].to_dict()
else:
    st.warning(f"Szenario '{szenario}' nicht gefunden. Standardwerte werden verwendet.")
    szen_values = {}

if modus == "Manuelle Eingabe":

    if st.session_state.szenario_bestaetigt:
        with st.container():
            st.markdown('''
                <div style="background-color: white; padding: 1.5em; border-radius: 10px; box-shadow: 0 0 6px rgba(0,0,0,0.1);border: 1px solid #004c93;">
                    <h4 style="color:#004c93; margin-top:0; margin-bottom:1rem;">Grunddaten</h4>
            ''', unsafe_allow_html=True)
            st.write("")
            with st.expander("Heizlast-Eingabe (optional)", expanded=False):   
                kenne_heizlast = st.checkbox(
                    "Ich kenne meine Heizlast", value=st.session_state.get("kenne_heizlast", False), key="kenne_heizlast")
                heizlast_user = st.number_input(
                    "Heizlast (kW)", min_value=1.0, max_value=110.0, value=st.session_state.get("heizlast_user", 10.0), step=0.5,
                    help="Bitte geben Sie hier Ihre bekannte Heizlast in kW ein.",
                    disabled=not st.session_state.kenne_heizlast,
                    key="heizlast_user"
                )
        with st.form("input_form", clear_on_submit=False):
            col_links, col_rechts = st.columns(2)
            with col_links:
                nutzflaeche = st.number_input(
                    "Wohnfläche [m²]", min_value=20.0, max_value=10000.0, value=200.0, step=10.0
                )
                baujahr = st.number_input(
                    "Baujahr des Gebäudes", min_value=1900, max_value=2025, value=1980
                )
                gesamtbedarf_input = st.text_input(
                    "Gesamtwärmebedarf [kWh/a]", 
                    value="", 
                    placeholder="optional",
                    help="Falls bekannt, hier eintragen (dann wird der spezifische Bedarf ignoriert!)"
                )
                gesamtbedarf = None
                if gesamtbedarf_input.strip() != "":
                    try:
                        gesamtbedarf = float(gesamtbedarf_input.replace(",", "."))
                    except ValueError:
                        gesamtbedarf = None
                        st.warning("Bitte einen gültigen Zahlenwert für den Gesamtwärmebedarf eintragen.")

                if gesamtbedarf is not None and gesamtbedarf > 0:
                    spezifisch_disabled = True
                    spezifisch_hint = "Wird ignoriert, weil Gesamtwärmebedarf eingetragen ist."
                else:
                    spezifisch_disabled = False
                    spezifisch_hint = ""

                energiebedarf = st.number_input(
                    "Spezifischer Wärmebedarf [kWh/m²a]",
                    min_value=0.0, max_value=500.0, value=150.0, step=5.0,
                    disabled=spezifisch_disabled,
                    help="Hinweis: Wird ignoriert, wenn Gesamtwärmebedarf eingetragen ist!"
                )
                if spezifisch_hint:
                    st.info(spezifisch_hint)
                st.markdown("</div>", unsafe_allow_html=True)
                
            with col_rechts:
                with st.expander("⚙️ Erweiterte Einstellungen", expanded=False):
                    preisaenderungsfaktor_emission = float(szen_values['preisaenderungsfaktor_emission'])
                    emission_cost_per_t = float(szen_values['emission_cost'])
                    zinssatz = float(szen_values['zinssatz'])
                    beobachtungszeitraum = int(szen_values['beobachtungszeitraum'])

                    zinssatz_prozent = (zinssatz - 1) * 100
                    zinssatz_prozent = st.number_input(
                        "Zinssatz [%]", value=zinssatz_prozent,
                        min_value=0.0, max_value=100.0, step=0.1, format="%.1f")
                    zinssatz = 1 + zinssatz_prozent / 100

                    beobachtungszeitraum = st.number_input(
                        "Beobachtungszeitraum (Jahre)", min_value=5, max_value=40, value=beobachtungszeitraum,
                        help="Nach VDI 2067 wird für Heizsysteme eine Beobachtungsdauer von 20 Jahren angenommen")
                    

                    wachstumsrate_emission = (preisaenderungsfaktor_emission - 1) * 100
                    wachstumsrate_emission = st.number_input(
                        "Wachstumsrate CO₂-Kosten [%]", value=wachstumsrate_emission,
                        min_value=0.0, max_value=100.0, step=0.1, format="%.1f")
                    preisaenderungsfaktor_emission = 1 + wachstumsrate_emission / 100

                    emission_cost_per_t = st.number_input(
                        "CO₂-Kosten EUR/t (2025)", value=emission_cost_per_t,
                        min_value=0.0, max_value=1000.0, step=1.0, format="%.1f")
                    emission_cost = emission_cost_per_t / 1000

            submitted = st.form_submit_button("Weiter zur Heizsystem-Auswahl")

            gesamtbedarf = None
            if gesamtbedarf_input.strip() != "":
                try:
                    gesamtbedarf = float(gesamtbedarf_input.replace(",", "."))
                except ValueError:
                    gesamtbedarf = None
                    st.warning("Bitte einen gültigen Zahlenwert für den Gesamtwärmebedarf eintragen.")

            if gesamtbedarf is not None and gesamtbedarf > 0 and nutzflaeche > 0:
                energiebedarf_spezifisch = gesamtbedarf / nutzflaeche
            else:
                energiebedarf_spezifisch = energiebedarf
            st.write(f"In Berechnungen wird verwendet: {energiebedarf_spezifisch:.2f}".replace(".", ",") + " kWh/m²a")

    if submitted or st.session_state.df_heizsysteme is not None:

        heizlast_user = st.session_state.heizlast_user if st.session_state.kenne_heizlast else None

        if submitted or st.session_state.df_heizsysteme is None:
            try:
                df = finde_passende_heizsysteme(energiebedarf_spezifisch, baujahr, nutzflaeche, szen_kurz, heizlast_user=heizlast_user)
                if df.empty:
                    st.error("Keine passenden Heizsysteme gefunden.")
                    st.stop()
                df.columns = [
                    "Name", "Leistung", "Investitionskosten", "Betriebsdauer", "Effizienz", "Emissionen",
                    "Preisänderungsfaktor_Inv", "Betriebskosten", "Preisänderungsfaktor_Bedarf",
                    "Fixkosten_O+M", "Preisänderungsfaktor_O+M", "Emissionsänderungsfaktor", "Förderung"
                ]
                for col in ["Investitionskosten", "Betriebsdauer", "Effizienz", "Emissionen",
                            "Preisänderungsfaktor_Inv", "Betriebskosten", "Preisänderungsfaktor_Bedarf",
                            "Fixkosten_O+M", "Preisänderungsfaktor_O+M", "Emissionsänderungsfaktor", "Förderung"]:
                    df[col] = df[col].astype(str).str.replace(",", ".").str.replace("|", ".")
                    df[col] = pd.to_numeric(df[col], errors='coerce')

                st.session_state.eingabedaten = {
                    "nutzflaeche": nutzflaeche, "baujahr": baujahr, "energiebedarf": energiebedarf_spezifisch,
                    "zinssatz": zinssatz, "beobachtungszeitraum": beobachtungszeitraum,
                    "preisaenderungsfaktor_emission": preisaenderungsfaktor_emission,
                    "emission_cost": emission_cost
                }
                st.session_state.df_heizsysteme = df.copy()
                st.session_state.user_values = {}
            except Exception as e:
                st.error(f"Fehler bei der Heizungsauswahl: {e}")
                st.stop()
        else:
            df = st.session_state.df_heizsysteme.copy()
            nutzflaeche = st.session_state.eingabedaten["nutzflaeche"]
            baujahr = st.session_state.eingabedaten["baujahr"]
            energiebedarf = st.session_state.eingabedaten["energiebedarf"]
            zinssatz = st.session_state.eingabedaten["zinssatz"]
            beobachtungszeitraum = st.session_state.eingabedaten["beobachtungszeitraum"]
            preisaenderungsfaktor_emission = st.session_state.eingabedaten["preisaenderungsfaktor_emission"]
            emission_cost = st.session_state.eingabedaten["emission_cost"]

        st.markdown("## 🔧 Einzelne Werte vor Berechnung anpassen (optional)")
        benutzerdef_werte = st.checkbox("Benutzerdefinierte Werte je Heizsystem aktivieren", value=False)
        with st.expander("ℹ️ Erläuterung zu den Betriebskosten-Angaben (Tarife)", expanded=False):
            st.markdown("""
        **Hinweis zu den angesetzten Betriebskosten je Heizsystem:**
        
        - **Wärmepumpen:** Für Luft-, Wasser- und Sole-Wärmepumpen wurde ein mittlerer Wärmepumpen-Tarif von **25 ct/kWh** angesetzt (Vergleichsportale wie Verivox, Stand 2025).
        - **Gasheizung:** Für Erdgas wurden durchschnittliche Privatkundenpreise von **12,28 ct/kWh** herangezogen (destatis, zweites Halbjahr 2024).
        - **Ölheizung:** Für Heizöl wurde ein Mittelwert von **10,12 ct/kWh** verwendet (Tecson, Jahresmittel 2024/2025).
        - **Pelletheizung:** Für Holzpellets wurden als Bezug der DEPV und die gängigen regionalen Durchschnittspreise 2024 betrachtet – **5,91 ct/kWh**.
        - **Holzhackschnitzelheizung:** Für Holzhackschnitzel wurde ein durchschnittlicher Preis von **3,07 ct/kWh** angenommen (Jahresmittelwert 2024 Carmen-ev).
        - **Wasserstoffheizung:** Hier wurde ein Mittelwert der Prognose der Thüga-Gruppe verwendet mit einem Aufschlag von 10 ct/kWh zu einem Preis von **33,65 ct/kWh**.
        
        **Bitte beachten Sie: Durch individuelle Verträge, regionale Unterschiede und Förderungen können tatsächliche Betriebskosten stark abweichen.**
        """)
        with st.form("edit_heizsysteme"):
            user_values = {}
            for idx, row in df.iterrows():
                with st.expander(f"{row['Name']} (Leistung: {row['Leistung']} kW)", expanded=False):
                    c1, c2 = st.columns([1,1])
                    field_state = not benutzerdef_werte
                    with c1:
                        invest = st.number_input("Investitionskosten (€)", min_value=0.0, value=float(row['Investitionskosten']),
                                                step=100.0, key=f"inv_{idx}", disabled=field_state, format="%f")
                        förderung = st.number_input("Förderung (%)", min_value=0.0, value=float(row['Förderung']),
                                                    step=1.0, key=f"foe_{idx}", disabled=field_state, format="%f",
                                                   help = "Prozentuale Förderung nach aktuellem GEG. 30 % Grundförderung für EE-Heizungen. 20 % Geschwindigkeitsbonus bei Eigennutzung. 30 % Einkommensbonus (Versteuerndes Jahreseinkommen bis 40.000 €). Zusammen kombinierbar bis 70 %.")
                        betrieb = st.number_input("Betriebsjahre", min_value=1, value=int(row['Betriebsdauer']),
                                                  key=f"betr_{idx}", disabled=field_state)
                        eff = st.number_input("Effizienz", min_value=0.01, value=float(row['Effizienz']),
                                             step=0.05, key=f"eff_{idx}", disabled=field_state)
                        bkosten = st.number_input("Betriebskosten (€/kWh)", value=float(row['Betriebskosten']),
                                             step=0.001, key=f"bk_{idx}", disabled=field_state, format="%.3f")

                    with c2:
                        preis_inv = st.number_input("Preisänderungsfaktor Investitionskosten", value=float(row['Preisänderungsfaktor_Inv']),
                                                step=0.01, key=f"prinv_{idx}", disabled=field_state)
                        preis_bedarf = st.number_input("Jährlicher Preisänderungsfaktor Betriebskosten", value=float(row['Preisänderungsfaktor_Bedarf']),
                                                       step=0.01, key=f"prbed_{idx}", disabled=field_state)
                        fix_om = st.number_input("Fixkosten Wartung (€/kW)", value=float(row['Fixkosten_O+M']),
                                                 step=0.01, key=f"fom_{idx}", disabled=field_state)
                        em_faktor = st.number_input("Emissionsänderungsfaktor", value=float(row['Emissionsänderungsfaktor']),
                                                step=0.01, key=f"emfac_{idx}", disabled=field_state)
                        emission = st.number_input("Emissionen (kg CO2/kWh)", value=float(row['Emissionen']),
                                                step=0.001, key=f"em_{idx}", disabled=field_state, format="%.3f")
                    user_values[idx] = {
                        "Investitionskosten": invest,
                        "Förderung": förderung,
                        "Betriebsdauer": betrieb,
                        "Effizienz": eff,
                        "Preisänderungsfaktor_Inv": preis_inv,
                        "Betriebskosten": bkosten,
                        "Preisänderungsfaktor_Bedarf": preis_bedarf,
                        "Fixkosten_O+M": fix_om,
                        "Emissionsänderungsfaktor": em_faktor,
                        "Emissionen": emission
                    }
            st.form_submit_button("Speichern")

        if benutzerdef_werte:
            st.session_state.user_values = user_values

        st.markdown("---")

        if st.button("Berechnung mit diesen Einstellungen durchführen"):
            df_berechnung = df.copy()
            if benutzerdef_werte:
                values = st.session_state.user_values
                for idx in df_berechnung.index:
                    for key in values[idx]:
                        df_berechnung.loc[idx, key] = values[idx][key]

            try:
                df_berechnung["Energiebedarf"] = df_berechnung.apply(lambda row: calculate_energiebedarf(
                    energiebedarf, nutzflaeche, row["Effizienz"]), axis=1)
                df_berechnung["Annuität_NK"] = df_berechnung.apply(
                    lambda row: calculate_annuity_nk(row["Förderung"], row["Investitionskosten"], zinssatz, row["Betriebsdauer"],
                                                     beobachtungszeitraum, row["Preisänderungsfaktor_Inv"]), axis=1)
                df_berechnung["Annuität_NV"] = df_berechnung.apply(
                    lambda row: calculate_annuity_nv(
                        float(row["Betriebskosten"]) - float(row["Emissionen"]) * emission_cost, row["Energiebedarf"], zinssatz,
                        row["Preisänderungsfaktor_Bedarf"], beobachtungszeitraum,
                        emission_cost, row["Emissionen"], preisaenderungsfaktor_emission, row["Emissionsänderungsfaktor"]
                    ), axis=1)
                df_berechnung["Annuität_NB"] = df_berechnung.apply(
                    lambda row: calculate_annuity_nb(row["Leistung"], row["Fixkosten_O+M"], row["Preisänderungsfaktor_O+M"],
                                                     zinssatz, beobachtungszeitraum), axis=1)
                df_berechnung["Annuität_NS"] = 0
                df_berechnung["Annuität"] = df_berechnung["Annuität_NK"] + df_berechnung["Annuität_NV"] + df_berechnung["Annuität_NB"] + df_berechnung["Annuität_NS"]

                resultat = df_berechnung[["Name", "Annuität"]].sort_values("Annuität")
                st.subheader("Annualisierte Gesamtkosten nach Heizsystem")
                st.dataframe(resultat.style.format({"Annuität": lambda x: f"{int(x):,}".replace(",", " ") + " €"}), hide_index=True)

                df_stacked = df_berechnung[["Name", "Annuität_NK", "Annuität_NV", "Annuität_NB"]].melt(
                    id_vars="Name",
                    value_vars=["Annuität_NK", "Annuität_NV", "Annuität_NB"],
                    var_name="Kostenart",
                    value_name="Wert"
                )

                df_stacked["Kostenart"] = df_stacked["Kostenart"].replace({
                    "Annuität_NK": "Kapitalgebundene Kosten",
                    "Annuität_NV": "Bedarfsgebundene Kosten",
                    "Annuität_NB": "Betriebsgebundene Kosten"
                })
                
                kostenart_order = ["Kapitalgebundene Kosten", "Bedarfsgebundene Kosten", "Betriebsgebundene Kosten"]
                df_stacked["Kostenart"] = pd.Categorical(df_stacked["Kostenart"], categories=kostenart_order, ordered=True)
                kostenart_sort_map = {k: i for i, k in enumerate(kostenart_order)}
                df_stacked["Kostenart_Sort"] = df_stacked["Kostenart"].map(kostenart_sort_map)
                
                gesamt_sortierung = df_berechnung[["Name", "Annuität"]].sort_values("Annuität", ascending=True)
                sortierte_names = list(gesamt_sortierung["Name"])
                color_order = ["Kapitalgebundene Kosten", "Bedarfsgebundene Kosten", "Betriebsgebundene Kosten"]
                color_scale = alt.Scale(domain=kostenart_order, range=[ALT1, PRIMARY, ALT2])

                stacked_chart = (
                    alt.Chart(df_stacked)
                    .mark_bar()
                    .encode(
                        x=alt.X("Wert:Q", title="Annualisierte Kosten (€)", stack="zero"),
                        y=alt.Y("Name:N", title="Heizsystem", sort=sortierte_names,
                                axis=alt.Axis(labelLimit=150)),
                        color=alt.Color(
                            "Kostenart:N",
                            scale=color_scale,
                            title="Kostenart",
                            legend=alt.Legend(
                                orient="bottom",
                                direction="horizontal",
                                titleOrient="top",
                                titleAnchor="middle",
                                columns=3,
                                symbolSize=150,
                                labelFontSize=12,
                                titleFontSize=13
                            )
                        ),
                        order=alt.Order("Kostenart_Sort:Q", sort="ascending"),
                        tooltip=["Name", "Kostenart", "Wert"]
                    )
                    .properties(
                        width="container",
                        height=500,
                        title=alt.TitleParams(
                            text="Zusammensetzung der Annualisierten Kosten pro Heizsystem",
                            fontSize=16,
                            anchor="start"
                        )
                    )
                )
                st.altair_chart(stacked_chart, use_container_width=True)

            except Exception as e:
                st.error(f"Fehler bei der Berechnung: {e}")

    else:
        st.info("Bitte Eingaben machen und auf den Button klicken.")

elif modus == "Upload csv-Datei":
    st.info("Bitte Eingaben machen und auf den Button klicken.")

    tabelle_vorlage = pd.read_csv("Input-Vorlage.csv", sep=";")
    tabelle_vorlage = tabelle_vorlage[["Objekt-ID", "Wohnflaeche", "Baujahr", "Gesamtwaermebedarf", "spezifischer Waermebedarf"]]
    
    outbuf = io.StringIO()
    tabelle_vorlage.to_csv(outbuf, sep=";", index=False)
    vorlage_bytes = outbuf.getvalue().encode("utf-8")
    
    st.markdown("#### Beispiel-CSV als Vorlage")    
    st.download_button(
        label="CSV-Vorlage herunterladen",
        data=vorlage_bytes,
        file_name="Input-Vorlage.csv",
        mime="text/csv",
        help="Diese Vorlage können Sie befüllen und anschließend hochladen. Bitte ändern Sie nicht die Spaltennamen."
    )

    if st.session_state.szenario_bestaetigt:
        st.caption(
            "Bitte eine CSV-Datei mit den Spalten 'Objekt-ID', 'Wohnflaeche', 'Baujahr', 'Gesamtwaermebedarf', 'spezifischer Waermebedarf' hochladen."
        )
        
        uploaded_file = st.file_uploader(
            "**⚠️  Hinweis:**\n Die Datei wird auf den Hugging-Face-Servern gespeichert. Laden Sie daher keine sensiblen Daten hoch.",
            type=['csv']
        )
        with st.expander("⚙️ Erweiterte Einstellungen", expanded=True):
            preisaenderungsfaktor_emission = float(szen_values.get('preisaenderungsfaktor_emission', 1.0))
            emission_cost_per_t = float(szen_values.get('emission_cost', 0.0))
            zinssatz = float(szen_values.get('zinssatz', 1.03))
            beobachtungszeitraum = int(szen_values.get('beobachtungszeitraum', 20))

            zinssatz_prozent = (zinssatz - 1) * 100
            zinssatz_prozent = st.number_input(
                "Zinssatz [%]", value=zinssatz_prozent,
                min_value=0.0, max_value=100.0, step=0.1, format="%.1f", key="zinssatz_csv")
            zinssatz = 1 + zinssatz_prozent / 100

            beobachtungszeitraum = st.number_input(
                "Beobachtungszeitraum (Jahre)", min_value=5, max_value=40, value=beobachtungszeitraum, key="beobachtungszeitraum_csv",
                help="Nach VDI 2067 wird für Heizsysteme eine Beobachtungsdauer von 20 Jahren angenommen")

            wachstumsrate_emission = (preisaenderungsfaktor_emission - 1) * 100
            wachstumsrate_emission = st.number_input(
                "Wachstumsrate CO₂-Kosten [%]", value=wachstumsrate_emission,
                min_value=0.0, max_value=100.0, step=0.1, format="%.1f", key="wachstumsrate_emission_csv")
            preisaenderungsfaktor_emission = 1 + wachstumsrate_emission / 100

            emission_cost_per_t = st.number_input(
                "CO₂-Kosten EUR/t (2025)", value=emission_cost_per_t,
                min_value=0.0, max_value=1000.0, step=1.0, format="%.1f", key="emission_cost_csv")
            emission_cost = emission_cost_per_t / 1000

        if uploaded_file:
            df_input = pd.read_csv(uploaded_file, sep=";", dtype={"Objekt-ID": str})
            st.write("Vorschau (erste Zeilen):", df_input.head())

            #Hilfsfunktion, damit auch Kommazahlen eingelesen werden können
            def parsefloat(cell):
                """Robuste Umwandlung beliebiger Zellen aus CSV in float, auch für , als Dezimaltrennzeichen."""
                try:
                    if pd.isnull(cell) or (isinstance(cell, str) and not cell.strip()):
                        return None
                    return float(str(cell).replace(",", "."))
                except Exception:
                    return None
                
            # Für die globale Anpassung Vorschau der Heizsysteme mit ersten Objekt
            row0 = df_input.iloc[0]
            try:
                nutzflaeche0 = parsefloat(row0["Wohnflaeche"])
                baujahr0 = int(row0["Baujahr"])
                gesamtbedarf0 = parsefloat(row0.get("Gesamtwaermebedarf", ""))
                spezbedarf0 = parsefloat(row0.get("spezifischer Waermebedarf", ""))
                if gesamtbedarf0 is not None and gesamtbedarf0 > 0 and nutzflaeche0 and nutzflaeche0 > 0:
                    energiebedarf_spezifisch0 = gesamtbedarf0 / nutzflaeche0
                elif spezbedarf0 is not None and spezbedarf0 > 0:
                    energiebedarf_spezifisch0 = spezbedarf0
                else:
                    energiebedarf_spezifisch0 = None
                preview_df = finde_passende_heizsysteme(energiebedarf_spezifisch0, baujahr0, nutzflaeche0, szen_kurz)
                preview_df.columns = [
                    "Name", "Leistung", "Investitionskosten", "Betriebsdauer", "Effizienz", "Emissionen",
                    "Preisänderungsfaktor_Inv", "Betriebskosten", "Preisänderungsfaktor_Bedarf",
                    "Fixkosten_O+M", "Preisänderungsfaktor_O+M", "Emissionsänderungsfaktor", "Förderung"
                ]
                for col in ["Investitionskosten", "Betriebsdauer", "Effizienz", "Emissionen",
                            "Preisänderungsfaktor_Inv", "Betriebskosten", "Preisänderungsfaktor_Bedarf",
                            "Fixkosten_O+M", "Preisänderungsfaktor_O+M", "Emissionsänderungsfaktor", "Förderung"]:
                    preview_df[col] = preview_df[col].astype(str).str.replace(",", ".").str.replace("|", ".")
                    preview_df[col] = pd.to_numeric(preview_df[col], errors='coerce')
            except Exception as e:
                st.error(f"Fehler bei Heizsystem-Auswahl-Vorschau: {e}")
                preview_df = pd.DataFrame()
            # --------------------------- Globale Anpassung nur für system-spezifische Werte
            benutzerdef_werte_batch = st.checkbox("Globale Heizsystem-Werte (system-spezifisch) anpassen", value=False)
            with st.expander("ℹ️ Erläuterung zu den Betriebskosten-Angaben (Tarife)", expanded=False):
                st.markdown("""
            **Hinweis zu den angesetzten Betriebskosten je Heizsystem:**
            
            - **Wärmepumpen:** Für Luft-, Wasser- und Sole-Wärmepumpen wurde ein mittlerer Wärmepumpen-Tarif von **25 ct/kWh** angesetzt (Vergleichsportale wie Verivox, Stand 2025).
            - **Gasheizung:** Für Erdgas wurden durchschnittliche Privatkundenpreise von **12,28 ct/kWh** herangezogen (destatis, zweites Halbjahr 2024).
            - **Ölheizung:** Für Heizöl wurde ein Mittelwert von **10,12 ct/kWh** verwendet (Tecson, Jahresmittel 2024/2025).
            - **Pelletheizung:** Für Holzpellets wurden als Bezug der DEPV und die gängigen regionalen Durchschnittspreise 2024 betrachtet – **5,91 ct/kWh**.
            - **Holzhackschnitzelheizung:** Für Holzhackschnitzel wurde ein durchschnittlicher Preis von **3,07 ct/kWh** angenommen (Jahresmittelwert 2024 Carmen-ev).
            - **Wasserstoffheizung:** Hier wurde ein Mittelwert der Prognose der Thüga-Gruppe verwendet mit einem Aufschlag von 10 ct/kWh zu einem Preis von **33,65 ct/kWh**.
            
            **Bitte beachten Sie: Durch individuelle Verträge, regionale Unterschiede und Förderungen können tatsächliche Betriebskosten stark abweichen.**
            """)
            batch_user_values = {}
            # Nur die system-spezifischen Felder!
            if benutzerdef_werte_batch and not preview_df.empty:
                with st.form("batch_edit_heizsysteme"):
                    for idx, row in preview_df.iterrows():
                        with st.expander(f"{row['Name']}", expanded=False):
                            förderung = st.number_input("Förderung (%)", min_value=0.0, value=float(row['Förderung']),
                                                        step=1.0, key=f"bfoe_{idx}", format="%f",
                                                        help = "Prozentuale Förderung nach aktuellem GEG. 30 % Grundförderung für EE-Heizungen. 20 % Geschwindigkeitsbonus bei Eigennutzung. 30 % Einkommensbonus (Versteuerndes Jahreseinkommen bis 40.000 €). Zusammen kombinierbar bis 70 %.")
                            bkosten = st.number_input("Betriebskosten (€/kWh)", value=float(row['Betriebskosten']),
                                                      step=0.001, key=f"bbk_{idx}", format="%.3f")
                            preis_bedarf = st.number_input("Jährlicher Preisänderungsfaktor Betriebskosten", value=float(row['Preisänderungsfaktor_Bedarf']),
                                                           step=0.01, key=f"bprbed_{idx}")
                            emission = st.number_input("Emissionen (kg CO2/kWh)", value=float(row['Emissionen']),
                                                        step=0.001, key=f"bem_{idx}", format="%.3f")
                            batch_user_values[idx] = {
                                "Förderung": förderung,
                                "Betriebskosten": bkosten,
                                "Preisänderungsfaktor_Bedarf": preis_bedarf,
                                "Emissionen": emission
                            }
                    st.form_submit_button("Globale Heizsystem-Werte speichern")
            if benutzerdef_werte_batch:
                st.session_state.batch_user_values = batch_user_values
            else:
                st.session_state.batch_user_values = None
  
            do_calc = st.button("Batch-Berechnung starten")
            if do_calc:
                try:
                    df_out = df_input.copy()
                    df_out["Guenstigste Alternative"] = None
                    for sys in alle_heizsysteme:
                        df_out[sys] = None
                    annuitaeten_gesamt = []
            
                    for idx, row in df_input.iterrows():
                        try:
                            nutzflaeche = parsefloat(row["Wohnflaeche"])
                            baujahr = int(row["Baujahr"])
                            gesamtbedarf = parsefloat(row.get("Gesamtwaermebedarf", ""))
                            spezbedarf   = parsefloat(row.get("spezifischer Waermebedarf", ""))
                            if gesamtbedarf is not None and gesamtbedarf > 0 and nutzflaeche and nutzflaeche > 0:
                                energiebedarf_spezifisch = gesamtbedarf / nutzflaeche
                            elif spezbedarf is not None and spezbedarf > 0:
                                energiebedarf_spezifisch = spezbedarf
                            else:
                                energiebedarf_spezifisch = None
                            df = finde_passende_heizsysteme(energiebedarf_spezifisch, baujahr, nutzflaeche, szen_kurz)
                            df.columns = [
                                "Name", "Leistung", "Investitionskosten", "Betriebsdauer", "Effizienz", "Emissionen",
                                "Preisänderungsfaktor_Inv", "Betriebskosten", "Preisänderungsfaktor_Bedarf",
                                "Fixkosten_O+M", "Preisänderungsfaktor_O+M", "Emissionsänderungsfaktor", "Förderung"
                            ]
                            for col in ["Investitionskosten", "Betriebsdauer", "Effizienz", "Emissionen",
                                        "Preisänderungsfaktor_Inv", "Betriebskosten", "Preisänderungsfaktor_Bedarf",
                                        "Fixkosten_O+M", "Preisänderungsfaktor_O+M", "Emissionsänderungsfaktor", "Förderung"]:
                                df[col] = df[col].astype(str).str.replace(",", ".").str.replace("|", ".")
                                df[col] = pd.to_numeric(df[col], errors='coerce')
                            # Wenn Batch-global-Werte gesetzt sind, anwenden:
                            batch_user_vals = st.session_state.get("batch_user_values")
                            if batch_user_vals is not None:
                                # Mapping via Name zur Sicherheit
                                preview_heizsys_names = {preview_df.loc[x, "Name"]: x for x in preview_df.index}
                                for pidx in batch_user_vals:
                                    sysname = preview_df.loc[pidx, "Name"]
                                    # Suche nach dem Namen im aktuellen DF
                                    df_idx = df[df["Name"] == sysname].index
                                    if not df_idx.empty:
                                        for key in batch_user_vals[pidx]:
                                            df.at[df_idx[0], key] = batch_user_vals[pidx][key]
                            energiebedarf = energiebedarf_spezifisch
                            df["Energiebedarf"] = df.apply(lambda rowh: calculate_energiebedarf(
                                energiebedarf, nutzflaeche, rowh["Effizienz"]), axis=1)
                            df["Annuität_NK"] = df.apply(
                                lambda rowh: calculate_annuity_nk(rowh["Förderung"], rowh["Investitionskosten"], zinssatz, rowh["Betriebsdauer"],
                                                                 beobachtungszeitraum, rowh["Preisänderungsfaktor_Inv"]), axis=1)
                            df["Annuität_NV"] = df.apply(
                                lambda rowh: calculate_annuity_nv(
                                    float(rowh["Betriebskosten"]) - float(rowh["Emissionen"]) * emission_cost, rowh["Energiebedarf"], zinssatz,
                                    rowh["Preisänderungsfaktor_Bedarf"], beobachtungszeitraum,
                                    emission_cost, rowh["Emissionen"], preisaenderungsfaktor_emission, rowh["Emissionsänderungsfaktor"]
                                ), axis=1)
                            df["Annuität_NB"] = df.apply(
                                lambda rowh: calculate_annuity_nb(rowh["Leistung"], rowh["Fixkosten_O+M"], rowh["Preisänderungsfaktor_O+M"],
                                                                 zinssatz, beobachtungszeitraum), axis=1)
                            df["Annuität_NS"] = 0
                            df["Annuität"] = df["Annuität_NK"] + df["Annuität_NV"] + df["Annuität_NB"] + df["Annuität_NS"]

                            ann_dict = dict(zip(df["Name"], df["Annuität"]))
                            guenstigstes_sys = min(ann_dict.items(), key=lambda x: x[1])[0] if ann_dict else ""
                            df_out.at[idx, "Guenstigste Alternative"] = guenstigstes_sys
                            for sys in alle_heizsysteme:
                                wert = ann_dict.get(sys, None)
                                df_out.at[idx, sys] = wert
                                
                            # df = df.sort_values("Annuität")
                            df_anni = df[[
                                "Name", "Annuität_NK", "Annuität_NV", "Annuität_NB", "Annuität"
                            ]].copy()
                            df_anni["Objekt-ID"] = row.get("Objekt-ID", idx)
                            annuitaeten_gesamt.append(df_anni)

                            # for j in range(min(len(df), max_count)):
                            #     df_out.at[idx, annuität_colnames[j]] = int(df.iloc[j]["Annuität"])
                            #     df_out.at[idx, hsystem_colnames[j]] = df.iloc[j]["Name"]
                        except Exception as e:
                            st.warning(f"Objekt-ID {row.get('Objekt-ID', idx)}: {e}")
    
                    heizsystem_namensmap = {
                        "Ölheizung": "Oelheizung",
                        "Luft-Wasser Wärmepumpe": "Luft-Wasser Waermepumpe",
                        "Sole-Wasser Wärmepumpe": "Sole-Wasser Waermepumpe",
                        "Wasser-Wasser Wärmepumpe": "Wasser-Wasser Waermepumpe",
                    }

                    
                    # Klarnamen-Liste aus Mapping generieren
                    heizsysteme_klar = [heizsystem_namensmap.get(hs, hs) for hs in alle_heizsysteme]
                    spalten_anni = [f"Annuitaet {name}" for name in heizsysteme_klar]
                    csv_map = {name: spalte for name, spalte in zip(heizsysteme_klar, spalten_anni)}
                    
                    # Schritt 1: Spalten korrekt umbenennen
                    df_out = df_out.rename(columns=heizsystem_namensmap)
                    df_out = df_out.rename(columns=csv_map)
                    df_out["Guenstigste Alternative"] = df_out["Guenstigste Alternative"].replace(heizsystem_namensmap)
                    
                    # Schritt 2: Format-Wandlung nur auf Annuität-Spalten
                    def format_eur(x):
                        try:
                            if pd.isnull(x):
                                return ""
                            return "{:.2f}".format(float(x)).replace(".", ",")
                        except Exception:
                            return str(x)
                    
                    for spalte in spalten_anni:
                        if spalte in df_out.columns:
                            df_out[spalte] = df_out[spalte].apply(format_eur)
                        
                    st.success("Berechnung abgeschlossen!")

                    st.session_state["annuitaeten_gesamt_batch"] = annuitaeten_gesamt

                    if annuitaeten_gesamt:
                        # Verbinde alle Einzel-DFs zu einem großen Table
                        df_alle = pd.concat(annuitaeten_gesamt, ignore_index=True)
                        # Mittelwerte pro System
                        df_mittel = df_alle.groupby("Name")[["Annuität_NK", "Annuität_NV", "Annuität_NB", "Annuität"]].mean(numeric_only=True).reset_index()
                        st.session_state["df_mittel"] = df_mittel
                        st.session_state["annuitaeten_gesamt_batch"] = annuitaeten_gesamt
                        st.session_state["df_out"] = df_out
                        
                except Exception as e:
                    st.error(f"Fehler beim Einlesen/Berechnen: {e}")

            if "df_out" in st.session_state and st.session_state["df_out"] is not None:    
                csv_buffer = io.StringIO()
                st.session_state["df_out"].to_csv(csv_buffer, sep=";", index=False)
                csv_bytes = csv_buffer.getvalue().encode("utf-8")
                st.download_button(
                    "Download Ergebnis-CSV",
                    csv_bytes,
                    file_name="heizsysteme_batch_ergebnis.csv",
                    mime="text/csv"
                )

            # --- Häufigkeit der günstigsten Technologie als Säulendiagramm ----------
            if "df_out" in st.session_state and st.session_state["df_out"] is not None:
                # Count cheapest alternatives
                freq = (
                    st.session_state["df_out"]["Guenstigste Alternative"]
                    .value_counts()
                    .rename_axis("Heizsystem")
                    .reset_index(name="Anzahl")
                )
            
                # Sort bars descending
                freq = freq.sort_values("Anzahl", ascending=False)
            
                # Bar chart with Altair
                bar_chart = (
                    alt.Chart(freq)
                    .mark_bar()
                    .encode(
                        x=alt.X("Heizsystem:N", sort=freq["Heizsystem"].tolist(), title="günstigste Alternative"),
                        y=alt.Y("Anzahl:Q", title="Häufigkeit"),
                        tooltip=["Heizsystem", "Anzahl"]
                    )
                    .properties(
                        width="container",
                        height=400,
                        title=alt.TitleParams(
                            text="Häufigkeit der günstigsten Heizsystem-Alternative",
                            fontSize=16,
                            anchor="start"
                        )
                    )
                )
                st.altair_chart(bar_chart, use_container_width=True)

            if "df_mittel" in st.session_state and st.session_state["df_mittel"] is not None:
                df_mittel = st.session_state["df_mittel"]
                df_stacked = df_mittel.melt(
                    id_vars="Name",
                    value_vars=["Annuität_NK", "Annuität_NV", "Annuität_NB"],
                    var_name="Kostenart",
                    value_name="Wert"
                )
                df_stacked["Kostenart"] = df_stacked["Kostenart"].replace({
                    "Annuität_NK": "Kapitalgebundene Kosten",
                    "Annuität_NV": "Bedarfsgebundene Kosten",
                    "Annuität_NB": "Betriebsgebundene Kosten"
                })
                kostenart_order = ["Kapitalgebundene Kosten", "Bedarfsgebundene Kosten", "Betriebsgebundene Kosten"]
                df_stacked["Kostenart"] = pd.Categorical(df_stacked["Kostenart"], categories=kostenart_order, ordered=True)
                color_scale = alt.Scale(domain=kostenart_order, range=["#00386c", "#004c93", "#0069c8"])
                sortierte_names = df_mittel.sort_values("Annuität")["Name"]
            
                st.markdown("### Mittlere annualisierte Kosten pro Heizsystem (Batch-Durchschnitt)")
                st.altair_chart(
                    (alt.Chart(df_stacked)
                    .mark_bar()
                    .encode(
                        x=alt.X("Wert:Q", title="mittlere annualisierte Kosten (€)", stack="zero"),
                        y=alt.Y("Name:N", title="Heizsystem", sort=list(sortierte_names)),
                        color=alt.Color("Kostenart:N", scale=color_scale, title="Kostenart",
                                legend=alt.Legend(
                                orient="bottom",
                                direction="horizontal",
                                titleOrient="top",
                                titleAnchor="middle",
                                columns=3,
                                symbolSize=150,
                                labelFontSize=12,
                                titleFontSize=13
                            )
                        ),
                        order=alt.Order("Kostenart_Sort:Q", sort="ascending"),
                        tooltip=["Name", "Kostenart", "Wert"])                        
                .properties(
                        width="container",
                        height=500,
                        title=alt.TitleParams(
                            text="Zusammensetzung der Annualisierten Kosten pro Heizsystem",
                            fontSize=16,
                            anchor="start"
                        )
                    )),
                use_container_width=True
                )
            
            if (
                "annuitaeten_gesamt_batch" in st.session_state
                and st.session_state["annuitaeten_gesamt_batch"]
            ):
                st.markdown("### Einzelobjekte interaktiv anzeigen")
                df_alle = pd.concat(st.session_state["annuitaeten_gesamt_batch"], ignore_index=True)
                objekt_ids = df_alle["Objekt-ID"].unique()
                selected_objekt_id = st.selectbox(
                    "Objekt-ID wählen für Einzelanzeige:",
                    objekt_ids,
                    key="objektid_einzelauswahl"
                )
                df_objekt = df_alle[df_alle["Objekt-ID"] == selected_objekt_id].copy()
                if not df_objekt.empty:
                    df_stacked_obj = df_objekt[["Name", "Annuität_NK", "Annuität_NV", "Annuität_NB"]].melt(
                        id_vars="Name",
                        value_vars=["Annuität_NK", "Annuität_NV", "Annuität_NB"],
                        var_name="Kostenart",
                        value_name="Wert"
                    )
                    df_stacked_obj["Kostenart"] = df_stacked_obj["Kostenart"].replace({
                        "Annuität_NK": "Kapitalgebundene Kosten",
                        "Annuität_NV": "Bedarfsgebundene Kosten",
                        "Annuität_NB": "Betriebsgebundene Kosten"
                    })
                    kostenart_order = ["Kapitalgebundene Kosten", "Bedarfsgebundene Kosten", "Betriebsgebundene Kosten"]
                    df_stacked_obj["Kostenart"] = pd.Categorical(df_stacked_obj["Kostenart"], categories=kostenart_order, ordered=True)
                    kostenart_sort_map = {k: i for i, k in enumerate(kostenart_order)}
                    df_stacked_obj["Kostenart_Sort"] = df_stacked_obj["Kostenart"].map(kostenart_sort_map)
                    gesamt_sortierung = df_objekt[["Name", "Annuität"]].sort_values("Annuität", ascending=True)
                    sortierte_names = list(gesamt_sortierung["Name"])
                    color_scale = alt.Scale(domain=kostenart_order, range=[ALT1, PRIMARY, ALT2])
                    stacked_chart_obj = (
                        alt.Chart(df_stacked_obj)
                        .mark_bar()
                        .encode(
                            x=alt.X("Wert:Q", title="Annualisierte Kosten (€)", stack="zero"),
                            y=alt.Y("Name:N", title="Heizsystem", sort=sortierte_names, axis=alt.Axis(labelLimit=150)),
                            color=alt.Color(
                                "Kostenart:N",
                                scale=color_scale,
                                title="Kostenart",
                                legend=alt.Legend(
                                    orient="bottom",
                                    direction="horizontal",
                                    titleOrient="top",
                                    titleAnchor="middle",
                                    columns=3,
                                    symbolSize=150,
                                    labelFontSize=12,
                                    titleFontSize=13
                                )
                            ),
                            order=alt.Order("Kostenart_Sort:Q", sort="ascending"),
                            tooltip=["Name", "Kostenart", "Wert"]
                        )
                        .properties(
                            width="container",
                            height=500,
                            title=alt.TitleParams(
                                text=f"Kostenaufteilung für Objekt-ID {selected_objekt_id}",
                                fontSize=16,
                                anchor="start"
                            )
                        )
                    )
                    st.altair_chart(stacked_chart_obj, use_container_width=True)
                else:
                    st.warning("Keine Daten für diese Objekt-ID.")    
            
            st.markdown("---")
            st.caption("Berechnung nach VDI 2067, Heizlastberechnung gemäß DIN EN 15378.")
        else:
            st.warning("Bitte laden Sie eine CSV-Datei hoch", icon="⚠️")