import dash
from datetime import datetime
from dash import html, dcc, Input, Output
from dash.dependencies import Input, Output, State
from openpyxl import load_workbook
from datetime import date
import pandas as pd
import dash_bootstrap_components as dbc
import plotly.graph_objects as go
import dash_ag_grid as dag
from datetime import date
from dash import html
from layouthome_2 import *
from layoutlogin import *
from layoutagende import *
from tempidiattesa import *
from commonlayout import *
from consiglidimensionamento import *
from ulterioridettagli_2 import *
from generazione_analisi_scheduler_per_frontend import *
from roots import *
#import os
import io
#import base64  
import time
dtype={'DIARY_ID': str,
       'SLOTS_MASK': str,
       'TIMEBAND_ID': str
       }
dtype_2 = {'CODICE_AGENDA': str,
    'STS11': str,
    'CODICE_EROGANTE': str,
    }
start_time = time.time()
###############################################
## BASI DATI NEL CASO SI DEVONO AGGIORNARE #####
###############################################
raggruppam = pd.read_csv(os.path.join(base_path, 'File_utili', 'calendar_7_agende_selezionate_con_raggruppamento.csv'), sep =',')
cluster = pd.read_excel(os.path.join(base_path, 'File_utili', 'Clustering_aderenza_grafo_new_600.xlsx'))
cluster.rename(columns ={'Prestazione' : 'Codice Prestazione'},inplace=True)
#calendar_agende = pd.read_csv(os.path.join(base_path, 'File_utili', 'V_AGENDE_ATTIVE_CE_CALENDAR.csv'), sep=";", dtype=dtype)
calendar_agende = pd.read_parquet(os.path.join(base_path, 'File_utili', 'V_AGENDE_ATTIVE_CE_CALENDAR.parquet'))
## TABELLA PRINCIPALE 
#priorita = pd.read_csv(os.path.join(base_path, 'File_utili', 'V_AGENDE_ATTIVE_CE.csv'), sep=';')
priorita = pd.read_parquet(os.path.join(base_path, 'File_utili', 'V_AGENDE_ATTIVE_CE.parquet'))
prenot = pd.read_parquet(os.path.join(base_path, 'File_utili', 'cup_prenotato_cleaned_wave_1.parquet'))
re_cluster = pd.read_excel(os.path.join(base_path, 'File_utili', 'Re-clustering_Spec_Fa.Re_v.2.0_20240624.xlsx'), sheet_name = 'Spectral_clustering_600_limited')
decodifica_catalogo = pd.read_csv(os.path.join(base_path,'File_utili','decodifica_descrizioni_catalogo.csv'),sep=';')
sentinella =pd.read_excel(os.path.join(base_path,'File_utili','Prestazioni sentinella_v1.0.xlsx'), sheet_name='Foglio 1')
edge_list_completa_22_23 = pd.read_parquet(os.path.join(base_path, 'File_utili', 'edge_list_completa_22_23.parquet'))
####prestazioni_agende_df = pd.read_csv(os.path.join(base_path, 'Df_demo','prestazioni_agende.csv'), sep =',')
decodifica_ambiti_garanzia = pd.read_csv(os.path.join(base_path, 'File_utili', 'decodifica_ambiti_garanzia 2.csv'),sep=';')
#timeband_orders_df = pd.read_csv(os.path.join(base_path,'File_utili','V_AGENDE_ATTIVE_CE_TIMEBAND_ORDERS.csv'), sep =';' , encoding='ISO-8859-1', dtype=dtype_2) #"ISO-8859-1",        , dtype=dtype
timeband_orders_df = pd.read_parquet(os.path.join(base_path,'File_utili','V_AGENDE_ATTIVE_CE_TIMEBAND_ORDERS.parquet'))
timeband_orders_df = timeband_orders_df[~(timeband_orders_df['ORDER_ID'].str.endswith('_5', na=False))] 
df_dettaglio_struttura = pd.read_csv(root_path+r"\df_dettaglio_struttura.csv", sep =',')
## DF CALCOLATI
## DF DI TUTTE LE AGENDE PRESE IN CONSIDERAZIONE CON DESCRIZIONE 
agende_descrizione = priorita[['CODICE_AGENDA','DESCRIZIONE_AGENDA']].drop_duplicates()
agende_descrizione.rename(columns = {'CODICE_AGENDA' : 'Codice Agenda','DESCRIZIONE_AGENDA' : 'Descrizione Agenda'},inplace=True)
### decodifica descrizione recluster
decodifica_descrizione = re_cluster[['Re-clustering','Descrizione post-Reclustering']].drop_duplicates()
decodifica_descrizione['Re-clustering'] = decodifica_descrizione['Re-clustering'].astype(str)
### prestazioni_agende_df
df_agende_no_prestazioni_nuove_agende_attive = priorita[~(priorita['CODICE_PREST_SERV'].str.endswith('_5', na=False))] 
df_agende_no_prestazioni_nuove_agende_attive = df_agende_no_prestazioni_nuove_agende_attive[['CODICE_AGENDA','CODICE_PREST_SERV','DESCR_PREST']].drop_duplicates()
prestazioni_agende_df = df_agende_no_prestazioni_nuove_agende_attive.rename(columns = {'CODICE_AGENDA' : 'Codice Agenda', 'CODICE_PREST_SERV' : 'Codice Prestazione' ,'DESCR_PREST' : 'Descrizione Prestazione' })
prestazioni_agende_df['Codice Agenda'] = prestazioni_agende_df['Codice Agenda'].astype(str)
# per funzionamento calcolo ambito dal file excel caricato
priorita_nuovo = priorita.drop_duplicates(subset=['CODICE_AGENDA', 'CODICE_EROGANTE', 'STS11'])
priorita_nuovo = priorita_nuovo[['CODICE_AGENDA', 'CODICE_EROGANTE', 'STS11']]
def import_funzione_fe():
    scheduler_a01 = load_scheduler(file_path = SCHEDULER_ABSOLUTE_PATH+r'\OUTPUT\my_scheduler_ASL Caserta A01_20240627.pkl')
    scheduler_a02 = load_scheduler(file_path = SCHEDULER_ABSOLUTE_PATH+r'\OUTPUT\my_scheduler_ASL Caserta A02_20240627.pkl')
    scheduler_a03 = load_scheduler(file_path = SCHEDULER_ABSOLUTE_PATH+r'\OUTPUT\my_scheduler_ASL Caserta A03_20240627.pkl')
    scheduler_a04 = load_scheduler(file_path = SCHEDULER_ABSOLUTE_PATH+r'\OUTPUT\my_scheduler_ASL Caserta A04_20240627.pkl')
    # CAPIRE SE SERVE DF_PREVISIONI
    #df_previsioni = import_previsioni_cluster(input_folder_location=SCHEDULER_ABSOLUTE_PATH+f'\INPUT\previsioni_domanda\{ambito}\\')
    #df_previsioni = transform_previsioni(df_previsioni, start_day)
    # Import agende con info ambito
    df_agende_da_suddividere = import_csv(SCHEDULER_ABSOLUTE_PATH+r"/INPUT/agende_da_suddividere.csv")
    df_agende_unique_fe = import_agende_ambito(df_agende_da_suddividere, SCHEDULER_ABSOLUTE_PATH+r"/INPUT/")    ## seerve??
    df_cluster = load_clustering_data(SCHEDULER_ABSOLUTE_PATH+r"/INPUT/")
    #dict_duration_cluster = filter_clusters(df_agende_unique, df_cluster, ambito, lambda x: x.mode().values[0])
    calendar_fe = import_calendar(SCHEDULER_ABSOLUTE_PATH+r'\INPUT\\')                                        ## seerve??
    file_c_df = read_file_c(cartella_file_sorgente = SCHEDULER_ABSOLUTE_PATH+r"/INPUT/")                   ## seerve??
    df_reclustering = load_reclustering_data(file_path=SCHEDULER_ABSOLUTE_PATH+r"/INPUT/", file_name="Re-clustering_Spec_Fa.Re_v.2.0_20240624.xlsx", sheet_name='Spectral_clustering_600_limited')
    dict_peso_prestazioni_tutto = ottieni_conteggio_prestazioni_tutto(file_c_df)
    df_reclustering = process_reclustering_data(df=df_reclustering, weight_dict=dict_peso_prestazioni_tutto)
    df_cluster = cluster_add_pesi(df_cluster, dict_peso_prestazioni_tutto)
    df_cup_eapoc = load_cup_eapoc(df_reclustering, input_path=SCHEDULER_ABSOLUTE_PATH+r"/INPUT/")
    print('df_agende_unique_fe',df_agende_unique_fe)
    return df_cluster,df_reclustering,df_cluster,df_cup_eapoc,calendar_fe,scheduler_a01,scheduler_a02,scheduler_a03,scheduler_a04,dict_peso_prestazioni_tutto,df_agende_unique_fe
## IMPORT FILE DA GENERAZIONE ANALISI SMART SCHEDULER 
df_cluster,df_reclustering,df_cluster,df_cup_eapoc,calendar_fe,scheduler_a01,scheduler_a02,scheduler_a03,scheduler_a04,dict_peso_prestazioni_tutto,df_agende_unique_fe = import_funzione_fe()
end_time = time.time()
execution_time = end_time - start_time
print(f"Il tempo di esecuzione è: {execution_time} secondi")
## FUNZIONI PER CALCOLO DF CALCOLATI ALL'INTERNO DELLA DASHBOARD 
def create_report_download(data_inizio,data_fine,lista_agende,recap_post_riconfigurazione_download):
    #print("________________PRINT CHECK______________")
    #print(lista_agende)
    #print(recap_post_riconfigurazione_download)
    #print(data_inizio)
    #print(data_fine)
    #print(recap_post_riconfigurazione_download.columns)
    data_inizio = pd.to_datetime(data_inizio)
    data_fine = pd.to_datetime(data_fine)
    priorita_filt = priorita[['STS11','CODICE_EROGANTE','DENOMINAZIONE_STRUTTURA','CODICE_AGENDA','DESCRIZIONE_AGENDA','UNITA_EROGANTE']].drop_duplicates()
    priorita_filt['CODICE_AGENDA'] = priorita_filt['CODICE_AGENDA'].astype(str)
    priorita_filt['CODICE_EROGANTE'] = priorita_filt['CODICE_EROGANTE'].astype(str)
    calendar_agende_filt = calendar_agende[['CALENDAR_DATE','DIARY_ID']].drop_duplicates()  ##'TIMEBAND_ID',
    calendar_agende_filt.rename(columns = {'DIARY_ID' : 'CODICE_AGENDA'},inplace=True)
    calendar_agende_filt['CALENDAR_DATE'] = pd.to_datetime(calendar_agende_filt['CALENDAR_DATE'])
    calendar_agende_filt_period = calendar_agende_filt[(calendar_agende_filt['CALENDAR_DATE'] >= data_inizio) & (calendar_agende_filt['CALENDAR_DATE'] <= data_fine)]
    recap_post_riconfigurazione_download['Giorno'] = pd.to_datetime(recap_post_riconfigurazione_download['Giorno'])
    
    recap_post_riconfigurazione_filt = recap_post_riconfigurazione_download[['Giorno','Codice_agenda','Slot_assegnati_U', 'Slot_assegnati_B','Slot_assegnati_D', 'Slot_assegnati_P']]
    recap_post_riconfigurazione_filt.rename(columns = {'Codice_agenda' : 'CODICE_AGENDA'},inplace=True)
    recap_post_riconfigurazione_filt['CODICE_AGENDA'] =recap_post_riconfigurazione_filt['CODICE_AGENDA'].astype(str)
    ## merge agende_attive--->calendar
    calendar_agende_attive = calendar_agende_filt_period.merge(priorita_filt, on ='CODICE_AGENDA', how ='left')
    calendar_agende_attive['CODICE_AGENDA'] = calendar_agende_attive['CODICE_AGENDA'].astype(str)
    ## lista valori 
    calendar_agende_filt = calendar_agende[['CALENDAR_DATE','DIARY_ID','TIMEBAND_ID']].drop_duplicates()  ##'TIMEBAND_ID',
    lista_timeband = calendar_agende_filt.groupby(['CALENDAR_DATE','DIARY_ID'])['TIMEBAND_ID'].unique().reset_index(name='TIMEBAND_ID')
    lista_timeband['CALENDAR_DATE'] = pd.to_datetime(lista_timeband['CALENDAR_DATE'])
    lista_timeband.rename(columns = {'DIARY_ID' : 'CODICE_AGENDA'}, inplace=True)
    #merge calendat_agende_attive con recap 
    calendar_agende_attive_recap = calendar_agende_attive.merge(recap_post_riconfigurazione_filt,left_on =['CALENDAR_DATE','CODICE_AGENDA'], right_on = ['Giorno','CODICE_AGENDA'], how='inner')
    ## MERGE TIMEBAND
    calendar_agende_attive_recap_final = calendar_agende_attive_recap.merge(lista_timeband, on = ['CALENDAR_DATE','CODICE_AGENDA'], how ='left')
    calendar_agende_attive_recap_final
    
    final_report_dettaglio = calendar_agende_attive_recap_final[['CALENDAR_DATE','STS11','DENOMINAZIONE_STRUTTURA','CODICE_EROGANTE','UNITA_EROGANTE','CODICE_AGENDA','DESCRIZIONE_AGENDA','TIMEBAND_ID','Slot_assegnati_U','Slot_assegnati_B','Slot_assegnati_D','Slot_assegnati_P']]
    final_report_dettaglio['CALENDAR_DATE'] = final_report_dettaglio['CALENDAR_DATE'].dt.strftime('%d/%m/%Y')
    final_report_dettaglio['TIMEBAND_ID'] = final_report_dettaglio['TIMEBAND_ID'].astype(str)
    
    final_report_dettaglio_filt_agende = final_report_dettaglio[final_report_dettaglio['CODICE_AGENDA'].isin(lista_agende)]
    
    final_sintetico = final_report_dettaglio_filt_agende.groupby(['STS11','DENOMINAZIONE_STRUTTURA','CODICE_EROGANTE','UNITA_EROGANTE','CODICE_AGENDA','DESCRIZIONE_AGENDA'])[['Slot_assegnati_U'	,'Slot_assegnati_B'	,'Slot_assegnati_D',	'Slot_assegnati_P']].sum().reset_index()
    final_sintetico['TOTALE'] = final_sintetico['Slot_assegnati_U'] + final_sintetico['Slot_assegnati_B'] + final_sintetico['Slot_assegnati_D'] + final_sintetico['Slot_assegnati_P']
    final_sintetico['PERC_U'] = ((final_sintetico['Slot_assegnati_U'] / final_sintetico['TOTALE']) * 100).round(2)
    final_sintetico['PERC_B'] = ((final_sintetico['Slot_assegnati_B'] / final_sintetico['TOTALE']) * 100).round(2)
    final_sintetico['PERC_D'] = ((final_sintetico['Slot_assegnati_D'] / final_sintetico['TOTALE']) * 100).round(2)
    final_sintetico['PERC_P'] = ((final_sintetico['Slot_assegnati_P'] / final_sintetico['TOTALE']) * 100).round(2)
    final_sintetico['data_inizio'] = pd.to_datetime(data_inizio)
    final_sintetico['data_inizio'] = final_sintetico['data_inizio'].dt.strftime('%d/%m/%Y')
    final_sintetico['data_fine'] = pd.to_datetime(data_fine)
    final_sintetico['data_fine'] = final_sintetico['data_fine'].dt.strftime('%d/%m/%Y')
    final_sintetico = final_sintetico[['data_inizio','data_fine','STS11', 'DENOMINAZIONE_STRUTTURA', 'CODICE_EROGANTE', 'UNITA_EROGANTE',
        'CODICE_AGENDA', 'DESCRIZIONE_AGENDA',
        'PERC_U', 'PERC_B', 'PERC_D', 'PERC_P']]
    
    
    return final_report_dettaglio_filt_agende,final_sintetico
