#!/bin/python3

# input data in frontend detailedForm ist:
# inData = {
# 'rechnertyp_select': 'detailedDefinition',
# 'cemtyp_select': 'cem1', 'cemtyp_masse': '280', 'cemtyp_lkw': 0, 'cemtyp_bahn': 0,
# 'betongranulat_masse': '0', 'betongranulat_lkw': 0, 'betongranulat_bahn': 0,
# 'mischgranulat_masse': '0', 'mischgranulat_lkw': 0, 'mischgranulat_bahn': 0,
# 'kies_rund_masse': '1405', 'kies_rund_lkw': 0, 'kies_rund_bahn': 0,
# 'kies_gebrochen_masse': '14.2', 'kies_gebrochen_lkw': 0, 'kies_gebrochen_bahn': 0,
# 'sand_masse': '400.4', 'sand_lkw': 0, 'sand_bahn': 0,
# 'kalksteinmehl_masse': '0', 'kalksteinmehl_lkw': 0, 'kalksteinmehl_bahn': 0,
# 'flugasche_masse': '0', 'flugasche_lkw': 0, 'flugasche_bahn': 0,
# 'fliessmittel_masse': '1.3', 'fliessmittel_lkw': 0, 'fliessmittel_bahn': 0,
# 'weitere_zusaetze_masse': '0', 'weitere_zusaetze_lkw': 0, 'weitere_zusaetze_bahn': 0,
# 'wasser_masse': '140', 'wasser_lkw': 0, 'wasser_bahn': 0,
# 'bewehrung_masse': '0', 'bewehrung_lkw': 0, 'bewehrung_bahn': 0,
# 'einspeicherungKohle_masse': '0', 'einspeicherungKohle_lkw': 0, 'einspeicherungKohle_bahn': 0,
# 'einspeicherungGranulat_masse': '0', 'einspeicherungGranulat_lkw': 0, 'einspeicherungGranulat_bahn': 0,
# 'umwindikator_select': 'thg', 'actTabInput_select': 'tabUebersicht'
#    }
# input data in frontend npkForm ist:
# inData = {
#  'rechnertyp_select': 'npkDefinition',
#  'anwendung_select': 'hochA',
#  'sorte_select': 'Pb',
#  'cemtyp_select': 'cem1',
#  'bewehrung_masse': '0',
#  'einspeicherungKohle_masse': '0',
#  'einspeicherungGranulat_masse': '0',
#  'umwindikator_select': 'thg',
#  'actTabInput_select': 'tabUebersicht'}

