import numpy as np

def main():

    #input = open('sample_input.txt', 'r')
    input = open('input.txt', 'r')

    data = []

    for line in input.readlines():
        data.append(line.strip())

    print(f"DATA: {input.name}")
    print('-' * 20)
    part_one(data)
    print('-' * 20)
    part_two(data)
    print('-' * 20)

    return 0

def part_two(data):
    splitted_data = []

    for d in data:
        s = [char for char in d]
        splitted_data.append(s)

    matrix = np.array(splitted_data)
    oxygen_generator_rating = get_oxygen_generator_rating(matrix)
    co2_scrubber_rating = get_co2_scrubber_rating(matrix)
    print(f"OXYGEN GENERATOR RATING: {oxygen_generator_rating}")
    print(f"OXYGEN CO2 SCRUBBER RATING: {co2_scrubber_rating}")
    print(f"LIFE SUPPORT RATING: {oxygen_generator_rating * co2_scrubber_rating}")

def get_oxygen_generator_rating(matrix):
    temp_data = matrix
    data_length = len(matrix[0])
    for i in range(data_length):
        if len(temp_data) == 1:
            break
        most_common_bit = most_common_value(temp_data.T[i], '1')
        temp_data = filter_by_value_at_position(temp_data, most_common_bit, i)
    return int('0b' + ''.join(temp_data[0]), 2)

def get_co2_scrubber_rating(matrix):
    temp_data = matrix
    data_length = len(matrix[0])
    for i in range(data_length):
        if len(temp_data) == 1:
            break
        least_common = least_common_value(temp_data.T[i], '0')
        temp_data = filter_by_value_at_position(temp_data, least_common, i)
    return int('0b' + ''.join(temp_data[0]), 2)

def filter_by_value_at_position(data, val, pos):
    result = []
    for d in data:
        if d[pos] == val:
            result.append(d)
    return np.array(result)

def most_common_value(v, fallback_value):
    ones_count = np.count_nonzero(v == '1')
    zeros_count = np.count_nonzero(v == '0')
    if ones_count > zeros_count:
        return '1'
    elif ones_count < zeros_count:
        return '0'
    else:
        return fallback_value

def least_common_value(v, fallback_value):
    ones_count = np.count_nonzero(v == '1')
    zeros_count = np.count_nonzero(v == '0')
    if ones_count > zeros_count:
        return '0'
    elif ones_count < zeros_count:
        return '1'
    else:
        return fallback_value

def part_one(data):
    splitted_data = []

    for d in data:
        s = [char for char in d]
        splitted_data.append(s)

    matrix = np.array(splitted_data)
    transposed = matrix.T

    gamma_rate = []
    epsilon_rate = []

    for t in transposed:
        number_of_zeros = 0
        number_of_ones = 0
        for i in t:
            if i == '0':
                number_of_zeros += 1
            else:
                number_of_ones += 1

        if number_of_zeros > number_of_ones:
            gamma_rate.append('0')
            epsilon_rate.append('1')
        else:
            gamma_rate.append('1')
            epsilon_rate.append('0')

    binary_gamma_rate = bin(int('0b' + ''.join(gamma_rate), 2))
    binary_epsilon_rate = bin(int('0b' + ''.join(epsilon_rate), 2))
    decimal_gamma_rate = int(binary_gamma_rate, 2)
    decimal_epsilon_rate = int(binary_epsilon_rate, 2)

    print(f"GAMMA RATE: {decimal_gamma_rate}")
    print(f"EPSILON RATE: {decimal_epsilon_rate}")
    print(f"POWER CONSUMPTION: {decimal_gamma_rate * decimal_epsilon_rate}")

if __name__ == "__main__":
    main()


import sys

T = int(input())

for i in range(T):
    stack = []
    li = list(sys.stdin.readline().rstrip())

    t_f = True
    for j in li:
        if j == '(':
            stack.append(j)
        elif j == ')':
            if len(stack)==0:
                print("NO")
                t_f = False
                break
            else:
                stack.pop()

    if len(stack)==0 and t_f == True:
        print("YES")
    elif t_f == True:
        print("NO")


#!/usr/bin/python

#$Id: scenariobuilder.py,v 1.56 2010-07-19 13:50:21 jorn Exp $

from xmlstore.qt_compat import QtGui, QtCore, QtWidgets, qt4_backend, qt4_backend_version

# Import modules from standard Python library
import sys,xml, os.path

from .core import scenario, result
from . import commonqt
import xmlstore.util, xmlstore.gui_qt4

class ScenarioWidget(QtWidgets.QWidget):

    onCompleteStateChanged = QtCore.Signal()

    def __init__(self,parent=None,mrupaths=[]):
        QtWidgets.QWidget.__init__(self,parent)

        self.bngroup      = QtWidgets.QButtonGroup()
        self.radioNew     = QtWidgets.QRadioButton('Create a new scenario.',self)
        self.radioOpen    = QtWidgets.QRadioButton('Open an existing scenario.',self)
        self.radioImport1 = QtWidgets.QRadioButton('Import a namelist-based scenario from an existing directory.',self)
        self.radioImport2 = QtWidgets.QRadioButton('Import a namelist-based scenario from an archive.',self)

        #self.labTemplate = QtWidgets.QLabel('Template:',self)
        #default2path = scenario.Scenario.getDefaultValues()
        #self.comboTemplates = QtWidgets.QComboBox(parent)
        #for (name,path) in default2path.items():
        #    self.comboTemplates.addItem(name,name)
        #self.templatelayout = QtWidgets.QHBoxLayout()
        #self.templatelayout.addWidget(self.labTemplate)
        #self.templatelayout.addWidget(self.comboTemplates,1)
        #self.templatelayout.addStretch()

        self.pathOpen    = commonqt.PathEditor(self,header='File to open: ',mrupaths=mrupaths)
        self.pathImport1 = commonqt.PathEditor(self,header='Directory to import: ',getdirectory=True)
        self.pathImport2 = commonqt.PathEditor(self,header='Archive to import: ')

        self.pathOpen.filter    = 'GOTM scenario files (*.gotmscenario);;GOTM result files (*.gotmresult);;dataless GOTM scenario files (*.xml);;All files (*.*)'
        self.pathImport2.filter = 'tar/gz files (*.tar.gz);;zip files (*.zip);;All files (*.*)'

        self.bngroup.addButton(self.radioNew,    0)
        self.bngroup.addButton(self.radioOpen,   1)
        self.bngroup.addButton(self.radioImport1,2)
        self.bngroup.addButton(self.radioImport2,3)

        layout = QtWidgets.QGridLayout()
        layout.addWidget(self.radioNew,       0,0,1,3)
        #layout.addLayout(self.templatelayout, 1,1)
        layout.addWidget(self.radioOpen,      2,0,1,3)
        layout.addWidget(self.pathOpen,       3,1,1,2)
        layout.addWidget(self.radioImport1,   4,0,1,3)
        layout.addWidget(self.pathImport1,    5,1,1,2)
        layout.addWidget(self.radioImport2,   6,0,1,3)
        layout.addWidget(self.pathImport2,    7,1,1,2)

        self.checkSkipToSimulation = QtWidgets.QCheckBox('Simulate the scenario without further configuration.',self)
        layout.addWidget(self.checkSkipToSimulation,8,0,1,3)

        layout.setColumnStretch(2,1)

        layout.setColumnMinimumWidth(0,commonqt.getRadioWidth())

        layout.setContentsMargins(0,0,0,0)

        self.setLayout(layout)

        self.bngroup.buttonClicked.connect(self.onSourceChange)
        self.pathOpen.onChanged.connect(self.completeStateChanged)
        self.pathImport1.onChanged.connect(self.completeStateChanged)
        self.pathImport2.onChanged.connect(self.completeStateChanged)

        self.radioNew.setChecked(True)
        self.onSourceChange()

    def setPath(self,path):
        self.radioOpen.click()
        self.pathOpen.setPath(path)

    def onSourceChange(self):
        self.setUpdatesEnabled(False)
        checkedid = self.bngroup.checkedId()
        #self.labTemplate.setVisible(checkedid==0)
        #self.comboTemplates.setVisible(checkedid==0)
        self.pathOpen.setVisible(checkedid==1)
        self.pathImport1.setVisible(checkedid==2)
        self.pathImport2.setVisible(checkedid==3)
        self.checkSkipToSimulation.setVisible(checkedid!=0)
        self.completeStateChanged()
        self.setUpdatesEnabled(True)

    def isComplete(self):
        checkedid = self.bngroup.checkedId()
        if   checkedid==0:
            return True
        elif checkedid==1:
            return self.pathOpen.hasPath()
        elif checkedid==2:
            return self.pathImport1.hasPath()
        elif checkedid==3:
            return self.pathImport2.hasPath()

    def getScenario(self,callback=None,completecallback=None):
        if not self.isComplete(): return None
        QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.WaitCursor))
        try:
            checkedid = self.bngroup.checkedId()
            if   checkedid==0:
                #index = self.comboTemplates.currentIndex()
                #defname = unicode(self.comboTemplates.itemData(index).toString())
                #defscenario = scenario.Scenario.getDefault(defname,scenario.guiscenarioversion)
                #xmldom = defscenario.toXmlDom()
                scen = scenario.Scenario.fromSchemaName(scenario.guiscenarioversion)
                #scen.setStore(xmldom)
                scen.fillMissingValues()
                scen.resetChanged()
            elif checkedid==1:
                path = self.pathOpen.path()
                if path.endswith('.gotmresult'):
                    try:
                        res = result.Result()
                        res.load(path)
                    except Exception as e:
                        raise Exception('An error occurred while loading the result: '+str(e))
                    scen = res.scenario.addref()  # Note: the scenario version will be guiscenarioversion, set by Result
                    res.release()
                elif path.endswith('.xml'):
                    try:
                        scen = scenario.Scenario.fromSchemaName(scenario.guiscenarioversion)
                        scen.load(path)
                    except Exception as e:
                        raise Exception('An error occurred while loading the scenario: '+str(e))
                else:
                    try:
                        scen = scenario.Scenario.fromSchemaName(scenario.guiscenarioversion)
                        scen.loadAll(path,callback=callback)
                    except Exception as e:
                        raise Exception('An error occurred while loading the scenario: '+str(e))
            elif checkedid==2:
                try:
                    scen = scenario.Scenario.fromNamelists(self.pathImport1.path(),strict = False,targetversion=scenario.guiscenarioversion,requireplatform='gotm')
                except Exception as e:
                    raise Exception('Cannot parse namelist files. Error: '+str(e))
            elif checkedid==3:
                try:
                    scen = scenario.Scenario.fromNamelists(self.pathImport2.path(),strict = False,targetversion=scenario.guiscenarioversion,requireplatform='gotm')
                except Exception as e:
                    raise Exception('Cannot parse namelist files. Error: '+str(e))

        finally:
            QtWidgets.QApplication.restoreOverrideCursor()

        if checkedid!=0:
            # We have loaded a scenario from file. Look for empty nodes and reset these to their defaults.
            #emptynodes = scen.root.getEmptyNodes()
            emptynodes = [n for n in scen.root.getEmptyNodes() if not n.isHidden()]
            for node in emptynodes:
                defval = node.getDefaultValue()
                assert defval is not None, 'No value set for "%s", but no default value is available.' % node
                if isinstance(defval,xmlstore.util.referencedobject): defval.release()
            emptycount = len(emptynodes)
            if emptycount>0:
                if completecallback is not None: completecallback()
                QtWidgets.QMessageBox.information(self,'Scenario is incomplete','In this scenario the following %i variables do not have a value:\n\n%s\n\nThese variables will be set to their default value.' % (emptycount,'\n'.join(['/'.join(n.location) for n in emptynodes])),QtWidgets.QMessageBox.Ok)
                scen.changed = True

        return scen

    def setSkipToSimulation(self,value):
        self.checkSkipToSimulation.setChecked(value)

    def skipToSimulation(self):
        return self.bngroup.checkedId()!=0 and self.checkSkipToSimulation.isChecked()

    def completeStateChanged(self):
        self.onCompleteStateChanged.emit()

class PageOpen(commonqt.WizardPage):

    def __init__(self,parent=None):
        commonqt.WizardPage.__init__(self, parent)

        self.label = QtWidgets.QLabel('How do you want to obtain a scenario?',self)
        self.scenariowidget = ScenarioWidget(self)
        self.scenariowidget.onCompleteStateChanged.connect(self.completeStateChanged)

        layout = QtWidgets.QVBoxLayout()
        layout.addWidget(self.label)
        layout.addWidget(self.scenariowidget)
        layout.addStretch()
        self.setLayout(layout)

    def isComplete(self):
        return self.scenariowidget.isComplete()

    def saveData(self,mustbevalid):
        dialog = commonqt.ProgressDialog(self,title='Please wait...',suppressstatus=True)
        try:
            newscen = self.scenariowidget.getScenario(callback=dialog.onProgressed,completecallback=dialog.close)
        except Exception as e:
            QtWidgets.QMessageBox.critical(self, 'Unable to obtain scenario', str(e), QtWidgets.QMessageBox.Ok, QtWidgets.QMessageBox.NoButton)
            dialog.close()
            return False
        self.owner.setProperty('result',None)
        self.owner.setProperty('scenario',newscen)
        dialog.close()
        return True