## CALCOLO DELL'AMBITO AD OGNI CODICE AGENDA 
decodifica_ambiti_garanzia_rename = decodifica_ambiti_garanzia.rename(columns={'sts11': 'STS11', 'ambiti_aziende': 'AMBITI_AZIENDE'})
cols=['STS11', 'CODICE_EROGANTE', 'CODICE_AGENDA', 'SERVICE_REGIONAL_CODE', 'ORDER_DURATION', 'CLASSI_PRIORITA', 'AMBITI_AZIENDE']
df_agende_no_prestazioni_nuove = priorita[~(priorita['ORDER_ID'].str.endswith('_5', na=False))] 
df_agende_no_prestazioni_nuove_merge = df_agende_no_prestazioni_nuove.merge(decodifica_ambiti_garanzia_rename[['STS11', 'AMBITI_AZIENDE']], how='left', on='STS11')
df_agende_unique = df_agende_no_prestazioni_nuove_merge[cols].drop_duplicates()
df_agende_unique['tripletta'] = df_agende_unique['CODICE_AGENDA'].astype(str) + '_' + df_agende_unique['STS11'] + '_' + df_agende_unique['CODICE_EROGANTE'].astype(str)
decodifica_agende_ambito = df_agende_unique.groupby(['CODICE_AGENDA','STS11','CODICE_EROGANTE','AMBITI_AZIENDE']).first().reset_index()[['CODICE_AGENDA','STS11','CODICE_EROGANTE','AMBITI_AZIENDE']]
merged_df = pd.merge(priorita_nuovo, decodifica_agende_ambito, 
                     on=['CODICE_AGENDA', 'CODICE_EROGANTE', 'STS11'], 
                     how='inner')
decodifica_agende_ambito.rename(columns = {'CODICE_AGENDA' : 'Codice Agenda'}, inplace=True)
## FUNZIONI NETWORK E SUGGERIMENTO 
def suggerimento_intelligente(): 
    merge_2 = prestazioni_agende_df.merge(cluster[['Codice Prestazione','Comunità']], on ='Codice Prestazione', how ='left')
    ### funzione suggerimento 
    all_differenze = pd.DataFrame(columns=['Codice Agenda', 'Prestazioni'])
    lista_unica = merge_2['Codice Agenda'].unique()
    for cod in lista_unica:
        filtered_1 = merge_2[merge_2['Codice Agenda'] == cod]
        lista_community = filtered_1['Comunità'].unique()
        #print('lista' , lista_community)
        community_filt = cluster[cluster['Comunità'].isin(lista_community)]
        set_prestazioni_community = set(community_filt['Codice Prestazione'])
        #print('set_prestazioni_community',set_prestazioni_community)
        set_prestazioni_agenda = set(filtered_1['Codice Prestazione'])
        #print('set_prestazioni_agenda',set_prestazioni_agenda)
        differenza = set_prestazioni_community -set_prestazioni_agenda
        differenza_list = list(differenza)
        differenza_df = pd.DataFrame(differenza_list, columns=['Prestazioni'])
        differenza_df['Codice Agenda'] = cod
        # Concateniamo al DataFrame finale
        all_differenze = pd.concat([all_differenze, differenza_df], ignore_index=True)
    decodifica_catalogo.rename(columns = {'COD_PRESTAZIONE_CATALOGO' : 'Prestazioni'},inplace=True) 
    df_suggerimento = all_differenze.merge(decodifica_catalogo, on ='Prestazioni', how ='left')
    df_suggerimento.rename(columns = {'DESC_PRESTAZIONE_CATALOGO': 'Descrizione prestazione'},inplace=True)
    df_suggerimento.rename(columns = {'Prestazioni': 'Codice prestazione'},inplace=True)
    return df_suggerimento
def statistiche_aderenza_al_grafo(raggruppam):
    ## Funzione per creare DF --> 3 aderenze al grafo e df_chart che sarebbe il donut-chart --> Pagina Ulteriori dettagli
    prestazioni_agende_df['Codice Agenda'] = prestazioni_agende_df['Codice Agenda'].astype('str')
    # FILTRO A GRAFO PER RENDERLO SIGNIFICATIVO
    edge_list_completa_22_23_filt = edge_list_completa_22_23[edge_list_completa_22_23['cnt'] > 10]
    # PULISCI AGENDE DAI CODICI NON IN CATALOGO E FAI UN SELF JOIN 
    filt_new_prest = prestazioni_agende_df[~prestazioni_agende_df['Codice Prestazione'].str.contains('_')]
    filt_new_prest['len'] = filt_new_prest['Codice Prestazione'].str.len()
    filt_new_prest = filt_new_prest[filt_new_prest['len'] == 9]
    filt_new_prest.drop(['len'], axis= 1, inplace=True)
    agende_self = filt_new_prest.merge(filt_new_prest, on='Codice Agenda', how='left', suffixes=('', '_opp'))
    agende_self = agende_self[agende_self['Codice Prestazione'] > agende_self['Codice Prestazione_opp']]
    agende_self = agende_self[[ 'Codice Agenda', 'Codice Prestazione',
        'Descrizione Prestazione', 'Codice Prestazione_opp',
        'Descrizione Prestazione_opp']]
    edge_list_completa_22_23_filt.rename(columns = {'CODICE_PRESTAZIONE_CATALOGO' : 'Codice Prestazione', 'CODICE_PRESTAZIONE_CATALOGO_opp' : 'Codice Prestazione_opp'}, inplace=True)
    aderenza = agende_self.merge(edge_list_completa_22_23_filt, on = ['Codice Prestazione', 'Codice Prestazione_opp'],  how='outer', indicator=True)
    aderenza['Aderenza'] = aderenza.apply(lambda x: 'Si' if x['_merge'] == 'both' else 'No', axis=1)
    somma_tot = aderenza.groupby('Codice Agenda').size().reset_index(name ='somma_tot')
    aderenza_si = aderenza[aderenza['Aderenza'] == 'Si']
    somma_tot_aderenza_si = aderenza_si.groupby('Codice Agenda').size().reset_index(name ='somma_tot_si')
    finale_somma = somma_tot.merge(somma_tot_aderenza_si, on ='Codice Agenda', how ='left')
    finale_somma['Congruenza al grafo'] = round(finale_somma['somma_tot_si'] /finale_somma['somma_tot'],2)
    finale_somma.fillna(0,inplace=True)
    finale_somma = finale_somma[['Codice Agenda','Congruenza al grafo']]
    # RIMUOVIAMO TUTTI I CODICI NON CONFORMI -> QUELLI NUOVI E CON DIMESNIONE DIVERSA DA 9 
    filt_new_prest = prestazioni_agende_df[~prestazioni_agende_df['Codice Prestazione'].str.contains('_')]
    filt_new_prest['len'] = filt_new_prest['Codice Prestazione'].str.len()
    filt_new_prest = filt_new_prest[filt_new_prest['len'] == 9]
    filt_new_prest.drop(['len'], axis= 1, inplace=True)
    agende_self = filt_new_prest.merge(filt_new_prest, on='Codice Agenda', how='left', suffixes=('', '_opp'))
    agende_self = agende_self[agende_self['Codice Prestazione'] > agende_self['Codice Prestazione_opp']]
    agende_self = agende_self[[ 'Codice Agenda', 'Codice Prestazione',
        'Descrizione Prestazione', 'Codice Prestazione_opp',
        'Descrizione Prestazione_opp']]
    cluster_filt = cluster[['Codice Prestazione', 'Comunità']]
    ## SELF JOIN CLUSTER PER CONFRONTARE LE EDGE LIST 
    cluster_self_merge = cluster_filt.merge(cluster_filt, on='Comunità', how='left', suffixes=('', '_opp'))
    cluster_self_merge_filt = cluster_self_merge[cluster_self_merge['Codice Prestazione'] > cluster_self_merge['Codice Prestazione_opp']]
    ## MERGE DI AGENDE E CLUSTER 
    merge_agende_cluster = agende_self.merge(cluster_self_merge_filt,on = ['Codice Prestazione','Codice Prestazione_opp'], how = 'outer',indicator =True)
    somma_tot = agende_self.groupby('Codice Agenda').size().reset_index(name ='cnt_tot')
    merge_agende_cluster['Aderenza'] = merge_agende_cluster.apply(lambda x: 'Si' if x['_merge'] == 'both' else 'No', axis=1)
    merge_agenda_cluster_only_si = merge_agende_cluster[merge_agende_cluster['Aderenza'] == 'Si']
    somma_tot_si = merge_agenda_cluster_only_si.groupby('Codice Agenda').size().reset_index(name ='cnt_tot_si')
    confronto_perc_cluster = somma_tot.merge(somma_tot_si , on ='Codice Agenda', how ='left')
    confronto_perc_cluster['Congruenza al cluster'] =round(confronto_perc_cluster['cnt_tot_si'] / confronto_perc_cluster['cnt_tot'],2)
    confronto_perc_cluster.fillna(0,inplace=True)
    confronto_perc_cluster['Codice Agenda'] = confronto_perc_cluster['Codice Agenda'].astype('str')
    confronto_perc_cluster = confronto_perc_cluster[['Codice Agenda','Congruenza al cluster']]
    ## PULIZIA DEI CODICI NON CONFORMI SECODNO SOLO 2 REGOLE PER ORA PRESTAZIONE NON CONTIENE '_' E NON è DIVERSA DA 9 CARATTERI DI LUGNHEZZA
    raggruppam['CODICE_REGIONALE_PREST'] = raggruppam['CODICE_REGIONALE_PREST'].astype('str')
    raggruppam = raggruppam[~raggruppam['CODICE_REGIONALE_PREST'].str.contains('_')]
    raggruppam['len'] = raggruppam['CODICE_REGIONALE_PREST'].str.len()
    raggruppam_filt = raggruppam[raggruppam['len'] == 9]
    raggruppam_filt.drop(['len'], axis= 1, inplace=True)
    raggruppam_filt['CODICE_AGENDA'] = raggruppam_filt['CODICE_AGENDA'].astype('str')
    raggruppam_filt_col = raggruppam_filt[['COD_AZIENDA_x','DESCRIZIONE_AZIENDA_x','TIPO_AZIENDA_x','STS11','CODICE_AGENDA','DESCRIZIONE_AGENDA_x','RAGGRUPPAMENTO','CODICE_REGIONALE_PREST','DESCR_PREST_y']]
    self_merge = raggruppam_filt_col.merge(raggruppam_filt_col, on =['RAGGRUPPAMENTO','CODICE_AGENDA'])
    self_merge_edge = self_merge[self_merge['CODICE_REGIONALE_PREST_x'] > self_merge['CODICE_REGIONALE_PREST_y']]
    self_merge_edge.rename(columns = {'CODICE_REGIONALE_PREST_x' :  'CODICE_PRESTAZIONE_CATALOGO' ,'CODICE_REGIONALE_PREST_y' :'CODICE_PRESTAZIONE_CATALOGO_opp'},inplace=True)
    edge_22_23 = edge_list_completa_22_23.copy()
    edge_22_23 = edge_22_23[edge_22_23['cnt'] >10]
    self_merge_edge['CODICE_PRESTAZIONE_CATALOGO'] = self_merge_edge['CODICE_PRESTAZIONE_CATALOGO'].astype('str')
    self_merge_edge['CODICE_PRESTAZIONE_CATALOGO_opp'] = self_merge_edge['CODICE_PRESTAZIONE_CATALOGO_opp'].astype('str')
    edge_22_23['CODICE_PRESTAZIONE_CATALOGO'] = edge_22_23['CODICE_PRESTAZIONE_CATALOGO'].astype('str')
    edge_22_23['CODICE_PRESTAZIONE_CATALOGO_opp'] = edge_22_23['CODICE_PRESTAZIONE_CATALOGO_opp'].astype('str')
    ragguppam_finale = self_merge_edge.merge(edge_22_23, on =['CODICE_PRESTAZIONE_CATALOGO','CODICE_PRESTAZIONE_CATALOGO_opp'], how = 'outer',indicator =True)
    somma_tot = self_merge_edge.groupby('CODICE_AGENDA').size().reset_index(name ='cnt_tot')
    ragguppam_finale['Aderenza'] = ragguppam_finale.apply(lambda x: 'Si' if x['_merge'] == 'both' else 'No', axis=1)
    merge_agenda_cluster_only_si = ragguppam_finale[ragguppam_finale['Aderenza'] == 'Si']
    somma_tot_si = merge_agenda_cluster_only_si.groupby('CODICE_AGENDA').size().reset_index(name ='cnt_tot_si')
    confronto_perc_raggru = somma_tot.merge(somma_tot_si , on ='CODICE_AGENDA', how ='left')
    confronto_perc_raggru['Congruenza ai raggruppamenti'] =round(confronto_perc_raggru['cnt_tot_si'] / confronto_perc_raggru['cnt_tot'],2)
    confronto_perc_raggru.fillna(0,inplace=True)
    confronto_perc_raggru.rename(columns ={'CODICE_AGENDA' : 'Codice Agenda'},inplace=True)
    confronto_perc_raggru = confronto_perc_raggru[['Codice Agenda','Congruenza ai raggruppamenti']]
    df_dettaglio_agenda_1 = confronto_perc_raggru.merge(confronto_perc_cluster, on ='Codice Agenda', how ='outer' )
    df_dettaglio_agenda_1 = df_dettaglio_agenda_1.merge(finale_somma, on ='Codice Agenda', how ='outer' )
    ## DF-CHART
    def calculate_weighted_value(row):
        # Define weights
        weights = {
            'Congruenza al grafo': 0.25,
            'Congruenza al cluster': 0.35,
            'Congruenza ai raggruppamenti': 0.45
        }
        
        # Get non-null columns and their corresponding weights
        non_null_columns = [col for col in weights if pd.notnull(row[col])]
        total_weight = sum(weights[col] for col in non_null_columns)
        
        # Normalize weights for non-null columns
        normalized_weights = {col: weights[col] / total_weight for col in non_null_columns}
        
        # Calculate the weighted value
        value = sum(row[col] * normalized_weights[col] for col in non_null_columns)
        return value
    df_dettaglio_agenda_aderenza = df_dettaglio_agenda_1.copy()
    df_dettaglio_agenda_aderenza['Value'] = round(df_dettaglio_agenda_1.apply(calculate_weighted_value, axis=1),2)
    agende_grado_aderenza = df_dettaglio_agenda_aderenza[['Codice Agenda','Value']]
    return agende_grado_aderenza,df_dettaglio_agenda_1