import os, sys
import numpy as np
import matrizen as mzn
import functions as fncts
#
# Auswertung Planer:innen/NPK-Rechner
#
def auswertungNPK(inData, input_meta, rechnerdaten_dict, stdDichteArray):
    anwArray = np.array(rechnerdaten_dict['Standardwerte'][['Parameter',
                    input_meta['anwendung']['options'][inData['anwendung_select']]['stdwerte_ods_title']]],
                        dtype=mzn.LCAdType)
    #print(f"anwArray={anwArray}", file=sys.stderr)
    betonmischArray = np.array(
        [(bestandteil['resultName'], 0.0) for bestandteil in mzn.input_meta['bestandteile'].values()],
        dtype = mzn.LCAdType )
    betonmischArray = fncts.updateMasse(betonmischArray, anwArray)
    ########################################
    # Anteile Gesteinskörnungen berechnen
    ########################################
    # Recyclatkörnungen berechnen
    ########################################
    try:
        float(inData['einspeicherungGranulat_masse'])
    except:
        print(f"error: cannot float inData['einspeicherungGranulat_masse] = {inData['einspeicherungGranulat_masse']}", file=sys.stderr)
        inData['einspeicherungGranulat_masse'] = 0
    betonmischArray['Wert'][betonmischArray['Parameter'] == 'Im Granulat eingelagertes CO₂'] = inData['einspeicherungGranulat_masse']
    inNormBez = inData['sorte_select']
    try:
        input_meta['sorte']['options'][inNormBez]
    except:
        inNormBez = next(iter(input_meta['sorte']['options']))
    recArray = np.array(rechnerdaten_dict['Standardwerte'][['Parameter',
                    input_meta['sorte']['options'][inNormBez]['stdwerte_ods_title']]] ,
                        dtype=mzn.LCAdType)
    #print(f"recArray={recArray}", file=sys.stderr)
    betonmischArray['Wert'][betonmischArray['Parameter'] == 'Betongranulat C'] = fncts.roundKg2g(
        (anwArray[anwArray['Parameter'] == 'Koernung grob']['Wert']
         + anwArray[anwArray['Parameter'] == 'Sand']['Wert'])
        * recArray[recArray['Parameter'] == 'Betongranulat C']['Wert'] - float(inData['einspeicherungGranulat_masse']))
    betonmischArray['Wert'][betonmischArray['Parameter'] == 'Mischgranulat M'] = fncts.roundKg2g(
        (anwArray[anwArray['Parameter'] == 'Koernung grob']['Wert']
         + anwArray[anwArray['Parameter'] == 'Sand']['Wert'])
        * recArray[recArray['Parameter'] == 'Mischgranulat M']['Wert'])
    # Koernung grob ist nun nur noch Primaeranteil
    anwArray['Wert'][anwArray['Parameter'] == 'Koernung grob'] = fncts.roundKg2g(
        anwArray[anwArray['Parameter'] == 'Koernung grob']['Wert']
        * recArray[recArray['Parameter'] == 'Koernung primaer']['Wert'])
    ## Kiesanteile rund / gebrochen berechnen
    gewaehlteAnwTitelKoernung = input_meta['anwendung']['options'][inData['anwendung_select']]['koernung_ods_title']
    koernungArray = np.array(rechnerdaten_dict['Gesteinskoernung']
                        [['Parameter', gewaehlteAnwTitelKoernung ]], dtype=mzn.LCAdType )
    faktorGebrochen =  koernungArray[koernungArray['Parameter'] == 'Anteil gebrochene Körnung']['Wert']
    betonmischArray['Wert'][betonmischArray['Parameter'] == 'Kies rund'] = fncts.roundKg2g(
        anwArray[anwArray['Parameter'] == 'Koernung grob']['Wert']
        *  (1 - faktorGebrochen))
    betonmischArray['Wert'][betonmischArray['Parameter'] == 'Kies gebrochen'] = fncts.roundKg2g(
        anwArray[anwArray['Parameter'] == 'Koernung grob']['Wert']
        *  faktorGebrochen)
    ## Sand ist nun nur noch Primaeranteil
    betonmischArray['Wert'][betonmischArray['Parameter'] == 'Sand'] = fncts.roundKg2g(
        anwArray[anwArray['Parameter'] == 'Sand']['Wert']
        * recArray[recArray['Parameter'] == 'Koernung primaer']['Wert'])
    ###########################################
    # Fliessmittel berechnen
    ###########################################
    factorFMCarray = np.array(rechnerdaten_dict['Fliessmittel'][['Parameter', 'Faktor Granulat C' ]],
                       dtype=mzn.LCAdType)
    factorFMC = factorFMCarray['Wert'][factorFMCarray['Parameter'] ==
                    input_meta['anwendung']['options'][inData['anwendung_select']]['fliess_ods_title']]
    constFMarray = np.array(rechnerdaten_dict['Fliessmittel'][['Parameter', 'Konstante' ]],
                       dtype=mzn.LCAdType)
    constFM = constFMarray['Wert'][constFMarray['Parameter'] ==
                    input_meta['anwendung']['options'][inData['anwendung_select']]['fliess_ods_title']]
    betonmischArray['Wert'][betonmischArray['Parameter'] == 'Fliessmittel'] = fncts.roundKg2g(
        ( betonmischArray['Wert'][betonmischArray['Parameter'] == 'Zement'] + betonmischArray['Wert'][betonmischArray['Parameter'] == '2. Zement'] )
        * ( factorFMC * ( recArray[recArray['Parameter'] == 'Betongranulat C']['Wert']
        + recArray[recArray['Parameter'] == 'Mischgranulat M']['Wert'] )
        + constFM ))
    # Korrekturfaktor Pflanzenkohle berechnen
    ###########################################
    # Zusammensetzung Granulatmischung korrigieren für Beton mit Pflanzenkohle
    # V_K [m3] = Masse Kohle / Dichte Kohle
    # V_G = Masse Granulat / Dichte Granulat
    # Korrekturfaktor Zusetzung = (V_G - V_K) / VG = 1 - V_K/V_G
    stdDichteArray = np.array(rechnerdaten_dict['Standardwerte'][['Parameter', 'Dichte']], dtype = mzn.LCAdType)
    stdDichteArray = stdDichteArray[stdDichteArray['Wert'] != 0]
    granulatListe = ['Sand', 'Kies rund', 'Kies gebrochen', 'Mischgranulat M', 'Betongranulat C']
    granulatVolArray = np.empty(len(granulatListe), dtype=mzn.LCAdType)
    for i, name in enumerate(granulatListe):
        try:
            granulatVolArray[i] = (name, betonmischArray['Wert'][betonmischArray['Parameter'] == name][0]
                                / stdDichteArray['Wert'][stdDichteArray['Parameter'] == name][0])
        except:
            raise ValueError(f"cannot calculate for {name}, betonmischArray={betonmischArray}, stdDichteArray={stdDichteArray}")
    einspeicherungKohle_masse = 0
    try:
        einspeicherungKohle_masse = float(inData['einspeicherungKohle_masse'])
    except:
        print(f"error: cannot float inData['einspeicherungKohle_masse'] = {inData['einspeicherungKohle_masse']}", file=sys.stderr)
    fKohle = fncts.roundKg2g( 1 - einspeicherungKohle_masse
                     / stdDichteArray['Wert'][stdDichteArray['Parameter'] == 'Pflanzenkohle'][0]
                     / np.sum(granulatVolArray['Wert']))
    for name in granulatListe:
        betonmischArray['Wert'][betonmischArray['Parameter'] == name] = fncts.roundKg2g(
            betonmischArray['Wert'][betonmischArray['Parameter'] == name] * fKohle)
    betonmischArray['Wert'][betonmischArray['Parameter'] == 'Pflanzenkohle'] = (
            einspeicherungKohle_masse )
    #print(f"betonmischArray = {betonmischArray}", file=sys.stderr)
    return betonmischArray