class ScenarioPage(commonqt.WizardPage):
    def __init__(self,parent=None):
        commonqt.WizardPage.__init__(self, parent)

        # Needed to make sure that GUI editors for xmlplot data types (gotmdatafile) are registered.
        import xmlplot.gui_qt4

        self.scenario = parent.getProperty('scenario')
        if self.scenario is None: raise Exception('No scenario available; this page should not have been available.')

        self.factory = xmlstore.gui_qt4.PropertyEditorFactory(self.scenario,live=True,allowhide=True,datasourcedir=parent.getProperty('datasourcedir'))

    def saveData(self,mustbevalid):

        editednodes = self.factory.getEditedNodes()
        if mustbevalid:
            progressdialog = commonqt.ProgressDialog(self,title='Validating settings...')
            try:
                errors = self.scenario.validate(editednodes,callback=progressdialog.onProgressed,repair=1)
            finally:
                progressdialog.close()
            if len(errors)>0:
                msg = '\n'.join(errors)
                if len(errors)>1: msg = 'The following problems remain:\n\n%s' % msg
                QtWidgets.QMessageBox.critical(self,'Scenario has not been configured correctly',msg,QtWidgets.QMessageBox.Ok,QtWidgets.QMessageBox.NoButton)
                return False
        else:
            self.scenario.clearValidationHistory(editednodes)

        return True

    def isComplete(self):
        return True

    def destroy(self,destroyWindow = True,destroySubWindows = True):
        self.factory.unlink()
        commonqt.WizardPage.destroy(self,destroyWindow,destroySubWindows)

class PageLocation(ScenarioPage):

    def __init__(self,parent=None):
        ScenarioPage.__init__(self, parent)

        layout = QtWidgets.QVBoxLayout()
        layout.setSpacing(25)

        self.title = self.createHeader('Simulated location and period','Here you specify the geographic location and time period that you want to simulate. Optionally you can provide a title for the simulation and location.')
        layout.addWidget(self.title)

        # Editor for title

        editGeneral = self.factory.createEditor('title',self,groupbox=True)
        groupboxGeneral = editGeneral.editor
        layoutTitle = QtWidgets.QHBoxLayout()
        editTitle = self.factory.createEditor('title',self)
        editTitle.addToBoxLayout(layoutTitle,addstretch=False,unit=False)
        groupboxGeneral.setLayout(layoutTitle)
        layout.addWidget(groupboxGeneral)

        # Editors for geographic location

        editStation = self.factory.createEditor('station',self,groupbox=True)
        groupboxLocation = editStation.editor

        layoutLocation = QtWidgets.QGridLayout()
        editName      = self.factory.createEditor('station/name',     self)
        editLongitude = self.factory.createEditor('station/longitude',self)
        editLatitude  = self.factory.createEditor('station/latitude', self)
        editDepth     = self.factory.createEditor('station/depth',    self)

        editName.addToGridLayout(layoutLocation,unit=False,colspan=3)
        editLongitude.addToGridLayout(layoutLocation)
        editLatitude.addToGridLayout(layoutLocation)
        editDepth.addToGridLayout(layoutLocation)
        layoutLocation.setColumnStretch(3,1)
        groupboxLocation.setLayout(layoutLocation)
        layout.addWidget(groupboxLocation)

        # Editors for simulated period

        editPeriod = self.factory.createEditor('time',self,groupbox=True)
        groupboxPeriod = editPeriod.editor

        layoutPeriod = QtWidgets.QGridLayout()
        editStart = self.factory.createEditor('time/start',self)
        editStop  = self.factory.createEditor('time/stop' ,self)
        editStart.addToGridLayout(layoutPeriod)
        editStop.addToGridLayout(layoutPeriod)
        layoutPeriod.setColumnStretch(3,1)
        groupboxPeriod.setLayout(layoutPeriod)
        layout.addWidget(groupboxPeriod)

        # Build final layout

        layout.addStretch()
        self.setLayout(layout)

    def saveData(self,mustbevalid):
        if self.scenario.hasChanged() and not mustbevalid:
            res = QtWidgets.QMessageBox.warning(self,'Leaving scenario editor','All changes to your scenario will be lost. Do you want to continue?',QtWidgets.QMessageBox.Yes|QtWidgets.QMessageBox.No,QtWidgets.QMessageBox.No)
            if res==QtWidgets.QMessageBox.No: return False

        return ScenarioPage.saveData(self,mustbevalid)

class PageDiscretization(ScenarioPage):

    def __init__(self,parent=None):
        ScenarioPage.__init__(self, parent)

        layout = QtWidgets.QVBoxLayout()
        layout.setSpacing(25)

        self.title = self.createHeader('Discretization of space and time','Here you specify how the water column and the time period are discretized. Generally this involves a balance between accuracy and simulation time.')
        layout.addWidget(self.title)

        editColumn = self.factory.createEditor('grid',self,groupbox=True)

        layoutGrid = QtWidgets.QGridLayout()

        # Create controls for grid layout.
        editGridMethod = self.factory.createEditor('grid/grid_method',self,selectwithradio=True)
        self.bngroup = editGridMethod.editor
        editLevelCount  = self.factory.createEditor('grid/nlev',self)
        editZoomSurface = self.factory.createEditor('grid/ddu', self)
        editZoomBottom  = self.factory.createEditor('grid/ddl', self)
        editGridFile = self.factory.createEditor('grid/grid_file',self)

        # Add controls for grid layout to the widget.
        layoutGrid.addWidget(self.bngroup.buttonFromValue(0),    0,0,1,4)
        editLevelCount.addToGridLayout (layoutGrid,1,1)
        editZoomSurface.addToGridLayout(layoutGrid,2,1)
        editZoomBottom.addToGridLayout (layoutGrid,3,1)
        layoutGrid.addWidget(self.bngroup.buttonFromValue(1),    4,0,1,4)
        layoutGrid.addWidget(self.bngroup.buttonFromValue(2),    5,0,1,4)
        editGridFile.addToGridLayout(layoutGrid,   6,1,label=False,unit=False)

        editColumn.editor.setLayout(layoutGrid)

        layoutGrid.setColumnStretch(3,1)
        layoutGrid.setColumnMinimumWidth(0,commonqt.getRadioWidth())

        editTime = self.factory.createEditor('timeintegration',self,groupbox=True)

        layoutTime = QtWidgets.QGridLayout()
        editTimeStep = self.factory.createEditor('timeintegration/dt',self)
        editOutputStep = self.factory.createEditor('output/dtsave',self)
        editTimeStep.addToGridLayout(layoutTime)
        editOutputStep.addToGridLayout(layoutTime)
        layoutTime.setColumnStretch(3,1)
        editTime.editor.setLayout(layoutTime)

        layout.addWidget(editColumn.editor)
        layout.addWidget(editTime.editor)
        layout.addStretch()
        self.setLayout(layout)

class PageSalinity(ScenarioPage):

    def __init__(self,parent=None):
        ScenarioPage.__init__(self, parent)

        layout = QtWidgets.QVBoxLayout()

        self.title = self.createHeader('Salinity observations','Here you can provide observations on vertical salinity profiles. These are used to initialize the column, and optionally to relax model results to.')
        layout.addWidget(self.title)
        layout.addSpacing(20)

        # Create main layout
        layoutSalinity = QtWidgets.QGridLayout()

        # Create button group for the salinity option, and an explanatory label.
        editSalinity = self.factory.createEditor('obs/sprofile',self,selectwithradio=True)

        # Create editors for all different salinity configurations.
        editSalinityConstant       = self.factory.createEditor('obs/sprofile/s_const',self)
        editSalinityUpperThickness = self.factory.createEditor('obs/sprofile/z_s1',self)
        editSalinityUpper          = self.factory.createEditor('obs/sprofile/s_1',self)
        editSalinityLowerThickness = self.factory.createEditor('obs/sprofile/z_s2',self)
        editSalinityLower          = self.factory.createEditor('obs/sprofile/s_2',self)
        editSalinitySurface        = self.factory.createEditor('obs/sprofile/s_surf',self)
        editSalinityNSquare        = self.factory.createEditor('obs/sprofile/s_obs_NN',self)
        editSalinityFile           = self.factory.createEditor('obs/sprofile/s_prof_file',self)

        # Create editors for relaxation.
        editSalinityRelaxation     = self.factory.createEditor('obs/sprofile/SRelax',self,boolwithcheckbox=True)
        editSalinityBulk           = self.factory.createEditor('obs/sprofile/SRelax/SRelaxTauM',self)

        # Add explanatory label
        layoutSalinity.addWidget(editSalinity.createLabel(),       0,0,1,2)

        # Add button for no salinity
        layoutSalinity.addWidget(editSalinity.editor.buttonFromValue(0),     1,0,1,2)

        # Add button for constant salinity.
        layoutSalinity.addWidget(editSalinity.editor.buttonFromValue(11),    2,0,1,2)

        # Add controls to edit constant salinity.
        layoutConstant = QtWidgets.QGridLayout()
        editSalinityConstant.addToGridLayout(layoutConstant)
        layoutConstant.setColumnStretch(3,1)
        layoutSalinity.addLayout(layoutConstant,3,1)

        # Add button for two-layer salinity.
        layoutSalinity.addWidget(editSalinity.editor.buttonFromValue(12),    4,0,1,2)

        # Add controls to edit two-layer salinity.
        layoutLayer = QtWidgets.QGridLayout()
        editSalinityUpperThickness.addToGridLayout(layoutLayer)
        editSalinityUpper.addToGridLayout(layoutLayer)
        editSalinityLowerThickness.addToGridLayout(layoutLayer)
        editSalinityLower.addToGridLayout(layoutLayer)
        layoutLayer.setColumnStretch(3,1)
        layoutSalinity.addLayout(layoutLayer,5,1)

        # Add button for stably stratified salinity.
        layoutSalinity.addWidget(editSalinity.editor.buttonFromValue(13),    6,0,1,2)

        # Add controls to edit stably stratified salinity.
        layoutNSquare = QtWidgets.QGridLayout()
        editSalinitySurface.addToGridLayout(layoutNSquare)
        editSalinityNSquare.addToGridLayout(layoutNSquare)
        layoutNSquare.setColumnStretch(3,1)
        layoutSalinity.addLayout(layoutNSquare,7,1)

        # Add button for custom salinities.
        layoutSalinity.addWidget(editSalinity.editor.buttonFromValue(2),     8,0,1,2)

        # Add control to choose custom salinities.
        layoutFile = QtWidgets.QHBoxLayout()
        editSalinityFile.addToBoxLayout(layoutFile,label=False,unit=False)
        layoutFile.addStretch()
        layoutSalinity.addLayout(layoutFile,9,1)

        layoutSalinity.setColumnMinimumWidth(0,commonqt.getRadioWidth())
        layout.addLayout(layoutSalinity)

        # Add control to configure bulk relaxation.
        layout.addSpacing(20)
        layoutRelaxation = QtWidgets.QGridLayout()
        editSalinityRelaxation.addToGridLayout(layoutRelaxation,0,0,1,5,label=False,unit=False)
        editSalinityBulk.addToGridLayout(layoutRelaxation,1,1)
        layoutRelaxation.setColumnMinimumWidth(0,commonqt.getRadioWidth())
        layoutRelaxation.setColumnStretch(3,1)
        layout.addLayout(layoutRelaxation)

        layout.addStretch(1)

        # Set layout.
        self.setLayout(layout)