###########################################
## DF CALCOLATI DA FUNZIONI CREATE  #######
##########################################
df_chart,df_dettaglio_agenda_1 = statistiche_aderenza_al_grafo(raggruppam)
df_suggerimento = suggerimento_intelligente()
### CAPIRE SE SI PUO ELIMINARE 
dict_ambiti = [
    {'label': 'Provincia di Caserta', 'value': 'ASL Caserta A04'},
    {'label': 'Provincia di Caserta 1', 'value': 'ASL Caserta 1'},
    {'label': 'Provincia di Caserta 2', 'value': 'ASL Caserta 2'},
    {'label': 'Provincia di Caserta 3', 'value': 'ASL Caserta 3'}
]
# Creare un dizionario che mappa il value alla label corrispondente
value_to_label = {item['value']: item['label'] for item in dict_ambiti}
vdati = {
    "Stato attuale": [
        {"Priorità": "Urgente", "Immagine": "urgent.png", "Slot": "-", "Giorni": "-", "Slot_Riconfigurazione": "-", "Giorni_Riconfigurazione": "-"},
        {"Priorità": "Breve", "Immagine": "short.png", "Slot": "-", "Giorni": "-", "Slot_Riconfigurazione": "-", "Giorni_Riconfigurazione": "-"},
        {"Priorità": "Differibile", "Immagine": "deferable.png", "Slot": "-", "Giorni": "-", "Slot_Riconfigurazione": "-", "Giorni_Riconfigurazione": "-"},
        {"Priorità": "Programmata", "Immagine": "planned.png", "Slot": "-", "Giorni": "-", "Slot_Riconfigurazione": "-", "Giorni_Riconfigurazione": "-"},
    ],
    
    "Stato post riconfigurazione": [
        {"Priorità": "Urgente", "Immagine": "urgent.png", "Slot": "-", "Giorni": "-", "Slot_Riconfigurazione": "-", "Giorni_Riconfigurazione": "-"},
        {"Priorità": "Breve", "Immagine": "short.png", "Slot": "-", "Giorni": "-", "Slot_Riconfigurazione": "-", "Giorni_Riconfigurazione": "-"},
        {"Priorità": "Differibile", "Immagine": "deferable.png", "Slot": "-", "Giorni": "-", "Slot_Riconfigurazione": "-", "Giorni_Riconfigurazione": "-"},
        {"Priorità": "Programmata", "Immagine": "planned.png", "Slot": "-", "Giorni": "-", "Slot_Riconfigurazione": "-", "Giorni_Riconfigurazione": "-"},
    ]
}
dati = {
    "Stato attuale": [
        {"Priorità": "Urgente", "Immagine": "urgent.png", "Slot": "-", "Giorni": "-", "Slot_Riconfigurazione": "-", "Giorni_Riconfigurazione": "-"},
        {"Priorità": "Breve", "Immagine": "short.png", "Slot": "-", "Giorni": "-", "Slot_Riconfigurazione": "-", "Giorni_Riconfigurazione": "-"},
        {"Priorità": "Differibile", "Immagine": "deferable.png", "Slot": "-", "Giorni": "-", "Slot_Riconfigurazione": "-", "Giorni_Riconfigurazione": "-"},
        {"Priorità": "Programmata", "Immagine": "planned.png", "Slot": "-", "Giorni": "-", "Slot_Riconfigurazione": "-", "Giorni_Riconfigurazione": "-"},
    ],
    
    "Stato post riconfigurazione": [
        {"Priorità": "Urgente", "Immagine": "urgent.png", "Slot": "-", "Giorni": "-", "Slot_Riconfigurazione": "-", "Giorni_Riconfigurazione": "-"},
        {"Priorità": "Breve", "Immagine": "short.png", "Slot": "-", "Giorni": "-", "Slot_Riconfigurazione": "-", "Giorni_Riconfigurazione": "-"},
        {"Priorità": "Differibile", "Immagine": "deferable.png", "Slot": "-", "Giorni": "-", "Slot_Riconfigurazione": "-", "Giorni_Riconfigurazione": "-"},
        {"Priorità": "Programmata", "Immagine": "planned.png", "Slot": "-", "Giorni": "-", "Slot_Riconfigurazione": "-", "Giorni_Riconfigurazione": "-"},
    ]
}
#df_data = pd.read_csv(root_path+r"\prima_data_disponibile.csv",sep =',')
# Funzione per formattare i numeri con il punto come separatore delle migliaia
def format_thousands(x):
    try:
        return "{:,.0f}".format(x).replace(",", ".")
    except (ValueError, TypeError):
        return x
# Applica la formattazione a tutte le colonne numeriche
#df_slot_giorni = df_slot_giorni.applymap(lambda x: format_thousands(x) if isinstance(x, (int, float)) else x)
# APP.LAYOUT
app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
server = app.server  # the Flask instance used by Dash
app.layout = html.Div([
    dcc.Location(id='url', refresh=False),
    dcc.Location(id='url-redirect', refresh=True),  # Added url-redirect
    #dcc.Store(id='calendar-filtered'),
    dcc.Store(id='stored-filename',storage_type='session'),
    dcc.Store(id='stored-contents',storage_type='session'),
    dcc.Store(id='start-date-store',storage_type='session'),
    dcc.Store(id='end-date-store',storage_type='session'),
    dcc.Store(id='start-day-store',storage_type='session'),
    dcc.Store(id='start-month-store',storage_type='session'),
    dcc.Store(id='start-year-store',storage_type='session'),
    dcc.Store(id='end-day-store',storage_type='session'),
    dcc.Store(id='end-month-store',storage_type='session'),
    dcc.Store(id='end-year-store',storage_type='session'),
    dcc.Store(id='selected-code-store',storage_type='session'),   ## Servire a tenere traccia delle righe selezionate 
    dcc.Store(id='dropdown-value-store',storage_type='session'),  # Nuovo Store per il valore del dropdown 
    dcc.Store(id='username1-store',storage_type='session'),
    dcc.Store(id='button-1-clicks-store',storage_type='session'),
    dcc.Store(id='username2-store',storage_type='session'),
    dcc.Store(id='button-2-clicks-store',storage_type='session'),
    dcc.Store(id='selected-rows',storage_type='session'),
    dcc.Store(id = 'selected-ambiti-store',storage_type='session'),
    dcc.Store(id='store-df1',storage_type='session'),
    dcc.Store(id='store-df2',storage_type='session'),
    dcc.Store(id='store-df3',storage_type='session'),
    dcc.Store(id='store-df4',storage_type='session'),
    dcc.Store(id='store-df5',storage_type='session'),
    dcc.Store(id='store-df6',storage_type='session'),
    dcc.Store(id='data-ready', data=False),
    dcc.Store(id = 'initial-options',storage_type='session'),
    dcc.Store(id = 'name',storage_type='session'),
    html.Div(id='page-content')
])
### CALLBACK FUNZIONAMENTO DASHBOARD 
# Callback to update page content based on URL
@app.callback(
    Output('page-content', 'children'),
    [Input('url', 'pathname')],
)
def display_page(pathname):
    if pathname == '/agende':
        return layout_agende()
    elif pathname == '/tempi_attesa':
        return layout_tempi_attesa()
    elif pathname == '/ulteriori_dettagli':
        return layout_ulteriori_dettagli()
    elif pathname== '/calendario':
        return layout_home()
    elif pathname =='/consigli':
        return consigli_di_dimensionamento()
    else:
        return layout_home()
    
    
### callback per filtro tabella pagina 1
@app.callback(
    Output('selected-rows-container', 'children'),
    Input('selected-code-store', 'data')
)
def display_selected_rows(selected_codes):
    if not selected_codes:
        return "Nessuna riga selezionata."
    return html.Pre(f"Codici selezionati:\n{selected_codes}")
## Callback per aggiornare i dati dello store quando le righe vengono selezionate
@app.callback(
    Output('selected-code-store', 'data'),
    Input('table-new', 'selectedRows')
    )
def update_selected_code(selected_rows):
    if selected_rows:
        selected_codes = [row['Codice Agenda'] for row in selected_rows]
        return selected_codes
    return []
"""@app.callback(
    Output('selected-rows', 'data'),
    Input('table-new', 'selectedRows'),
    )
def update_selected_code(selected_rows):
   
    return selected_rows"""
@app.callback(
    Output('table-new', 'selectedRows'),
    [Input('selected-code-store', 'data'),
    Input('store-df6', 'data')],
    
    )
def update_selected_code(selected_codes,df_data_agende_dict):
    if selected_codes:
        data_agende = pd.DataFrame.from_dict(df_data_agende_dict)
        selected_rows = [row for row in data_agende.to_dict('records') if row['Codice Agenda'] in selected_codes]
        return selected_rows
    else:
        return []
    
####
# Aggiorna la callback per leggere e scrivere il valore selezionato dallo store
@app.callback(
    [Output('dropdown-tempi-attesa', 'options'),
     Output('dropdown-tempi-attesa', 'value')],
    [Input('url', 'pathname'), Input('selected-code-store', 'data'),Input('dropdown-value-store', 'data'),Input('store-df6', 'data')],
    
)
def update_dropdown(pathname, selected_codes, selected_value,df_data_agende_dict):
    if pathname == '/tempi_attesa' and selected_codes:
        data_agende = pd.DataFrame.from_dict(df_data_agende_dict)
        options = [{'label': data_agende.loc[data_agende['Codice Agenda'] == code, 'Descrizione Agenda'].values[0], 'value': code} for code in selected_codes]
        # Se c'è un valore selezionato memorizzato, usa quello
        if selected_value in selected_codes:
            selected_value = str(selected_value)
            return options, selected_value
        else:
            value = options[0]['value'] if options else None
            return options, value
    return [], None
@app.callback(
    [Output('table-actual-slot', 'children'),
     Output('table-actual-days', 'children'),],
    [Input('dropdown-tempi-attesa', 'value'), Input('store-df1','data')]
)
def update_tables(selected_code, four_tables_dict):
    df_slot_giorni = pd.DataFrame.from_dict(four_tables_dict) if four_tables_dict else pd.DataFrame()
    if selected_code:
        selected_code = str(selected_code)
        filtered_data = df_slot_giorni[df_slot_giorni['Codice Agenda'] == selected_code]
        
        def update_data(original_data, filtered_data, slot_col, giorni_col):
            updated_data = []
            giorni_values = []
            for row in original_data:
                if row["Priorità"] in filtered_data['Priorità'].values:
                    row_data = filtered_data[filtered_data['Priorità'] == row["Priorità"]].iloc[0]
                    updated_row = row.copy()
                    updated_row["Slot"] = row_data[slot_col] if not pd.isna(row_data[slot_col]) else "-"
                    updated_row["Giorni"] = row_data[giorni_col] if not pd.isna(row_data[giorni_col]) else "-"
                    
                    if updated_row["Giorni"] != "-" and updated_row["Giorni"] != "xx giorni":
                        giorni_values.append(int(updated_row["Giorni"]))
                    updated_data.append(updated_row)
                else:
                    updated_row = row.copy()
                    updated_row["Slot"] = "-"
                    updated_row["Giorni"] = "-"
                    updated_data.append(updated_row)
            return updated_data
        
        # Update data per ogni sezione
        updated_stato_attuale_slot = update_data(dati["Stato attuale"], filtered_data, "Slot_Attuale", "Giorni_Attuale")
        updated_stato_post_riconfigurazione_slot = update_data(dati["Stato post riconfigurazione"], filtered_data, "Slot_Riconfigurazione", "Giorni_Riconfigurazione")
        updated_stato_attuale_days = update_data(dati["Stato attuale"], filtered_data, "Slot_Attuale", "Giorni_Attuale")
        updated_stato_post_riconfigurazione_days = update_data(dati["Stato post riconfigurazione"], filtered_data, "Slot_Riconfigurazione", "Giorni_Riconfigurazione")
                
        # Applica la formattazione ai campi Slot e Giorni
        for updated_row in updated_stato_attuale_slot:
            if updated_row["Slot"] != "-":
                updated_row["Slot"] = format_thousands(updated_row["Slot"])
            if updated_row["Giorni"] != "-" and updated_row["Giorni"] != "xx giorni":
                updated_row["Giorni"] = format_thousands(updated_row["Giorni"])
        for updated_row in updated_stato_post_riconfigurazione_slot:
            if updated_row["Slot"] != "-":
                updated_row["Slot"] = format_thousands(updated_row["Slot"])
            if updated_row["Giorni"] != "-" and updated_row["Giorni"] != "xx giorni":
                updated_row["Giorni"] = format_thousands(updated_row["Giorni"])
        # Creazione di una nuova lista di dizionari con la colonna aggiuntiva Slot_Riconfigurazione
        updated_stato_attuale_slot_updated = []
        for updated_row_attuale, updated_row_riconfigurazione in zip(updated_stato_attuale_slot, updated_stato_post_riconfigurazione_slot):
            updated_row_attuale_updated = updated_row_attuale.copy()  # Copia il dizionario esistente
            updated_row_attuale_updated["Slot_Riconfigurazione"] = updated_row_riconfigurazione["Slot"]  # Aggiungi la nuova colonna
            updated_stato_attuale_slot_updated.append(updated_row_attuale_updated)
        
        for updated_row in updated_stato_attuale_days:
            if updated_row["Slot"] != "-":
                updated_row["Slot"] = format_thousands(updated_row["Slot"])
            if updated_row["Giorni"] != "-" and updated_row["Giorni"] != "xx giorni":
                updated_row["Giorni"] = format_thousands(updated_row["Giorni"])
        for updated_row in updated_stato_post_riconfigurazione_days:
            if updated_row["Slot"] != "-":
                updated_row["Slot"] = format_thousands(updated_row["Slot"])
            if updated_row["Giorni"] != "-" and updated_row["Giorni"] != "xx giorni":
                updated_row["Giorni"] = format_thousands(updated_row["Giorni"])
        # Creazione di una nuova lista di dizionari con la colonna aggiuntiva Giorni_Riconfigurazione
        updated_stato_attuale_days_updated = []
        for updated_row_attuale, updated_row_riconfigurazione in zip(updated_stato_attuale_days, updated_stato_post_riconfigurazione_days):
            updated_row_attuale_updated = updated_row_attuale.copy()  # Copia il dizionario esistente
            updated_row_attuale_updated["Giorni_Riconfigurazione"] = updated_row_riconfigurazione["Giorni"]  # Aggiungi la nuova colonna
            updated_stato_attuale_days_updated.append(updated_row_attuale_updated)
        return (
            create_table_rows(updated_stato_attuale_slot_updated, include_slot=True),
            create_table_rows(updated_stato_attuale_days_updated, include_days=True),
        )
    else:
        return (
            None, None
        )
def create_table_rows(data, include_days=False, include_slot=False, include_image=True):
    rows = []
    has_average_row = any(row['Priorità'] == "" for row in data)
    for row in data:
        class_name = ""
        if row["Immagine"] == "urgent.png":
            class_name = "urgent"
        elif row["Immagine"] == "short.png":
            class_name = "short"
        elif row["Immagine"] == "deferable.png":
            class_name = "deferable"
        elif row["Immagine"] == "planned.png":
            class_name = "planned"
        if include_image:
            row_cells = [
                html.Td(row["Priorità"], className="table-cell", style={"border-bottom": "none"}),
                html.Td(html.Img(src=f"/assets/{row['Immagine']}", style={"width": "30px"}), className=f"table-cell {class_name}", style={"border-bottom": "none"}) if row["Immagine"] else html.Td("", className="table-cell", style={"border-bottom": "none"})
            ]
        else:
            row_cells = [
                html.Td(row["Priorità"], className="table-cell", style={"border-bottom": "none"}, hidden=True),
                html.Td(html.Img(src=f"/assets/{row['Immagine']}", style={"width": "30px"}), className=f"table-cell {class_name}", style={"border-bottom": "none"}, hidden=True) if row["Immagine"] else html.Td("", className="table-cell", style={"border-bottom": "none"}, hidden=True)
            ]
        if include_slot:
            row_cells.append(html.Td(row["Slot"], className="table-cell", style={"border-bottom": "none"}))
            if "Slot_Riconfigurazione" in row:  # Aggiungi Slot_Riconfigurazione solo se presente
                row_cells.append(html.Td(row["Slot_Riconfigurazione"], className="table-cell", style={"border-bottom": "none"}))
        if include_days:
            row_cells.append(html.Td(row["Giorni"], className="table-cell", style={"border-bottom": "none"}))
            if "Giorni_Riconfigurazione" in row:  # Aggiungi Giorni_Riconfigurazione solo se presente
                row_cells.append(html.Td(row["Giorni_Riconfigurazione"], className="table-cell", style={"border-bottom": "none"}))
        # Linea blu per Media
        if has_average_row and row["Priorità"] == "":
            rows.append(html.Tr(html.Td(html.Hr(style={"border": "2px solid", "width": "100%", 'color': '#0F2471'}), colSpan=len(row_cells), style={"padding": "0", "border-bottom": "none"})))
        rows.append(html.Tr(row_cells))
    return rows
