Source code for districtheatingsim.gui.BuildingTab.building_tab

"""
Building data management and heat demand calculation module.

Provides MVP architecture for building heat requirement calculations using
BDEW profiles and Test Reference Year (TRY) climate data.

:author: Dipl.-Ing. (FH) Jonas Pfeiffer
"""

import json
import os
import traceback
from collections import namedtuple

import matplotlib.gridspec as gridspec
import matplotlib.pyplot as plt
import pandas as pd
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qtagg import NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure
from PyQt6.QtCore import Qt, pyqtSignal
from PyQt6.QtGui import QAction
from PyQt6.QtWidgets import (
    QComboBox,
    QFileDialog,
    QGroupBox,
    QHBoxLayout,
    QLabel,
    QLineEdit,
    QMenuBar,
    QMessageBox,
    QSizePolicy,
    QTableWidget,
    QTableWidgetItem,
    QVBoxLayout,
    QWidget,
)

from districtheatingsim.gui.utilities import CheckableComboBox, convert_to_serializable
from districtheatingsim.heat_requirement.heat_requirement_calculation_csv import generate_profiles_from_csv
from districtheatingsim.utilities.schema import add_meta, check_version


[docs] class BuildingModel: """ Data model for building information and heat demand calculations. Manages CSV input data, JSON results, and heat profile generation. """
[docs] def __init__(self): self.base_path = None self.csv_path = "" self.json_path = "" self.data = None self.results = None
[docs] def load_csv(self): """ Load CSV data into DataFrame. :raises Exception: If CSV loading fails """ try: self.data = pd.read_csv(self.csv_path, delimiter=";", dtype={"Subtyp": str}) except Exception as e: raise Exception(f"Fehler beim Laden der CSV-Datei: {e}") from e
[docs] def save_csv(self): """ Save DataFrame to CSV file. :raises Exception: If CSV saving fails """ if self.data is not None: try: self.data.to_csv(self.csv_path, index=False, sep=";", encoding="utf-8-sig") except Exception as e: raise Exception(f"Fehler beim Speichern der CSV-Datei: {e}") from e
[docs] def load_json(self): """ Load results from JSON file. :raises Exception: If JSON loading fails """ try: with open(self.json_path, encoding="utf-8") as f: loaded_data = json.load(f) check_version(loaded_data, "building_data") # Building entries are keyed by index; the _meta block (and any other # non-building key) is naturally skipped by the 'wärme' filter. self.results = {k: v for k, v in loaded_data.items() if isinstance(v, dict) and "wärme" in v} except Exception as e: raise Exception(f"Fehler beim Laden der JSON-Datei: {e}") from e
[docs] def save_json(self, combined_data): """ Save results to JSON file. :param combined_data: Data to save :type combined_data: dict :raises Exception: If JSON saving fails """ try: with open(self.json_path, "w", encoding="utf-8") as f: json.dump(add_meta(combined_data, "building_data"), f, indent=4) except Exception as e: raise Exception(f"Fehler beim Speichern der Ergebnisse: {e}") from e
HeatDemandResult = namedtuple( "HeatDemandResult", ["time_steps", "total_kw", "heating_kw", "warmwater_kw", "max_kw", "supply_temp", "return_temp", "air_temp"], )
[docs] def calculate_heat_demand(self, data, try_filename, year: int = 2023): """ Calculate heat demand profiles from building data. :param data: Building input data :type data: pd.DataFrame :param try_filename: Climate data filename :type try_filename: str :param year: Calculation year for profile generation (BDEW/VDI 4655), defaults to 2023 :type year: int :return: Calculated heat demand profiles in kW :rtype: HeatDemandResult """ ( yearly_time_steps, total_heat_W, heating_heat_W, warmwater_heat_W, max_heat_requirement_W, supply_temperature_curve, return_temperature_curve, hourly_air_temperatures, ) = generate_profiles_from_csv(data=data, TRY=try_filename, calc_method="Datensatz", year=year) # Convert from W to kW return self.HeatDemandResult( time_steps=yearly_time_steps, total_kw=total_heat_W / 1000, heating_kw=heating_heat_W / 1000, warmwater_kw=warmwater_heat_W / 1000, max_kw=max_heat_requirement_W / 1000, supply_temp=supply_temperature_curve, return_temp=return_temperature_curve, air_temp=hourly_air_temperatures, )
[docs] class BuildingPresenter: """ Presenter managing interaction between BuildingModel and BuildingTabView. Coordinates building data operations, heat demand calculations, and UI updates. """
[docs] def __init__(self, model, view, folder_manager, data_manager, config_manager): """ Initialize building presenter. :param model: Data model :type model: BuildingModel :param view: View component :type view: BuildingTabView :param folder_manager: Folder manager :type folder_manager: ProjectFolderManager :param data_manager: Data manager :type data_manager: DataManager :param config_manager: Configuration manager :type config_manager: ProjectConfigManager """ self.model = model self.view = view self.folder_manager = folder_manager self.data_manager = data_manager self.config_manager = config_manager self.combined_data = None # Connect signals self.folder_manager.project_folder_changed.connect(self.standard_path) if self.folder_manager.variant_folder: self.standard_path(self.folder_manager.variant_folder) self.view.create_csv_template_signal.connect(self.create_csv_template) self.view.load_csv_signal.connect(self.load_csv) self.view.save_csv_signal.connect(self.save_csv) self.view.load_json_signal.connect(self.load_json) self.view.save_json_signal.connect(self.save_json) self.view.calculate_heat_demand_signal.connect(self.calculate_heat_demand) self.view.data_type_combobox.view().pressed.connect(self.on_combobox_selection_changed) self.view.building_combobox.view().pressed.connect(self.on_combobox_selection_changed) self.view.plot(self.model.results) # Initial plot
[docs] def standard_path(self, path): """ Update default file paths. :param path: New base path :type path: str """ if path: self.model.base_path = path self.model.csv_path = os.path.join( self.model.base_path, self.config_manager.get_relative_path("current_building_data_path") ) self.model.json_path = os.path.join( self.model.base_path, self.config_manager.get_relative_path("building_load_profile_path") )
# Column order must match what geocoding, heat-profile calc, and net generation expect. # Heizgrenztemperatur / Heizexponent / P_max are optional BDEW parameters; leave blank to use defaults. _TEMPLATE_COLUMNS = [ "Land", "Bundesland", "Stadt", "Adresse", "Wärmebedarf", "Gebäudetyp", "Subtyp", "WW_Anteil", "Typ_Heizflächen", "VLT_max", "Steigung_Heizkurve", "RLT_max", "Normaußentemperatur", "Heizgrenztemperatur", "Heizexponent", "P_max", "UTM_X", "UTM_Y", ] _TEMPLATE_EXAMPLE = [ "Deutschland", "Sachsen", "Leipzig", "Musterstraße 1", "50000", "MFH", "", "0.15", "HK", "70", "1.5", "50", "-15", "", "", "", "", "", ]
[docs] def create_csv_template(self): """ Save an empty CSV template with the required column headers and one example row. The user picks the save location via file dialog. """ default_path = os.path.join(self.model.base_path or "", "Quartier.csv") fname, _ = QFileDialog.getSaveFileName(self.view, "CSV-Vorlage speichern", default_path, "CSV Files (*.csv)") if not fname: return try: template_df = pd.DataFrame([self._TEMPLATE_EXAMPLE], columns=self._TEMPLATE_COLUMNS) template_df.to_csv(fname, index=False, sep=";", encoding="utf-8-sig") self.view.populate_table(template_df) self.model.csv_path = fname self.model.data = template_df self.view.show_message( "Vorlage erstellt", f"CSV-Vorlage wurde gespeichert und geladen:\n{fname}\n\n" "Pflichtfelder: Land, Bundesland, Stadt, Adresse, Wärmebedarf, Gebäudetyp,\n" "WW_Anteil, VLT_max, Steigung_Heizkurve, RLT_max, Normaußentemperatur.\n\n" "Optionale BDEW-Felder (leer lassen = pyslpheat-Standard):\n" " Heizgrenztemperatur – Temperatur ab der geheizt wird (Standard ~15 °C)\n" " Heizexponent – Formparameter der Heizkurve (Standard 1.0)\n" " P_max – maximale Wärmeleistung in kW (begrenzt Lastspitzen)\n\n" "UTM_X/UTM_Y werden beim Geocoding automatisch befüllt.", ) except Exception as e: self.view.show_error_message("Fehler", f"Vorlage konnte nicht gespeichert werden: {e}")
[docs] def load_csv(self, fname=None, show_dialog=True): """ Load CSV file with file dialog. Parameters ---------- fname : str, optional Filename to load. show_dialog : bool, optional Whether to show success/error dialogs. Default is True. """ if fname is None or fname == "": fname, _ = QFileDialog.getOpenFileName( self.view, "Select CSV File", self.model.csv_path, "CSV Files (*.csv);;All Files (*)" ) if fname: try: self.model.csv_path = fname self.model.load_csv() self.view.populate_table(self.model.data) if show_dialog: self.view.show_message("Erfolg", f"CSV-Datei {fname} wurde geladen.") except Exception as e: if show_dialog: self.view.show_error_message("Fehler", str(e))
[docs] def save_csv(self, fname=None, show_dialog=True): """ Save CSV file with file dialog. Parameters ---------- fname : str, optional Filename to save. """ if fname is None or fname == "": if show_dialog: fname, _ = QFileDialog.getSaveFileName( self.view, "Save CSV File", self.model.csv_path, "CSV Files (*.csv);;All Files (*)" ) else: fname = self.model.csv_path if fname: try: self.model.csv_path = fname self.model.save_csv() if show_dialog: self.view.show_message("Erfolg", f"CSV-Datei wurde in {fname} gespeichert.") except Exception as e: if show_dialog: self.view.show_error_message("Fehler", str(e))
[docs] def load_json(self, fname=None, show_dialog=True): """ Load JSON results with optional file dialog. Parameters ---------- fname : str, optional Filename to load. show_dialog : bool, optional Whether to show file dialog if no filename provided. Default is True. """ if fname is None or fname == "": if show_dialog: fname, _ = QFileDialog.getOpenFileName( self.view, "Select JSON File", self.model.json_path, "JSON Files (*.json);;All Files (*)" ) else: return if fname: try: self.model.json_path = fname self.model.load_json() self.view.populate_building_combobox(self.model.results) self.view.plot(self.model.results) except Exception as e: self.view.show_error_message("Fehler", str(e))
[docs] def save_json(self, fname=None, show_dialog=True): """ Save JSON results with file dialog. Parameters ---------- fname : str, optional Filename to save. """ if self.combined_data is None: self.view.show_error_message("Fehler", "Es sind keine Daten zum Speichern vorhanden.") return if fname is None or fname == "": if show_dialog: fname, _ = QFileDialog.getSaveFileName( self.view, "Save JSON File", self.model.json_path, "JSON Files (*.json);;All Files (*)" ) else: fname = self.model.json_path if fname: try: self.model.json_path = fname self.model.save_json(self.combined_data) if show_dialog: self.view.show_message("Erfolg", f"Ergebnisse wurden in {fname} gespeichert.") except Exception as e: if show_dialog: self.view.show_error_message("Fehler", str(e))
[docs] def calculate_heat_demand(self, _=None): """Calculate heat demand profiles and save results.""" self.model.data = self.view.get_table_data() if self.model.data.empty: self.view.show_error_message("Fehler", "Die Tabelle enthält keine Daten.") return try: try_filename = self.folder_manager.try_filename year = getattr(self.folder_manager, "calculation_year", 2023) results = self.model.calculate_heat_demand(self.model.data, try_filename, year=year) self.model.results = self.format_results(results, self.model.data) self.view.populate_building_combobox(self.model.results) self.view.plot(self.model.results) self.combined_data = self.combine_data_with_results(self.model.data, self.model.results) self.model.save_json(self.combined_data) self.view.show_message( "Erfolg", f"Berechnung der Gebäudelastgänge abgeschlossen und in {self.model.json_path} gespeichert." ) except Exception as e: tb_str = "".join(traceback.format_exception(type(e), e, e.__traceback__)) self.view.show_error_message("Fehler", f"Es ist ein Fehler aufgetreten: {str(e)}\n\nDetails:\n{tb_str}")
[docs] def format_results(self, results, data): """ Format calculation results for JSON storage. :param results: Raw calculation results :type results: tuple :param data: Input building data :type data: pd.DataFrame :return: Formatted results dictionary :rtype: dict """ formatted_results = {} for idx in range(len(data)): building_id = str(idx) formatted_results[building_id] = { "zeitschritte": [convert_to_serializable(ts) for ts in results.time_steps], "außentemperatur": results.air_temp.tolist(), "wärme": results.total_kw[idx].tolist(), "heizwärme": results.heating_kw[idx].tolist(), "warmwasserwärme": results.warmwater_kw[idx].tolist(), "max_last": results.max_kw.tolist(), "vorlauftemperatur": results.supply_temp[idx].tolist(), "rücklauftemperatur": results.return_temp[idx].tolist(), } for key, value in data.iloc[idx].items(): formatted_results[building_id][key] = convert_to_serializable(value) return formatted_results
[docs] def combine_data_with_results(self, data, results): """ Combine input data with calculation results. :param data: Input data :type data: pd.DataFrame :param results: Calculation results :type results: dict :return: Combined data dictionary :rtype: dict """ data.reset_index(drop=True, inplace=True) data_dict = data.map(convert_to_serializable).to_dict(orient="index") combined_data = {str(idx): {**data_dict[idx], **results[str(idx)]} for idx in range(len(data))} return combined_data
[docs] def on_combobox_selection_changed(self): """Update plot when combobox selection changes.""" self.view.plot(self.model.results)
[docs] class BuildingTabView(QWidget): """ View component for building tab UI. Provides table for building data input and interactive plotting of heat demand profiles. """ create_csv_template_signal = pyqtSignal() load_csv_signal = pyqtSignal() save_csv_signal = pyqtSignal() load_json_signal = pyqtSignal() save_json_signal = pyqtSignal() calculate_heat_demand_signal = pyqtSignal()
[docs] def __init__(self, parent=None): """ Initialize building tab view. :param parent: Parent widget (optional) :type parent: QWidget """ super().__init__(parent) self.initUI()
[docs] def initUI(self): """Initialize UI components.""" self.main_layout = QVBoxLayout(self) self.initMenuBar() self.initDataTable() self.initPlotAndComboboxes() self.setLayout(self.main_layout)
[docs] def initMenuBar(self): """Initialize menu bar with file operations.""" self.menubar = QMenuBar(self) self.menubar.setFixedHeight(30) new_template_action = QAction("Neue CSV-Vorlage erstellen", self) new_template_action.triggered.connect(self.createCsvTemplate) self.menubar.addAction(new_template_action) load_csv_action = QAction("Gebäudedaten laden", self) load_csv_action.triggered.connect(self.loadCsvFile) self.menubar.addAction(load_csv_action) save_csv_action = QAction("Gebäudedaten speichern", self) save_csv_action.triggered.connect(self.saveCsvFile) self.menubar.addAction(save_csv_action) calculate_action = QAction("Gebäudelastgänge berechnen", self) calculate_action.triggered.connect(self.calculateHeatDemand) self.menubar.addAction(calculate_action) save_json_action = QAction("Gebäudelastgänge speichern", self) save_json_action.triggered.connect(self.saveJsonFile) self.menubar.addAction(save_json_action) load_json_action = QAction("Gebäudelastgänge laden", self) load_json_action.triggered.connect(self.loadJsonFile) self.menubar.addAction(load_json_action) self.main_layout.setMenuBar(self.menubar)
[docs] def initDataTable(self): """Initialize data table widget.""" self.table_widget = QTableWidget(self) self.table_widget.setMinimumSize(1200, 300) self.table_widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) self.main_layout.addWidget(self.table_widget)
[docs] def initPlotAndComboboxes(self): """Initialize plot area and data selection controls.""" plt.style.use("seaborn-v0_8-darkgrid") # Plot area oben plot_layout = QVBoxLayout() self.figure = Figure(constrained_layout=True) self.canvas = FigureCanvas(self.figure) self.canvas.setMinimumSize(1200, 500) self.canvas.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) self.toolbar = NavigationToolbar(self.canvas, self) plot_layout.addWidget(self.canvas) toolbar_layout = QHBoxLayout() toolbar_layout.addStretch(1) toolbar_layout.addWidget(self.toolbar) toolbar_layout.addStretch(1) plot_layout.addLayout(toolbar_layout) self.main_layout.addLayout(plot_layout) combobox_group = QGroupBox() combobox_group.setTitle("Auswahl") combobox_group_layout = QHBoxLayout() # Daten auswählen data_label = QLabel("<b>Daten auswählen:</b>") data_label.setStyleSheet("font-size: 15px; margin-right: 10px;") self.data_type_combobox = CheckableComboBox(self) for data_type in [ "Wärmebedarf", "Heizwärmebedarf", "Warmwasserbedarf", "Vorlauftemperatur", "Rücklauftemperatur", ]: self.data_type_combobox.addItem(data_type) self.data_type_combobox.model().item(0).setCheckState(Qt.CheckState.Checked) combobox_group_layout.addWidget(data_label) combobox_group_layout.addWidget(self.data_type_combobox) # Gebäude auswählen building_label = QLabel("<b>Gebäude auswählen:</b>") building_label.setStyleSheet("font-size: 15px; margin-left: 30px; margin-right: 10px;") self.building_combobox = CheckableComboBox(self) combobox_group_layout.addWidget(building_label) combobox_group_layout.addWidget(self.building_combobox) combobox_group.setLayout(combobox_group_layout) self.main_layout.addWidget(combobox_group)
[docs] def createCsvTemplate(self): """Emit signal to create a new CSV template.""" self.create_csv_template_signal.emit()
[docs] def loadCsvFile(self): """Emit signal to load CSV file.""" self.load_csv_signal.emit()
[docs] def saveCsvFile(self): """Emit signal to save CSV file.""" self.save_csv_signal.emit()
[docs] def loadJsonFile(self): """Emit signal to load JSON file.""" self.load_json_signal.emit()
[docs] def saveJsonFile(self): """Emit signal to save JSON file.""" self.save_json_signal.emit()
[docs] def calculateHeatDemand(self): """Emit signal to calculate heat demand.""" self.calculate_heat_demand_signal.emit()
[docs] def populate_table(self, data): """ Populate table with DataFrame data. :param data: Data to display in table :type data: pd.DataFrame """ self.table_widget.setColumnCount(len(data.columns)) self.table_widget.setRowCount(len(data.index)) self.table_widget.setHorizontalHeaderLabels(data.columns) for i in range(len(data.index)): for j in range(len(data.columns)): item = QTableWidgetItem(str(data.iat[i, j])) self.table_widget.setItem(i, j, item) self.table_widget.resizeColumnsToContents()
[docs] def get_table_data(self): """ Extract data from table widget. :return: Table data as DataFrame :rtype: pd.DataFrame """ rows = self.table_widget.rowCount() columns = self.table_widget.columnCount() data = [] for row in range(rows): row_data = [] for column in range(columns): widget = self.table_widget.cellWidget(row, column) if widget: if isinstance(widget, QComboBox): row_data.append(widget.currentText()) elif isinstance(widget, QLineEdit): row_data.append(widget.text()) else: item = self.table_widget.item(row, column) if item and item.text(): row_data.append(item.text()) else: row_data.append(None) data.append(row_data) df = pd.DataFrame(data, columns=[self.table_widget.horizontalHeaderItem(i).text() for i in range(columns)]) if "Subtyp" in df.columns: df["Subtyp"] = df["Subtyp"].astype(str) return df
[docs] def populate_building_combobox(self, results): """ Populate building selection combobox. :param results: Results data for building selection :type results: dict """ self.building_combobox.clear() for key in results.keys(): self.building_combobox.addItem(f"Gebäude {key}") item = self.building_combobox.model().item(self.building_combobox.count() - 1, 0) item.setCheckState(Qt.CheckState.Checked)
[docs] def plot(self, results=None): """ Plot heat demand profiles for selected buildings and data types. :param results: Heat demand calculation results :type results: dict """ if results is None: return self.figure.clear() gs = gridspec.GridSpec(1, 3, width_ratios=[0.18, 0.64, 0.18], figure=self.figure) ax_legend_left = self.figure.add_subplot(gs[0, 0]) ax_main = self.figure.add_subplot(gs[0, 1]) ax_legend_right = self.figure.add_subplot(gs[0, 2]) ax2 = ax_main.twinx() selected_data_types = self.data_type_combobox.checkedItems() selected_buildings = self.building_combobox.checkedItems() label_fontsize = 16 legend_fontsize = 12 line_width = 2 color_map = plt.get_cmap("tab10") temp_color_map = plt.get_cmap("Set2") color_idx = 0 temp_color_idx = 0 lines_ax1 = [] labels_ax1 = [] lines_ax2 = [] labels_ax2 = [] for building in selected_buildings: key = building.split()[-1] value = results[key] x = list(range(len(value["wärme"]))) if "Wärmebedarf" in selected_data_types: (line,) = ax_main.plot( x, value["wärme"], label=f"Wärmebedarf Gebäude {key}", color=color_map(color_idx % 10), linewidth=line_width, ) lines_ax1.append(line) labels_ax1.append(f"Wärmebedarf Gebäude {key}") color_idx += 1 if "Heizwärmebedarf" in selected_data_types: (line,) = ax_main.plot( x, value["heizwärme"], label=f"Heizwärmebedarf Gebäude {key}", color=color_map(color_idx % 10), linestyle="--", linewidth=line_width, ) lines_ax1.append(line) labels_ax1.append(f"Heizwärmebedarf Gebäude {key}") color_idx += 1 if "Warmwasserbedarf" in selected_data_types: (line,) = ax_main.plot( x, value["warmwasserwärme"], label=f"Warmwasserbedarf Gebäude {key}", color=color_map(color_idx % 10), linestyle=":", linewidth=line_width, ) lines_ax1.append(line) labels_ax1.append(f"Warmwasserbedarf Gebäude {key}") color_idx += 1 if "Vorlauftemperatur" in selected_data_types: (line,) = ax2.plot( x, value["vorlauftemperatur"], label=f"Vorlauftemperatur Gebäude {key}", color=temp_color_map(temp_color_idx % 8), linestyle="-.", linewidth=line_width, ) lines_ax2.append(line) labels_ax2.append(f"Vorlauftemperatur Gebäude {key}") temp_color_idx += 1 if "Rücklauftemperatur" in selected_data_types: (line,) = ax2.plot( x, value["rücklauftemperatur"], label=f"Rücklauftemperatur Gebäude {key}", color=temp_color_map(temp_color_idx % 8), linestyle="-.", linewidth=line_width, ) lines_ax2.append(line) labels_ax2.append(f"Rücklauftemperatur Gebäude {key}") temp_color_idx += 1 ax_main.set_xlabel("Jahresstunden", fontsize=label_fontsize) ax_main.set_ylabel("Wärmebedarf (kW)", fontsize=label_fontsize) ax2.set_ylabel("Temperatur (°C)", fontsize=label_fontsize) ax_main.tick_params(axis="both", labelsize=14) ax2.tick_params(axis="y", labelsize=14) # Legenden als eigene Achsen ax_legend_left.axis("off") ax_legend_right.axis("off") if lines_ax1: ncol_left = 2 if len(lines_ax1) > 18 else 1 ax_legend_left.legend( lines_ax1, labels_ax1, loc="center", fontsize=legend_fontsize, frameon=False, ncol=ncol_left ) if lines_ax2: ncol_right = 2 if len(lines_ax2) > 18 else 1 ax_legend_right.legend( lines_ax2, labels_ax2, loc="center", fontsize=legend_fontsize, frameon=False, ncol=ncol_right ) self.figure.suptitle("Gebäude Wärmebedarf & Temperaturen", fontsize=18) ax_main.grid(True, alpha=0.3) self.canvas.draw()
[docs] def show_error_message(self, title, message): """ Display error message dialog. :param title: Dialog title :type title: str :param message: Error message :type message: str """ QMessageBox.critical(self, title, message)
[docs] def show_message(self, title, message): """ Display information message dialog. :param title: Dialog title :type title: str :param message: Information message :type message: str """ QMessageBox.information(self, title, message)
[docs] class BuildingTab(QWidget): """ Main building tab widget integrating MVP components. Central interface for building data management and heat demand analysis. """
[docs] def __init__(self, folder_manager, data_manager, config_manager, parent=None): """ Initialize building tab with MVP architecture. :param folder_manager: Folder manager :type folder_manager: ProjectFolderManager :param data_manager: Data manager :type data_manager: DataManager :param config_manager: Configuration manager :type config_manager: ProjectConfigManager :param parent: Parent widget (optional) :type parent: QWidget """ super().__init__(parent) self.model = BuildingModel() self.view = BuildingTabView() self.presenter = BuildingPresenter(self.model, self.view, folder_manager, data_manager, config_manager) layout = QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.view)
if __name__ == "__main__": import sys from PyQt6.QtWidgets import QApplication app = QApplication(sys.argv) window = BuildingTabView() window.resize(1400, 900) window.show() # Simuliere das Laden einer JSON wie im Model/Presenter json_path = os.path.join( os.path.dirname(__file__), "..", "..", "project_data", "Görlitz", "Variante 1", "Lastgang", "Gebäude Lastgang.json", ) json_path = os.path.abspath(json_path) import json with open(json_path, encoding="utf-8") as f: loaded_data = json.load(f) # Filter wie im Model: nur dicts mit 'wärme' results = {k: v for k, v in loaded_data.items() if isinstance(v, dict) and "wärme" in v} # Simuliere Presenter: populate_building_combobox und plot window.populate_building_combobox(results) window.plot(results) sys.exit(app.exec())