def auswertungDetails(inData, input_meta, LCAdType):
    #print(f"inData = {inData}", file=sys.stderr)
    for bestandteil, bestDict in input_meta['bestandteile'].items():
        try:
            float(inData[f"{bestandteil}_masse"])
        except:
            #print(f"info: cannot float {bestandteil}_masse = {inData[f'{bestandteil}_masse']}", file=sys.stderr)
            inData[f"{bestandteil}_masse"] = 0
            pass
    betonmischArray = np.array(
        [(bestDict['resultName'], float(inData[f"{bestandteil}_masse"])) for bestandteil, bestDict
         in input_meta['bestandteile'].items()
         if 'detailedDefinition' in bestDict.get('valUsedAsInputIn', [])],
        dtype = LCAdType )
    # get rid of Bewehrung, will be added in main function afterwards
    betonmischArray['Wert'][betonmischArray['Parameter'] == 'Bewehrung'] = 0
    return betonmischArray

def rechneBilanz(inData, inRechnertyp):
    # print(f"inData = {inData}", file=sys.stderr)
    datadir = fncts.get_data_dir()
    try:
        rechnerdaten_dict = np.load(os.path.join(datadir, "rechnerdaten_py.npz"), allow_pickle=True)
    except:
        import updateData as uD
        uD.updateFromODS()
        rechnerdaten_dict = np.load(os.path.join(datadir, "rechnerdaten_py.npz"), allow_pickle=True)
        # print(f"rechnerdaten_dict: {rechnerdaten_dict}", file=sys.stderr)

    # lade Standarddistanzen
    transStdStrArray = np.array(rechnerdaten_dict['Standardwerte'][['Parameter',
                    mzn.input_meta['transporte']['strasse']['stdwerte_ods_title']]],
                        dtype=mzn.LCAdType)
    transStdBahnArray = np.array(rechnerdaten_dict['Standardwerte'][['Parameter',
                    mzn.input_meta['transporte']['bahn']['stdwerte_ods_title']]],
                        dtype=mzn.LCAdType)
    if len(inData) == 0:
        inData = fncts.setStdInputValues(mzn.input_meta, inRechnertyp, transStdStrArray, transStdBahnArray)
    datadir = fncts.get_data_dir()

    stdDichteArray = np.array(rechnerdaten_dict['Standardwerte'][['Parameter', 'Dichte']],
                              dtype = mzn.LCAdType)
    stdDichteArray = stdDichteArray[stdDichteArray['Wert'] != 0]


    # Auswertung nach gewähltem Rechnertyp
    if inData['rechnertyp_select'] ==  'npkDefinition':
        betonmischungUnbewehrtArray = auswertungNPK(inData, mzn.input_meta, rechnerdaten_dict, stdDichteArray)
    elif inData['rechnertyp_select'] ==  'detailedDefinition':
         betonmischungUnbewehrtArray = auswertungDetails(inData, mzn.input_meta, mzn.LCAdType)

    #
    # Start Auswertung für alle Varianten
    #
    # Betondichte unbewehrt berechnen
    ###########################################
    dichteArray = np.empty(2, dtype=mzn.ZusdType)
    dichteArray[0] = ('Beton unbewehrt', np.sum( betonmischungUnbewehrtArray['Wert']), 'kg/m³')

    # Bewehrung ergänzen
    ###########################################
    # Umrechnung Betonbestandteile auf Volumen ohne Bewehrung = 1 - V Bewehrung = 1 - Masse / Dichte Bewehrung
    bewehrung_masse = 0
    try:
        bewehrung_masse = float(inData['bewehrung_masse'])
    except:
        print(f"error: cannot float inData['bewehrung_masse'] = {inData['bewehrung_masse']}", file=sys.stderr)
    volBeton = (1 - bewehrung_masse /
                  stdDichteArray['Wert'][stdDichteArray['Parameter'] == 'Bewehrung'][0] )
    betonmischungArray = betonmischungUnbewehrtArray.copy()
    betonmischungArray['Wert'] = betonmischungArray['Wert'] * volBeton
    betonmischungArray['Wert'][betonmischungArray['Parameter'] == 'Bewehrung'] = bewehrung_masse
    # Betondichte bewehrt berechnen
    ###########################################
    dichteArray[1] = ('Beton bewehrt', np.sum(betonmischungArray['Wert']), 'kg/m³')
    # Fixiertes CO2 ergänzen
    # wird korrigiert um Volumen der Bewehrung (Version 5 korrigierte mit Dichte, was falsch war)
    #############################################################################################
    betonmischungArray['Wert'][betonmischungArray['Parameter'] == 'Im Granulat eingelagertes CO₂'] = (
        float(inData['einspeicherungGranulat_masse']) * volBeton )
    # Volumen ergänzen
    ###################
    volMaske = betonmischungArray['Parameter'] == 'Volumen Beton'
    if np.any(volMaske):
        betonmischungArray['Wert'][volMaske] = volBeton
    else:
        betonmischungArray = np.append(betonmischungArray, np.array(('Volumen Beton', volBeton), dtype=betonmischungArray.dtype))
    # Zuordnungsmatrix erzeugen
    zuordArray = {}
    zuordArray['Herstellung'] = np.array(rechnerdaten_dict['Bestandteile zu LCIA Herstellung'], rechnerdaten_dict['Bestandteile zu LCIA Herstellung'].dtype)
    # Common base ergänzen
    #####################
    rowMaske = zuordArray['Herstellung']['Parameter'] == mzn.input_meta['anwendung']['options'][inData['anwendung_select']]['Datensatz']
    zuordArray['Herstellung']['Volumen Beton'][rowMaske] = 1
    # Zement ergänzen
    #####################
    rowMaske = zuordArray['Herstellung']['Parameter'] == mzn.input_meta['bestandteile']['cemtyp']['options'][inData['cemtyp_select']]['Datensatz']
    zuordArray['Herstellung']['Zement'][rowMaske] = 1
    ######### Für 2. Zement
    try:
        rowMaske = zuordArray['Herstellung']['Parameter'] == mzn.input_meta['bestandteile']['cemtyp']['options'][inData['cemtyp2_select']]['Datensatz']
        zuordArray['Herstellung']['2. Zement'][rowMaske] = 1
    except:
        #print(f"info: only one cement defined in inData", file=sys.stderr)
        pass
    # ausgewählte Stahlsorte in Zuordnungsmatrix ergänzen
    rowMaske = zuordArray['Herstellung']['Parameter'] == mzn.input_meta['bestandteile']['bewehrung']['options'][inData['bewehrung_select']]['Datensatz']
    zuordArray['Herstellung']['Bewehrung'][rowMaske] = 1

    # Auswertung Transporte
    # nur detailliertes Formular: überschreibe Standardwerte mit Nutzereingaben
    if inData['rechnertyp_select'] == 'detailedDefinition':
        for inKey, inValue in inData.items():
            if "_lkw" in inKey:
                transStdStrArray['Wert'][(transStdStrArray['Parameter'] ==
                            mzn.input_meta['bestandteile'][inKey[:-4]]['resultName'])] = inValue
            if "_bahn" in inKey:
                transStdBahnArray['Wert'][(transStdBahnArray['Parameter'] ==
                            mzn.input_meta['bestandteile'][inKey[:-5]]['resultName'])] = inValue
    # Transporte in Zuordnungsmatrix ergänzen (km/1000, weil Bezug Datensätze tkm ist)
    for row in transStdStrArray:
        if row[0] in zuordArray['Herstellung'].dtype.names:
            zuordArray['Herstellung'][row[0]][zuordArray['Herstellung']['Parameter'] == mzn.input_meta['transporte']['strasse']['Datensatz']] = row[1] / 1000
    for row in transStdBahnArray:
        if row[0] in zuordArray['Herstellung'].dtype.names:
            zuordArray['Herstellung'][row[0]][zuordArray['Herstellung']['Parameter'] == mzn.input_meta['transporte']['bahn']['Datensatz']] = row[1] / 1000
    ################################################
    # Start LCA Herstellung
    ###############################################
    # Zuordnungsmatrix mal Bestandteil-Vektor -> Faktor-Matrix
    eingabe2LCIADict = {}
    eingabe2LCIADict['Herstellung'] = fncts.vectMatrix(betonmischungArray, zuordArray['Herstellung'])
    # LCIA-Daten laden
    lcia_data_openlca_dict = np.load(os.path.join(datadir, "lcia_data_openlca.npz"), allow_pickle=True)
    #print(f"lcia_data_openlca_dict['LCIA Results'] = {lcia_data_openlca_dict['LCIA Results']}", file=sys.stderr)
    lciaArray = lcia_data_openlca_dict['LCIA Results']
    lciaArray = lciaArray[lciaArray['Parameter'] != 0]
    newLciaRows = np.array( [tuple([param] + [0.0] * len(lciaArray.dtype.names[1:])) for param in mzn.additionalLCIAparams for param in mzn.additionalLCIAparams], dtype=lciaArray.dtype )
    lciaArray = np.concatenate([lciaArray, newLciaRows])
    # Energiedaten in kWh umrechnen
    testStrings = ['priGE', 'priErn', 'priTot']
    for i, row in enumerate(lciaArray):
        for testStr in testStrings:
            if row['Parameter'] ==  mzn.input_meta['umwindikator']['options'][testStr]['DatensatzMJ']:
                lciaArray[i]['Parameter'] = mzn.input_meta['umwindikator']['options'][testStr]['Datensatz']
                for name in lciaArray.dtype.names[1:]:
                    lciaArray[i][name] = lciaArray[i][name] / 3.6

    # Zusatzdaten aus rechnerdaten_dict ergänzen (sind schon in kWh)
    lciaArray = fncts.addAdditionalLCIAData(lciaArray,
        rechnerdaten_dict['LCIA Zusatzdaten'][rechnerdaten_dict['LCIA Zusatzdaten']['Parameter'] != 0])
    #print(f"lciaArray =\n{lciaArray.dtype}\n{lciaArray}", file=sys.stderr)
    # Faktor-Matrix * LCIA-Matrix -> LCIA-Result-Matrix
    resultDict = {}
    resultDict['Herstellung'] = fncts.matrixMult(lciaArray, eingabe2LCIADict['Herstellung'])
    ##########################################################
    #
    # Print arrays for report
    #
    ##########################################################
    #print(f"lciaArray.dtype: {lciaArray.dtype}", file=sys.stderr)
    #print(f"lciaArray: {lciaArray}", file=sys.stderr)
    #print(f"eingabe2LCIADict['Herstellung'].dtype: {eingabe2LCIADict['Herstellung'].dtype}", file=sys.stderr)
    #print(f"eingabe2LCIADict['Herstellung']: {eingabe2LCIADict['Herstellung']}", file=sys.stderr)
    #print(f"resultDict['Herstellung'].dtype: {resultDict['Herstellung'].dtype}", file=sys.stderr)
    #print(f"resultDict['Herstellung']: {resultDict['Herstellung']}", file=sys.stderr)
    ################################################
    # Start LCA Entsorgung
    ###############################################
    zuordArray['Entsorgung'] = np.array(rechnerdaten_dict['Bestandteile zu LCIA Entsorgung'], rechnerdaten_dict['Bestandteile zu LCIA Entsorgung'].dtype)
    eingabe2LCIADict['Entsorgung'] = fncts.vectMatrix(betonmischungArray, zuordArray['Entsorgung'])
    resultDict['Entsorgung'] = fncts.matrixMult(lciaArray, eingabe2LCIADict['Entsorgung'])

    # Resulatdarstellung
    # LCIA Herstellung + LCIA Transporte
    # LCIA Entsorgung

    #
    # Datenaufbereitung für Frontend
    #
    # Zusammensetzung ohne Bewehrung in Resultat ergänzen
    #####################################################
    zusBetonUnbewehrtArray = np.empty(betonmischungUnbewehrtArray.shape, dtype=mzn.ZusdType)
    zusBetonUnbewehrtArray['Parameter'] = betonmischungUnbewehrtArray['Parameter']
    zusBetonUnbewehrtArray['Zusammensetzung'] = betonmischungUnbewehrtArray['Wert']
    zusBetonUnbewehrtArray['Einheit'] = 'kg/m³'
    #zusBetonUnbewehrtArray = zusBetonUnbewehrtArray[zusBetonUnbewehrtArray['Parameter'] != 'Volumen Beton']  # drop Volumen
    #zusBetonUnbewehrtArray = zusBetonUnbewehrtArray[zusBetonUnbewehrtArray['Zusammensetzung'] != 0]  # drop lines with 0
    zusBetonUnbewehrtArray = np.concatenate((
        zusBetonUnbewehrtArray,
        np.array([('Dichte unbewehrt', dichteArray[0]['Zusammensetzung'], dichteArray[0]['Einheit'])], dtype=mzn.ZusdType)
    ))
    # Zusammensetzung mit Bewehrung aufbereiten für Frontend
    ##########################################
    zusBetonArray = np.empty(betonmischungArray.shape, dtype=mzn.ZusdType)
    zusBetonArray['Parameter'] = betonmischungArray['Parameter']
    zusBetonArray['Zusammensetzung'] = betonmischungArray['Wert']
    zusBetonArray['Einheit'] = 'kg/m³'
    zusBetonArray = zusBetonArray[zusBetonArray['Parameter'] != 'Volumen Beton']  # drop Volumen
    zusBetonArray = zusBetonArray[zusBetonArray['Zusammensetzung'] != 0]  # drop lines with 0
    zusBetonArray = np.concatenate((
        zusBetonArray,
        np.array([('Dichte bewehrt', dichteArray[1]['Zusammensetzung'], dichteArray[1]['Einheit'])], dtype=mzn.ZusdType)
    ))
    # Details Resultate filtern nach gewähltem Umweltindikator
    ##########################################################
    detailsResGrpArray = fncts.lciaDetails(resultDict, inData)
    detailsResGrpArray = fncts.gruppiereBestandteile(detailsResGrpArray)
    # Übersicht Resultate aufbereiten für frontend
    ####################################################
    uebersichtResArray = fncts.lciaSum(resultDict)
    # Betonsorte anzeigen nur in detaillierter Erfassung
    #####################################################
    if inData['rechnertyp_select'] ==  'detailedDefinition':
        tabNormBezArray = np.array(
                [mzn.input_meta['normbezeichnungen']
                [fncts.calcConcreteNormLabel({t[0]: t[1] for t in zusBetonUnbewehrtArray})]['text']
                ], dtype=mzn.normBezdType)
    elif inData['rechnertyp_select'] ==  'npkDefinition':
        tabNormBezArray = np.array([])
    return inData, {
        'tabZusBetonUnbewehrt': zusBetonUnbewehrtArray,
        # to look at unbewehrt table, you my uncomment this line 'tabZusBeton': zusBetonUnbewehrtArray,
        'tabZusBeton': zusBetonArray,
        'tabDetailsAllBest': fncts.transposeNamedArray(resultDict['Herstellung']),
        'tabDetailsResGruppen':  detailsResGrpArray,
        'tabUebersichtRes': uebersichtResArray,
        'tabDichte': np.array([('Dichte', dichteArray[1]['Zusammensetzung'], dichteArray[1]['Einheit'])], dtype=mzn.ZusdType),
        'tabNormBez': tabNormBezArray,
    }

    #except Exception as e:
    #    import traceback
    #    return f"error={traceback.format_exc()}"