## callback per menu a tendina in alto ulteriori dettagli 
@app.callback(
    Output('agenda_name', 'children'),
    [Input('dropdown-value-store', 'data'),
    Input('store-df6', 'data')]
)
def update_agenda_viewer(selected_agenda,df_data_agende_dict):
    if selected_agenda:
        selected_agenda = str(selected_agenda)
        data_agende = pd.DataFrame.from_dict(df_data_agende_dict)
        if selected_agenda in data_agende['Codice Agenda'].values:
            description = data_agende.loc[data_agende['Codice Agenda'] == selected_agenda, 'Descrizione Agenda'].values[0]
            return html.Div(description, style={'white-space': 'pre-line'})
        else:
            return html.Span("Codice agenda non trovato.")
    return html.Span("Seleziona un codice agenda dal menu a tendina.")
#### Informazioni sulla struttura 
@app.callback(
    Output('output-container', 'children'),
    [Input('dropdown-value-store', 'data')]
)
def update_output(selected_agenda):
    filtered_df = df_dettaglio_struttura[df_dettaglio_struttura['Codice Agenda'].astype(str) == selected_agenda]
    if filtered_df.empty:
        return html.P('No data available')
    row = filtered_df.iloc[0]
    return html.Div([
        html.P([
            html.Div('Azienda: ', style={'fontWeight': 'bold', 'font-family': font_family_variable, 'color': '#0F2471'}),
            html.Div(row['Azienda'])
        ]),
        html.P([
            html.Div('Codice Erogante: ', style={'fontWeight': 'bold', 'font-family': font_family_variable, 'color': '#0F2471'}),
            html.Div(row['Codice Erogante'])
        ]),
        html.P([
            html.Div('Specializzazione: ', style={'fontWeight': 'bold', 'font-family': font_family_variable, 'color': '#0F2471'}),
            html.Div(row['Specializzazione'])
        ]),
    ])
# Callback per memorizzare il valore del dropdown selezionato
@app.callback(
    Output('dropdown-value-store', 'data'),
    [Input('dropdown-tempi-attesa', 'value')]
)
def store_dropdown_value(selected_code):
    return selected_code
# Callback per aggiornare la tabella filtrata
@app.callback(
    [Output('filtered-table-ulteriori', 'rowData'),
     Output('filtered-table-ulteriori', 'columnDefs'),
     Output('filtered-table-ulteriori', 'style')],
    [Input('dropdown-value-store', 'data')]
)
def update_filtered_table(selected_code):
    if selected_code:
        selected_code = str(selected_code)
        filtered_data = prestazioni_agende_df[prestazioni_agende_df['Codice Agenda'] == selected_code]
        filtered_data = filtered_data.drop('Codice Agenda', axis= 1)
        if not filtered_data.empty:
            columns = [{'headerName': i, 'field': i} for i in filtered_data.columns]
            data = filtered_data.to_dict('records')
            # Calcoliamo l'altezza della tabella in base al numero di righe
            table_height = 100 # Altezza minima di 40px, massima di 500px
            table_style = {'height': f'{table_height}%'}
            return data, columns, table_style
    # Se non ci sono dati, restituisci liste vuote per righe e colonne e stile della tabella vuoto
    return [], [], {'height': '0px'}
@app.callback(
    Output('chart-text', 'children'),
    [Input('dropdown-value-store', 'data')]
)
def update_donut_chart(selected_code):
    if selected_code:
        selected_code = str(selected_code)
        filtered_chart_data = df_chart[df_chart['Codice Agenda'] == selected_code]
        
        if not filtered_chart_data.empty:
            valore_aderenza = filtered_chart_data['Value'].values[0]
            values = [valore_aderenza * 100, (1 - valore_aderenza) * 100]
            labels = ['Grado di Cooperazione', ' ']
            
            # Creiamo il grafico a torta 3D
            fig = go.Figure(data=[go.Pie(
                labels=labels,
                values=values,
                hole=0.7,
                marker=dict(
                    colors=['#00338D', '#8aa0e0'],
                    line=dict(color='white', width=3)
                ),
                pull=[0.1],
                textinfo='label+percent',
                textposition='outside',
                insidetextfont=dict(
                    color=['#00338D', '#ECF2FE']  # Colori del testo interno per ogni label
                ),
                outsidetextfont=dict(
                    color=['#00338D', '#ECF2FE']  # Colori del testo esterno per ogni label
                )
            )])
            fig.update_traces(
                textposition='outside',
                textfont=dict(size=16, color='#0F2471', family='Arial', weight='bold')
            )
            fig.update_layout(
                paper_bgcolor='#ECF2FE',
                plot_bgcolor='#ECF2FE',
                showlegend=False,
                margin={'b': 0, 'l': 0, 't': 0, 'r': 0},
                height=None,  # Permette di essere responsivo
                width=None   # Permette di essere responsivo
            )
            return html.Div([
                dcc.Graph(figure=fig, style={'width': '100%', 'height': '100%'})
            ], style={'width': '100%', 'height': '90%'})  # Altezza massima del contenitore
        else:
            return html.Div("Nessun dato disponibile.")
    else:
        return html.Div("Seleziona un codice.")
"""
def update_donut_chart(selected_code):
    if selected_code:
        selected_code = str(selected_code)
        filtered_chart_data = df_chart[df_chart['Codice Agenda'] == selected_code]
        
        if not filtered_chart_data.empty:
            valore_aderenza = filtered_chart_data['Value'].values[0]
            values = [valore_aderenza, 1 - valore_aderenza]
            labels = ['Grado di Aderenza', 'Non Aderenza']
            
            fig = go.Figure(data=[go.Pie(
                labels=labels,
                values=values,
                hole=.7,
                marker=dict(colors=['#B5D0FF', 'lightgrey'], line=dict(color='white', width=3)),
                textinfo='none',
                hoverinfo='label+percent',
                hovertext=labels
            )])
            
            fig.update_layout(
                paper_bgcolor='#F2F2F2',
                plot_bgcolor='#F2F2F2',
                width=580,
                height=390,
                showlegend=False,
                annotations=[
                    dict(
                        text=f'{valore_aderenza*100:.0f}%', 
                        x=0.5, 
                        y=0.5, 
                        font=dict(color='#B5D0FF', family='Arial',weight='bold', size=24), 
                        showarrow=False
                    ),
                    dict(
                        text="Rappresenta la % di presenza nel<br>"
                             "medesimo cluster del grafo delle coppie di<br>"
                             "prestazioni in agenda.",
                        x=0.5,
                        y=-0.37,  # Spostiamo la ciambella un po' più in alto
                        align='center',
                        valign='middle',
                        font=dict(color='#00194C', size=15),
                        showarrow=False
                    )
                ]
            )
            return html.Div([
                dcc.Graph(figure=fig),
                #html.Div("Rappresentazione testuale aggiuntiva")
            ])
    # If no data is available or no code is selected, return a message
    return "Dati non presenti"
"""
### tab 2 
def create_pie_chart_with_annotation(labels, values, annotation_text):
    fig = go.Figure(go.Pie(labels=labels, values=values,hole=.7,textinfo='none'))
    fig.update_traces(marker=dict(colors=['#00338D', '#8aa0e0']))
    fig.update_layout(
        showlegend=False,
        paper_bgcolor='#ECF2FE',
        plot_bgcolor='#ECF2FE',
        annotations=[dict(text=annotation_text, x=0.5, y=0.5, font_size=20, showarrow=False)]
    )
    return fig
@app.callback(
    [Output('grafo-donut-chart', 'figure'),
     Output('cluster-donut-chart', 'figure'),
     Output('raggruppamenti-donut-chart', 'figure')],
    [Input('dropdown-value-store', 'data')]
)
def update_charts(selected_code):
    # Filtra il dataframe in base al codice selezionato
    selected_code = str(selected_code)
    df_filtered = df_dettaglio_agenda_1[df_dettaglio_agenda_1['Codice Agenda'] == selected_code]
    
    if not df_filtered.empty:
        grafo_value = df_filtered['Congruenza al grafo'].iat[0]
        cluster_value = df_filtered['Congruenza al cluster'].iat[0]
        raggruppamenti_value = df_filtered['Congruenza ai raggruppamenti'].iat[0]
    else:
        grafo_value = cluster_value = raggruppamenti_value = None
    
    # Create the donut charts
    # Grafico per "Congruenza al grafo"
    fig_grafo = create_pie_chart_with_annotation(
        labels=['Congruenza al grafo', ''],
        values=[grafo_value if grafo_value is not None else 0, 1 - grafo_value if grafo_value is not None else 1],
        annotation_text=f"{grafo_value*100:.0f}%" if pd.notna(grafo_value) else "-"
    )
    # Grafico per "Congruenza al cluster"
    fig_cluster = create_pie_chart_with_annotation(
        labels=['Congruenza al cluster', ''],
        values=[cluster_value if cluster_value is not None else 0, 1 - cluster_value if cluster_value is not None else 1],
        annotation_text=f"{cluster_value*100:.0f}%" if pd.notna(cluster_value) else "-"
    )
    # Grafico per "Congruenza ai raggruppamenti"
    fig_raggruppamenti = create_pie_chart_with_annotation(
        labels=['Congruenza ai raggruppamenti', ''],
        values=[raggruppamenti_value if raggruppamenti_value is not None else 0, 1 - raggruppamenti_value if raggruppamenti_value is not None else 1],
        annotation_text=f"{raggruppamenti_value*100:.0f}%" if pd.notna(raggruppamenti_value) else "-"
    )
    labels = ['Congruenza al grafo','Congruenza al cluster','Congruenza ai raggruppamenti']
    # Update traces and layout for each figure
    i=0
    for fig in [fig_grafo, fig_cluster, fig_raggruppamenti]:
        #for label in labels:
        fig.update_traces(marker=dict(colors=['#00338D','#8aa0e0']),textinfo='none')
        fig.update_layout(showlegend=False,paper_bgcolor='#ECF2FE',
                    plot_bgcolor='#ECF2FE',title_text = labels[i],title_font=dict(size=12, color='#00338D'),title=dict(x=0.50,yanchor='top',  # Anchor point for y
                y=0.95), margin=dict(b=10, l=10, r=10, t=35) )
        i+=1
    
    return fig_grafo, fig_cluster, fig_raggruppamenti
## filtro tabella suggerimento intellingente
@app.callback(
    [Output('table-suggerimento-intelligente', 'rowData'),
     Output('table-suggerimento-intelligente', 'columnDefs'),
     Output('table-suggerimento-intelligente', 'style')],
    [Input('dropdown-value-store', 'data')]
)
def update_table(selected_code):
    if selected_code:
        selected_code =str(selected_code)
        filtered_data = df_suggerimento[df_suggerimento['Codice Agenda'] == selected_code]
        filtered_data = filtered_data.drop('Codice Agenda', axis= 1)
        if not filtered_data.empty:
            columns = [{'headerName': i, 'field': i} for i in filtered_data.columns]
            data = filtered_data.to_dict('records')
            # Calcoliamo l'altezza della tabella in base al numero di righe
            table_height = 18 # Altezza minima di 40px, massima di 500px
            table_style = {'height': f'{table_height}vh'}
            return data, columns, table_style
    # Se non ci sono dati, restituisci liste vuote per righe e colonne e stile della tabella vuoto
    return [], [], {'height': '0px'}
# Example callback for download
# Callback for downloading the file
@app.callback(
    Output("download-file", "data"),
    [Input("download-image", "n_clicks"), Input('start-date-store','data'),Input('end-date-store','data'),Input('selected-code-store', 'data'),Input('store-df4','data')],
)
def update_download_data(n_clicks,start_date,end_date,options,recap_stato_post_riconfigurazione_dict):
 
    #print(df_report_percentuale)
    if n_clicks :
        
        df_daily_requests_in_minutes_per_agend = pd.read_csv(os.path.join(base_path,'File_utili','df_daily_requests_in_minutes_per_agend.csv'))
        #recap_stato_post_riconfigurazione_download = pd.DataFrame.from_dict(recap_stato_post_riconfigurazione_dict)
        #print('---recap download inner--', df_daily_requests_in_minutes_per_agend)
        # Path to the original file
        file_path = root_path_download
        # Load the existing Excel file
        workbook = load_workbook(file_path)
        current_date = datetime.now().strftime("%Y-%m-%d")
        download_filename = f"Smart Scheduler_Slot Giornalieri Consigliati_{current_date}.xlsx"
        df_report_dettaglio,df_report_percentuale = create_report_download(start_date,end_date,options,df_daily_requests_in_minutes_per_agend)
        workbook = load_workbook(file_path)
        #print('download')
        sheet_name_1 = 'Report_Dettaglio'
        sheet_name_2 = 'Report_Percentuale'
        worksheet_det = workbook[sheet_name_1]
        worksheet_per = workbook[sheet_name_2]
        start_row = 5
        start_col = 2
        for r_idx, row in enumerate(df_report_dettaglio.itertuples(index=False), start=start_row):
            for c_idx, value in enumerate(row, start=start_col):
                worksheet_det.cell(row=r_idx, column=c_idx, value=value)
        for r_idx, row in enumerate(df_report_percentuale.itertuples(index=False), start=start_row):
            for c_idx, value in enumerate(row, start=start_col):
                worksheet_per.cell(row=r_idx, column=c_idx, value=value)
        workbook.save(download_filename)
                # Return the download link using dcc.send_file
        return dcc.send_file(download_filename,filename=download_filename)
                #return dcc.send_file
    return None
@app.callback(
    Output('table-container-2', 'children'),
    [
        Input('dropdown-menu', 'value'),
        Input('start-date-store', 'data'),
        Input('end-date-store', 'data'),
        Input('store-df3', 'data')
    ]
)
def update_table2(selected_region, date_start, date_end,direttore_ore_ambulatorio__dict):
    # Example DataFrame 
    
    df_ore_ambulatorio = pd.DataFrame.from_dict(direttore_ore_ambulatorio__dict)
    for col in df_ore_ambulatorio.select_dtypes(include=['int64', 'float64']).columns:
        df_ore_ambulatorio[col] = df_ore_ambulatorio[col].apply(lambda x: "-" if pd.isna(x) else format_thousands(x))
    num_rows = len(df_ore_ambulatorio)
    
    # Set row height and header height
    row_height = 60  # Estimated row height
    
    # Set max table height for 8 rows plus header
    max_table_height = 6 * row_height + 30
    
    # Determine the table height
    table_height = min(num_rows * row_height + 30, max_table_height)
    
    # Set table style with dynamic height and scroll bar if necessary
    table_style = {
        'height': f'{table_height}px', 
        'overflowY': 'auto' if num_rows > 8 else 'hidden', 
        'width': '100%', 
        'margin': 'auto'
    }
    
    # Define columns
    columns = [{'headerName': col, 'field': col} for col in df_ore_ambulatorio.columns]
    
    return [
        dag.AgGrid(
            id='table',
            columnDefs=columns,
            rowData=df_ore_ambulatorio.to_dict('records'),
            className="ag-theme-alpine",
            rowStyle={'background-color': 'rgba(181, 208, 255, 0.2)'},
            dashGridOptions={
                "rowSelection": "multiple", 
                "suppressRowClickSelection": True, 
                "animateRows": False,
                "defaultColDef": {
                    "resizable": True,
                    "sortable": True,
                    "filter": True,
                    "flex": 1,
                    "rowHeight": row_height
                },
                "scrollContainer": "body",
                "alwaysShowVerticalScroll": True,
                "alwaysShowHorizontalScroll": True,
                "getRowStyle": {
                    'backgroundColor':'#ECF2FE'
                },
            },
            style=table_style
        )
    ]