class PageTemperature(ScenarioPage):

    def __init__(self,parent=None):
        ScenarioPage.__init__(self, parent)

        layout = QtWidgets.QVBoxLayout()

        self.title = self.createHeader('Temperature observations','Here you can provide observations on vertical temperature profiles. These are used to initialize the column, and optionally to relax model results to.')
        layout.addWidget(self.title)
        layout.addSpacing(20)

        # Create main layout
        layoutTemperature = QtWidgets.QGridLayout()

        # Create button group for the temperature option, and an explanatory label.
        editTemperature = self.factory.createEditor('obs/tprofile',self,selectwithradio=True)

        # Create editors for all different temperature configurations.
        editTemperatureConstant       = self.factory.createEditor('obs/tprofile/t_const',self)
        editTemperatureUpperThickness = self.factory.createEditor('obs/tprofile/z_t1',self)
        editTemperatureUpper          = self.factory.createEditor('obs/tprofile/t_1',self)
        editTemperatureLowerThickness = self.factory.createEditor('obs/tprofile/z_t2',self)
        editTemperatureLower          = self.factory.createEditor('obs/tprofile/t_2',self)
        editTemperatureSurface        = self.factory.createEditor('obs/tprofile/t_surf',self)
        editTemperatureNSquare        = self.factory.createEditor('obs/tprofile/t_obs_NN',self)
        editTemperatureFile           = self.factory.createEditor('obs/tprofile/t_prof_file',self)

        # Create editors for relaxation.
        editTemperatureRelaxation     = self.factory.createEditor('obs/tprofile/TRelax',self,boolwithcheckbox=True)
        editTemperatureBulk           = self.factory.createEditor('obs/tprofile/TRelax/TRelaxTauM',self)

        # Add explanatory label
        layoutTemperature.addWidget(editTemperature.createLabel(),       0,0,1,2)

        # Add button for no temperature
        layoutTemperature.addWidget(editTemperature.editor.buttonFromValue(0),     1,0,1,2)

        # Add button for constant temperature.
        layoutTemperature.addWidget(editTemperature.editor.buttonFromValue(11),    2,0,1,2)

        # Add controls to edit constant temperature.
        layoutConstant = QtWidgets.QGridLayout()
        editTemperatureConstant.addToGridLayout(layoutConstant)
        layoutConstant.setColumnStretch(3,1)
        layoutTemperature.addLayout(layoutConstant,3,1)

        # Add button for two-layer temperature.
        layoutTemperature.addWidget(editTemperature.editor.buttonFromValue(12),    4,0,1,2)

        # Add controls to edit two-layer temperature.
        layoutLayer = QtWidgets.QGridLayout()
        editTemperatureUpperThickness.addToGridLayout(layoutLayer)
        editTemperatureUpper.addToGridLayout(layoutLayer)
        editTemperatureLowerThickness.addToGridLayout(layoutLayer)
        editTemperatureLower.addToGridLayout(layoutLayer)
        layoutLayer.setColumnStretch(3,1)
        layoutTemperature.addLayout(layoutLayer,5,1)

        # Add button for stably stratified temperature.
        layoutTemperature.addWidget(editTemperature.editor.buttonFromValue(13),    6,0,1,2)

        # Add controls to edit stably stratified temperature.
        layoutNSquare = QtWidgets.QGridLayout()
        editTemperatureSurface.addToGridLayout(layoutNSquare)
        editTemperatureNSquare.addToGridLayout(layoutNSquare)
        layoutNSquare.setColumnStretch(3,1)
        layoutTemperature.addLayout(layoutNSquare,7,1)

        # Add button for custom salinities.
        layoutTemperature.addWidget(editTemperature.editor.buttonFromValue(2),     8,0,1,2)

        # Add control to choose custom salinities.
        layoutFile = QtWidgets.QHBoxLayout()
        editTemperatureFile.addToBoxLayout(layoutFile,label=False,unit=False)
        layoutFile.addStretch()
        layoutTemperature.addLayout(layoutFile,9,1)

        layoutTemperature.setColumnMinimumWidth(0,commonqt.getRadioWidth())
        layout.addLayout(layoutTemperature)

        # Add control to configure bulk relaxation.
        layout.addSpacing(20)
        layoutRelaxation = QtWidgets.QGridLayout()
        editTemperatureRelaxation.addToGridLayout(layoutRelaxation,0,0,1,5,label=False,unit=False)
        editTemperatureBulk.addToGridLayout(layoutRelaxation,1,1)
        layoutRelaxation.setColumnMinimumWidth(0,commonqt.getRadioWidth())
        layoutRelaxation.setColumnStretch(3,1)
        layout.addLayout(layoutRelaxation)

        layout.addStretch(1)

        # Set layout.
        self.setLayout(layout)

class PageTurbulence(ScenarioPage):

    def __init__(self,parent=None):
        ScenarioPage.__init__(self, parent)

        # Create main layout
        layout = QtWidgets.QVBoxLayout()

        self.title = self.createHeader('Turbulence model','Here you choose the turbulence model to be used.')
        layout.addWidget(self.title)
        layout.addSpacing(20)

        layoutTurbulence = QtWidgets.QGridLayout()

        # Create button group for the turbulence method option, and an explanatory label.
        editMethod = self.factory.createEditor('gotmturb/turb_method',self,selectwithradio=True)
        bngrpMethod = editMethod.editor

        # Add explanatory label
        layoutTurbulence.addWidget(editMethod.createLabel(),0,0,1,3)

        # Add button for convective adjustment
        layoutTurbulence.addWidget(bngrpMethod.buttonFromValue(0), 1,0,1,3)

        # Add button for turbulence model calculating TKE and length scale
        layoutTurbulence.addWidget(bngrpMethod.buttonFromValue(2), 2,0,1,3)

        # Add controls specific to first-order model
        layoutFirstOrder = QtWidgets.QGridLayout()
        editStabilityMethod = self.factory.createEditor('gotmturb/stab_method',self)
        editStabilityMethod.addToGridLayout(layoutFirstOrder,0,0)
        layoutTurbulence.addLayout(layoutFirstOrder,      3,1)

        # Add button for second-order model
        layoutTurbulence.addWidget(bngrpMethod.buttonFromValue(3), 4,0,1,3)

        # Add controls specific to second-order model
        layoutSecondOrder = QtWidgets.QGridLayout()
        editSecondCoef = self.factory.createEditor('gotmturb/scnd/scnd_coeff',self)
        editSecondCoef.addToGridLayout(layoutSecondOrder,0,0)
        layoutTurbulence.addLayout(layoutSecondOrder,     5,1)

        # Add button for KPP model
        layoutTurbulence.addWidget(bngrpMethod.buttonFromValue(99),6,0,1,3)

        layoutTurbulence.setColumnStretch(3,1)
        layoutTurbulence.setColumnMinimumWidth(0,commonqt.getRadioWidth())
        layout.addLayout(layoutTurbulence)

        layout.addSpacing(20)

        layoutOther = QtWidgets.QGridLayout()
        editTkeMethod = self.factory.createEditor('gotmturb/tke_method',self)
        editLenScaleMethod = self.factory.createEditor('gotmturb/len_scale_method',self)
        editTkeMethod.addToGridLayout(layoutOther,0,0)
        editLenScaleMethod.addToGridLayout(layoutOther)
        layoutOther.setColumnStretch(3,1)
        layout.addLayout(layoutOther)

        layout.addStretch(1)

        # Set layout.
        self.setLayout(layout)

class PageAirSeaInteraction(ScenarioPage):

    def __init__(self,parent=None):
        ScenarioPage.__init__(self, parent)

        radiowidth = commonqt.getRadioWidth()

        # Create main layout
        layout = QtWidgets.QVBoxLayout()

        self.title = self.createHeader('Air-sea interaction: heat and momentum','Here you specify fluxes of heat and momentum across the ocean-atmosphere interface. Note that heat fluxes include latent and sensible fluxes, but not short-wave solar radiation.')
        layout.addWidget(self.title)
        layout.addSpacing(20)

        layoutAirSea = QtWidgets.QGridLayout()
        layoutAirSea.setColumnMinimumWidth(0,radiowidth)

        editCalcFluxes = self.factory.createEditor('airsea/flux_source',self,selectwithradio=True)

        # Meteo file and unit

        meteolayout = QtWidgets.QVBoxLayout()
        editMeteoFile = self.factory.createEditor('airsea/meteo_file',self)
        editWetMode   = self.factory.createEditor('airsea/hum_method',  self)

        meteofilelayout = QtWidgets.QHBoxLayout()
        editMeteoFile.addToBoxLayout(meteofilelayout,label=False,unit=False)
        meteofilelayout.addStretch()
        meteolayout.addLayout(meteofilelayout)

        meteowetmodelayout = QtWidgets.QHBoxLayout()
        editWetMode.addToBoxLayout(meteowetmodelayout)
        meteowetmodelayout.addStretch()
        meteolayout.addLayout(meteowetmodelayout)

        # Shortwave radiation

        #groupboxSwr = self.factory.createEditor('airsea/swr_method',self,groupbox=True).editor
        #
        #swrlayout = QtWidgets.QGridLayout()
        #swrlayout.setColumnMinimumWidth(0,radiowidth)
#
        #editSwrMethod = self.factory.createEditor('airsea/swr_method', self,selectwithradio=True)
        #editConstSwr  = self.factory.createEditor('airsea/const_swr',  self)
        #editSwrFile   = self.factory.createEditor('airsea/swr_file',   self)
        #
        #swrlayout.addWidget(editSwrMethod.editor.buttonFromValue(0),1,0,1,2)
#
        #swrlayout.addWidget(editSwrMethod.editor.buttonFromValue(1),2,0,1,2)
        #constswrlayout = QtWidgets.QHBoxLayout()
        #editConstSwr.addToBoxLayout(constswrlayout)
        #swrlayout.addLayout(constswrlayout,3,1)
        #
        #swrlayout.addWidget(editSwrMethod.editor.buttonFromValue(2),4,0,1,2)
        #swrfilelayout = QtWidgets.QHBoxLayout()
        #editSwrFile.addToBoxLayout(swrfilelayout,label=False,unit=False)
        #swrfilelayout.addStretch()
        #swrlayout.addLayout(swrfilelayout,5,1)
#
        #swrlayout.addWidget(editSwrMethod.editor.buttonFromValue(3),6,0,1,2)
        #
        #swrlayout.setColumnStretch(2,1)
        #
        #groupboxSwr.setLayout(swrlayout)
#
        # Heat flux

        groupboxHeat = self.factory.createEditor('airsea/heat_method',self,groupbox=True).editor

        heatlayout = QtWidgets.QGridLayout()
        heatlayout.setColumnMinimumWidth(0,radiowidth)

        editHeatMethod   = self.factory.createEditor('airsea/heat_method',  self,selectwithradio=True)
        editConstHeat    = self.factory.createEditor('airsea/const_heat',   self)
        editHeatfluxFile = self.factory.createEditor('airsea/heatflux_file',self)

        heatlayout.addWidget(editHeatMethod.editor.buttonFromValue(0),1,0,1,2)

        heatlayout.addWidget(editHeatMethod.editor.buttonFromValue(1),2,0,1,2)
        constheatlayout = QtWidgets.QHBoxLayout()
        editConstHeat.addToBoxLayout(constheatlayout)
        heatlayout.addLayout(constheatlayout,3,1)

        heatlayout.addWidget(editHeatMethod.editor.buttonFromValue(2),4,0,1,2)
        heatfilelayout = QtWidgets.QHBoxLayout()
        editHeatfluxFile.addToBoxLayout(heatfilelayout,label=False,unit=False)
        heatfilelayout.addStretch()
        heatlayout.addLayout(heatfilelayout,5,1)

        heatlayout.setColumnStretch(2,1)

        groupboxHeat.setLayout(heatlayout)

        # Momentum fluxes

        groupboxMomentum = self.factory.createEditor('airsea/momentum_method',self,groupbox=True).editor

        layoutMomentum = QtWidgets.QGridLayout()
        layoutMomentum.setColumnMinimumWidth(0,radiowidth)

        editMomentumMethod  = self.factory.createEditor('airsea/momentum_method',  self,selectwithradio=True)
        editMomentumConstTx = self.factory.createEditor('airsea/const_tx',         self)
        editMomentumConstTy = self.factory.createEditor('airsea/const_ty',         self)
        editmomentumFile    = self.factory.createEditor('airsea/momentumflux_file',self)

        layoutMomentum.addWidget(editMomentumMethod.editor.buttonFromValue(0),1,0,1,2)

        layoutMomentum.addWidget(editMomentumMethod.editor.buttonFromValue(1),2,0,1,2)
        constmomentumlayout = QtWidgets.QGridLayout()
        editMomentumConstTx.addToGridLayout(constmomentumlayout)
        editMomentumConstTy.addToGridLayout(constmomentumlayout)
        layoutMomentum.addLayout(constmomentumlayout,3,1)

        layoutMomentum.addWidget(editMomentumMethod.editor.buttonFromValue(2),4,0,1,2)
        momentumfilelayout = QtWidgets.QHBoxLayout()
        editmomentumFile.addToBoxLayout(momentumfilelayout,label=False,unit=False)
        momentumfilelayout.addStretch()
        layoutMomentum.addLayout(momentumfilelayout,5,1)

        layoutMomentum.setColumnStretch(2,1)

        groupboxMomentum.setLayout(layoutMomentum)

        # Freshwater fluxes

#        groupboxPe = QtWidgets.QGroupBox('fresh water flux',self)