if __name__ == "__main__":
    # For testing only
    test_input = {
        'rechnertyp_select': 'detailedDefinition', 'anwendung_select': 'hochA',
        'cemtyp_select': 'cem1', 'cemtyp_masse': '280', 'cemtyp_lkw': '20.0', 'cemtyp_bahn': '100.0',
        'cemtyp2_select': 'cem1', 'cemtyp2_masse': '0', 'cemtyp2_lkw': '20.0', 'cemtyp2_bahn': '100.0',
        'betongranulat_masse': '10', 'betongranulat_lkw': '20.0', 'betongranulat_bahn': '0.0',
        'mischgranulat_masse': '20', 'mischgranulat_lkw': '20.0', 'mischgranulat_bahn': '0.0',
        'kies_rund_masse': '1405', 'kies_rund_lkw': '20.0', 'kies_rund_bahn': '0.0',
        'kies_gebrochen_masse': '14.2', 'kies_gebrochen_lkw': '20.0', 'kies_gebrochen_bahn': '0.0',
        'sand_masse': '400.4', 'sand_lkw': '20.0', 'sand_bahn': '0.0',
        'kalksteinmehl_masse': '50', 'kalksteinmehl_lkw': '20.0', 'kalksteinmehl_bahn': '0.0',
        'flugasche_masse': '66', 'flugasche_lkw': '20.0', 'flugasche_bahn': '0.0',
        'fliessmittel_masse': '1.3', 'fliessmittel_lkw': '50.0', 'fliessmittel_bahn': '600.0',
        'weitere_zusaetze_masse': '100', 'weitere_zusaetze_lkw': '50.0', 'weitere_zusaetze_bahn': '600.0',
        'wasser_masse': '140', 'wasser_lkw': '0.0', 'wasser_bahn': '0.0',
        'bewehrung_select': 'global', 'bewehrung_masse': '0', 'bewehrung_lkw': '50.0', 'bewehrung_bahn': '600.0',
        'einspeicherungKohle_masse': '11', 'einspeicherungKohle_lkw': '70.0', 'einspeicherungKohle_bahn': '0.0',
        'einspeicherungGranulat_masse': '12', 'einspeicherungGranulat_lkw': '0.0', 'einspeicherungGranulat_bahn': '0.0',
        'umwindikator_select': 'thg', 'actTabInput_select': 'tabZus'
        }
    result = rechneBilanz(test_input, 'detailedDefinition')
    print(result)  # This will show the R-script-like formatted string