### TABELLA DIRETTORE - TEMPI MEDI DI ATTESA PER RECLUSTERING
@app.callback(
    Output('table-container-1', 'children'),
    [Input('dropdown-menu', 'value'),
     Input('store-df2','data')
     ]
)
def update_table1(selected_region,direttore_reclustering_tempi_attesa_dict):
    df_recluster_direttore_tempi = pd.DataFrame.from_dict(direttore_reclustering_tempi_attesa_dict)  # Use actual data source
    for col in df_recluster_direttore_tempi.select_dtypes(include=['int64', 'float64']).columns:
        df_recluster_direttore_tempi[col] = df_recluster_direttore_tempi[col].apply(lambda x: "-" if pd.isna(x) else format_thousands(x))
    
    #La colonna si chiama ambito per fare il filtro
# Stampiamo il DataFrame filtrato
    
    num_rows = len(df_recluster_direttore_tempi)
    row_height = 60
    max_table_height =  6 * row_height + 30
    table_height = min(num_rows * row_height + 30, max_table_height)
    table_style = {'height': f'{table_height}px', 'overflowY': 'auto', 'width': '100%', 'margin': 'auto'} if num_rows > 8 else {'height': f'{table_height}px', 'width': '100%', 'margin': 'auto'}
    columns = [{'headerName': col, 'field': col} for col in df_recluster_direttore_tempi.columns]
    
    return dag.AgGrid(
        id='table',
        columnDefs=columns,
        rowData=df_recluster_direttore_tempi.to_dict('records'),
        className="ag-theme-alpine",
        dashGridOptions={
            'suppressRowTransform': True,
            "rowSelection": "multiple",
            "suppressRowClickSelection": True,
            "animateRows": False,
            "defaultColDef": {
                "resizable": True,
                "sortable": True,
                "filter": True,
                "flex": 1,
                "rowHeight": 60
            },
            "alwaysShowVerticalScroll": True,
            "getRowStyle": {
                    'backgroundColor':'#ECF2FE'
                },
        },
        style=table_style,
    )
#### PROVA CALLBACK ALTERNA I DIVERSI LOGIN 
@app.callback(
    Output('output-div_login', 'children'),
    [Input('button_login', 'n_clicks')]
)
def update_output_div(n_clicks):
    global value
    if n_clicks is None:
        # Imposta il valore della variabile su None se il pulsante non è stato ancora cliccato
        value = None
    else:
        # Imposta il valore della variabile su qualcosa di diverso da None quando il pulsante è stato cliccato
        value = 'La variabile è stata valorizzata!'
# Callback per memorizzare l'username associato al pulsante 1
@app.callback(
    Output('username1-store', 'data'),
    [Input('button-1', 'n_clicks')],
    [State('username1', 'value')]
)
def store_username1(button1_clicks, username1):
    if button1_clicks and username1:
        return username1
    else:
        return None
# Callback per memorizzare l'username associato al pulsante 2
@app.callback(
    Output('username2-store', 'data'),
    [Input('button-2', 'n_clicks')],
    [State('username2', 'value')]
)
def store_username2(button2_clicks, username2):
    if button2_clicks and username2:
        return username2
    else:
        return None
# Callback per recuperare l'username memorizzato nei pulsanti 1 e 2 negli store
@app.callback(
    Output('button-1-clicks-store', 'data'),
    [Input('button-1', 'n_clicks')]
)
def store_button_1_clicks(button_1_clicks):
    return button_1_clicks
@app.callback(
    Output('button-2-clicks-store', 'data'),
    [Input('button-2', 'n_clicks')]
)
def store_button_2_clicks(button_2_clicks):
    return button_2_clicks
@app.callback(
    Output('icona', 'children'),
    [Input('button-2-clicks-store', 'data')]
)
def display_icon(button_2_clicks):
    if button_2_clicks and button_2_clicks > 0:
        return html.Div([
            dcc.Link(html.Img(src='/assets/research.png',height = '35px'),    href='/consigli',
                        style={'float': 'right', 'display': 'inline-block','display': 'grid',
    'grid-template-columns': '1fr auto'}
                    )
        ])
    else:
        return None
# Callback per aggiornare il testo data prima disponibilità
@app.callback(
    Output('stima-tempi-attesa', 'children'),
    [Input('dropdown-value-store', 'data'), Input('store-df5','data'),Input('start-date-store','data')]
)
def update_text(codice_agenda,df_data,start_date):
    
    df_prima_disp = pd.DataFrame(df_data) if df_data else pd.DataFrame()
    df_prima_disp['Codice Agenda'] = df_prima_disp['Codice Agenda'].astype(str)
    codice_agenda = str(codice_agenda)
    # Filtrare il dataframe
    filtered_df = df_prima_disp[df_prima_disp['Codice Agenda'] == codice_agenda]
    # Ottenere la prima data dal dataframe filtrato
    if not filtered_df.empty:
        data_riconfigurazione = filtered_df['Data_Prima_disponibilita'].iloc[0]
    else:
        data_riconfigurazione = 'N/A'  # Nel caso in cui non ci siano dati corrispondenti
    start = pd.to_datetime(start_date)
    print(type(start))
    print(type(data_riconfigurazione))
    dr = pd.to_datetime(data_riconfigurazione)
    print(dr)
    # Calcola il mese successivo
    mese_successivo = dr.month + 1
    anno_successivo = dr.year
    # Gestisci il cambio di anno se necessario
    if mese_successivo > 12:
        mese_successivo = 1
        anno_successivo += 1
    # Nome del mese in italiano
    mesi_italiani = [
        'gennaio', 'febbraio', 'marzo', 'aprile', 'maggio', 'giugno',
        'luglio', 'agosto', 'settembre', 'ottobre', 'novembre', 'dicembre'
    ]
    # Ottieni il nome del mese successivo
    nome_mese_successivo = mesi_italiani[mese_successivo - 1]
    frase = f"Gli effetti della riconfigurazione partiranno da "
    return html.Div([
        frase,
        html.B(f"{nome_mese_successivo.capitalize()} {anno_successivo}")
    ])
    #return f"Gli effetti della riconfigurazione partiranno da {nome_mese_successivo.capitalize()} {anno_successivo}"
# Callback per aggiornare l'intervallo di date selezionato
@app.callback(
    Output('start-date-store', 'data'),
    Input('start-day-dropdown', 'value'),
    Input('start-month-dropdown', 'value'),
    Input('start-year-dropdown', 'value')
)
def update_start_date_store(day, month, year):
    formatted_date = f"{year}-{month:02d}-{day:02d}"
    return formatted_date
@app.callback(
    Output('end-date-store', 'data'),
    Input('end-day-dropdown', 'value'),
    Input('end-month-dropdown', 'value'),
    Input('end-year-dropdown', 'value')
)
def update_end_date_store(day, month, year):
    formatted_date = f"{year}-{month:02d}-{day:02d}"
    return formatted_date
@app.callback(
    Output('date-error-message', 'children'),
    [
        Input('start-day-dropdown', 'value'),
        Input('start-month-dropdown', 'value'),
        Input('start-year-dropdown', 'value'),
        Input('end-day-dropdown', 'value'),
        Input('end-month-dropdown', 'value'),
        Input('end-year-dropdown', 'value')
    ]
)
def update_dates(start_day, start_month, start_year, end_day, end_month, end_year):
    
    
    date_start = date(int(start_year), int(start_month), int(start_day))
    date_end = date(int(end_year), int(end_month), int(end_day))
    
    # Check if date_start is greater than date_end
    if date_start > date_end:
        error_message = 'La data di inizio non può essere maggiore della data di fine.'
        return html.Div(error_message, style={'color': 'red'})
    
    
    return ''
"""giorno_odierno = int(oggi.strftime('%d'))
mese_odierno = int(oggi.strftime('%m'))
anno_odierno = int(oggi.strftime('%Y'))"""
data_di_riferimento = date(2024, 10, 27)
"""@app.callback(
    [
        Output('start-day-dropdown', 'options'),
        Output('end-day-dropdown', 'options')
    ],
    [
        Input('start-month-dropdown', 'value'),
        Input('start-year-dropdown', 'value'),
        Input('end-month-dropdown', 'value'),
        Input('end-year-dropdown', 'value')
    ]
)
def update_day_options(start_month, start_year, end_month, end_year):
    # Check if the month and year are provided
    if None in [start_month, start_year, end_month, end_year]:
        raise PreventUpdate
    
    # Filtra le opzioni di giorno per il mese di inizio
    start_days_in_month = calendar.monthrange(start_year, start_month)[1]
    
    start_day_options = [{'label': str(i), 'value': i} for i in range(1, start_days_in_month + 1)]
    if end_year == data_di_riferimento.year and end_month == data_di_riferimento.month:
        end_days_in_month = min(calendar.monthrange(end_year, end_month)[1], data_di_riferimento.day)
    else:
        end_days_in_month = calendar.monthrange(end_year, end_month)[1]
    end_day_options = [{'label': str(i), 'value': i} for i in range(1, end_days_in_month + 1)]
    return start_day_options, end_day_options"""
@app.callback(
    [
        Output('start-month-dropdown', 'options'),
        Output('end-month-dropdown', 'options')
    ],
    [
        Input('start-year-dropdown', 'value'),
        Input('end-year-dropdown', 'value')
    ]
)
def update_date_options(start_year, end_year):
    # Check if the year is provided
    if None in [start_year, end_year]:
        raise PreventUpdate
    # Filtra le opzioni del mese per l'anno di inizio
    if start_year == 2024:
        start_month_options = [{'label': str(i), 'value': i} for i in range(1, data_di_riferimento.month + 1)]
    else:
        start_month_options = [{'label': str(i), 'value': i} for i in range(1, 13)]
    
    # Filtra le opzioni del mese per l'anno di fine
    if end_year == data_di_riferimento.year:
        end_month_options = [{'label': str(i), 'value': i} for i in range(1, data_di_riferimento.month + 1)]
    else:
        end_month_options = [{'label': str(i), 'value': i} for i in range(1, 13)]
    return start_month_options, end_month_options
#### CALCOLO GRADO DI URGENZA 
def data_agende_home(prima_disp_file_Manuel,start_date,end_date):
    ## Funzione per la creazione--> Grado di urgenza** e  file data_agende pagina layoutagende
    
    ### GRADO DI GRADO_SATURAZIONE
    calendar_agende['GRADO_SATURAZIONE'] = calendar_agende['BUSY_SLOTS'] / calendar_agende['MAX_QUANTITY']
    calendar_agende['CALENDAR_DATE'] = pd.to_datetime(calendar_agende['CALENDAR_DATE'])
    calendar_agende_filt = calendar_agende[(calendar_agende['CALENDAR_DATE'] >= start_date) & (calendar_agende['CALENDAR_DATE'] <= end_date)]
    # Raggruppiamo per DIARY_ID e calcoliamo la media di GRADO_SATURAZIONE
    grado_saturazione_avg = calendar_agende_filt.groupby('DIARY_ID')['GRADO_SATURAZIONE'].mean().reset_index()
    
    
    #agende_file = common_path + '\V_AGENDE_ATTIVE_CE.csv'
    grado_saturazione_avg['DIARY_ID'] = grado_saturazione_avg['DIARY_ID'].astype('str')
    ## GRADO DI PRIORITIZZAZIONE
    #df_decodifica= pd.read_csv(path_decodifica_ambiti_garanzia, sep=";", dtype=dtype)
    #df_decodifica.rename(columns={'sts11': 'STS11', 'ambiti_aziende': 'AMBITI_AZIENDE'}, inplace=True)
    timeband_orders_df['START_DATE'] = pd.to_datetime(timeband_orders_df['START_DATE'])
    # Filtriamo il DataFrame per includere solo le righea con date nel range specificato data_agednda start <= data_filtro
    #                                                                                    data_agednda end >= data_filtro
    
    timeband_orders_df_filt =  timeband_orders_df[(timeband_orders_df['START_DATE'] <= end_date) & (timeband_orders_df['END_DATE'] >= start_date)]
    ## da modificare
    
    timeband_orders_df_filt.PRIORITY_CLASS_MASK.fillna(value='BDUP', inplace=True)
    # Funzione per calcolare la percentuale di classi di priorità
    def calculate_priority_percentage(classi_priorita):
        if pd.isnull(classi_priorita):
            return 0
        # Conta il numero di lettere nella stringa delle classi di priorità
        num_letters = len(classi_priorita)
        # Se ci sono più di una lettera, restituisci 0%, altrimenti 100%
        return 0 if num_letters > 1 else 1
    # Applica la funzione alla colonna CLASSI_PRIORITA per ottenere grado_di_prioritizzazione
    # Raggruppa per DIARY_ID e calcola la media di grado_di_prioritizzazione
    timeband_orders_df_filt_no_dup = timeband_orders_df_filt[['TIMEBAND_ID','DIARY_ID','PRIORITY_CLASS_MASK']].drop_duplicates(keep='first')
    timeband_orders_df_filt_no_dup['grado_di_prioritizzazione'] = timeband_orders_df_filt_no_dup['PRIORITY_CLASS_MASK'].apply(calculate_priority_percentage)
    grado_prioritizzazione_avg = timeband_orders_df_filt_no_dup.groupby('DIARY_ID')['grado_di_prioritizzazione'].mean().reset_index()
    grado_prioritizzazione_avg['DIARY_ID'] = grado_prioritizzazione_avg['DIARY_ID'].astype('str')
    ## GRADO DI PRIMA DISPONIBILITA'
    # Raggruppa per DIARY_ID e calcola la media di grado_di_prioritizzazione
    
    
    prima_disp_file_Manuel_rename =prima_disp_file_Manuel.rename(columns={'Codice Agenda': 'DIARY_ID'})
    
    # Converti DIARY_ID in calendar in int64
    calendar_agende['DIARY_ID'] = calendar_agende['DIARY_ID'].astype('str')
    prima_disp_file_Manuel_rename['DIARY_ID'] = prima_disp_file_Manuel_rename['DIARY_ID'].astype('str')
    # Esegui il merge
    joined_df = calendar_agende.merge(prima_disp_file_Manuel_rename,on='DIARY_ID',how='left')
    joined_df['Data_Prima_disponibilita']= pd.to_datetime(joined_df['Data_Prima_disponibilita'],format="%d-%m-%Y")
    joined_df_no_timeband_dup =joined_df.groupby(['CALENDAR_DATE','DIARY_ID','Data_Prima_disponibilita']).first().reset_index()
    numeratore = joined_df_no_timeband_dup[joined_df_no_timeband_dup['CALENDAR_DATE'] <= joined_df_no_timeband_dup['Data_Prima_disponibilita']].groupby('DIARY_ID').size().reset_index(name='count')
    denominatore = joined_df_no_timeband_dup.groupby('DIARY_ID')['CALENDAR_DATE'].size().reset_index(name='denominatore')
    new_df = numeratore.merge(denominatore, on='DIARY_ID',how='left')
    new_df['GRADO DI PRIMA DISP']=((new_df['count']/new_df['denominatore']))
    new_df['DIARY_ID'] = new_df['DIARY_ID'].astype('str')
    ### FAI COMANDARE LA TABELLA DI TUTTE LE AGENDE E TUTTE LE DESCRIZIONI 
    agende_descrizione_join = agende_descrizione.copy()
    agende_descrizione_join['Codice Agenda'] = agende_descrizione_join['Codice Agenda'].astype('str')
    agende_descrizione_join.rename(columns ={'Codice Agenda' : 'DIARY_ID'},inplace=True)
    join_agenda_grado_prima_disp = agende_descrizione_join.merge(new_df,on='DIARY_ID',how='left')
    join_prima_disp_saturazione = join_agenda_grado_prima_disp.merge(grado_saturazione_avg,on='DIARY_ID',how='left')
    join_prima_disp_saturaz_prioritizz= join_prima_disp_saturazione.merge(grado_prioritizzazione_avg,on='DIARY_ID',how='left')
    join_prima_disp_saturaz_prioritizz.grado_di_prioritizzazione.fillna(value=0, inplace=True)
    ### CALCOLO GRADO DI URGENZA**
    def calculate_value(row):
        if pd.isnull(row['GRADO DI PRIMA DISP']) or pd.isnull(row['GRADO_SATURAZIONE'])or pd.isnull(row['grado_di_prioritizzazione']):
            return np.nan
        else:
            return round(1 -(0.2 * row['GRADO DI PRIMA DISP'] + 0.4 * row['GRADO_SATURAZIONE'] + 0.4 * row['grado_di_prioritizzazione'] ),2)
    join_prima_disp_saturaz_prioritizz['Grado di urgenza**'] = join_prima_disp_saturaz_prioritizz.apply(calculate_value, axis=1)
    grado_di_urgenza = join_prima_disp_saturaz_prioritizz[['DIARY_ID','Grado di urgenza**']]
    grado_di_urgenza.rename(columns = {'DIARY_ID':'Codice Agenda'},inplace=True)
    
    ### CALCOLO DATA_AGENDE UNENDO I DF CREATI (COD/DESC AGENDA - GRADO URGENZA - ADERENZA GRAFO - SENTINELLA)
    
    ###CALCOLO COLONNA SENTINELLA 
    sentinella_2 = sentinella.rename(columns = {'Codice Catalogo regionale' : 'Codice Prestazione'})
    sentinella_2['flag'] = 'Si'
    agende_copy = prestazioni_agende_df.copy()
    sentinella_2['Codice Prestazione'] = sentinella_2['Codice Prestazione'].astype('str')
    agende_copy['Codice Prestazione'] = agende_copy['Codice Prestazione'].astype('str')
    agende_con_sentinella = agende_copy.merge(sentinella_2, on = 'Codice Prestazione', how ='left')
    df_sentinella = agende_con_sentinella.groupby('Codice Agenda').apply(lambda x: 'Si' if (x['flag'] == 'Si').any() else 'No').reset_index(name ='Sentinella')
    ## AGGIUNTA GRADO DI ADERENZA AL GRAFO **
    df_chart_1 = df_chart.rename(columns = {'Value' : 'Grado di aderenza al grafo*'})
    ## Parti dal file agende_descrizione che contiene tutte le agende con la descrizioni e fai tutte left
    agende_descrizione['Codice Agenda'] = agende_descrizione['Codice Agenda'].astype('str')
    df_chart_1['Codice Agenda'] = df_chart_1['Codice Agenda'].astype('str')
    codice_descrizione = agende_descrizione.merge(df_chart_1, on ='Codice Agenda', how='left')
    urg_aderenza = codice_descrizione.merge(grado_di_urgenza, on ='Codice Agenda', how='left')
    df_data_agende = urg_aderenza.merge(df_sentinella, on ='Codice Agenda', how='left')
    """df_data_agende['Grado di aderenza al grafo*'] *= 100
    df_data_agende['Grado di urgenza**'] *= 100
    df_data_agende['Grado di aderenza al grafo*'] = df_data_agende['Grado di aderenza al grafo*'].apply(lambda x: '-' if pd.isna(x) else f"{x:.0f}%")
    df_data_agende['Grado di urgenza**'] = df_data_agende['Grado di urgenza**'].apply(lambda x: '-' if pd.isna(x) else f"{x:.0f}%")"""
    ## EXPORT DEL DF data_agende per poterlo manipolare
    #df_data_agende.to_excel(os.path.join(root_path, 'data_agende.xlsx'))
    
    ## Aggiunta ambito 
    
    print('---FINEE FUNZIONE---')
    
    return df_data_agende