#        layoutPe = QtWidgets.QGridLayout()
#        layoutPe.setColumnMinimumWidth(0,radiowidth)

#        self.editPeMethod  = self.factory.createEditor(['airsea','p_e_method'],   self,selectwithradio=True)
#        self.editPeConst   = self.factory.createEditor(['airsea','const_p_e'],    self)
#        self.editPeFile    = self.factory.createEditor(['airsea','p_e_flux_file'],self)

#        layoutPe.addWidget(self.editPeMethod.editor.buttonFromValue(0),1,0,1,2)

#        layoutPe.addWidget(self.editPeMethod.editor.buttonFromValue(1),2,0,1,2)
#        constpelayout = QtWidgets.QGridLayout()
#        self.editPeConst.addToGridLayout(constpelayout)
#        layoutPe.addLayout(constpelayout,3,1)

#        layoutPe.addWidget(self.editPeMethod.editor.buttonFromValue(2),4,0,1,2)
#        pefilelayout = QtWidgets.QHBoxLayout()
#        self.editPeFile.addToBoxLayout(pefilelayout,label=False,unit=False)
#        pefilelayout.addStretch()
#        layoutPe.addLayout(pefilelayout,5,1)

#        groupboxPe.setLayout(layoutPe)

        # Create final layout

        layoutAirSea.addWidget(editCalcFluxes.createLabel(),   0,0,1,2)
        layoutAirSea.addWidget(editCalcFluxes.editor.buttonFromValue(0),1,0,1,2)
        layoutAirSea.addLayout(meteolayout,                    2,1)
        layoutAirSea.addWidget(editCalcFluxes.editor.buttonFromValue(1),3,0,1,2)
        layoutAirSea.addWidget(groupboxHeat,                   4,1)
        layoutAirSea.addWidget(groupboxMomentum,               5,1)
        #layoutAirSea.addWidget(groupboxSwr,                    6,0,1,2)
        #layoutAirSea.addWidget(groupboxPe,4,0,1,2)

        layout.addLayout(layoutAirSea)

        layout.addStretch(1)

        self.setLayout(layout)

class PageAirSeaInteraction2(ScenarioPage):

    def __init__(self,parent=None):
        ScenarioPage.__init__(self, parent)

        radiowidth = commonqt.getRadioWidth()

        # Create main layout
        layout = QtWidgets.QVBoxLayout()

        self.title = self.createHeader('Air-sea interaction: short-wave radiation and precipitation','Here you specify short-wave solar radiation and precipitation at the ocean-atmosphere interface.')
        layout.addWidget(self.title)
        layout.addSpacing(20)

        layoutAirSea = QtWidgets.QGridLayout()
        layoutAirSea.setColumnMinimumWidth(0,radiowidth)

        # Shortwave radiation

        groupboxSwr = self.factory.createEditor('airsea/swr_method',self,groupbox=True).editor

        swrlayout = QtWidgets.QGridLayout()
        swrlayout.setColumnMinimumWidth(0,radiowidth)

        editSwrMethod = self.factory.createEditor('airsea/swr_method', self,selectwithradio=True)
        editConstSwr  = self.factory.createEditor('airsea/const_swr',  self)
        editSwrFile   = self.factory.createEditor('airsea/swr_file',   self)

        swrlayout.addWidget(editSwrMethod.editor.buttonFromValue(0),1,0,1,2)

        swrlayout.addWidget(editSwrMethod.editor.buttonFromValue(1),2,0,1,2)
        constswrlayout = QtWidgets.QHBoxLayout()
        editConstSwr.addToBoxLayout(constswrlayout)
        swrlayout.addLayout(constswrlayout,3,1)

        swrlayout.addWidget(editSwrMethod.editor.buttonFromValue(2),4,0,1,2)
        swrfilelayout = QtWidgets.QHBoxLayout()
        editSwrFile.addToBoxLayout(swrfilelayout,label=False,unit=False)
        swrfilelayout.addStretch()
        swrlayout.addLayout(swrfilelayout,5,1)

        swrlayout.addWidget(editSwrMethod.editor.buttonFromValue(3),6,0,1,2)

        swrlayout.setColumnStretch(2,1)

        groupboxSwr.setLayout(swrlayout)

        # Freshwater fluxes

        groupboxPe = self.factory.createEditor('airsea/precip_method',self,groupbox=True).editor

        layoutPe = QtWidgets.QGridLayout()
        layoutPe.setColumnMinimumWidth(0,radiowidth)

        self.editPeMethod  = self.factory.createEditor('airsea/precip_method',self,selectwithradio=True)
        self.editPeConst   = self.factory.createEditor('airsea/const_precip', self)
        self.editPeFile    = self.factory.createEditor('airsea/precip_file',  self)

        layoutPe.addWidget(self.editPeMethod.editor.buttonFromValue(0),1,0,1,2)

        layoutPe.addWidget(self.editPeMethod.editor.buttonFromValue(1),2,0,1,2)
        constpelayout = QtWidgets.QGridLayout()
        self.editPeConst.addToGridLayout(constpelayout)
        layoutPe.addLayout(constpelayout,3,1)

        layoutPe.addWidget(self.editPeMethod.editor.buttonFromValue(2),4,0,1,2)
        pefilelayout = QtWidgets.QHBoxLayout()
        self.editPeFile.addToBoxLayout(pefilelayout,label=False,unit=False)
        pefilelayout.addStretch()
        layoutPe.addLayout(pefilelayout,5,1)

        layoutPe.setColumnStretch(2,1)

        groupboxPe.setLayout(layoutPe)

        # Create final layout
        layoutAirSea.addWidget(groupboxSwr,1,0,1,2)
        layoutAirSea.addWidget(groupboxPe, 2,0,1,2)

        layout.addLayout(layoutAirSea)

        layout.addStretch(1)

        self.setLayout(layout)

class PageBio(ScenarioPage):

    def __init__(self,parent=None):
        ScenarioPage.__init__(self, parent)

        layout = QtWidgets.QVBoxLayout()
        layout.setSpacing(25)

        self.title = self.createHeader('Biogeochemistry','Here you can specify a biogeochemical model to use, and configure this model.')
        layout.addWidget(self.title)

        # Set up model/treeview for bio section of scenario
        self.tree = xmlstore.gui_qt4.TypedStoreTreeView(self,self.scenario,self.scenario['/bio/bio_model'],datasourcedir=parent.getProperty('datasourcedir'))
        self.tree.setRootIsDecorated(True)

        editBioModel = self.factory.createEditor('bio/bio_model',self)
        self.factory.attachExternalEditor(self.tree,'bio/bio_model',conditiontype='ne',conditionvalue=0)
        editBioModel.addToBoxLayout(layout)
        layout.addWidget(self.tree,10000)
        layout.addStretch(1)

        self.setLayout(layout)

    def destroy(self,destroyWindow = True,destroySubWindows = True):
        self.tree.destroy(destroyWindow,destroySubWindows)
        commonqt.WizardPage.destroy(self,destroyWindow,destroySubWindows)

class PageAdvanced(commonqt.WizardPage):

    def __init__(self,parent=None):
        commonqt.WizardPage.__init__(self, parent)

        self.scenario = parent.getProperty('scenario')
        if self.scenario is None: raise Exception('No scenario available; this page should not have been available.')

        self.tree = xmlstore.gui_qt4.TypedStoreTreeView(self,self.scenario,datasourcedir=parent.getProperty('datasourcedir'))

        layout = QtWidgets.QVBoxLayout()

        self.title = self.createHeader('Advanced settings','Here you can change all properties of your scenario. Values that differ from the default are shown in bold; they can be reset to their default by clicking them with the right mousebutton and choosing "Reset value".')
        layout.addWidget(self.title)
        layout.addSpacing(20)
        layout.addWidget(self.tree)
        self.setLayout(layout)

    def isComplete(self):
        return True

    def saveData(self,mustbevalid):
        if mustbevalid:
            progressdialog = commonqt.ProgressDialog(self,title='Validating settings...')
            try:
                errors = self.scenario.validate(callback=progressdialog.onProgressed,repair=1)
            finally:
                progressdialog.close()
            if len(errors)>0:
                msg = '\n'.join(errors)
                if len(errors)>1: msg = 'The following problems remain:\n\n%s' % msg
                QtWidgets.QMessageBox.critical(self,'Scenario is incomplete',msg,QtWidgets.QMessageBox.Ok,QtWidgets.QMessageBox.NoButton)
                return False
        return True

    def destroy(self,destroyWindow = True,destroySubWindows = True):
        self.tree.destroy(destroyWindow,destroySubWindows)
        commonqt.WizardPage.destroy(self,destroyWindow,destroySubWindows)

class PageSave(commonqt.WizardPage):

    def __init__(self,parent=None):
        commonqt.WizardPage.__init__(self, parent)

        self.scenario = parent.getProperty('scenario')

        self.label = QtWidgets.QLabel('The scenario has been modified. Do you want to save it?',self)
        self.bngroup     = QtWidgets.QButtonGroup()
        self.radioNoSave = QtWidgets.QRadioButton('No, I do not want to save the modified scenario.', parent)
        self.radioSave   = QtWidgets.QRadioButton('Yes, I want to save the scenario to file.', parent)

        self.pathSave = commonqt.PathEditor(self,header='File to save to: ',save=True)
        self.pathSave.filter = 'GOTM scenario files (*.gotmscenario);;All files (*.*)'

        if self.scenario.path is not None:
            self.pathSave.setPath(self.scenario.path)

        self.bngroup.addButton(self.radioNoSave, 0)
        self.bngroup.addButton(self.radioSave,   1)

        layout = QtWidgets.QGridLayout()
        layout.addWidget(self.label,0,0,1,2)
        layout.addWidget(self.radioNoSave,1,0,1,2)
        layout.addWidget(self.radioSave,2,0,1,2)
        layout.addWidget(self.pathSave,3,1,1,1)

        layout.setRowStretch(4,1)
        layout.setColumnStretch(1,1)

        layout.setColumnMinimumWidth(0,commonqt.getRadioWidth())

        self.setLayout(layout)

        self.bngroup.buttonClicked.connect(self.onSourceChange)
        self.pathSave.onChanged.connect(self.completeStateChanged)

        self.radioSave.setChecked(True)
        self.onSourceChange()

    def onSourceChange(self):
        checkedid = self.bngroup.checkedId()
        self.pathSave.setVisible(checkedid==1)
        self.completeStateChanged()

    def isComplete(self):
        checkedid = self.bngroup.checkedId()
        if   checkedid==0:
            return True
        elif checkedid==1:
            return self.pathSave.hasPath()

    def saveData(self,mustbevalid):
        if not mustbevalid: return True
        checkedid = self.bngroup.checkedId()
        if checkedid==1:
            targetpath = self.pathSave.path()
            if os.path.isfile(targetpath):
                ret = QtWidgets.QMessageBox.warning(self, 'Overwrite existing file?', 'There already exists a file at the specified location. Overwrite it?', QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No)
                if ret==QtWidgets.QMessageBox.No:
                    return False
            dialog = commonqt.ProgressDialog(self,title='Saving...',suppressstatus=True)
            try:
                self.scenario.saveAll(targetpath,callback=dialog.onProgressed)
            except Exception as e:
                QtWidgets.QMessageBox.critical(self, 'Unable to save scenario', str(e), QtWidgets.QMessageBox.Ok, QtWidgets.QMessageBox.NoButton)
                dialog.close()
                return False
            dialog.close()
            self.owner.getSettings().addUniqueValue('Paths/RecentScenarios','Path',targetpath)
        return True

    def doNotShow(self):
        return (not self.scenario.hasChanged())

class PageFinal(commonqt.WizardPage):

    def __init__(self,parent=None):
        commonqt.WizardPage.__init__(self, parent)

        self.label = QtWidgets.QLabel('Your scenario is now complete.',self)

        layout = QtWidgets.QVBoxLayout()
        layout.addWidget(self.label)
        layout.addStretch()
        self.setLayout(layout)

    def isComplete(self):
        return True

class SequenceEditScenario(commonqt.WizardSequence):
    def __init__(self):
        #commonqt.WizardSequence.__init__(self,[PageLocation,PageDiscretization,PageAirSeaInteraction,PageAirSeaInteraction2,PageTurbulence,PageTemperature,PageSalinity,PageBio,PageAdvanced,PageSave])
        commonqt.WizardSequence.__init__(self,[PageLocation,PageDiscretization,PageAirSeaInteraction,PageAirSeaInteraction2,PageTurbulence,PageTemperature,PageSalinity,PageAdvanced,PageSave])

