import os
import re
import datetime
import pandas as pd
import numpy as np
from win32com.client import DispatchEx
from siger import ImportSIGER
import traceback

class VerificaSIGER(ImportSIGER):
    """
    Classe que herda funcionalidades de ImportSIGER e adiciona métodos para verificação e comparação de bases do SIGER.

    Principais Métodos:
    --------
    compare_bases_siger(self, path_siger, dic_dfs_1, dic_dfs_2):
        Realiza a comparação entre duas bases do SIGER e gera arquivos Excel com as diferenças encontradas.

    verifica_carregamento(self, path_decks="", df_robras_original=""):
        Analisa o carregamento automático dos 7 arquivos do SIGER e gera um relatório com as verificações realizadas.
    """

    ###================================================================================================================
    ###
    ### CÓDIGOS DE INICIALIZAÇÃO
    ###
    ###================================================================================================================
    def __init__(self, url_siger, usuario, senha):
        """
        Inicializa um objeto da classe com as informações necessárias para acesso ao SIGER.

        Parameters:
        -----------
        url_siger : str
            URL de acesso ao sistema SIGER.
        usuario : str
            Nome de usuário para autenticação no SIGER.
        senha : str
            Senha de usuário para autenticação no SIGER.
        """
        # Pegando todas as funções da Import_SIGER para uso nesse módulo
        super().__init__(url_siger, usuario, senha)

    ###================================================================================================================
    ###
    ### VERIFICAÇÕES PRÉ CARREGAMENTO - DECKS INDIVIDUAIS
    ###
    ###================================================================================================================
    # 01. Verifica erro nas datas
    def __create_dic_erro_data(self, file, df_robras):
        """
        Cria um dicionário de erros relacionados aos dados do arquivo.

        Parameters:
        -----------
        file : str
            Caminho do arquivo contendo os dados a serem verificados.
        df_robras : pandas.DataFrame
            DataFrame contendo os dados das obras do SIGER.

        Returns:
        --------
        list
            Lista de mensagens de erro encontradas durante a validação dos dados.
        """
        errors_file = []
        #
        with open(file, 'r', errors='ignore') as file:
            str_data = file.read()
            list_data = str_data.splitlines()

        # Montando lista com datas no arquivo
        dados_siger_obra = []
        dados_data_siger_obra = []
        dados_estado = ["Pré-Operacional","Carga Inicial","Típico","Como Construído","Acesso","Projeto Básico","Agente Fora da RB"]
        for i in range(len(list_data)):
            if list_data[i][:13] == "(#SIGER_OBRA:":
                dados_siger_obra.append(list_data[i][13:])
            elif list_data[i][:13] == "(#SIGER_DATA:":
                dados_data_siger_obra.append(list_data[i][13:].strip())
                if list_data[i+1][:12] != "(#SIGER_EST:":
                    errors_file.append(f"Atenção! Não foi informado o estado da obra {dados_siger_obra[-1]}!")
            elif list_data[i][:12] == "(#SIGER_EST:":
                siger_estado = list_data[i][13:].strip().replace('"','').replace("'","")
                if siger_estado not in dados_estado:
                    errors_file.append(f"Atenção! O estado da obra {dados_siger_obra[-1]} não está preenchido corretamente ({siger_estado})!")

        # Comparando datas com base siger
        if len(dados_siger_obra) == len(dados_data_siger_obra):
            for i in range(len(dados_siger_obra)):
                codigo_obra_arquiv = dados_siger_obra[i].replace('"','')
                #
                # Verificando data em formato errado
                if dados_data_siger_obra[i][:1] == '"':
                    errors_file.append(f"Atenção! A data da obra {codigo_obra_arquiv} está no formato errado! Favor retirar as aspas em: {dados_data_siger_obra[i]}!")

                # Pegando índice de ocorrência
                index_df = df_robras.index[df_robras['Código de Obra']==codigo_obra_arquiv].tolist()

                if len(index_df) > 0:
                    # Data no servidor:
                    data_servidor = df_robras["Data"].iloc[index_df].iloc[0]
                    # Data no arquivo
                    data_arquivo = dados_data_siger_obra[i]
                    #
                    # Comparando datas
                    if data_servidor != data_arquivo:
                        errors_file.append(f"Atenção! A data da obra {codigo_obra_arquiv} foi alterada nesse arquivo de {data_servidor} para {data_arquivo}! Favor concentrar mudanças de datas apenas no arquivo 7!")

        # Comparando cabeçalhos
        return errors_file

    def analyze_datas_from_folder(self, path_decks):
        # Análise deck a deck
        dic_errors = {}
        df_robras = self.get_robras()
        for index, deck in enumerate(path_decks):
            list_error = self.__create_dic_erro_data(deck, df_robras)
            filename = deck[deck.rfind("/")+1:]

            if len(list_error) > 0:
                dic_errors[filename] = list_error

        return dic_errors

    # 02. Verifica erro nos comentários
    def __create_dic_erro_comment(self, file):
        """
        Analisa os dados em cada deck do caminho fornecido e retorna um dicionário de erros.

        Parameters:
        -----------
        path_decks : list
            Lista de caminhos para os decks a serem analisados.

        Returns:
        --------
        dict
            Dicionário onde as chaves são os nomes dos decks e os valores são listas de mensagens de erro encontradas durante a análise.
        """

        errors_file = []
        #
        with open(file, 'r', errors="ignore") as file:
            str_data = file.read()
            list_data = str_data.splitlines()

        # Montando lista com comentários no arquivo
        dados_com = []
        siger_obra = ""
        for i in range(len(list_data)):
            if list_data[i][:13] == "(#SIGER_OBRA:":
                if siger_obra == "":
                    siger_obra = list_data[i][13:].strip()
                else:
                    # verifica comentário até então
                    if siger_data_adj > pd.to_datetime("01/10/2023", format="%d/%m/%Y"):
                        if dados_com == "":
                            errors_file.append(f"Atenção! O comentário da obra {siger_obra} não está em branco! Data da obra: {siger_data}")
                        elif len(dados_com) < 11:
                             # Está faltando coisa nos comentários
                            if not siger_obra.upper().replace('"',"").startswith("BASE_"):
                                errors_file.append(f"Atenção! O comentário da obra {siger_obra}, da data {siger_data}, não foi identificado ou está incompleto! Favor verificar possível esquecimento de algum campo!")
                            else:
                                if not any(texto.startswith("(= REGIÃO") for texto in dados_com):
                                    errors_file.append(f"Atenção! O comentário da obra {siger_obra} não está preenchido corretamente! Não está preenchida/informada a REGIÃO!")
                                if not any(texto.startswith("(= TIPO OBRA") for texto in dados_com):
                                    errors_file.append(f"Atenção! O comentário da obra {siger_obra} não está preenchido corretamente! Não está preenchida/informada o TIPO OBRA!")

                            # errors_file.append(f"Atenção! O comentário da obra {siger_obra} não está preenchido corretamente!")
                    # if len(dados_com) < 11:
                    #     # Está faltando coisa nos comentários
                    #     if not siger_obra.upper().replace('"',"").startswith("BASE_"):
                    #         errors_file.append(f"Atenção! O comentário da obra {siger_obra} não foi identificado! Favor verificar possível esquecimento!")
                    # else:
                    #     if not any(texto.startswith("(= REGIÃO") for texto in dados_com):
                    #         errors_file.append(f"Atenção! O comentário da obra {siger_obra} não está preenchido corretamente! Não está preenchida/informada a REGIÃO!")
                    #     if not any(texto.startswith("(= TIPO OBRA") for texto in dados_com):
                    #         errors_file.append(f"Atenção! O comentário da obra {siger_obra} não está preenchido corretamente! Não está preenchida/informada o TIPO OBRA!")

                    siger_obra = list_data[i][13:].strip()
                    dados_com = []

            elif list_data[i][:12] == "(#SIGER_COM:":
                dados_com.append(list_data[i][12:])

                if list_data[i].startswith("(#SIGER_COM:(= REGIÃO        :"):
                    regiao_obra = list_data[i].replace("(#SIGER_COM:(= REGIÃO        :","").replace("=)","").strip()
                    if regiao_obra not in ["S+MS", "SECO", "NNE"]:
                        errors_file.append(f"Atenção! A região cadastrada '{regiao_obra}' da obra {siger_obra} é inválida! Favor usar S+MS/SECO/NNE!")

                if ";" in list_data[i]:
                    errors_file.append(f"ERRO! Caracter inválido ';' localizado no campo Comentário da Obra {siger_obra}! Verificar linha:\n {list_data[i]}")
            elif list_data[i][:13] == "(#SIGER_DATA:":
                siger_data = list_data[i][13:].strip()
                siger_data_adj = pd.to_datetime(siger_data, format="%d/%m/%Y")

        # Check final do deck
        # if siger_data_adj > pd.to_datetime("01/10/2023", format="%d/%m/%Y"):
        #     if dados_com == "":
        #         errors_file.append(f"Atenção! O comentário da obra {siger_obra} não está em branco! Data da obra: {siger_data}")
        #     elif len(dados_com) < 11:
        #         errors_file.append(f"Atenção! O comentário da obra {siger_obra} não está preenchido corretamente!")

        # Comparando cabeçalhos
        return errors_file

    def analyze_comment_from_folder(self, path_decks):
        """
        Analisa comentários de uma pasta contendo vários decks.

        Parâmetros:
        -----------
        path_decks : str
            Caminho para a pasta contendo os decks.

        Retorna:
        --------
        dict
            Um dicionário contendo os erros encontrados nos comentários, indexados pelo nome do arquivo do deck.
        """

        # Análise deck a deck
        dic_errors = {}
        # df_robras = self.get_robras()
        for index, deck in enumerate(path_decks):
            list_error = []
            filename = deck[deck.rfind("/")+1:]
            if filename.startswith("5_") or filename.startswith("6_"):
                list_error = self.__create_dic_erro_comment(deck)

            if len(list_error) > 0:
                dic_errors[filename] = list_error

        return dic_errors

    ###================================================================================================================
    ###
    ### CÓDIGOS PARA COMPARAÇÃO ENTRE BASES DO ARQUIVO 5 e 6
    ###
    ###================================================================================================================
    def download_base_arquivo5(self, path_siger):
        """
        Baixa os arquivos da base de dados 5 do SIGER.

        Parâmetros:
        -----------
        path_siger : str
            Caminho para a pasta onde os arquivos serão salvos.
        """
        dic_dfs = self.get_all_siger_dfs()

        # Salvando arquivos na pasta
        # encoding = "cp1252"
        encoding = "utf-8-sig"
        dic_dfs["barra"].to_csv(path_siger + "/5_barra.csv", index=False, sep=";", encoding=encoding)
        dic_dfs["cs"].to_csv(path_siger + "/5_cs.csv", index=False, sep=";", encoding=encoding)
        dic_dfs["cer"].to_csv(path_siger + "/5_cer.csv", index=False, sep=";", encoding=encoding)
        dic_dfs["linha"].to_csv(path_siger + "/5_linha.csv", index=False, sep=";", encoding=encoding)
        dic_dfs["sbarra"].to_csv(path_siger + "/5_sbarra.csv", index=False, sep=";", encoding=encoding)
        dic_dfs["slinha"].to_csv(path_siger + "/5_slinha.csv", index=False, sep=";", encoding=encoding)
        dic_dfs["trafo"].to_csv(path_siger + "/5_trafo.csv", index=False, sep=";", encoding=encoding)
        dic_dfs["robras"].to_csv(path_siger + "/5_robras.csv", index=False, sep=";", encoding=encoding)
        dic_dfs["gerador"].to_csv(path_siger + "/5_gerador.csv", index=False, sep=";", encoding=encoding)
        # dic_dfs["maquina_sincrona"].to_csv(path_siger + "/5_maquina_sincrona.csv", index=False, sep=";", encoding=encoding)

        return

    def download_base_arquivo6(self, path_siger):
        """
        Baixa os arquivos da base de dados 6 do SIGER.

        Parâmetros:
        -----------
        path_siger : str
            Caminho para a pasta onde os arquivos serão salvos.
        """
        dic_dfs = self.get_all_siger_dfs()

        # Salvando arquivos na pasta
        # encoding = "cp1252"
        encoding = "utf-8-sig"
        dic_dfs["barra"].to_csv(path_siger + "/6_barra.csv", index=False, sep=";", encoding=encoding)
        dic_dfs["cs"].to_csv(path_siger + "/6_cs.csv", index=False, sep=";", encoding=encoding)
        dic_dfs["cer"].to_csv(path_siger + "/6_cer.csv", index=False, sep=";", encoding=encoding)
        dic_dfs["linha"].to_csv(path_siger + "/6_linha.csv", index=False, sep=";", encoding=encoding)
        dic_dfs["sbarra"].to_csv(path_siger + "/6_sbarra.csv", index=False, sep=";", encoding=encoding)
        dic_dfs["slinha"].to_csv(path_siger + "/6_slinha.csv", index=False, sep=";", encoding=encoding)
        dic_dfs["trafo"].to_csv(path_siger + "/6_trafo.csv", index=False, sep=";", encoding=encoding)
        dic_dfs["robras"].to_csv(path_siger + "/6_robras.csv", index=False, sep=";", encoding=encoding)
        dic_dfs["gerador"].to_csv(path_siger + "/6_gerador.csv", index=False, sep=";", encoding=encoding)
        # dic_dfs["maquina_sincrona"].to_csv(path_siger + "/6_maquina_sincrona.csv", index=False, sep=";", encoding=encoding)

        return

    def get_code_vba(self):
        """
        Retorna o código VBA para comparar e destacar diferenças em uma tabela.

        Retorna:
        --------
        str
            O código VBA para a macro de comparação.
        """
        vba_code = """
        Sub Macro2()
        '
        ' Macro2 Macro
        '
        '
            Dim table As ListObject
            Dim row As Long, col As Long

            Sheets("Comparacao").Select
            On error resume next
            Set table = ActiveSheet.ListObjects("Table1")
            For row = 1 To table.ListRows.Count
                For col = 1 To table.ListColumns.Count
                    If table.DataBodyRange(row, col) <> table.DataBodyRange(row + 1, col) Then
                        If Mid(table.Range(1, col), 1, 6) = "Estado" Then
                            If table.DataBodyRange(row, col - 1) = 0 And table.DataBodyRange(row + 1, col - 1) = 0 Then
                                table.DataBodyRange(row + 1, col).Interior.ColorIndex = 15
                            ElseIf table.DataBodyRange(row, col - 1) = "" And table.DataBodyRange(row + 1, col - 1) = "" Then
                                table.DataBodyRange(row + 1, col).Interior.ColorIndex = 15
                            Else
                                table.DataBodyRange(row + 1, col).Interior.ColorIndex = 6
                            End If
                        Else
                            table.DataBodyRange(row + 1, col).Interior.ColorIndex = 6
                        End If
                    End If
                Next col
                row = row + 1
            Next row
            On error goto 0
        End Sub
        """
        return vba_code

    def plot_table_excel(self, df, title):
        """
        Plota um dataframe do Pandas em uma planilha do Excel com formatação adicional e executa uma macro VBA para destacar diferenças.

        Parâmetros:
        -----------
        df : pandas.DataFrame
            O dataframe a ser plotado na planilha do Excel.
        title : str
            O título do arquivo Excel que será salvo.

        Nota:
        -----
        Este método pressupõe a existência de uma função `get_code_vba()` na classe que retorna o código VBA necessário para a macro de comparação.
        """

        # Initialize Excel writer
        writer = pd.ExcelWriter(title, engine='xlsxwriter')

        # Write dataframe to excel
        df.to_excel(writer, sheet_name='Comparacao', startrow=1, header=False, index=False)

        # Get worksheet
        worksheet = writer.sheets['Comparacao']

        # Get dataframe shape
        (max_row, max_col) = df.shape

        # Create a list of column headers, to use in add_table()
        column_settings = [{'header': column} for column in df.columns]

        # Add the Excel table structure. Pandas will add the data
        worksheet.add_table(0, 0, max_row, max_col - 1, {'columns': column_settings})

        # Make the columns wider for clarity
        worksheet.set_column(0, max_col - 1, 12)

        # Save and close the writer
        writer.close()

        # Initialize Excel Application
        xl = DispatchEx("Excel.Application")
        wb = xl.Workbooks.Open(title)

        # Create a new Module and insert the macro code
        vba_code =  self.get_code_vba()
        mod = wb.VBProject.VBComponents.Add(1)
        mod.CodeModule.AddFromString(vba_code)

        # Run the new Macro
        xl.Run("Macro2")

        # Replace .xlsx with .xlsm in the title
        title = title.replace(".xlsx", ".xlsm")
        title = title.replace("/", "\\")

        # Save the workbook and close Excel
        wb.SaveAs(title, FileFormat=52)
        xl.Quit()

    def comp_base_arquivo56(self, path_siger):
        """
        Função avançada para comparar e converter os dataframes das bases de dados 5 e 6 do SIGER em um único dataframe para cada tipo de elemento.

        Parâmetros:
        -----------
        path_siger : str
            Caminho para a pasta onde os arquivos das bases de dados 5 e 6 do SIGER estão localizados.

        Retorna:
        --------
        list
            Uma lista contendo um relatório de comparação para cada tipo de elemento.

        Nota:
        -----
        Este método pressupõe a existência de uma função `plot_table_excel(df, title)` na classe para plotar um dataframe em uma planilha do Excel.
        """
        # encoding = "cp1252"
        encoding = "utf-8-sig"
        dic_dfs = {}
        dic_dfs["barra_5"] = pd.read_csv(path_siger + "/5_barra.csv", sep=";", encoding=encoding).drop(["Conjunto","Fronteira"], axis=1)
        dic_dfs["barra_6"] = pd.read_csv(path_siger + "/6_barra.csv", sep=";", encoding=encoding).drop(["Conjunto","Fronteira"], axis=1)
        dic_dfs["cs_5"] = pd.read_csv(path_siger + "/5_cs.csv", sep=";", encoding=encoding).drop(["Conjunto","Nome","Tipo de Disparo","Área"], axis=1)
        dic_dfs["cs_6"] = pd.read_csv(path_siger + "/6_cs.csv", sep=";", encoding=encoding).drop(["Conjunto","Nome","Tipo de Disparo","Área"], axis=1)
        dic_dfs["cer_5"] = pd.read_csv(path_siger + "/5_cer.csv", sep=";", encoding=encoding).drop(["Conjunto","Área"], axis=1)
        dic_dfs["cer_6"] = pd.read_csv(path_siger + "/6_cer.csv", sep=";", encoding=encoding).drop(["Conjunto","Área"], axis=1)
        dic_dfs["linha_5"] = pd.read_csv(path_siger + "/5_linha.csv", sep=";", encoding=encoding).drop(["Conjunto","Nome","Resistência (R0 %)","Reatância (X0 %)","Comprimento (km)","Área","Suscept. (S0 Mvar)", "Terminal De","Estado Terminal De", "Terminal Para", "Estado Terminal Para"], axis=1)
        dic_dfs["linha_6"] = pd.read_csv(path_siger + "/6_linha.csv", sep=";", encoding=encoding).drop(["Conjunto","Nome","Resistência (R0 %)","Reatância (X0 %)","Comprimento (km)","Área","Suscept. (S0 Mvar)", "Terminal De","Estado Terminal De", "Terminal Para", "Estado Terminal Para"], axis=1)
        dic_dfs["slinha_5"] = pd.read_csv(path_siger + "/5_slinha.csv", sep=";", encoding=encoding).drop(["Conjunto","Nome","Tipo de Conexão","Resistência Aterramento (%)","Reatância Aterramento (%)","Área"], axis=1)
        dic_dfs["slinha_6"] = pd.read_csv(path_siger + "/6_slinha.csv", sep=";", encoding=encoding).drop(["Conjunto","Nome","Tipo de Conexão","Resistência Aterramento (%)","Reatância Aterramento (%)","Área"], axis=1)
        dic_dfs["trafo_5"] = pd.read_csv(path_siger + "/5_trafo.csv", sep=";", encoding=encoding).drop(["Conjunto","Nome","Resistência (R0 %)","Estado Resistência (R0 %)","Reatância (X0 %)","Estado Reatância (X0 %)","Área","Conexao De","Resistência De","Reatância De","Resistência Para","Reatância Para","Defasamento Conexão","Conexao Para"], axis=1)
        dic_dfs["trafo_6"] = pd.read_csv(path_siger + "/6_trafo.csv", sep=";", encoding=encoding).drop(["Conjunto","Nome","Resistência (R0 %)","Estado Resistência (R0 %)","Reatância (X0 %)","Estado Reatância (X0 %)","Área","Conexao De","Resistência De","Reatância De","Resistência Para","Reatância Para","Defasamento Conexão","Conexao Para"], axis=1)
        dic_dfs["sbarra_5"] = pd.read_csv(path_siger + "/5_sbarra.csv", sep=";", encoding=encoding).drop(["Conjunto","Tipo de Conexão","Resistência Seq. Zero (%)","Resistência Aterramento (%)","Reatância Aterramento (%)","Área"], axis=1)
        dic_dfs["sbarra_6"] = pd.read_csv(path_siger + "/6_sbarra.csv", sep=";", encoding=encoding).drop(["Conjunto","Tipo de Conexão","Resistência Seq. Zero (%)","Resistência Aterramento (%)", "Reatância Aterramento (%)","Área"], axis=1)
        dic_dfs["gerador_5"] = pd.read_csv(path_siger + "/5_gerador.csv", sep=";", encoding=encoding).drop("Conjunto", axis=1)
        dic_dfs["gerador_6"] = pd.read_csv(path_siger + "/6_gerador.csv", sep=";", encoding=encoding).drop("Conjunto", axis=1)
        # dic_dfs["ms_5"] = pd.read_csv(path_siger + "/5_maquina_sincrona.csv", sep=";", encoding=encoding).drop("Conjunto", axis=1)
        # dic_dfs["ms_6"] = pd.read_csv(path_siger + "/6_maquina_sincrona.csv", sep=";", encoding=encoding).drop("Conjunto", axis=1)
        dic_dfs["robras_5"] = pd.read_csv(path_siger + "/5_robras.csv", sep=";", encoding=encoding)
        dic_dfs["robras_6"] = pd.read_csv(path_siger + "/6_robras.csv", sep=";", encoding=encoding)
        #
        # Inicia comparação
        dic_dfs["comp_barra"] = self.__generate_df_diffs(dic_dfs["barra_5"], dic_dfs["barra_6"], ['Número', "Código de Obra de Entrada"])
        dic_dfs["comp_cs"] = self.__generate_df_diffs(dic_dfs["cs_5"], dic_dfs["cs_6"], ['Barra De','Barra Para','Número', "Código de Obra de Entrada"])
        dic_dfs["comp_cer"] = self.__generate_df_diffs(dic_dfs["cer_5"], dic_dfs["cer_6"], ['Número da Barra', "Número", "Código de Obra de Entrada"])
        dic_dfs["comp_linha"] = self.__generate_df_diffs(dic_dfs["linha_5"], dic_dfs["linha_6"], ['Barra De','Barra Para','Número', "Código de Obra de Entrada"])
        dic_dfs["comp_sbarra"] = self.__generate_df_diffs(dic_dfs["sbarra_5"], dic_dfs["sbarra_6"], ['Número da Barra', "Número", "Código de Obra de Entrada"])
        dic_dfs["comp_slinha"] = self.__generate_df_diffs(dic_dfs["slinha_5"], dic_dfs["slinha_6"], ['Barra De','Barra Para','Número do Circuito', "Número", "Extremidade",  "Código de Obra de Entrada"])
        dic_dfs["comp_trafo"] = self.__generate_df_diffs(dic_dfs["trafo_5"], dic_dfs["trafo_6"], ['Barra De','Barra Para','Número', "Código de Obra de Entrada"])
        dic_dfs["comp_gerador"] = self.__generate_df_diffs(dic_dfs["gerador_5"], dic_dfs["gerador_6"], ['Número da Barra', 'Número', "Código de Obra de Entrada"])
        # dic_dfs["comp_ms"] = self.__generate_df_diffs(dic_dfs["ms_5"], dic_dfs["ms_6"], ['Número da Barra', 'Número', "Código de Obra de Entrada"])
        dic_dfs["comp_robras"] = self.__generate_df_diffs(dic_dfs["robras_5"], dic_dfs["robras_6"], ['Código de Obra', 'Data', 'Estado', 'Comentário sobre a Obra'])
        dic_dfs["comp_gerador"] = self.__generate_df_diffs(dic_dfs["gerador_5"], dic_dfs["gerador_6"], ['Número da Barra', 'Número', "Código de Obra de Entrada"])
        #
        # dic_dfs["comp_barra"] = pd.concat([dic_dfs["barra_5"], dic_dfs["barra_6"]]).drop_duplicates(keep=False).sort_values(by=['Número', "Código de Obra de Entrada"])
        # dic_dfs["comp_cs"] = pd.concat([dic_dfs["cs_5"], dic_dfs["cs_6"]]).drop_duplicates(keep=False).sort_values(by=['Barra De','Barra Para','Número', "Código de Obra de Entrada"])
        # dic_dfs["comp_cer"] = pd.concat([dic_dfs["cer_5"], dic_dfs["cer_6"]]).drop_duplicates(keep=False).sort_values(by=['Número da Barra', "Número", "Código de Obra de Entrada"])
        # dic_dfs["comp_linha"] = pd.concat([dic_dfs["linha_5"], dic_dfs["linha_6"]]).drop_duplicates(keep=False).sort_values(by=['Barra De','Barra Para','Número', "Código de Obra de Entrada"])
        # dic_dfs["comp_slinha"] = pd.concat([dic_dfs["slinha_5"], dic_dfs["slinha_6"]]).drop_duplicates(keep=False).sort_values(by=['Barra De','Barra Para','Número do Circuito', "Número", "Extremidade",  "Código de Obra de Entrada"])
        # dic_dfs["comp_trafo"] = pd.concat([dic_dfs["trafo_5"], dic_dfs["trafo_6"]]).drop_duplicates(keep=False).sort_values(by=['Barra De','Barra Para','Número', "Código de Obra de Entrada"])
        # dic_dfs["comp_sbarra"] = pd.concat([dic_dfs["sbarra_5"], dic_dfs["sbarra_6"]]).drop_duplicates(keep=False).sort_values(by=['Número da Barra', "Número", "Código de Obra de Entrada"])
        #
        # Tratamento devido aos equivalentes
        nomes_equiv = ["EQVDIS", "EQUIV."]
        dic_dfs["comp_sbarra"] = dic_dfs["comp_sbarra"][~dic_dfs["comp_sbarra"]["Nome"].isin(nomes_equiv)]
        dic_dfs["comp_sbarra"] = dic_dfs["comp_sbarra"].drop("Nome", axis=1)
        dic_dfs["comp_sbarra"] = dic_dfs["comp_sbarra"].drop_duplicates(keep=False).reset_index(drop=True)
        #
        # dic_dfs["comp_gerador"] = dic_dfs["comp_gerador"][~dic_dfs["comp_gerador"]["Nome"].isin(nomes_equiv)]
        # dic_dfs["comp_gerador"] = dic_dfs["comp_gerador"].drop("Nome", axis=1)
        # dic_dfs["comp_gerador"] = dic_dfs["comp_gerador"].drop_duplicates(keep=False).reset_index(drop=True)
        #
        # Gera arquivo excel comparação na pasta
        files_siger = os.listdir(path_siger)
        try:
            for item in files_siger:
                if item.endswith(".xlsm") or item.endswith(".xlsx"):
                    os.remove( os.path.join(path_siger, item))
        except:
            pass

        if len(dic_dfs["comp_barra"]) > 0: self.plot_table_excel(dic_dfs["comp_barra"], path_siger + "/comp_barra.xlsx")
        if len(dic_dfs["comp_cs"]) > 0: self.plot_table_excel(dic_dfs["comp_cs"], path_siger + "/comp_cs.xlsx")
        if len(dic_dfs["comp_cer"]) > 0: self.plot_table_excel(dic_dfs["comp_cer"], path_siger + "/comp_cer.xlsx")
        if len(dic_dfs["comp_linha"]) > 0: self.plot_table_excel(dic_dfs["comp_linha"], path_siger + "/comp_linha.xlsx")
        if len(dic_dfs["comp_slinha"]) > 0: self.plot_table_excel(dic_dfs["comp_slinha"], path_siger + "/comp_slinha.xlsx")
        if len(dic_dfs["comp_trafo"]) > 0: self.plot_table_excel(dic_dfs["comp_trafo"], path_siger + "/comp_trafo.xlsx")
        if len(dic_dfs["comp_sbarra"]) > 0: self.plot_table_excel(dic_dfs["comp_sbarra"], path_siger + "/comp_sbarra.xlsx")
        if len(dic_dfs["comp_gerador"]) > 0: self.plot_table_excel(dic_dfs["comp_gerador"], path_siger + "/comp_gerador.xlsx")
        if len(dic_dfs["comp_robras"]) > 0: self.plot_table_excel(dic_dfs["comp_robras"], path_siger + "/comp_robras.xlsx")

        files_siger = os.listdir(path_siger)
        for item in files_siger:
            if item.endswith(".xlsx"):
                os.remove( os.path.join(path_siger, item))

        list_report = []
        list_report.append("Relatório de Comparação: \n")
        list_report.append("Comparação BARRA: VERIFICAR!" if len(dic_dfs["comp_barra"]) > 0 else "Comparação BARRA: OK!")
        list_report.append("Comparação CS: VERIFICAR!" if len(dic_dfs["comp_cs"]) > 0 else "Comparação CS: OK!")
        list_report.append("Comparação CER: VERIFICAR!" if len(dic_dfs["comp_cer"]) > 0 else "Comparação CER: OK!")
        list_report.append("Comparação LINHA: VERIFICAR!" if len(dic_dfs["comp_linha"]) > 0 else "Comparação LINHA: OK!")
        list_report.append("Comparação SHUNT_LINHA: VERIFICAR!" if len(dic_dfs["comp_slinha"]) > 0 else "Comparação SHUNT_LINHA: OK!")
        list_report.append("Comparação TRANSFORMADOR: VERIFICAR!" if len(dic_dfs["comp_trafo"]) > 0 else "Comparação TRANSFORMADOR: OK!")
        list_report.append("Comparação SHUNT_BARRA: VERIFICAR!" if len(dic_dfs["comp_sbarra"]) > 0 else "Comparação SHUNT_BARRA: OK!")
        # list_report.append("Comparação GERADOR: VERIFICAR!" if len(dic_dfs["comp_gerador"]) > 0 else "Comparação GERADOR: OK!")
        list_report.append("Comparação RELATÓRIO_OBRAS: VERIFICAR!" if len(dic_dfs["comp_robras"]) > 0 else "Comparação RELATÓRIO_OBRAS: OK!")

        return list_report

    ###================================================================================================================
    ###
    ### VERIFICAÇÕES PÓS CARREGAMENTO - INDIVIDUAIS
    ###
    ###================================================================================================================
    # 01. Conjuntos exclusivos
    def __aux_check_exclusives(self, dic_dfs):
        """
        Função auxiliar para verificar equipamentos exclusivos que possuem conjunto diferente de 'Anafas e Anarede' em cada dataframe.

        Parâmetros:
        -----------
        dic_dfs : dict
            Um dicionário contendo dataframes para cada tipo de equipamento.

        Retorna:
        --------
        str
            Uma mensagem indicando os equipamentos exclusivos encontrados ou informando que nenhum foi encontrado.

        Nota:
        -----
        Este método é uma função interna e não deve ser chamado diretamente fora da classe.
        """
        list_errors = []
        if len(dic_dfs["barra"][dic_dfs["barra"]["Conjunto"] != "Anafas e Anarede"]) > 0:
            list_errors.append("Barra")
        if len(dic_dfs["cs"][dic_dfs["cs"]["Conjunto"] != "Anafas e Anarede"]) > 0:
            list_errors.append("Compensador Série")
        if len(dic_dfs["cer"][dic_dfs["cer"]["Conjunto"] != "Anafas e Anarede"]) > 0:
            list_errors.append("Compensador Estático")
        if len(dic_dfs["linha"][dic_dfs["linha"]["Conjunto"] != "Anafas e Anarede"]) > 0:
            list_errors.append("Linha")
        if len(dic_dfs["slinha"][dic_dfs["slinha"]["Conjunto"] != "Anafas e Anarede"]) > 0:
            list_errors.append("Shunt Linha")
        if len(dic_dfs["trafo"][dic_dfs["trafo"]["Conjunto"] != "Anafas e Anarede"]) > 0:
            list_errors.append("Transformador")
        #
        # SHUNT DE BARRA é um caso a parte
        if len(dic_dfs["sbarra"][dic_dfs["sbarra"]["Conjunto"] != "Anafas e Anarede"]) > 0:
            flag_sb = False
            # Check 1 - Se existir Shunt só no ANAREDE é erro!
            if len(dic_dfs["sbarra"][dic_dfs["sbarra"]["Conjunto"] == "Anarede"]) > 0:
                list_errors.append("Shunt Barra - FP (atenção!)")
            if len(dic_dfs["sbarra"][dic_dfs["sbarra"]["Conjunto"] == "Anafas"]) > 0:
                list_errors.append("Shunt Barra - CC (esperado!)")

        if len(list_errors) > 0:
            #
            equips = " / ".join(list_errors)
            str_exclusivo = f"Os seguintes equipamentos possuem conjunto diferente de 'Anafas e Anarede': {equips}."
        else:
            str_exclusivo = "Não foram encontrados equipamentos que possuem conjunto diferente de 'Anafas e Anarede'"
        #
        return str_exclusivo

    def check_exclusives(self):
        """
        Função avançada para verificar equipamentos exclusivos que possuem conjunto diferente de 'Anafas e Anarede' em cada dataframe.

        Retorna:
        --------
        str
            Uma mensagem indicando os equipamentos exclusivos encontrados ou informando que nenhum foi encontrado.

        Nota:
        -----
        Este método pressupõe a existência de uma função `__aux_check_exclusives(dic_dfs)` na classe para realizar a verificação interna.
        """

        dic_dfs = self.get_all_siger_dfs()
        str_exclusivo = self.__aux_check_exclusives(dic_dfs)

        return str_exclusivo

    # 02. Estados Múltiplos
    def __aux_check_estados_multiplos(self, df, df_siger):
        """
        Função auxiliar para filtrar o dataframe do SIGER com base em condições específicas.

        Parâmetros:
        -----------
        df : pandas.DataFrame
            O dataframe a ser filtrado.
        df_siger : pandas.DataFrame
            O dataframe do SIGER a ser filtrado.

        Retorna:
        --------
        pandas.DataFrame
            Um novo dataframe do SIGER filtrado de acordo com as condições especificadas.

        Nota:
        -----
        Este método é uma função interna e não deve ser chamado diretamente fora da classe.
        """
        condicao = ["Como Construído", "Acesso", "Carga Inicial", "Típico", "Pré-Operacional", "Projeto Básico", "Agente Fora da RB"]
        df = df[~df['Estado'].isin(condicao)]
        df = df[~df["Código de Obra de Entrada"].str.startswith("BASE_")]
        list_obras = df["Código de Obra de Entrada"].to_list()
        df_filtrado = df_siger[df_siger["Código de Obra de Entrada"].isin(list_obras)]
        df_filtrado = df_filtrado.sort_values(by='Código de Obra de Entrada', ascending=False)

        return df_filtrado

    def check_estados_multiplos(self):
        """
        Função avançada para verificar e filtrar os estados múltiplos em um dataframe do SIGER.

        Retorna:
        --------
        pandas.DataFrame
            Um novo dataframe do SIGER filtrado com base nos estados múltiplos.

        Nota:
        -----
        Este método pressupõe a existência de uma função `__aux_check_estados_multiplos(df, df_siger)` na classe para realizar a verificação interna.
        """
        df_siger = self.get_base_siger()
        #
        df_agg = df_siger.groupby(['Código de Obra de Entrada', "Data de Entrada"], as_index=False).agg({'Estado': lambda x: ' '.join(set(x))})
        #
        df_estado_mult = self.__aux_check_estados_multiplos(df_agg, df_siger)

        return df_estado_mult

    # 03. Datas múltiplas
    def __aux_check_datas_multiplas(self, df):
        """
        Função auxiliar para verificar duplicatas de datas em um dataframe.

        Parâmetros:
        -----------
        df : pandas.DataFrame
            O dataframe a ser verificado.

        Retorna:
        --------
        pandas.DataFrame
            Um novo dataframe contendo apenas as duplicatas de datas.

        Nota:
        -----
        Este método é uma função interna e não deve ser chamado diretamente fora da classe.
        """
        df = df[df['Código de Obra de Entrada'].duplicated()]
        df = df[~df['Código de Obra de Entrada'].str.startswith('BASE_')]

        return df

    def check_datas_multiplas(self):
        """
        Função avançada para verificar e retornar duplicatas de datas em um dataframe do SIGER.

        Retorna:
        --------
        pandas.DataFrame
            Um novo dataframe contendo apenas as duplicatas de datas.

        Nota:
        -----
        Este método pressupõe a existência de uma função `__aux_check_datas_multiplas(df)` na classe para realizar a verificação interna.
        """
        df_siger = self.get_base_siger()
        #
        df_agg = df_siger.groupby(['Código de Obra de Entrada', "Data de Entrada"], as_index=False).agg({'Estado': lambda x: ' '.join(set(x))})
        #
        df_data_mult = self.__aux_check_datas_multiplas(df_agg)

        return df_data_mult

    # 04. Estados Defasados
    def __aux_check_estados_defasados(self, df_agg):
        """
        Função auxiliar para verificar e retornar estados defasados em um dataframe agregado.

        Parâmetros:
        -----------
        df_agg : pandas.DataFrame
            O dataframe agregado a ser verificado.

        Retorna:
        --------
        pandas.DataFrame
            Um novo dataframe contendo apenas os estados defasados.

        Nota:
        -----
        Este método é uma função interna e não deve ser chamado diretamente fora da classe.
        """
        current_datetime = datetime.datetime.now()
        day = current_datetime.day
        month = current_datetime.month
        year = current_datetime.year
        today = f"{day:02d}-{month:02d}-{year}"
        df_past = df_agg.copy()
        df_past['Data de Entrada'] = pd.to_datetime(df_past['Data de Entrada'])
        df_past = df_past[df_past['Data de Entrada'] < current_datetime]
        df_past = df_past[~df_past["Código de Obra de Entrada"].str.startswith("BASE_")]
        condicao = ["Como Construído", "Pré-Operacional", "Agente Fora da RB"]
        df_past = df_past[~df_past['Estado'].isin(condicao)]
        df_past = df_past.sort_values(by='Data de Entrada')

        return df_past

    def check_estados_defasados(self):
        """
        Função avançada para verificar e retornar estados defasados em um dataframe do SIGER.

        Retorna:
        --------
        pandas.DataFrame
            Um novo dataframe contendo apenas os estados defasados.

        Nota:
        -----
        Este método pressupõe a existência de uma função `__aux_check_estados_defasados(df_agg)` na classe para realizar a verificação interna.
        """
        df_siger = self.get_base_siger()
        #
        df_agg = df_siger.groupby(['Código de Obra de Entrada', "Data de Entrada"], as_index=False).agg({'Estado': lambda x: ' '.join(set(x))})
        #
        df_estado_def = self.__aux_check_estados_defasados(df_agg)

        return df_estado_def

    # 05. Nomes repetidos
    def __aux_check_nome_repetidos(self, df_agg):
        """
        Função auxiliar para verificar e retornar nomes de equipamentos repetidos em um dataframe agregado.

        Parâmetros:
        -----------
        df_agg : pandas.DataFrame
            O dataframe agregado a ser verificado.

        Retorna:
        --------
        pandas.DataFrame
            Um novo dataframe contendo apenas os nomes de equipamentos repetidos.

        Nota:
        -----
        Este método é uma função interna e não deve ser chamado diretamente fora da classe.
        """
        df_bar = df_agg[["Número", "Nome"]]
        df_bar_dup = df_bar[df_bar.duplicated(subset='Nome', keep=False)].sort_values(by='Número')
        df_bar_uniq = df_bar_dup.drop_duplicates(keep=False).sort_values(by='Nome')
        exceptions = ["NIGUT6-RJ000","NIGUT6-RJ013","NIGUT8-RJ000","NIGUT8-RJ013"]
        df_bar_uniq = df_bar_uniq[~df_bar_uniq['Nome'].isin(exceptions)]

        return df_bar_uniq

    def __aux_check_nome_fora_padrao(self, df_agg):
        """
        Função auxiliar para verificar e retornar nomes de equipamentos repetidos em um dataframe agregado.

        Parâmetros:
        -----------
        df_agg : pandas.DataFrame
            O dataframe agregado a ser verificado.

        Retorna:
        --------
        pandas.DataFrame
            Um novo dataframe contendo apenas os nomes de equipamentos repetidos.

        Nota:
        -----
        Este método é uma função interna e não deve ser chamado diretamente fora da classe.
        """
        df_bar = df_agg[["Número", "Nome"]]
        df_bar_nome = df_bar[df_bar['Nome'].str.len() != 12]

        return df_bar_nome

    def __aux_check_barra_70k(self, df_agg):
        """
        Função auxiliar para verificar e retornar nomes de equipamentos repetidos em um dataframe agregado.

        Parâmetros:
        -----------
        df_agg : pandas.DataFrame
            O dataframe agregado a ser verificado.

        Retorna:
        --------
        pandas.DataFrame
            Um novo dataframe contendo apenas os nomes de equipamentos repetidos.

        Nota:
        -----
        Este método é uma função interna e não deve ser chamado diretamente fora da classe.
        """
        df_bar = df_agg[["Número", "Nome"]]
        df_bar_70k = df_bar[df_bar['Número'] > 70000]

        return df_bar_70k

    def check_nome_repetidos(self):
        """
        Função avançada para verificar e retornar nomes de equipamentos repetidos em um dataframe do SIGER.

        Retorna:
        --------
        pandas.DataFrame
            Um novo dataframe contendo apenas os nomes de equipamentos repetidos.

        Nota:
        -----
        Este método pressupõe a existência de uma função `__aux_check_nome_repetidos(df_agg)` na classe para realizar a verificação interna.
        """

        df_siger = self.get_base_siger()
        #
        df_agg = df_siger.groupby(['Código de Obra de Entrada', "Data de Entrada"], as_index=False).agg({'Estado': lambda x: ' '.join(set(x))})
        #
        df_nom_rep = self.__aux_check_nome_repetidos(df_agg)

        return df_nom_rep

    # 06. Checagem nos comentários
    def __analisa_comentarios_siger(self, dic_decks):
        """
        Função auxiliar para analisar comentários do SIGER e identificar possíveis erros.

        Parâmetros:
        -----------
        dic_decks : dict
            Um dicionário contendo códigos de obra como chaves e comentários do SIGER como valores.

        Retorna:
        --------
        dict
            Um dicionário contendo listas de códigos de obra associados a diferentes tipos de erro nos comentários.

        Nota:
        -----
        Este método é uma função interna e não deve ser chamado diretamente fora da classe.
        """
        # Inicializa dicionário para capturar erros
        dic_erro_coment = {"obras_sem_com_empreendedimento": [], "obras_sem_com_tipo_obra": [], "obras_sem_com_regiao": [],
                           "obras_com_vaz_empreendedimento": [], "obras_com_vaz_tipo_obra": [], "obras_com_vaz_regiao": [],
                           "obras_com_erro_tipo": [], "obras_com_erro_regiao": []}

        # Verificação dos erros
        for index, (codigo_obra, comentario) in enumerate(dic_decks.items()):
            # Verifica se não tem comentário
            if isinstance(comentario,float):
                comentario = ""

            if ("(= EMPREENDIMENTO" not in comentario):
                dic_erro_coment["obras_sem_com_empreendedimento"].append(codigo_obra)

            if ("(= TIPO OBRA" not in comentario):
                dic_erro_coment["obras_sem_com_tipo_obra"].append(codigo_obra)

            if ("(= REGIÃO" not in comentario):
                dic_erro_coment["obras_sem_com_regiao"].append(codigo_obra)

            # Verifica empreendimento
            if ("(= EMPREENDIMENTO:                                                                                               =)" in comentario):
                dic_erro_coment["obras_com_vaz_empreendedimento"].append(codigo_obra)

            # # Verifica região
            if ("(= REGIÃO        :                                                                                               =)" in comentario):
                dic_erro_coment["obras_com_vaz_regiao"].append(codigo_obra)

            # Verifica tipo obra
            if ("(= TIPO OBRA     :                                                                                               =)" in comentario):
                dic_erro_coment["obras_com_vaz_tipo_obra"].append(codigo_obra)

            # Coletando dado inserido no campo Tipo Obra
            if comentario.find("(= TIPO OBRA") > 0:
                start_string = comentario.find("(= TIPO OBRA")
                end_string = comentario.find("\r\n", start_string+2)
                tipo_obra = comentario[start_string+12:end_string-2].replace(":","").strip()

                if tipo_obra not in ["TRANSMISSÃO", "TRANSMISSÃO (DIT)", "CONSUMIDOR", "GERAÇÃO - UHE","GERAÇÃO - UTE","GERAÇÃO - UEE","GERAÇÃO - UFV","GERAÇÃO - PCH","DISTRIBUIÇÃO COM IMPACTO SISTÊMICO", "CONSUMIDOR LIVRE - RB/DIT"]:
                    dic_erro_coment["obras_com_erro_tipo"].append(codigo_obra)

            # Coletando dado inserido no campo Região
            if comentario.find("(= REGIÃO        :") > 0:
                start_string = comentario[comentario.find("(= REGIÃO        :"):]
                end_string = start_string[:start_string.find("\n")].strip()
                regiao = (end_string.replace("(= REGIÃO        :","")).replace("=)","").strip()
                if regiao not in ["S+MS","SECO","NNE"]:
                    dic_erro_coment["obras_com_erro_regiao"].append(codigo_obra)

        return dic_erro_coment

    def __aux_check_comentarios(self, df_robras):
        """
        Função auxiliar para verificar e analisar comentários presentes nos registros de obras do SIGER.

        Parâmetros:
        -----------
        df_robras : DataFrame
            Um DataFrame contendo registros de obras do SIGER.

        Retorna:
        --------
        str
            Uma string contendo um relatório da análise dos comentários dos registros de obras do SIGER.

        Nota:
        -----
        Este método é uma função interna e não deve ser chamado diretamente fora da classe.
        """
        # Coletando dicionário com erros nos comentários
        # df_robras = oSIGER.get_robras()
        df_robras['Data'] = pd.to_datetime(df_robras['Data'], format='%d/%m/%Y')
        df_robras = df_robras[df_robras['Data'] >= '2023-10-01']
        dic_decks = df_robras.set_index('Código de Obra')['Comentário sobre a Obra'].to_dict()
        #
        dic_erro_coment = self.__analisa_comentarios_siger(dic_decks)

        # Imprimindo na tela os resultados
        list_report = []
        list_report.append("Análise sobre o campo COMENTÁRIO presente no servidor siger selecionado:\n")
        list_report.append(f"Foram analisados {len(dic_decks)} códigos de obra presentes no servidor, com data de entrada igual ou superior a 01/10/2023. \nSeguem resultados: \n")
        len_errors = sum(len(lst) for lst in dic_erro_coment.values())

        if len_errors == 0:
            list_report.append("- Análise concluída com sucesso sem erros aparentes!")
        else:
            if len(dic_erro_coment["obras_sem_com_empreendedimento"]) > 0:
                list_report.append("- Verificar falta das informações de 'EMPREENDIMENTO' do cabeçalho padrão dos seguintes códigos de obra:")
                list_report.append("\n".join(dic_erro_coment["obras_sem_com_empreendedimento"]))
                list_report.append("\n")
            if len(dic_erro_coment["obras_sem_com_regiao"]) > 0:
                list_report.append("- Verificar falta das informações de 'REGIÃO' do cabeçalho padrão dos seguintes códigos de obra:")
                list_report.append("\n".join(dic_erro_coment["obras_sem_com_regiao"]))
                list_report.append("\n")
            if len(dic_erro_coment["obras_sem_com_tipo_obra"]) > 0:
                list_report.append("- Verificar falta das informações de 'TIPO OBRA' do cabeçalho padrão dos seguintes códigos de obra:")
                list_report.append("\n".join(dic_erro_coment["obras_sem_com_tipo_obra"]))
                list_report.append("\n")
            if len(dic_erro_coment["obras_com_vaz_empreendedimento"]) > 0:
                list_report.append("- Verificar falta de preenchimento do campo COMENTÁRIO - EMPREENDIMENTO dos seguintes códigos de obra:")
                list_report.append("\n".join(dic_erro_coment["obras_com_vaz_empreendedimento"]))
                list_report.append("\n")
            if len(dic_erro_coment["obras_com_vaz_tipo_obra"]) > 0:
                list_report.append("- Verificar falta de preenchimento do campo COMENTÁRIO - TIPO OBRA dos seguintes códigos de obra:")
                list_report.append("\n".join(dic_erro_coment["obras_com_vaz_tipo_obra"]))
                list_report.append("\n")
            if len(dic_erro_coment["obras_com_vaz_regiao"]) > 0:
                list_report.append("- Verificar falta de preenchimento do campo COMENTÁRIO - REGIÃO dos seguintes códigos de obra:")
                list_report.append("\n".join(dic_erro_coment["obras_com_vaz_regiao"]))
                list_report.append("\n")
            if len(dic_erro_coment["obras_com_erro_tipo"]) > 0:
                list_report.append("- Verificar erro de preenchimento no campo TIPO OBRA dos seguintes códigos de obra. Lembrando que são apenas válidos os seguintes valores ['TRANSMISSÃO','GERAÇÃO - UHE','GERAÇÃO - UTE','GERAÇÃO - UEE','GERAÇÃO - UFV','GERAÇÃO - PCH','DISTRIBUIÇÃO COM IMPACTO SISTÊMICO']:")
                list_report.append("\n".join(dic_erro_coment["obras_com_erro_tipo"]))
                list_report.append("\n")
            if len(dic_erro_coment["obras_com_erro_regiao"]) > 0:
                list_report.append("- Verificar erro de preenchimento no campo REGIÃO dos seguintes códigos de obra. Lembrando que são apenas válidos os seguintes valores ['S+MS','SECO','NNE']:")
                list_report.append("\n".join(dic_erro_coment["obras_com_erro_regiao"]))
                list_report.append("\n")

        return "\n".join(list_report)

    def check_comentarios(self):
        """
        Função para verificar e analisar comentários presentes nos registros de obras do SIGER.

        Retorna um relatório da análise dos comentários dos registros de obras do SIGER.

        Retorna:
        --------
        str
            Uma string contendo um relatório da análise dos comentários dos registros de obras do SIGER.
        """
        # df_robras_mod, dic_dfs = self.get_all_siger_dfs()
        df_robras = self.get_robras()

        str_comentarios = self.__aux_check_comentarios(df_robras)

        return str_comentarios

    # 07. Verifica escorregamento em cascata
    def __check_change_date(self, data_antes, data_depois):
        """
        Função para verificar se houve alteração entre duas datas.

        Parâmetros:
        -----------
        data_antes : str
            Data antes da alteração no formato 'dd/mm/aaaa'.
        data_depois : str
            Data depois da alteração no formato 'dd/mm/aaaa'.

        Retorna:
        --------
        str
            Retorna 'S' se houve alteração entre as datas, caso contrário retorna 'N'.
        """
        if data_antes == data_depois:
            id = "N"
        else:
            id = "S"
        return id

    def check_escorreg_cascata(self, df_robras_antes, df_robras_depois, file_7, file_4):
        """
        Compara datas entre ANTES, DEPOIS e ARQ7.
        Retorna obras onde:
        (A) Mudou entre ANTES e DEPOIS e o DEPOIS é diferente do ARQ7  [DEPOIS ≠ ARQ7]
        (B) Mudou entre ANTES e DEPOIS mas essa mudança não está registrada no ARQ7
            (ARQ7 ausente OU ARQ7 == ANTES)                           [MUDOU sem registro no ARQ7]
        """

        def _to_date(s):
            return pd.to_datetime(s, dayfirst=True, errors="coerce").dt.normalize()

        # --- Normalização ANTES/DEPOIS ---
        df_a = df_robras_antes.rename(columns={"Data": "Data_antes"}).copy()
        df_d = df_robras_depois.rename(columns={"Data": "Data_depois"}).copy()

        for df_ in (df_a, df_d):
            df_["Código de Obra"] = df_["Código de Obra"].astype(str).str.strip()

        df_a["Data_antes"] = _to_date(df_a["Data_antes"])
        df_d["Data_depois"] = _to_date(df_d["Data_depois"])

        df_a = df_a.drop_duplicates(subset=["Código de Obra"], keep="last")
        df_d = df_d.drop_duplicates(subset=["Código de Obra"], keep="last")

        # --- Leitura pareada do ARQ7 (OBRA -> ESC) ---
        obras_datas = []
        obra_atual = None
        obra_re = re.compile(r'^\(#SIGER_OBRA:\s*"?([^"]+?)"?\)?\s*$')
        esc_re  = re.compile(r'^\(#SIGER_ESC:\s*"?([^"]+?)"?\)?\s*$')

        with open(file_7, "r", encoding="cp1252", errors="ignore") as f:
            for line in f:
                s = line.strip()
                m_obra = obra_re.match(s)
                if m_obra:
                    obra_atual = m_obra.group(1).strip()
                    continue
                m_esc = esc_re.match(s)
                if m_esc and obra_atual is not None:
                    data_str = m_esc.group(1).strip()
                    obras_datas.append((obra_atual, data_str))
                    obra_atual = None

        df_file7 = pd.DataFrame(obras_datas, columns=["Código de Obra", "Data_Arquivo_7"])
        if not df_file7.empty:
            df_file7["Código de Obra"] = df_file7["Código de Obra"].astype(str).str.strip()
            df_file7["Data_Arquivo_7"] = _to_date(df_file7["Data_Arquivo_7"])
            df_file7 = df_file7.drop_duplicates(subset=["Código de Obra"], keep="last")

        # --- Base mestra (apenas obras presentes em ANTES e DEPOIS) ---
        df_m = df_a.merge(df_d, on="Código de Obra", how="inner")
        df_m = df_m.merge(df_file7, on="Código de Obra", how="left") if not df_file7.empty \
            else df_m.assign(Data_Arquivo_7=pd.NaT)

        # Mudou entre ANTES e DEPOIS?
        mask_changed = (df_m["Data_antes"].notna() & df_m["Data_depois"].notna() &
                        (df_m["Data_antes"] != df_m["Data_depois"]))

        # (A) Mudou entre DFs e DEPOIS ≠ ARQ7 (há registro no 7, mas não bate com o DEPOIS)
        mask_A = mask_changed & df_m["Data_Arquivo_7"].notna() & (df_m["Data_depois"] != df_m["Data_Arquivo_7"])

        df_A = df_m.loc[mask_A, ["Código de Obra","Data_antes","Data_depois","Data_Arquivo_7"]].copy()
        df_A["Alterou_Data_Arqv7"] = "S"
        df_A["Tipo_Desalinhamento"] = "DEPOIS≠ARQ7"

        # (B) Mudou entre DFs mas NÃO foi registrado no ARQ7
        #     (ARQ7 ausente OU ARQ7 == ANTES)
        mask_B = mask_changed & (df_m["Data_Arquivo_7"].isna() | (df_m["Data_Arquivo_7"] == df_m["Data_antes"]))

        df_B = df_m.loc[mask_B, ["Código de Obra","Data_antes","Data_depois","Data_Arquivo_7"]].copy()
        df_B["Alterou_Data_Arqv7"] = "S"
        df_B["Tipo_Desalinhamento"] = "MUDOU_sem_registro_no_ARQ7"

        # Resultado unificado (mesma “saída” base com uma coluna a mais para classificar)
        df_out = pd.concat([df_A, df_B], ignore_index=True).sort_values(["Código de Obra", "Tipo_Desalinhamento"])

        if file_4:
            remove_set = self._load_remove_obras(file_4)  # set de códigos
            if remove_set:
                # df_out já tem 'Código de Obra' normalizado; apenas strip no set para consistência
                remove_set = {str(x).strip() for x in remove_set}
                df_out = df_out[~df_out["Código de Obra"].isin(remove_set)].copy()

        return df_out

    def _load_remove_obras(self, path_file_4: str) -> set[str]:
        """
        Lê o file_4 e extrai todos os códigos após '(#SIGER_REMOVE_OBRA:'.
        Ignora linhas '99999' e parênteses avulsos.
        """
        pattern = re.compile(r'^\(#SIGER_REMOVE_OBRA:\s*"?([^"\)]*?)"?\s*\)?\s*$')
        cods = set()
        with open(path_file_4, "r", encoding="utf-8", errors="ignore") as f:
            for line in f:
                s = line.strip()
                m = pattern.match(s)
                if m:
                    cod = m.group(1).strip()
                    if cod:
                        cods.add(cod)
        return cods


    # 08. Verifica apagamento em cascata
    def check_apagamento_cascata(self, df_robras_antes, df_robras_depois, file_1, file_4):
        """
        Função para verificar se houve apagamento em cascata de obras entre dois conjuntos de dados e em relação a dois arquivos.

        Parâmetros:
        -----------
        df_robras_antes : pandas.DataFrame
            DataFrame contendo as informações das obras antes do apagamento.
        df_robras_depois : pandas.DataFrame
            DataFrame contendo as informações das obras depois do apagamento.
        file_1 : str
            Caminho do arquivo 1.
        file_4 : str
            Caminho do arquivo 4.

        Retorna:
        --------
        list
            Lista contendo os códigos de obra que foram apagados em cascata.
        """
        lista_obras_antes = list(df_robras_antes["Código de Obra"].values)
        lista_obras_depois = list(df_robras_depois["Código de Obra"].values)
        lista_obras_removidas = list(set(lista_obras_antes) - set(lista_obras_depois))
        #
        # Buscar nos arquivos 1 e 4 as obras removidas
        lista_obras_files = []
        with open(file_1, 'r') as file:
            str_data = file.read()
            list_data = str_data.splitlines()
        for i in range(len(list_data)):
            if list_data[i][:13] == "(#SIGER_OBRA:":
                siger_obra = list_data[i][13:].replace('"',"").strip()
                lista_obras_files.append(siger_obra)
        with open(file_4, 'r') as file:
            str_data = file.read()
            list_data = str_data.splitlines()
        for i in range(len(list_data)):
            if list_data[i][:20] == "(#SIGER_REMOVE_OBRA:":
                siger_obra = list_data[i][20:].replace('"',"").strip()
                if siger_obra != "REMOVE-1":
                    lista_obras_files.append(siger_obra)

        # Compara Arquivos
        lista_obras_removidas_cascata = [x for x in lista_obras_removidas if x not in lista_obras_files]

        return lista_obras_removidas_cascata

    # 09. Verifica obras não presentes no banco, mas que estavam no 5 e 6
    def check_obras_nao_presentes(self, df_robras_depois, file_5, file_6):
        """
        Função para verificar se há obras presentes nos arquivos 5 e 6 que não estão presentes no DataFrame de obras depois do processamento.

        Parâmetros:
        -----------
        df_robras_depois : pandas.DataFrame
            DataFrame contendo as informações das obras depois do processamento.
        file_5 : str
            Caminho do arquivo 5.
        file_6 : str
            Caminho do arquivo 6.

        Retorna:
        --------
        list
            Lista contendo os códigos de obra que estão presentes nos arquivos 5 e 6, mas não estão no DataFrame de obras depois do processamento.
        """
        lista_obras_depois = list(df_robras_depois["Código de Obra"].values)
        #
        # Buscar nos arquivos 1 e 4 as obras removidas
        lista_obras_files = []
        with open(file_5, 'r') as file:
            str_data = file.read()
            list_data = str_data.splitlines()
        for i in range(len(list_data)):
            if list_data[i][:13] == "(#SIGER_OBRA:":
                siger_obra = list_data[i][13:].replace('"',"").strip()
                lista_obras_files.append(siger_obra)

        with open(file_6, 'r') as file:
            str_data = file.read()
            list_data = str_data.splitlines()
        for i in range(len(list_data)):
            if list_data[i][:13] == "(#SIGER_OBRA:":
                siger_obra = list_data[i][13:].replace('"',"").strip()
                lista_obras_files.append(siger_obra)

        # Juntar listas
        lista_obras_files = list(set(lista_obras_files))
        list_missing_obras = [element for element in lista_obras_files if element.upper() not in lista_obras_depois]

        return list_missing_obras

    ###================================================================================================================
    ###
    ### VERIFICAÇÃO GERAL PÓS CARREGAMENTO
    ###
    ###================================================================================================================
    # def verifica_carregamento(self, path_decks="", df_robras_original = ""): #carrega_7_arquivos_gui_pt2
    #     """
    #     Analisa o carregamento dos 7 arquivos.

    #     Parâmetros:
    #     -----------
    #     path_decks : str, opcional
    #         Caminho da pasta contendo os 7 arquivos. (default é "")
    #     df_robras_original : pandas.DataFrame, opcional
    #         DataFrame contendo as informações das obras antes do processamento. (default é "")

    #     Retorna:
    #     --------
    #     str
    #         Relatório contendo as análises realizadas sobre o carregamento dos 7 arquivos.
    #     """
    #     # Finalizada a execução do carregamento, verificar o resultado obtido
    #     dic_dfs = self.get_all_siger_dfs()
    #     df_robras_mod = dic_dfs["robras"]
    #     df_siger = self._make_siger_base(dic_dfs)
    #     df_agg = df_siger.groupby(['Código de Obra de Entrada', "Data de Entrada"], as_index=False).agg({'Estado': lambda x: ' '.join(set(x))})
    #     #
    #     # PARTE 1 - CHECAR CONJUNTO EXCLUSIVO
    #     str_exclusivo = self.__aux_check_exclusives(dic_dfs)
    #     #
    #     # PARTE 2 - CHECAR ESTADOS MÚLTIPLOS
    #     df_estado_mult = self.__aux_check_estados_multiplos(df_agg, df_siger)
    #     #
    #     # PARTE 3 - CHECAR DATAS MÚLTIPLAS
    #     df_data_mult = self.__aux_check_datas_multiplas(df_agg)
    #     #
    #     # PARTE 4 - CHECAR ESTADOS DEFASADOS
    #     df_estado_def = self.__aux_check_estados_defasados(df_agg)

    #     # PARTE 5 - CHECAR NOMES DE BARRAS REPETIDAS
    #     df_barra_nome_repet = self.__aux_check_nome_repetidos(dic_dfs["barra"])

    #     # PARTE 6 - CHECAR NOMES DE BARRAS FORA PADRÃO
    #     df_barra_nome_fora_padrao = self.__aux_check_nome_fora_padrao(dic_dfs["barra"])

    #     # PARTE 7 - CHECAR BARRAS ACIMA DE 70.000
    #     df_barra_acima_70k = self.__aux_check_barra_70k(dic_dfs["barra"])

    #     # PARTE 8 - CHECAGEM COMENTÁRIOS
    #     str_comentarios = self.__aux_check_comentarios(df_robras_mod)

    #     # Crie um arquivo de texto para escrever o relatório
    #     report = []
    #     data_hora = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    #     report.append("\n" + "#"*88 + "\n")
    #     report.append(f"# RELATÓRIO DE CARREGAMENTO SIGER - {data_hora}\n\n")

    #     # Escreva a variável string no arquivo
    #     report.append("#"*88 + "\n")
    #     report.append("# CHECAGEM 01 - CONJUNTOS EXCLUSIVOS\n")
    #     report.append(f"{str_exclusivo}\n\n")

    #     # Escreva os DataFrames no arquivo
    #     report.append("#"*88 + "\n")
    #     report.append("# CHECAGEM 02 - ESTADOS MÚLTIPLOS\n")
    #     df_estado_mult_mod = df_estado_mult.copy()
    #     df_estado_mult_mod['Data de Entrada'] = pd.to_datetime(df_estado_mult_mod['Data de Entrada']).dt.strftime('%d/%m/%Y')
    #     df_estado_mult_mod['Data de Saída'] = pd.to_datetime(df_estado_mult_mod['Data de Saída']).dt.strftime('%d/%m/%Y')
    #     if len(df_estado_mult_mod) > 0:
    #         report.append(df_estado_mult_mod.to_string(index=False) + '\n\n')
    #     else:
    #         report.append(" NÃO FORAM ENCONTRADAS OBRAS COM ESTADOS MÚLTIPLOS!\n")

    #     report.append("#"*88 + "\n")
    #     report.append("# CHECAGEM 03 - DATAS MÚLTIPLAS\n")
    #     df_data_mult_mod = df_data_mult.copy()
    #     df_data_mult_mod['Data de Entrada'] = pd.to_datetime(df_data_mult_mod['Data de Entrada']).dt.strftime('%d/%m/%Y')
    #     if len(df_data_mult_mod) > 0:
    #         report.append(df_data_mult_mod.to_string(index=False) + '\n\n')
    #     else:
    #         report.append(" NÃO FORAM ENCONTRADAS OBRAS COM DATAS MÚLTIPLAS!\n")

    #     report.append("#"*88 + "\n")
    #     report.append("# CHECAGEM 04 - ESTADOS DEFASADOS\n")
    #     df_estado_def_mod = df_estado_def.copy()
    #     df_estado_def_mod['Data de Entrada'] = pd.to_datetime(df_estado_def_mod['Data de Entrada']).dt.strftime('%d/%m/%Y')
    #     if len(df_estado_def_mod) > 0:
    #         report.append(df_estado_def_mod.to_string(index=False) + '\n\n')
    #     else:
    #         report.append(" NÃO FORAM ENCONTRADAS OBRAS COM ESTADOS DEFASADOS!\n")

    #     report.append("#"*88 + "\n")
    #     report.append("# CHECAGEM 05 - BARRAS COM NOMES REPETIDOS\n")
    #     df_barra_nome_repet_mod = df_barra_nome_repet.copy()
    #     if len(df_barra_nome_repet_mod) > 0:
    #         report.append(df_barra_nome_repet_mod.to_string(index=False) + '\n\n')
    #     else:
    #         report.append(" NÃO FORAM ENCONTRADAS BARRAS COM NOMES REPETIDOS!\n")

    #     report.append("#"*88 + "\n")
    #     report.append("# CHECAGEM 06 - BARRAS COM NOMES FORA DO PADRÃO (12 CARACTERES)\n")
    #     df_barra_nome_fora_padrao_mod = df_barra_nome_fora_padrao.copy()
    #     if len(df_barra_nome_fora_padrao_mod) > 0:
    #         report.append(df_barra_nome_fora_padrao_mod.to_string(index=False) + '\n\n')
    #     else:
    #         report.append(" NÃO FORAM ENCONTRADAS BARRAS COM NOMES FORA DO PADRÃO (12 CARACTERES)!\n")

    #     report.append("#"*88 + "\n")
    #     report.append("# CHECAGEM 07 - BARRAS COM NUMERAÇÃO ACIMA DE 70.000\n")
    #     df_barra_acima_70k_mod = df_barra_acima_70k.copy()
    #     if len(df_barra_acima_70k_mod) > 0:
    #         report.append(df_barra_acima_70k_mod.to_string(index=False) + '\n\n')
    #     else:
    #         report.append(" NÃO FORAM ENCONTRADAS BARRAS COM NUMERAÇÃO ACIMA DE 70.000!\n")

    #     report.append("#"*88 + "\n")
    #     report.append("# CHECAGEM 07 - PREENCHIMENTO COMENTÁRIOS\n")
    #     report.append(f"{str_comentarios}\n\n")

    #     # Coleta lista de decks presentes na pasta
    #     if path_decks != "":
    #         decks_siger = []
    #         for filename in os.listdir(path_decks):
    #             if filename.endswith(('.pwf', '.alt')) or filename.startswith(('1_', '2_', '3_', '4_', '5_', '6_', '7_')):
    #                 decks_siger.append(os.path.join(path_decks, filename))

    #         # Verifica se estamos com os 7 arquivos para conseguir prosseguir:
    #         if len(decks_siger) == 7:
    #             # PARTE 5 - VERIFICAR ESCORREGAMENTOS EM CASCATA
    #             df_obras_escorregadas_cascata = self.check_escorreg_cascata(df_robras_original, df_robras_mod, decks_siger[6], decks_siger[3])
    #             #
    #             # PARTE 6 - VERIFICAR EXCLUSÕES EM CASCATA
    #             lista_obras_removidas_cascata = self.check_apagamento_cascata(df_robras_original, df_robras_mod, decks_siger[0], decks_siger[3])

    #             # PARTE 7 - VERIFICAR FALSAS INCLUSÕES NA BASE
    #             lista_obras_falso_positivo = self.check_obras_nao_presentes(df_robras_mod, decks_siger[4], decks_siger[5])

    #             report.append("#"*88 + "\n")
    #             report.append("# CHECAGEM 07 - OBRAS ESCORREGADAS EM CASCATA\n")
    #             df_obras_escorregadas_cascata_mod = df_obras_escorregadas_cascata.copy()
    #             df_obras_escorregadas_cascata_mod['Data_antes'] = pd.to_datetime(df_obras_escorregadas_cascata_mod['Data_antes']).dt.strftime('%d/%m/%Y')
    #             df_obras_escorregadas_cascata_mod['Data_depois'] = pd.to_datetime(df_obras_escorregadas_cascata_mod['Data_depois']).dt.strftime('%d/%m/%Y')
    #             df_obras_escorregadas_cascata_mod['Data_Arquivo_7'] = pd.to_datetime(df_obras_escorregadas_cascata_mod['Data_Arquivo_7']).dt.strftime('%d/%m/%Y')
    #             if len(df_obras_escorregadas_cascata_mod) > 0:
    #                 report.append(df_obras_escorregadas_cascata_mod.to_string(index=False) + '\n\n')
    #             else:
    #                 report.append(" NÃO FORAM ENCONTRADAS OBRAS ESCORREGADAS EM CASCATA!\n")

    #             report.append("#"*88 + "\n")
    #             report.append("# CHECAGEM 08 - OBRAS APAGADAS EM CASCATA\n")
    #             if len(lista_obras_removidas_cascata) > 0:
    #                 report.append("\n".join(lista_obras_removidas_cascata) + '\n\n')
    #             else:
    #                 report.append(" NÃO FORAM ENCONTRADAS OBRAS APAGADAS EM CASCATA!\n")

    #             report.append("#"*88 + "\n")
    #             report.append("# CHECAGEM 09 - OBRAS PRESENTES NOS ARQUIVOS 5/6 MAS NÃO INCLUÍDAS NO BANCO\n")
    #             if len(lista_obras_falso_positivo) > 0:
    #                 report.append("\n".join(lista_obras_falso_positivo) + '\n\n')
    #             else:
    #                 report.append(" NÃO FORAM ENCONTRADAS OBRAS PRESENTES NOS ARQUIVOS 5/6 MAS NÃO INCLUÍDAS NO BANCO!\n")
    #         else:
    #             print("Não foi possível localizar os 7 arquivos na pasta informada! Favor conferir se os decks estão na pasta ou se há mais decks que os 7 a serem analisados!")

    #     str_report = "\n".join(report)
    #     return str_report


    def verifica_carregamento(self, path_decks="", df_robras_original=""):  # carrega_7_arquivos_gui_pt2
        """
        Analisa o carregamento dos 7 arquivos com tratamento robusto de erros.
        Se houver falhas em alguma etapa, o relatório aponta onde ocorreu e o processamento continua.
        """

        # ---------- Helper para capturar erros sem interromper ----------
        report = []
        errors = []

        def safe(nome_etapa, func, default):
            """
            Executa func() dentro de try/except.
            Em caso de erro, registra no relatório e retorna 'default'.
            """
            try:
                return func()
            except Exception as e:
                msg = f"[ERRO] {nome_etapa}: {type(e).__name__}: {e}"
                # registra imediatamente no relatório e também numa lista de erros resumida
                report.append("\n" + "#"*88 + "\n")
                report.append(f"# FALHA NA ETAPA: {nome_etapa}\n")
                report.append(msg + "\n")
                report.append("Traceback (resumo):\n")
                report.append(traceback.format_exc(limit=2) + "\n")
                errors.append(msg)
                return default

        # ---------- Cabeçalho ----------
        data_hora = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        report.append("\n" + "#"*88 + "\n")
        report.append(f"# RELATÓRIO DE CARREGAMENTO SIGER - {data_hora}\n\n")

        # ---------- Cargas iniciais ----------
        dic_dfs = safe(
            "get_all_siger_dfs()",
            lambda: self.get_all_siger_dfs(),
            default={"robras": pd.DataFrame(), "barra": pd.DataFrame()}
        )

        df_robras_mod = dic_dfs.get("robras", pd.DataFrame())
        df_barra = dic_dfs.get("barra", pd.DataFrame())

        df_siger = safe(
            "_make_siger_base(dic_dfs)",
            lambda: self._make_siger_base(dic_dfs),
            default=pd.DataFrame(columns=['Código de Obra de Entrada', 'Data de Entrada', 'Estado'])
        )

        df_agg = safe(
            "groupby(['Código de Obra de Entrada','Data de Entrada']).agg(Estado)",
            lambda: df_siger.groupby(['Código de Obra de Entrada', "Data de Entrada"], as_index=False)
                            .agg({'Estado': lambda x: ' '.join(set(x))}),
            default=pd.DataFrame(columns=['Código de Obra de Entrada', 'Data de Entrada', 'Estado'])
        )

        # ---------- PARTE 1 - EXCLUSIVOS ----------
        str_exclusivo = safe(
            "__aux_check_exclusives(dic_dfs)",
            lambda: self.__aux_check_exclusives(dic_dfs),
            default="NÃO EXECUTADO: erro na checagem de conjuntos exclusivos."
        )
        report.append("#"*88 + "\n")
        report.append("# CHECAGEM 01 - CONJUNTOS EXCLUSIVOS\n")
        report.append(f"{str_exclusivo}\n\n")

        # ---------- PARTE 2 - ESTADOS MÚLTIPLOS ----------
        df_estado_mult = safe(
            "__aux_check_estados_multiplos(df_agg, df_siger)",
            lambda: self.__aux_check_estados_multiplos(df_agg, df_siger),
            default=pd.DataFrame()
        )
        report.append("#"*88 + "\n")
        report.append("# CHECAGEM 02 - ESTADOS MÚLTIPLOS\n")
        try:
            df_estado_mult_mod = df_estado_mult.copy()
            if not df_estado_mult_mod.empty:
                if 'Data de Entrada' in df_estado_mult_mod.columns:
                    df_estado_mult_mod['Data de Entrada'] = pd.to_datetime(df_estado_mult_mod['Data de Entrada'], errors='coerce').dt.strftime('%d/%m/%Y')
                if 'Data de Saída' in df_estado_mult_mod.columns:
                    df_estado_mult_mod['Data de Saída'] = pd.to_datetime(df_estado_mult_mod['Data de Saída'], errors='coerce').dt.strftime('%d/%m/%Y')
                report.append(df_estado_mult_mod.to_string(index=False) + '\n\n')
            else:
                report.append(" NÃO FORAM ENCONTRADAS OBRAS COM ESTADOS MÚLTIPLOS!\n\n")
        except Exception as e:
            report.append(f"[ERRO ao formatar ESTADOS MÚLTIPLOS] {e}\n")

        # ---------- PARTE 3 - DATAS MÚLTIPLAS ----------
        df_data_mult = safe(
            "__aux_check_datas_multiplas(df_agg)",
            lambda: self.__aux_check_datas_multiplas(df_agg),
            default=pd.DataFrame()
        )
        report.append("#"*88 + "\n")
        report.append("# CHECAGEM 03 - DATAS MÚLTIPLAS\n")
        try:
            df_data_mult_mod = df_data_mult.copy()
            if not df_data_mult_mod.empty and 'Data de Entrada' in df_data_mult_mod.columns:
                df_data_mult_mod['Data de Entrada'] = pd.to_datetime(df_data_mult_mod['Data de Entrada'], errors='coerce').dt.strftime('%d/%m/%Y')
            if not df_data_mult_mod.empty:
                report.append(df_data_mult_mod.to_string(index=False) + '\n\n')
            else:
                report.append(" NÃO FORAM ENCONTRADAS OBRAS COM DATAS MÚLTIPLAS!\n\n")
        except Exception as e:
            report.append(f"[ERRO ao formatar DATAS MÚLTIPLAS] {e}\n")

        # ---------- PARTE 4 - ESTADOS DEFASADOS ----------
        df_estado_def = safe(
            "__aux_check_estados_defasados(df_agg)",
            lambda: self.__aux_check_estados_defasados(df_agg),
            default=pd.DataFrame()
        )
        report.append("#"*88 + "\n")
        report.append("# CHECAGEM 04 - ESTADOS DEFASADOS\n")
        try:
            df_estado_def_mod = df_estado_def.copy()
            if not df_estado_def_mod.empty and 'Data de Entrada' in df_estado_def_mod.columns:
                df_estado_def_mod['Data de Entrada'] = pd.to_datetime(df_estado_def_mod['Data de Entrada'], errors='coerce').dt.strftime('%d/%m/%Y')
            if not df_estado_def_mod.empty:
                report.append(df_estado_def_mod.to_string(index=False) + '\n\n')
            else:
                report.append(" NÃO FORAM ENCONTRADAS OBRAS COM ESTADOS DEFASADOS!\n")
        except Exception as e:
            report.append(f"[ERRO ao formatar ESTADOS DEFASADOS] {e}\n")

        # ---------- PARTE 5 - NOMES DE BARRAS REPETIDOS ----------
        df_barra_nome_repet = safe(
            "__aux_check_nome_repetidos(dic_dfs['barra'])",
            lambda: self.__aux_check_nome_repetidos(df_barra),
            default=pd.DataFrame()
        )
        report.append("#"*88 + "\n")
        report.append("# CHECAGEM 05 - BARRAS COM NOMES REPETIDOS\n")
        try:
            if not df_barra_nome_repet.empty:
                report.append(df_barra_nome_repet.to_string(index=False) + '\n\n')
            else:
                report.append(" NÃO FORAM ENCONTRADAS BARRAS COM NOMES REPETIDOS!\n\n")
        except Exception as e:
            report.append(f"[ERRO ao formatar NOMES REPETIDOS] {e}\n")

        # ---------- PARTE 6 - NOMES DE BARRAS FORA DO PADRÃO ----------
        df_barra_nome_fora_padrao = safe(
            "__aux_check_nome_fora_padrao(dic_dfs['barra'])",
            lambda: self.__aux_check_nome_fora_padrao(df_barra),
            default=pd.DataFrame()
        )
        report.append("#"*88 + "\n")
        report.append("# CHECAGEM 06 - BARRAS COM NOMES FORA DO PADRÃO (12 CARACTERES)\n")
        try:
            if not df_barra_nome_fora_padrao.empty:
                report.append(df_barra_nome_fora_padrao.to_string(index=False) + '\n\n')
            else:
                report.append(" NÃO FORAM ENCONTRADAS BARRAS COM NOMES FORA DO PADRÃO (12 CARACTERES)!\n\n")
        except Exception as e:
            report.append(f"[ERRO ao formatar NOMES FORA DO PADRÃO] {e}\n")

        # ---------- PARTE 7 - BARRAS ACIMA DE 70.000 ----------
        df_barra_acima_70k = safe(
            "__aux_check_barra_70k(dic_dfs['barra'])",
            lambda: self.__aux_check_barra_70k(df_barra),
            default=pd.DataFrame()
        )
        report.append("#"*88 + "\n")
        report.append("# CHECAGEM 07 - BARRAS COM NUMERAÇÃO ACIMA DE 70.000\n")
        try:
            if not df_barra_acima_70k.empty:
                report.append(df_barra_acima_70k.to_string(index=False) + '\n\n')
            else:
                report.append(" NÃO FORAM ENCONTRADAS BARRAS COM NUMERAÇÃO ACIMA DE 70.000!\n\n")
        except Exception as e:
            report.append(f"[ERRO ao formatar BARRAS > 70k] {e}\n")

        # ---------- PARTE 8 - COMENTÁRIOS ----------
        str_comentarios = safe(
            "__aux_check_comentarios(df_robras_mod)",
            lambda: self.__aux_check_comentarios(df_robras_mod),
            default="NÃO EXECUTADO: erro na checagem de comentários."
        )
        report.append("#"*88 + "\n")
        report.append("# CHECAGEM 08 - PREENCHIMENTO COMENTÁRIOS\n")
        report.append(f"{str_comentarios}\n\n")

        # ---------- Bloco dos 7 decks (cascatas/falsos positivos) ----------
        if path_decks != "":
            try:
                decks_siger = []
                for filename in os.listdir(path_decks):
                    if filename.endswith(('.pwf', '.alt')) or filename.startswith(('1_', '2_', '3_', '4_', '5_', '6_', '7_')):
                        decks_siger.append(os.path.join(path_decks, filename))

                if len(decks_siger) == 7:
                    # Escorregamento em cascata
                    df_obras_escorregadas_cascata = safe(
                        "check_escorreg_cascata(df_robras_original, df_robras_mod, arq7, arq4)",
                        lambda: self.check_escorreg_cascata(df_robras_original, df_robras_mod, decks_siger[6], decks_siger[3]),
                        default=pd.DataFrame(columns=['Obra','Data_antes','Data_depois','Data_Arquivo_7'])
                    )

                    # Exclusões em cascata
                    lista_obras_removidas_cascata = safe(
                        "check_apagamento_cascata(df_robras_original, df_robras_mod, arq1, arq4)",
                        lambda: self.check_apagamento_cascata(df_robras_original, df_robras_mod, decks_siger[0], decks_siger[3]),
                        default=[]
                    )

                    # Falsas inclusões
                    lista_obras_falso_positivo = safe(
                        "check_obras_nao_presentes(df_robras_mod, arq5, arq6)",
                        lambda: self.check_obras_nao_presentes(df_robras_mod, decks_siger[4], decks_siger[5]),
                        default=[]
                    )

                    report.append("#"*88 + "\n")
                    report.append("# CHECAGEM 09 - OBRAS ESCORREGADAS EM CASCATA\n")
                    try:
                        df_tmp = df_obras_escorregadas_cascata.copy()
                        for col in ('Data_antes', 'Data_depois', 'Data_Arquivo_7'):
                            if col in df_tmp.columns:
                                df_tmp[col] = pd.to_datetime(df_tmp[col], errors='coerce').dt.strftime('%d/%m/%Y')
                        if not df_tmp.empty:
                            report.append(df_tmp.to_string(index=False) + '\n\n')
                        else:
                            report.append(" NÃO FORAM ENCONTRADAS OBRAS ESCORREGADAS EM CASCATA!\n")
                    except Exception as e:
                        report.append(f"[ERRO ao formatar OBRAS ESCORREGADAS EM CASCATA] {e}\n")

                    report.append("#"*88 + "\n")
                    report.append("# CHECAGEM 10 - OBRAS APAGADAS EM CASCATA\n")
                    try:
                        if lista_obras_removidas_cascata:
                            report.append("\n".join(lista_obras_removidas_cascata) + '\n\n')
                        else:
                            report.append(" NÃO FORAM ENCONTRADAS OBRAS APAGADAS EM CASCATA!\n")
                    except Exception as e:
                        report.append(f"[ERRO ao listar OBRAS APAGADAS EM CASCATA] {e}\n")

                    report.append("#"*88 + "\n")
                    report.append("# CHECAGEM 11 - OBRAS PRESENTES NOS ARQUIVOS 5/6 MAS NÃO INCLUÍDAS NO BANCO\n")
                    try:
                        if lista_obras_falso_positivo:
                            report.append("\n".join(lista_obras_falso_positivo) + '\n\n')
                        else:
                            report.append(" NÃO FORAM ENCONTRADAS OBRAS PRESENTES NOS ARQUIVOS 5/6 MAS NÃO INCLUÍDAS NO BANCO!\n")
                    except Exception as e:
                        report.append(f"[ERRO ao listar FALSAS INCLUSÕES] {e}\n")
                else:
                    report.append("#"*88 + "\n")
                    report.append("# ATENÇÃO: VARREDURA DE DECKS\n")
                    report.append("Não foi possível localizar exatamente 7 arquivos na pasta informada.\n")
            except Exception as e:
                report.append("#"*88 + "\n")
                report.append("# ERRO AO LISTAR/VARREDURAR DECKS\n")
                report.append(f"{type(e).__name__}: {e}\n")
                report.append(traceback.format_exc(limit=2) + "\n")

        # ---------- Rodapé com sumário de erros (se houver) ----------
        if errors:
            report.append("\n" + "#"*88 + "\n")
            report.append("# SUMÁRIO DE ERROS ENCONTRADOS (execução seguiu adiante)\n")
            for i, msg in enumerate(errors, 1):
                report.append(f"{i:02d}. {msg}\n")

        return "".join(report)
    ###================================================================================================================
    ###
    ### CÓDIGOS PARA COMPARAÇÃO ENTRE DUAS BASES (URLs)
    ###
    ###================================================================================================================
    def __generate_df_diffs(self, df1, df2, list_sort):
        """
        Função auxiliar para montar dataframe comparado.

        Parâmetros:
        -----------
        df1, df2 : Pandas DataFrame
            DataFrames a serem comparados.

        Retorna:
        --------
        df1_comp
            DataFrame com as diferenças.

        Nota:
        -----
        Este método é uma função interna e não deve ser chamado diretamente fora da classe.
        """
        df_comp = pd.concat([df1, df2]).drop_duplicates(keep=False).reset_index(drop=True).reset_index()
        df_comp = df_comp.sort_values(by=list_sort + ["index"])
        df_comp = df_comp.drop(columns=["index"])

        return df_comp

    def compare_bases_siger(self, path_siger, dic_dfs_1, dic_dfs_2):
        """
        Realiza a comparação entre duas bases do SIGER e gera arquivos Excel com as diferenças encontradas.

        Parâmetros:
        -----------
        path_siger : str
            Caminho da pasta onde serão armazenados os arquivos Excel gerados.
        dic_dfs_1 : dict
            Dicionário contendo os DataFrames da primeira base do SIGER.
        dic_dfs_2 : dict
            Dicionário contendo os DataFrames da segunda base do SIGER.

        Retorna:
        --------
        str
            Relatório contendo as diferenças encontradas entre as bases.
        """
        print("PASSO 01 - REALIZANDO COMPARAÇÕES DOS CSVs...")
        dic_dfs = {}
        dic_dfs["comp_robras"] = self.__generate_df_diffs(dic_dfs_1["robras"], dic_dfs_2["robras"], ["Código de Obra"])
        dic_dfs["comp_area"] = self.__generate_df_diffs(dic_dfs_1["area"], dic_dfs_2["area"], ["Número"])
        dic_dfs["comp_glt"] = self.__generate_df_diffs(dic_dfs_1["glt"], dic_dfs_2["glt"], ["Grupo"])
        dic_dfs["comp_gbt"] = self.__generate_df_diffs(dic_dfs_1["gbt"], dic_dfs_2["gbt"], ["Grupo"])
        #
        dic_dfs["comp_barra"] = self.__generate_df_diffs(dic_dfs_1["barra"].drop(columns=["Identificação"]), dic_dfs_2["barra"].drop(columns=["Identificação"]), ['Número', "Código de Obra de Entrada"])
        dic_dfs["comp_cs"] = self.__generate_df_diffs(dic_dfs_1["cs"].drop(columns=["Identificação"]), dic_dfs_2["cs"].drop(columns=["Identificação"]), ['Barra De','Barra Para','Número', "Código de Obra de Entrada"])
        dic_dfs["comp_cer"] = self.__generate_df_diffs(dic_dfs_1["cer"].drop(columns=["Identificação"]), dic_dfs_2["cer"].drop(columns=["Identificação"]), ['Número da Barra', "Número", "Código de Obra de Entrada"])
        dic_dfs["comp_linha"] = self.__generate_df_diffs(dic_dfs_1["linha"].drop(columns=["Identificação"]), dic_dfs_2["linha"].drop(columns=["Identificação"]), ['Barra De','Barra Para','Número', "Código de Obra de Entrada"])
        dic_dfs["comp_mutua"] = self.__generate_df_diffs(dic_dfs_1["mutua"].drop(columns=["Identificação"]), dic_dfs_2["mutua"].drop(columns=["Identificação"]), ['Barra De 1','Barra Para 1','Número de Circuito 1', "Barra De 2", "Barra Para 2", "Número de Circuito 2", "% Inicial 1", "% Final 1", "Código de Obra de Entrada"])
        dic_dfs["comp_sbarra"] = self.__generate_df_diffs(dic_dfs_1["sbarra"].drop(columns=["Identificação"]), dic_dfs_2["sbarra"].drop(columns=["Identificação"]), ['Número da Barra', "Número", "Código de Obra de Entrada"])
        dic_dfs["comp_slinha"] = self.__generate_df_diffs(dic_dfs_1["slinha"].drop(columns=["Identificação"]), dic_dfs_2["slinha"].drop(columns=["Identificação"]), ['Barra De','Barra Para','Número do Circuito', "Número", "Extremidade",  "Código de Obra de Entrada"])
        dic_dfs["comp_trafo"] = self.__generate_df_diffs(dic_dfs_1["trafo"].drop(columns=["Identificação"]), dic_dfs_2["trafo"].drop(columns=["Identificação"]), ['Barra De','Barra Para','Número', "Código de Obra de Entrada"])
        dic_dfs["comp_gerador"] = self.__generate_df_diffs(dic_dfs_1["gerador"].drop(columns=["Identificação"]), dic_dfs_2["gerador"].drop(columns=["Identificação"]), ['Número da Barra', 'Número', "Código de Obra de Entrada"])
        # dic_dfs["comp_barra"] = pd.concat([dic_dfs_1["barra"], dic_dfs_2["barra"]]).drop_duplicates(keep=False).sort_values(by=['Número', "Código de Obra de Entrada"])
        # dic_dfs["comp_cs"] = pd.concat([dic_dfs_1["cs"], dic_dfs_2["cs"]]).drop_duplicates(keep=False).sort_values(by=['Barra De','Barra Para','Número', "Código de Obra de Entrada"])
        # dic_dfs["comp_cer"] = pd.concat([dic_dfs_1["cer"], dic_dfs_2["cer"]]).drop_duplicates(keep=False).sort_values(by=['Número da Barra', "Número", "Código de Obra de Entrada"])
        # dic_dfs["comp_linha"] = pd.concat([dic_dfs_1["linha"], dic_dfs_2["linha"]]).drop_duplicates(keep=False).sort_values(by=['Barra De','Barra Para','Número', "Código de Obra de Entrada"])
        # dic_dfs["comp_mutua"] = pd.concat([dic_dfs_1["mutua"], dic_dfs_2["mutua"]]).drop_duplicates(keep=False).sort_values(by=['Barra De 1','Barra Para 1','Número de Circuito 1', "Barra De 2", "Barra Para 2", "Número de Circuito 2", "% Inicial 1", "% Final 1", "Código de Obra de Entrada"])
        # dic_dfs["comp_sbarra"] = pd.concat([dic_dfs_1["sbarra"], dic_dfs_2["sbarra"]]).drop_duplicates(keep=False).sort_values(by=['Número da Barra', "Número", "Código de Obra de Entrada"])
        # dic_dfs["comp_slinha"] = pd.concat([dic_dfs_1["slinha"], dic_dfs_2["slinha"]]).drop_duplicates(keep=False).sort_values(by=['Barra De','Barra Para','Número do Circuito', "Número", "Extremidade",  "Código de Obra de Entrada"])
        # dic_dfs["comp_trafo"] = pd.concat([dic_dfs_1["trafo"], dic_dfs_2["trafo"]]).drop_duplicates(keep=False).sort_values(by=['Barra De','Barra Para','Número', "Código de Obra de Entrada"])
        # dic_dfs["comp_gerador"] = pd.concat([dic_dfs_1["gerador"], dic_dfs_2["gerador"]]).drop_duplicates(keep=False).sort_values(by=['Número da Barra', 'Número', "Código de Obra de Entrada"])
        # dic_dfs["comp_robras"] = pd.concat([dic_dfs_1["robras"], dic_dfs_2["robras"]]).drop_duplicates(keep=False).sort_values(by=["Código de Obra"])
        # dic_dfs["comp_area"] = pd.concat([dic_dfs_1["area"], dic_dfs_2["area"]]).drop_duplicates(keep=False).sort_values(by=["Número"])
        # dic_dfs["comp_glt"] = pd.concat([dic_dfs_1["glt"], dic_dfs_2["glt"]]).drop_duplicates(keep=False).sort_values(by=["Grupo"])
        # dic_dfs["comp_gbt"] = pd.concat([dic_dfs_1["gbt"], dic_dfs_2["gbt"]]).drop_duplicates(keep=False).sort_values(by=["Grupo"])

        print("PASSO 02 - LIMPANDO ARQUIVOS EXCEL DA PASTA (.XLSX e .XLSM)")
        files_siger = os.listdir(path_siger)
        try:
            for item in files_siger:
                if item.endswith(".xlsm") or item.endswith(".xlsx"):
                    os.remove( os.path.join(path_siger, item))
        except:
            return (f"Erro ao excluir o arquivo {item}! Favor verificar se o excel não está aberto ou rodando em segundo plano")

        print("PASSO 03 - MONTANDO ARQUIVOS EXCEL FRUTOS DA COMPARAÇÃO")
        if len(dic_dfs["comp_barra"]) > 0:
            self.plot_table_excel(dic_dfs["comp_barra"], path_siger + "/comp_barra.xlsx")
        if len(dic_dfs["comp_cs"]) > 0:
            self.plot_table_excel(dic_dfs["comp_cs"], path_siger + "/comp_cs.xlsx")
        if len(dic_dfs["comp_cer"]) > 0:
            self.plot_table_excel(dic_dfs["comp_cer"], path_siger + "/comp_cer.xlsx")
        if len(dic_dfs["comp_linha"]) > 0:
            self.plot_table_excel(dic_dfs["comp_linha"], path_siger + "/comp_linha.xlsx")
        if len(dic_dfs["comp_mutua"]) > 0:
            self.plot_table_excel(dic_dfs["comp_mutua"], path_siger + "/comp_mutua.xlsx")
        if len(dic_dfs["comp_slinha"]) > 0:
            self.plot_table_excel(dic_dfs["comp_slinha"], path_siger + "/comp_slinha.xlsx")
        if len(dic_dfs["comp_trafo"]) > 0:
            self.plot_table_excel(dic_dfs["comp_trafo"], path_siger + "/comp_trafo.xlsx")
        if len(dic_dfs["comp_sbarra"]) > 0:
            self.plot_table_excel(dic_dfs["comp_sbarra"], path_siger + "/comp_sbarra.xlsx")
        if len(dic_dfs["comp_gerador"]) > 0:
            self.plot_table_excel(dic_dfs["comp_gerador"], path_siger + "/comp_gerador.xlsx")
        if len(dic_dfs["comp_robras"]) > 0:
            self.plot_table_excel(dic_dfs["comp_robras"], path_siger + "/comp_robras.xlsx")
        if len(dic_dfs["comp_area"]) > 0:
            self.plot_table_excel(dic_dfs["comp_area"], path_siger + "/comp_area.xlsx")
        if len(dic_dfs["comp_glt"]) > 0:
            self.plot_table_excel(dic_dfs["comp_glt"], path_siger + "/comp_glt.xlsx")
        if len(dic_dfs["comp_gbt"]) > 0:
            self.plot_table_excel(dic_dfs["comp_gbt"], path_siger + "/comp_gbt.xlsx")

        print("PASSO 04 - LIMPANDO ARQUIVOS .XLSX")
        files_siger = os.listdir(path_siger)
        for item in files_siger:
            if item.endswith(".xlsx"):
                os.remove( os.path.join(path_siger, item))

        print("PASSO 05 - MONTANDO RELATÓRIO DE SAÍDA")
        list_report = []
        list_report.append("Relatório de Comparação: \n")
        list_report.append("Comparação BARRA: VERIFICAR!" if len(dic_dfs["comp_barra"]) > 0 else "Comparação BARRA: OK!")
        list_report.append("Comparação CS: VERIFICAR!" if len(dic_dfs["comp_cs"]) > 0 else "Comparação CS: OK!")
        list_report.append("Comparação CER: VERIFICAR!" if len(dic_dfs["comp_cer"]) > 0 else "Comparação CER: OK!")
        list_report.append("Comparação LINHA: VERIFICAR!" if len(dic_dfs["comp_linha"]) > 0 else "Comparação LINHA: OK!")
        list_report.append("Comparação SHUNT_LINHA: VERIFICAR!" if len(dic_dfs["comp_slinha"]) > 0 else "Comparação SHUNT_LINHA: OK!")
        list_report.append("Comparação TRANSFORMADOR: VERIFICAR!" if len(dic_dfs["comp_trafo"]) > 0 else "Comparação TRANSFORMADOR: OK!")
        list_report.append("Comparação SHUNT_BARRA: VERIFICAR!" if len(dic_dfs["comp_sbarra"]) > 0 else "Comparação SHUNT_BARRA: OK!")
        list_report.append("Comparação GERADOR: VERIFICAR!" if len(dic_dfs["comp_gerador"]) > 0 else "Comparação GERADOR: OK!")
        list_report.append("Comparação ROBRAS: VERIFICAR!" if len(dic_dfs["comp_robras"]) > 0 else "Comparação ROBRAS: OK!")
        list_report.append("Comparação AREA: VERIFICAR!" if len(dic_dfs["comp_area"]) > 0 else "Comparação AREA: OK!")
        list_report.append("Comparação GLT: VERIFICAR!" if len(dic_dfs["comp_glt"]) > 0 else "Comparação GLT: OK!")
        list_report.append("Comparação GBT: VERIFICAR!" if len(dic_dfs["comp_gbt"]) > 0 else "Comparação GBT: OK!")

        return "\n".join(list_report)