def data_prima_disponibilita(df_prima_disponibilita):
    ## Funzione per la creazione df prima disponibilità pagina 4 tables righetto in basso
    df_prima_disponibilita['Data_Prima_disponibilita'] = pd.to_datetime(df_prima_disponibilita['Data_Prima_disponibilita'])
    # Convert the datetime column to the desired format
    df_prima_disponibilita['Data_Prima_disponibilita'] = df_prima_disponibilita['Data_Prima_disponibilita'].dt.strftime('%d-%m-%Y')
    df_prima_disponibilita_df = df_prima_disponibilita.reset_index()
    df_prima_disponibilita_df.rename(columns = {'id_agenda':'Codice Agenda'},inplace=True)
    
    return df_prima_disponibilita_df
def direttore_ore_ambulatorio(ore_ambulatorio_da_aggiungere):
    ## Funzione per creazione df ore ambulatorio da aggiungere 
    
    ore_ambulatorio_da_aggiungere['Giorno'] = pd.to_datetime(ore_ambulatorio_da_aggiungere['Giorno'])
    # Aggiungere una colonna 'mese' per facilitare il raggruppamento
    ore_ambulatorio_da_aggiungere['mese'] = ore_ambulatorio_da_aggiungere['Giorno'].dt.to_period('M')
    # Ordinare il DataFrame per 'mese' e 'giorno'
    ore_ambulatorio_da_aggiungere_sorted =  ore_ambulatorio_da_aggiungere.sort_values(by=['mese', 'Re_cluster', 'Giorno'])
    # Prendere l'ultima riga di ogni gruppo 'mese'
    ore_ambulatorio_da_aggiungere_last_day = ore_ambulatorio_da_aggiungere_sorted.groupby(['mese', 'Re_cluster']).tail(1).reset_index(drop=True)
    ore_ambulatorio_da_aggiungere_last_day['Re_cluster'] = ore_ambulatorio_da_aggiungere_last_day['Re_cluster'].astype('str')
    decodifica_descrizione.rename(columns = {'Re-clustering' : 'Re_cluster'},inplace=True)
    ore_ambulatorio_da_aggiungere_group_2 = ore_ambulatorio_da_aggiungere_last_day.merge(decodifica_descrizione, on ='Re_cluster', how='left')
    ore_ambulatorio_da_aggiungere_group_2['ora'] = ore_ambulatorio_da_aggiungere_group_2['minuti_ritardo']/60
    # Dizionario per la conversione dei mesi
    month_map = {
        '01': "Gennaio",
        '02': "Febbraio",
        '03': "Marzo",
        '04': "Aprile",
        '05': "Maggio",
        '06': "Giugno",
        '07': "Luglio",
        '08': "Agosto",
        '09': "Settembre",
        '10': "Ottobre",
        '11': "Novembre",
        '12': "Dicembre"
    }
    ## Funzione per creare la colonna desc_mese
    def convert_month_year(mese):
        year, month = mese.split('-')
        return f"{month_map[month]}'{year[-2:]}"
    # Applicare la funzione a ogni elemento della colonna 'mese'
    ore_ambulatorio_da_aggiungere_group_2['mese_2'] = ore_ambulatorio_da_aggiungere_group_2['mese'].astype('str')
    ore_ambulatorio_da_aggiungere_group_2['desc_mese'] = ore_ambulatorio_da_aggiungere_group_2['mese_2'].apply(convert_month_year)
    ore_ambulatorio_da_aggiungere_group_2['ora'] = ore_ambulatorio_da_aggiungere_group_2['ora'].astype('int32')
    ore_ambulatorio_da_aggiungere_group_2['ora'] = round(ore_ambulatorio_da_aggiungere_group_2['ora'],0)
    ordered_months = [
        "Gennaio'24", "Febbraio'24", "Marzo'24", "Aprile'24", "Maggio'24", "Giugno'24",
        "Luglio'24", "Agosto'24", "Settembre'24", "Ottobre'24", "Novembre'24", "Dicembre'24",
            "Gennaio'25", "Febbraio'25", "Marzo'25", "Aprile'25", "Maggio'25", "Giugno'25",
        "Luglio'25", "Agosto'25", "Settembre'25", "Ottobre'25", "Novembre'25", "Dicembre'25",
    ]
    ## Trasposizione del DataFrame per rendere i mesi colonne
    df_transposed = ore_ambulatorio_da_aggiungere_group_2.pivot(index=['Re_cluster', 'Descrizione post-Reclustering'], columns='desc_mese', values = 'ora')
    ## Resetta l'indice per ottenere un DataFrame classico
    df_transposed.reset_index(inplace=True)
    df_transposed = df_transposed.reindex(columns=['Re_cluster', 'Descrizione post-Reclustering']  + [col for col in ordered_months if col in df_transposed.columns])
    for col in df_transposed.columns[2:]:
        df_transposed[col] = pd.to_numeric(df_transposed[col], errors='coerce').astype('Int64')
        
    df_transposed.rename(columns = {'Descrizione post-Reclustering' : 'Tipo di ambulatorio/attrezzatura' }, inplace=True)
    data_tabella_ore_di_ambulatorio =  df_transposed.drop('Re_cluster',axis=1)
    data_tabella_ore_di_ambulatorio = data_tabella_ore_di_ambulatorio[(data_tabella_ore_di_ambulatorio['Tipo di ambulatorio/attrezzatura'] != 'DIALISI') & (data_tabella_ore_di_ambulatorio['Tipo di ambulatorio/attrezzatura'] != 'PACC')]
    return data_tabella_ore_di_ambulatorio
    
def direttore_tabella_tempi_di_attesa(reclustering_tempi_attesa,df_prima_disponibilita_reclusterizzato,start_date,end_date): #modifiche Antonio
    ## Funzione per creazione tabella tempi di attesa reclustering 
    start_date = pd.to_datetime(start_date)
    end_date = pd.to_datetime(end_date)
    decodifica_descrizione = re_cluster[['Re-clustering','Descrizione post-Reclustering']].drop_duplicates()
    prenot['codice_priorità'].replace(['D1','D2'], ['D','D'],inplace = True)
    prenot_solo_prenotato = prenot[prenot['stato_appuntamento'] == 'Prenotata']
    decodifica_descrizione = re_cluster[['Re-clustering','Descrizione post-Reclustering']].drop_duplicates()
    decodifica_recluster = re_cluster[['Codice Catalogo','Descrizione Catalogo','Re-clustering']]
    decodifica_recluster.rename(columns = {'Codice Catalogo' : 'codice_prestazione_cur'},inplace=True)
    ## Rimuovi duplicati
    decodifica_recluster = decodifica_recluster.drop_duplicates()
    ## Aggiungi informazioni recluster
    prenot_with_recluster = prenot_solo_prenotato.merge(decodifica_recluster[['codice_prestazione_cur','Re-clustering']], on = 'codice_prestazione_cur', how ='left')
    # Calcola differenza giorni 
    prenot_with_recluster['data_diff'] = prenot_with_recluster['d_data_appuntame'] - prenot_with_recluster['d_data_contatto']
    prenot_with_recluster['Re-clustering'] = prenot_with_recluster['Re-clustering'].astype('str')
    ## Ragguppamento media su prio- re_cluster e 
    tab_recluster = prenot_with_recluster.groupby(['ambiti_aziende','Re-clustering','codice_priorità'])['data_diff'].mean().reset_index(name = 'Attuale')
    # convertito in giorni 
    tab_recluster['Attuale'] = tab_recluster['Attuale'].dt.days
    #decodifica_descrizione['Re-clustering'] =decodifica_descrizione['Re-clustering'].astype('str')
    #tab_recluster_fin = tab_recluster.merge(decodifica_descrizione, on = 'Re-clustering', how ='left')
    ## NON TUTTO ENTRA NEL RECLUSTER QUINDI ALCUNI GRUPPI DI PRESTAZIONI NON HANNO RECLUSTER 
    #recluster_attuale = tab_recluster_fin[tab_recluster_fin['Re-clustering'] != 'nan']
    tab_recluster.rename(columns = {'ambiti_aziende': 'ambito','codice_priorità': 'Priorità'},inplace=True)
    attesa_2_new = reclustering_tempi_attesa.rename(columns ={'classe_priorita' : 'Priorità' ,'attesa_media' : 'Post-Riconfigurazione'})
    attesa_2_new['Post-Riconfigurazione'] = attesa_2_new['Post-Riconfigurazione'].astype('int32')
    #MERGE STATO ATTUALE CON POST-RICONFIGURAZIONE 
    total_finale = attesa_2_new[['Re-clustering','Priorità','Post-Riconfigurazione','ambito']].merge(tab_recluster, on =['Re-clustering','Priorità','ambito'], how ='left')
    decodifica_descrizione['Re-clustering'] =decodifica_descrizione['Re-clustering'].astype('str')
    ## ELIMINIAMO I RECLUSTER CHE NON HANNO RICONFIGURAZIONE 
    total_finale_desc = total_finale.merge(decodifica_descrizione, on = 'Re-clustering', how ='left')
    not_null = total_finale_desc[total_finale_desc['Post-Riconfigurazione'].notnull()]
    not_null['Post-Riconfigurazione'] = not_null['Post-Riconfigurazione'].astype('int32')
    b = not_null.rename(columns = {'Descrizione post-Reclustering': 'Gruppi di prestazioni','Priorità' : 'Classi di Priorità'})
    recluster_tempi_attesa_fin = b[['Re-clustering','ambito','Gruppi di prestazioni','Classi di Priorità' ,'Attuale' ,'Post-Riconfigurazione']]
    df_prima_disponibilita_reclusterizzato = df_prima_disponibilita_reclusterizzato.reset_index()
    df_prima_disponibilita_reclusterizzato.rename(columns = {'Re_cluster':'Re-clustering'},inplace=True)
    
    merge_recluster_final = recluster_tempi_attesa_fin.merge(df_prima_disponibilita_reclusterizzato, on =['Re-clustering','ambito'], how ='left')
    def calculate_month_diff(start_date, end_date):
        if pd.isna(end_date):
            return 'Non efficientabile nel periodo selezionato'
        diff_anni =  end_date.year - start_date.year 
        diff_mesi =  end_date.month +1 -start_date.month 
        total_months = diff_anni * 12 + diff_mesi 
        if total_months == 1: 
            return "1 mese"
        else:
            return f"{total_months} mesi"
    merge_recluster_final['Data_Prima_disponibilita'] = pd.to_datetime(merge_recluster_final['Data_Prima_disponibilita'])
    merge_recluster_final['Effetti della riconfigurazione a partire da*'] = merge_recluster_final['Data_Prima_disponibilita'].apply(lambda x: calculate_month_diff(start_date, x))
    merge_recluster_final_2 = merge_recluster_final[['Gruppi di prestazioni',
       'Classi di Priorità', 'Attuale', 'Post-Riconfigurazione',
       'Effetti della riconfigurazione a partire da*']]
    ### FILTRO NO PACC E DIALISI
    merge_recluster_final_2 = merge_recluster_final_2[(merge_recluster_final_2['Gruppi di prestazioni'] != 'PACC') & (merge_recluster_final_2['Gruppi di prestazioni'] != 'DIALISI')]
    return merge_recluster_final_2