def loadScenario():
    # Create the application and enter the main message loop.
    createQApp = QtWidgets.QApplication.startingUp()
    if createQApp:
        app = QtWidgets.QApplication([" "])
    else:
        app = QtWidgets.qApp

    # Create wizard dialog
    wiz = commonqt.Wizard(headerlogo=os.path.join(core.common.getDataRoot(),'logo.png'),allowfinish=True)
    wiz.setWindowTitle('Load scenario')
    wiz.resize(800, 600)
    seq = commonqt.WizardSequence([PageOpen])
    wiz.setSequence(seq)
    wiz.show()

    ret = app.exec_()

    scenario = wiz.getProperty('scenario')
    if scenario is not None: scenario.addref()

    wiz.destroy()

    return scenario

def editScenario(scenario):
    # Create the application and enter the main message loop.
    createQApp = QtWidgets.QApplication.startingUp()
    if createQApp:
        app = QtWidgets.QApplication([" "])
    else:
        app = QtWidgets.qApp

    import xmlplot.gui_qt4

    # Create wizard dialog
    wiz = commonqt.Wizard(headerlogo=os.path.join(core.common.getDataRoot(),'logo.png'),allowfinish=True)
    wiz.setWindowTitle('Scenario builder')
    wiz.resize(800, 600)

    wiz.setProperty('scenario',scenario.addref())
    wiz.setSequence(SequenceEditScenario())

    wiz.show()

    ret = app.exec_()

    scenario = wiz.getProperty('scenario')
    if scenario is not None: scenario.addref()

    wiz.destroy()

def main():
    # Debug info
    print('Python version: '+str(sys.version_info))
    print('%s version: %s' % (qt4_backend,qt4_backend_version))
    print('Qt version: '+QtCore.qVersion())
    print('xml version: '+xml.__version__)

    # Create the application and enter the main message loop.
    createQApp = QtWidgets.QApplication.startingUp()
    if createQApp:
        app = QtWidgets.QApplication([" "])
    else:
        app = QtWidgets.qApp

    import xmlplot.gui_qt4

    # Create wizard dialog
    wiz = commonqt.Wizard(headerlogo='./logo.png')
    wiz.setWindowTitle('Scenario builder')
    wiz.resize(800, 600)

    seq = commonqt.WizardSequence([PageOpen,SequenceEditScenario(),PageFinal])
    wiz.setSequence(seq)

    wiz.show()

    ret = app.exec_()

    scenario = wiz.getProperty('scenario')
    if scenario is not None: scenario.addref()

    wiz.destroy()

    return scenario

# If the script has been run (as opposed to imported), enter the main loop.
if (__name__=='__main__'):
    ret = main()
    sys.exit(0)


from fastapi import APIRouter, Depends, HTTPException, Request

from app.auth import get_current_active_user
from app.servicios.conectar_mongo import conexion_mongo
from app.servicios.Filtro import Filtro
from datetime import datetime, date, timedelta, time
from app.servicios.conectar_sql import conexion_sql, crear_diccionario
from app.servicios.permisos import tienePermiso
from app.servicios.logs import loguearConsulta, loguearError
import traceback
from inspect import stack
from app.servicios.formatoFechas import mesTexto

router = APIRouter(
    prefix="/columnasBasicas",
    # dependencies=[Depends(get_current_active_user)],
    responses={404: {"description": "Not found"}},
)

