diff --git a/example_files/Template_PVtool5_0_SAFE_2022_PV.xlsx b/example_files/Template_PVtool5_0_SAFE_2022_PV.xlsx
index f579cd0..b5c7587 100644
Binary files a/example_files/Template_PVtool5_0_SAFE_2022_PV.xlsx and b/example_files/Template_PVtool5_0_SAFE_2022_PV.xlsx differ
diff --git a/example_files/WSRL 2025 PVtool5_0_gevalideerd.xlsx b/example_files/WSRL 2025 PVtool5_0_gevalideerd.xlsx
new file mode 100644
index 0000000..72aaa74
Binary files /dev/null and b/example_files/WSRL 2025 PVtool5_0_gevalideerd.xlsx differ
diff --git a/import/shansep_pdf_export_TXT_SAFE_klei_licht_16_175_TXT_S_POP_15procent_rek.pdf b/import/shansep_pdf_export_TXT_SAFE_klei_licht_16_175_TXT_S_POP_15procent_rek.pdf
new file mode 100644
index 0000000..c4d155b
Binary files /dev/null and b/import/shansep_pdf_export_TXT_SAFE_klei_licht_16_175_TXT_S_POP_15procent_rek.pdf differ
diff --git a/main_nathan.py b/main_nathan.py
index 1533e2d..352250c 100644
--- a/main_nathan.py
+++ b/main_nathan.py
@@ -14,6 +14,8 @@
from pv_tool.imports.validation import Validation
from pv_tool.imports.import_data import Dbase
from pv_tool.cphi_analysis.c_phi_analysis import CPhiAnalyse
+from pv_tool.shansep_analysis.shansep_analysis import SHANSEP
+from pv_tool.sutabel_analysis import SUTABEL
from pv_tool.imports.import_options import *
from typing import Literal
@@ -57,42 +59,54 @@ def get_repo_root(root_search_dir: Optional[str] = None) -> str:
return repo.working_tree_dir
-def test_database_import(source: Literal['Stowa', 'PV-tool', 'Dbase'],
+def database_import_test(source: Literal['Stowa', 'PV-tool', 'Dbase'],
file_name_import: str, file_name_export: str = 'Template_PVtool5_0.xlsx', short=False, validate=False, export=False):
"""Test de database import en validatie functionaliteit."""
repo_root = Path(get_repo_root())
path_to_data = repo_root / "example_files" / file_name_import
save_test = Path(r"c:\Users\gebraadn0645\ARCADIS\103076457 - STOWA PV Tool - 05 Project execution\Deliverables\2. validatie\Test output")
- if short and not validate:
- print("Uitvoeren van korte database import test...")
- dbase = Dbase()
- dbase.import_dbase_short(source=source, source_dir=path_to_data)
- if export:
- dbase.export_dbase_to_excel(export_dir = save_test, filename=file_name_export)
- return dbase
- elif validate and not short:
- print("Uitvoeren van database import en validatie test...")
- dbase = Dbase()
- dbase.import_data(source=source, source_dir=path_to_data)
- dbase.validate_data(export_path=save_test)
- if export:
- dbase.export_dbase_to_excel(export_dir = save_test, filename=file_name_export)
- return dbase
- elif short and validate:
- print("Korte import en validatie kan niet samen worden uitgevoerd. Kies één optie.")
+ # Check if input file exists
+ if not path_to_data.exists():
+ print(f"Error: Input file not found at {path_to_data}")
return None
- else:
- print("Uitvoeren van volledige database import test...")
- # Database import
- dbase = Dbase()
- dbase.import_data(source=source, source_dir=path_to_data)
+
+ # Create save directory if it doesn't exist
+ save_test.mkdir(parents=True, exist_ok=True)
+
+ dbase = Dbase()
+ try:
+ if short and not validate:
+ print(f"Uitvoeren van korte database import test voor {path_to_data}...")
+ dbase.import_dbase_short(source=source, source_dir=path_to_data)
+ elif validate and not short:
+ print(f"Uitvoeren van database import en validatie test voor {path_to_data}...")
+ dbase.import_data(source=source, source_dir=path_to_data)
+ dbase.validate_data(export_path=save_test)
+ elif short and validate:
+ print("Korte import en validatie kan niet samen worden uitgevoerd. Kies één optie.")
+ return None
+ else:
+ print(f"Uitvoeren van volledige database import test voor {path_to_data}...")
+ dbase.import_data(source=source, source_dir=path_to_data)
+
+ # Verify database was loaded successfully
+ if dbase.dbase_df is None or dbase.dbase_df.empty:
+ print("Error: Database import failed - dataframe is None or empty")
+ return None
+
if export:
- dbase.export_dbase_to_excel(export_dir = save_test, filename=file_name_export)
+ print(f"Exporting database to {save_test / file_name_export}")
+ dbase.export_dbase_to_excel(export_dir=save_test, filename=file_name_export)
+
return dbase
+ except Exception as e:
+ print(f"Error during database import/export: {str(e)}")
+ return None
+
-def test_cphi_analysis_txt(dbase: Dbase, file_name: str = 'Template_PVtool5_0.xlsx'):
+def cphi_analysis_txt_test(dbase: Dbase, export_path: Path, export_file: str, plot_extra_dataset = None, plot_spanningspaden=False):
"""
Test een TXT C-phi analyse.
@@ -100,9 +114,11 @@ def test_cphi_analysis_txt(dbase: Dbase, file_name: str = 'Template_PVtool5_0.xl
----------
dbase : Dbase
Database instance met testdata
+ export_path : Path
+ Directory waar resultaten worden opgeslagen
+ export_file : str
+ Naam van het export database bestand
"""
- save_test = Path(r"c:\Users\gebraadn0645\ARCADIS\103076457 - STOWA PV Tool - 05 Project execution\Deliverables\2. validatie\Test output")
-
# Initialiseer analyse
analyse = CPhiAnalyse(
dbase=dbase,
@@ -111,21 +127,29 @@ def test_cphi_analysis_txt(dbase: Dbase, file_name: str = 'Template_PVtool5_0.xl
analysis_type='TXT_CPhi'
)
+ # analyse = CPhiAnalyse(
+ # dbase=dbase,
+ # investigation_groups=['TXT_SAFE_klei_licht_16_175'],
+ # effective_stress='eindsterkte',
+ # analysis_type='TXT_CPhi'
+ # )
+
# Pas instellingen toe
analyse.apply_settings(alpha=0.75)
- analyse.apply_parameters(cohesie_kar=0.0)
+ # analyse.apply_parameters(cohesie_gem=6.5, phi_kar=0.45, cohesie_kar=0.1)
+ analyse.apply_parameters(cohesie_gem=8.0, phi_kar=0.53, cohesie_kar=6.72)
# Print en exporteer resultaten
print('\nResultaten TXT C-phi analyse:')
print(analyse.print_short_results())
- analyse.add_results_to_dbase(path=str(save_test), file_name=file_name)
+ analyse.add_results_to_dbase(path=str(export_path), file_name=export_file)
# Visualisatie
- analyse.show_figure()
- analyse.save_to_pdf(path=str(save_test))
+ analyse.show_figure(plot_extra_dataset = plot_extra_dataset, plot_spanningspaden=plot_spanningspaden)
+ analyse.save_to_pdf(path=str(export_path))
-def test_cphi_analysis_dss(dbase: Dbase):
+def cphi_analysis_dss_test(dbase: Dbase, export_path: Path, export_file: str, plot_extra_dataset = None, plot_spanningspaden=False):
"""
Test een DSS C-phi analyse.
@@ -133,9 +157,11 @@ def test_cphi_analysis_dss(dbase: Dbase):
----------
dbase : Dbase
Database instance met testdata
+ export_path : Path
+ Directory waar resultaten worden opgeslagen
+ export_file : str
+ Naam van het export database bestand
"""
- save_test = Path(r"c:\Users\gebraadn0645\ARCADIS\103076457 - STOWA PV Tool - 05 Project execution\Deliverables\2. validatie\Test output")
-
# Initialiseer analyse
analyse = CPhiAnalyse(
dbase=dbase,
@@ -150,14 +176,14 @@ def test_cphi_analysis_dss(dbase: Dbase):
# Print en exporteer resultaten
print('\nResultaten DSS C-phi analyse:')
print(analyse.print_short_results())
- analyse.add_results_to_dbase(path=str(save_test))
+ analyse.add_results_to_dbase(path=str(export_path), file_name=export_file)
# Visualisatie
- analyse.show_figure()
- analyse.save_to_pdf(path=str(save_test))
+ analyse.show_figure(plot_extra_dataset = plot_extra_dataset, plot_spanningspaden=plot_spanningspaden)
+ analyse.save_to_pdf(path=str(export_path))
-def test_cphi_analysis_txt_sh(dbase: Dbase):
+def cphi_analysis_txt_sh_test(dbase: Dbase, export_path: Path, export_file: str, plot_extra_dataset = None, plot_spanningspaden=False):
"""
Test een TXT C-phi analyse volgens schematiseringshandleiding (SH).
@@ -165,9 +191,11 @@ def test_cphi_analysis_txt_sh(dbase: Dbase):
----------
dbase : Dbase
Database instance met testdata
+ export_path : Path
+ Directory waar resultaten worden opgeslagen
+ export_file : str
+ Naam van het export database bestand
"""
- save_test = Path(r"c:\Users\gebraadn0645\ARCADIS\103076457 - STOWA PV Tool - 05 Project execution\Deliverables\2. validatie\Test output")
-
# Initialiseer analyse
analyse = CPhiAnalyse(
dbase=dbase,
@@ -182,14 +210,14 @@ def test_cphi_analysis_txt_sh(dbase: Dbase):
# Print en exporteer resultaten
print('\nResultaten TXT C-phi analyse (schematiseringshandleiding):')
print(analyse.print_short_results())
- analyse.add_results_to_dbase(path=str(save_test))
+ analyse.add_results_to_dbase(path=str(export_path), file_name=export_file)
# Visualisatie
- analyse.show_figure()
- analyse.save_to_pdf(path=str(save_test))
+ analyse.show_figure(plot_extra_dataset = plot_extra_dataset, plot_spanningspaden=plot_spanningspaden)
+ analyse.save_to_pdf(path=str(export_path))
-def test_cphi_analysis_dss_sh(dbase: Dbase):
+def cphi_analysis_dss_sh_test(dbase: Dbase, export_path: Path, export_file: str, plot_extra_dataset = None, plot_spanningspaden=False):
"""
Test een DSS C-phi analyse volgens schematiseringshandleiding (SH).
@@ -197,9 +225,11 @@ def test_cphi_analysis_dss_sh(dbase: Dbase):
----------
dbase : Dbase
Database instance met testdata
+ export_path : Path
+ Directory waar resultaten worden opgeslagen
+ export_file : str
+ Naam van het export database bestand
"""
- save_test = Path(r"c:\Users\gebraadn0645\ARCADIS\103076457 - STOWA PV Tool - 05 Project execution\Deliverables\2. validatie\Test output")
-
# Initialiseer analyse
analyse = CPhiAnalyse(
dbase=dbase,
@@ -214,36 +244,148 @@ def test_cphi_analysis_dss_sh(dbase: Dbase):
# Print en exporteer resultaten
print('\nResultaten DSS C-phi analyse (schematiseringshandleiding):')
print(analyse.print_short_results())
- analyse.add_results_to_dbase(path=str(save_test))
+ analyse.add_results_to_dbase(path=str(export_path), file_name=export_file)
# Visualisatie
- analyse.show_figure()
- analyse.save_to_pdf(path=str(save_test))
+ analyse.show_figure(plot_extra_dataset = plot_extra_dataset, plot_spanningspaden=plot_spanningspaden)
+ analyse.save_to_pdf(path=str(export_path))
+
+
+# function for testing shansep analysis
+def shansep_analysis_test(dbase: Dbase, export_path: Path, export_file: str, plot_extra_dataset = None):
+ """
+ Test een SHANSEP analyse.
+
+ Parameters
+ ----------
+ dbase : Dbase
+ Database instance met testdata
+ export_path : Path
+ Directory waar resultaten worden opgeslagen
+ export_file : str
+ Naam van het export database bestand
+ """
+ analyse = SHANSEP(
+ dbase=dbase,
+ investigation_groups=['DSS_SAFE_veen'],
+ effective_stress='20% rek',
+ analysis_type='DSS_S_POP')
+
+ # analyse = SHANSEP(
+ # dbase=dbase,
+ # investigation_groups=['TXT_SAFE_klei_licht_16_175'],
+ # effective_stress='15% rek',
+ # analysis_type='TXT_S_POP')
+
+ # Pas instellingen toe
+ analyse.apply_settings(alpha=0.75)
+
+ # Print en exporteer resultaten
+ print('\nResultaten SHANSEP analyse:')
+ analyse.add_results_to_dbase(path=str(export_path), file_name=export_file)
+
+ analyse.set_parameters_handmatig(snijpunt_gem=11, s_gem=0.31, m_gem=0.9, snijpunt_kar=7, s_kar=0.28, m_kar=0.9)
+
+ # sutabel = analyse.calculate_sutabel()
+ analyse.show_figure_sv_su(plot_extra_dataset=plot_extra_dataset)
+ analyse.show_figure_ln_ocr_ln_s(plot_extra_dataset=plot_extra_dataset)
+ analyse.show_figure_sv_su_nc(plot_extra_dataset=plot_extra_dataset)
+ pdf_path = analyse.save_to_pdf(path=str(export_path))
+ analyse.save_total_to_excel(path=str(export_path))
+
+
+def sutabel_analysis_test(dbase: Dbase, export_path: Path, export_file: str, plot_extra_dataset = None):
+ """
+ Test een SUTABEL analyse.
+
+ Parameters
+ ----------
+ dbase : Dbase
+ Database instance met testdata
+ export_path : Path
+ Directory waar resultaten worden opgeslagen
+ export_file : str
+ Naam van het export database bestand
+ """
+ sutabel = SUTABEL(
+ dbase=dbase,
+ analysis_type='DSS_su_tabel',
+ investigation_groups=['DSS_SAFE_veen'],
+ effective_stress='20% rek'
+ )
+
+ # sutabel = SUTABEL(
+ # dbase=dbase,
+ # analysis_type='TXT_su_tabel',
+ # investigation_groups=['TXT_SAFE_klei_licht_16_175'],
+ # effective_stress='15% rek'
+ # )
+
+ sutabel.apply_settings(alpha=0.75)
+
+ sutabel.set_manual_parameters(a2_kar=0.683, a1_kar=0.489, vc_fit_kar=0.1)
+
+ # Visualize (analysis runs automatically)
+ sutabel.show_figure_ln_sv_ln_su_sutabel(plot_extra_dataset=plot_extra_dataset)
+ sutabel.show_figure_sv_su_sutabel(plot_extra_dataset=plot_extra_dataset)
+
+ # Export (analysis runs automatically if needed)
+ sutabel.add_results_to_dbase(str(export_path), export_file)
+ sutabel.save_to_pdf(str(export_path))
if __name__ == "__main__":
# Test database import
- source = 'PV-tool' # Opties: 'Stowa', 'PV-tool', 'Dbase'
- import_name = 'WSRL 2025 Proevenverzameling_tool_v4.2n_gevalideerd_nieuw4.xlsm'
- export_name = 'Template_PVtool5_0_WSRL_2025_PV.xlsx'
- import_name2 = export_name # TODO eventueel: import naam kan nu niet export naam zijn, dan kan die niet de layout aanpassen. Moet nog worden aangepast in de toekomst.
+ source = 'Dbase' # Opties: 'Stowa', 'PV-tool', 'Dbase'
+ import_name = 'Template_PVtool5_0_SAFE_2022_PV.xlsx'
+ # import_name = 'WSRL 2025 PVtool5_0_gevalideerd.xlsx'
+ export_name = 'Template_PVtool5_0_SAFE_2022_PV_aangepast.xlsx'
+ export_dir = Path(r"c:\Users\gebraadn0645\ARCADIS\103076457 - STOWA PV Tool - 05 Project execution\Deliverables\2. validatie\Test output")
+ export_dir.mkdir(parents=True, exist_ok=True)
+
print("Start van de tests...\n")
- dbase = test_database_import(source=source, file_name_import=import_name, file_name_export=export_name, short=False, validate=False, export=True)
+ print(f"Using source: {source}")
+ print(f"Import file: {import_name}")
+ print(f"Export file: {export_name}")
+ print(f"Export directory: {export_dir}")
+
+ dbase = database_import_test(
+ source=source,
+ file_name_import=import_name,
+ file_name_export=export_name,
+ short=True,
+ validate=False,
+ export=True
+ )
+
+ if dbase is None or dbase.dbase_df is None:
+ print("ERROR: Database import gefaald!")
+ exit(1)
+
+ plot_extra_dataset = None
+ plot_spanningspaden = False
# Test verschillende analyses
print("\nUitvoeren van verschillende test cases...")
- print("\n1. TXT C-phi analyse test")
- # file_name = 'Template_PVtool5_0_SAFE_2022_PV.xlsx'
- # test_cphi_analysis_txt(dbase, file_name=file_name)
-
- # print("\n2. DSS C-phi analyse test")
- # test_cphi_analysis_dss(dbase)
- #
- # print("\n3. TXT C-phi analyse test (schematiseringshandleiding)")
- # test_cphi_analysis_txt_sh(dbase)
- #
- # print("\n4. DSS C-phi analyse test (schematiseringshandleiding)")
- # test_cphi_analysis_dss_sh(dbase)
+ print("\nTXT C-phi analyse test")
+ cphi_analysis_txt_test(dbase, export_dir, export_name, plot_extra_dataset=plot_extra_dataset, plot_spanningspaden=plot_spanningspaden)
+
+ print("\nDSS C-phi analyse test")
+ cphi_analysis_dss_test(dbase, export_dir, export_name, plot_extra_dataset=plot_extra_dataset, plot_spanningspaden=plot_spanningspaden)
+
+ print("\nTXT C-phi analyse (schematiseringshandleiding) test")
+ cphi_analysis_txt_sh_test(dbase, export_dir, export_name, plot_extra_dataset=plot_extra_dataset, plot_spanningspaden=plot_spanningspaden)
+
+ print("\nDSS C-phi analyse (schematiseringshandleiding) test")
+ cphi_analysis_dss_sh_test(dbase, export_dir, export_name, plot_extra_dataset=plot_extra_dataset, plot_spanningspaden=plot_spanningspaden)
+
+ plot_extra_dataset = ['TXT_SAFE_klei_zwaar']
+
+ print("\nTXT SHANSEP analyse test")
+ shansep_analysis_test(dbase, export_dir, export_name, plot_extra_dataset=plot_extra_dataset)
+
+ print("\nSUTABEL analyse test")
+ sutabel_analysis_test(dbase, export_dir, export_name, plot_extra_dataset=plot_extra_dataset)
print("\nAlle tests zijn voltooid!")
diff --git a/pv_tool/cphi_analysis/c_phi_analysis.py b/pv_tool/cphi_analysis/c_phi_analysis.py
index a6cafbb..d3124bd 100644
--- a/pv_tool/cphi_analysis/c_phi_analysis.py
+++ b/pv_tool/cphi_analysis/c_phi_analysis.py
@@ -35,7 +35,7 @@ class CPhiAnalyse:
Klasse voor het uitvoeren van c-phi analyses op grondmonsters.
Ondersteunt zowel triaxiaal (TXT) als direct simple shear (DSS) testen,
- en kan zowel reguliere c-phi als shansep (SH) analyses uitvoeren.
+ en kan zowel reguliere c-phi als schematiseringshandleiding (SH) analyses uitvoeren.
"""
def __init__(self, dbase: Dbase,
@@ -122,6 +122,8 @@ def __init__(self, dbase: Dbase,
self.figure = go.Figure()
self.show_title: Optional[bool] = True
+ # ========= Instelling en Data Ophalen Methodes ==========
+
def get_cphi_data(self):
"""
Filtert de database op basis van analysetype en proefgroepen.
@@ -133,7 +135,7 @@ def get_cphi_data(self):
if self.analysis_type in ['TXT_CPhi', 'TXT_SH']:
self.cphi_analyses_data_df = self.dbase_df[self.dbase_df['ALG__TRIAXIAAL']]
self.cphi_analyses_data_df = self.cphi_analyses_data_df[self.cphi_analyses_data_df['PV_NAAM'].isin(
- self.investigation_groups)] # TODO should have selectie veranderen in de dbase en daarmee verder
+ self.investigation_groups)]
self.calc_watergehalte_gem = calc_watergehalte_gem(self)
self.calc_watergehalte_sd = calc_watergehalte_sd(self)
self.calc_vgwnat_gem = calc_vgwnat_gem(self)
@@ -141,10 +143,17 @@ def get_cphi_data(self):
self.total_cphi_analyses_data_df = self.cphi_analyses_data_df
self.cphi_analyses_data_df = self.cphi_analyses_data_df[TEXTUAL_NAMES.get(self.effective_stress, [])]
+ # Valideer of er data overblijft na filtering
+ if self.cphi_analyses_data_df.empty:
+ raise ValueError(f"Geen data gevonden na filtering op investigation_groups {self.investigation_groups} "
+ f"en effective_stress '{self.effective_stress}' voor analyse type '{self.analysis_type}'")
+
+ print(f"Data na filtering: {len(self.cphi_analyses_data_df)} rijen gevonden")
+
elif self.analysis_type in ['DSS_CPhi', 'DSS_SH']:
self.cphi_analyses_data_df = self.dbase_df[self.dbase_df['ALG__DSS']]
self.cphi_analyses_data_df = self.cphi_analyses_data_df[self.cphi_analyses_data_df['PV_NAAM'].isin(
- self.investigation_groups)] # TODO should have selectie veranderen in de dbase en daarmee verder
+ self.investigation_groups)]
self.calc_watergehalte_gem = calc_watergehalte_gem(self)
self.calc_watergehalte_sd = calc_watergehalte_sd(self)
self.calc_vgwnat_gem = calc_vgwnat_gem(self)
@@ -152,6 +161,13 @@ def get_cphi_data(self):
self.total_cphi_analyses_data_df = self.cphi_analyses_data_df
self.cphi_analyses_data_df = self.cphi_analyses_data_df[TEXTUAL_NAMES_DSS.get(self.effective_stress, [])]
+ # Valideer of er data overblijft na filtering
+ if self.cphi_analyses_data_df.empty:
+ raise ValueError(f"Geen data gevonden na filtering op investigation_groups {self.investigation_groups} "
+ f"en effective_stress '{self.effective_stress}' voor analyse type '{self.analysis_type}'")
+
+ print(f"Data na filtering: {len(self.cphi_analyses_data_df)} rijen gevonden")
+
self.cphi_analyses_data_df.columns = NEW_COLUMN_NAMES
def apply_settings(self, alpha: Optional[float] = None,
@@ -309,21 +325,23 @@ def plot_spanningspaden(self):
else:
raise ValueError("Geen geldige data gevonden voor de spanningspaden.")
- def get_previous_results(self, path: str):
+ def get_previous_results(self, path: str, file_name: str):
"""
Zoekt naar eerdere analyseresultaten in een Excel-bestand.
Parameters
----------
- path : str
+ path: str
Pad naar de map waar het Excel-bestand staat
+ file_name: str
+ Naam van het Excel-bestand
Returns
-------
DataFrame of None
DataFrame met eerdere resultaten als deze gevonden zijn, anders None
"""
- file_name = 'Template_PVtool5_0.xlsx'
+
file_path = f"{path}/{file_name}"
try:
@@ -333,9 +351,9 @@ def get_previous_results(self, path: str):
raise FileNotFoundError("Er is geen dbase aanwezig onder de naam Template_PVtool5_0.xlsx")
try:
- results_df = read_excel(file_path, sheet_name='Resultaten')
+ results_df = read_excel(file_path, sheet_name='Resultaten c-phi')
except ValueError:
- print("Er is geen tabblad 'Resultaten' aanwezig in het Excel-bestand.")
+ print("Er is geen tabblad 'Resultaten c-phi' aanwezig in het Excel-bestand.")
return None
filtered_df = results_df[
@@ -352,6 +370,8 @@ def get_previous_results(self, path: str):
return latest_entry
+ # ========= Analyse Methodes ==========
+
def expand_analysis_df(self):
"""
Berekent afgeleide parameters voor de c-phi analyse.
@@ -371,7 +391,7 @@ def expand_analysis_df(self):
def expand_analysis_df_sh(self):
"""
- Berekent afgeleide parameters voor de shansep analyse.
+ Berekent afgeleide parameters voor de schematiseringshandleiding analyse.
Voegt kolommen toe aan het dataframe met berekende waarden voor
tan(alpha) en ln(tan(alpha)).
@@ -406,6 +426,8 @@ def expand_analysis_df_corrected(self):
calculate_s_ty_ondergrens_correctie_c(self)
calculate_kappa_2_ondergrens_correctie_c(self)
+ # ========= Resultaten Methodes ==========
+
def result_values(self):
"""
Berekent de definitieve resultaten van de c-phi analyse: gemiddelde, karakteristieke en rekenwaarden voor
@@ -436,7 +458,7 @@ def result_values(self):
def result_values_sh(self):
"""
- Berekent de definitieve resultaten van de shansep analyse:
+ Berekent de definitieve resultaten van de c-phi schematiseringshandleiding analyse:
gemiddelde, karakteristieke en rekenwaarden voor phi,
inclusief standaarddeviatie.
@@ -470,13 +492,15 @@ def _run(self):
def _run_sh(self):
"""
- Voert de volledige shansep analyse uit in de juiste volgorde:
+ Voert de volledige c-phi schematiseringshandleiding analyse uit in de juiste volgorde:
data ophalen, parameters berekenen en resultaten bepalen.
"""
self.get_cphi_data()
self.expand_analysis_df_sh()
self.result_values_sh()
+ # ========== Visualisatie Methodes ==========
+
def set_figure(self, plot_extra_dataset: Optional[List] = None, plot_spanningspaden: bool = False):
"""
Maakt een visualisatie van de analyseresultaten.
@@ -555,11 +579,13 @@ def print_short_results(self):
analyse_output_df['cohesie [kPa]'] = [self.c_gem, self.c_kar, self.c_d, self.st_dev_c]
return analyse_output_df
+ # ========== Export Methodes ==========
+
def add_results_to_dbase(self, path, file_name: str = 'Template_PVtool5_0.xlsx'):
"""
Voegt analyseresultaten toe aan de database export.
- Voegt de resultaten toe aan een tabblad 'Resultaten' in de Template_PVtool5_0.xlsx.
+ Voegt de resultaten toe aan een tabblad 'Resultaten c-phi' in de Template_PVtool5_0.xlsx.
Als het tabblad al bestaat wordt het aangevuld, anders wordt het aangemaakt.
Parameters
@@ -632,30 +658,30 @@ def add_results_to_dbase(self, path, file_name: str = 'Template_PVtool5_0.xlsx')
workbook = load_workbook(file_path)
- if 'Resultaten' in workbook.sheetnames:
- print('Tabblad resultaten in dbase excel bestaat al en wordt aangevuld')
- df_existing = read_excel(file_path, sheet_name='Resultaten')
+ if 'Resultaten c-phi' in workbook.sheetnames:
+ print('Tabblad Resultaten c-phi in dbase excel bestaat al en wordt aangevuld')
+ df_existing = read_excel(file_path, sheet_name='Resultaten c-phi')
# Filter out empty rows and ensure consistent types before concatenation
df_existing = df_existing.dropna(how='all')
new_row_df = DataFrame([new_row], columns=df_existing.columns)
df_updated = concat([df_existing, new_row_df], ignore_index=True)
else:
- print('Tabblad resultaten in dbase excel bestaat nog niet en wordt aangemaakt')
+ print('Tabblad Resultaten c-phi in dbase excel bestaat nog niet en wordt aangemaakt')
df_updated = DataFrame([new_row], columns=expected_columns)
# Write data to Excel
with ExcelWriter(file_path, engine='openpyxl', mode='a', if_sheet_exists='replace') as writer:
- df_updated.to_excel(writer, sheet_name='Resultaten', index=False)
+ df_updated.to_excel(writer, sheet_name='Resultaten c-phi', index=False)
# Formatting
num_columns = df_updated.shape[1]
num_rows = df_updated.shape[0]
format_excel_sheet(
file_path=file_path,
- sheet_name='Resultaten',
+ sheet_name='Resultaten c-phi',
num_columns=num_columns,
num_rows=num_rows,
- table_name='ResultatenTable',
+ table_name='Resultaten_CPHI_Table',
index=False # Changed from True to False to match the to_excel call
)
diff --git a/pv_tool/cphi_analysis/calc_parameters.py b/pv_tool/cphi_analysis/calc_parameters.py
index bdf4fac..518bed4 100644
--- a/pv_tool/cphi_analysis/calc_parameters.py
+++ b/pv_tool/cphi_analysis/calc_parameters.py
@@ -8,11 +8,10 @@
if TYPE_CHECKING:
from pv_tool.cphi_analysis.c_phi_analysis import CPhiAnalyse
-from pv_tool.cphi_analysis.variables import (count_s, sum_s, sum_t, e_a2, a2_kar, a1_kar, t_n_2, t_n_2_sh,
+from pv_tool.cphi_analysis.variables import (count_s, sum_s, sum_t, e_a2, a2_kar, a1_kar, t_n_2_sh,
gem_ln_tan_a_sh, std_ln_tan_a_sh,
a2_kar_gecorrigeerd, a1_kar_gecorrigeerd, helling_gecor, var_tan_phi_gem,
- var_tan_phi_kar, var_tan_phi_kar_sh, var_a2_gem_sh, var_a2_boven_sh,
- var_a2_onder_sh)
+ var_tan_phi_kar, var_tan_phi_kar_sh)
def calc_watergehalte_gem(self: CPhiAnalyse):
"""Berekent het gemiddelde watergehalte van de geselecteerde monsters [%]."""
@@ -126,7 +125,7 @@ def calc_c_gem(self: CPhiAnalyse):
elif self.analysis_type == 'DSS_CPhi':
return float(coh_gem)
else:
- print('WARNING: analysis type SH does not include cohesion')
+ print('WAARSCHUWING: analysetype SH bevat geen cohesie')
def calc_phi_kar(self: CPhiAnalyse):
@@ -152,7 +151,7 @@ def calc_c_kar(self: CPhiAnalyse):
elif self.analysis_type == 'DSS_CPhi':
return float(coh_kar)
else:
- print('WARNING: analysis type SH does not include cohesion')
+ print('WAARSCHUWING: analysetype SH bevat geen cohesie')
def calc_phi_d(self: CPhiAnalyse):
@@ -222,7 +221,7 @@ def calc_tan_phi_kar(self: CPhiAnalyse):
elif self.analysis_type == 'DSS_CPhi':
return float(phi_kar)
else:
- print('WARNING: tan phi kar for analysis type in SH is calculated with the function calc_tan_phi_kar_sh')
+ print('WAARSCHUWING: tan phi kar voor analysetype SH wordt berekend met de functie calc_tan_phi_kar_sh')
def calc_tan_phi_kar_sh(self: CPhiAnalyse):
"""Berekent de karakteristieke tan(phi) voor shansep analyses."""
@@ -231,4 +230,4 @@ def calc_tan_phi_kar_sh(self: CPhiAnalyse):
elif self.analysis_type == 'DSS_SH':
return float(calc_a2_phi_kar_onder_sh(self))
else:
- print('WARNING: tan phi kar for analysis type in CPhi is calculated with the function calc_tan_phi_kar')
+ print('WAARSCHUWING: tan phi kar voor analysetype CPhi wordt berekend met de functie calc_tan_phi_kar')
diff --git a/pv_tool/cphi_analysis/expand_analysis_df.py b/pv_tool/cphi_analysis/expand_analysis_df.py
index c8f0706..e0b10fb 100644
--- a/pv_tool/cphi_analysis/expand_analysis_df.py
+++ b/pv_tool/cphi_analysis/expand_analysis_df.py
@@ -1,6 +1,5 @@
from __future__ import annotations
from typing import TYPE_CHECKING
-import numpy as np
if TYPE_CHECKING:
from pv_tool.cphi_analysis.c_phi_analysis import CPhiAnalyse
@@ -8,14 +7,12 @@
def calculate_tan_a(self: CPhiAnalyse):
"""Berekent de tangens van hoek a voor alle rijen in de DataFrame."""
- # Create a copy to avoid chained assignment
df = self.cphi_analyses_data_df.copy()
df.loc[:, 'tan(a)'] = df['T'] / df['S\'']
self.cphi_analyses_data_df = df
def calculate_ln_tan_a(self: CPhiAnalyse):
"""Berekent de natuurlijke logaritme van tan(a) voor alle rijen in de DataFrame."""
- # Create a copy to avoid chained assignment
df = self.cphi_analyses_data_df.copy()
df.loc[:, 'LN(tan(a))'] = df['tan(a)'].apply(lambda x: np.log(x) if x is not None and x > 0 else "")
self.cphi_analyses_data_df = df
diff --git a/pv_tool/cphi_analysis/globals.py b/pv_tool/cphi_analysis/globals.py
index 5c52626..891bfda 100644
--- a/pv_tool/cphi_analysis/globals.py
+++ b/pv_tool/cphi_analysis/globals.py
@@ -35,7 +35,6 @@
# -------------------------------------- DSS ---------------------------------------
CONSOLIDATIE_COLUMNS_DSS = ['PV_NAAM', 'DSS_EFF_VERT_SPANNING_EINDE_CONSOLIDATIE', 'DSS_T_CONSOLIDATIE']
-# TODO die laatste kolomnaam klopt niet - check bij Leo welke kolom erbij hoort
TWO_PERC_COLUMNS_DSS = ['PV_NAAM', 'DSS_S_2%', 'DSS_T_2%']
diff --git a/pv_tool/cphi_analysis/save_and_export.py b/pv_tool/cphi_analysis/save_and_export.py
index e77baf0..05ef37c 100644
--- a/pv_tool/cphi_analysis/save_and_export.py
+++ b/pv_tool/cphi_analysis/save_and_export.py
@@ -5,7 +5,7 @@
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_LEFT
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, LongTable
-
+import plotly.graph_objects as go
if TYPE_CHECKING:
from pv_tool.cphi_analysis.c_phi_analysis import CPhiAnalyse
@@ -244,7 +244,8 @@ def save_to_pdf(self: "CPhiAnalyse", path: str) -> str:
fig_path = f"{path}/temp_plot.png"
if not hasattr(self, 'figure') or len(self.figure.data) == 0:
self.show_title = False
- self.show_figure()
+ self.figure = go.Figure()
+ self.set_figure()
self.show_title = True
fig_width = 1280
diff --git a/pv_tool/cphi_analysis/variables.py b/pv_tool/cphi_analysis/variables.py
index 3aea9af..3c9de38 100644
--- a/pv_tool/cphi_analysis/variables.py
+++ b/pv_tool/cphi_analysis/variables.py
@@ -168,8 +168,30 @@ def helling_gecor(self: CPhiAnalyse):
if self.cohesie_gem_handmatig is not None:
x_values = self.cphi_analyses_data_df['S\'']
y_values = self.cphi_analyses_data_df['correctie_t']
- x = np.array(x_values)[:, np.newaxis]
- helling, _, _, _ = np.linalg.lstsq(x, np.array(y_values))
+
+ # Valideer of er voldoende data is
+ if len(x_values) == 0 or len(y_values) == 0:
+ raise ValueError(f"Onvoldoende data voor helling berekening. Aantal datapunten: {len(x_values)}")
+
+ if len(x_values) != len(y_values):
+ raise ValueError(f"Dimensie mismatch: x_values heeft {len(x_values)} elementen, y_values heeft {len(y_values)} elementen")
+
+ # Controleer op NaN waarden
+ x_clean = x_values.dropna()
+ y_clean = y_values.dropna()
+
+ if len(x_clean) < 2 or len(y_clean) < 2:
+ raise ValueError(f"Onvoldoende geldige datapunten voor regressie. Geldige x: {len(x_clean)}, geldige y: {len(y_clean)}")
+
+ # Zorg ervoor dat we de juiste indices gebruiken
+ valid_indices = x_values.notna() & y_values.notna()
+ if valid_indices.sum() < 2:
+ raise ValueError(f"Onvoldoende geldige datapunt paren voor regressie: {valid_indices.sum()}")
+
+ x_array = np.array(x_values[valid_indices])[:, np.newaxis]
+ y_array = np.array(y_values[valid_indices])
+
+ helling, residuals, rank, singular_values = np.linalg.lstsq(x_array, y_array, rcond=None)
return float(helling[0])
else:
helling = sum_s_ty(self) / sum_s_tt(self)
diff --git a/pv_tool/imports/add_ana_columns.py b/pv_tool/imports/add_ana_columns.py
index 39b612f..8a1de66 100644
--- a/pv_tool/imports/add_ana_columns.py
+++ b/pv_tool/imports/add_ana_columns.py
@@ -213,6 +213,59 @@ def calculate_row(row):
else:
self.dbase_df['OCR_TXT'] = None
+# def calc_ocr_txt(self: Dbase):
+# """
+# Deze functie berekent de OCR van de triaxiaalproeven volgens de formule:
+# MAX(TXT_SS_OCR, (MAX(ANA_GRENSSPANNING_INCL_CORRECTIE, ANA_TXT_MAX_VERTICALE_CONSOLIDATIE_SPANNING) /
+# (TXT_SS_S''_EIND_CONSOLIDATIE + TXT_SS_T_EIND_CONSOLIDATIE)))
+# """
+# def calculate_row(row):
+# try:
+# # Get the first part: TXT_SS_OCR
+# ocr_txt = row.get('TXT_SS_OCR', None)
+# rownr = row.get('ALG__REGEL', None)
+# if rownr == 1201:
+# print('ocr_txt:', ocr_txt)
+# # Only proceed with calculation if any of these tests are True
+# if any([row.get('ALG__CRS', False),
+# row.get('ALG__SAMENDRUKKING', False),
+# row.get('ALG__DSS', False),
+# row.get('ALG__TRIAXIAAL', False)]):
+# if rownr == 1201:
+# print('proceeding with calculation for row 1201')
+#
+# # Get maximum of grensspanning and max verticale consolidatie spanning
+# grensspanning = row.get('ANA_GRENSSPANNING_INCL_CORRECTIE', None)
+# max_vert_consol = row.get('ANA_TXT_MAX_VERTICALE_CONSOLIDATIE_SPANNING', None)
+# numerator = max(filter(None, [grensspanning, max_vert_consol])) if any(x is not None for x in [grensspanning, max_vert_consol]) else None
+#
+# # Get denominator components
+# s_eind = row.get("TXT_SS_S'_EIND_CONSOLIDATIE", 0)
+# t_eind = row.get('TXT_SS_T_EIND_CONSOLIDATIE', 0)
+# denominator = s_eind + t_eind if (s_eind is not None and t_eind is not None) else None
+#
+# if rownr == 1201:
+# print('proceeding with calculation for row 1201')
+# print('numerator:', numerator)
+# print('denominator:', denominator)
+#
+# # Calculate the ratio if possible
+# if numerator is not None and denominator is not None and denominator != 0:
+# calculated_ocr = numerator / denominator
+#
+# # Return maximum of TXT_SS_OCR and calculated OCR
+# if ocr_txt is not None:
+# return max(ocr_txt, calculated_ocr)
+# return calculated_ocr
+#
+# # If no calculation possible but TXT_SS_OCR exists, return that
+# return ocr_txt if ocr_txt is not None else None
+#
+# except Exception:
+# return None
+#
+# self.dbase_df['OCR_TXT'] = self.dbase_df.apply(calculate_row, axis=1)
+
def calc_ocr_dss(self: Dbase):
"""Deze functie berekent de OCR van de DSS-proeven."""
diff --git a/pv_tool/imports/import_data.py b/pv_tool/imports/import_data.py
index be58608..a404416 100644
--- a/pv_tool/imports/import_data.py
+++ b/pv_tool/imports/import_data.py
@@ -104,14 +104,14 @@ def export_dbase_to_excel(self, export_dir: Path, filename: str = 'Template_PVto
export_df = self.dbase_df[final_columns].copy()
# rond waarden af voor consistentie
- cols_to_round = ['ANA_TERREINSPANNING', 'ANA_TXT_MAX_VERTICALE_CONSOLIDATIE_SPANNING',
- 'ANA_DSS_MAX_CONSOLIDATIE_SPANNING', 'ANA_GRENSSPANNING_PROEF',
- 'ANA_POP_VELD', 'ANA_POP_VELD_GEMIDDELD', 'ANA_GRENSSPANNING_VOORSTEL',
- 'ANA_GRENSSPANNING_REKEN', 'OCR_TXT', 'OCR_DSS']
- numeric_columns = export_df.select_dtypes(include=['float64', 'int64']).columns
- for col in cols_to_round:
- if col in numeric_columns:
- export_df[col] = export_df[col].round(2)
+ # cols_to_round = ['ANA_TERREINSPANNING', 'ANA_TXT_MAX_VERTICALE_CONSOLIDATIE_SPANNING',
+ # 'ANA_DSS_MAX_CONSOLIDATIE_SPANNING', 'ANA_GRENSSPANNING_PROEF',
+ # 'ANA_POP_VELD', 'ANA_POP_VELD_GEMIDDELD', 'ANA_GRENSSPANNING_VOORSTEL',
+ # 'ANA_GRENSSPANNING_REKEN', 'OCR_TXT', 'OCR_DSS']
+ # numeric_columns = export_df.select_dtypes(include=['float64', 'int64']).columns
+ # for col in cols_to_round:
+ # if col in numeric_columns:
+ # export_df[col] = export_df[col].round(2)
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
print(f"Excel sheet Dbase5_0 wordt weggeschreven op {timestamp}")
@@ -136,7 +136,7 @@ def export_dbase_to_excel(self, export_dir: Path, filename: str = 'Template_PVto
float_format="%.6f" # Use consistent float format
)
- print(f"Excel file exported to: {export_path}")
+ print(f"Excel bestand geëxporteerd naar: {export_path}")
# Formatting
num_columns = export_df.shape[1]
diff --git a/pv_tool/imports/validation.py b/pv_tool/imports/validation.py
index 8b72553..ad78581 100644
--- a/pv_tool/imports/validation.py
+++ b/pv_tool/imports/validation.py
@@ -107,7 +107,7 @@ def validate_with_schema(self, category, schema: Schema):
missing_columns = [col for col in schema_columns if col not in df.columns]
if missing_columns:
- print(f"Missing columns in category '{category}': {', '.join(missing_columns)}")
+ print(f"Ontbrekende kolommen in categorie '{category}': {', '.join(missing_columns)}")
available_columns = [col for col in schema_columns if col in df.columns]
data_to_validate = df[available_columns]
@@ -248,7 +248,7 @@ def validation_log(self, export_path: Path, critical: Optional[bool] = True):
index=True
)
except Exception as format_error:
- print(f"Warning: Could not format sheet {sheet_name}: {str(format_error)}")
+ print(f"Waarschuwing: Kon sheet {sheet_name} niet formatteren: {str(format_error)}")
# Continue with other sheets even if one fails to format
except Exception as e:
diff --git a/pv_tool/shansep_analysis/calc_parameters.py b/pv_tool/shansep_analysis/calc_parameters.py
index 3b7ed4b..dafe407 100644
--- a/pv_tool/shansep_analysis/calc_parameters.py
+++ b/pv_tool/shansep_analysis/calc_parameters.py
@@ -1,31 +1,234 @@
-import math
+from pandas import DataFrame
import numpy as np
-from scipy.stats import linregress, norm
-from typing import TYPE_CHECKING
-
-if TYPE_CHECKING:
- from pv_tool.shansep_analysis.shansep_analysis import SHANSEP
-
-def calc_watergehalte_gem(self: SHANSEP):
- """Geeft gemiddelde watergehalte bij de geselecteerde pvnaam"""
- column_name = self.shansep_data_df.filter(like='WATERGEHALTE_VOOR').columns[0]
- watergehalte = self.shansep_data_df[column_name]
- return watergehalte.mean()
-
-def calc_watergehalte_sd(self: SHANSEP):
- """Geeft standaard deviatie watergehalte bij de geselecteerde pvnaam"""
- column_name = self.shansep_data_df.filter(like='WATERGEHALTE_VOOR').columns[0]
- watergehalte = self.shansep_data_df[column_name]
- return watergehalte.std()
-
-def calc_vgwnat_gem(self: SHANSEP):
- """Geeft gemiddelde nat volumegewicht bij de geselecteerde pvnaam [[[VOLUMEGEWICHT_NAT]]]"""
- column_name = self.shansep_data_df.filter(like='VOLUMEGEWICHT_NAT').columns[0]
- vgwnat = self.shansep_data_df[column_name]
- return vgwnat.mean()
-
-def calc_vgwnat_sd(self: SHANSEP):
- """Geeft standaard deviatie nat volumegewicht bij de geselecteerde pvnaam"""
- column_name = self.shansep_data_df.filter(like='VOLUMEGEWICHT_NAT').columns[0]
- vgwnat = self.shansep_data_df[column_name]
- return vgwnat.std()
\ No newline at end of file
+
+
+def calc_watergehalte_gem_txt(df: DataFrame) -> float:
+ """
+ Berekent het gemiddelde watergehalte voor TXT analyses.
+
+ Parameters
+ ----------
+ df : DataFrame
+ DataFrame met TXT data
+
+ Returns
+ -------
+ float
+ Gemiddelde watergehalte of NaN als geen geldige data beschikbaar is
+ """
+ try:
+ if 'TXT_SS_WATERGEHALTE_VOOR' not in df.columns:
+ return np.nan
+
+ watergehalte = df['TXT_SS_WATERGEHALTE_VOOR']
+
+ numeric_values = watergehalte.dropna()
+ numeric_values = numeric_values[numeric_values != '']
+ numeric_values = numeric_values.apply(lambda x: float(x) if isinstance(x, (int, float, str)) and str(x).replace('.', '').replace('-', '').isdigit() else np.nan).dropna()
+
+ return numeric_values.mean() if len(numeric_values) > 0 else np.nan
+ except Exception:
+ return np.nan
+
+
+def calc_watergehalte_sd_txt(df: DataFrame) -> float:
+ """
+ Berekent de standaarddeviatie van het watergehalte voor TXT analyses.
+
+ Parameters
+ ----------
+ df : DataFrame
+ DataFrame met TXT data
+
+ Returns
+ -------
+ float
+ Standaarddeviatie watergehalte of NaN als geen geldige data beschikbaar is
+ """
+ try:
+ if 'TXT_SS_WATERGEHALTE_VOOR' not in df.columns:
+ return np.nan
+
+ watergehalte = df['TXT_SS_WATERGEHALTE_VOOR']
+
+ numeric_values = watergehalte.dropna()
+ numeric_values = numeric_values[numeric_values != '']
+ numeric_values = numeric_values.apply(lambda x: float(x) if isinstance(x, (int, float, str)) and str(x).replace('.', '').replace('-', '').isdigit() else np.nan).dropna()
+
+ return numeric_values.std() if len(numeric_values) > 1 else np.nan
+ except Exception:
+ return np.nan
+
+
+def calc_vgwnat_gem_txt(df: DataFrame) -> float:
+ """
+ Berekent het gemiddelde volumegewicht nat voor TXT analyses.
+
+ Parameters
+ ----------
+ df : DataFrame
+ DataFrame met TXT data
+
+ Returns
+ -------
+ float
+ Gemiddelde volumegewicht nat of NaN als geen geldige data beschikbaar is
+ """
+ try:
+ if 'TXT_SS_VOLUMEGEWICHT_NAT' not in df.columns:
+ return np.nan
+
+ vgwnat = df['TXT_SS_VOLUMEGEWICHT_NAT']
+
+ numeric_values = vgwnat.dropna()
+ numeric_values = numeric_values[numeric_values != '']
+ numeric_values = numeric_values.apply(lambda x: float(x) if isinstance(x, (int, float, str)) and str(x).replace('.', '').replace('-', '').isdigit() else np.nan).dropna()
+
+ return numeric_values.mean() if len(numeric_values) > 0 else np.nan
+ except Exception:
+ return np.nan
+
+
+def calc_vgwnat_sd_txt(df: DataFrame) -> float:
+ """
+ Berekent de standaarddeviatie van het volumegewicht nat voor TXT analyses.
+
+ Parameters
+ ----------
+ df : DataFrame
+ DataFrame met TXT data
+
+ Returns
+ -------
+ float
+ Standaarddeviatie volumegewicht nat of NaN als geen geldige data beschikbaar is
+ """
+ try:
+ if 'TXT_SS_VOLUMEGEWICHT_NAT' not in df.columns:
+ return np.nan
+
+ vgwnat = df['TXT_SS_VOLUMEGEWICHT_NAT']
+
+ numeric_values = vgwnat.dropna()
+ numeric_values = numeric_values[numeric_values != '']
+ numeric_values = numeric_values.apply(lambda x: float(x) if isinstance(x, (int, float, str)) and str(x).replace('.', '').replace('-', '').isdigit() else np.nan).dropna()
+
+ return numeric_values.std() if len(numeric_values) > 1 else np.nan
+ except Exception:
+ return np.nan
+
+
+def calc_watergehalte_gem_dss(df: DataFrame) -> float:
+ """
+ Berekent het gemiddelde watergehalte voor DSS analyses.
+
+ Parameters
+ ----------
+ df : DataFrame
+ DataFrame met DSS data
+
+ Returns
+ -------
+ float
+ Gemiddelde watergehalte of NaN als geen geldige data beschikbaar is
+ """
+ try:
+ if 'DSS_WATERGEHALTE_VOOR' not in df.columns:
+ return np.nan
+
+ watergehalte = df['DSS_WATERGEHALTE_VOOR']
+
+ numeric_values = watergehalte.dropna()
+ numeric_values = numeric_values[numeric_values != '']
+ numeric_values = numeric_values.apply(lambda x: float(x) if isinstance(x, (int, float, str)) and str(x).replace('.', '').replace('-', '').isdigit() else np.nan).dropna()
+
+ return numeric_values.mean() if len(numeric_values) > 0 else np.nan
+ except Exception:
+ return np.nan
+
+
+def calc_watergehalte_sd_dss(df: DataFrame) -> float:
+ """
+ Berekent de standaarddeviatie van het watergehalte voor DSS analyses.
+
+ Parameters
+ ----------
+ df : DataFrame
+ DataFrame met DSS data
+
+ Returns
+ -------
+ float
+ Standaarddeviatie watergehalte of NaN als geen geldige data beschikbaar is
+ """
+ try:
+ if 'DSS_WATERGEHALTE_VOOR' not in df.columns:
+ return np.nan
+
+ watergehalte = df['DSS_WATERGEHALTE_VOOR']
+
+ numeric_values = watergehalte.dropna()
+ numeric_values = numeric_values[numeric_values != '']
+ numeric_values = numeric_values.apply(lambda x: float(x) if isinstance(x, (int, float, str)) and str(x).replace('.', '').replace('-', '').isdigit() else np.nan).dropna()
+
+ return numeric_values.std() if len(numeric_values) > 1 else np.nan
+ except Exception:
+ return np.nan
+
+
+def calc_vgwnat_gem_dss(df: DataFrame) -> float:
+ """
+ Berekent het gemiddelde volumegewicht nat voor DSS analyses.
+
+ Parameters
+ ----------
+ df : DataFrame
+ DataFrame met DSS data
+
+ Returns
+ -------
+ float
+ Gemiddelde volumegewicht nat of NaN als geen geldige data beschikbaar is
+ """
+ try:
+ if 'DSS_VOLUMEGEWICHT_NAT' not in df.columns:
+ return np.nan
+
+ vgwnat = df['DSS_VOLUMEGEWICHT_NAT']
+
+ numeric_values = vgwnat.dropna()
+ numeric_values = numeric_values[numeric_values != '']
+ numeric_values = numeric_values.apply(lambda x: float(x) if isinstance(x, (int, float, str)) and str(x).replace('.', '').replace('-', '').isdigit() else np.nan).dropna()
+
+ return numeric_values.mean() if len(numeric_values) > 0 else np.nan
+ except Exception:
+ return np.nan
+
+
+def calc_vgwnat_sd_dss(df: DataFrame) -> float:
+ """
+ Berekent de standaarddeviatie van het volumegewicht nat voor DSS analyses.
+
+ Parameters
+ ----------
+ df : DataFrame
+ DataFrame met DSS data
+
+ Returns
+ -------
+ float
+ Standaarddeviatie volumegewicht nat of NaN als geen geldige data beschikbaar is
+ """
+ try:
+ if 'DSS_VOLUMEGEWICHT_NAT' not in df.columns:
+ return np.nan
+
+ vgwnat = df['DSS_VOLUMEGEWICHT_NAT']
+
+ numeric_values = vgwnat.dropna()
+ numeric_values = numeric_values[numeric_values != '']
+ numeric_values = numeric_values.apply(lambda x: float(x) if isinstance(x, (int, float, str)) and str(x).replace('.', '').replace('-', '').isdigit() else np.nan).dropna()
+
+ return numeric_values.std() if len(numeric_values) > 1 else np.nan
+ except Exception:
+ return np.nan
diff --git a/pv_tool/shansep_analysis/expand_analysis.py b/pv_tool/shansep_analysis/expand_analysis.py
index 6e25447..eadd7d0 100644
--- a/pv_tool/shansep_analysis/expand_analysis.py
+++ b/pv_tool/shansep_analysis/expand_analysis.py
@@ -3,25 +3,33 @@
if TYPE_CHECKING:
from pv_tool.shansep_analysis.shansep_analysis import SHANSEP
-from pv_tool.shansep_analysis.variables import *
+from pv_tool.shansep_analysis.variables import (count_sv_oc, sum_sv_oc, e_a1_oc, e_a2_oc,
+ t_n_2_oc, sigma_a1_oc, sigma_a2_oc, rho_a1_a2_oc,
+ sum_chi_2_oc, count_s_eff_oc, sum_s_eff_oc,
+ count_ln_ocr_nc_oc, sum_ln_ocr_nc_oc, e_a1_nc_oc, e_a2_nc_oc,
+ t_n_2_nc_oc, sigma_a1_nc_oc, sigma_a2_nc_oc, rho_a1_a2_nc_oc,
+ sum_chi_2_nc_oc, count_s_eff_nc_oc, sum_s_eff_nc_oc,
+ a1_kar_oc, a2_kar_oc,
+ a1_kar_nc_oc, a2_kar_nc_oc)
+import numpy as np
# -------------------------- algemeen ---------------------------------------
def calculate_ln_ocr(self: SHANSEP):
- self.shansep_data_df['LN(OCR)'] = (
+ self.shansep_data_df.loc[:, 'LN(OCR)'] = (
self.shansep_data_df['OCR'].apply
(lambda x: np.log(x) if x is not None and x > 0 else ""))
def calculate_sv_spop(self: SHANSEP):
- self.shansep_data_df['S'] = self.shansep_data_df['Su'] / self.shansep_data_df['S\'v']
+ self.shansep_data_df.loc[:, 'S'] = self.shansep_data_df['Su'] / self.shansep_data_df['S\'v']
def calculate_ln_sv_spop(self: SHANSEP):
- self.shansep_data_df['LN(su/svc)'] = (
+ self.shansep_data_df.loc[:, 'LN(su/svc)'] = (
self.shansep_data_df['S'].apply
(lambda x: np.log(x) if x is not None and x > 0 else ""))
def calculate_pop(self: SHANSEP):
- self.shansep_data_df['POP'] = ((self.shansep_data_df['terreinspanning'] *
+ self.shansep_data_df.loc[:, 'POP'] = ((self.shansep_data_df['terreinspanning'] *
self.shansep_data_df['OCR']) -
self.shansep_data_df['terreinspanning'])
@@ -38,9 +46,9 @@ def calculate_sv_ty_oc(self: SHANSEP):
df['s_ty'] = (df['S\'v'] - mean_s) * df['Su']
self.shansep_data_df_oc = df
-def calculate_kappa_2_oc(self: SHANSEP):
+def calculate_chi_2_oc(self: SHANSEP):
formule = (self.shansep_data_df_oc['Su'] - e_a1_oc(self) - e_a2_oc(self) * self.shansep_data_df_oc['S\'v']) ** 2
- self.shansep_data_df_oc['kappa_2'] = formule
+ self.shansep_data_df_oc['chi_2'] = formule
def s_min_oc(self: SHANSEP):
return self.shansep_data_df_oc['S\'v'].min()
@@ -64,7 +72,7 @@ def calculate_5pr_ondergrens_oc(self: SHANSEP):
e_a2_oc(self) * self.shansep_data_df_oc['s\''] - t_n_2_oc(self) *
(sigma_a1_oc(self) ** 2 + self.shansep_data_df_oc['s\''] ** 2 * sigma_a2_oc(self) ** 2 +
2 * rho_a1_a2_oc(self) * self.shansep_data_df_oc['s\''] * sigma_a1_oc(self) * sigma_a2_oc(self) +
- (1.0 - self.alpha) * (sum_kappa_2_oc(self) / (count_sv_oc(self) - 2))) ** 0.5
+ (1.0 - self.alpha) * (sum_chi_2_oc(self) / (count_sv_oc(self) - 2))) ** 0.5
)
self.shansep_data_df_oc['5_pr_ondergrens'] = formule
@@ -75,7 +83,7 @@ def calculate_5pr_bovengrens_oc(self: SHANSEP):
e_a2_oc(self) * self.shansep_data_df_oc['s\''] + t_n_2_oc(self) *
(sigma_a1_oc(self) ** 2 + self.shansep_data_df_oc['s\''] ** 2 * sigma_a2_oc(self) ** 2 +
2 * rho_a1_a2_oc(self) * self.shansep_data_df_oc['s\''] * sigma_a1_oc(self) * sigma_a2_oc(self) +
- (1 - self.alpha) * (sum_kappa_2_oc(self) / (count_sv_oc(self) - 2))) ** 0.5
+ (1 - self.alpha) * (sum_chi_2_oc(self) / (count_sv_oc(self) - 2))) ** 0.5
)
self.shansep_data_df_oc['5_pr_bovengrens'] = formule
@@ -91,41 +99,39 @@ def calculate_sv_ty_ondergrens_oc(self: SHANSEP):
self.shansep_data_df_oc['s_ty_ondergrens'] = formule
-def calculate_kappa_2_ondergrens_oc(self: SHANSEP):
+def calculate_chi_2_ondergrens_oc(self: SHANSEP):
formule = (self.shansep_data_df_oc['5_pr_ondergrens'] - a1_kar_oc(self) -
a2_kar_oc(self) * self.shansep_data_df_oc['s\'']) ** 2
- self.shansep_data_df_oc['kappa_2_ondergrens'] = formule
+ self.shansep_data_df_oc['chi_2_ondergrens'] = formule
# -------------------------- NC en OC ---------------------------------------
def calculate_sv_tt_nc_oc(self: SHANSEP):
df = self.shansep_data_df_nc_oc.copy()
- df['s_tt'] = (df['LN(OCR)'] - sum_sv_nc_oc(self) / count_sv_nc_oc(self)) ** 2
+ df['s_tt'] = (df['LN(OCR)'] - sum_ln_ocr_nc_oc(self) / count_ln_ocr_nc_oc(self)) ** 2
self.shansep_data_df_nc_oc = df
def calculate_sv_ty_nc_oc(self: SHANSEP):
df = self.shansep_data_df_nc_oc.copy()
- mean_s = sum_sv_nc_oc(self) / count_sv_nc_oc(self)
+ mean_s = sum_ln_ocr_nc_oc(self) / count_ln_ocr_nc_oc(self)
df['s_ty'] = (df['LN(OCR)'] - mean_s) * df['LN(su/svc)']
self.shansep_data_df_nc_oc = df
-# TODO vanaf hier verder met aanpassen namen variabelen en functies
-
-def calculate_kappa_2_nc_oc(self: SHANSEP):
- formule = (self.shansep_data_df_nc_oc['Su'] - e_a1_nc_oc(self) - e_a2_nc_oc(self) * self.shansep_data_df_nc_oc['S\'v']) ** 2
- self.shansep_data_df_nc_oc['kappa_2'] = formule
+def calculate_chi_2_nc_oc(self: SHANSEP):
+ formule = (self.shansep_data_df_nc_oc['LN(su/svc)'] - e_a1_nc_oc(self) - e_a2_nc_oc(self) * self.shansep_data_df_nc_oc['LN(OCR)']) ** 2
+ self.shansep_data_df_nc_oc['chi_2'] = formule
def s_min_nc_oc(self: SHANSEP):
- return self.shansep_data_df_nc_oc['S\'v'].min()
+ return self.shansep_data_df_nc_oc['LN(OCR)'].min()
def s_max_nc_oc(self: SHANSEP):
- return self.shansep_data_df_nc_oc['S\'v'].max()
+ return self.shansep_data_df_nc_oc['LN(OCR)'].max()
def calculate_sv_eff_nc_oc(self: SHANSEP):
"""Berekent s' waarden met gelijke intervallen tussen min en max."""
lijst = [s_min_nc_oc(self)]
- formule = (s_max_nc_oc(self) - s_min_nc_oc(self)) / (count_sv_nc_oc(self) - 1)
+ formule = (s_max_nc_oc(self) - s_min_nc_oc(self)) / (count_ln_ocr_nc_oc(self) - 1)
aantal_waarden = len(self.shansep_data_df_nc_oc)
for i in range(1, aantal_waarden):
nieuwe_waarde = lijst[i - 1] + formule
@@ -138,8 +144,9 @@ def calculate_5pr_ondergrens_nc_oc(self: SHANSEP):
e_a2_nc_oc(self) * self.shansep_data_df_nc_oc['s\''] - t_n_2_nc_oc(self) *
(sigma_a1_nc_oc(self) ** 2 + self.shansep_data_df_nc_oc['s\''] ** 2 * sigma_a2_nc_oc(self) ** 2 +
2 * rho_a1_a2_nc_oc(self) * self.shansep_data_df_nc_oc['s\''] * sigma_a1_nc_oc(self) * sigma_a2_nc_oc(self) +
- (1.0 - self.alpha) * (sum_kappa_2_nc_oc(self) / (count_sv_nc_oc(self) - 2))) ** 0.5
+ (1.0 - self.alpha) * (sum_chi_2_nc_oc(self) / (count_ln_ocr_nc_oc(self) - 2))) ** 0.5
)
+
self.shansep_data_df_nc_oc['5_pr_ondergrens'] = formule
@@ -149,7 +156,7 @@ def calculate_5pr_bovengrens_nc_oc(self: SHANSEP):
e_a2_nc_oc(self) * self.shansep_data_df_nc_oc['s\''] + t_n_2_nc_oc(self) *
(sigma_a1_nc_oc(self) ** 2 + self.shansep_data_df_nc_oc['s\''] ** 2 * sigma_a2_nc_oc(self) ** 2 +
2 * rho_a1_a2_nc_oc(self) * self.shansep_data_df_nc_oc['s\''] * sigma_a1_nc_oc(self) * sigma_a2_nc_oc(self) +
- (1 - self.alpha) * (sum_kappa_2_nc_oc(self) / (count_sv_nc_oc(self) - 2))) ** 0.5
+ (1 - self.alpha) * (sum_chi_2_nc_oc(self) / (count_ln_ocr_nc_oc(self) - 2))) ** 0.5
)
self.shansep_data_df_nc_oc['5_pr_bovengrens'] = formule
@@ -165,7 +172,7 @@ def calculate_sv_ty_ondergrens_nc_oc(self: SHANSEP):
self.shansep_data_df_nc_oc['s_ty_ondergrens'] = formule
-def calculate_kappa_2_ondergrens_nc_oc(self: SHANSEP):
+def calculate_chi_2_ondergrens_nc_oc(self: SHANSEP):
formule = (self.shansep_data_df_nc_oc['5_pr_ondergrens'] - a1_kar_nc_oc(self) -
a2_kar_nc_oc(self) * self.shansep_data_df_nc_oc['s\'']) ** 2
- self.shansep_data_df_nc_oc['kappa_2_ondergrens'] = formule
\ No newline at end of file
+ self.shansep_data_df_nc_oc['chi_2_ondergrens'] = formule
\ No newline at end of file
diff --git a/pv_tool/shansep_analysis/globals.py b/pv_tool/shansep_analysis/globals.py
index 6ccb175..785e968 100644
--- a/pv_tool/shansep_analysis/globals.py
+++ b/pv_tool/shansep_analysis/globals.py
@@ -1,15 +1,15 @@
#------------------------------------SHANSEP----------------------------------------
#------------------------------------TXT-----------------------------------------
-TWO_PERC_COLUMNS = ['PV_NAAM', 'TXT_SS_T_2%', 'ANA_TXT_MAX_VERTICALE_CONSOLIDATIE_SPANNING', 'ANA_TXT_CONSOLIDATIE_TYPE', 'ANA_OCR_TXT_MONSTER', 'ANA_TERREINSPANNING']
+TWO_PERC_COLUMNS = ['PV_NAAM', 'ALG__REGEL', 'ANA_TXT_MAX_VERTICALE_CONSOLIDATIE_SPANNING', 'TXT_SS_T_2%', 'ANA_TXT_CONSOLIDATIE_TYPE_REKEN', 'OCR_TXT', 'ANA_TERREINSPANNING']
-FIVE_PERC_COLUMNS = ['PV_NAAM', 'TXT_SS_T_5%', 'ANA_TXT_MAX_VERTICALE_CONSOLIDATIE_SPANNING', 'ANA_TXT_CONSOLIDATIE_TYPE', 'ANA_OCR_TXT_MONSTER', 'ANA_TERREINSPANNING']
+FIVE_PERC_COLUMNS = ['PV_NAAM', 'ALG__REGEL', 'ANA_TXT_MAX_VERTICALE_CONSOLIDATIE_SPANNING','TXT_SS_T_5%', 'ANA_TXT_CONSOLIDATIE_TYPE_REKEN', 'OCR_TXT', 'ANA_TERREINSPANNING']
-FIFTEEN_PERC_COLUMNS = ['PV_NAAM', 'TXT_SS_T_15%', 'ANA_TXT_MAX_VERTICALE_CONSOLIDATIE_SPANNING', 'ANA_TXT_CONSOLIDATIE_TYPE', 'ANA_OCR_TXT_MONSTER', 'ANA_TERREINSPANNING']
+FIFTEEN_PERC_COLUMNS = ['PV_NAAM', 'ALG__REGEL', 'ANA_TXT_MAX_VERTICALE_CONSOLIDATIE_SPANNING', 'TXT_SS_T_15%', 'ANA_TXT_CONSOLIDATIE_TYPE_REKEN', 'OCR_TXT', 'ANA_TERREINSPANNING']
-PIEKSTERKTE_COLUMNS = ['PV_NAAM', 'TXT_SS_T_BIJ_T_PIEK', 'ANA_TXT_MAX_VERTICALE_CONSOLIDATIE_SPANNING', 'ANA_TXT_CONSOLIDATIE_TYPE', 'ANA_OCR_TXT_MONSTER', 'ANA_TERREINSPANNING']
+PIEKSTERKTE_COLUMNS = ['PV_NAAM', 'ALG__REGEL', 'ANA_TXT_MAX_VERTICALE_CONSOLIDATIE_SPANNING', 'TXT_SS_T_BIJ_T_PIEK', 'ANA_TXT_CONSOLIDATIE_TYPE_REKEN', 'OCR_TXT', 'ANA_TERREINSPANNING']
-EINDSTERKTE_COLUMNS = ['PV_NAAM', 'TXT_SS_T_BIJ_T_EIND', 'ANA_TXT_MAX_VERTICALE_CONSOLIDATIE_SPANNING', 'ANA_TXT_CONSOLIDATIE_TYPE', 'ANA_OCR_TXT_MONSTER', 'ANA_TERREINSPANNING']
+EINDSTERKTE_COLUMNS = ['PV_NAAM', 'ALG__REGEL', 'ANA_TXT_MAX_VERTICALE_CONSOLIDATIE_SPANNING', 'TXT_SS_T_BIJ_T_EIND', 'ANA_TXT_CONSOLIDATIE_TYPE_REKEN', 'OCR_TXT', 'ANA_TERREINSPANNING']
TEXTUAL_NAMES = {
'2% rek': TWO_PERC_COLUMNS,
@@ -19,23 +19,23 @@
'eindsterkte': EINDSTERKTE_COLUMNS,
}
-NEW_COLUMN_NAMES = ['PV_NAAM', 'S\'v', 'Su', 'consolidatietype', 'OCR', 'terreinspanning']
+NEW_COLUMN_NAMES = ['PV_NAAM', 'regel nr', 'S\'v', 'Su', 'consolidatietype', 'OCR', 'terreinspanning']
# -------------------------------------- DSS ---------------------------------------
-TWO_PERC_COLUMNS_DSS = ['PV_NAAM', 'ANA_DSS_MAX_CONSOLIDATIE_SPANNING', 'DSS_T_2%', 'ANA_TXT_CONSOLIDATIE_TYPE', 'ANA_OCR_TXT_MONSTER', 'ANA_TERREINSPANNING']
+TWO_PERC_COLUMNS_DSS = ['PV_NAAM', 'ALG__REGEL', 'ANA_DSS_MAX_CONSOLIDATIE_SPANNING', 'DSS_T_2%', 'ANA_DSS_CONSOLIDATIE_TYPE_REKEN', 'OCR_DSS', 'ANA_TERREINSPANNING']
-FIVE_PERC_COLUMNS_DSS = ['PV_NAAM', 'DSS_T_5%', 'ANA_DSS_MAX_CONSOLIDATIE_SPANNING', 'ANA_TXT_CONSOLIDATIE_TYPE', 'ANA_OCR_TXT_MONSTER', 'ANA_TERREINSPANNING']
+FIVE_PERC_COLUMNS_DSS = ['PV_NAAM', 'ALG__REGEL', 'ANA_DSS_MAX_CONSOLIDATIE_SPANNING', 'DSS_T_5%', 'ANA_DSS_CONSOLIDATIE_TYPE_REKEN', 'OCR_DSS', 'ANA_TERREINSPANNING']
-TEN_PERC_COLUMNS_DSS = ['PV_NAAM', 'DSS_T_10%', 'ANA_DSS_MAX_CONSOLIDATIE_SPANNING', 'ANA_TXT_CONSOLIDATIE_TYPE', 'ANA_OCR_TXT_MONSTER', 'ANA_TERREINSPANNING']
+TEN_PERC_COLUMNS_DSS = ['PV_NAAM', 'ALG__REGEL', 'ANA_DSS_MAX_CONSOLIDATIE_SPANNING', 'DSS_T_10%', 'ANA_DSS_CONSOLIDATIE_TYPE_REKEN', 'OCR_DSS', 'ANA_TERREINSPANNING']
-FIFTEEN_PERC_COLUMNS_DSS = ['PV_NAAM', 'DSS_T_15%', 'ANA_DSS_MAX_CONSOLIDATIE_SPANNING', 'ANA_TXT_CONSOLIDATIE_TYPE', 'ANA_OCR_TXT_MONSTER', 'ANA_TERREINSPANNING']
+FIFTEEN_PERC_COLUMNS_DSS = ['PV_NAAM', 'ALG__REGEL', 'ANA_DSS_MAX_CONSOLIDATIE_SPANNING', 'DSS_T_15%', 'ANA_DSS_CONSOLIDATIE_TYPE_REKEN', 'OCR_DSS', 'ANA_TERREINSPANNING']
-TWENTY_PERC_COLUMNS_DSS = ['PV_NAAM', 'DSS_T_20%', 'ANA_DSS_MAX_CONSOLIDATIE_SPANNING', 'ANA_TXT_CONSOLIDATIE_TYPE', 'ANA_OCR_TXT_MONSTER', 'ANA_TERREINSPANNING']
+TWENTY_PERC_COLUMNS_DSS = ['PV_NAAM', 'ALG__REGEL', 'ANA_DSS_MAX_CONSOLIDATIE_SPANNING', 'DSS_T_20%', 'ANA_DSS_CONSOLIDATIE_TYPE_REKEN', 'OCR_DSS', 'ANA_TERREINSPANNING']
-PIEKSTERKTE_COLUMNS_DSS = ['PV_NAAM', 'DSS_T_BIJ_T_MAX', 'ANA_DSS_MAX_CONSOLIDATIE_SPANNING', 'ANA_TXT_CONSOLIDATIE_TYPE', 'ANA_OCR_TXT_MONSTER', 'ANA_TERREINSPANNING']
+PIEKSTERKTE_COLUMNS_DSS = ['PV_NAAM', 'ALG__REGEL', 'ANA_DSS_MAX_CONSOLIDATIE_SPANNING', 'DSS_T_BIJ_T_MAX', 'ANA_DSS_CONSOLIDATIE_TYPE_REKEN', 'OCR_DSS', 'ANA_TERREINSPANNING']
-EINDSTERKTE_COLUMNS_DSS = ['PV_NAAM', 'DSS_T_BIJ_T_EIND', 'ANA_DSS_MAX_CONSOLIDATIE_SPANNING', 'ANA_TXT_CONSOLIDATIE_TYPE', 'ANA_OCR_TXT_MONSTER', 'ANA_TERREINSPANNING']
+EINDSTERKTE_COLUMNS_DSS = ['PV_NAAM', 'ALG__REGEL', 'ANA_DSS_MAX_CONSOLIDATIE_SPANNING', 'DSS_T_BIJ_T_EIND', 'ANA_DSS_CONSOLIDATIE_TYPE_REKEN', 'OCR_DSS', 'ANA_TERREINSPANNING']
TEXTUAL_NAMES_DSS = {
'2% rek': TWO_PERC_COLUMNS_DSS,
diff --git a/pv_tool/shansep_analysis/save_and_export.py b/pv_tool/shansep_analysis/save_and_export.py
new file mode 100644
index 0000000..f4bd66a
--- /dev/null
+++ b/pv_tool/shansep_analysis/save_and_export.py
@@ -0,0 +1,907 @@
+from typing import TYPE_CHECKING, List
+from pandas import ExcelWriter, concat, DataFrame, read_excel
+from datetime import datetime
+from openpyxl import load_workbook
+from reportlab.lib.pagesizes import A4, landscape
+from reportlab.lib import colors
+from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
+from reportlab.lib.enums import TA_LEFT
+from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, LongTable
+from pv_tool.imports.excel_utils import format_excel_sheet
+import plotly.graph_objects as go
+import numpy as np
+
+try:
+ from PIL import Image as PILImage
+ PIL_AVAILABLE = True
+except ImportError:
+ PIL_AVAILABLE = False
+ print("Waarschuwing: PIL (Pillow) is niet beschikbaar. Figuren kunnen niet aan PDF worden toegevoegd.")
+
+if TYPE_CHECKING:
+ from pv_tool.shansep_analysis.shansep_analysis import SHANSEP
+
+
+
+def add_results_to_dbase(self: "SHANSEP", path: str, file_name: str = 'Template_PVtool5_0.xlsx'):
+ """
+ Voegt de SHANSEP analyseresultaten toe aan de database Excel-bestand.
+
+ Parameters
+ ----------
+ self : SHANSEP
+ Instantie van de SHANSEP analyse klasse
+ path : str
+ Pad naar de map waar het Excel-bestand staat
+ file_name : str
+ Naam van het Excel-bestand
+
+ Returns
+ -------
+ DataFrame
+ Bijgewerkte DataFrame met alle resultaten
+ """
+ file_path = f"{path}/{file_name}"
+
+ # Run analysis to get results
+ df_gem, df_kar = self.get_result_values_shansep()
+
+ # Expected columns structure voor SHANSEP resultaten
+ expected_columns = [
+ 'PVNAAM', 'PV_REK', 'PV_TYPE_PROEF', 'PV_ANALYSE', 'PV_RESULTAAT_ID', 'PV_TYPEVERZAMELING',
+ 'PV_A1_SNIJPUNT_YAS_GEM [-]', 'PV_A2_S_GEM [-]', 'PV_m_GEM [-]', 'PV_POP_GEM [kPa]',
+ 'PV_A1_SNIJPUNT_YAS_KAR [-]', 'PV_A2_S_KAR [-]', 'PV_m_KAR [-]', 'PV_POP_KAR [kPa]',
+ 'PV_S_SD_DSTAB [-]', 'PV_m_SD_DSTAB [-]', 'PV_POP_SD_DSTAB [-]',
+ 'PV_VGWNAT_GEM [kN/m3]', 'PV_VGWNAT_SD [kN/m3]', 'PV_WATERGEHALTE_GEM', 'PV_WATERGEHALTE_SD',
+ 'Timestamp'
+ ]
+
+ new_row = {
+ 'PVNAAM': self.investigation_groups[0],
+ 'PV_REK': self.effective_stress,
+ 'PV_TYPE_PROEF': self.analysis_type.split('_')[0],
+ 'PV_ANALYSE': '_'.join(self.analysis_type.split('_')[1:]),
+ 'PV_RESULTAAT_ID': f"{self.investigation_groups[0]}_{self.effective_stress}_{self.analysis_type}",
+ 'PV_TYPEVERZAMELING': self.alpha,
+ 'PV_A1_SNIJPUNT_YAS_GEM [-]': round(self.snijpunt_gem_handmatig, 3) if self.snijpunt_gem_handmatig is not None else None,
+ 'PV_A2_S_GEM [-]': round(self.s_gem_handmatig, 3) if self.s_gem_handmatig is not None else None,
+ 'PV_m_GEM [-]': round(self.m_gem_handmatig, 3) if self.m_gem_handmatig is not None else None,
+ 'PV_POP_GEM [kPa]': round(self.pop_gem_handmatig, 3) if self.pop_gem_handmatig is not None else None,
+ 'PV_A1_SNIJPUNT_YAS_KAR [-]': round(self.snijpunt_kar_handmatig, 3) if self.snijpunt_kar_handmatig is not None else None,
+ 'PV_A2_S_KAR [-]': round(self.s_kar_handmatig, 3) if self.s_kar_handmatig is not None else None,
+ 'PV_m_KAR [-]': round(self.m_kar_handmatig, 3) if self.m_kar_handmatig is not None else None,
+ 'PV_POP_KAR [kPa]': round(self.pop_kar_handmatig, 3) if self.pop_kar_handmatig is not None else None,
+ 'PV_S_SD_DSTAB [-]': round(self.st_dev_s_handmatig, 3) if self.st_dev_s_handmatig is not None else None,
+ 'PV_m_SD_DSTAB [-]': round(self.st_dev_m_handmatig, 3) if self.st_dev_s_handmatig is not None else None,
+ 'PV_POP_SD_DSTAB [-]': round(self.st_dev_pop_handmatig, 3) if self.st_dev_pop_handmatig is not None else None,
+ 'PV_VGWNAT_GEM [kN/m3]': round(self.calc_vgwnat_gem, 3) if self.calc_vgwnat_gem is not None else None,
+ 'PV_VGWNAT_SD [kN/m3]': round(self.calc_vgwnat_sd, 3) if self.calc_vgwnat_sd is not None else None,
+ 'PV_WATERGEHALTE_GEM': round(self.calc_watergehalte_gem, 3) if self.calc_watergehalte_gem is not None else None,
+ 'PV_WATERGEHALTE_SD': round(self.calc_watergehalte_sd, 3) if self.calc_watergehalte_sd is not None else None,
+ 'Timestamp': datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+ }
+
+ workbook = load_workbook(file_path)
+
+ if 'Resultaten SHANSEP' in workbook.sheetnames:
+ print('Tabblad resultaten SHANSEP in dbase excel bestaat al en wordt aangevuld')
+ df_existing = read_excel(file_path, sheet_name='Resultaten SHANSEP')
+ # Filter out empty rows and ensure consistent types before concatenation
+ df_existing = df_existing.dropna(how='all')
+ # Ensure column headers are strings
+ df_existing.columns = df_existing.columns.astype(str)
+ new_row_df = DataFrame([new_row], columns=df_existing.columns)
+ df_updated = concat([df_existing, new_row_df], ignore_index=True)
+ else:
+ print('Tabblad resultaten SHANSEP in dbase excel bestaat nog niet en wordt aangemaakt')
+ df_updated = DataFrame([new_row], columns=expected_columns)
+
+ # Ensure all column headers are strings
+ df_updated.columns = df_updated.columns.astype(str)
+
+ # Write data to Excel
+ with ExcelWriter(file_path, engine='openpyxl', mode='a', if_sheet_exists='replace') as writer:
+ df_updated.to_excel(writer, sheet_name='Resultaten SHANSEP', index=False)
+
+ # Formatting
+ num_columns = df_updated.shape[1]
+ num_rows = df_updated.shape[0]
+ format_excel_sheet(
+ file_path=file_path,
+ sheet_name='Resultaten SHANSEP',
+ num_columns=num_columns,
+ num_rows=num_rows,
+ table_name='ResultatenSHANSEPTable',
+ index=False
+ )
+
+ return df_updated
+
+
+def save_total_to_excel(self: "SHANSEP", path: str):
+ """
+ Exporteert alle SHANSEP analysegegevens naar Excel.
+
+ Slaat de volledige dataset met alle berekende kolommen op in een Excel bestand.
+ De bestandsnaam wordt automatisch gegenereerd op basis van de analyse-instellingen.
+
+ Parameters
+ ----------
+ path : str
+ Map locatie waar het Excel-bestand moet worden opgeslagen
+ self : SHANSEP
+ Instantie van de SHANSEP klasse
+ """
+
+ # Pas de effective stress naam aan zodat het weggeschreven kan worden in de bestandsnaam
+ effective_stress = str(self.effective_stress).replace('%', 'procent_')
+ effective_stress = str(effective_stress).replace(' ', '')
+
+ # Exporteer onder de juiste naam
+ file_name = f"shansep_export_{self.investigation_groups[0]}_{self.analysis_type}_{effective_stress}.xlsx"
+ file_path = f"{path}/{file_name}"
+
+ # Run analysis to ensure data is available
+ self._run_shansep()
+
+ # Get results
+ df_gem, df_kar = self.get_result_values_shansep()
+
+ # Ensure all column headers are strings
+ if self.shansep_data_df_oc is not None:
+ self.shansep_data_df_oc.columns = self.shansep_data_df_oc.columns.astype(str)
+ if self.shansep_data_df_nc_oc is not None:
+ self.shansep_data_df_nc_oc.columns = self.shansep_data_df_nc_oc.columns.astype(str)
+ if hasattr(self, 'sutabel') and self.sutabel is not None:
+ self.sutabel.columns = self.sutabel.columns.astype(str)
+
+ # Ensure result DataFrames have string columns and index
+ df_gem.columns = df_gem.columns.astype(str)
+ df_kar.columns = df_kar.columns.astype(str)
+
+ # Ensure index is also string type
+ df_gem.index = df_gem.index.astype(str)
+ df_kar.index = df_kar.index.astype(str)
+
+ # Set index names to avoid Excel adding 'Column1' header
+ df_gem.index.name = 'analyse'
+ df_kar.index.name = 'analyse'
+
+ # Write all data to Excel
+ with ExcelWriter(file_path, engine='openpyxl') as writer:
+ # Main analysis data
+ if self.shansep_data_df_oc is not None:
+ self.shansep_data_df_oc.to_excel(writer, sheet_name='Analyse Data OC', index=False)
+ if self.shansep_data_df_nc_oc is not None:
+ self.shansep_data_df_nc_oc.to_excel(writer, sheet_name='Analyse Data OC en NC', index=False)
+
+ # Results - clean data before export
+ df_gem_export = df_gem.copy()
+ df_kar_export = df_kar.copy()
+
+ # Replace None values with empty string to avoid Excel issues
+ df_gem_export = df_gem_export.fillna('')
+ df_kar_export = df_kar_export.fillna('')
+
+ df_gem_export.to_excel(writer, sheet_name='Resultaten Gemiddeld', index=True)
+ df_kar_export.to_excel(writer, sheet_name='Resultaten Karakteristiek', index=True)
+
+ # Su tabel if available
+ if hasattr(self, 'sutabel') and self.sutabel is not None:
+ self.sutabel.to_excel(writer, sheet_name='Su Tabel', index=False)
+
+ # format the sheets
+ if self.shansep_data_df_oc is not None:
+ format_excel_sheet(
+ file_path=file_path,
+ sheet_name='Analyse Data OC',
+ num_columns=self.shansep_data_df_oc.shape[1],
+ num_rows=self.shansep_data_df_oc.shape[0],
+ table_name='AnalyseDataOCTable',
+ index=False
+ )
+ if self.shansep_data_df_nc_oc is not None:
+ format_excel_sheet(
+ file_path=file_path,
+ sheet_name='Analyse Data OC en NC',
+ num_columns=self.shansep_data_df_nc_oc.shape[1],
+ num_rows=self.shansep_data_df_nc_oc.shape[0],
+ table_name='AnalyseDataNC_OCTable',
+ index=False
+ )
+ format_excel_sheet(
+ file_path=file_path,
+ sheet_name='Resultaten Gemiddeld',
+ num_columns=df_gem.shape[1], # +1 for index
+ num_rows=df_gem.shape[0],
+ table_name='ResultatenGemiddeldTable',
+ index=True
+ )
+ format_excel_sheet(
+ file_path=file_path,
+ sheet_name='Resultaten Karakteristiek',
+ num_columns=df_kar.shape[1], # +1 for index
+ num_rows=df_kar.shape[0],
+ table_name='ResultatenKarakteristiekTable',
+ index=True
+ )
+ if self.sutabel is not None:
+ format_excel_sheet(
+ file_path=file_path,
+ sheet_name='Su Tabel',
+ num_columns=self.sutabel.shape[1],
+ num_rows=self.sutabel.shape[0],
+ table_name='SuTabelTable',
+ index=False
+ )
+
+ print(f"SHANSEP Excel export voltooid: {file_path}")
+
+
+def _df_to_table_with_index(df, index_name='Index'):
+ """
+ Zet een DataFrame om naar een lijst voor gebruik in een PDF tabel.
+
+ Parameters
+ ----------
+ df : DataFrame
+ De DataFrame die moet worden omgezet
+ index_name : str, optioneel
+ Naam voor de index kolom (default='Index')
+
+ Returns
+ -------
+ list
+ Lijst met header en data rijen voor een PDF tabel
+ """
+ header = [df.index.name or index_name] + df.columns.tolist()
+ data = [[idx] + row.tolist() for idx, row in df.iterrows()]
+ return [header] + data
+
+
+def _create_input_table(self: "SHANSEP") -> Table:
+ """
+ Maakt een tabel met de invoerselectie informatie voor SHANSEP analyse.
+
+ Returns
+ -------
+ Table
+ ReportLab tabel object met de invoerselectie informatie
+ """
+ if self.shansep_data_df_nc_oc_unsorted is None:
+ return Table([['Geen invoerdata beschikbaar']], hAlign='LEFT')
+
+ # Selecteer relevante kolommen
+ columns_base = ['PV_NAAM', 'BORING_POSITIE', 'MONSTER_NIVEAU_NAP_VANAF', 'MONSTER_NIVEAU_NAP_TOT',
+ 'ANA_TERREINSPANNING', 'ANA_GRENSSPANNING_REKEN', 'ANA_POP_VELD']
+
+ if self.analysis_type.startswith('TXT'):
+ columns_extra = ['TXT_SS_VOLUMEGEWICHT_NAT', 'TXT_SS_VOLUMEGEWICHT_DRG', 'TXT_SS_WATERGEHALTE_VOOR']
+ else:
+ columns_extra = ['DSS_VOLUMEGEWICHT_NAT', 'DSS_VOLUMEGEWICHT_DRG', 'DSS_WATERGEHALTE_VOOR']
+
+ # Check welke kolommen bestaan in de dataframe
+ available_columns = [col for col in columns_base + columns_extra if col in self.total_shansep_data_df.columns]
+
+ if not available_columns:
+ return Table([['Geen invoerdata kolommen beschikbaar']], hAlign='LEFT')
+
+ table_df = self.total_shansep_data_df[available_columns].copy()
+
+ # Add additional columns from shansep_data_df_nc_oc if available and data is properly sorted
+ additional_columns = ['S\'v', 'Su', 'consolidatietype', 'OCR']
+ if (hasattr(self, 'shansep_data_df_nc_oc_unsorted') and
+ self.shansep_data_df_nc_oc_unsorted is not None and
+ len(self.shansep_data_df_nc_oc_unsorted) == len(table_df)):
+
+ # Check which additional columns are available
+ for col in additional_columns:
+ if col in self.shansep_data_df_nc_oc_unsorted.columns and col not in table_df.columns:
+ # Ensure the indices match before adding the column
+ if table_df.index.equals(self.shansep_data_df_nc_oc_unsorted.index):
+ table_df[col] = self.shansep_data_df_nc_oc_unsorted[col]
+
+ else:
+ print(f"WAARSCHUWING: extra kolommen {additional_columns} in invoer tabel konden niet worden toegevoegd aan invoer tabel vanwege ontbrekende of niet-overeenkomende gegevens.")
+
+ # Hernoem kolommen voor leesbaarheid met kortere namen en line breaks
+ column_mapping = {
+ 'PV_NAAM': 'Groep',
+ 'BORING_POSITIE': 'Pos.',
+ 'MONSTER_NIVEAU_NAP_VANAF': 'NAP Van\n[m]',
+ 'MONSTER_NIVEAU_NAP_TOT': 'NAP Tot\n[m]',
+ 'TXT_SS_VOLUMEGEWICHT_NAT': 'VGW nat\n[kN/m3]',
+ 'TXT_SS_VOLUMEGEWICHT_DRG': 'VGW drg\n[kN/m3]',
+ 'TXT_SS_WATERGEHALTE_VOOR': 'Water\n[%]',
+ 'DSS_VOLUMEGEWICHT_NAT': 'VGW nat\n[kN/m3]',
+ 'DSS_VOLUMEGEWICHT_DRG': 'VGW drg\n[kN/m3]',
+ 'DSS_WATERGEHALTE_VOOR': 'Water\n[%]',
+ 'S\'v': "σ'v\n[kPa]",
+ 'Su': "su\n[kPa]",
+ 'OCR': 'OCR\n[-]',
+ 'ANA_TERREINSPANNING': 'Terreinspanning\n[kPa]',
+ 'ANA_GRENSSPANNING_REKEN': 'Grensspanning\n[kPa]',
+ 'ANA_POP_VELD': 'POP\n[-]'
+ }
+
+ table_df = table_df.rename(columns={k: v for k, v in column_mapping.items() if k in table_df.columns})
+
+ # replace column POP by new calculation to account for missing values: terreinspanning*ocr-terreinspanning
+ if 'Terreinspanning\n[kPa]' in table_df.columns and 'OCR\n[-]' in table_df.columns:
+ table_df['POP\n[-]'] = table_df['Terreinspanning\n[kPa]'] * table_df['OCR\n[-]'] - table_df['Terreinspanning\n[kPa]']
+
+ # Sort table_df in the same way as shansep_data_df_nc_oc
+ # Check if consolidatietype column exists in the data (use original column name before renaming)
+
+ if 'consolidatietype' in table_df.columns:
+ table_df = table_df.sort_values(
+ by=['consolidatietype', table_df.index.name or table_df.index],
+ ascending=[False, True]
+ ).copy()
+ else:
+ table_df = table_df.sort_index()
+ print(f"WAARSCHUWING: invoer tabel kon niet worden gesorteerd op 'consolidatietype' omdat de kolom ontbreekt.")
+
+ # delete consolidatietype column after sorting
+ if 'consolidatietype' in table_df.columns:
+ table_df = table_df.drop(columns=['consolidatietype'])
+
+ # Handle NaN values and format numeric values properly
+ for col in table_df.columns:
+ if table_df[col].dtype in ['float64', 'int64']:
+ table_df[col] = table_df[col].apply(
+ lambda x: f"{x:.2f}" if isinstance(x, (float, int)) and not np.isnan(x) else ""
+ )
+ else:
+ table_df[col] = table_df[col].apply(
+ lambda x: "" if (isinstance(x, float) and np.isnan(x)) or x is None else str(x)
+ )
+
+ # Create table data with proper line break handling
+ # First create the header row with Paragraph objects for consistent formatting
+ from reportlab.lib.styles import getSampleStyleSheet
+ header_style = getSampleStyleSheet()['Normal']
+ header_style.fontSize = 7 # Set to 7 as requested
+ header_style.fontName = 'Helvetica-Bold'
+ header_style.alignment = 0 # Left align
+
+ # Create header row with Paragraph objects for ALL headers (consistent formatting)
+ header_row = []
+ header_row.append(Paragraph('Monster ID', header_style)) # Make Monster ID a Paragraph too
+
+ for col in table_df.columns:
+ if '\n' in col:
+ # Create Paragraph for multi-line headers
+ header_row.append(Paragraph(col.replace('\n', '
'), header_style))
+ else:
+ # Create Paragraph for single-line headers too (for consistency)
+ header_row.append(Paragraph(col, header_style))
+
+ # Create data rows
+ data_rows = []
+ for idx, row in table_df.iterrows():
+ data_row = [str(idx)] + [str(val) if val != "" else "" for val in row]
+ data_rows.append(data_row)
+
+ # Combine header and data
+ t1_data = [header_row] + data_rows
+
+ # Create table with automatic column sizing
+ t1 = LongTable(t1_data, repeatRows=1, hAlign='LEFT')
+ t1.setStyle(TableStyle([
+ ('ALIGN', (0, 0), (-1, -1), 'LEFT'),
+ ('BACKGROUND', (0, 0), (-1, 0), colors.lightgrey),
+ ('GRID', (0, 0), (-1, -1), 0.5, colors.black),
+ ('FONTNAME', (0, 1), (-1, -1), 'Helvetica'), # Only apply font to data rows (headers are Paragraphs)
+ ('FONTSIZE', (0, 1), (-1, -1), 6), # Set data font size to 6 as requested
+ ('VALIGN', (0, 0), (-1, 0), 'MIDDLE'), # Header row alignment
+ ('VALIGN', (0, 1), (-1, -1), 'TOP'), # Data row alignment
+ ('LEFTPADDING', (0, 0), (-1, -1), 2), # Reduce padding
+ ('RIGHTPADDING', (0, 0), (-1, -1), 2),
+ ('TOPPADDING', (0, 0), (-1, -1), 2),
+ ('BOTTOMPADDING', (0, 0), (-1, -1), 2),
+ # Special styling for header row with Paragraphs
+ ('ROWBACKGROUNDS', (0, 0), (-1, 0), [colors.lightgrey]),
+ ]))
+ return t1
+
+
+
+def _create_parameters_table(self: "SHANSEP") -> Table:
+ """
+ Maakt een tabel met de SHANSEP parameters.
+
+ Returns
+ -------
+ Table
+ ReportLab tabel object met de SHANSEP parameters
+ """
+ parameters: List[list] = []
+
+ # Voeg parameters toe
+ if hasattr(self, 'e_a1_oc') and self.e_a1_oc is not None and self.snijpunt_gem_handmatig is None:
+ parameters.append(['Snijpunt y-as gemiddeld [kPa] (voorstel)', round(self.e_a1_oc, 3)])
+ if hasattr(self, 'e_a2_oc') and self.e_a2_oc is not None and self.s_gem_handmatig is None:
+ parameters.append(['S gemiddeld [-] (voorstel)', round(self.e_a2_oc, 3)])
+ if hasattr(self, 'e_a2_nc_oc') and self.e_a2_nc_oc is not None and self.m_gem_handmatig is None:
+ parameters.append(['m gemiddeld [-] (voorstel)', round(self.e_a2_nc_oc, 3)])
+ if hasattr(self, 'pop_gem_oc') and self.pop_gem_oc is not None and self.pop_gem_handmatig is None:
+ parameters.append(['POP gemiddeld [kPa] (voorstel)', round(self.pop_gem_oc, 3)])
+ if hasattr(self, 'a1_kar_oc') and self.a1_kar_oc is not None and self.snijpunt_kar_handmatig is None:
+ parameters.append(['Snijpunt y-as karakteristiek [kPa] (voorstel)', round(self.a1_kar_oc, 3)])
+ if hasattr(self, 'a2_kar_oc') and self.a2_kar_oc is not None and self.s_kar_handmatig is None:
+ parameters.append(['S karakteristiek [-] (voorstel)', round(self.a2_kar_oc, 3)])
+ if hasattr(self, 'a2_kar_nc_oc') and self.a2_kar_nc_oc is not None and self.m_kar_handmatig is None:
+ parameters.append(['m karakteristiek [-] (voorstel)', round(self.a2_kar_nc_oc, 3)])
+ if hasattr(self, 'pop_kar_oc') and self.pop_kar_oc is not None and self.pop_kar_handmatig is None:
+ parameters.append(['POP karakteristiek [kPa] (voorstel)', round(self.pop_kar_oc, 3)])
+
+ parameters.append(['Type verzameling: lokaal = 1.0; regionaal = 0.75', self.alpha])
+
+ if hasattr(self, 'calc_vgwnat_gem') and self.calc_vgwnat_gem is not None:
+ parameters.append(['VGW nat gemiddeld [kN/m3]', round(self.calc_vgwnat_gem, 3)])
+ if hasattr(self, 'calc_watergehalte_gem') and self.calc_watergehalte_gem is not None:
+ parameters.append(['Watergehalte gemiddeld', round(self.calc_watergehalte_gem, 3)])
+
+ t = Table([['Parameter', 'Waarde']] + parameters, hAlign='LEFT')
+ t.setStyle(TableStyle([
+ ('ALIGN', (0, 0), (-1, -1), 'LEFT'),
+ ('BACKGROUND', (0, 0), (-1, 0), colors.lightgrey),
+ ('GRID', (0, 0), (-1, -1), 0.5, colors.black),
+ ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
+ ('FONTSIZE', (0, 0), (-1, 0), 10),
+ ('FONTSIZE', (0, 1), (-1, -1), 9),
+ ]))
+ return t
+
+
+def _create_gem_results_table(self: "SHANSEP") -> Table:
+ """
+ Maakt een tabel met de SHANSEP gemiddelde resultaten (alleen voorgestelde parameters).
+
+ Returns
+ -------
+ Table
+ ReportLab tabel object met de SHANSEP gemiddelde resultaten
+ """
+ df_gem, df_kar = self.get_result_values_shansep()
+
+ # Filter out handmatige rows - alleen voorgestelde parameters behouden
+ df_gem_filtered = df_gem[~df_gem.index.str.contains('handmatig', na=False)]
+
+ # Combineer gemiddelde resultaten
+ df_gem_new = DataFrame({
+ 'Analyse methode': df_gem_filtered.index,
+ 'Snijpunt y-as [kPa]': df_gem_filtered['snijpunt y-as [kPa]'].values,
+ 'S [-]': df_gem_filtered['Schuifsterkteratio S [-]'].values,
+ 'm [-]': df_gem_filtered['sterkte toename exponent = m [-]'].values,
+ 'POP [kPa]': df_gem_filtered['POP [kPa]'].values
+ })
+
+ # Format numeric values and handle NaN properly
+ for col in df_gem_new.columns:
+ if col != 'Analyse methode':
+ df_gem_new[col] = df_gem_new[col].apply(
+ lambda x: f"{x:.3f}" if isinstance(x, (float, int)) and x is not None and not np.isnan(x) else ""
+ )
+
+ # Maak tabel data zonder index
+ header = df_gem_new.columns.tolist()
+ data = [row.tolist() for _, row in df_gem_new.iterrows()]
+ t_data = [header] + data
+
+ t = LongTable(t_data, repeatRows=1, hAlign='LEFT')
+ t.setStyle(TableStyle([
+ ('ALIGN', (0, 0), (-1, -1), 'LEFT'),
+ ('BACKGROUND', (0, 0), (-1, 0), colors.lightgrey),
+ ('GRID', (0, 0), (-1, -1), 0.5, colors.black),
+ ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
+ ('FONTSIZE', (0, 0), (-1, 0), 10),
+ ('FONTSIZE', (0, 1), (-1, -1), 9),
+ ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
+ ]))
+ return t
+
+def _create_kar_results_table(self: "SHANSEP") -> Table:
+ """
+ Maakt een tabel met de SHANSEP karakteristieke resultaten (alleen voorgestelde parameters).
+
+ Returns
+ -------
+ Table
+ ReportLab tabel object met de SHANSEP karakteristieke resultaten
+ """
+ df_gem, df_kar = self.get_result_values_shansep()
+
+ # Filter out handmatige rows - alleen voorgestelde parameters behouden
+ df_kar_filtered = df_kar[~df_kar.index.str.contains('handmatig', na=False)]
+
+ df_kar_new = DataFrame({
+ 'Analyse methode': df_kar_filtered.index,
+ 'Snijpunt y-as [kPa]': df_kar_filtered['snijpunt y-as [kPa]'].values,
+ 'S [-]': df_kar_filtered['Schuifsterkteratio S [-]'].values,
+ 'm [-]': df_kar_filtered['sterkte toename exponent = m [-]'].values,
+ 'POP [kPa]': df_kar_filtered['POP [kPa]'].values,
+ })
+
+ # Format numeric values and handle NaN properly
+ for col in df_kar_new.columns:
+ if col != 'Analyse methode':
+ df_kar_new[col] = df_kar_new[col].apply(
+ lambda x: f"{x:.3f}" if isinstance(x, (float, int)) and x is not None and not np.isnan(x) else ""
+ )
+
+ # Maak tabel data zonder index
+ header = df_kar_new.columns.tolist()
+ data = [row.tolist() for _, row in df_kar_new.iterrows()]
+ t_data = [header] + data
+
+ t = LongTable(t_data, repeatRows=1, hAlign='LEFT')
+ t.setStyle(TableStyle([
+ ('ALIGN', (0, 0), (-1, -1), 'LEFT'),
+ ('BACKGROUND', (0, 0), (-1, 0), colors.lightgrey),
+ ('GRID', (0, 0), (-1, -1), 0.5, colors.black),
+ ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
+ ('FONTSIZE', (0, 0), (-1, 0), 10),
+ ('FONTSIZE', (0, 1), (-1, -1), 9),
+ ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
+ ]))
+ return t
+
+def _create_handmatig_results_table(self: "SHANSEP") -> Table:
+ """
+ Maakt een tabel met de SHANSEP handmatige parameters in horizontale indeling.
+
+ Returns
+ -------
+ Table
+ ReportLab tabel object met de SHANSEP handmatige parameters
+ """
+ # Verzamel gemiddelde handmatige waarden
+ gem_data = {
+ 'Snijpunt y-as [kPa]': round(self.snijpunt_gem_handmatig, 3) if self.snijpunt_gem_handmatig is not None else "",
+ 'S [-]': round(self.s_gem_handmatig, 3) if self.s_gem_handmatig is not None else "",
+ 'm [-]': round(self.m_gem_handmatig, 3) if self.m_gem_handmatig is not None else "",
+ 'POP [kPa]': round(self.pop_gem_handmatig, 3) if self.pop_gem_handmatig is not None else ""
+ }
+
+ # Verzamel karakteristieke handmatige waarden
+ kar_data = {
+ 'Snijpunt y-as [kPa]': round(self.snijpunt_kar_handmatig, 3) if self.snijpunt_kar_handmatig is not None else "",
+ 'S [-]': round(self.s_kar_handmatig, 3) if self.s_kar_handmatig is not None else "",
+ 'm [-]': round(self.m_kar_handmatig, 3) if self.m_kar_handmatig is not None else "",
+ 'POP [kPa]': round(self.pop_kar_handmatig, 3) if self.pop_kar_handmatig is not None else ""
+ }
+
+ # Maak DataFrame in hetzelfde formaat als gem/kar tabellen
+ df_handmatig = DataFrame({
+ 'Analyse methode': ['Gemiddeld (handmatig)', 'Karakteristiek (handmatig)'],
+ 'Snijpunt y-as [kPa]': [gem_data['Snijpunt y-as [kPa]'], kar_data['Snijpunt y-as [kPa]']],
+ 'S [-]': [gem_data['S [-]'], kar_data['S [-]']],
+ 'm [-]': [gem_data['m [-]'], kar_data['m [-]']],
+ 'POP [kPa]': [gem_data['POP [kPa]'], kar_data['POP [kPa]']]
+ })
+
+ # Maak tabel data
+ header = df_handmatig.columns.tolist()
+ data = [row.tolist() for _, row in df_handmatig.iterrows()]
+ t_data = [header] + data
+
+ t = LongTable(t_data, repeatRows=1, hAlign='LEFT')
+ t.setStyle(TableStyle([
+ ('ALIGN', (0, 0), (-1, -1), 'LEFT'),
+ ('BACKGROUND', (0, 0), (-1, 0), colors.lightgrey),
+ ('GRID', (0, 0), (-1, -1), 0.5, colors.black),
+ ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
+ ('FONTSIZE', (0, 0), (-1, 0), 10),
+ ('FONTSIZE', (0, 1), (-1, -1), 9),
+ ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
+ ]))
+ return t
+
+def save_to_pdf(self: "SHANSEP", path: str) -> str:
+ """
+ Slaat de SHANSEP analyseresultaten op in een PDF-document.
+
+ De PDF bevat:
+ - Titel met analysedetails
+ - Beide overzichtsfiguren van de analyse (sv-su en ln(OCR)-ln(su/svc))
+ - Su tabel
+ - Tabel met gemiddelde en karakteristieke resultaten
+ - Tabel met invoerselectie informatie
+
+ Parameters
+ ----------
+ path : str
+ Map locatie waar het PDF-bestand moet worden opgeslagen
+
+ Returns
+ -------
+ str
+ Het absolute bestandspad van het aangemaakte PDF-bestand
+ """
+ # Maak titel en bestandsnaam
+ title = f"SHANSEP analyse met {self.effective_stress} op {self.investigation_groups[0]}"
+ file_name = f"shansep_pdf_export_{self.investigation_groups[0]}_{self.analysis_type}_{str(self.effective_stress).replace('%', 'procent_').replace(' ', '')}.pdf"
+ file_path = f"{path}/{file_name}"
+
+ # Ensure analysis is run
+ self._run_shansep()
+
+ # Maak het PDF document
+ doc = SimpleDocTemplate(file_path, pagesize=landscape(A4))
+ styles = getSampleStyleSheet()
+
+ # Voeg alleen styles toe die nog niet bestaan
+ if 'Left' not in styles:
+ styles.add(ParagraphStyle(name='Left', parent=styles['Normal'], alignment=TA_LEFT))
+ if 'TitleLeft' not in styles:
+ styles.add(ParagraphStyle(name='TitleLeft', parent=styles['Title'], alignment=TA_LEFT))
+ if 'Heading3' not in styles:
+ styles.add(ParagraphStyle(name='Heading3', parent=styles['Heading2'], alignment=TA_LEFT))
+ story = []
+
+ # Voeg titel toe
+ story.append(Paragraph(title, styles['TitleLeft']))
+ story.append(Spacer(width=1, height=12))
+
+ # Probeer figuren te genereren en toe te voegen
+ # Controleer of handmatige parameters zijn ingesteld
+ has_manual_params = hasattr(self, 'parameters_handmatig') and self.parameters_handmatig
+
+ from reportlab.platypus import PageBreak, Image as RLImage
+ from PIL import Image as PILImage
+
+ # Eerste figuur: sv-su plot
+ try:
+ fig_path1 = f"{path}/temp_plot1.png"
+ self.show_title = False
+
+ # Genereer figuur
+ self.figure = go.Figure()
+ self.set_figure_sv_su()
+
+ if hasattr(self, 'figure') and self.figure is not None:
+ fig_width = 1280
+ fig_height = 720
+ self.figure.write_image(fig_path1, width=fig_width, height=fig_height, scale=4, format="png")
+
+ # Bereken figuurgrootte voor pagina 1 (met titel en heading)
+ # Ruimte voor titel (ongeveer 50 pt) + heading (ongeveer 25 pt) + spacers (ongeveer 25 pt)
+ title_and_heading_height = 100
+ available_height = doc.height - title_and_heading_height
+
+ # Laad PNG en bepaal pixelafmetingen
+ with PILImage.open(fig_path1) as im:
+ img_width_px, img_height_px = im.size
+
+ # Bereken optimale grootte die op de pagina past
+ max_width_pt = doc.width * 0.95
+ aspect = img_height_px / img_width_px
+
+ # Probeer eerst op basis van breedte
+ img_width_pt = min(max_width_pt, doc.width)
+ img_height_pt = img_width_pt * aspect
+
+ # Controleer of het past in de beschikbare hoogte, anders schaal naar hoogte
+ if img_height_pt > available_height:
+ img_height_pt = available_height * 0.95
+ img_width_pt = img_height_pt / aspect
+
+ # Maak ReportLab Image aan
+ img1 = RLImage(fig_path1)
+ img1.drawWidth = img_width_pt
+ img1.drawHeight = img_height_pt
+ img1.hAlign = 'LEFT'
+
+ # Voeg titel toe die aangeeft of handmatige parameters zijn gebruikt
+ if has_manual_params:
+ story.append(Paragraph("su - σ'v Relatie (met handmatige parameters)", styles['Heading3']))
+ else:
+ story.append(Paragraph("su - σ'v Relatie (met voorgestelde parameters)", styles['Heading3']))
+ story.append(Spacer(width=1, height=12))
+ story.append(img1)
+
+ # Nieuwe pagina voor tweede figuur
+ story.append(PageBreak())
+
+ except Exception as e:
+ print(f"Waarschuwing: Kon eerste figuur (Sv-Su) niet toevoegen aan PDF: {e}")
+ story.append(Paragraph("Eerste figuur: (kon niet worden gegenereerd)", styles['Heading3']))
+ story.append(Spacer(width=1, height=12))
+ story.append(PageBreak())
+
+ # Tweede figuur: ln(OCR) - ln(su/svc) plot
+ try:
+ fig_path2 = f"{path}/temp_plot2.png"
+ self.show_title = False
+
+ # Genereer figuur
+ self.figure = go.Figure()
+ self.set_figure_ln_ocr_ln_s()
+
+ if hasattr(self, 'figure') and self.figure is not None:
+ fig_width = 1280
+ fig_height = 720
+ self.figure.write_image(fig_path2, width=fig_width, height=fig_height, scale=4, format="png")
+
+ # Bereken figuurgrootte voor pagina 2 (alleen met heading)
+ # Ruimte voor heading (ongeveer 25 pt) + spacer (ongeveer 12 pt)
+ heading_height = 37
+ available_height = doc.height - heading_height
+
+ # Laad PNG en bepaal pixelafmetingen
+ with PILImage.open(fig_path2) as im:
+ img_width_px, img_height_px = im.size
+
+ # Bereken optimale grootte die op de pagina past
+ max_width_pt = doc.width * 0.95
+ aspect = img_height_px / img_width_px
+
+ # Probeer eerst op basis van breedte
+ img_width_pt = min(max_width_pt, doc.width)
+ img_height_pt = img_width_pt * aspect
+
+ # Controleer of het past in de beschikbare hoogte, anders schaal naar hoogte
+ if img_height_pt > available_height:
+ img_height_pt = available_height * 0.95
+ img_width_pt = img_height_pt / aspect
+
+ # Maak ReportLab Image aan
+ img2 = RLImage(fig_path2)
+ img2.drawWidth = img_width_pt
+ img2.drawHeight = img_height_pt
+ img2.hAlign = 'LEFT'
+
+ # Voeg titel toe die aangeeft of handmatige parameters zijn gebruikt
+ if has_manual_params:
+ story.append(Paragraph("LN(OCR) - LN(su/σ'v) Relatie (met handmatige parameters)", styles['Heading3']))
+ else:
+ story.append(Paragraph("LN(OCR) - LN(su/σ'v) Relatie (met voorgestelde parameters)", styles['Heading3']))
+ story.append(Spacer(width=1, height=12))
+ story.append(img2)
+
+ # Nieuwe pagina voor derde figuur
+ story.append(PageBreak())
+
+ except Exception as e:
+ print(f"Waarschuwing: Kon tweede figuur (ln(OCR) - ln(su/svc)) niet toevoegen aan PDF: {e}")
+ story.append(Paragraph("Tweede figuur: (kon niet worden gegenereerd)", styles['Heading3']))
+ story.append(Spacer(width=1, height=12))
+ story.append(PageBreak())
+
+ # Derde figuur: sv-su NC plot
+ try:
+ fig_path3 = f"{path}/temp_plot3.png"
+ self.show_title = False
+
+ self.figure = go.Figure()
+ self.set_figure_sv_su_nc()
+
+ if hasattr(self, 'figure') and self.figure is not None:
+ fig_width = 1280
+ fig_height = 720
+ self.figure.write_image(fig_path3, width=fig_width, height=fig_height, scale=4, format="png")
+
+ # Bereken figuurgrootte voor pagina 3 (alleen met heading)
+ # Ruimte voor heading (ongeveer 25 pt) + spacer (ongeveer 12 pt)
+ heading_height = 37
+ available_height = doc.height - heading_height
+
+ # Laad PNG en bepaal pixelafmetingen
+ with PILImage.open(fig_path3) as im:
+ img_width_px, img_height_px = im.size
+
+ # Bereken optimale grootte die op de pagina past
+ max_width_pt = doc.width * 0.95
+ aspect = img_height_px / img_width_px
+
+ # Probeer eerst op basis van breedte
+ img_width_pt = min(max_width_pt, doc.width)
+ img_height_pt = img_width_pt * aspect
+
+ # Controleer of het past in de beschikbare hoogte, anders schaal naar hoogte
+ if img_height_pt > available_height:
+ img_height_pt = available_height * 0.95
+ img_width_pt = img_height_pt / aspect
+
+ # Maak ReportLab Image aan
+ img3 = RLImage(fig_path3)
+ img3.drawWidth = img_width_pt
+ img3.drawHeight = img_height_pt
+ img3.hAlign = 'LEFT'
+
+ # Voeg titel toe die aangeeft of handmatige parameters zijn gebruikt
+ if has_manual_params:
+ story.append(Paragraph("su - σ'v Relatie op alleen NC proeven (met handmatige parameters)", styles['Heading3']))
+ else:
+ story.append(Paragraph("su - σ'v Relatie op alleen NC proeven (met voorgestelde parameters)", styles['Heading3']))
+ story.append(Spacer(width=1, height=12))
+ story.append(img3)
+
+ # Nieuwe pagina voor de tabellen en resultaten
+ story.append(PageBreak())
+
+ except Exception as e:
+ print(f"Waarschuwing: Kon derde figuur (Sv-Su NC) niet toevoegen aan PDF: {e}")
+ story.append(Paragraph("Derde figuur: (kon niet worden gegenereerd)", styles['Heading3']))
+ story.append(Spacer(width=1, height=12))
+ story.append(PageBreak())
+
+ # Reset title setting
+ self.show_title = True
+
+ # Voeg parameters toe
+ story.append(Paragraph("Invoer parameters en eigenschappen", styles['Heading2']))
+ story.append(_create_parameters_table(self))
+ story.append(Spacer(1, 12))
+
+ # Voeg resultaten toe
+ story.append(Paragraph("SHANSEP Resultaten eerste benadering gemiddeld", styles['Heading2']))
+ story.append(_create_gem_results_table(self))
+ story.append(Spacer(1, 12))
+
+ # Voeg resultaten toe
+ story.append(Paragraph("SHANSEP Resultaten eerste benadering karakteristiek", styles['Heading2']))
+ story.append(_create_kar_results_table(self))
+ story.append(Spacer(1, 12))
+
+ story.append(PageBreak())
+
+ # Voeg handmatige resultaten toe (alleen als er handmatige parameters zijn)
+ if any([
+ self.snijpunt_gem_handmatig is not None,
+ self.s_gem_handmatig is not None,
+ self.m_gem_handmatig is not None,
+ self.pop_gem_handmatig is not None,
+ self.snijpunt_kar_handmatig is not None,
+ self.s_kar_handmatig is not None,
+ self.m_kar_handmatig is not None,
+ self.pop_kar_handmatig is not None
+ ]):
+ story.append(Paragraph("SHANSEP Handmatige parameters", styles['Heading2']))
+ story.append(_create_handmatig_results_table(self))
+ story.append(Spacer(1, 12))
+
+
+ # Voeg Su tabel toe indien beschikbaar
+ if hasattr(self, 'sutabel') and self.sutabel is not None:
+ story.append(Paragraph("Su Tabel", styles['Heading2']))
+
+ # Handle NaN values in the Su table
+ sutabel_clean = self.sutabel.copy()
+
+ # Replace NaN values with empty strings for all columns
+ for col in sutabel_clean.columns:
+ sutabel_clean[col] = sutabel_clean[col].apply(
+ lambda x: "" if (isinstance(x, float) and np.isnan(x)) or x is None else x
+ )
+ sutabel_clean[col] = sutabel_clean[col].map(lambda x: f"{x:.3f}" if isinstance(x, (float, int)) else x)
+
+ # Maak tabel data zonder index
+ header = sutabel_clean.columns.tolist()
+ data = [row.tolist() for _, row in sutabel_clean.iterrows()]
+ sutabel_data = [header] + data
+
+ sutabel_table = LongTable(sutabel_data, repeatRows=1, hAlign='LEFT')
+ sutabel_table.setStyle(TableStyle([
+ ('ALIGN', (0, 0), (-1, -1), 'LEFT'),
+ ('BACKGROUND', (0, 0), (-1, 0), colors.lightgrey),
+ ('GRID', (0, 0), (-1, -1), 0.5, colors.black),
+ ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
+ ('FONTSIZE', (0, 0), (-1, 0), 10),
+ ('FONTSIZE', (0, 1), (-1, -1), 9),
+ ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
+ ]))
+ story.append(sutabel_table)
+ story.append(Spacer(1, 12))
+
+ # Voeg invoerselectie toe
+ story.append(Paragraph("Invoerselectie Informatie", styles['Heading2']))
+ story.append(_create_input_table(self))
+
+ # Maak PDF
+ doc.build(story)
+ print(f"SHANSEP PDF export voltooid: {file_path}")
+
+ return file_path
diff --git a/pv_tool/shansep_analysis/shansep_analysis.py b/pv_tool/shansep_analysis/shansep_analysis.py
index ea43a33..261059b 100644
--- a/pv_tool/shansep_analysis/shansep_analysis.py
+++ b/pv_tool/shansep_analysis/shansep_analysis.py
@@ -1,28 +1,74 @@
+import math
from pv_tool.imports.import_data import Dbase
from typing import Optional, List, Literal
from pv_tool.shansep_analysis.globals import (TEXTUAL_NAMES, NEW_COLUMN_NAMES, TEXTUAL_NAMES_DSS)
-from pandas import DataFrame, ExcelWriter, concat, read_excel
+from pandas import DataFrame, ExcelWriter, read_excel
+import plotly.graph_objects as go
+from pv_tool.shansep_analysis.calc_parameters import (
+ calc_watergehalte_gem_txt, calc_watergehalte_gem_dss,
+ calc_watergehalte_sd_txt, calc_watergehalte_sd_dss,
+ calc_vgwnat_gem_txt, calc_vgwnat_gem_dss,
+ calc_vgwnat_sd_txt, calc_vgwnat_sd_dss
+)
+from pv_tool.imports.excel_utils import format_excel_sheet
-from pv_tool.shansep_analysis.calc_parameters import (calc_watergehalte_gem, calc_watergehalte_sd, calc_vgwnat_gem,
- calc_vgwnat_sd)
+from pv_tool.shansep_analysis.visualization_shansep import (
+ add_proefresultaten_sv_su,
+ add_extra_proefresultaten_sv_su,
+ add_extra_proefresultaten_sv_su_nc,
+ add_extra_proefresultaten_ln_ocr_ln_s,
+ add_5pr_bovengrens_sv_su,
+ add_5pr_ondergrens_sv_su,
+ add_fysische_realiseerbare_ondergrens_sv_su,
+ add_lineair_fit_sv_su,
+ add_proefresultaten_ln_ocr_ln_s,
+ add_5pr_bovengrens_ln_ocr_ln_s,
+ add_5pr_ondergrens_ln_ocr_ln_s,
+ add_lineair_fit_ln_ocr_ln_s,
+ set_layout_sv_su,
+ set_layout_ln_ocr_ln_s, add_shansep_lijn_sv_su, add_shansep_lijn_ln_ocr_ln_s,
+ add_proefresultaten_sv_su_nc, add_fysische_realiseerbare_ondergrens_sv_su_nc,
+ add_lineair_fit_sv_su_nc, add_shansep_lijn_sv_su_nc
+)
+
+from pv_tool.shansep_analysis.variables import (
+ exp_gem_ln_su_svc_nc, gem_pop_oc, exp_kar_ln_su_svc_nc, kar_pop_oc,
+ e_a1_oc, e_a2_oc, e_a1_nc_oc, e_a2_nc_oc,
+ a1_kar_oc, a2_kar_oc,
+ a1_kar_nc_oc, a2_kar_nc_oc,
+ st_dev_s_handmatig, st_dev_pop_handmatig, st_dev_m_handmatig
+)
+
+from pv_tool.shansep_analysis.expand_analysis import (calculate_ln_ocr, calculate_pop,
+ calculate_chi_2_nc_oc, calculate_sv_tt_oc, calculate_sv_spop,
+ calculate_chi_2_oc, calculate_ln_sv_spop, calculate_sv_ty_oc,
+ calculate_chi_2_ondergrens_nc_oc, calculate_sv_tt_ondergrens_nc_oc,
+ calculate_chi_2_ondergrens_oc, calculate_sv_tt_nc_oc,
+ calculate_sv_ty_ondergrens_nc_oc, calculate_sv_ty_ondergrens_oc,
+ calculate_5pr_ondergrens_nc_oc, calculate_5pr_ondergrens_oc,
+ calculate_sv_ty_nc_oc, calculate_sv_tt_ondergrens_oc, calculate_sv_eff_oc,
+ calculate_sv_eff_nc_oc, calculate_5pr_bovengrens_oc, calculate_5pr_bovengrens_nc_oc)
+
+from pv_tool.shansep_analysis.save_and_export import (
+ add_results_to_dbase as _add_results_to_dbase,
+ save_total_to_excel as _save_total_to_excel,
+ save_to_pdf as _save_to_pdf
+)
+
+
+#-------------------------- SHANSEP Analysis Class ---------------------------------------#
-from pv_tool.shansep_analysis.expand_analysis import (calculate_s_tt, calculate_s_sutabel, calculate_pop,
- calculate_s_spop, calculate_ln_ocr, calculate_ln_s_spop,
- calculate_s_ty, calculate_kappa_2,
- calculate_5pr_ondergrens, calculate_5pr_bovengrens,
- calculate_s_tt_ondergrens, calculate_s_ty_ondergrens,
- calculate_kappa_2_ondergrens)
class SHANSEP:
def __init__(self, dbase: Dbase,
- analysis_type: Literal['TXT_S_POP', 'TXT_su_tabel', 'DSS_S_POP', 'DSS_su_tabel'],
+ analysis_type: Literal['TXT_S_POP', 'DSS_S_POP'],
investigation_groups: List,
effective_stress: Literal['2% rek', '5% rek', '10% rek', '15% rek', '20% rek',
'pieksterkte', 'eindsterkte']):
# Validate effective_stress based on analysis_type
- if analysis_type in ['TXT_S_POP', 'TXT_su_tabel'] and effective_stress in ['10% rek', '20% rek']:
+ if analysis_type in ['TXT_S_POP'] and effective_stress in ['10% rek', '20% rek']:
raise ValueError(
f"De waardes '10% rek' and '20% rek' kunnen alleen gebruikt worden bij de DSS analyse. "
f"De gekozen analyse is: '{analysis_type}', en de sterkte is: '{effective_stress}'"
@@ -38,101 +84,585 @@ def __init__(self, dbase: Dbase,
self.alpha: Optional[float] = 0.75
# Parameters
-
self.calc_watergehalte_gem: Optional[float] = None
self.calc_watergehalte_sd: Optional[float] = None
self.calc_vgwnat_gem: Optional[float] = None
self.calc_vgwnat_sd: Optional[float] = None
- # Placeholder
+ self.e_a2_oc: Optional[float] = None
+ self.e_a1_oc: Optional[float] = None
+ self.e_a2_nc_oc: Optional[float] = None
+ self.e_a1_nc_oc: Optional[float] = None
+ self.exp_e_a1_nc_oc: Optional[float] = None
+ self.exp_gem_ln_su_svc_nc: Optional[float] = None
+ self.pop_gem_oc: Optional[float] = None
+
+ self.a2_kar_oc: Optional[float] = None
+ self.a1_kar_oc: Optional[float] = None
+ self.a2_kar_nc_oc: Optional[float] = None
+ self.a1_kar_nc_oc: Optional[float] = None
+ self.exp_a1_kar_nc_oc: Optional[float] = None
+ self.exp_kar_ln_su_svc_nc: Optional[float] = None
+ self.pop_kar_oc: Optional[float] = None
+
+
+ # Handmatige parameters
+ self.parameters_handmatig: Optional[bool] = False
+
+ self.snijpunt_gem_handmatig: Optional[float] = None
+ self.s_gem_handmatig: Optional[float] = None
+ self.m_gem_handmatig: Optional[float] = None
+
+ self.snijpunt_kar_handmatig: Optional[float] = None
+ self.s_kar_handmatig: Optional[float] = None
+ self.m_kar_handmatig: Optional[float] = None
+
+ self.pop_kar_handmatig: Optional[float] = None
+ self.pop_gem_handmatig: Optional[float] = None
+
+ self.st_dev_s_handmatig: Optional[float] = None
+ self.st_dev_pop_handmatig: Optional[float] = None
+ self.st_dev_m_handmatig: Optional[float] = None
+
+ # dataframes
self.shansep_data_df: Optional[DataFrame] = None
self.total_shansep_data_df: Optional[DataFrame] = None
+ self.shansep_data_df_nc: Optional[DataFrame] = None
self.shansep_data_df_oc: Optional[DataFrame] = None
self.shansep_data_df_nc_oc: Optional[DataFrame] = None
+ self.shansep_data_df_nc_oc_unsorted: Optional[DataFrame] = None
+ self.sutabel: Optional[DataFrame] = None
+ self.sutabel_nc: Optional[DataFrame] = None
+ self.df_results_shansep_gem: Optional[DataFrame] = None
+ self.df_results_shansep_kar: Optional[DataFrame] = None
+
+ # Figure
+ self.figure = go.Figure()
+ self.show_title: Optional[bool] = True
- pass
+ # ========= Instelling en Data Ophalen Methodes ==========
def get_shansep_data(self):
"""Deze functie filtert de dbase-dataframe op basis van type analyse, PV_NAAM en de gewenste effectieve stress.
Daarnaast worden de kolomnamen aangepast zodat ze ongeacht het rekpercentage allemaal dezelfde kolomnaam hebben.
"""
- if self.analysis_type in ['TXT_S_POP', 'TXT_su_tabel']:
- self.shansep_data_df = self.dbase_df[self.dbase_df['ALG__TRIAXIAAL']]
+ if self.analysis_type in ['TXT_S_POP']:
+ self.shansep_data_df = self.dbase_df[self.dbase_df['ALG__TRIAXIAAL']].copy()
self.shansep_data_df = self.shansep_data_df[self.shansep_data_df['PV_NAAM'].isin(
- self.investigation_groups)]
- self.calc_watergehalte_gem = calc_watergehalte_gem(self)
- self.calc_watergehalte_sd = calc_watergehalte_sd(self)
- self.calc_vgwnat_gem = calc_vgwnat_gem(self)
- self.calc_vgwnat_sd = calc_vgwnat_sd(self)
- self.total_shansep_data_df = self.shansep_data_df
- self.shansep_data_df = self.shansep_data_df[TEXTUAL_NAMES.get(self.effective_stress, [])]
-
- elif self.analysis_type in ['DSS_S_POP', 'DSS_su_tabel']:
- self.shansep_data_df = self.dbase_df[self.dbase_df['ALG__DSS']]
+ self.investigation_groups)].copy()
+ self.calc_watergehalte_gem = calc_watergehalte_gem_txt(self.shansep_data_df)
+ self.calc_watergehalte_sd = calc_watergehalte_sd_txt(self.shansep_data_df)
+ self.calc_vgwnat_gem = calc_vgwnat_gem_txt(self.shansep_data_df)
+ self.calc_vgwnat_sd = calc_vgwnat_sd_txt(self.shansep_data_df)
+ self.total_shansep_data_df = self.shansep_data_df.copy()
+ self.shansep_data_df = self.shansep_data_df[TEXTUAL_NAMES.get(self.effective_stress, [])].copy()
+
+ elif self.analysis_type in ['DSS_S_POP']:
+ self.shansep_data_df = self.dbase_df[self.dbase_df['ALG__DSS']].copy()
self.shansep_data_df = self.shansep_data_df[self.shansep_data_df['PV_NAAM'].isin(
- self.investigation_groups)]
- self.calc_watergehalte_gem = calc_watergehalte_gem(self)
- self.calc_watergehalte_sd = calc_watergehalte_sd(self)
- self.calc_vgwnat_gem = calc_vgwnat_gem(self)
- self.calc_vgwnat_sd = calc_vgwnat_sd(self)
- self.total_shansep_data_df = self.shansep_data_df
- self.shansep_data_df = self.shansep_data_df[TEXTUAL_NAMES_DSS.get(self.effective_stress, [])]
+ self.investigation_groups)].copy()
+ self.calc_watergehalte_gem = calc_watergehalte_gem_dss(self.shansep_data_df)
+ self.calc_watergehalte_sd = calc_watergehalte_sd_dss(self.shansep_data_df)
+ self.calc_vgwnat_gem = calc_vgwnat_gem_dss(self.shansep_data_df)
+ self.calc_vgwnat_sd = calc_vgwnat_sd_dss(self.shansep_data_df)
+ self.total_shansep_data_df = self.shansep_data_df.copy()
+ self.shansep_data_df = self.shansep_data_df[TEXTUAL_NAMES_DSS.get(self.effective_stress, [])].copy()
self.shansep_data_df.columns = NEW_COLUMN_NAMES
-
-
def apply_settings(self, alpha: Optional[float] = None):
"""Met deze functie kan je de alpha en materiaalfactoren opgeven."""
self.alpha = alpha if alpha is not None else self.alpha
- def expand_analysis_df_sutabel(self):
- """Deze functie berekent alle benodigde parameters per monster voor de analyse."""
- # calculate_s_tt(self)
- # calculate_s_ty(self)
- # calculate_kappa_2(self)
- # calculate_s_sutabel(self)
- # calculate_5pr_ondergrens(self)
- # calculate_5pr_bovengrens(self)
- # calculate_s_tt_ondergrens(self)
- # calculate_s_ty_ondergrens(self)
- # calculate_kappa_2_ondergrens(self)
- return f"Deze functie is nog niet geïmplementeerd voor de su tabel analyse."
+ def get_previous_results(self, path: str, file_name: str):
+ """
+ Zoekt naar eerdere analyseresultaten in een Excel-bestand.
+
+ Parameters
+ ----------
+ path: str
+ Pad naar de map waar het Excel-bestand staat
+ file_name: str
+ Naam van het Excel-bestand
+
+ Returns
+ -------
+ DataFrame of None
+ DataFrame met eerdere resultaten als deze gevonden zijn, anders None
+ """
+
+ file_path = f"{path}/{file_name}"
+
+ try:
+ with open(file_path, 'r'):
+ pass
+ except FileNotFoundError:
+ raise FileNotFoundError(f"Er is geen dbase aanwezig op de locatie {file_path}.")
+
+ try:
+ results_df = read_excel(file_path, sheet_name='Resultaten')
+ except ValueError:
+ print("Er is geen tabblad 'Resultaten' aanwezig in het Excel-bestand.")
+ return None
+
+ filtered_df = results_df[
+ (results_df['PV_RESULTAAT_ID'].str.contains(self.investigation_groups[0])) &
+ (results_df['PV_RESULTAAT_ID'].str.contains(self.effective_stress)) &
+ (results_df['PV_RESULTAAT_ID'].str.contains(self.analysis_type))
+ ]
+
+ if filtered_df.empty:
+ print("Er zijn geen eerdere resultaten gevonden voor de opgegeven parameters.")
+ return None
+
+ latest_entry = filtered_df.sort_values(by='Timestamp', ascending=False).iloc[0]
+
+ return latest_entry
+
+ # ========= Analyse Methodes ==========
def expand_analysis_df_s_pop_alleen_oc(self):
"""Deze functie berekent alle benodigde parameters per monster voor de analyse."""
calculate_ln_ocr(self)
- calculate_s_spop(self)
- calculate_ln_s_spop(self)
+ calculate_sv_spop(self)
+ calculate_ln_sv_spop(self)
calculate_pop(self)
self.shansep_data_df_oc = self.shansep_data_df[self.shansep_data_df['consolidatietype'] == 'OC'].copy()
- calculate_s_tt(self)
- calculate_s_ty(self)
- calculate_kappa_2(self)
- calculate_s_sutabel(self)
- calculate_5pr_ondergrens(self)
- calculate_5pr_bovengrens(self)
- calculate_s_tt_ondergrens(self)
- calculate_s_ty_ondergrens(self)
- calculate_kappa_2_ondergrens(self)
+ calculate_sv_tt_oc(self)
+ calculate_sv_ty_oc(self)
+ calculate_chi_2_oc(self)
+ calculate_sv_eff_oc(self)
+ calculate_5pr_ondergrens_oc(self)
+ calculate_5pr_bovengrens_oc(self)
+ calculate_sv_tt_ondergrens_oc(self)
+ calculate_sv_ty_ondergrens_oc(self)
+ calculate_chi_2_ondergrens_oc(self)
def expand_analysis_df_s_pop(self):
"""Deze functie berekent alle benodigde parameters per monster voor de analyse."""
calculate_ln_ocr(self)
- calculate_s_spop(self)
- calculate_ln_s_spop(self)
+ calculate_sv_spop(self)
+ calculate_ln_sv_spop(self)
calculate_pop(self)
self.shansep_data_df_nc_oc = self.shansep_data_df.copy()
+ self.shansep_data_df_nc_oc_unsorted = self.shansep_data_df.copy()
+ self.shansep_data_df_nc_oc = self.shansep_data_df_nc_oc.sort_values(
+ by=['consolidatietype', self.shansep_data_df_nc_oc.index.name or self.shansep_data_df_nc_oc.index],
+ ascending=[False, True]
+ ).copy()
+
+ calculate_sv_tt_nc_oc(self)
+ calculate_sv_ty_nc_oc(self)
+ calculate_chi_2_nc_oc(self)
+ calculate_sv_eff_nc_oc(self)
+ calculate_5pr_ondergrens_nc_oc(self)
+ calculate_5pr_bovengrens_nc_oc(self)
+ calculate_sv_tt_ondergrens_nc_oc(self)
+ calculate_sv_ty_ondergrens_nc_oc(self)
+ calculate_chi_2_ondergrens_nc_oc(self)
+
+ def get_shansep_parameters(self):
+ """
+ Berekent de parameters van de shansep analyse
+ """
+
+ # Voor S-POP analyse berekenen we de standaard parameters
+ self.e_a2_oc = e_a2_oc(self)
+ self.e_a1_oc = e_a1_oc(self)
+ self.e_a2_nc_oc = e_a2_nc_oc(self)
+ self.e_a1_nc_oc = e_a1_nc_oc(self)
+ self.exp_e_a1_nc_oc = math.exp(e_a1_nc_oc(self))
+ self.exp_gem_ln_su_svc_nc = exp_gem_ln_su_svc_nc(self)
+ self.pop_gem_oc = gem_pop_oc(self)
+
+ self.a2_kar_oc = a2_kar_oc(self)
+ self.a1_kar_oc = a1_kar_oc(self)
+ self.a2_kar_nc_oc = a2_kar_nc_oc(self)
+ self.a1_kar_nc_oc = a1_kar_nc_oc(self)
+ self.exp_a1_kar_nc_oc = math.exp(a1_kar_nc_oc(self))
+ self.exp_kar_ln_su_svc_nc = exp_kar_ln_su_svc_nc(self)
+ self.pop_kar_oc = kar_pop_oc(self)
+
+ # Only calculate standard deviations if manual parameters are set
+ if hasattr(self, 'parameters_handmatig') and self.parameters_handmatig:
+ self.st_dev_m_handmatig = st_dev_m_handmatig(self)
+ self.st_dev_pop_handmatig = st_dev_pop_handmatig(self)
+ self.st_dev_s_handmatig = st_dev_s_handmatig(self)
+ else:
+ # Initialize as None when manual parameters are not set
+ self.st_dev_m_handmatig = None
+ self.st_dev_pop_handmatig = None
+ self.st_dev_s_handmatig = None
+
+ def _run_shansep(self):
+ """
+ Voert de volledige shansep analyse uit in de juiste volgorde:
+ data ophalen, parameters berekenen en resultaten bepalen.
+ """
+ self.get_shansep_data()
+
+ if self.analysis_type in ['TXT_S_POP', 'DSS_S_POP']:
+ self.expand_analysis_df_s_pop_alleen_oc()
+ self.expand_analysis_df_s_pop()
+
+ self.get_shansep_parameters()
+
+ # ========= Resultaten Methodes ==========
+
+ def get_result_values_shansep(self):
+ """
+ Berekent de definitieve resultaten van de shansep analyse
+ """
+ self._run_shansep()
+ self.df_results_shansep_gem = DataFrame(
+ index=['bepaling S en POP uit triaxiaal- of DSS proeven', 'bepaling S en m uit triaxiaal- of DSS proeven',
+ 'o.b.v. opgegeven POP bij triaxiaal- of DSS proeven',
+ 'bepaling S uit triaxiaal- of DSS proeven OCR=1',
+ 'gemiddelde handmatige keuze snijput, S en m'])
+ self.df_results_shansep_gem['snijpunt y-as [kPa]'] = [self.e_a1_oc, None, None, None, self.snijpunt_gem_handmatig]
+ self.df_results_shansep_gem['Schuifsterkteratio S [-]'] = [self.e_a2_oc, self.exp_e_a1_nc_oc, None, self.exp_gem_ln_su_svc_nc, self.s_gem_handmatig]
+ self.df_results_shansep_gem['sterkte toename exponent = m [-]'] = [None, self.e_a2_nc_oc, None, None, self.m_gem_handmatig]
+ pop_bepaald = self.e_a1_oc/self.e_a2_oc/self.e_a2_nc_oc
+ self.df_results_shansep_gem['POP [kPa]'] = [pop_bepaald, pop_bepaald, self.pop_gem_oc, None, self.pop_gem_handmatig]
+
+ self.df_results_shansep_kar = DataFrame(
+ index=['bepaling S en POP uit triaxiaal- of DSS proeven', 'bepaling S en m uit triaxiaal- of DSS proeven',
+ 'o.b.v. opgegeven POP bij triaxiaal- of DSS proeven',
+ 'bepaling S uit triaxiaal- of DSS proeven OCR=1',
+ 'karakteristieke handmatige keuze snijput, S en m'])
+ self.df_results_shansep_kar['snijpunt y-as [kPa]'] = [self.a1_kar_oc, None, None, None, self.snijpunt_kar_handmatig]
+ self.df_results_shansep_kar['Schuifsterkteratio S [-]'] = [self.a2_kar_oc, self.exp_a1_kar_nc_oc, None, self.exp_kar_ln_su_svc_nc, self.s_kar_handmatig]
+ self.df_results_shansep_kar['sterkte toename exponent = m [-]'] = [None, self.a2_kar_nc_oc, None, None, self.m_kar_handmatig]
+ pop_bepaald = self.a1_kar_oc/self.a2_kar_oc/self.a2_kar_nc_oc
+ self.df_results_shansep_kar['POP [kPa]'] = [pop_bepaald, pop_bepaald, self.pop_kar_oc, None, self.pop_kar_handmatig]
+
+ # write these dataframes to excel
+ return self.df_results_shansep_gem, self.df_results_shansep_kar
+
+ def get_result_values_nc(self):
+ """
+ Berekent de resultaten voor NC (Normal Consolidated) analyse waarbij snijpunt, m en pop waarden op 0 staan.
+ Dit is bedoeld voor visualisatie van alleen NC data zonder overconsolidatie effecten.
+
+ Returns
+ -------
+ tuple[DataFrame, DataFrame]
+ Gemiddelde en karakteristieke resultaten voor NC analyse
+ """
+ self._run_shansep()
+
+ # Maak kopie van bestaande resultaten maar zet specifieke waarden op 0 voor NC analyse
+ df_results_nc_gem = DataFrame(
+ index=['bepaling S en POP uit triaxiaal- of DSS proeven', 'bepaling S en m uit triaxiaal- of DSS proeven',
+ 'o.b.v. opgegeven POP bij triaxiaal- of DSS proeven',
+ 'bepaling S uit triaxiaal- of DSS proeven OCR=1',
+ 'gemiddelde handmatige keuze snijput, S en m'])
+
+ # Voor NC analyse: snijpunt = 0, m = 0, POP = 0
+ df_results_nc_gem['snijpunt y-as [kPa]'] = [0, None, None, None, 0]
+ df_results_nc_gem['Schuifsterkteratio S [-]'] = [self.e_a2_oc, self.exp_e_a1_nc_oc, None, self.exp_gem_ln_su_svc_nc, self.s_gem_handmatig if self.s_gem_handmatig else 0]
+ df_results_nc_gem['sterkte toename exponent = m [-]'] = [None, 0, None, None, 0]
+ df_results_nc_gem['POP [kPa]'] = [0, 0, 0, None, 0]
+
+ df_results_nc_kar = DataFrame(
+ index=['bepaling S en POP uit triaxiaal- of DSS proeven', 'bepaling S en m uit triaxiaal- of DSS proeven',
+ 'o.b.v. opgegeven POP bij triaxiaal- of DSS proeven',
+ 'bepaling S uit triaxiaal- of DSS proeven OCR=1',
+ 'karakteristieke handmatige keuze snijput, S en m'])
+
+ # Voor NC analyse: snijpunt = 0, m = 0, POP = 0
+ df_results_nc_kar['snijpunt y-as [kPa]'] = [0, None, None, None, 0]
+ df_results_nc_kar['Schuifsterkteratio S [-]'] = [self.a2_kar_oc, self.exp_a1_kar_nc_oc, None, self.exp_kar_ln_su_svc_nc, self.s_kar_handmatig if self.s_kar_handmatig else 0]
+ df_results_nc_kar['sterkte toename exponent = m [-]'] = [None, 0, None, None, 0]
+ df_results_nc_kar['POP [kPa]'] = [0, 0, 0, None, 0]
+
+ return df_results_nc_gem, df_results_nc_kar
+
+ def calculate_sutabel(self):
+ """print de blauwe tabel in de excel naar excel
+ wordt ook weggeschreven naar pdf bij save_to_pdf
+ wordt nu bij visualisation ook aangeroepen all
+ """
+ self.sutabel = DataFrame(columns=['S\'v [kPa]', 'Su in-situ karakteristiek', 'Su in-situ gemiddeld'])
+ x = [0.1, 1, 5, 10, 20, 30, self.shansep_data_df_oc['S\'v'].max()]
+ shansep_kar = [self.s_kar_handmatig * x * ((self.pop_kar_handmatig + x) / x) ** self.m_kar_handmatig for x in x]
+ shansep_gem = [self.s_gem_handmatig * x * ((self.pop_gem_handmatig + x) / x) ** self.m_gem_handmatig for x in x]
+
+ self.sutabel['S\'v [kPa]'] = x
+ self.sutabel['Su in-situ karakteristiek'] = shansep_kar
+ self.sutabel['Su in-situ gemiddeld'] = shansep_gem
+ return self.sutabel
+
+ def calculate_sutabel_nc(self):
+ """print de blauwe tabel in de excel naar excel
+ wordt ook weggeschreven naar pdf bij save_to_pdf
+ wordt nu bij visualisation ook aangeroepen all
+ """
+ self.sutabel_nc = DataFrame(columns=['S\'v [kPa]', 'Su in-situ karakteristiek', 'Su in-situ gemiddeld'])
+ x = [0.1, 1, 5, 10, 20, 30, self.shansep_data_df_nc['S\'v'].max()]
+ shansep_kar = [self.s_kar_handmatig * x * ((self.pop_kar_handmatig + x) / x) ** self.m_kar_handmatig for x in x]
+ shansep_gem = [self.s_gem_handmatig * x * ((self.pop_gem_handmatig + x) / x) ** self.m_gem_handmatig for x in x]
+
+ self.sutabel_nc['S\'v [kPa]'] = x
+ self.sutabel_nc['Su in-situ karakteristiek'] = shansep_kar
+ self.sutabel_nc['Su in-situ gemiddeld'] = shansep_gem
+ return self.sutabel_nc
+
+ def export_shansep_results_excel(self, file_path: str):
+ """
+ Exporteert de shansep resultaten naar een Excel-bestand.
- calculate_s_tt(self)
- calculate_s_ty(self)
- calculate_kappa_2(self)
- calculate_s_sutabel(self)
- calculate_5pr_ondergrens(self)
- calculate_5pr_bovengrens(self)
- calculate_s_tt_ondergrens(self)
- calculate_s_ty_ondergrens(self)
- calculate_kappa_2_ondergrens(self)
\ No newline at end of file
+ Parameters
+ ----------
+ file_path : str
+ Pad naar de map waar het Excel-bestand moet worden opgeslagen plus de bestandsnaam
+ """
+ df_gem, df_kar = self.get_result_values_shansep()
+
+ # Prepare DataFrames for Excel export to prevent corruption
+ # Ensure index is strings and has a name
+ df_gem.index = df_gem.index.astype(str)
+ df_kar.index = df_kar.index.astype(str)
+ if df_gem.index.name is None:
+ df_gem.index.name = 'Analyse'
+ if df_kar.index.name is None:
+ df_kar.index.name = 'Analyse'
+
+ with ExcelWriter(file_path) as writer:
+ df_gem.to_excel(writer, sheet_name='Gemiddeld', index=True)
+ df_kar.to_excel(writer, sheet_name='Karakteristiek', index=True)
+
+ # Format the Excel sheets
+ format_excel_sheet(
+ file_path=file_path,
+ sheet_name='Gemiddeld',
+ num_columns=len(df_gem.columns),
+ num_rows=len(df_gem),
+ table_name='GemiddeldResultaten',
+ index=True
+ )
+
+ format_excel_sheet(
+ file_path=file_path,
+ sheet_name='Karakteristiek',
+ num_columns=len(df_kar.columns),
+ num_rows=len(df_kar),
+ table_name='KarakteristiekResultaten',
+ index=True
+ )
+
+ # ========== Handmatige Parameters en wegschrijven ==========
+
+ def set_parameters_handmatig(self, snijpunt_gem, s_gem, m_gem, snijpunt_kar, s_kar, m_kar):
+ """
+ Stelt de handmatige parameters in voor de analyse. De invoer moet handmatig worden gedaan door de gebruiker op basis van de resultaten tabel
+ """
+ self._run_shansep()
+ self.parameters_handmatig = True
+
+ self.snijpunt_gem_handmatig = snijpunt_gem
+ self.s_gem_handmatig = s_gem
+ self.m_gem_handmatig = m_gem
+ self.pop_gem_handmatig = snijpunt_gem / s_gem / m_gem
+
+ self.snijpunt_kar_handmatig = snijpunt_kar
+ self.s_kar_handmatig = s_kar
+ self.m_kar_handmatig = m_kar
+ self.pop_kar_handmatig = snijpunt_kar / s_kar / m_kar
+
+ # Recalculate standard deviations now that manual parameters are set
+ self.st_dev_m_handmatig = st_dev_m_handmatig(self)
+ self.st_dev_pop_handmatig = st_dev_pop_handmatig(self)
+ self.st_dev_s_handmatig = st_dev_s_handmatig(self)
+
+ def write_analysis_to_excel(self, file_path: str):
+ """
+ Schrijft de analyse dataframes naar een Excel-bestand met verschillende sheets.
+
+ Parameters
+ ----------
+ file_path : str
+ Pad naar de map waar het Excel-bestand moet worden opgeslagen plus de bestandsnaam
+ """
+ with ExcelWriter(file_path) as writer:
+ if self.shansep_data_df is not None:
+ self.shansep_data_df.to_excel(writer, sheet_name='Shansep Data', index=False)
+ if self.shansep_data_df_oc is not None:
+ self.shansep_data_df_oc.to_excel(writer, sheet_name='Shansep Data OC', index=False)
+ if self.shansep_data_df_nc_oc is not None:
+ self.shansep_data_df_nc_oc.to_excel(writer, sheet_name='Shansep Data NC_OC', index=False)
+
+ # ========== Visualisatie Methodes ==========
+
+ def set_figure_sv_su(self, plot_extra_dataset: Optional[List] = None):
+ """
+ Maakt een visualisatie van de analyseresultaten.
+
+ Parameters
+ ----------
+ plot_extra_dataset : List, optioneel
+ Extra dataset om in de plot weer te geven
+ """
+ self._run_shansep()
+ add_proefresultaten_sv_su(self)
+ if plot_extra_dataset is not None:
+ add_extra_proefresultaten_sv_su(self, plot_extra_dataset)
+ add_5pr_bovengrens_sv_su(self)
+ add_5pr_ondergrens_sv_su(self)
+ add_lineair_fit_sv_su(self)
+
+ if self.parameters_handmatig:
+ add_fysische_realiseerbare_ondergrens_sv_su(self)
+ self.calculate_sutabel()
+ add_shansep_lijn_sv_su(self)
+
+ set_layout_sv_su(self)
+
+ def set_figure_sv_su_nc(self, plot_extra_dataset: Optional[List] = None):
+ """
+ Maakt een visualisatie van de analyseresultaten.
+
+ Parameters
+ ----------
+ plot_extra_dataset : List, optioneel
+ Extra dataset om in de plot weer te geven
+ """
+ self._run_shansep()
+
+ self.shansep_data_df_nc = self.shansep_data_df_nc_oc[self.shansep_data_df_nc_oc['consolidatietype'] == 'NC'].copy() if hasattr(
+ self, 'shansep_data_df_nc_oc') and self.shansep_data_df_nc_oc is not None else None
+
+ add_proefresultaten_sv_su_nc(self)
+ if plot_extra_dataset is not None:
+ add_extra_proefresultaten_sv_su_nc(self, plot_extra_dataset)
+ add_lineair_fit_sv_su_nc(self)
+
+ if self.parameters_handmatig:
+ add_fysische_realiseerbare_ondergrens_sv_su_nc(self)
+ self.sutabel_nc = self.calculate_sutabel_nc()
+ add_shansep_lijn_sv_su_nc(self)
+
+ set_layout_sv_su(self)
+
+ def set_figure_ln_ocr_ln_s(self, plot_extra_dataset: Optional[List] = None):
+ """
+ Maakt een visualisatie van de analyseresultaten.
+
+ Parameters
+ ----------
+ plot_extra_dataset : List, optioneel
+ Extra dataset om in de plot weer te geven
+ """
+ self._run_shansep()
+ add_proefresultaten_ln_ocr_ln_s(self)
+ if plot_extra_dataset is not None:
+ add_extra_proefresultaten_ln_ocr_ln_s(self, plot_extra_dataset)
+ add_5pr_bovengrens_ln_ocr_ln_s(self)
+ add_5pr_ondergrens_ln_ocr_ln_s(self)
+ add_lineair_fit_ln_ocr_ln_s(self)
+
+ if self.parameters_handmatig:
+ add_shansep_lijn_ln_ocr_ln_s(self)
+
+ set_layout_ln_ocr_ln_s(self)
+
+ def show_figure_sv_su(self, plot_extra_dataset: Optional[List] = None):
+ """
+ Toont de visualisatie van de analyseresultaten.
+
+ Parameters
+ ----------
+ plot_extra_dataset : List, optioneel
+ Extra dataset om in de plot weer te geven
+ """
+ self._run_shansep()
+ self.figure = go.Figure()
+ self.set_figure_sv_su(plot_extra_dataset)
+
+ def show_figure_ln_ocr_ln_s(self, plot_extra_dataset: Optional[List] = None):
+ """
+ Toont de visualisatie van de analyseresultaten.
+
+ Parameters
+ ----------
+ plot_extra_dataset : List, optioneel
+ Extra dataset om in de plot weer te geven
+ """
+ self._run_shansep()
+ self.figure = go.Figure()
+ self.set_figure_ln_ocr_ln_s(plot_extra_dataset)
+ self.figure.show()
+
+ def show_figure_sv_su_nc(self, plot_extra_dataset: Optional[List] = None):
+ """
+ Toont de visualisatie van NC (Normal Consolidated) analyseresultaten met sv-su plot.
+
+ Parameters
+ ----------
+ plot_extra_dataset : List, optioneel
+ Extra dataset om in de plot weer te geven
+ """
+ self._run_shansep()
+ self.figure = go.Figure()
+ self.set_figure_sv_su_nc(plot_extra_dataset)
+ self.figure.show()
+
+ # ========== Export Methodes ==========
+
+ def add_results_to_dbase(self, path: str, file_name: str = 'Template_PVtool5_0.xlsx'):
+ """
+ Voegt de SHANSEP analyseresultaten toe aan de database Excel-bestand.
+
+ Parameters
+ ----------
+ path : str
+ Pad naar de map waar het Excel-bestand staat
+ file_name : str, optioneel
+ Naam van het Excel-bestand (default: 'Template_PVtool5_0.xlsx')
+
+ Returns
+ -------
+ DataFrame
+ Bijgewerkte DataFrame met alle resultaten
+ """
+ return _add_results_to_dbase(self, path, file_name)
+
+ def save_total_to_excel(self, path: str):
+ """
+ Exporteert alle SHANSEP analysegegevens naar Excel.
+
+ Slaat de volledige dataset met alle berekende kolommen op in een Excel bestand.
+ De bestandsnaam wordt automatisch gegenereerd op basis van de analyse-instellingen.
+
+ Parameters
+ ----------
+ path : str
+ Map locatie waar het Excel-bestand moet worden opgeslagen
+ """
+ return _save_total_to_excel(self, path)
+
+ def save_to_pdf(self, path: str) -> str:
+ """
+ Slaat de SHANSEP analyseresultaten op in een PDF-document.
+
+ De PDF bevat:
+ - Titel met analysedetails
+ - Beide overzichtsfiguren van de analyse (sv-su en ln(OCR)-ln(su/svc))
+ - Su tabel
+ - Tabel met gemiddelde en karakteristieke resultaten
+ - Tabel met invoerselectie informatie
+
+ Parameters
+ ----------
+ path : str
+ Map locatie waar het PDF-bestand moet worden opgeslagen
+
+ Returns
+ -------
+ str
+ Het absolute bestandspad van het aangemaakte PDF-bestand
+ """
+ return _save_to_pdf(self, path)
diff --git a/pv_tool/shansep_analysis/variables.py b/pv_tool/shansep_analysis/variables.py
index 951435e..126ba3d 100644
--- a/pv_tool/shansep_analysis/variables.py
+++ b/pv_tool/shansep_analysis/variables.py
@@ -6,7 +6,7 @@
if TYPE_CHECKING:
from pv_tool.shansep_analysis.shansep_analysis import SHANSEP
import numpy as np
-from scipy.stats import t
+from scipy.stats import t, norm
# -------------------------- alleen OC ---------------------------------------
def count_sv_oc(self: SHANSEP):
@@ -37,21 +37,21 @@ def e_a1_oc(self: SHANSEP):
return (sum_su_oc(self) - sum_sv_oc(self) * e_a2_oc(self)) / count_sv_oc(self)
-def sum_kappa_2_oc(self: SHANSEP):
- return self.shansep_data_df_oc['kappa_2'].sum()
+def sum_chi_2_oc(self: SHANSEP):
+ return self.shansep_data_df_oc['chi_2'].sum()
def var_a2_oc(self: SHANSEP):
- return (1 / sum_sv_tt_oc(self)) * sum_kappa_2_oc(self) / (count_sv_oc(self) - 2)
+ return (1 / sum_sv_tt_oc(self)) * sum_chi_2_oc(self) / (count_sv_oc(self) - 2)
def var_a1_oc(self: SHANSEP):
- return 1 / count_sv_oc(self) * (1 + sum_sv_oc(self) ** 2 / (count_sv_oc(self) * sum_sv_tt_oc(self))) * sum_kappa_2_oc(self) / (
+ return 1 / count_sv_oc(self) * (1 + sum_sv_oc(self) ** 2 / (count_sv_oc(self) * sum_sv_tt_oc(self))) * sum_chi_2_oc(self) / (
count_sv_oc(self) - 2)
def cov_a1_a2_oc(self: SHANSEP):
- return -(sum_sv_oc(self) / (count_sv_oc(self) * sum_sv_tt_oc(self))) * sum_kappa_2_oc(self) / (count_sv_oc(self) - 2)
+ return -(sum_sv_oc(self) / (count_sv_oc(self) * sum_sv_tt_oc(self))) * sum_chi_2_oc(self) / (count_sv_oc(self) - 2)
def rho_a1_a2_oc(self: SHANSEP):
@@ -71,6 +71,8 @@ def t_n_2_oc(self: SHANSEP):
degrees_of_freedom = count_sv_oc(self) - 2
return t.ppf(1 - significantieniveau / 2, degrees_of_freedom)
+# - - - - - - - - - - - - - - op blauwe kolommen in excel - - - - - - - - - - - - - - - - -
+
def sum_s_eff_oc(self: SHANSEP):
return self.shansep_data_df_oc['s\''].sum()
@@ -92,17 +94,44 @@ def a2_kar_oc(self: SHANSEP):
def a1_kar_oc(self: SHANSEP):
return (sum_5_pr_ondergrens_oc(self) - a2_kar_oc(self) * sum_s_eff_oc(self)) / count_s_eff_oc(self)
+# ------------------ bepaling s en pop op basis van nc proeven ----------------
+def gem_ln_su_svc_nc(self: SHANSEP):
+ shansep_data_df_nc = self.shansep_data_df_nc_oc[self.shansep_data_df_nc_oc['consolidatietype'] == 'NC']
+ return shansep_data_df_nc['LN(su/svc)'].mean()
+
+def exp_gem_ln_su_svc_nc(self: SHANSEP):
+ return math.exp(gem_ln_su_svc_nc(self))
+
+def std_ln_su_svc_nc(self: SHANSEP):
+ shansep_data_df_nc = self.shansep_data_df_nc_oc[self.shansep_data_df_nc_oc['consolidatietype'] == 'NC']
+ return shansep_data_df_nc['LN(su/svc)'].std(ddof=1)
+
+def kar_ln_su_svc_nc(self: SHANSEP):
+ return gem_ln_su_svc_nc(self) - std_ln_su_svc_nc(self)*t_n_2_oc(self) * ((1-self.alpha)+1/count_sv_oc(self))**0.5
+
+def exp_kar_ln_su_svc_nc(self: SHANSEP):
+ return math.exp(kar_ln_su_svc_nc(self))
+
+def gem_pop_oc(self: SHANSEP):
+ return self.shansep_data_df_oc['POP'].mean()
+
+def std_pop_oc(self: SHANSEP):
+ return self.shansep_data_df_oc['POP'].std(ddof=1)
+
+def kar_pop_oc(self: SHANSEP):
+ return gem_pop_oc(self) + std_pop_oc(self)*t_n_2_oc(self) * ((1-self.alpha)+1/count_sv_oc(self))**0.5
+
# -------------------------- NC en OC ---------------------------------------
-def count_sv_nc_oc(self: SHANSEP):
+def count_ln_ocr_nc_oc(self: SHANSEP):
return self.shansep_data_df_nc_oc['LN(OCR)'].count()
-def sum_sv_nc_oc(self: SHANSEP):
+def sum_ln_ocr_nc_oc(self: SHANSEP):
return self.shansep_data_df_nc_oc['LN(OCR)'].sum()
-def sum_su_nc_oc(self: SHANSEP):
- return self.shansep_data_df_nc_oc['Su'].sum()
+def sum_ln_su_svc_nc_oc(self: SHANSEP):
+ return self.shansep_data_df_nc_oc['LN(su/svc)'].sum()
def sum_sv_tt_nc_oc(self: SHANSEP):
@@ -118,24 +147,24 @@ def e_a2_nc_oc(self: SHANSEP):
def e_a1_nc_oc(self: SHANSEP):
- return (sum_su_nc_oc(self) - sum_sv_nc_oc(self) * e_a2_nc_oc(self)) / count_sv_nc_oc(self)
+ return (sum_ln_su_svc_nc_oc(self) - sum_ln_ocr_nc_oc(self) * e_a2_nc_oc(self)) / count_ln_ocr_nc_oc(self)
-def sum_kappa_2_nc_oc(self: SHANSEP):
- return self.shansep_data_df_nc_oc['kappa_2'].sum()
+def sum_chi_2_nc_oc(self: SHANSEP):
+ return self.shansep_data_df_nc_oc['chi_2'].sum()
def var_a2_nc_oc(self: SHANSEP):
- return (1 / sum_sv_tt_nc_oc(self)) * sum_kappa_2_nc_oc(self) / (count_sv_nc_oc(self) - 2)
+ return (1 / sum_sv_tt_nc_oc(self)) * sum_chi_2_nc_oc(self) / (count_ln_ocr_nc_oc(self) - 2)
def var_a1_nc_oc(self: SHANSEP):
- return 1 / count_sv_nc_oc(self) * (1 + sum_sv_nc_oc(self) ** 2 / (count_sv_nc_oc(self) * sum_sv_tt_nc_oc(self))) * sum_kappa_2_nc_oc(self) / (
- count_sv_nc_oc(self) - 2)
+ return 1 / count_ln_ocr_nc_oc(self) * (1 + sum_ln_ocr_nc_oc(self) ** 2 / (count_ln_ocr_nc_oc(self) * sum_sv_tt_nc_oc(self))) * sum_chi_2_nc_oc(self) / (
+ count_ln_ocr_nc_oc(self) - 2)
def cov_a1_a2_nc_oc(self: SHANSEP):
- return -(sum_sv_nc_oc(self) / (count_sv_nc_oc(self) * sum_sv_tt_nc_oc(self))) * sum_kappa_2_nc_oc(self) / (count_sv_nc_oc(self) - 2)
+ return -(sum_ln_ocr_nc_oc(self) / (count_ln_ocr_nc_oc(self) * sum_sv_tt_nc_oc(self))) * sum_chi_2_nc_oc(self) / (count_ln_ocr_nc_oc(self) - 2)
def rho_a1_a2_nc_oc(self: SHANSEP):
@@ -152,7 +181,7 @@ def sigma_a1_nc_oc(self: SHANSEP):
def t_n_2_nc_oc(self: SHANSEP):
significantieniveau = 0.1
- degrees_of_freedom = count_sv_nc_oc(self) - 2
+ degrees_of_freedom = count_ln_ocr_nc_oc(self) - 2
return t.ppf(1 - significantieniveau / 2, degrees_of_freedom)
def sum_s_eff_nc_oc(self: SHANSEP):
@@ -176,3 +205,87 @@ def a2_kar_nc_oc(self: SHANSEP):
def a1_kar_nc_oc(self: SHANSEP):
return (sum_5_pr_ondergrens_nc_oc(self) - a2_kar_nc_oc(self) * sum_s_eff_nc_oc(self)) / count_s_eff_nc_oc(self)
+# --------------------- stdev van resultaten -----------------------
+def st_dev_s_handmatig(self: SHANSEP):
+ """Berekent de standaarddeviatie van s op basis van gemiddelde en rekenwaarde.
+
+ Returns:
+ float or None: De standaarddeviatie van s, of None als de berekening niet mogelijk is.
+ """
+ import warnings
+
+ s_gem = self.s_gem_handmatig
+ s_kar = self.s_kar_handmatig
+
+ # Controleer op fysisch onmogelijke waarden - check None first
+ if s_gem is None or s_gem <= 0:
+ warnings.warn(f"Gemiddelde s waarde moet positief zijn voor berekenen standaarddeviatie, gevonden waarde: {s_gem}")
+ return None
+ if s_kar is None or s_kar <= 0:
+ warnings.warn(f"Karakteristieke s waarde moet positief zijn voor berekenen standaarddeviatie, gevonden waarde: {s_kar}")
+ return None
+
+ try:
+ st_dev = s_gem * math.sqrt(math.exp((((norm.ppf(0.05) * 2) + math.sqrt((norm.ppf(0.05) * 2) ** 2 +
+ 8 * (math.log(s_gem) - math.log(s_kar))))
+ / 2) ** 2) - 1)
+ return float(st_dev)
+ except Exception as e:
+ warnings.warn(f"Kon standaarddeviatie van s niet berekenen: {e}")
+ return None
+
+def st_dev_m_handmatig(self: SHANSEP):
+ """Berekent de standaarddeviatie van m op basis van gemiddelde en rekenwaarde.
+
+ Returns:
+ float or None: De standaarddeviatie van m, of None als de berekening niet mogelijk is.
+ """
+ import warnings
+
+ m_gem = self.m_gem_handmatig
+ m_kar = self.m_kar_handmatig
+
+ # Controleer op fysisch onmogelijke waarden - check None first
+ if m_gem is None or m_gem <= 0:
+ warnings.warn(f"Gemiddelde m waarde moet positief zijn voor berekenen standaarddeviatie, gevonden waarde: {m_gem}")
+ return None
+ if m_kar is None or m_kar <= 0:
+ warnings.warn(f"Karakteristieke m waarde moet positief zijn voor berekenen standaarddeviatie, gevonden waarde: {m_kar}")
+ return None
+
+ try:
+ st_dev = m_gem * math.sqrt(math.exp((((norm.ppf(0.05) * 2) + math.sqrt((norm.ppf(0.05) * 2) ** 2 +
+ 8 * (math.log(m_gem) - math.log(m_kar))))
+ / 2) ** 2) - 1)
+ return float(st_dev)
+ except Exception as e:
+ warnings.warn(f"Kon standaarddeviatie van m niet berekenen: {e}")
+ return None
+
+def st_dev_pop_handmatig(self: SHANSEP):
+ """Berekent de standaarddeviatie van pop op basis van gemiddelde en rekenwaarde.
+
+ Returns:
+ float or None: De standaarddeviatie van pop, of None als de berekening niet mogelijk is.
+ """
+ import warnings
+
+ pop_gem = self.pop_gem_handmatig
+ pop_kar = self.pop_kar_handmatig
+
+ # Controleer op fysisch onmogelijke waarden - check None first
+ if pop_gem is None or pop_gem <= 0:
+ warnings.warn(f"Gemiddelde pop waarde moet positief zijn voor berekenen standaarddeviatie, gevonden waarde: {pop_gem}")
+ return None
+ if pop_kar is None or pop_kar <= 0:
+ warnings.warn(f"Karakteristieke pop waarde moet positief zijn voor berekenen standaarddeviatie, gevonden waarde: {pop_kar}")
+ return None
+
+ try:
+ st_dev = pop_gem * math.sqrt(math.exp((((norm.ppf(0.05) * 2) + math.sqrt((norm.ppf(0.05) * 2) ** 2 +
+ 8 * (math.log(pop_gem) - math.log(pop_kar))))
+ / 2) ** 2) - 1)
+ return float(st_dev)
+ except Exception as e:
+ warnings.warn(f"Kon standaarddeviatie van pop niet berekenen: {e}")
+ return None
\ No newline at end of file
diff --git a/pv_tool/shansep_analysis/visualization_shansep.py b/pv_tool/shansep_analysis/visualization_shansep.py
new file mode 100644
index 0000000..a7eb118
--- /dev/null
+++ b/pv_tool/shansep_analysis/visualization_shansep.py
@@ -0,0 +1,514 @@
+from __future__ import annotations
+from typing import TYPE_CHECKING
+import math
+
+if TYPE_CHECKING:
+ from pv_tool.shansep_analysis.shansep_analysis import SHANSEP
+import plotly.graph_objects as go
+from typing import Optional, List
+from pv_tool.shansep_analysis.globals import (TEXTUAL_NAMES, TEXTUAL_NAMES_DSS, NEW_COLUMN_NAMES)
+import numpy as np
+
+# ----------------------------------- proefresultaten -----------------------------------
+
+def add_proefresultaten_sv_su(self: SHANSEP):
+ """Deze functie voegt de proefresultaten toe aan de figuur."""
+ boring_monsternummer = self.shansep_data_df_oc.index
+
+ x_proefresultaten = self.shansep_data_df_oc['S\'v']
+ y_proefresultaten = self.shansep_data_df_oc['Su']
+
+ self.figure.add_trace(
+ go.Scatter(
+ x=x_proefresultaten,
+ y=y_proefresultaten,
+ mode='markers',
+ marker=dict(
+ color='blue'
+ ),
+ name=f'Geanalyseerd: {self.investigation_groups[0]} (OC)',
+ text=boring_monsternummer,
+ hoverinfo='text'
+ )
+ )
+
+def add_proefresultaten_sv_su_nc(self: SHANSEP):
+ """Deze functie voegt de proefresultaten toe aan de figuur."""
+ boring_monsternummer = self.shansep_data_df_nc.index
+
+ x_proefresultaten = self.shansep_data_df_nc['S\'v']
+ y_proefresultaten = self.shansep_data_df_nc['Su']
+
+ self.figure.add_trace(
+ go.Scatter(
+ x=x_proefresultaten,
+ y=y_proefresultaten,
+ mode='markers',
+ marker=dict(
+ color='blue'
+ ),
+ name=f'Geanalyseerd: {self.investigation_groups[0]} (NC)',
+ text=boring_monsternummer,
+ hoverinfo='text'
+ )
+ )
+
+def add_proefresultaten_ln_ocr_ln_s(self: SHANSEP):
+ """Deze functie voegt de proefresultaten toe aan de figuur."""
+ boring_monsternummer = self.shansep_data_df_oc.index
+
+ x_proefresultaten = self.shansep_data_df_nc_oc['LN(OCR)']
+ y_proefresultaten = self.shansep_data_df_nc_oc['LN(su/svc)']
+
+ self.figure.add_trace(
+ go.Scatter(
+ x=x_proefresultaten,
+ y=y_proefresultaten,
+ mode='markers',
+ marker=dict(
+ color='blue'
+ ),
+ name=f'Geanalyseerd: {self.investigation_groups[0]}',
+ text=boring_monsternummer,
+ hoverinfo='text'
+ )
+ )
+
+def get_extra_data(self: SHANSEP, investigationgroups_extra: Optional[List]):
+ if self.analysis_type in ['TXT_S_POP']:
+ dataset_df = self.dbase_df[self.dbase_df['ALG__TRIAXIAAL']]
+ elif self.analysis_type in ['DSS_S_POP']:
+ dataset_df = self.dbase_df[self.dbase_df['ALG__DSS']]
+ else:
+ raise ValueError(f"analysis type for extra dataset not right: {self.analysis_type}")
+
+ dataset_df = dataset_df[
+ dataset_df['PV_NAAM'].isin(investigationgroups_extra)]
+ if self.analysis_type in ['DSS_S_POP']:
+ dataset_df = dataset_df[TEXTUAL_NAMES_DSS.get(self.effective_stress, [])]
+ else:
+ dataset_df = dataset_df[TEXTUAL_NAMES.get(self.effective_stress, [])]
+ dataset_df.columns = NEW_COLUMN_NAMES
+ return dataset_df
+
+def add_extra_proefresultaten_sv_su(self: SHANSEP, extra_groepen: Optional[List]):
+ """Deze functie voegt de proefresultaten toe aan de figuur."""
+ df = get_extra_data(self, investigationgroups_extra=extra_groepen)
+ df = df[df['consolidatietype'] == 'OC'].copy()
+ boring_monsternummer = df.index
+
+ n=0
+ for naam in df['PV_NAAM'].unique():
+
+ sub_df = df[df['PV_NAAM'] == naam]
+ x_extra_proefresultaten = sub_df['S\'v']
+ y_extra_proefresultaten = sub_df['Su']
+
+ self.figure.add_trace(
+ go.Scatter(
+ x=x_extra_proefresultaten,
+ y=y_extra_proefresultaten,
+ mode='markers',
+ name=f'Extra: {extra_groepen[n]}',
+ text=boring_monsternummer,
+ hoverinfo='text'
+ )
+ )
+ n += 1
+
+def add_extra_proefresultaten_ln_ocr_ln_s(self: SHANSEP, extra_groepen: Optional[List]):
+ """Deze functie voegt de proefresultaten toe aan de figuur."""
+ df = get_extra_data(self, investigationgroups_extra=extra_groepen)
+ boring_monsternummer = df.index
+
+ # paar zaken berekenen van deze nieuwe data
+ df.loc[:, 'LN(OCR)'] = (
+ df['OCR'].apply
+ (lambda x: np.log(x) if x is not None and x > 0 else ""))
+
+ df.loc[:, 'S'] = df['Su'] / df['S\'v']
+
+ df.loc[:, 'LN(su/svc)'] = (
+ df['S'].apply
+ (lambda x: np.log(x) if x is not None and x > 0 else ""))
+
+ n=0
+ for naam in df['PV_NAAM'].unique():
+
+ sub_df = df[df['PV_NAAM'] == naam]
+ x_extra_proefresultaten = sub_df['LN(OCR)']
+ y_extra_proefresultaten = sub_df['LN(su/svc)']
+
+ self.figure.add_trace(
+ go.Scatter(
+ x=x_extra_proefresultaten,
+ y=y_extra_proefresultaten,
+ mode='markers',
+ name=f'Extra: {extra_groepen[n]}',
+ text=boring_monsternummer,
+ hoverinfo='text'
+ )
+ )
+ n += 1
+
+def add_extra_proefresultaten_sv_su_nc(self: SHANSEP, extra_groepen: Optional[List]):
+ """Deze functie voegt de proefresultaten toe aan de figuur."""
+ df = get_extra_data(self, investigationgroups_extra=extra_groepen)
+
+ df = df[df['consolidatietype'] == 'NC'].copy()
+
+ boring_monsternummer = df.index
+
+ n=0
+ for naam in df['PV_NAAM'].unique():
+
+ sub_df = df[df['PV_NAAM'] == naam]
+ x_extra_proefresultaten = sub_df['S\'v']
+ y_extra_proefresultaten = sub_df['Su']
+
+ self.figure.add_trace(
+ go.Scatter(
+ x=x_extra_proefresultaten,
+ y=y_extra_proefresultaten,
+ mode='markers',
+ name=f'Extra: {extra_groepen[n]}',
+ text=boring_monsternummer,
+ hoverinfo='text'
+ )
+ )
+ n += 1
+
+# ----------------------------------- statistiek ----------------------------------------
+
+def add_5pr_bovengrens_sv_su(self: SHANSEP):
+ """Deze functie voegt de 5% bovengrens toe aan de figuur."""
+ x_5pr = self.shansep_data_df_oc['s\'']
+ y_5pr = self.shansep_data_df_oc['5_pr_bovengrens']
+
+ self.figure.add_trace(
+ go.Scatter(
+ x=x_5pr,
+ y=y_5pr,
+ mode='lines',
+ name='5% bovengrens',
+ line=dict(
+ color='black',
+ width=1,
+ dash='dash'
+ )
+ )
+ )
+
+def add_5pr_ondergrens_sv_su(self: SHANSEP):
+ """Deze functie voegt de 5% bovengrens toe aan de figuur."""
+ x_5pr = self.shansep_data_df_oc['s\'']
+ y_5pr = self.shansep_data_df_oc['5_pr_ondergrens']
+
+ self.figure.add_trace(
+ go.Scatter(
+ x=x_5pr,
+ y=y_5pr,
+ mode='lines',
+ name='5% ondergrens',
+ line=dict(
+ color='black',
+ width=1,
+ dash='dash'
+ )
+ )
+ )
+
+def add_5pr_bovengrens_ln_ocr_ln_s(self: SHANSEP):
+ """Deze functie voegt de 5% bovengrens toe aan de figuur."""
+ x_5pr = self.shansep_data_df_nc_oc['s\'']
+ y_5pr = self.shansep_data_df_nc_oc['5_pr_bovengrens']
+
+ self.figure.add_trace(
+ go.Scatter(
+ x=x_5pr,
+ y=y_5pr,
+ mode='lines',
+ name='5% bovengrens',
+ line=dict(
+ color='black',
+ width=1,
+ dash='dash'
+ )
+ )
+ )
+
+def add_5pr_ondergrens_ln_ocr_ln_s(self: SHANSEP):
+ """Deze functie voegt de 5% bovengrens toe aan de figuur."""
+ x_5pr = self.shansep_data_df_nc_oc['s\'']
+ y_5pr = self.shansep_data_df_nc_oc['5_pr_ondergrens']
+
+ self.figure.add_trace(
+ go.Scatter(
+ x=x_5pr,
+ y=y_5pr,
+ mode='lines',
+ name='5% ondergrens',
+ line=dict(
+ color='black',
+ width=1,
+ dash='dash'
+ )
+ )
+ )
+
+def add_fysische_realiseerbare_ondergrens_sv_su(self: SHANSEP):
+ """Deze functie voegt de fysische realiseerbare ondergrens toe aan de figuur."""
+ raaklijn_kar_x1 = 0
+ raaklijn_kar_x2 = self.shansep_data_df_oc['S\'v'].max() + 5
+
+ raaklijn_kar_y1 = self.snijpunt_kar_handmatig + (raaklijn_kar_x1 * self.s_kar_handmatig)
+ raaklijn_kar_y2 = self.snijpunt_kar_handmatig + (raaklijn_kar_x2 * self.s_kar_handmatig)
+
+ x = [raaklijn_kar_x1, raaklijn_kar_x2]
+ y = [raaklijn_kar_y1, raaklijn_kar_y2]
+
+ self.figure.add_trace(
+ go.Scatter(
+ x=x,
+ y=y,
+ mode='lines',
+ name='Fysische realiseerbare ondergrens',
+ line=dict(color='black', width=2),
+ )
+ )
+
+def add_fysische_realiseerbare_ondergrens_sv_su_nc(self: SHANSEP):
+ """Deze functie voegt de fysische realiseerbare ondergrens toe aan de figuur."""
+ raaklijn_kar_x1 = 0
+ raaklijn_kar_x2 = self.shansep_data_df_nc['S\'v'].max() + 5
+
+ raaklijn_kar_y1 = 0 + (raaklijn_kar_x1 * self.s_kar_handmatig)
+ raaklijn_kar_y2 = 0 + (raaklijn_kar_x2 * self.s_kar_handmatig)
+
+ x = [raaklijn_kar_x1, raaklijn_kar_x2]
+ y = [raaklijn_kar_y1, raaklijn_kar_y2]
+
+ self.figure.add_trace(
+ go.Scatter(
+ x=x,
+ y=y,
+ mode='lines',
+ name='Fysische realiseerbare ondergrens',
+ line=dict(color='black', width=2),
+ )
+ )
+
+def _get_helling_value(helling):
+ """Helper function om de juiste waarde uit helling te halen, ongeacht of het een float of array is."""
+ if isinstance(helling, (list, np.ndarray)):
+ return float(helling[0])
+ return float(helling)
+
+def add_lineair_fit_sv_su(self: SHANSEP):
+ """Deze functie voegt de lineaire fit van de proefresultaten toe aan de figuur."""
+ x1 = self.shansep_data_df_oc['S\'v'].min()
+ x2 = self.shansep_data_df_oc['S\'v'].max()
+
+ # lineaire fit helling berekenen
+ x_data = self.shansep_data_df_oc['S\'v'].values
+ y_data = self.shansep_data_df_oc['Su'].values
+ helling, intercept = np.polyfit(x_data, y_data, 1)
+
+ y1 = x1 * helling + intercept
+ y2 = x2 * helling + intercept
+
+ x = [x1, x2]
+ y = [y1, y2]
+
+ self.figure.add_trace(
+ go.Scatter(
+ x=x,
+ y=y,
+ mode='lines',
+ name='Lineaire fit proefresultaten',
+ line=dict(color='green', width=2),
+ )
+ )
+
+def add_lineair_fit_sv_su_nc(self: SHANSEP):
+ """Deze functie voegt de lineaire fit van de proefresultaten toe aan de figuur."""
+ x1 = self.shansep_data_df_nc['S\'v'].min()
+ x2 = self.shansep_data_df_nc['S\'v'].max()
+
+ x_data = self.shansep_data_df_nc['S\'v'].values
+ y_data = self.shansep_data_df_nc['Su'].values
+ helling, intercept = np.polyfit(x_data, y_data, 1)
+
+ y1 = x1 * helling + intercept
+ y2 = x2 * helling + intercept
+
+ x = [x1, x2]
+ y = [y1, y2]
+
+ self.figure.add_trace(
+ go.Scatter(
+ x=x,
+ y=y,
+ mode='lines',
+ name='Lineaire fit proefresultaten',
+ line=dict(color='green', width=2),
+ )
+ )
+
+def add_lineair_fit_ln_ocr_ln_s(self: SHANSEP):
+ """Deze functie voegt de lineaire fit van de proefresultaten toe aan de figuur."""
+ x1 = self.shansep_data_df_nc_oc['LN(OCR)'].min()
+ x2 = self.shansep_data_df_nc_oc['LN(OCR)'].max()
+
+ x_data = self.shansep_data_df_nc_oc['LN(OCR)'].values
+ y_data = self.shansep_data_df_nc_oc['LN(su/svc)'].values
+ helling, intercept = np.polyfit(x_data, y_data, 1)
+
+ y1 = x1 * helling + intercept
+ y2 = x2 * helling + intercept
+
+ x = [x1, x2]
+ y = [y1, y2]
+
+ self.figure.add_trace(
+ go.Scatter(
+ x=x,
+ y=y,
+ mode='lines',
+ name='Lineaire fit proefresultaten',
+ line=dict(color='green', width=2),
+ )
+ )
+
+# ----------------------------------- shansep ----------------------------------------
+
+def add_shansep_lijn_sv_su(self: SHANSEP):
+ x = self.sutabel['S\'v [kPa]'].tolist()
+ shansep_kar = self.sutabel['Su in-situ karakteristiek'].tolist()
+ shansep_gem = self.sutabel['Su in-situ gemiddeld'].tolist()
+
+ self.figure.add_trace(
+ go.Scatter(
+ x=x,
+ y=shansep_gem,
+ mode='lines',
+ name='SHANSEP gemiddelde lijn',
+ line=dict(color='purple', width=2, dash='dot')
+ )
+ )
+
+ self.figure.add_trace(
+ go.Scatter(
+ x=x,
+ y=shansep_kar,
+ mode='lines',
+ name='SHANSEP karakteristieke lijn',
+ line=dict(color='purple', width=2)
+ )
+ )
+
+def add_shansep_lijn_sv_su_nc(self: SHANSEP):
+ if self.sutabel_nc is None:
+ self.calculate_sutabel_nc()
+ x = self.sutabel_nc['S\'v [kPa]'].tolist()
+ shansep_kar = self.sutabel_nc['Su in-situ karakteristiek'].tolist()
+ shansep_gem = self.sutabel_nc['Su in-situ gemiddeld'].tolist()
+
+ self.figure.add_trace(
+ go.Scatter(
+ x=x,
+ y=shansep_gem,
+ mode='lines',
+ name='SHANSEP gemiddelde lijn',
+ line=dict(color='purple', width=2, dash='dot')
+ )
+ )
+
+ self.figure.add_trace(
+ go.Scatter(
+ x=x,
+ y=shansep_kar,
+ mode='lines',
+ name='SHANSEP karakteristieke lijn',
+ line=dict(color='purple', width=2)
+ )
+ )
+
+def add_shansep_lijn_ln_ocr_ln_s(self: SHANSEP):
+ s = [0, 1.01]
+
+ a2gem = self.m_gem_handmatig
+ a1gem = math.log(self.s_gem_handmatig)
+
+ a2kar = self.m_kar_handmatig
+ a1kar = math.log(self.s_kar_handmatig)
+
+ t_gem = [a1gem+(x*a2gem) for x in s]
+ t_kar = [a1kar+(x*a2kar) for x in s]
+
+
+ self.figure.add_trace(
+ go.Scatter(
+ x=s,
+ y=t_gem,
+ mode='lines',
+ name='SHANSEP gemiddelde lijn',
+ line=dict(color='purple', width=2, dash='dot')
+ )
+ )
+
+ self.figure.add_trace(
+ go.Scatter(
+ x=s,
+ y=t_kar,
+ mode='lines',
+ name='SHANSEP karakteristieke lijn',
+ line=dict(color='purple', width=2)
+ )
+ )
+
+# ----------------------------------- layout ----------------------------------------
+
+def set_layout_sv_su(self: SHANSEP):
+ """
+ Stelt de layout van de figuur in met titel en as-labels.
+
+ De figuurgrootte is geoptimaliseerd voor zowel schermdisplay als PDF-export.
+ """
+ title = f'Bepaling S en POP uit {self.analysis_type} proef'
+
+ xas_title = r"$\sigma'_{v} [kPa]$"
+ yas_title = r'$s_{u} [kPa]$'
+
+ legend_title = 'Legenda'
+ self.figure.update_layout(
+ width=1280,
+ height=720,
+ title=title if self.show_title else None,
+ xaxis_title=xas_title,
+ yaxis_title=yas_title,
+ legend_title=legend_title,
+ margin=dict(t=100, r=50, b=100, l=50)
+ )
+
+def set_layout_ln_ocr_ln_s(self: SHANSEP):
+ """
+ Stelt de layout van de figuur in met titel en as-labels.
+
+ De figuurgrootte is geoptimaliseerd voor zowel schermdisplay als PDF-export.
+ """
+ title = f'Bepaling op basis van S en m op {self.analysis_type} proef'
+
+ xas_title = r'$LN(OCR) [-]$'
+ yas_title = r"$LN(s_{u}/\sigma'_{v}) [-]$"
+
+ legend_title = 'Legenda'
+ self.figure.update_layout(
+ width=1280,
+ height=720,
+ title=title if self.show_title else None,
+ xaxis_title=xas_title,
+ yaxis_title=yas_title,
+ legend_title=legend_title,
+ margin=dict(t=100, r=50, b=100, l=50)
+ )
diff --git a/pv_tool/sutabel_analysis/__init__.py b/pv_tool/sutabel_analysis/__init__.py
new file mode 100644
index 0000000..c9e825b
--- /dev/null
+++ b/pv_tool/sutabel_analysis/__init__.py
@@ -0,0 +1,15 @@
+"""
+Sutabel-m Analysis Module
+
+Deze module bevat alle functionaliteit voor sutabel-m analyses.
+De sutabel-m methode is een alternatieve methode voor het bepalen van
+ongedraineerde schuifsterkte op basis van overgeconsolideerde (OC) proeven.
+
+Hoofdklasse:
+ SUTABEL: Klasse voor het uitvoeren van sutabel-m analyses
+"""
+
+from pv_tool.sutabel_analysis.sutabel_analysis import SUTABEL
+
+__all__ = ['SUTABEL']
+
diff --git a/pv_tool/sutabel_analysis/expand_analysis.py b/pv_tool/sutabel_analysis/expand_analysis.py
new file mode 100644
index 0000000..cfd40d3
--- /dev/null
+++ b/pv_tool/sutabel_analysis/expand_analysis.py
@@ -0,0 +1,134 @@
+"""
+Expand Analysis functies voor Sutabel-m analyse.
+
+Deze module bevat functies voor het uitbreiden van de analyse dataframe
+met berekende kolommen voor sutabel-m analyses.
+"""
+
+from typing import TYPE_CHECKING
+import numpy as np
+import pandas as pd
+from pandas import DataFrame
+
+if TYPE_CHECKING:
+ from pv_tool.sutabel_analysis.sutabel_analysis import SUTABEL
+
+
+def calculate_ln_sv_sutabel(self: "SUTABEL"):
+ """Berekent ln(s'v) voor de sutabel dataframe."""
+ self.sutabel_data_df.loc[:, 'ln(s\'v)'] = (
+ self.sutabel_data_df['S\'v'].apply
+ (lambda x: np.log(x) if x is not None and x > 0 else ""))
+
+def calculate_ln_su_sutabel(self: "SUTABEL"):
+ """Berekent ln(su) voor de sutabel dataframe."""
+ self.sutabel_data_df.loc[:, 'ln(su)'] = (
+ self.sutabel_data_df['Su'].apply
+ (lambda x: np.log(x) if x is not None and x > 0 else ""))
+
+def calculate_sv_tt_sutabel(self: "SUTABEL"):
+ """Berekent s_tt voor sutabel analyse."""
+ from pv_tool.sutabel_analysis.variables import count_ln_sv_sutabel, sum_ln_sv_sutabel
+ if self.sutabel_data_df is None or self.sutabel_data_df.empty:
+ raise ValueError("sutabel_data_df is None of leeg. Run de analyse en controleer je data.")
+ count = count_ln_sv_sutabel(self)
+ if count == 0:
+ raise ValueError("Aantal ln(s'v) waarden is 0. Kan niet delen door nul.")
+ df: DataFrame = self.sutabel_data_df.copy()
+ # Zorg dat kolom numeriek is
+ df['ln(s\'v)'] = pd.to_numeric(df['ln(s\'v)'], errors='coerce')
+ mean_sv = sum_ln_sv_sutabel(self) / count
+ df['s_tt'] = (df['ln(s\'v)'] - mean_sv) ** 2
+ self.sutabel_data_df = df
+
+def calculate_sv_ty_sutabel(self: "SUTABEL"):
+ """Berekent s_ty voor sutabel analyse."""
+ from pv_tool.sutabel_analysis.variables import count_ln_sv_sutabel, sum_ln_sv_sutabel
+ if self.sutabel_data_df is None or self.sutabel_data_df.empty:
+ raise ValueError("sutabel_data_df is None of leeg. Run de analyse en controleer je data.")
+ count = count_ln_sv_sutabel(self)
+ if count == 0:
+ raise ValueError("Aantal ln(s'v) waarden is 0. Kan niet delen door nul.")
+ df: DataFrame = self.sutabel_data_df.copy()
+ # Zorg dat kolommen numeriek zijn
+ df['ln(s\'v)'] = pd.to_numeric(df['ln(s\'v)'], errors='coerce')
+ df['ln(su)'] = pd.to_numeric(df['ln(su)'], errors='coerce')
+ mean_s = sum_ln_sv_sutabel(self) / count
+ df['s_ty'] = (df['ln(s\'v)'] - mean_s) * df['ln(su)']
+ self.sutabel_data_df = df
+
+def calculate_chi_2_sutabel(self: "SUTABEL"):
+ """Berekent chi_2 voor sutabel analyse."""
+ from pv_tool.sutabel_analysis.variables import e_a1_sutabel, e_a2_sutabel
+ formule = (self.sutabel_data_df['ln(su)'] - e_a1_sutabel(self) -
+ e_a2_sutabel(self) * self.sutabel_data_df['ln(s\'v)']) ** 2
+ self.sutabel_data_df['chi_2'] = formule
+
+def s_min_sutabel(self: "SUTABEL"):
+ """Berekent minimum ln(s'v) waarde voor sutabel analyse."""
+ return self.sutabel_data_df['ln(s\'v)'].min()
+
+def s_max_sutabel(self: "SUTABEL"):
+ """Berekent maximum ln(s'v) waarde voor sutabel analyse."""
+ return self.sutabel_data_df['ln(s\'v)'].max()
+
+def calculate_sv_eff_sutabel(self: "SUTABEL"):
+ """Berekent s' waarden met gelijke intervallen tussen min en max voor sutabel analyse."""
+ from pv_tool.sutabel_analysis.variables import count_ln_sv_sutabel
+ lijst = [s_min_sutabel(self)]
+ formule = (s_max_sutabel(self) - s_min_sutabel(self)) / (count_ln_sv_sutabel(self) - 1)
+ aantal_waarden = len(self.sutabel_data_df)
+ for i in range(1, aantal_waarden):
+ nieuwe_waarde = lijst[i - 1] + formule
+ lijst.append(nieuwe_waarde)
+ self.sutabel_data_df['s\''] = lijst
+
+def calculate_5pr_ondergrens_sutabel(self: "SUTABEL"):
+ """Berekent 5% ondergrens voor sutabel analyse."""
+ from pv_tool.sutabel_analysis.variables import (e_a1_sutabel, e_a2_sutabel, t_n_2_sutabel,
+ sigma_a1_sutabel, sigma_a2_sutabel,
+ rho_a1_a2_sutabel, sum_chi_2_sutabel,
+ count_ln_sv_sutabel)
+ formule = (
+ e_a1_sutabel(self) +
+ e_a2_sutabel(self) * self.sutabel_data_df['s\''] - t_n_2_sutabel(self) *
+ (sigma_a1_sutabel(self) ** 2 + self.sutabel_data_df['s\''] ** 2 * sigma_a2_sutabel(self) ** 2 +
+ 2 * rho_a1_a2_sutabel(self) * self.sutabel_data_df['s\''] * sigma_a1_sutabel(self) * sigma_a2_sutabel(self) +
+ (1.0 - self.alpha) * (sum_chi_2_sutabel(self) / (count_ln_sv_sutabel(self) - 2))) ** 0.5
+ )
+ self.sutabel_data_df['5_pr_ondergrens'] = formule
+
+def calculate_5pr_bovengrens_sutabel(self: "SUTABEL"):
+ """Berekent 5% bovengrens voor sutabel analyse."""
+ from pv_tool.sutabel_analysis.variables import (e_a1_sutabel, e_a2_sutabel, t_n_2_sutabel,
+ sigma_a1_sutabel, sigma_a2_sutabel,
+ rho_a1_a2_sutabel, sum_chi_2_sutabel,
+ count_ln_sv_sutabel)
+ formule = (
+ e_a1_sutabel(self) +
+ e_a2_sutabel(self) * self.sutabel_data_df['s\''] + t_n_2_sutabel(self) *
+ (sigma_a1_sutabel(self) ** 2 + self.sutabel_data_df['s\''] ** 2 * sigma_a2_sutabel(self) ** 2 +
+ 2 * rho_a1_a2_sutabel(self) * self.sutabel_data_df['s\''] * sigma_a1_sutabel(self) * sigma_a2_sutabel(self) +
+ (1 - self.alpha) * (sum_chi_2_sutabel(self) / (count_ln_sv_sutabel(self) - 2))) ** 0.5
+ )
+ self.sutabel_data_df['5_pr_bovengrens'] = formule
+
+def calculate_sv_tt_ondergrens_sutabel(self: "SUTABEL"):
+ """Berekent s_tt ondergrens voor sutabel analyse."""
+ from pv_tool.sutabel_analysis.variables import sum_s_eff_sutabel, count_s_eff_sutabel
+ formule = (self.sutabel_data_df['s\''] - sum_s_eff_sutabel(self) / count_s_eff_sutabel(self)) ** 2
+ self.sutabel_data_df['s_tt_ondergrens'] = formule
+
+def calculate_sv_ty_ondergrens_sutabel(self: "SUTABEL"):
+ """Berekent s_ty ondergrens voor sutabel analyse."""
+ from pv_tool.sutabel_analysis.variables import sum_s_eff_sutabel, count_s_eff_sutabel
+ formule = (self.sutabel_data_df['s\''] - sum_s_eff_sutabel(self) /
+ count_s_eff_sutabel(self)) * self.sutabel_data_df['5_pr_ondergrens']
+ self.sutabel_data_df['s_ty_ondergrens'] = formule
+
+def calculate_chi_2_ondergrens_sutabel(self: "SUTABEL"):
+ """Berekent chi_2 ondergrens voor sutabel analyse."""
+ from pv_tool.sutabel_analysis.variables import a1_kar_sutabel, a2_kar_sutabel
+ formule = (self.sutabel_data_df['5_pr_ondergrens'] - a1_kar_sutabel(self) -
+ a2_kar_sutabel(self) * self.sutabel_data_df['s\'']) ** 2
+ self.sutabel_data_df['chi_2_ondergrens'] = formule
diff --git a/pv_tool/sutabel_analysis/save_and_export.py b/pv_tool/sutabel_analysis/save_and_export.py
new file mode 100644
index 0000000..eab684d
--- /dev/null
+++ b/pv_tool/sutabel_analysis/save_and_export.py
@@ -0,0 +1,506 @@
+"""
+Save and Export functies voor Sutabel-m analyse.
+
+Deze module bevat functies voor het opslaan en exporteren van sutabel-m analyseresultaten
+naar Excel en PDF formaat.
+"""
+
+from typing import TYPE_CHECKING, List
+from pandas import ExcelWriter, concat, DataFrame, read_excel
+from datetime import datetime
+from openpyxl import load_workbook
+from reportlab.lib.pagesizes import A4, landscape
+from reportlab.lib import colors
+from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
+from reportlab.lib.enums import TA_LEFT
+from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, LongTable, PageBreak
+from pv_tool.imports.excel_utils import format_excel_sheet
+import plotly.graph_objects as go
+
+try:
+ from reportlab.platypus import Image as RLImage
+ from PIL import Image as PILImage
+ PIL_AVAILABLE = True
+except ImportError:
+ PIL_AVAILABLE = False
+ print("Waarschuwing: PIL (Pillow) is niet beschikbaar. Figuren kunnen niet aan PDF worden toegevoegd.")
+
+if TYPE_CHECKING:
+ from pv_tool.sutabel_analysis.sutabel_analysis import SUTABEL
+
+
+def add_sutabel_results_to_dbase(self: "SUTABEL", path: str, file_name: str = 'Template_PVtool5_0.xlsx'):
+ """
+ Voegt de sutabel-m analyseresultaten toe aan de database Excel-bestand.
+
+ Parameters
+ ----------
+ self : sutabel
+ Instantie van de sutabel analyse klasse
+ path : str
+ Pad naar de map waar het Excel-bestand staat
+ file_name : str
+ Naam van het Excel-bestand
+
+ Returns
+ -------
+ DataFrame
+ Bijgewerkte DataFrame met alle resultaten
+ """
+ file_path = f"{path}/{file_name}"
+
+ if self.sutabel_grafiek is None or self.e_a1_sutabel is None:
+ self._run_sutabel()
+ if hasattr(self, 'vc_fit_kar_sutabel') and self.vc_fit_kar_sutabel is not None:
+ self.get_sutabel_parameters(vc_fit_kar_sutabel=self.vc_fit_kar_sutabel)
+ else:
+ self.get_sutabel_parameters()
+
+ expected_columns = [
+ 'PVNAAM', 'PV_REK', 'PV_TYPE_PROEF', 'PV_ANALYSE', 'PV_RESULTAAT_ID', 'PV_TYPEVERZAMELING',
+ 'PV_e_a1_GEM [-]', 'PV_e_a2_GEM [-]', 'PV_svgm_GEM [kPa]', 'PV_m_GEM [-]',
+ 'PV_a1_KAR [-]', 'PV_a2_KAR [-]', 'PV_svgm_KAR [kPa]', 'PV_m_KAR [-]',
+ 'PV_vc_FIT_KAR [-]', 'PV_STDEV_LOGN_vc [-]', 'PV_STEYX [-]',
+ 'PV_VGWNAT_GEM [kN/m3]', 'PV_VGWNAT_SD [kN/m3]', 'PV_WATERGEHALTE_GEM', 'PV_WATERGEHALTE_SD',
+ 'Timestamp'
+ ]
+
+ new_row = {
+ 'PVNAAM': self.investigation_groups[0],
+ 'PV_REK': self.effective_stress,
+ 'PV_TYPE_PROEF': self.analysis_type.split('_')[0],
+ 'PV_ANALYSE': '_'.join(self.analysis_type.split('_')[1:]),
+ 'PV_RESULTAAT_ID': f"{self.investigation_groups[0]}_{self.effective_stress}_{self.analysis_type}",
+ 'PV_TYPEVERZAMELING': self.alpha,
+ 'PV_e_a1_GEM [-]': round(self.e_a1_sutabel, 6) if self.e_a1_sutabel is not None else None,
+ 'PV_e_a2_GEM [-]': round(self.e_a2_sutabel, 6) if self.e_a2_sutabel is not None else None,
+ 'PV_svgm_GEM [kPa]': round(self.svgm_gem_sutabel, 6) if self.svgm_gem_sutabel is not None else None,
+ 'PV_m_GEM [-]': round(self.m_gem_sutabel, 6) if self.m_gem_sutabel is not None else None,
+ 'PV_a1_KAR [-]': round(self.a1_kar_sutabel, 6) if self.a1_kar_sutabel is not None else None,
+ 'PV_a2_KAR [-]': round(self.a2_kar_sutabel, 6) if self.a2_kar_sutabel is not None else None,
+ 'PV_svgm_KAR [kPa]': round(self.svgm_kar_sutabel, 6) if self.svgm_kar_sutabel is not None else None,
+ 'PV_m_KAR [-]': round(self.m_kar_sutabel, 6) if self.m_kar_sutabel is not None else None,
+ 'PV_vc_FIT_KAR [-]': round(self.vc_fit_kar_sutabel, 6) if self.vc_fit_kar_sutabel is not None else None,
+ 'PV_STDEV_LOGN_vc [-]': round(self.STDEV_logn_vc_sutabel, 6) if self.STDEV_logn_vc_sutabel is not None else None,
+ 'PV_STEYX [-]': round(self.steyx_sutabel, 6) if self.steyx_sutabel is not None else None,
+ 'PV_VGWNAT_GEM [kN/m3]': round(self.calc_vgwnat_gem, 3) if self.calc_vgwnat_gem is not None else None,
+ 'PV_VGWNAT_SD [kN/m3]': round(self.calc_vgwnat_sd, 3) if self.calc_vgwnat_sd is not None else None,
+ 'PV_WATERGEHALTE_GEM': round(self.calc_watergehalte_gem, 3) if self.calc_watergehalte_gem is not None else None,
+ 'PV_WATERGEHALTE_SD': round(self.calc_watergehalte_sd, 3) if self.calc_watergehalte_sd is not None else None,
+ 'Timestamp': datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+ }
+
+ workbook = load_workbook(file_path)
+
+ if 'Resultaten SU-tabel - m' in workbook.sheetnames:
+ print('Tabblad Resultaten SU-tabel - m in dbase excel bestaat al en wordt aangevuld')
+ df_existing = read_excel(file_path, sheet_name='Resultaten SU-tabel - m')
+ df_existing = df_existing.dropna(how='all')
+ df_existing.columns = df_existing.columns.astype(str)
+ new_row_df = DataFrame([new_row], columns=df_existing.columns)
+ df_updated = concat([df_existing, new_row_df], ignore_index=True)
+ else:
+ print('Tabblad Resultaten SU-tabel - m in dbase excel bestaat nog niet en wordt aangemaakt')
+ df_updated = DataFrame([new_row], columns=expected_columns)
+
+ # Ensure all column headers are strings
+ df_updated.columns = df_updated.columns.astype(str)
+
+ # Write data to Excel
+ with ExcelWriter(file_path, mode='a', engine='openpyxl', if_sheet_exists='replace') as writer:
+ df_updated.to_excel(writer, sheet_name='Resultaten SU-tabel - m', index=False)
+
+ # Format the Excel sheet
+ format_excel_sheet(
+ file_path=file_path,
+ sheet_name='Resultaten SU-tabel - m',
+ num_columns=df_updated.shape[1],
+ num_rows=df_updated.shape[0],
+ table_name='ResultatenSUTabelMTable',
+ index=False
+ )
+
+ print(f"Sutabel resultaten toegevoegd aan database: {file_path}")
+ return df_updated
+
+
+def _create_sutabel_input_table(self: "SUTABEL") -> Table:
+ """
+ Maakt een tabel met de invoerselectie informatie voor sutabel analyse.
+
+ Returns
+ -------
+ Table
+ ReportLab tabel object met de invoerselectie informatie
+ """
+ if self.sutabel_data_df is None:
+ return Table([['Geen invoerdata beschikbaar']], hAlign='LEFT')
+
+ columns_base = ['PV_NAAM', 'BORING_POSITIE', 'MONSTER_NIVEAU_NAP_VANAF', 'MONSTER_NIVEAU_NAP_TOT',
+ 'ANA_TERREINSPANNING', 'ANA_GRENSSPANNING_REKEN', 'ANA_POP_VELD']
+
+ if self.analysis_type.startswith('TXT'):
+ columns_extra = ['TXT_SS_VOLUMEGEWICHT_NAT', 'TXT_SS_WATERGEHALTE_VOOR']
+ else:
+ columns_extra = ['DSS_VOLUMEGEWICHT_NAT', 'DSS_WATERGEHALTE_VOOR']
+
+ available_columns = [col for col in columns_base + columns_extra if col in self.total_sutabel_data_df.columns]
+
+ if not available_columns:
+ return Table([['Geen invoerdata kolommen beschikbaar']], hAlign='LEFT')
+
+ table_df = self.total_sutabel_data_df[available_columns].copy()
+
+ additional_columns = ['S\'v', 'Su', 'consolidatietype']
+ for col in additional_columns:
+ if col in self.sutabel_data_df.columns and col not in table_df.columns:
+ # Zorg ervoor dat de indices overeenkomen
+ if len(table_df) == len(self.sutabel_data_df):
+ table_df[col] = self.sutabel_data_df[col].values
+
+ column_mapping = {
+ 'PV_NAAM': 'Groep',
+ 'BORING_POSITIE': 'Pos.',
+ 'MONSTER_NIVEAU_NAP_VANAF': 'NAP Van\n[m]',
+ 'MONSTER_NIVEAU_NAP_TOT': 'NAP Tot\n[m]',
+ 'TXT_SS_VOLUMEGEWICHT_NAT': 'VGW nat\n[kN/m3]',
+ 'TXT_SS_VOLUMEGEWICHT_DRG': 'VGW drg\n[kN/m3]',
+ 'TXT_SS_WATERGEHALTE_VOOR': 'Water\n[%]',
+ 'DSS_VOLUMEGEWICHT_NAT': 'VGW nat\n[kN/m3]',
+ 'DSS_VOLUMEGEWICHT_DRG': 'VGW drg\n[kN/m3]',
+ 'DSS_WATERGEHALTE_VOOR': 'Water\n[%]',
+ 'S\'v': "σ'v\n[kPa]",
+ 'Su': "su\n[kPa]",
+ 'OCR': 'OCR\n[-]',
+ 'ANA_TERREINSPANNING': 'Terreinspanning\n[kPa]',
+ 'ANA_GRENSSPANNING_REKEN': 'Grensspanning\n[kPa]',
+ 'ANA_POP_VELD': 'POP\n[-]'
+ }
+
+ table_df = table_df.rename(columns={k: v for k, v in column_mapping.items() if k in table_df.columns})
+
+ for col in table_df.columns:
+ if table_df[col].dtype in ['float64', 'float32']:
+ table_df[col] = table_df[col].round(3)
+
+ from reportlab.platypus import Paragraph
+ header_paragraphs = []
+ for col in table_df.columns:
+ col_display = col.replace('_', '_
')
+ header_paragraphs.append(Paragraph(f'{col_display}', getSampleStyleSheet()['Normal']))
+
+ data = table_df.values.tolist()
+ t_data = [header_paragraphs] + data
+
+ t1 = LongTable(t_data, repeatRows=1, hAlign='LEFT')
+ t1.setStyle(TableStyle([
+ ('ALIGN', (0, 0), (-1, -1), 'LEFT'),
+ ('GRID', (0, 0), (-1, -1), 0.5, colors.black),
+ ('FONTNAME', (0, 1), (-1, -1), 'Helvetica'),
+ ('FONTSIZE', (0, 1), (-1, -1), 7),
+ ('TOPPADDING', (0, 0), (-1, -1), 2),
+ ('BOTTOMPADDING', (0, 0), (-1, -1), 2),
+ ('ROWBACKGROUNDS', (0, 0), (-1, 0), [colors.lightgrey]),
+ ]))
+ return t1
+
+
+def _create_sutabel_parameters_table(self: "SUTABEL") -> Table:
+ """
+ Maakt een tabel met de sutabel-m parameters.
+
+ Returns
+ -------
+ Table
+ ReportLab tabel object met de sutabel-m parameters
+ """
+ parameters: List[list] = []
+
+ if hasattr(self, 'e_a1_sutabel') and self.e_a1_sutabel is not None:
+ parameters.append(['e_a1 (snijpunt gemiddeld) [-]', f"{self.e_a1_sutabel:.6f}"])
+ if hasattr(self, 'e_a2_sutabel') and self.e_a2_sutabel is not None:
+ parameters.append(['e_a2 (helling gemiddeld) [-]', f"{self.e_a2_sutabel:.6f}"])
+ if hasattr(self, 'svgm_gem_sutabel') and self.svgm_gem_sutabel is not None:
+ parameters.append(['svgm_gem [kPa]', f"{self.svgm_gem_sutabel:.6f}"])
+ if hasattr(self, 'm_gem_sutabel') and self.m_gem_sutabel is not None:
+ parameters.append(['m_gem [-]', f"{self.m_gem_sutabel:.6f}"])
+
+ if hasattr(self, 'a1_kar_sutabel') and self.a1_kar_sutabel is not None:
+ parameters.append(['a1_kar (snijpunt karakteristiek) [-]', f"{self.a1_kar_sutabel:.6f}"])
+ if hasattr(self, 'a2_kar_sutabel') and self.a2_kar_sutabel is not None:
+ parameters.append(['a2_kar (helling karakteristiek) [-]', f"{self.a2_kar_sutabel:.6f}"])
+ if hasattr(self, 'svgm_kar_sutabel') and self.svgm_kar_sutabel is not None:
+ parameters.append(['svgm_kar [kPa]', f"{self.svgm_kar_sutabel:.6f}"])
+ if hasattr(self, 'm_kar_sutabel') and self.m_kar_sutabel is not None:
+ parameters.append(['m_kar [-]', f"{self.m_kar_sutabel:.6f}"])
+
+ if hasattr(self, 'vc_fit_kar_sutabel') and self.vc_fit_kar_sutabel is not None:
+ parameters.append(['vc_fit_kar [-]', f"{self.vc_fit_kar_sutabel:.6f}"])
+ if hasattr(self, 'STDEV_logn_vc_sutabel') and self.STDEV_logn_vc_sutabel is not None:
+ parameters.append(['STDEV lognormaal [-]', f"{self.STDEV_logn_vc_sutabel:.6f}"])
+ if hasattr(self, 'steyx_sutabel') and self.steyx_sutabel is not None:
+ parameters.append(['STEYX [-]', f"{self.steyx_sutabel:.6f}"])
+
+ parameters.append(['Type verzameling: lokaal = 1.0; regionaal = 0.75', f"{self.alpha:.2f}"])
+
+ if hasattr(self, 'calc_vgwnat_gem') and self.calc_vgwnat_gem is not None:
+ parameters.append(['VGW nat gemiddeld [kN/m3]', f"{self.calc_vgwnat_gem:.3f}"])
+ if hasattr(self, 'calc_watergehalte_gem') and self.calc_watergehalte_gem is not None:
+ parameters.append(['Watergehalte gemiddeld', f"{self.calc_watergehalte_gem:.3f}"])
+
+ t = Table([['Parameter', 'Waarde']] + parameters, hAlign='LEFT')
+ t.setStyle(TableStyle([
+ ('ALIGN', (0, 0), (-1, -1), 'LEFT'),
+ ('BACKGROUND', (0, 0), (-1, 0), colors.lightgrey),
+ ('GRID', (0, 0), (-1, -1), 0.5, colors.black),
+ ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
+ ('FONTSIZE', (0, 0), (-1, 0), 10),
+ ('FONTSIZE', (0, 1), (-1, -1), 9),
+ ]))
+ return t
+
+
+def _create_sutabel_grafiek_table(self: "SUTABEL") -> Table:
+ """
+ Maakt een tabel met de sutabel grafiek data (su_gem en su_kar lijnen).
+
+ Returns
+ -------
+ Table
+ ReportLab tabel object met de sutabel grafiek data
+ """
+ if self.sutabel_grafiek is None:
+ return Table([['Geen sutabel grafiek data beschikbaar']], hAlign='LEFT')
+
+ df = self.sutabel_grafiek.copy()
+ for col in df.columns:
+ if df[col].dtype in ['float64', 'float32']:
+ df[col] = df[col].round(3)
+
+ header = df.columns.tolist()
+ data = df.values.tolist()
+ t_data = [header] + data
+
+ t = Table(t_data, hAlign='LEFT')
+ t.setStyle(TableStyle([
+ ('ALIGN', (0, 0), (-1, -1), 'LEFT'),
+ ('BACKGROUND', (0, 0), (-1, 0), colors.lightgrey),
+ ('GRID', (0, 0), (-1, -1), 0.5, colors.black),
+ ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
+ ('FONTSIZE', (0, 0), (-1, 0), 10),
+ ('FONTSIZE', (0, 1), (-1, -1), 9),
+ ]))
+ return t
+
+
+def _create_su_fit_vc_table(self: "SUTABEL") -> Table:
+ """
+ Maakt een tabel met de su fit constante vc data.
+
+ Returns
+ -------
+ Table
+ ReportLab tabel object met de su fit vc data
+ """
+ if self.su_fit_constante_vc is None:
+ return Table([['Geen vc fit data beschikbaar (vc_fit_kar niet opgegeven)']], hAlign='LEFT')
+
+ df = self.su_fit_constante_vc.copy()
+ for col in df.columns:
+ if df[col].dtype in ['float64', 'float32']:
+ df[col] = df[col].round(3)
+
+ header = df.columns.tolist()
+ data = df.values.tolist()
+ t_data = [header] + data
+
+ t = Table(t_data, hAlign='LEFT')
+ t.setStyle(TableStyle([
+ ('ALIGN', (0, 0), (-1, -1), 'LEFT'),
+ ('BACKGROUND', (0, 0), (-1, 0), colors.lightgrey),
+ ('GRID', (0, 0), (-1, -1), 0.5, colors.black),
+ ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
+ ('FONTSIZE', (0, 0), (-1, 0), 10),
+ ('FONTSIZE', (0, 1), (-1, -1), 9),
+ ]))
+ return t
+
+
+def save_sutabel_to_pdf(self: "SUTABEL", path: str, vc_fit_kar: float = None) -> str:
+ """
+ Slaat de sutabel-m analyseresultaten op in een PDF-document.
+
+ De PDF bevat:
+ - Titel met analysedetails
+ - Beide overzichtsfiguren (ln(s'v)-ln(su) en s'v-su)
+ - Tabel met parameters
+ - Tabel met sutabel grafiek data (su_gem en su_kar)
+ - Tabel met su fit constante vc data (indien van toepassing)
+ - Tabel met invoerselectie informatie
+
+ Parameters
+ ----------
+ path : str
+ Map locatie waar het PDF-bestand moet worden opgeslagen
+ vc_fit_kar : float, optioneel
+ Coefficient of Variation voor de fit
+
+ Returns
+ -------
+ str
+ Het absolute bestandspad van het aangemaakte PDF-bestand
+ """
+ title = f"Sutabel-m analyse met {self.effective_stress} op {self.investigation_groups[0]}"
+ file_name = f"sutabel_pdf_export_{self.investigation_groups[0]}_{self.analysis_type}_{str(self.effective_stress).replace('%', 'procent_').replace(' ', '')}.pdf"
+ file_path = f"{path}/{file_name}"
+
+ self._run_sutabel()
+ if vc_fit_kar is not None:
+ self.get_sutabel_parameters(vc_fit_kar_sutabel=vc_fit_kar)
+ elif hasattr(self, 'vc_fit_kar_sutabel') and self.vc_fit_kar_sutabel is not None:
+ self.get_sutabel_parameters(vc_fit_kar_sutabel=self.vc_fit_kar_sutabel)
+ else:
+ self.get_sutabel_parameters()
+
+ if self.sutabel_grafiek is None:
+ self.calculate_sutabel_grafiek()
+
+ doc = SimpleDocTemplate(file_path, pagesize=landscape(A4))
+ styles = getSampleStyleSheet()
+
+ if 'Left' not in styles:
+ styles.add(ParagraphStyle(name='Left', parent=styles['Normal'], alignment=TA_LEFT))
+ if 'TitleLeft' not in styles:
+ styles.add(ParagraphStyle(name='TitleLeft', parent=styles['Title'], alignment=TA_LEFT))
+ if 'Heading3' not in styles:
+ styles.add(ParagraphStyle(name='Heading3', parent=styles['Heading2'], alignment=TA_LEFT))
+
+ story = []
+
+ story.append(Paragraph(title, styles['TitleLeft']))
+ story.append(Spacer(width=1, height=12))
+
+ if not PIL_AVAILABLE:
+ story.append(Paragraph("Figuren kunnen niet worden toegevoegd: PIL (Pillow) niet beschikbaar", styles['Normal']))
+ story.append(Spacer(width=1, height=12))
+ else:
+ try:
+ fig_path1 = f"{path}/temp_sutabel_plot1.png"
+ self.show_title = False
+
+ self.figure = go.Figure()
+ self.set_figure_sv_su_sutabel()
+
+ if hasattr(self, 'figure') and self.figure is not None:
+ fig_width = 1280
+ fig_height = 720
+ self.figure.write_image(fig_path1, width=fig_width, height=fig_height, scale=4, format="png")
+
+ title_and_heading_height = 100
+ available_height = doc.height - title_and_heading_height
+
+ with PILImage.open(fig_path1) as im:
+ img_width_px, img_height_px = im.size
+
+ max_width_pt = doc.width * 0.95
+ aspect = img_height_px / img_width_px
+
+ img_width_pt = min(max_width_pt, doc.width)
+ img_height_pt = img_width_pt * aspect
+
+ if img_height_pt > available_height:
+ img_height_pt = available_height * 0.95
+ img_width_pt = img_height_pt / aspect
+
+ # Maak ReportLab Image aan
+ img1 = RLImage(fig_path1)
+ img1.drawWidth = img_width_pt
+ img1.drawHeight = img_height_pt
+ img1.hAlign = 'LEFT'
+
+ story.append(Paragraph("Figuur 1: ln(s'v) vs ln(su)", styles['Heading2']))
+ story.append(Spacer(width=1, height=6))
+ story.append(img1)
+ story.append(PageBreak())
+ else:
+ story.append(Paragraph("Figuur 1 kon niet worden gegenereerd", styles['Normal']))
+ story.append(Spacer(width=1, height=12))
+ except Exception as e:
+ story.append(Paragraph(f"Fout bij genereren Figuur 1: {str(e)}", styles['Normal']))
+ story.append(Spacer(width=1, height=12))
+
+ try:
+ fig_path2 = f"{path}/temp_sutabel_plot2.png"
+ self.show_title = False
+
+ self.figure = go.Figure()
+ self.set_figure_ln_sv_ln_su_sutabel()
+
+ if hasattr(self, 'figure') and self.figure is not None:
+ fig_width = 1280
+ fig_height = 720
+ self.figure.write_image(fig_path2, width=fig_width, height=fig_height, scale=4, format="png")
+
+ with PILImage.open(fig_path2) as im:
+ img_width_px, img_height_px = im.size
+
+ max_width_pt = doc.width * 0.95
+ aspect = img_height_px / img_width_px
+
+ img_width_pt = min(max_width_pt, doc.width)
+ img_height_pt = img_width_pt * aspect
+
+ available_height = doc.height - 50
+
+ if img_height_pt > available_height:
+ img_height_pt = available_height * 0.95
+ img_width_pt = img_height_pt / aspect
+
+ img2 = RLImage(fig_path2)
+ img2.drawWidth = img_width_pt
+ img2.drawHeight = img_height_pt
+ img2.hAlign = 'LEFT'
+
+ story.append(Paragraph("Figuur 2: s'v vs su", styles['Heading2']))
+ story.append(Spacer(width=1, height=6))
+ story.append(img2)
+ story.append(PageBreak())
+ else:
+ story.append(Paragraph("Figuur 2 kon niet worden gegenereerd", styles['Normal']))
+ story.append(Spacer(width=1, height=12))
+ except Exception as e:
+ story.append(Paragraph(f"Fout bij genereren Figuur 2: {str(e)}", styles['Normal']))
+ story.append(Spacer(width=1, height=12))
+
+ # Voeg parameters tabel toe
+ story.append(Paragraph("Parameters", styles['Heading2']))
+ story.append(Spacer(width=1, height=6))
+ story.append(_create_sutabel_parameters_table(self))
+ story.append(Spacer(width=1, height=12))
+
+ # Voeg sutabel grafiek data toe
+ story.append(Paragraph("Sutabel grafiek data (su_gem en su_kar lijnen)", styles['Heading2']))
+ story.append(Spacer(width=1, height=6))
+ story.append(_create_sutabel_grafiek_table(self))
+ story.append(Spacer(width=1, height=12))
+
+ # Voeg su fit vc data toe (indien beschikbaar)
+ if self.su_fit_constante_vc is not None:
+ story.append(Paragraph("Su fit met constante vc data", styles['Heading2']))
+ story.append(Spacer(width=1, height=6))
+ story.append(_create_su_fit_vc_table(self))
+ story.append(Spacer(width=1, height=12))
+
+ story.append(PageBreak())
+ story.append(Paragraph("Invoerselectie", styles['Heading2']))
+ story.append(Spacer(width=1, height=6))
+ story.append(_create_sutabel_input_table(self))
+
+ try:
+ doc.build(story)
+ print(f"Sutabel PDF export voltooid: {file_path}")
+ return file_path
+ except Exception as e:
+ print(f"Fout bij maken PDF: {e}")
+ raise
+
diff --git a/pv_tool/sutabel_analysis/sutabel_analysis.py b/pv_tool/sutabel_analysis/sutabel_analysis.py
new file mode 100644
index 0000000..c4f6ce0
--- /dev/null
+++ b/pv_tool/sutabel_analysis/sutabel_analysis.py
@@ -0,0 +1,630 @@
+"""
+SUTABEL Analysis Class
+
+Deze module bevat de SUTABEL klasse voor het uitvoeren van sutabel-m analyses.
+De sutabel-m methode analyseert overgeconsolideerde (OC) triaxiaal of DSS proeven
+om de ongedraineerde schuifsterkte te bepalen.
+"""
+
+import math
+from pv_tool.imports.import_data import Dbase
+from typing import Optional, List, Literal
+from pv_tool.shansep_analysis.globals import (TEXTUAL_NAMES, NEW_COLUMN_NAMES, TEXTUAL_NAMES_DSS)
+from pandas import DataFrame, ExcelWriter
+import plotly.graph_objects as go
+from pv_tool.shansep_analysis.calc_parameters import (
+ calc_watergehalte_gem_txt, calc_watergehalte_gem_dss,
+ calc_watergehalte_sd_txt, calc_watergehalte_sd_dss,
+ calc_vgwnat_gem_txt, calc_vgwnat_gem_dss,
+ calc_vgwnat_sd_txt, calc_vgwnat_sd_dss
+)
+from pandas import read_excel
+
+
+class SUTABEL:
+ """
+ Klasse voor het uitvoeren van sutabel-m analyses.
+
+ De sutabel-m methode analyseert overgeconsolideerde (OC) triaxiaal of DSS proeven
+ om parameters te bepalen voor het berekenen van ongedraineerde schuifsterkte.
+
+ Attributes
+ ----------
+ dbase : Dbase
+ Database object met proefgegevens
+ analysis_type : str
+ Type analyse ('TXT_su_tabel' of 'DSS_su_tabel')
+ investigation_groups : List[str]
+ Lijst met te analyseren proevenverzamelingen
+ effective_stress : str
+ Effectieve spanning niveau (bijv. '15% rek')
+ alpha : float
+ Type verzameling (1.0 = lokaal, 0.75 = regionaal)
+
+ Parameters (berekend)
+ ----------
+ e_a1_sutabel : float
+ Snijpunt gemiddeld in ln-ruimte
+ e_a2_sutabel : float
+ Helling gemiddeld in ln-ruimte
+ svgm_gem_sutabel : float
+ exp(e_a1) - sutabel parameter gemiddeld [kPa]
+ m_gem_sutabel : float
+ 1 - e_a2 - exponent parameter gemiddeld
+ a1_kar_sutabel : float
+ Snijpunt karakteristiek in ln-ruimte
+ a2_kar_sutabel : float
+ Helling karakteristiek in ln-ruimte
+ svgm_kar_sutabel : float
+ exp(a1_kar) - sutabel parameter karakteristiek [kPa]
+ m_kar_sutabel : float
+ 1 - a2_kar - exponent parameter karakteristiek
+ vc_fit_kar_sutabel : float
+ Coefficient of Variation voor fit (user input)
+ STDEV_logn_vc_sutabel : float
+ sqrt(LN(1 + vc^2)) - standaarddeviatie lognormaal
+ steyx_sutabel : float
+ Standaardfout van de schatting
+ """
+
+ def __init__(self,
+ dbase: Dbase,
+ analysis_type: Literal['TXT_su_tabel', 'DSS_su_tabel'],
+ investigation_groups: List[str],
+ effective_stress: str):
+ """
+ Initialiseert een SUTABEL analyse instantie.
+
+ Parameters
+ ----------
+ dbase : Dbase
+ Database object met proefgegevens
+ analysis_type : Literal['TXT_su_tabel', 'DSS_su_tabel']
+ Type analyse
+ investigation_groups : List[str]
+ Lijst met te analyseren proevenverzamelingen
+ effective_stress : str
+ Effectieve spanning niveau (bijv. '15% rek')
+ alpha : float, optional
+ Type verzameling (1.0 = lokaal, 0.75 = regionaal), default 0.75
+ """
+ self.dbase = dbase
+ self.dbase_df = dbase.dbase_df
+ self.analysis_type = analysis_type
+ self.investigation_groups = investigation_groups
+ self.effective_stress = effective_stress
+ self.alpha = 0.75
+ self.show_title = True
+
+ # Berekende watergehalte en volumegewicht
+ self.calc_watergehalte_gem: Optional[float] = None
+ self.calc_watergehalte_sd: Optional[float] = None
+ self.calc_vgwnat_gem: Optional[float] = None
+ self.calc_vgwnat_sd: Optional[float] = None
+
+ # Sutabel basis parameters
+ self.e_a2_sutabel: Optional[float] = None
+ self.e_a1_sutabel: Optional[float] = None
+ self.a2_kar_sutabel: Optional[float] = None
+ self.a1_kar_sutabel: Optional[float] = None
+ self.steyx_sutabel: Optional[float] = None
+
+ # Sutabel afgeleide parameters voor grafieken
+ self.svgm_gem_sutabel: Optional[float] = None # exp(e_a1)
+ self.m_gem_sutabel: Optional[float] = None # 1 - e_a2
+ self.svgm_kar_sutabel: Optional[float] = None # exp(a1_kar)
+ self.m_kar_sutabel: Optional[float] = None # 1 - a2_kar
+ self.vc_fit_kar_sutabel: Optional[float] = None # User input
+ self.STDEV_logn_vc_sutabel: Optional[float] = None # sqrt(LN(1 + vc^2))
+
+ # Handmatige parameters
+ self.parameters_handmatig: bool = False
+ self.a1_kar_handmatig: Optional[float] = None
+ self.a2_kar_handmatig: Optional[float] = None
+ self.vc_fit_kar_handmatig: Optional[float] = None
+
+ # Dataframes
+ self.sutabel_data_df: Optional[DataFrame] = None
+ self.total_sutabel_data_df: Optional[DataFrame] = None
+ self.sutabel_filtered_data_df: Optional[DataFrame] = None
+ self.sutabel_grafiek: Optional[DataFrame] = None
+ self.su_fit_constante_vc: Optional[DataFrame] = None
+
+ # Figure voor plotly
+ self.figure: Optional[go.Figure] = None
+
+ # ========= Instelling en Data Ophalen Methodes ==========
+
+ def apply_settings(self, alpha: Optional[float] = None):
+ """Met deze functie kan je de alpha en materiaalfactoren opgeven."""
+ self.alpha = alpha if alpha is not None else self.alpha
+
+ def get_sutabel_data(self):
+ """
+ Haalt de relevante proefgegevens op uit de database.
+
+ Filtert de database op basis van:
+ - Analysis type (TXT of DSS)
+ - Investigation groups (PV_NAAM)
+ - Effective stress niveau
+
+ Maakt self.sutabel_data_df en self.total_sutabel_data_df aan.
+ """
+ if self.analysis_type in ['TXT_su_tabel']:
+ self.sutabel_data_df = self.dbase_df[self.dbase_df['ALG__TRIAXIAAL']].copy()
+ self.sutabel_data_df = self.sutabel_data_df[self.sutabel_data_df['PV_NAAM'].isin(
+ self.investigation_groups)].copy()
+ self.calc_watergehalte_gem = calc_watergehalte_gem_txt(self.sutabel_data_df)
+ self.calc_watergehalte_sd = calc_watergehalte_sd_txt(self.sutabel_data_df)
+ self.calc_vgwnat_gem = calc_vgwnat_gem_txt(self.sutabel_data_df)
+ self.calc_vgwnat_sd = calc_vgwnat_sd_txt(self.sutabel_data_df)
+ self.total_sutabel_data_df = self.sutabel_data_df.copy()
+ self.sutabel_data_df = self.sutabel_data_df[TEXTUAL_NAMES.get(self.effective_stress, [])].copy()
+
+ elif self.analysis_type in ['DSS_su_tabel']:
+ self.sutabel_data_df = self.dbase_df[self.dbase_df['ALG__DSS']].copy()
+ self.sutabel_data_df = self.sutabel_data_df[self.sutabel_data_df['PV_NAAM'].isin(
+ self.investigation_groups)].copy()
+ self.calc_watergehalte_gem = calc_watergehalte_gem_dss(self.sutabel_data_df)
+ self.calc_watergehalte_sd = calc_watergehalte_sd_dss(self.sutabel_data_df)
+ self.calc_vgwnat_gem = calc_vgwnat_gem_dss(self.sutabel_data_df)
+ self.calc_vgwnat_sd = calc_vgwnat_sd_dss(self.sutabel_data_df)
+ self.total_sutabel_data_df = self.sutabel_data_df.copy()
+ self.sutabel_data_df = self.sutabel_data_df[TEXTUAL_NAMES_DSS.get(self.effective_stress, [])].copy()
+
+ self.sutabel_data_df.columns = NEW_COLUMN_NAMES
+
+ def get_previous_results(self, path: str, file_name: str):
+ """
+ Zoekt naar eerdere analyseresultaten in een Excel-bestand.
+
+ Parameters
+ ----------
+ path: str
+ Pad naar de map waar het Excel-bestand staat
+ file_name: str
+ Naam van het Excel-bestand
+
+ Returns
+ -------
+ DataFrame of None
+ DataFrame met eerdere resultaten als deze gevonden zijn, anders None
+ """
+
+ file_path = f"{path}/{file_name}"
+
+ try:
+ with open(file_path, 'r'):
+ pass
+ except FileNotFoundError:
+ raise FileNotFoundError(f"Er is geen dbase aanwezig op de locatie {file_path}.")
+
+ try:
+ results_df = read_excel(file_path, sheet_name='Resultaten')
+ except ValueError:
+ print("Er is geen tabblad 'Resultaten' aanwezig in het Excel-bestand.")
+ return None
+
+ filtered_df = results_df[
+ (results_df['PV_RESULTAAT_ID'].str.contains(self.investigation_groups[0])) &
+ (results_df['PV_RESULTAAT_ID'].str.contains(self.effective_stress)) &
+ (results_df['PV_RESULTAAT_ID'].str.contains(self.analysis_type))
+ ]
+
+ if filtered_df.empty:
+ print("Er zijn geen eerdere resultaten gevonden voor de opgegeven parameters.")
+ return None
+
+ latest_entry = filtered_df.sort_values(by='Timestamp', ascending=False).iloc[0]
+
+ return latest_entry
+
+ def expand_analysis_df_sutabel(self):
+ """
+ Berekent alle benodigde parameters per monster voor de sutabel-m analyse.
+
+ Filtert op OC proeven en maakt kolommen aan voor:
+ - ln(s'v) en ln(su)
+ - Regressie componenten (s_tt, s_ty, chi_2)
+ - Effectieve spanning waarden
+ - 5% boven- en ondergrenzen
+ - Ondergrens parameters
+ """
+ from pv_tool.sutabel_analysis.expand_analysis import (
+ calculate_ln_sv_sutabel, calculate_ln_su_sutabel,
+ calculate_sv_tt_sutabel, calculate_sv_ty_sutabel,
+ calculate_chi_2_sutabel, calculate_sv_eff_sutabel,
+ calculate_5pr_ondergrens_sutabel, calculate_5pr_bovengrens_sutabel,
+ calculate_sv_tt_ondergrens_sutabel, calculate_sv_ty_ondergrens_sutabel,
+ calculate_chi_2_ondergrens_sutabel
+ )
+
+ # Filter op alleen OC proeven
+ self.sutabel_filtered_data_df = self.sutabel_data_df[
+ self.sutabel_data_df['consolidatietype'] == 'OC'
+ ].copy()
+
+ # Update sutabel_data_df to work with the OC-filtered data
+ self.sutabel_data_df = self.sutabel_filtered_data_df.copy()
+
+ # Bereken ln(s'v) en ln(su) kolommen
+ calculate_ln_sv_sutabel(self)
+ calculate_ln_su_sutabel(self)
+
+ # Bereken s_tt en s_ty voor lineaire regressie
+ calculate_sv_tt_sutabel(self)
+ calculate_sv_ty_sutabel(self)
+
+ # Bereken chi_2
+ calculate_chi_2_sutabel(self)
+
+ # Bereken effectieve spanning waarden
+ calculate_sv_eff_sutabel(self)
+
+ # Bereken 5% onder- en bovengrenzen
+ calculate_5pr_ondergrens_sutabel(self)
+ calculate_5pr_bovengrens_sutabel(self)
+
+ # Bereken s_tt, s_ty en chi_2 voor ondergrenzen
+ calculate_sv_tt_ondergrens_sutabel(self)
+ calculate_sv_ty_ondergrens_sutabel(self)
+ calculate_chi_2_ondergrens_sutabel(self)
+
+ def get_sutabel_parameters(self, vc_fit_kar_sutabel: Optional[float] = None):
+ """
+ Berekent de sutabel-m parameters.
+
+ Parameters
+ ----------
+ vc_fit_kar_sutabel : float, optioneel
+ Coefficient of Variation voor sutabel fit (user input)
+ """
+ from pv_tool.sutabel_analysis.variables import (
+ e_a2_sutabel, e_a1_sutabel,
+ a2_kar_sutabel, a1_kar_sutabel,
+ steyx_sutabel
+ )
+
+ self.e_a2_sutabel = e_a2_sutabel(self)
+ self.e_a1_sutabel = e_a1_sutabel(self)
+ self.a2_kar_sutabel = a2_kar_sutabel(self)
+ self.a1_kar_sutabel = a1_kar_sutabel(self)
+ self.steyx_sutabel = steyx_sutabel(self)
+
+ # Bereken afgeleide parameters voor grafiek
+ self.svgm_gem_sutabel = math.exp(self.e_a1_sutabel)
+ self.m_gem_sutabel = 1 - self.e_a2_sutabel
+ self.svgm_kar_sutabel = math.exp(self.a1_kar_sutabel)
+ self.m_kar_sutabel = 1 - self.a2_kar_sutabel
+
+ # Sla vc_fit_kar op (kan None zijn als niet opgegeven)
+ if vc_fit_kar_sutabel is not None:
+ self.vc_fit_kar_sutabel = vc_fit_kar_sutabel
+ self.STDEV_logn_vc_sutabel = math.sqrt(math.log(1 + (self.vc_fit_kar_sutabel ** 2)))
+ else:
+ self.vc_fit_kar_sutabel = None
+ self.STDEV_logn_vc_sutabel = None
+
+ def calculate_sutabel_grafiek(self):
+ """
+ Berekent de dataframes voor sutabel grafiek lijnen.
+
+ Maakt twee dataframes:
+ - sutabel_grafiek: bevat su_gem en su_kar lijnen
+ - su_fit_constante_vc: bevat su_kar fit met constante vc lijn
+ """
+ import numpy as np
+ from scipy.stats import lognorm
+
+ # Bepaal s'v waarden voor de grafiek
+ max_sv = self.sutabel_data_df['S\'v'].max()
+ sv_values = [1, 5, 10, 20, 30, 40, max_sv]
+
+ # Bereken su_gem en su_kar
+ su_gem_values = [self.svgm_gem_sutabel * (sv ** (1 - self.m_gem_sutabel)) for sv in sv_values]
+ su_kar_values = [self.svgm_kar_sutabel * (sv ** (1 - self.m_kar_sutabel)) for sv in sv_values]
+
+ # Maak sutabel_grafiek dataframe
+ self.sutabel_grafiek = DataFrame({
+ "s'v [kPa]": sv_values,
+ "su_gem [kPa]": su_gem_values,
+ "su_kar [kPa]": su_kar_values
+ })
+
+ # Als vc_fit_kar is opgegeven, bereken ook de constante vc fit
+ if self.vc_fit_kar_sutabel is not None and self.STDEV_logn_vc_sutabel is not None:
+ # Bereken ln waarden
+ ln_su_gem = [math.log(su) - 0.5 * (self.STDEV_logn_vc_sutabel ** 2) for su in su_gem_values]
+ ln_su_kar = [math.log(su) - 0.5 * (self.STDEV_logn_vc_sutabel ** 2) for su in su_kar_values]
+
+ # Bereken su_kar fit met constante vc
+ su_kar_fit_vc = [lognorm.ppf(0.05, s=self.STDEV_logn_vc_sutabel, scale=math.exp(ln))
+ for ln in ln_su_gem]
+
+ self.su_fit_constante_vc = DataFrame({
+ "s'v [kPa]": sv_values,
+ "ln(su_gem) [kPa]": ln_su_gem,
+ "ln(su_kar) [kPa]": ln_su_kar,
+ "su_kar fit met constante vc [kPa]": su_kar_fit_vc
+ })
+ else:
+ self.su_fit_constante_vc = None
+
+ def _run_sutabel(self):
+ """
+ Voert de volledige sutabel analyse uit in de juiste volgorde.
+ """
+ self.get_sutabel_data()
+ self.expand_analysis_df_sutabel()
+ self.get_sutabel_parameters()
+
+ # ========== Handmatige Parameters en wegschrijven ==========
+
+ def set_manual_parameters(self,
+ a1_kar: Optional[float] = None,
+ a2_kar: Optional[float] = None,
+ vc_fit_kar: Optional[float] = None):
+ """
+ Stelt handmatige parameters in voor de sutabel analyse.
+
+ Na het instellen van handmatige parameters worden deze gebruikt in plaats van
+ de berekende parameters. Dit is handig voor iteratieve analyses waarbij de
+ gebruiker verschillende parameterwaarden wil testen.
+
+ De analyse wordt automatisch uitgevoerd als deze nog niet is gedaan.
+
+ Parameters
+ ----------
+ a1_kar : float, optioneel
+ Handmatig ingesteld karakteristiek snijpunt in ln-ruimte
+ a2_kar : float, optioneel
+ Handmatig ingestelde karakteristieke helling in ln-ruimte
+ vc_fit_kar : float, optioneel
+ Handmatig ingestelde Coefficient of Variation voor fit
+
+ Examples
+ --------
+ >>> sutabel = SUTABEL(...)
+ >>> sutabel.show_figure_sv_su_sutabel() # Automatisch analyse
+ >>>
+ >>> # Pas parameters aan en analyseer opnieuw
+ >>> sutabel.set_manual_parameters(a1_kar=0.85, a2_kar=0.70, vc_fit_kar=0.25)
+ >>> sutabel.show_figure_sv_su_sutabel() # Gebruikt nu handmatige parameters
+ """
+ # Zorg dat analyse is uitgevoerd voordat handmatige parameters worden ingesteld
+ if self.sutabel_data_df is None:
+ self._run_sutabel()
+
+ self.parameters_handmatig = True
+
+ # Sla handmatige parameters op
+ if a1_kar is not None:
+ self.a1_kar_handmatig = a1_kar
+ if a2_kar is not None:
+ self.a2_kar_handmatig = a2_kar
+ if vc_fit_kar is not None:
+ self.vc_fit_kar_handmatig = vc_fit_kar
+
+ # Herbereken de afgeleide parameters met de nieuwe waarden
+ self._update_parameters_from_manual()
+
+ def _update_parameters_from_manual(self):
+ """
+ Update de gebruikte parameters op basis van handmatige input.
+
+ Als handmatige parameters zijn ingesteld, worden deze gebruikt.
+ Anders blijven de berekende parameters behouden.
+ """
+ if not self.parameters_handmatig:
+ return
+
+ # Gebruik handmatige a1_kar of behoud berekende waarde
+ if self.a1_kar_handmatig is not None:
+ a1_kar_te_gebruiken = self.a1_kar_handmatig
+ else:
+ a1_kar_te_gebruiken = self.a1_kar_sutabel
+
+ # Gebruik handmatige a2_kar of behoud berekende waarde
+ if self.a2_kar_handmatig is not None:
+ a2_kar_te_gebruiken = self.a2_kar_handmatig
+ else:
+ a2_kar_te_gebruiken = self.a2_kar_sutabel
+
+ # Gebruik handmatige vc_fit_kar of behoud berekende waarde
+ if self.vc_fit_kar_handmatig is not None:
+ vc_te_gebruiken = self.vc_fit_kar_handmatig
+ else:
+ vc_te_gebruiken = self.vc_fit_kar_sutabel
+
+ # Bereken afgeleide parameters met de te gebruiken waarden
+ self.svgm_kar_sutabel = math.exp(a1_kar_te_gebruiken)
+ self.m_kar_sutabel = 1 - a2_kar_te_gebruiken
+
+ # Update vc parameters
+ if vc_te_gebruiken is not None:
+ self.vc_fit_kar_sutabel = vc_te_gebruiken
+ self.STDEV_logn_vc_sutabel = math.sqrt(math.log(1 + (vc_te_gebruiken ** 2)))
+
+ # Herbereken de grafiek dataframes met de nieuwe parameters
+ self.calculate_sutabel_grafiek()
+
+ def write_analysis_to_excel(self, file_path: str):
+ """
+ Schrijft de analyse dataframes naar een Excel-bestand.
+
+ Parameters
+ ----------
+ file_path : str
+ Pad naar de map waar het Excel-bestand moet worden opgeslagen plus de bestandsnaam
+ """
+ with ExcelWriter(file_path) as writer:
+ if self.sutabel_data_df is not None:
+ self.sutabel_data_df.to_excel(writer, sheet_name='Data', index=False)
+ if self.sutabel_filtered_data_df is not None:
+ self.sutabel_filtered_data_df.to_excel(writer, sheet_name='Sutabel Filtered Data', index=False)
+ if self.sutabel_grafiek is not None:
+ self.sutabel_grafiek.to_excel(writer, sheet_name='Sutabel Grafiek', index=False)
+ if self.su_fit_constante_vc is not None:
+ self.su_fit_constante_vc.to_excel(writer, sheet_name='vc Fit', index=False)
+
+ # ========== Visualisatie Methodes ==========
+
+ def set_figure_ln_sv_ln_su_sutabel(self, plot_extra_dataset: Optional[List] = None):
+ """
+ Maakt een visualisatie van de sutabel analyseresultaten voor ln(s'v) vs ln(su).
+
+ Deze plot toont:
+ - Proefresultaten (OC data)
+ - Lineaire fit
+ - 5% boven- en ondergrens
+ - Fysische realiseerbare ondergrens (gebaseerd op a1_kar en a2_kar)
+
+ De analyse wordt automatisch uitgevoerd als deze nog niet is gedaan.
+ """
+ from pv_tool.sutabel_analysis.visualization_sutabel import (
+ add_proefresultaten_ln_sv_ln_su_sutabel,
+ add_extra_proefresultaten_ln_sv_ln_su_sutabel,
+ add_lineair_fit_ln_sv_ln_su_sutabel,
+ add_5pr_bovengrens_ln_sv_ln_su_sutabel,
+ add_5pr_ondergrens_ln_sv_ln_su_sutabel,
+ add_fysische_realiseerbare_ondergrens_ln_sv_ln_su_sutabel,
+ set_layout_ln_sv_ln_su_sutabel
+ )
+
+ # Voer analyse uit als nog niet gedaan
+ if self.sutabel_data_df is None:
+ self._run_sutabel()
+
+ if plot_extra_dataset is not None:
+ add_extra_proefresultaten_ln_sv_ln_su_sutabel(self, plot_extra_dataset)
+ add_proefresultaten_ln_sv_ln_su_sutabel(self)
+ add_lineair_fit_ln_sv_ln_su_sutabel(self)
+ add_5pr_bovengrens_ln_sv_ln_su_sutabel(self)
+ add_5pr_ondergrens_ln_sv_ln_su_sutabel(self)
+ add_fysische_realiseerbare_ondergrens_ln_sv_ln_su_sutabel(self)
+ set_layout_ln_sv_ln_su_sutabel(self)
+
+ def show_figure_ln_sv_ln_su_sutabel(self, plot_extra_dataset: Optional[List] = None):
+ """
+ Toont de visualisatie van de sutabel analyseresultaten voor ln(s'v) vs ln(su).
+
+ De analyse wordt automatisch uitgevoerd als deze nog niet is gedaan.
+ """
+ self.figure = go.Figure()
+ self.set_figure_ln_sv_ln_su_sutabel(plot_extra_dataset)
+ self.figure.show()
+
+ def set_figure_sv_su_sutabel(self, plot_extra_dataset: Optional[List] = None):
+ """
+ Maakt een visualisatie van de sutabel analyseresultaten voor s'v vs su.
+
+ Deze plot toont:
+ - Proefresultaten (OC data)
+ - Sutabel_gem lijn
+ - Sutabel_kar lijn
+ - Su_kar fit met constante VC (als vc_fit_kar is opgegeven)
+
+ Als handmatige parameters zijn ingesteld, worden deze gebruikt voor de visualisatie.
+ De analyse wordt automatisch uitgevoerd als deze nog niet is gedaan.
+ """
+ from pv_tool.sutabel_analysis.visualization_sutabel import (
+ add_proefresultaten_sv_su_sutabel,
+ add_extra_proefresultaten_sv_su_sutabel,
+ add_sutabel_gem_line,
+ add_sutabel_kar_line,
+ add_su_kar_fit_constante_vc,
+ set_layout_sv_su_sutabel
+ )
+
+ # Voer analyse uit als nog niet gedaan
+ if self.sutabel_data_df is None:
+ self._run_sutabel()
+
+ # Update parameters als handmatige waarden zijn ingesteld
+ if self.parameters_handmatig:
+ self._update_parameters_from_manual()
+
+ # Bereken grafiek dataframes
+ self.calculate_sutabel_grafiek()
+
+ # Voeg data en lijnen toe
+ if plot_extra_dataset is not None:
+ add_extra_proefresultaten_sv_su_sutabel(self, plot_extra_dataset)
+ add_proefresultaten_sv_su_sutabel(self)
+ add_sutabel_gem_line(self)
+ add_sutabel_kar_line(self)
+
+ # Voeg vc fit lijn toe als deze beschikbaar is
+ if self.su_fit_constante_vc is not None:
+ add_su_kar_fit_constante_vc(self)
+
+ set_layout_sv_su_sutabel(self)
+
+ def show_figure_sv_su_sutabel(self, plot_extra_dataset: Optional[List] = None):
+ """
+ Toont de visualisatie van de sutabel analyseresultaten voor s'v vs su.
+
+ De analyse wordt automatisch uitgevoerd als deze nog niet is gedaan.
+ """
+ self.figure = go.Figure()
+ self.set_figure_sv_su_sutabel(plot_extra_dataset)
+ self.figure.show()
+
+ # ========== Export Methodes ==========
+
+ def add_results_to_dbase(self, path: str, file_name: str = 'Template_PVtool5_0.xlsx'):
+ """
+ Voegt de sutabel-m analyseresultaten toe aan de database Excel-bestand.
+
+ De analyse wordt automatisch uitgevoerd als deze nog niet is gedaan.
+
+ Parameters
+ ----------
+ path : str
+ Pad naar de map waar het Excel-bestand staat
+ file_name : str
+ Naam van het Excel-bestand
+
+ Returns
+ -------
+ DataFrame
+ Bijgewerkte DataFrame met alle resultaten
+ """
+ # Voer analyse uit als nog niet gedaan
+ if self.sutabel_data_df is None or self.e_a1_sutabel is None:
+ self._run_sutabel()
+ # Zorg dat parameters zijn berekend
+ if self.e_a1_sutabel is None:
+ self.get_sutabel_parameters()
+
+ from pv_tool.sutabel_analysis.save_and_export import add_sutabel_results_to_dbase
+ return add_sutabel_results_to_dbase(self, path, file_name)
+
+ def save_to_pdf(self, path: str, vc_fit_kar: float = None) -> str:
+ """
+ Slaat de sutabel-m analyseresultaten op in een PDF-document.
+
+ De analyse wordt automatisch uitgevoerd als deze nog niet is gedaan.
+
+ Parameters
+ ----------
+ path : str
+ Map locatie waar het PDF-bestand moet worden opgeslagen
+ vc_fit_kar : float, optioneel
+ Coefficient of Variation voor de fit
+
+ Returns
+ -------
+ str
+ Het absolute bestandspad van het aangemaakte PDF-bestand
+ """
+ # Voer analyse uit als nog niet gedaan
+ if self.sutabel_data_df is None or self.e_a1_sutabel is None:
+ self._run_sutabel()
+ # Zorg dat parameters zijn berekend
+ if self.e_a1_sutabel is None:
+ self.get_sutabel_parameters(vc_fit_kar_sutabel=vc_fit_kar)
+
+ from pv_tool.sutabel_analysis.save_and_export import save_sutabel_to_pdf
+ return save_sutabel_to_pdf(self, path, vc_fit_kar)
+
diff --git a/pv_tool/sutabel_analysis/variables.py b/pv_tool/sutabel_analysis/variables.py
new file mode 100644
index 0000000..ad37fa0
--- /dev/null
+++ b/pv_tool/sutabel_analysis/variables.py
@@ -0,0 +1,141 @@
+"""
+Variabelen en parameters voor Sutabel-m analyse.
+
+Deze module bevat alle functies voor het berekenen van statistische parameters
+en variabelen voor sutabel-m analyses.
+"""
+
+from typing import TYPE_CHECKING
+import numpy as np
+from scipy.stats import t
+
+if TYPE_CHECKING:
+ from pv_tool.sutabel_analysis.sutabel_analysis import SUTABEL
+
+
+# -------------------------- sutabel-m methode ---------------------------------------
+
+def count_ln_sv_sutabel(self: "SUTABEL"):
+ """Telt het aantal ln(s'v) waarden in de sutabel dataframe."""
+ return self.sutabel_data_df['ln(s\'v)'].count()
+
+
+def sum_ln_sv_sutabel(self: "SUTABEL"):
+ """Berekent de som van ln(s'v) waarden in de sutabel dataframe."""
+ return self.sutabel_data_df['ln(s\'v)'].sum()
+
+
+def sum_ln_su_sutabel(self: "SUTABEL"):
+ """Berekent de som van ln(su) waarden in de sutabel dataframe."""
+ return self.sutabel_data_df['ln(su)'].sum()
+
+
+def sum_sv_tt_sutabel(self: "SUTABEL"):
+ """Berekent de som van s_tt waarden in de sutabel dataframe."""
+ return self.sutabel_data_df['s_tt'].sum()
+
+
+def sum_sv_ty_sutabel(self: "SUTABEL"):
+ """Berekent de som van s_ty waarden in de sutabel dataframe."""
+ return self.sutabel_data_df['s_ty'].sum()
+
+
+def e_a2_sutabel(self: "SUTABEL"):
+ """Berekent de helling (a2) van de lineaire regressie voor sutabel analyse."""
+ return sum_sv_ty_sutabel(self) / sum_sv_tt_sutabel(self)
+
+
+def e_a1_sutabel(self: "SUTABEL"):
+ """Berekent het snijpunt (a1) van de lineaire regressie voor sutabel analyse."""
+ return (sum_ln_su_sutabel(self) - sum_ln_sv_sutabel(self) * e_a2_sutabel(self)) / count_ln_sv_sutabel(self)
+
+
+def sum_chi_2_sutabel(self: "SUTABEL"):
+ """Berekent de som van chi-kwadraat waarden in de sutabel dataframe."""
+ return self.sutabel_data_df['chi_2'].sum()
+
+
+def var_a2_sutabel(self: "SUTABEL"):
+ """Berekent de variantie van a2 voor sutabel analyse."""
+ return (1 / sum_sv_tt_sutabel(self)) * sum_chi_2_sutabel(self) / (count_ln_sv_sutabel(self) - 2)
+
+
+def var_a1_sutabel(self: "SUTABEL"):
+ """Berekent de variantie van a1 voor sutabel analyse."""
+ return 1 / count_ln_sv_sutabel(self) * (1 + sum_ln_sv_sutabel(self) ** 2 / (count_ln_sv_sutabel(self) * sum_sv_tt_sutabel(self))) * sum_chi_2_sutabel(self) / (
+ count_ln_sv_sutabel(self) - 2)
+
+
+def cov_a1_a2_sutabel(self: "SUTABEL"):
+ """Berekent de covariantie tussen a1 en a2 voor sutabel analyse."""
+ return -(sum_ln_sv_sutabel(self) / (count_ln_sv_sutabel(self) * sum_sv_tt_sutabel(self))) * sum_chi_2_sutabel(self) / (count_ln_sv_sutabel(self) - 2)
+
+
+def rho_a1_a2_sutabel(self: "SUTABEL"):
+ """Berekent de correlatie tussen a1 en a2 voor sutabel analyse."""
+ return cov_a1_a2_sutabel(self) / (var_a2_sutabel(self) * var_a1_sutabel(self)) ** 0.5
+
+
+def sigma_a2_sutabel(self: "SUTABEL"):
+ """Berekent de standaarddeviatie van a2 voor sutabel analyse."""
+ return np.sqrt(var_a2_sutabel(self))
+
+
+def sigma_a1_sutabel(self: "SUTABEL"):
+ """Berekent de standaarddeviatie van a1 voor sutabel analyse."""
+ return np.sqrt(var_a1_sutabel(self))
+
+
+def t_n_2_sutabel(self: "SUTABEL"):
+ """Berekent de t-waarde voor sutabel analyse."""
+ significantieniveau = 0.1
+ degrees_of_freedom = count_ln_sv_sutabel(self) - 2
+ return t.ppf(1 - significantieniveau / 2, degrees_of_freedom)
+
+
+def sum_s_eff_sutabel(self: "SUTABEL"):
+ """Berekent de som van effectieve spanning waarden in de sutabel dataframe."""
+ return self.sutabel_data_df['s\''].sum()
+
+
+def count_s_eff_sutabel(self: "SUTABEL"):
+ """Telt het aantal effectieve spanning waarden in de sutabel dataframe."""
+ return self.sutabel_data_df['s\''].count()
+
+
+def sum_5_pr_ondergrens_sutabel(self: "SUTABEL"):
+ """Berekent de som van 5% ondergrens waarden in de sutabel dataframe."""
+ return self.sutabel_data_df['5_pr_ondergrens'].sum()
+
+
+def sum_stt_ondergrens_sutabel(self: "SUTABEL"):
+ """Berekent de som van s_tt ondergrens waarden in de sutabel dataframe."""
+ return self.sutabel_data_df['s_tt_ondergrens'].sum()
+
+
+def sum_sty_ondergrens_sutabel(self: "SUTABEL"):
+ """Berekent de som van s_ty ondergrens waarden in de sutabel dataframe."""
+ return self.sutabel_data_df['s_ty_ondergrens'].sum()
+
+
+def a2_kar_sutabel(self: "SUTABEL"):
+ """Berekent de karakteristieke helling (a2) voor sutabel analyse."""
+ return sum_sty_ondergrens_sutabel(self) / sum_stt_ondergrens_sutabel(self)
+
+
+def a1_kar_sutabel(self: "SUTABEL"):
+ """Berekent het karakteristieke snijpunt (a1) voor sutabel analyse."""
+ return (sum_5_pr_ondergrens_sutabel(self) - a2_kar_sutabel(self) * sum_s_eff_sutabel(self)) / count_s_eff_sutabel(self)
+
+
+def steyx_sutabel(self: "SUTABEL"):
+ """Berekent de STEYX (standaardfout van de schatting) voor ln(s'v) en ln(su) data."""
+ # Bereken de residuen
+ n = count_ln_sv_sutabel(self)
+ sum_squared_residuals = sum_chi_2_sutabel(self)
+
+ # STEYX formule: sqrt(sum((y - y_pred)^2) / (n - 2))
+ steyx = np.sqrt(sum_squared_residuals / (n - 2))
+
+ return steyx
+
diff --git a/pv_tool/sutabel_analysis/visualization_sutabel.py b/pv_tool/sutabel_analysis/visualization_sutabel.py
new file mode 100644
index 0000000..009f66c
--- /dev/null
+++ b/pv_tool/sutabel_analysis/visualization_sutabel.py
@@ -0,0 +1,342 @@
+"""
+Visualisatie functies voor Sutabel-m analyse.
+
+Deze module bevat functies voor het visualiseren van sutabel-m analyseresultaten
+met Plotly.
+"""
+
+from typing import TYPE_CHECKING
+import plotly.graph_objects as go
+import numpy as np
+
+if TYPE_CHECKING:
+ from pv_tool.sutabel_analysis.sutabel_analysis import SUTABEL
+
+from typing import List, Optional
+from pv_tool.shansep_analysis.globals import TEXTUAL_NAMES, TEXTUAL_NAMES_DSS, NEW_COLUMN_NAMES
+
+# ========================== SUTABEL LN-LN PLOT ==========================
+
+def add_proefresultaten_ln_sv_ln_su_sutabel(self: "SUTABEL"):
+ """Voegt de proefresultaten toe aan de figuur voor sutabel analyse (ln(s'v) vs ln(su))."""
+ boring_monsternummer = self.sutabel_data_df.index
+
+ x_proefresultaten = self.sutabel_data_df['ln(s\'v)']
+ y_proefresultaten = self.sutabel_data_df['ln(su)']
+
+ self.figure.add_trace(
+ go.Scatter(
+ x=x_proefresultaten,
+ y=y_proefresultaten,
+ mode='markers',
+ marker=dict(
+ color='blue'
+ ),
+ name=f'Geanalyseerd: {self.investigation_groups[0]} (OC)',
+ text=boring_monsternummer,
+ hoverinfo='text'
+ )
+ )
+
+def get_extra_data(self: "SUTABEL", investigationgroups_extra: Optional[List]):
+ if self.analysis_type in ['TXT_su_tabel']:
+ dataset_df = self.dbase_df[self.dbase_df['ALG__TRIAXIAAL']]
+ elif self.analysis_type in ['DSS_su_tabel']:
+ dataset_df = self.dbase_df[self.dbase_df['ALG__DSS']]
+ else:
+ raise ValueError(f"analysis type for extra dataset not right: {self.analysis_type}")
+
+ dataset_df = dataset_df[
+ dataset_df['PV_NAAM'].isin(investigationgroups_extra)]
+ if self.analysis_type in ['DSS_su_tabel']:
+ dataset_df = dataset_df[TEXTUAL_NAMES_DSS.get(self.effective_stress, [])]
+ else:
+ dataset_df = dataset_df[TEXTUAL_NAMES.get(self.effective_stress, [])]
+ dataset_df.columns = NEW_COLUMN_NAMES
+ return dataset_df
+
+def add_extra_proefresultaten_ln_sv_ln_su_sutabel(self: "SUTABEL", extra_groepen: Optional[List]):
+ """Deze functie voegt de proefresultaten toe aan de figuur."""
+ df = get_extra_data(self, investigationgroups_extra=extra_groepen)
+ df = df[df['consolidatietype'] == 'OC'].copy()
+
+ # paar zaken berekenen van deze nieuwe data
+ df.loc[:, 'ln(s\'v)'] = (
+ df['S\'v'].apply
+ (lambda x: np.log(x) if x is not None and x > 0 else ""))
+
+ df.loc[:, 'ln(su)'] = (
+ df['Su'].apply
+ (lambda x: np.log(x) if x is not None and x > 0 else ""))
+
+ boring_monsternummer = df.index
+
+ n=0
+ for naam in df['PV_NAAM'].unique():
+
+ sub_df = df[df['PV_NAAM'] == naam]
+ x_extra_proefresultaten = self.sutabel_data_df['ln(s\'v)']
+ y_extra_proefresultaten = self.sutabel_data_df['ln(su)']
+
+ self.figure.add_trace(
+ go.Scatter(
+ x=x_extra_proefresultaten,
+ y=y_extra_proefresultaten,
+ mode='markers',
+ name=f'Extra: {extra_groepen[n]}',
+ text=boring_monsternummer,
+ hoverinfo='text'
+ )
+ )
+ n += 1
+
+def add_lineair_fit_ln_sv_ln_su_sutabel(self: "SUTABEL"):
+ """Voegt de lineaire fit toe aan de figuur voor sutabel analyse."""
+ # Gebruik de min en max van ln(s'v) voor de lijn
+ x1 = self.sutabel_data_df['ln(s\'v)'].min()
+ x2 = self.sutabel_data_df['ln(s\'v)'].max()
+
+ # Gebruik de berekende regressie parameters
+ from pv_tool.sutabel_analysis.variables import e_a2_sutabel, e_a1_sutabel
+ a2 = e_a2_sutabel(self)
+ a1 = e_a1_sutabel(self)
+
+ # Bereken y-waarden met de regressievergelijking: ln(su) = a1 + a2 * ln(s'v)
+ y1 = a1 + a2 * x1
+ y2 = a1 + a2 * x2
+
+ x = [x1, x2]
+ y = [y1, y2]
+
+ self.figure.add_trace(
+ go.Scatter(
+ x=x,
+ y=y,
+ mode='lines',
+ name='Lineaire fit proefresultaten',
+ line=dict(color='green', width=2),
+ )
+ )
+
+def add_5pr_bovengrens_ln_sv_ln_su_sutabel(self: "SUTABEL"):
+ """Voegt de 5% bovengrens toe aan de figuur voor sutabel analyse."""
+ x_5pr = self.sutabel_data_df['s\'']
+ y_5pr = self.sutabel_data_df['5_pr_bovengrens']
+
+ self.figure.add_trace(
+ go.Scatter(
+ x=x_5pr,
+ y=y_5pr,
+ mode='lines',
+ name='5% bovengrens',
+ line=dict(
+ color='black',
+ width=1,
+ dash='dash'
+ )
+ )
+ )
+
+def add_5pr_ondergrens_ln_sv_ln_su_sutabel(self: "SUTABEL"):
+ """Voegt de 5% ondergrens toe aan de figuur voor sutabel analyse."""
+ x_5pr = self.sutabel_data_df['s\'']
+ y_5pr = self.sutabel_data_df['5_pr_ondergrens']
+
+ self.figure.add_trace(
+ go.Scatter(
+ x=x_5pr,
+ y=y_5pr,
+ mode='lines',
+ name='5% ondergrens',
+ line=dict(
+ color='black',
+ width=1,
+ dash='dash'
+ )
+ )
+ )
+
+def add_fysische_realiseerbare_ondergrens_ln_sv_ln_su_sutabel(self: "SUTABEL"):
+ """Voegt de fysische realiseerbare ondergrens toe aan de figuur voor sutabel analyse."""
+ from pv_tool.sutabel_analysis.variables import a2_kar_sutabel, a1_kar_sutabel
+
+ # Bereken de karakteristieke lijn: ln(su) = a1_kar + a2_kar * ln(s'v)
+ x1 = self.sutabel_data_df['ln(s\'v)'].min()
+ x2 = self.sutabel_data_df['ln(s\'v)'].max()
+
+ # Gebruik handmatige parameters als deze zijn ingesteld, anders berekende waarden
+ if hasattr(self, 'parameters_handmatig') and self.parameters_handmatig:
+ # Gebruik handmatige waarden als beschikbaar
+ a2_kar = self.a2_kar_handmatig if self.a2_kar_handmatig is not None else a2_kar_sutabel(self)
+ a1_kar = self.a1_kar_handmatig if self.a1_kar_handmatig is not None else a1_kar_sutabel(self)
+ else:
+ # Gebruik berekende waarden
+ a2_kar = a2_kar_sutabel(self)
+ a1_kar = a1_kar_sutabel(self)
+
+ y1 = a1_kar + a2_kar * x1
+ y2 = a1_kar + a2_kar * x2
+
+ x = [x1, x2]
+ y = [y1, y2]
+
+ self.figure.add_trace(
+ go.Scatter(
+ x=x,
+ y=y,
+ mode='lines',
+ name='Fysische realiseerbare ondergrens',
+ line=dict(color='black', width=2),
+ )
+ )
+
+def set_layout_ln_sv_ln_su_sutabel(self: "SUTABEL"):
+ """Stelt de layout van de figuur in voor sutabel analyse (ln(s'v) vs ln(su))."""
+ title = f'Sutabel-m analyse: ln(s\'v) vs ln(su) - {self.analysis_type} proef'
+
+ xas_title = r"$\ln(\sigma'_{v}) [-]$"
+ yas_title = r'$\ln(s_{u}) [-]$'
+
+ legend_title = 'Legenda'
+ self.figure.update_layout(
+ width=1280,
+ height=720,
+ title=title if self.show_title else None,
+ xaxis_title=xas_title,
+ yaxis_title=yas_title,
+ legend_title=legend_title,
+ margin=dict(t=100, r=50, b=100, l=50)
+ )
+
+# ========================== SUTABEL SV-SU PLOT ==========================
+
+def add_proefresultaten_sv_su_sutabel(self: "SUTABEL"):
+ """Voegt de proefresultaten toe aan de figuur voor sutabel analyse (s'v vs su)."""
+ boring_monsternummer = self.sutabel_data_df.index
+
+ x_proefresultaten = self.sutabel_data_df['S\'v']
+ y_proefresultaten = self.sutabel_data_df['Su']
+
+ self.figure.add_trace(
+ go.Scatter(
+ x=x_proefresultaten,
+ y=y_proefresultaten,
+ mode='markers',
+ marker=dict(
+ color='blue'
+ ),
+ name=f'Geanalyseerd: {self.investigation_groups[0]} (OC)',
+ text=boring_monsternummer,
+ hoverinfo='text'
+ )
+ )
+
+def add_extra_proefresultaten_sv_su_sutabel(self: "SUTABEL", extra_groepen: Optional[List]):
+ """Deze functie voegt de proefresultaten toe aan de figuur."""
+ df = get_extra_data(self, investigationgroups_extra=extra_groepen)
+ df = df[df['consolidatietype'] == 'OC'].copy()
+ boring_monsternummer = df.index
+
+ n=0
+ for naam in df['PV_NAAM'].unique():
+
+ sub_df = df[df['PV_NAAM'] == naam]
+ x_extra_proefresultaten = self.sutabel_data_df['S\'v']
+ y_extra_proefresultaten = self.sutabel_data_df['Su']
+
+ self.figure.add_trace(
+ go.Scatter(
+ x=x_extra_proefresultaten,
+ y=y_extra_proefresultaten,
+ mode='markers',
+ name=f'Extra: {extra_groepen[n]}',
+ text=boring_monsternummer,
+ hoverinfo='text'
+ )
+ )
+ n += 1
+
+def add_sutabel_kar_line(self: "SUTABEL"):
+ """
+ Voegt de karakteristieke sutabel lijn toe aan de s'v vs su plot.
+
+ Deze lijn wordt berekend met: su_kar = svgm_kar * (s'v)^(1-m_kar)
+ """
+ if self.sutabel_grafiek is None:
+ return
+
+ x = self.sutabel_grafiek["s'v [kPa]"].tolist()
+ y = self.sutabel_grafiek["su_kar [kPa]"].tolist()
+
+ self.figure.add_trace(
+ go.Scatter(
+ x=x,
+ y=y,
+ mode='lines',
+ name='Sutabel karakteristiek',
+ line=dict(color='black', width=2)
+ )
+ )
+
+def add_sutabel_gem_line(self: "SUTABEL"):
+ """
+ Voegt de gemiddelde sutabel lijn toe aan de s'v vs su plot.
+
+ Deze lijn wordt berekend met: su_gem = svgm_gem * (s'v)^(1-m_gem)
+ """
+ if self.sutabel_grafiek is None:
+ return
+
+ x = self.sutabel_grafiek["s'v [kPa]"].tolist()
+ y = self.sutabel_grafiek["su_gem [kPa]"].tolist()
+
+ self.figure.add_trace(
+ go.Scatter(
+ x=x,
+ y=y,
+ mode='lines',
+ name='Sutabel gemiddeld',
+ line=dict(color='purple', width=2, dash='dot')
+ )
+ )
+
+def add_su_kar_fit_constante_vc(self: "SUTABEL"):
+ """
+ Voegt de karakteristieke su fit met constante s'vc toe aan de s'v vs su plot.
+
+ Deze lijn wordt berekend met lognormale verdeling:
+ su_kar_fit = LOGNORM.INV(0.05; ln(su_gem); STDEV_logn_vc)
+ """
+ if self.su_fit_constante_vc is None:
+ return
+
+ x = self.su_fit_constante_vc["s'v [kPa]"].tolist()
+ y = self.su_fit_constante_vc["su_kar fit met constante vc [kPa]"].tolist()
+
+ self.figure.add_trace(
+ go.Scatter(
+ x=x,
+ y=y,
+ mode='lines',
+ name='Su kar fit met constante vc',
+ line=dict(color='red', width=2, dash='dashdot')
+ )
+ )
+
+def set_layout_sv_su_sutabel(self: "SUTABEL"):
+ """Stelt de layout van de figuur in voor sutabel analyse (s'v vs su)."""
+ title = f'Sutabel-m analyse: s\'v vs su - {self.analysis_type} proef'
+
+ xas_title = r"$\sigma'_{v} [kPa]$"
+ yas_title = r'$s_{u} [kPa]$'
+
+ legend_title = 'Legenda'
+ self.figure.update_layout(
+ width=1280,
+ height=720,
+ title=title if self.show_title else None,
+ xaxis_title=xas_title,
+ yaxis_title=yas_title,
+ legend_title=legend_title,
+ margin=dict(t=100, r=50, b=100, l=50)
+ )
\ No newline at end of file