def transform_4_tables(calendar_agende,recap_post_riconfigurazione,tempi_attesa_post_riconfigurazione,start_date,end_date):
    ## Funzione per creazione 4 tables 
    
    calendar_agende['CALENDAR_DATE'] = pd.to_datetime(calendar_agende['CALENDAR_DATE'])
    # Filtriamo il DataFrame per includere solo le righe con date nel range specificato
    calendar_agende = calendar_agende[(calendar_agende['CALENDAR_DATE'] >= start_date) & (calendar_agende['CALENDAR_DATE'] <= end_date)]
    #calendar_agende['SLOT_SIZE'] = pd.to_numeric(calendar_agende['SLOT_SIZE'])
    calendar_agende['FREE_SLOTS'] = pd.to_numeric(calendar_agende['FREE_SLOTS'])
    ## Slot Attuale
    calendar_agende['Slot_Attuale'] =  calendar_agende['FREE_SLOTS']
    # Raggruppa il DataFrame per la colonna 'DIARY_ID' e somma solo i valori della colonna 'PRODUCT'
    recap_stato_attuale_elab = calendar_agende.groupby('DIARY_ID')['Slot_Attuale'].sum().reset_index()
    recap_stato_attuale_elab['DIARY_ID'] = recap_stato_attuale_elab['DIARY_ID'].astype('str')
    recap_stato_attuale_elab.rename(columns = {'DIARY_ID' : 'Codice Agenda'},inplace=True)
    ### ******** ATTENZIONA QUESTO PUNTO *******
    #priorita.groupby('DIARY_ID')['CLASSI_PRIORITA'].nunique().reset_index().query('CLASSI_PRIORITA >1')
    decod_prio = priorita.groupby('DIARY_ID')['CLASSI_PRIORITA'].first().reset_index()
    decod_prio.CLASSI_PRIORITA.fillna(value='BDUP', inplace=True)
    decod_prio['DIARY_ID'] = decod_prio['DIARY_ID'].astype('str')
    decod_prio.rename(columns = {'DIARY_ID' : 'Codice Agenda'},inplace=True)
    recap_stato_attuale_elab = recap_stato_attuale_elab.merge(decod_prio,on ='Codice Agenda', how ='left')
    recap_stato_attuale_elab.rename(columns = {'CLASSI_PRIORITA' : 'Priorità'},inplace=True)
    # Funzione per esplodere i caratteri in righe separate mantenendo le altre colonne
    def esplodi_caratteri(df, colonna):
        exploded_df = df[colonna].apply(list).explode()
        repeated_columns = df.drop(columns=[colonna]).loc[df.index.repeat(df[colonna].str.len())].reset_index(drop=True)
        result_df = pd.concat([exploded_df.reset_index(drop=True), repeated_columns], axis=1)
        result_df.columns = [colonna] + list(repeated_columns.columns)  
        
        return result_df
    # Applichiamo la funzione al DataFrame
    recap_stato_attuale = esplodi_caratteri(recap_stato_attuale_elab, 'Priorità')
    recap_stato_attuale.replace(['U','B','D','P'],['Urgente','Breve','Differibile','Programmata'],inplace =True)
    ## RECAP - POST-RICONFIGURAZIONE  ##df_daily_requests_in_minutes_per_agend
    recap_post_riconfigurazione_renamed = recap_post_riconfigurazione.rename(columns = {'Slot_assegnati_U' : 'Urgente' ,'Slot_assegnati_B' : 'Breve', 'Slot_assegnati_D' :'Differibile','Slot_assegnati_P' : 'Programmata','Codice_agenda' : 'Codice Agenda'  })
    asseganzione_melted = recap_post_riconfigurazione_renamed.melt(id_vars=['Giorno', 'ambito', 'Codice Agenda'],
                        value_vars=['Urgente', 'Breve', 'Differibile', 'Programmata'],
                        var_name='Priorità',
                        value_name='Slot_Riconfigurazione')
    asseganzione_melted['Giorno'] = pd.to_datetime(asseganzione_melted['Giorno'])
    recap_post_riconfigurazione_group = asseganzione_melted.groupby(['Codice Agenda','Priorità'])['Slot_Riconfigurazione'].sum().reset_index(name='Slot_Riconfigurazione')
    recap_post_riconfigurazione_group['Codice Agenda'] = recap_post_riconfigurazione_group['Codice Agenda'].astype('str')
    ## TEMPI MEDI STIMATI - ATTUALE   ## da dataframe 
    ## INTEGRA
    #prenot = pd.read_parquet(r'C:\Users\lucamartino\OneDrive - KPMG\So.Re.Sa\Data Ingestion\CUP_Forecast\cup_prenotato_cleaned_wave_1.parquet')
    prenot['codice_priorità'].replace(['D1','D2'], ['D','D'],inplace = True)
    prenot_solo_prenotato = prenot[prenot['stato_appuntamento'].isin(['Prenotata'])]
    prenot_solo_prenotato['Giorni_Attuale'] = (prenot_solo_prenotato['d_data_appuntame'] - prenot_solo_prenotato['d_data_contatto']).dt.days
    tempi_medi_attuale = prenot_solo_prenotato.groupby(['codice_agenda', 'codice_priorità']).agg({
        'Giorni_Attuale': 'mean'  
    }).reset_index()
    tempi_medi_attuale['Giorni_Attuale'] = tempi_medi_attuale['Giorni_Attuale'].round(0).astype(int)
    tempi_medi_attuale.replace(['U','B','D','P'],['Urgente','Breve','Differibile','Programmata'],inplace =True)
    tempi_medi_attuale.rename(columns = {'codice_agenda' : 'Codice Agenda', 'codice_priorità' : 'Priorità'}, inplace=True)
    ## TEMPI MEDI STIMATI - POST-RICONFIGURAZIONE   ##df_attesa_agenda_eapoc_combined
    tempi_attesa_post_riconfigurazione['classe_priorita'].replace(['P','U','B','D'], ['Programmata','Urgente','Breve','Differibile'],inplace=True)
    tempi_attesa_post_riconfigurazione_ren = tempi_attesa_post_riconfigurazione.rename(columns ={'codice_agenda' : 'Codice Agenda','classe_priorita' : 'Priorità' ,'attesa_media' : 'Giorni_Riconfigurazione'})
    tempi_attesa_post_riconfigurazione_ren['Giorni_Riconfigurazione'] = tempi_attesa_post_riconfigurazione_ren['Giorni_Riconfigurazione'].astype('int32')
    tempi_attesa_post_riconfigurazione_ult = tempi_attesa_post_riconfigurazione_ren[['Codice Agenda','Priorità','Giorni_Riconfigurazione']]
    ## JOIN FINALE PER OUTPUT 
    recap_tot = recap_stato_attuale.merge(recap_post_riconfigurazione_group, on =['Codice Agenda','Priorità'], how ='outer')
    tempi_tot = tempi_medi_attuale.merge(tempi_attesa_post_riconfigurazione_ult, on =['Codice Agenda','Priorità'], how ='outer')
    all_4_tables = recap_tot.merge(tempi_tot, on =['Codice Agenda','Priorità'], how ='outer')
    return all_4_tables
@app.callback(
    [Output('store-df1', 'data'),
     Output('store-df2', 'data'),
     Output('store-df3', 'data'),
     Output('store-df4', 'data'),
     Output('store-df5', 'data'),
     Output('store-df6', 'data'),
     Output('url-redirect', 'pathname')],
    [Input('button-avanti', 'n_clicks'),
    Input('start-day-dropdown', 'value'),
    Input('start-month-dropdown', 'value'),
    Input('start-year-dropdown', 'value'),
    Input('end-day-dropdown', 'value'),
    Input('end-month-dropdown', 'value'),
    Input('end-year-dropdown', 'value'),
    Input('button-2-clicks-store', 'data'),
    Input('button-1-clicks-store', 'data'),
    Input('initial-dropdown', 'value'), ### ambito per filtro df_agende legato al file caricato
    Input('upload-data', 'contents'),   ## codice_agenda per filtro df_agende legato al file_caricato
    State('upload-data', 'filename')    ## codice_agenda per filtro df_agende legato al file_caricato
    
    ]
    )
def generate_data_and_redirect(n_clicks,startday,startmonth,startyear,endday,endmonth,endyear,button_2_clicks,button_1_clicks,ambito_filtro,contents,filename):
    
    if contents is not None:
        df_filtro_file = parse_contents(contents, filename)
        print('Filtro file', df_filtro_file)
        if df_filtro_file is not None and 'CODICE_AGENDA' in df_filtro_file.columns:
            unique_values = df_filtro_file['CODICE_AGENDA'].unique()
            unique_values = unique_values.astype(str)
            print('Valori unici da utilizzare ??', unique_values)
    if n_clicks is None:
        raise PreventUpdate
    datainiziale = str(startyear) + '-' + str(startmonth) + '-' + str(startday)
    datainiziale_dt = pd.to_datetime(datainiziale).date()
    datainiziale = str(datainiziale_dt)
    datafinale = str(endyear) + '-' + str(endmonth) + '-' + str(endday)
    datafinale_dt = pd.to_datetime(datafinale).date()
    datafinale = str(datafinale_dt)
    print(datainiziale)
    print(datafinale)
    def main_new(ambito,data_iniziale,data_finale,lista_agende,df_agende_unique_fe): 
            start_day = '2024-01-01'         #### NOTE_PER_AGGIORNAMENTO -> questa data dovra essere allineata con il today dello smart scheduler
            if ambito == 'ASL Caserta A01': 
                scheduler = scheduler_a01
            elif ambito == 'ASL Caserta A02':
                scheduler = scheduler_a02
            elif ambito == 'ASL Caserta A03':
                scheduler = scheduler_a03
            elif ambito == 'ASL Caserta A04':
                scheduler = scheduler_a04
        #    #strutture_private_accreditate_uniche = get_strutture_private_accreditate_uniche(input_path=SCHEDULER_ABSOLUTE_PATH+r"/INPUT/", output_path=SCHEDULER_ABSOLUTE_PATH+f"/OUTPUT/{ambito}")
        #    #df_cluster_descrizione = get_durata_slot_assegnato_cluster(dict_duration_cluster, df_cluster, input_path=SCHEDULER_ABSOLUTE_PATH+r"/INPUT/", output_path=SCHEDULER_ABSOLUTE_PATH+f"/OUTPUT/{ambito}")
        #    df_prima_disponibilita = create_dataframe_prime_date_disponibili_agende(scheduler.agenda_manager.agende_senza_fascie, data_iniziale, output_path=SCHEDULER_ABSOLUTE_PATH+f"/OUTPUT/{ambito}")
        #    prima_data_disponibile_int = convert_df_prima_data_disponibile_a_int(df_prima_disponibilita)
        #    all_processes_filtered = filter_all_processes(scheduler.queue_manager.all_processes, start_day, data_iniziale, data_finale)
        #    agende_associate_set = get_agende_utilizzate(all_processes=scheduler.queue_manager.all_processes)
        #    #file_c_df = read_file_c(cartella_file_sorgente = SCHEDULER_ABSOLUTE_PATH+r"/INPUT/")
        #    df_cup_eapoc_filtered_date = filter_df_date_init_fine(df_cup_eapoc, 'd_data_appuntame', data_iniziale, data_finale)
        #    df_prima_disponibilita_recluster = compute_first_available_date_agende_reclusterizzate(df_cup_eapoc_filtered_date, df_prima_disponibilita, ambito, flag_to_save=False, output_path=SCHEDULER_ABSOLUTE_PATH+f"/OUTPUT/{ambito}")
        #    df_cup_eapoc_filtered_agenda = filter_and_compute_attesa_eapoc(df_cup_eapoc_filtered_date.copy(deep=True), agende_associate_set, df_prima_disponibilita, data_iniziale, 'codice_agenda')
        #    df_cup_eapoc_filtered_recluster = filter_and_compute_attesa_eapoc(df_cup_eapoc_filtered_date.copy(deep=True), agende_associate_set, df_prima_disponibilita_recluster, data_iniziale, 'Re-clustering')
        #    #df_attesa_media_recluster_eaprec = process_cup_ex_ante_pre_configurazione(df_reclustering, agende_associate_set, ambito, output_path=SCHEDULER_ABSOLUTE_PATH+f"/OUTPUT/{ambito}", input_path=SCHEDULER_ABSOLUTE_PATH+r"/INPUT/")
        #    #df_attesa_media_recluster_ex_post_preconfigurazione = process_cup_erogato_ex_post_preconfigurazione(agende_associate_set, df_reclustering, ambito, output_path=SCHEDULER_ABSOLUTE_PATH+f"/OUTPUT/{ambito}", input_path=SCHEDULER_ABSOLUTE_PATH+r"/INPUT/")
        #    # eapoc: ex-ante post-reconfigurazione. eaprec: ex-ante pre-reconfigurazione. epprec: ex-post pre-reconfigurazione. 
        #    df_attesa_reclustering_eapoc_combined = process_tempi_medi_stimati_recluster_eapoc(all_processes_filtered, df_cup_eapoc_filtered_recluster, df_reclustering, df_prima_disponibilita_recluster, ambito, start_day, flag_to_save=True, output_path=SCHEDULER_ABSOLUTE_PATH+f"/OUTPUT/{ambito}")
        #    df_attesa_agenda_eapoc_combined = process_tempi_medi_stimati_agenda_attesa_post_riconfigurazione(all_processes_filtered, df_cup_eapoc_filtered_agenda, ambito, flag_to_save=False, output_path=SCHEDULER_ABSOLUTE_PATH+f"/OUTPUT/{ambito}")
        #    df_daily_requests_in_minutes_per_agend = process_assegnazioni_giornaliere_per_agenda(scheduler.agenda_manager.agende_senza_fascie, calendar_fe, start_day, ambito, data_iniziale, data_finale, flag_to_save=False, output_path=SCHEDULER_ABSOLUTE_PATH+f"/OUTPUT/{ambito}")
        #    df_reclustering_ritardi = process_ore_ambulatoriali(scheduler.queue_manager.storico_queue, df_cluster, df_reclustering, start_day, ambito, data_iniziale, data_finale, flag_to_save=False, output_path=SCHEDULER_ABSOLUTE_PATH+f"/OUTPUT/{ambito}")
        #    return df_attesa_reclustering_eapoc_combined, df_attesa_agenda_eapoc_combined, df_daily_requests_in_minutes_per_agend, df_reclustering_ritardi, df_prima_disponibilita,df_prima_disponibilita_recluster 
        
            print('debug', df_agende_unique_fe)
            dict_duration_cluster = filter_clusters(df_agende_unique_fe, df_cluster, ambito, lambda x: x.mode().values[0])
            strutture_private_accreditate_uniche = get_strutture_private_accreditate_uniche(input_path=SCHEDULER_ABSOLUTE_PATH+r"/INPUT/", output_path=SCHEDULER_ABSOLUTE_PATH+f"/OUTPUT/{ambito}")
            df_cluster_descrizione = get_durata_slot_assegnato_cluster(dict_duration_cluster, df_cluster, input_path=SCHEDULER_ABSOLUTE_PATH+r"/INPUT/", output_path=SCHEDULER_ABSOLUTE_PATH+f"/OUTPUT/{ambito}")
            all_processes_filtered = filter_all_processes(scheduler.queue_manager.all_processes, start_day, data_iniziale, data_finale)
            agende_output_filtered = filter_agende_output(scheduler.agenda_manager.agende_senza_fascie, lista_agende)
            try:
                assert len(agende_output_filtered) > 0
            except:
                print("Selezionare agende da filtrare valide, controllare l'ambito.")
                agende_output_filtered = scheduler.agenda_manager.agende_senza_fascie
            agende_associate_set = get_agende_utilizzate(all_processes=scheduler.queue_manager.all_processes)
            df_prima_disponibilita = create_dataframe_prime_date_disponibili_agende(agende_output_filtered, data_iniziale, output_path=SCHEDULER_ABSOLUTE_PATH+f"/OUTPUT/{ambito}")
            prima_data_disponibile_int = convert_df_prima_data_disponibile_a_int(df_prima_disponibilita)
            #df_reclustering = load_reclustering_data(file_path=SCHEDULER_ABSOLUTE_PATH+r"/INPUT/", file_name="Re-clustering_Spec_Fa.Re_v.2.0_20240624.xlsx", sheet_name='Spectral_clustering_600_limited')
            df_agende_unique_fe = add_recluster_column_to_df(df_agende_unique_fe, df_reclustering, cod_prestazione_nome_col='SERVICE_REGIONAL_CODE')
            df_prima_disponibilita_recluster = compute_first_available_date_agende_reclusterizzate(df_agende_unique_fe, df_prima_disponibilita, ambito, flag_to_save=False, output_path=SCHEDULER_ABSOLUTE_PATH+f"/OUTPUT/{ambito}")
            df_cup_eapoc_filtered_date = filter_df_date_init_fine(df_cup_eapoc, 'd_data_appuntame', data_iniziale, data_finale)
            df_cup_eapoc_filtered_agenda = filter_and_compute_attesa_eapoc(df_cup_eapoc_filtered_date.copy(deep=True), agende_associate_set, df_prima_disponibilita, data_iniziale, 'codice_agenda')
            df_cup_eapoc_filtered_recluster = filter_and_compute_attesa_eapoc(df_cup_eapoc_filtered_date.copy(deep=True), agende_associate_set, df_prima_disponibilita_recluster, data_iniziale, 'Re-clustering')
            #df_attesa_media_recluster_eaprec = process_cup_ex_ante_pre_configurazione(df_reclustering, agende_associate_set, ambito, output_path=SCHEDULER_ABSOLUTE_PATH+f"/OUTPUT/{ambito}", input_path=SCHEDULER_ABSOLUTE_PATH+r"/INPUT/")
            #df_attesa_media_recluster_ex_post_preconfigurazione = process_cup_erogato_ex_post_preconfigurazione(agende_associate_set, df_reclustering, ambito, output_path=SCHEDULER_ABSOLUTE_PATH+f"/OUTPUT/{ambito}", input_path=SCHEDULER_ABSOLUTE_PATH+r"/INPUT/")
            # eapoc: ex-ante post-reconfigurazione. eaprec: ex-ante pre-reconfigurazione. epprec: ex-post pre-reconfigurazione. 
            df_attesa_reclustering_eapoc_combined = process_tempi_medi_stimati_recluster_eapoc(all_processes_filtered, df_cup_eapoc_filtered_recluster, df_reclustering, df_prima_disponibilita_recluster, ambito, start_day, flag_to_save=False, output_path=SCHEDULER_ABSOLUTE_PATH+f"/OUTPUT/{ambito}")
            df_attesa_agenda_eapoc_combined = process_tempi_medi_stimati_agenda_attesa_post_riconfigurazione(all_processes_filtered, df_cup_eapoc_filtered_agenda, ambito, flag_to_save=False, output_path=SCHEDULER_ABSOLUTE_PATH+f"/OUTPUT/{ambito}")
            df_daily_requests_in_minutes_per_agend = process_assegnazioni_giornaliere_per_agenda(agende_output_filtered, calendar_fe, start_day, ambito, data_iniziale, data_finale, flag_to_save=False, output_path=SCHEDULER_ABSOLUTE_PATH+f"/OUTPUT/{ambito}")
            df_reclustering_ritardi = process_ore_ambulatoriali(scheduler.queue_manager.storico_queue, df_cluster, df_reclustering, start_day, ambito, data_iniziale, data_finale, flag_to_save=False, output_path=SCHEDULER_ABSOLUTE_PATH+f"/OUTPUT/{ambito}")
            return (df_attesa_reclustering_eapoc_combined,
                    df_attesa_agenda_eapoc_combined,
                    df_daily_requests_in_minutes_per_agend,
                    df_reclustering_ritardi,
                    df_prima_disponibilita,
                    df_prima_disponibilita_recluster)
                
        
        
        ## LANCIO FUNZIONE
    (df_attesa_reclustering_eapoc_combined, ##direttore tabella 1
        df_attesa_agenda_eapoc_combined,       ## 4 tables -post riconfigurazione tempi attesa
        df_daily_requests_in_minutes_per_agend,## 4tables -post riconfigurazione slot 
        df_reclustering_ritardi,              ## direttore ore di ambulatorio
        df_prima_disponibilita,               ## prima disp
        df_prima_disponibilita_reclusterizzato)  = main_new(ambito=ambito_filtro,
            data_iniziale = datainiziale,
            data_finale = datafinale,
            lista_agende = unique_values,
            df_agende_unique_fe=df_agende_unique_fe)
        #print(df_prima_disponibilita_reclusterizzato)
        # ## SOLO PER DIRETTORE
        # ## FILTRO
 ## OPERATORE
    df_attesa_agenda_eapoc_combined=df_attesa_agenda_eapoc_combined[df_attesa_agenda_eapoc_combined['solo_cup'] != True]
        
        
    four_tables = transform_4_tables(calendar_agende,df_daily_requests_in_minutes_per_agend,df_attesa_agenda_eapoc_combined,start_date=datainiziale,end_date=datafinale)
    df_prima_disponibilita_df = data_prima_disponibilita(df_prima_disponibilita)
    df_data_agende = data_agende_home(df_prima_disponibilita_df,start_date=datainiziale,end_date=datafinale)
    decodifica_agende_ambito['Codice Agenda'] = decodifica_agende_ambito['Codice Agenda'].astype('str')
    df_data_agende['Codice Agenda'] = df_data_agende['Codice Agenda'].astype('str')
    df_data_agende_con_ambito = df_data_agende.merge(decodifica_agende_ambito, on = 'Codice Agenda', how='left')
    df_data_agende_filtered = df_data_agende_con_ambito[(df_data_agende_con_ambito['AMBITI_AZIENDE'] == ambito_filtro) &(df_data_agende_con_ambito['Codice Agenda'].isin(unique_values)) ]
    df_data_agende_filtered = df_data_agende_filtered[['Codice Agenda','Descrizione Agenda','Grado di aderenza al grafo*','Grado di urgenza**','Sentinella']]
    four_tables_dict = four_tables.to_dict('records') if four_tables is not None else None
    df_prima_disponibilita_dict = df_prima_disponibilita_df.to_dict('records') if df_prima_disponibilita_df is not None else None
    df_data_agende_dict = df_data_agende_filtered.to_dict('records') if df_data_agende_filtered is not None else None
    df_daily_requests_in_minutes_per_agend_dict = df_daily_requests_in_minutes_per_agend.to_dict('records') 
        #return four_tables,direttore_reclustering_tempi_attesa,direttore_ore_ambulatorio_df,df_prima_disponibilita_2,df_data_agende,'/agende'
    return four_tables_dict,None,None,df_daily_requests_in_minutes_per_agend_dict,df_prima_disponibilita_dict,df_data_agende_dict,'/agende'   