class ColumnasBasicas():
    def __init__(self, filtros: Filtro, titulo: str):
        self.filtros = filtros
        self.titulo = titulo
        if self.filtros.fechas != None:
            self.fecha_ini_a12 = datetime.combine(datetime.strptime(filtros.fechas['fecha_ini'], '%Y-%m-%dT%H:%M:%S.%fZ'), datetime.min.time()) if filtros.fechas['fecha_ini'] != None and filtros.fechas['fecha_ini'] != '' else None
            self.fecha_fin_a12 = datetime.combine(datetime.strptime(filtros.fechas['fecha_fin'], '%Y-%m-%dT%H:%M:%S.%fZ'), datetime.min.time()) + timedelta(days=1) if filtros.fechas['fecha_fin'] != None and filtros.fechas['fecha_fin'] != '' else None

    async def FoundRate(self):
        categorias = []
        series = []

        if self.titulo == 'Found Rate Vs. Fulfillment Rate':
            serie1 = []
            serie2 = []

            if self.filtros.region != '' and self.filtros.region != "False" and self.filtros.region != None:
                filtro_lugar = True
                if self.filtros.zona != '' and self.filtros.zona != "False" and self.filtros.zona != None:
                    nivel = 'zona'
                    siguiente_nivel = 'tiendaNombre'
                    lugar = int(self.filtros.zona)
                else:
                    nivel = 'region'
                    siguiente_nivel = 'zonaNombre'
                    lugar = int(self.filtros.region)
            else:
                filtro_lugar = False
                siguiente_nivel = 'regionNombre'
                lugar = ''

            collection = conexion_mongo('report').report_foundRate
            pipeline = [{'$unwind': '$sucursal'}]
            if filtro_lugar:
                pipeline.append({'$match': {'sucursal.'+ nivel: lugar}})
            pipeline.append({'$match': {'fechaUltimoCambio': {'$gte': self.fecha_ini_a12, '$lt': self.fecha_fin_a12}}})
            pipeline.append({'$group':{'_id':'$sucursal.'+siguiente_nivel, 'items_ini': {'$sum': '$items_ini'}, 'items_fin': {'$sum': '$items_fin'}, 'items_found': {'$sum': '$items_found'}}})
            pipeline.append({'$project':{'_id':'$_id', 'fulfillment_rate': {'$divide': ['$items_fin', '$items_ini']}, 'found_rate': {'$divide': ['$items_found', '$items_ini']}}})
            cursor = collection.aggregate(pipeline)
            arreglo = await cursor.to_list(length=1000)
            if len(arreglo) >0:
                hayResultados = "si"
                for row in arreglo:
                    categorias.append(row['_id'])
                    serie1.append(round(row['fulfillment_rate'], 4))
                    serie2.append(round(row['found_rate'], 4))
                series.extend([
                    {'name': 'Fulfillment Rate', 'data':serie1, 'color': 'primary'},
                    {'name': 'Found Rate', 'data':serie2, 'color': 'secondary'}
                ])
            else:
                hayResultados = "no"
        return {'hayResultados':hayResultados,'categorias':categorias, 'series':series, 'pipeline': pipeline, 'categoria':self.filtros.categoria}
        # Para debugging
        # return {'hayResultados':'no','categorias':[], 'series':[], 'pipeline': []}

    async def Nps(self):
        categorias = []
        series = []
        clauseCatProveedor = ""
        if len(self.filtros.provLogist) > 0:
            clauseCatProveedor = " AND ("
            contador = 0
            for prov in self.filtros.provLogist:
                if prov == 'Recursos Propios':
                    clauseCatProveedor += f" ho.tercero IS NULL "
                else:
                    clauseCatProveedor += f" ho.tercero = '{prov}' "
                if contador < len(self.filtros.provLogist) - 1:
                    clauseCatProveedor += f" OR "
                else:
                    clauseCatProveedor += f") "
                contador += 1

        if self.titulo == 'Distribución de clientes por calificación':
            serie1 = []
            serie2 = []

            if self.filtros.agrupador == 'dia':
                agrupador_select = "dt.fecha"
                mes = int(self.filtros.periodo['mes'])
                mes = str(mes) if mes >= 10 else '0'+str(mes)
                dia = int(self.filtros.periodo['dia'])
                dia = str(dia) if dia >= 10 else '0'+str(dia)
                agrupador_where = f" dt.fecha='{self.filtros.periodo['anio']}-{mes}-{dia}'"
            elif self.filtros.agrupador == "semana":
                agrupador_select = "dt.anio, dt.num_semana"
                semana_completa = str(self.filtros.periodo['semana'])
                num_semana = str(int(semana_completa[4:]))
                anio = semana_completa[0:4]
                agrupador_where = f" dt.anio={anio} and dt.num_semana='W{num_semana}'"
            elif self.filtros.agrupador == "mes":
                agrupador_select = "dt.anio, dt.num_mes"
                agrupador_where = f" dt.num_mes={self.filtros.periodo['mes']} and dt.anio={self.filtros.periodo['anio']}"

            # Rawa
            # pipeline = f"""select nd.calificacion,count(1) reg, {agrupador_select}
            # from DWH.limesurvey.nps_mail_pedido nmp
            # inner join DWH.limesurvey.nps_detalle nd on nmp.id_encuesta =nd.id_encuesta and nd.nEncuesta=nmp.nEncuesta
            # left join DWH.artus.catTienda ct on nmp.idTienda =ct.tienda
            # left join DWH.dbo.dim_tiempo dt on nmp.fecha = dt.fecha
            # where {agrupador_where} """
            pipeline = f"""select nd.calificacion,count(1) reg, {agrupador_select}
            from DWH.limesurvey.nps_mail_pedido nmp
            inner join DWH.limesurvey.nps_detalle nd on nmp.id_encuesta =nd.id_encuesta and nd.nEncuesta=nmp.nEncuesta
            left join DWH.artus.catTienda ct on nmp.idTienda =ct.tienda
            LEFT JOIN DWH.dbo.hecho_order ho ON ho.order_number =nmp.pedido
            left join DWH.dbo.dim_tiempo dt on ho.creation_date = dt.fecha
            where {agrupador_where} """
            if self.filtros.tienda != '' and self.filtros.tienda != None and self.filtros.tienda != 'False':
                pipeline += f""" and ct.tienda ='{self.filtros.tienda}' """
            elif self.filtros.zona != '' and self.filtros.zona != None and self.filtros.zona != 'False':
                pipeline += f" and ct.zona='{self.filtros.zona}' "
            elif self.filtros.region != '' and self.filtros.region != None and self.filtros.region != 'False':
                pipeline += f" and ct.region ='{self.filtros.region}' "
            pipeline += clauseCatProveedor
            pipeline += f" group by nd.calificacion, {agrupador_select} order by calificacion"

            # print("query desde columnas básicas nps ahorita: "+pipeline)
            cnxn = conexion_sql('DWH')
            cursor = cnxn.cursor().execute(pipeline)
            arreglo = crear_diccionario(cursor)

            if len(arreglo) > 0:
                hayResultados = "si"
                for i in range(len(arreglo)):
                    categorias = []
                    serie1.append(arreglo[i]['reg'])

                series = [
                    {
                        'name': 'Distribución',
                        'data': serie1,
                        'type': 'column',
                        'formato_tooltip':'entero',
                        'color':'primary'
                    }
                ]
            else:
                hayResultados = 'no'

        if self.titulo == 'Distribución de clientes por calificación 2':
            serie1 = []
            serie2 = []

            if self.filtros.agrupador == 'dia':
                agrupador_select = "dt.fecha"
                mes = int(self.filtros.periodo['mes'])
                mes = str(mes) if mes >= 10 else '0'+str(mes)
                dia = int(self.filtros.periodo['dia'])
                dia = str(dia) if dia >= 10 else '0'+str(dia)
                agrupador_where = f" dt.fecha='{self.filtros.periodo['anio']}-{mes}-{dia}'"
            elif self.filtros.agrupador == "semana":
                agrupador_select = "dt.anio, dt.num_semana"
                semana_completa = str(self.filtros.periodo['semana'])
                num_semana = str(int(semana_completa[4:]))
                anio = semana_completa[0:4]
                agrupador_where = f" dt.anio={anio} and dt.num_semana='W{num_semana}'"
            elif self.filtros.agrupador == "mes":
                agrupador_select = "dt.anio, dt.num_mes"
                agrupador_where = f" dt.num_mes={self.filtros.periodo['mes']} and dt.anio={self.filtros.periodo['anio']}"
            # Rawa
            # pipeline = f"""select nd.calificacion,count(1) reg, {agrupador_select}
            # from DWH.limesurvey.nps_mail_pedido nmp
            # inner join DWH.limesurvey.nps_detalle nd on nmp.id_encuesta =nd.id_encuesta and nd.nEncuesta=nmp.nEncuesta
            # left join DWH.artus.catTienda ct on nmp.idTienda =ct.tienda
            # left join DWH.dbo.dim_tiempo dt on nmp.fecha = dt.fecha
            # where {agrupador_where} """
            pipeline = f"""select nd.calificacion,count(1) reg, {agrupador_select}
            from DWH.limesurvey.nps_mail_pedido nmp
            inner join DWH.limesurvey.nps_detalle nd on nmp.id_encuesta =nd.id_encuesta and nd.nEncuesta=nmp.nEncuesta
            left join DWH.artus.catTienda ct on nmp.idTienda =ct.tienda
            LEFT JOIN DWH.dbo.hecho_order ho ON ho.order_number =nmp.pedido
            left join DWH.dbo.dim_tiempo dt on ho.creation_date = dt.fecha
            where {agrupador_where} """
            if self.filtros.tienda != '' and self.filtros.tienda != None and self.filtros.tienda != 'False':
                pipeline += f""" and ct.tienda ='{self.filtros.tienda}' """
            elif self.filtros.zona != '' and self.filtros.zona != None and self.filtros.zona != 'False':
                pipeline += f" and ct.zona='{self.filtros.zona}' "
            elif self.filtros.region != '' and self.filtros.region != None and self.filtros.region != 'False':
                pipeline += f" and ct.region ='{self.filtros.region}' "
            pipeline += clauseCatProveedor
            pipeline += f" group by nd.calificacion, {agrupador_select} order by calificacion"

            # print("query desde columnas básicas nps ahorita: "+pipeline)
            cnxn = conexion_sql('DWH')
            cursor = cnxn.cursor().execute(pipeline)
            arreglo = crear_diccionario(cursor)

            if len(arreglo) > 0:
                hayResultados = "si"
                categorias = ['']
                for i in range(len(arreglo)):
                    name = int(arreglo[i]['calificacion'])
                    y = [arreglo[i]['reg']]
                    if name <=6:
                        color = 'danger'
                    elif name <= 8:
                        color = 'warning'
                    else:
                        color = 'success'
                    series.append( {
                        'name': name,
                        'y': y,
                        'type': 'column',
                        'formato_tooltip':'entero',
                        'color': color
                    })
            else:
                hayResultados = 'no'

        return {'hayResultados':hayResultados,'categorias':categorias, 'series':series, 'pipeline': pipeline, 'categoria':self.filtros.categoria}
        # Para debugging
        # return {'hayResultados':'no','categorias':[], 'series':[], 'pipeline': []}

    async def CostoPorPedido(self):
        categorias = []
        series = []
        subSubTitulo = ''
        queryMetodoEnvio = f"and cf.TiendaEnLinea = '{self.filtros.metodoEnvio}'" if self.filtros.metodoEnvio != '' and self.filtros.metodoEnvio != "False" and self.filtros.metodoEnvio != None else ''
        if self.filtros.anio != 0 and self.filtros.anio != None:
            query1Anio = f"and cf.Anio = {self.filtros.anio}"
            query2Anio = f"and Anio = {self.filtros.anio}"
        else:
            query1Anio = query2Anio = ''
        if self.filtros.mes != 0 and self.filtros.mes != None:
            query1Mes = f"and cf.Mes = {self.filtros.mes}"
            query2Mes = f"and Mes = {self.filtros.mes}"
        else:
            query1Mes = query2Mes = ''
        if self.filtros.region != '' and self.filtros.region != "False" and self.filtros.region != None:
            if self.filtros.zona != '' and self.filtros.zona != "False" and self.filtros.zona != None:
                if self.filtros.tienda != '' and self.filtros.tienda != "False" and self.filtros.tienda != None:
                    queryLugar = f""" and ct.tienda = {self.filtros.tienda} """
                    lugar = f"tiendaNombre"
                else:
                    queryLugar = f""" and ct.zona = {self.filtros.zona} """
                    lugar = f"tiendaNombre"
            else:
                queryLugar = f""" and ct.region = {self.filtros.region} """
                lugar = f"zonaNombre"
        else:
            queryLugar = ''
            lugar = f"regionNombre"

        if self.titulo == 'Pedidos por Picker':
            series = []
            data = []
            query = f"""select * from  DWH.artus.catCostos"""
            cnxn = conexion_sql('DWH')
            cursor = cnxn.cursor().execute(query)
            costosReferencia_tmp = crear_diccionario(cursor)
            costosReferencia = {}
            for row in costosReferencia_tmp:
                costosReferencia[row['descripCosto']] = row['Costo']
            pipeline = f"""select {lugar} as lugar, sum(cf.pRH) pedidos from dwh.report.consolidadoFinanzas cf
                left join DWH.artus.catTienda ct on cf.Cebe = ct.tienda
                left join DWH.dbo.dim_tiempo dt on dt.id_fecha = cf.Anio * 10000 + cf.Mes * 100 + 1
                where Mes <= 12
                {queryMetodoEnvio}
                {query1Anio}
                {query1Mes}
                {queryLugar}
                group by {lugar}
                """
            # print(f"query desde ColumnasBasicas->PedidosPorPicker->{self.titulo}->General: {str(pipeline)}")
            cnxn = conexion_sql('DWH')
            cursor = cnxn.cursor().execute(pipeline)
            arreglo = crear_diccionario(cursor)
            if len(arreglo) > 0:
                hayResultados = "si"
                for row in arreglo:
                    query = f"""select SUM(Ocupada) as pickers from DWH.report.consolidadoFinanzasPicker cfp
                    left join DWH.artus.catTienda ct on cfp.Cebe = ct.tienda
                    where Puesto like '%surtidor%'
                    and ct.{lugar} = '{row['lugar']}'
                    {query2Anio}
                    {query2Mes}
                    {queryLugar}"""
                    # print(f"query desde columnasBasicas->PedidosPorPicker->{self.titulo}->Picker: {str(query)}")
                    cursor = cnxn.cursor().execute(query)
                    arregloSub = crear_diccionario(cursor)
                    pedidos = float(row['pedidos'])
                    pickers = float(arregloSub[0]['pickers']) if arregloSub[0]['pickers'] is not None else 0
                    # print(f"DataSub: {str(dataSub)}")
                    pedidosPorPicker = pedidos / pickers if pickers != 0 else 0
                    data.append((pedidosPorPicker, row['lugar']))
                # Lo ordenas según https://stackoverflow.com/questions/31942169/python-sort-array-of-arrays-by-multiple-conditions
                data = sorted(data)
                # Extraes los datos para las series y las categories
                for tupla in data:
                    series.append(tupla[0])
                    categorias.append(tupla[1])
                series = [
                    {
                        'name': 'Pedidos por Picker',
                        'data': series,
                        'type': 'column',
                        'formato_tooltip':'moneda',
                        'color':'primary'
                    }
                ]
            else:
                hayResultados = 'no'
                series = []

        if self.titulo == 'Costo de RH por pedido':
            series = []
            data = []
            query = f"""select * from  DWH.artus.catCostos"""
            cnxn = conexion_sql('DWH')
            cursor = cnxn.cursor().execute(query)
            costosReferencia_tmp = crear_diccionario(cursor)
            costosReferencia = {}
            for row in costosReferencia_tmp:
                costosReferencia[row['descripCosto']] = row['Costo']
            pipeline = f"""select {lugar} as lugar, sum(cf.pRH) pedidos, sum(cf.RH) RH, SUM(cf.Envio) Envio, SUM(cf.Combustible) Combustible from dwh.report.consolidadoFinanzas cf
                left join DWH.artus.catTienda ct on cf.Cebe = ct.tienda
                left join DWH.dbo.dim_tiempo dt on dt.id_fecha = cf.Anio * 10000 + cf.Mes * 100 + 1
                where Mes <= 12
                {queryMetodoEnvio}
                {query1Anio}
                {query1Mes}
                {queryLugar}
                group by {lugar}
                """
            # print(f"query desde tablas->CostoPorPedido->{self.titulo}->General: {str(pipeline)}")
            cnxn = conexion_sql('DWH')
            cursor = cnxn.cursor().execute(pipeline)
            arreglo = crear_diccionario(cursor)
            if len(arreglo) > 0:
                hayResultados = "si"
                for row in arreglo:
                    pedidos = float(row['pedidos'])
                    RH = float(row['RH']) if row['RH'] is not None else 0
                    Envio = float(row['Envio']) if row['Envio'] is not None else 0
                    Combustible = float(row['Combustible']) if row['Combustible'] is not None else 0
                    # print(f"DataSub: {str(dataSub)}")
                    RHxPedido = ((RH + Envio + Combustible) / pedidos) if pedidos != 0 else 0
                    data.append((RHxPedido, row['lugar']))
                # Lo ordenas según https://stackoverflow.com/questions/31942169/python-sort-array-of-arrays-by-multiple-conditions
                data = sorted(data)
                # Extraes los datos para las series y las categories
                for tupla in data:
                    series.append(
                    {
                        'name': tupla[1],
                        'y': tupla[0],
                        'type': 'column',
                        'formato_tooltip':'moneda',
                        'color':'secondary'
                    }
                    )
            else:
                hayResultados = 'no'
                series = []

        if self.titulo == 'Prueba':
            hayResultados = 'si'
            pipeline = []
            categorias = ['mexicanos', 'al', 'grito', 'de', 'guerra', 'acero']
            series = []
            #     {
            #         'name': 'Costo de RH por Pedido',
            #         'data': [5,3,7,9,4,5],
            #         'type': 'column',
            #         'formato_tooltip':'moneda',
            #         'color':'secondary'
            #     }
            # ]
            data = [300,500,100, 900,600,300]
            for i in range(6):
                if i % 3 == 0:
                    color = 'success'
                elif i % 3 == 1:
                    color = '#FFFFFF'
                else:
                    color = 'danger'
                series.append( {
                    'y': data[i],
                    'type': 'column',
                    'formato_tooltip':'entero',
                    'color': color
                })
            subSubTitulo = f'Actualizado al {datetime.now().strftime("%d/%m/%Y a las %H:%M")}'
        return {'hayResultados':hayResultados, 'series':series, 'categorias': categorias, 'subSubTitulo': subSubTitulo, 'pipeline': pipeline}

    async def PedidosPendientes(self):
        pipeline = []
        data = []
        categorias = []
        subSubTitulo = ''
        series = []
        filtroHoy = {
            '$match': {
                'prioridad': {
                    '$ne': 'Futuro'
                }
            }
        }
        if self.filtros.region != '' and self.filtros.region != "False" and self.filtros.region != None:
            filtro_lugar = True
            if self.filtros.zona != '' and self.filtros.zona != "False" and self.filtros.zona != None:
                if self.filtros.tienda != '' and self.filtros.tienda != "False" and self.filtros.tienda != None:
                    nivel = 'idTienda'
                    lugar = int(self.filtros.tienda)
                else:
                    nivel = 'zona'
                    lugar = int(self.filtros.zona)
            else:
                nivel = 'region'
                lugar = int(self.filtros.region)
        else:
            filtro_lugar = False
            lugar = ''

        collection = conexion_mongo('report').report_pedidoPendientes
        pipeline.extend([
            {'$unwind': '$sucursal'},
            filtroHoy
            ])
        if filtro_lugar:
            pipeline.append({'$match': {'sucursal.'+ nivel: lugar}})
        if self.filtros.tipoEntrega != None and self.filtros.tipoEntrega != "False" and self.filtros.tipoEntrega != "":
            pipeline.append({'$match': {'metodoEntrega': self.filtros.tipoEntrega}})
        if self.filtros.origen != None and self.filtros.origen != "False" and self.filtros.origen != "":
            pipeline.append({'$match': {'origen': self.filtros.origen}})

        if self.titulo == 'Pedidos pendientes de entrega':
            # pipeline.append({'$project': {'2_DIAS': {'$cond': [{'$eq':['$prioridad', '2 DIAS']}, 1, 0]}, 'HOY_ATRASADO': {'$cond': [{'$eq':['$prioridad', 'HOY ATRASADO']}, 1, 0]}, '1_DIA': {'$cond': [{'$eq':['$prioridad', '1 DIA']}, 1, 0]}, 'HOY_A_TIEMPO': {'$cond': [{'$eq':['$prioridad', 'HOY A TIEMPO']}, 1, 0]}, 'ANTERIORES': {'$cond': [{'$eq':['$prioridad', 'ANTERIORES']}, 1, 0]}}})
            # pipeline.append({'$group':{'_id':0, '2_DIAS':{'$sum':'$2_DIAS'}, 'HOY_ATRASADO':{'$sum':'$HOY_ATRASADO'}, '1_DIA':{'$sum':'$1_DIA'}, 'HOY_A_TIEMPO':{'$sum':'$HOY_A_TIEMPO'}, 'ANTERIORES':{'$sum':'$ANTERIORES'}}})
            pipeline.append({
                '$group': {
                    '_id':'$prioridad',
                    'pedidos':{'$sum':1},
                    'actualizacion': {'$max': '$fechaUpdate'}
                }
            })
            # print(f"Pipeline desde columnasBasicas -> Pedidos Pendientes: {str(pipeline)}")
            cursor = collection.aggregate(pipeline)
            arreglo = await cursor.to_list(length=1000)
            # print(str(arreglo))
            if len(arreglo) <= 0:
                # print("No hubo resultados")
                hayResultados = "no"
            else:
                hayResultados = "si"
                estatus = ['ANTERIORES', '2 DIAS', '1 DIA', 'HOY ATRASADO', 'HOY A TIEMPO']
                categorias = [elemento.title() for elemento in estatus]
                valores = [next((item.get("pedidos", 0) for item in arreglo if item["_id"] == key),0) for key in estatus]
                # print(f"Valores = {valores}")
                for i in range(len(estatus)):
                    if i == 0:
                        color = 'danger'
                    elif i == 4:
                        color = 'secondary'
                    else:
                        color = 'light'
                    # res[indice]['y'] = resultado['pedidos']
                    series.append({
                        'y': valores[i],
                        'type': 'column',
                        'formato_tooltip':'entero',
                        'color': color
                    })
                actualizacion = arreglo[0]['actualizacion'] - timedelta(hours=6)
                actualizacion = actualizacion.strftime("%d/%m/%Y a las %H:%M:%S")
                subSubTitulo = f"Actualizado al {actualizacion} hrs."

        return {'hayResultados':hayResultados, 'series':series, 'categorias': categorias, 'subSubTitulo': subSubTitulo, 'pipeline': pipeline}

@router.post("/{seccion}")
async def columnas_basicas (filtros: Filtro, titulo: str, seccion: str, request: Request, user: dict = Depends(get_current_active_user)):
    loguearConsulta(stack()[0][3], user.usuario, seccion, titulo, filtros, request.client.host)
    if tienePermiso(user.id, seccion):
        try:
            objeto = ColumnasBasicas(filtros, titulo)
            funcion = getattr(objeto, seccion)
            diccionario = await funcion()
        except:
            error = traceback.format_exc()
            loguearError(stack()[0][3], user.usuario, seccion, titulo, error, filtros, request.client.host)
            return {'hayResultados':'error'}
        return diccionario

    else:
        return {"message": "No tienes permiso para acceder a este recurso."}


import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split

def create_train_vaidate():
    data = pd.read_csv('./data/train.csv')
    data = data.sample(frac=1).reset_index(drop=True)

    labels = data['label']
    images = data[[i for i in data.columns if i != 'label']]

    images = images.values
    images = np.reshape(images, (images.shape[0], 28, 28, 1))

    train_data, valid_data, train_labels, valid_labels = train_test_split(images, labels, test_size=0.25)

    np.save('./data/train_data.pickle', train_data)
    np.save('./data/train_labels.pickle', train_labels)
    np.save('./data/valid_data.pickle', valid_data)
    np.save('./data/valid_labels.pickle', valid_labels)

if __name__ == '__main__':
    create_train_vaidate()


import numpy as np
import scipy.ndimage as nd
from sklearn.base import BaseEstimator
from sklearn.ensemble import RandomForestClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.dummy import DummyClassifier

class SegmentationClassifier(BaseEstimator):
    def __init__(self):
        #self.clf = RandomForestClassifier(n_estimators=3, max_depth=10)
        self.clf = DummyClassifier(strategy="constant", constant=0)
        #self.shift = 2 # how many neighbours are taken for calculating features

    def _get_features(self, X):
        # very slow
        n_img, n_h, n_w, n_d = X.shape
        n_features = 2
        x_features = np.zeros((n_img, n_h, n_w, n_d, n_features))
        for img_no in range(n_img):
            for h in range(self.shift,n_h-self.shift):
                for w in range(self.shift,n_w-self.shift):
                    for d in range(self.shift,n_d-self.shift):
                        # calculate features: mean and std of all neighbouring voxels
                        cube = X[img_no,h-self.shift:h+self.shift,w-self.shift:w+self.shift,d-self.shift:d+self.shift]

                        x_features[img_no,h,w,d,:] = (cube.mean(), np.std(cube))
        x_features = x_features.reshape(n_img * n_h * n_w * n_d, n_features)

        return x_features

    def _get_features_scipy(self, X):

        n_features = 1
        n_img, n_h, n_w, n_d = X.shape

        x_features = np.zeros((n_img, n_h, n_w, n_d, n_features))
        # for every patient separately
        for img_no in range(n_img):
            # calculate gaussian filter
            x_features[img_no,:,:,:,0] = nd.gaussian_filter(X[img_no,:,:,:],sigma=5)

            # calculate uniform filter
            #x_features[img_no,:,:,:,1] = nd.uniform_filter(X[img_no,:,:,:])

        x_features = x_features.reshape(n_img * n_h * n_w * n_d, n_features)
        return x_features

    def _get_features_strided(self, X):

        # cut the borders
        n_img, n_h, n_w, n_d = X.shape
        n_features = 2
        new_shape = (n_img, n_h - (2 * self.shift), n_w - (2 * self.shift), n_d - (2 * self.shift),
                     (2 * self.shift), 2 * self.shift, 2 * self.shift)
        new_strides = X.strides + X.strides[1:]
        X_cube = np.lib.stride_tricks.as_strided(X, shape=new_shape, strides=new_strides, writeable = False)

        #  FIXME: produces nans
        #  calculate feature: STD avoiding creation of large temporary arrays
        Xsq_cube = np.lib.stride_tricks.as_strided(X * X, new_shape, new_strides)

        # calculate feature: means
        X_means = np.mean(X_cube, axis=(4, 5, 6))
        Xsq_means = np.mean(Xsq_cube, axis=(4, 5, 6))
        X_stds = np.sqrt(Xsq_means - X_means * X_means)

        # get voxel value
        #voxel = np.reshape(X, new_shape[:4])

        X_features = np.stack([X_means, voxel], axis=4).reshape(n_img * (n_h - 2 * self.shift) * (n_w - 2 * self.shift)  * (n_d - 2 * self.shift), n_features)

        return X_features

    def _unpack_y(self, y, cut_bordering_pixels=False):
        # return y flattened and sparse matrices unpacked
        n_img, n_h = y.shape
        n_w, n_d = y[0][0].shape

        if cut_bordering_pixels:
            # cut out the bordering voxels (if you want to use it with strided function)
            new_h = n_h - (2 * self.shift)
            new_w = n_w - (2 * self.shift)
            new_d = n_d - (2 * self.shift)
            y_new = np.zeros((n_img,new_h, new_w, new_d))

            for img_no in range(n_img):
                for h in range(new_h):
                    y_new[img_no, h, :, :] = y[img_no, h].todense()[self.shift:-self.shift,self.shift:-self.shift]
                    y_new = y_new.reshape(n_img * new_h * new_w * new_d)
        else:
            y_new = np.zeros((n_img,n_h, n_w, n_d))

            for img_no in range(n_img):
                for h in range(n_h):
                    y_new[img_no, h, :, :] = y[img_no, h].todense()
            y_new = y_new.reshape(n_img * n_h * n_w * n_d)
        return y_new

    def fit(self, X, y):
        # takes image ravels it and returns 1s where there are maxs
        return self

    def predict(self, X):
        # X_features = self._get_features_scipy(X)
        #X = X.ravel()[:, np.newaxis]

        #return self.clf.predict(X)
        # takes image ravels it and returns 1s where there are maxs
        #X = X.ravel() #[:, np.newaxis]
        thres = np.max(X)
        X[X < thres] = 0
        X[X == thres] = 1
        return X.astype(np.uint8)

    def predict_proba(self, X):
        # X_features = self._get_features_scipy(X)
        X = X.ravel()[:, np.newaxis]
        y_pred = self.clf.predict_proba(X)
        print(y_pred)

        return np.hstack([y_pred, 1 - y_pred])


import itertools
import pytest
import logging

from django.urls import reverse
from django.db.models import Q

from tasks.models import Task
from tasks.views import (CREATE_VIEW, LIST_VIEW, UPDATE_VIEW,
                         DELETE_VIEW, DETAIL_VIEW)
from tasks.views import (LIST_TITLE, CREATE_TITLE, DELETE_TITLE,
                         UPDATE_TITLE, DETAIL_TITLE)
from tasks.views import (MESSAGE_UPDATE_SUCCESS, MESSAGE_DELETE_SUCCESS,
                         MESSAGE_CREATE_SUCCESS, DELETE_CONSTRAINT_MESSAGE,
                         QUESTION_DELETE)

logger = logging.getLogger(__name__)

NEW_TASK = {
    "name": "forth task name",
    "description": "forth task description",
    "status": 1,
    "executor": 1,
    "labels": [1, 3]
}

@pytest.mark.django_db
def test_create_get(client, log_user1):
    response = client.get(reverse(CREATE_VIEW))
    content = response.rendered_content
    assert content.find('method="post"') > 0
    assert content.find('name="description"') > 0
    assert content.find('name="name"') > 0
    assert content.find('name="status"') > 0
    assert content.find('name="executor"') > 0
    assert content.find('name="labels"') > 0
    assert content.find(CREATE_TITLE) > 0

@pytest.mark.django_db
@pytest.mark.parametrize('configuration', [
    (reverse(CREATE_VIEW), 1, MESSAGE_CREATE_SUCCESS),
    (reverse(UPDATE_VIEW, kwargs={'pk': 1}), 0, MESSAGE_UPDATE_SUCCESS)])
def test_create_update_task_post(client, log_user1,
                                 setup_tasks, configuration):
    """ in this tests two views are tested : update and create"""
    path, incremental, msg = configuration
    initial_count = Task.objects.all().count()
    response = client.post(path, NEW_TASK)
    created_item = Task.objects.get(name=NEW_TASK['name'])
    assert response.status_code == 302
    assert response.url == reverse(LIST_VIEW)
    assert Task.objects.all().count() == initial_count + incremental
    assert created_item.description == NEW_TASK['description']
    assert created_item.executor.id == NEW_TASK['executor']
    assert created_item.author.id == log_user1.id
    assert created_item.status.id == NEW_TASK['status']
    assigned_labels = list(itertools.chain(
        *created_item.labels.values_list('id')))
    assert assigned_labels == NEW_TASK['labels']
    # force flashing of message
    response = client.get(response.url)
    content = response.rendered_content
    assert content.find(msg)

@pytest.mark.django_db
def test_update_html_tasks(client, log_user1, setup_tasks):
    item = Task.objects.all().first()
    response = client.get(reverse(UPDATE_VIEW,
                                  kwargs={'pk': item.id}))
    assert response.status_code == 200
    content = response.rendered_content
    assert content.find('method="post"') > 0
    assert content.find('name="name"') > 0
    assert content.find(UPDATE_TITLE) > 0
    assert content.find(item.name) > 0

@pytest.mark.django_db
@pytest.mark.parametrize('test_string',
                         ['name', 'author__first_name', 'author__last_name',
                          'executor__first_name', 'executor__last_name',
                          'status__name'])
def test_view_tasks(client, log_user1, setup_tasks, test_string, filter_data):
    get_request_args, q = filter_data
    response = client.get(reverse(LIST_VIEW), get_request_args)
    content = response.rendered_content
    lines = content.count('</tr')
    lines_expected = Task.objects.all().filter(Q(**q)).count()
    assert lines == lines_expected
    assert content.find(LIST_TITLE) > 0
    names = Task.objects.filter(Q(**q)).values_list(test_string).all()
    inclusions = list(map(lambda x: content.find(str(*x)) > 0, names))
    assert all(inclusions)
    assert content.find(reverse(CREATE_VIEW)) > 0

@pytest.mark.django_db
def test_delete_html_task(
        client, log_user1, setup_tasks):
    item = Task.objects.all().first()
    response = client.get(reverse(DELETE_VIEW,
                                  kwargs={'pk': item.id}))
    assert response.status_code == 200
    content = response.rendered_content
    assert content.find('method="post"') > 0
    assert content.find(QUESTION_DELETE) > 0
    assert content.find(DELETE_TITLE) > 0
    assert content.find(item.name) > 0

@pytest.mark.django_db
def test_delete_own_task(client, log_user1, setup_tasks):
    count_before = Task.objects.all().count()
    item = Task.objects.all().first()
    response = client.post(reverse(DELETE_VIEW,
                                   kwargs={'pk': item.id}))
    assert response.status_code == 302
    assert response.url == reverse(LIST_VIEW)
    with pytest.raises(Exception) as e:
        Task.objects.get(id=item.id)
    assert e.match('matching query does not exist')
    count_after = Task.objects.all().count()
    assert count_after == count_before - 1
    # force flashing of message
    response = client.get(response.url)
    content = response.rendered_content
    assert content.find(MESSAGE_DELETE_SUCCESS)

@pytest.mark.django_db
def test_delete_not_own_task(client, log_user1, setup_tasks):
    count_before = Task.objects.all().count()
    item = Task.objects.exclude(author=log_user1.id).first()
    response = client.post(reverse(DELETE_VIEW,
                                   kwargs={'pk': item.id}))
    assert response.status_code == 302
    assert response.url == reverse(LIST_VIEW)
    assert Task.objects.get(id=item.id)
    count_after = Task.objects.all().count()
    assert count_after == count_before
    # force to flash message
    response = client.get(response.url)
    content = response.rendered_content
    assert content.find(DELETE_CONSTRAINT_MESSAGE) > 0