@app.callback(
    Output('selected-ambiti-store', 'data'),
    Input('initial-dropdown', 'value')
)
def update_selected_code(selected_values):
    if selected_values:
        return selected_values
    else:
        return []
    
    
### Visualizzza l 'area territoriale scelta nella pagina del direttore non è un filtro    
@app.callback(
    Output('dropdown-menu', 'value'),
    Input('selected-ambiti-store', 'data')
)
def update_textarea(selected_value):
    if selected_value:
        print(selected_value)
        dizionario_decodifica = {"ASL Caserta A01" : "Caserta 01",
                                 "ASL Caserta A02" : "Caserta 02",
                                 "ASL Caserta A03" : "Caserta 03",
                                 "ASL Caserta A04" : "Caserta 04"}
        return dizionario_decodifica[selected_value]
    else:
        return ''
   
    
#@app.callback(
#    Output('dropdown-menu', 'options'),
#    Input('selected-ambiti-store', 'data')
#)
#def update_dropdown(selected_values):
#    if selected_values:
#        return [{'label': value_to_label[item['value']], 'value': item['value']} for item in selected_values]
#    else:
#        return [
#            {'label': 'Provincia di Caserta', 'value': 'ASL Caserta A04'},
#            {'label': 'Provincia di Caserta 1', 'value': 'ASL Caserta 1'},
#            {'label': 'Provincia di Caserta 2', 'value': 'ASL Caserta 2'},
#            {'label': 'Provincia di Caserta 3', 'value': 'ASL Caserta 3'}
#        ]
### LEGGE I VALORI UNICI ELLA COLONNA AMBITO E SI CREA LO STORE DELLE OPZIONI POSSIBILI
## 
def parse_contents(contents, filename):
    content_type, content_string = contents.split(',')
    decoded = base64.b64decode(content_string)
    try:
        if 'xls' in filename:
            # Usare pd.read_excel per leggere file excel
            df = pd.read_excel(io.BytesIO(decoded))
        elif 'csv' in filename:
            # Usare pd.read_csv per leggere file csv
            df = pd.read_csv(io.StringIO(decoded.decode('utf-8')))
        else:
            return None
    except Exception as e:
        print(e)
        return None
    return df
### DROPDOWN DELLA PRIMA PAGINA HOME PUNTO 3)
@app.callback(
    [Output('initial-dropdown', 'options'),
     Output('output-data-upload', 'children')],
    [Input('upload-data', 'contents')],
    [State('upload-data', 'filename'),
     State('initial-options', 'data')]
)
def update_output(contents, filename, stored_options):
    if contents is not None:
        df = parse_contents(contents, filename)
        # Effettua il join mantenendo solo le colonne desiderate
        
        df_joined = pd.merge(df, merged_df, 
                             on=['CODICE_AGENDA', 'CODICE_EROGANTE', 'STS11'], 
                             how='left')
        print("------------CHECK PRINT--------------------")
        print(df_joined)
        if df_joined is not None and 'AMBITI_AZIENDE' in df_joined.columns:
            unique_values = df_joined['AMBITI_AZIENDE'].unique()
            options = [{'label': val, 'value': val} for val in unique_values]
            dizionario_decodifica = {"ASL Caserta A01" : "Caserta 01",
                                 "ASL Caserta A02" : "Caserta 02",
                                 "ASL Caserta A03" : "Caserta 03",
                                 "ASL Caserta A04" : "Caserta 04"}
            options_provincia = [{'label': dizionario_decodifica[val], 'value': val} for val in unique_values]
            options_provincia_sorted = sorted(options_provincia, key=lambda x: x['label'])
            return options_provincia_sorted, ''
        else:
            return [], 'File in formato errato.'
    elif stored_options is not None:
        return stored_options, ''
    return [], ''
### SI RICORDA L AMBITO CHE HAI SALVATO 
@app.callback(
    Output('initial-options', 'data'),
    Input('initial-dropdown', 'options')
)
def remember_options(options):
    return options
### QUANDO TORNA INDIETRO SI RICORDA CHE IL FILE E'STATO CARICATO
@app.callback(
    Output('name', 'data'),
    Input('upload-container', 'children')
)
def remember_options(value):
    return value
### FA USCIRE LA SCRITTA NOME DEL FILE QUANDO FAI L UPLOAD 
@app.callback(
    Output('upload-container', 'children'),
    [Input('upload-data', 'contents')],
    [State('upload-data', 'filename'),
     State('name', 'data')]
)
def update_output(contents, filename, saved_filename):
    if contents is not None:
        return html.Div([
                    html.Br(),
                    html.Img(src='/assets/load_file.png', style={'height': '30px'}),
                    html.Div(filename, style={'textAlign': 'center', 'font-size': '14px', 'marginTop': '5px','margin-left':'10px','font-weight':'bold','textDecoration': 'underline'})
                ], style={'display': 'flex','alignItems': 'center','height':'50px'})
    #elif saved_filename is not None:
    #    return 
    #saved_filename
    return html.Div([
                html.Img(src='/assets/load_file.png', style={'height': '30px'}),
                html.Div('Trascina o seleziona il file', id='upload-text', style={'textAlign': 'center', 'font-size': '14px'})
            ], style={'display': 'flex', 'flexDirection': 'column', 'alignItems': 'center'}) 
### tabella data_agende
@app.callback(
    Output('table-new', 'rowData'),
    Output('table-new', 'columnDefs'),
    [ Input('store-df6', 'data')]
)
def update_table2(df_data_agende_dict):
    data_agende = pd.DataFrame.from_dict(df_data_agende_dict)
    #table_height = calculate_table_height(data_agende)
    print("----------CHECK---------")
    print(data_agende)
    data_agende["id"] = data_agende.index
    
    """def discrete_background_color_bins(df,columns):
        styleConditions = []
        for column in columns:
            if column == 'Sentinella':
                styleConditions.append({
                    "condition": "params.data['Sentinella'] == 'Si'",
                    "style": {"backgroundColor": "#FFFFA7",'display': 'flex',
            'justifyContent': 'center',
            'alignItems': 'center'},
                    
                })
                styleConditions.append({
                    "condition": "params.data['Sentinella'] == 'No'",
                    "style": {"backgroundColor": "#F0F6FF", "color": "black",'display': 'flex',
            'justifyContent': 'center',
            'alignItems': 'center'},
                })
            
        return styleConditions"""
    columns = [
        {'headerName': 'Codice Agenda', 'field': 'Codice Agenda'},
        {'headerName': 'Descrizione Agenda', 'field': 'Descrizione Agenda'},
        {'headerName': 'Sentinella', 'field': 'Sentinella'},
        {'headerName': 'Grado di aderenza al grafo*', 'field': 'Grado di aderenza al grafo*',"valueFormatter": { "function": "params.value ? Math.round((params.value * 100)) + '%' : '-'"}, "cellStyle": {
    "styleConditions": [
        {
            
            "condition": "params.value >= 0.001 && params.value <= 0.25",
            "style": {"color": "#FF0000","font-weight": "bold"}
        },
        {
            "condition": "params.value > 0.25",
            "style": {"color": "#00B050","font-weight": "bold"}
        }
    ],
    "defaultStyle": {"color": "#ece9e8"} 
}
},
        {'headerName': 'Grado di urgenza**', 'field': 'Grado di urgenza**',"valueFormatter": { "function": "params.value ?  Math.round((params.value * 100)) + '%' : '-'"}, "cellStyle": {
    "styleConditions": [
        {
            
            "condition": "params.value >= 0.001 && params.value <= 0.25",
            "style": {"color": "#00B050","font-weight": "bold"}
        },
        {
            "condition": "params.value > 0.25",
            "style": {"color": "#FF0000","font-weight": "bold"}
        },
        
    ],
    "defaultStyle": {"color": "#ece9e8"}  
}
,
        },
        {'headerName': 'Selezione agenda/e da prioritizzare',
        'field': 'row_selector',
        'checkboxSelection': True,
        "cellStyle": {'display': 'flex',
            'justifyContent': 'center',
            'alignItems': 'center',
            'backgroundColor':'#ECF2FE'}
            },
        
    ]
    # Creazione della tabella con Dash AG Grid
    return df_data_agende_dict,columns
    
# Callback per aprire e chiudere la modale
@app.callback(
    Output("modal-aderenza", "is_open"),
    [Input("image-click-aderenza", "n_clicks"), Input("close-aderenza", "n_clicks")],
    [State("modal-aderenza", "is_open")],
)
def toggle_modal(n1, n2, is_open):
    if n1 or n2:
        return not is_open
    return is_open
@app.callback(
    Output("modal-congruenza", "is_open"),
    [Input("image-click-congruenza", "n_clicks"), Input("close-congruenza", "n_clicks")],
    [State("modal-congruenza", "is_open")],
)
def toggle_modal(n1, n2, is_open):
    if n1 or n2:
        return not is_open
    return is_open
@app.callback(
    Output("modal-agende", "is_open"),
    [Input("image-click-agende", "n_clicks"), Input("close-agende", "n_clicks")],
    [State("modal-agende", "is_open")],
)
def toggle_modal(n1, n2, is_open):
    if n1 or n2:
        return not is_open
    return is_open
app.config.suppress_callback_exceptions=True
if __name__ == '__main__':
    app.run_server(debug=False)