@pytest.mark.django_db
def test_detail_view_task(client, log_user1, setup_tasks):
    item = Task.objects.get(id=1)
    response = client.get(reverse(DETAIL_VIEW,
                                  kwargs={'pk': 1}))
    assert response.status_code == 200
    content = response.rendered_content
    assert content.find(DETAIL_TITLE) > 0
    assert content.find(item.name) > 0
    assert content.find(item.description) > 0
    assert content.find(item.author.get_full_name()) > 0
    assert content.find(item.executor.get_full_name()) > 0
    assert content.find(item.status.name) > 0
    labels = list(itertools.chain(
        *item.labels.values_list('name')))
    inclusions = list(map(lambda x: content.find(str(x)) > 0, labels))
    assert all(inclusions)
    assert content.find(reverse(DELETE_VIEW, kwargs={'pk': 1})) > 0
    assert content.find(reverse(UPDATE_VIEW, kwargs={'pk': 1})) > 0


# Question 1.1 - What about 42 = n?
print('Question 1.1')
# 42=n # Invalid left-hand side in assignment

# Question 1.2 - How about x = y = 1?
print('Question 1.2 - x, y')
x = y = 1
print(x)
print(y)

# Question 1.3 - Semi-colon affection?
print('Question 1.3 - semi-colon'); # no affection

# Question 1.4 - Put period at the end of a statement?
# print('Question 1.4 - period in the end'). # Invalid Syntax

# Question 1.5 - Math notation xy = x * y , how about in python?
print('Question 1.5 - xy = x * y')
# print(xy) # x=1, y=1, xy = not defined

# Question 2.1 - Volume of a sphere is 4/3πr^3,
#                get volume of a sphere with radius 5.
print('Question 2.1 - volume of sphere with radius 5')
radius = 5
pi = 3.141592
volumeOfSphere = (4/3) * pi * (radius ** 3)
print(volumeOfSphere)

# Question 2.2 - Cover price of a book is $24.95, but you got a 40% discount.
#                Shipping costs $3 for first copy and 75cents for each additional.
#                What is the total wholesale cost for 60 copies ?

coverPrice = 24.95
discount = 40/100
firstShippingCost = 3
nextShippingCost = 0.75
copies = 60

discountedPrice = coverPrice * discount
totalPrice = (discountedPrice + firstShippingCost) + (( discountedPrice + nextShippingCost) * (copies-1))
print(totalPrice)
# Question 2.3 - Leaving time : 6:52 am, 1 mile easy pace ( 8:15 per mile ),
#                then 3 miles at tempo ( 7:12 per mile ) and 1 mile easy pace again.
#                What time is the end of running?

leavingTimeToSec = ((6 * 60) + 52) * 60
easypaceSec = (8 * 60) + 15
tempoSec = (7 * 60) + 12
endTime = leavingTimeToSec + (easypaceSec * 2) + (tempoSec * 3)
hour = endTime // (60 * 60)
minute = (endTime % (60 * 60)) // 60
second = (endTime % (60 * 60)) % 60
print(hour,minute,second)


from .tracer_base import Tracer
import numpy as np

def CIBTracer(cosmo, *, z_min=0., z_max=6., n_samples=1024):
    r"""Specific ``Tracer`` associated with the cosmic infrared
    background (CIB). The radial kernel for this tracer is:

    .. math::

       W(\chi) = \frac{1}{1+z}.

    Any angular power spectra computed with this tracer, should use a 3-D
    power spectrum involving the CIB emissivity density expressed in
    :math:`\rm Jy \, Mpc^{-1} \, sr^{-1}` (or multiples thereof).

    Arguments
    ---------
    cosmo : :class:`~pyccl.core.Cosmology`
        Cosmological parameters.
    z_min, z_max : float, optional
        Minimum and maximum redshift up to which the tracer is defined.
        The default range is [0, 6].
    n_samples : float, optional
        Number of sampling points for the radial kernel.
        The samples are linearly spaced in distance.
        The default is 1024.
    """
    tracer = Tracer()

    chi_max = cosmo.comoving_radial_distance(1./(1+z_max))
    chi_min = cosmo.comoving_radial_distance(1./(1+z_min))
    chi_arr = np.linspace(chi_min, chi_max, n_samples)
    a_arr = cosmo.scale_factor_of_chi(chi_arr)

    tracer.add_tracer(kernel=(chi_arr, a_arr))
    return tracer


import pandas as pd
import pytest

import awkward_pandas

def test_len():
    s = pd.Series(awkward_pandas.AwkwardExtensionArray([[6, 2, 3], [4, 5]]))
    assert s.ak.count() == 5
    s2 = s.ak.count(axis=1)
    assert s2.tolist() == [3, 2]

def test_no_access():
    s = pd.Series([1, 2])
    with pytest.raises(Exception):
        s.ak.count()

def test_getitem():
    s = pd.Series(awkward_pandas.AwkwardExtensionArray([[6, 2, 3], [4, 5]]))
    s2 = s.ak[:, :1]
    assert isinstance(s2, pd.Series)
    assert s2.dtype == "awkward"
    assert s2.tolist() == [[6], [4]]

def test_to_column():
    s = pd.Series(awkward_pandas.AwkwardExtensionArray([6, 2, 3]), name="test")
    s2 = s.ak.to_column()
    assert s2.dtype == "int64"

    s = pd.Series(awkward_pandas.AwkwardExtensionArray(["6", "2", "3"]), name="test")
    s2 = s.ak.to_column()
    assert s2.dtype == "string[pyarrow]"

    s = pd.Series(awkward_pandas.AwkwardExtensionArray([["6", "2", "3"]]), name="test")
    with pytest.raises(ValueError):
        s.ak.to_column()

def test_to_columns():
    s = pd.Series(
        awkward_pandas.AwkwardExtensionArray(
            {"num": [6, 2, 3], "deep": [[0], [], None], "text": ["hi", "ho", "hum"]}
        ),
        name="test",
    )
    df = s.ak.to_columns()
    assert df.columns.tolist() == ["num", "text", "test"]
    assert df.num.tolist() == [6, 2, 3]
    assert df.test.tolist() == [{"deep": [0]}, {"deep": []}, {"deep": None}]
    assert df.text.tolist() == ["hi", "ho", "hum"]

    df = s.ak.to_columns(cull=False)
    assert df.columns.tolist() == ["num", "text", "test"]
    assert df.num.tolist() == [6, 2, 3]
    assert df.test[0].tolist() == {"num": 6, "deep": [0], "text": "hi"}
    assert df.text.tolist() == ["hi", "ho", "hum"]

def test_dir():
    s = pd.Series(awkward_pandas.AwkwardExtensionArray([6, 2, 3]), name="test")
    assert "sum" in dir(s.ak)
    assert "Array" not in dir(s.ak)
    assert "ak_num" not in dir(s.ak)
    assert "_util" not in dir(s.ak)


import sys
from collections import deque
T = int(sys.stdin.readline())
for _ in range(T):
    N, M = map(int, sys.stdin.readline().split())
    cnts = [0]*9
    documents = deque([])
    for i, importance in enumerate(sys.stdin.readline().split()):
        documents.append((i, int(importance)))
        cnts[documents[-1][-1]-1] += 1
    cnt = 0
    while True:
        document = documents.popleft()
        for i in range(document[-1], 9):
            if cnts[i]:
                documents.append(document)
                break
        else:
            cnt += 1
            cnts[document[-1]-1] -= 1
            if document[0] == M:
                print(cnt)
                break


import numpy as np
import matplotlib.pyplot as plt

r_values = np.linspace(0.2, 4, num=100000)
points_y = []
points_r = []
for r in r_values:
    x = 0.4
    for i in range(50):
        y = r*x*(1-x)
        if i >= 40:
            points_r.append(r)
            points_y.append(y)
        x = y
fig, ax = plt.subplots(figsize=(17, 10))
ax.scatter(points_r, points_y, alpha=0.07, edgecolors='none', s=21,
           marker='.', color='#4da4eb', label=r'$x_{n+1} = r x_n(1-x_n)$')
ax.grid(True)
ax.set_facecolor("#f5f5f5")
ax.set_xlabel("r", fontsize="xx-large")
ax.set_ylabel("x", fontsize="xx-large")
ax.set_title("Bifurcation Diagram of Logistic Map", fontsize="xx-large")
ax.legend(loc='upper left', fontsize='xx-large')


import json

import pandas as pd
from fastapi import UploadFile, HTTPException, status

def get_list_current_names(list_names: list[str]) -> list[str]:
    result_list = []
    for i in list_names:
        names = i
        if i.find(';') != -1:
            names = names.replace(',', '')
            names = names.replace(';', ',')
        result_list.append(names)
    return result_list

async def service_view_data_from_file(file: UploadFile):
    df = pd.read_excel(file.file.read())
    cols_name = (
        'title',
        'source',
        'authors',
        'publication date',
        'link',
        'document type',
    )
    df_cols = list(df.columns)
    for i in cols_name:
        if i not in df_cols:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"Столбик {i} не найден")
    get_authors = df['authors'].to_list()
    df.drop(columns=['authors'], axis=1)
    df['authors'] = get_list_current_names(get_authors)
    df.rename(columns={'publication date': 'publication_date', 'document type': 'document_type'}, inplace=True)
    json_result = json.loads(df.to_json(orient="table", index=False))
    return json.dumps(json_result, indent=4)


"""
벽의 경계가 중요하다.
왼쪽벽이 어디까지인가 ,오른쪽벽은 어디까지인가.
그리고 채워진 벽은 쓸모가 없을 것이다.

왼쪽벽과 오른쪽 벽이 어디서 생성되는지 유의깊게 볼 것.

stack에 왼쪽 벽 및 최저 층 index 싹다 저장. 마치 괄호 문제처럼 생각하자. 왼쪽벽이 있고, 오른쪽 벽이 있어야 채워질 수 있다.

그리고, 오른쪽 벽이 생길 때 , 그 즉시 채울 수 있는 공간을 채우자.
"""

class Solution(object):
    def trap(self, height):
        #첫번째 유효한 왼쪽 index(벽) 찾기
        for i in range(len(height)):
            if height[i] > height[i+1]:
                first_left_index = i
                break

        stack = []
        ans = 0
        lowHeight = -1
        for index in range(first_left_index, 9):
            if height[index] > height[index+1] : #감소 - 왼쪽벽 (내림차 순으로 저장될 것이 자명)
                lowHeight = -1 #아직 최저층이 정해지지 않았음
                if not stack:
                    stack.append(index)

            elif height[index] < height[index+1] and lowHeight == -1: #증가 - 오른쪽 벽 최초로 만들어지는 중, 최초로 증가하는 구간이 생길때=> 각 trap 공간의 최저층 생김
                if not stack : #왼쪽벽이 없이 오른쪽벽이존재할 수 없음
                    continue
                lowHeight = height[index]
                left_index = stack.pop()
                if height[left_index] > height[index+1] : #지금 참조하고 있는 오른쪽 벽의 높이와 stack에 맨 마지막(제일 높이가 낮은) 높이 비교후 물 채우기
                    ans += (index - left_index) * (height[index+1] - lowHeight)
                    lowHeight = height[index+1]
                    stack.append(left_index) #stack에 다시 push
                    continue #다음 index로 넘어가야함. 오른쪽벽이 채워졌으므로 ->벽기능 상실
                else : #(4,3,5)
                    while height[left_index] < height[index+1]:  #다음 왼쪽벽 높이 꺼내기 ->기존 왼쪽벽은 기능 상실

                        ans += (index - left_index) * (height[left_index] - lowHeight)
                        lowHeight = height[left_index]
                        if not stack:
                            break
                        left_index = stack.pop()
                    stack.append(index+1) #왼쪽벽이 다 사용되었으면 오른쪽 벽을 왼쪽벽으로 바꿈.
                if not stack : #stack이 비어있으면, 해당 오른쪽 벽 인덱스는 곧 왼쪽 벽이 됨 (기존 왼쪽 벽의 기능상실)
                    stack.append(index+1) #오른쪽벽이 왼쪽벽이 되는 순간

            elif height[index] < height[index+1] :
                left_index = stack.pop()
                if height[left_index] >= height[index+1]:
                    ans += (index - left_index) * (height[index+1] - lowHeight)
                    lowHeight = height[index+1]
                    continue #다음 index로 넘어가야함. 오른쪽벽이 채워졌으므로 ->벽기능 상실
                else :
                    while stack and  height[left_index] < height[index+1]:
                        ans += (index - left_index) * (height[left_index] - lowHeight)
                        lowHeight = height[left_index] - lowHeight
                        left_index = stack.pop()

            else : #같으면 그냥 넘어가자. index만(너비) 중요할 뿐 (같은 층끼리만 물을 채울 것이므로)
                continue
        return ans