eternaut commited on
Commit
a28054f
·
verified ·
1 Parent(s): a115dee

Delete src/01_TagesMütter_RobertaBovoEdition.py

Browse files
src/01_TagesMütter_RobertaBovoEdition.py DELETED
@@ -1,2026 +0,0 @@
1
- from distutils import filelist
2
- from os import path
3
- from unicodedata import numeric
4
- import numpy as np
5
- import pandas as pd
6
- import os
7
- import sys
8
-
9
- pd.set_option("future.no_silent_downcasting", True)
10
- import copy
11
- import xlsxwriter
12
- import streamlit as st
13
-
14
- import streamlit as ste # cerchiamo di fare in modo che rimanga una soluzione temporanea
15
-
16
- # from streamlit.uploaded_file_manager import UploadedFile as FileUploadBase
17
- from st_aggrid import AgGrid
18
- from st_aggrid.grid_options_builder import GridOptionsBuilder
19
- from this import d
20
- from datetime import date, datetime
21
-
22
- from typing import Optional, List, Dict, Any
23
- import re
24
-
25
- # Get the absolute path to the script's directory
26
- current_dir = os.path.dirname(os.path.abspath(__file__))
27
- # Get the parent directory
28
- parent_dir = os.path.dirname(current_dir)
29
- # Add parent directory to sys.path
30
- sys.path.append(parent_dir)
31
-
32
- from interface import makeInterface
33
- from helpertools_TM import helpers
34
- from reportmaker import ReportGenerator
35
- from valoriEtErrori import valoriEtErrori
36
-
37
- st.set_page_config(
38
- page_title="Controllo Errori TAGESMÜTTER", layout="wide", page_icon="🤗"
39
- ) # 👽9
40
- st.markdown("# CONTROLLO ERRORI TAGESMUETTER")
41
- st.markdown('## Roberta Bovo Special Edition 🤗 (ver. 4.1.0)')
42
-
43
- # Add vertical spacing to create better separation from file uploader
44
- st.markdown("<div style='margin-bottom: 50px;'></div>", unsafe_allow_html=True)
45
-
46
- # st.write(f"{ReportGenerator.get_version(1)}, {helpers.get_version(1)}, {makeInterface.get_version(1)}")
47
-
48
- # st.sidebar.markdown(f"### Impostazioni controlli TAGESMÜTTER") # ffff
49
-
50
-
51
- class trovaErrori:
52
- def __init__(self, df, interface, df_gemeinden, df_allegato666):
53
- self.checks = interface.checks
54
- self.df_allegato666 = df_allegato666
55
- self.uploaded_allegato666 = interface.uploaded_allegato666
56
- self.ANNO_RIFERIMENTO = interface.ANNO_RIFERIMENTO
57
- self.ERRORDICT = valoriEtErrori.ERRORDICT[self.ANNO_RIFERIMENTO]
58
- self.COSTANTI = valoriEtErrori.COSTANTI[self.ANNO_RIFERIMENTO]
59
- self.df_gemeinden = df_gemeinden
60
- self.df = df
61
- self.dffinal = pd.DataFrame
62
- self.df_bambini_che_impediscono_controllo_superamento_ore = pd.DataFrame
63
-
64
-
65
- # let the show start
66
- self.check_data()
67
-
68
- def check_data(self) -> None:
69
- """
70
- Checks the data for errors and performs necessary operations.
71
-
72
- This method iterates through the selected checks, calls the corresponding error checking methods,
73
- and updates the status of the checks.
74
- """
75
- global NO_ZERO
76
- column_name = "Ore di assistenza ai sensi della delibera n. 666/2019"
77
- NO_ZERO = self.df[column_name].astype("float") > 0
78
-
79
- with interface.TAB2:
80
- for check, enabled in self.checks.items():
81
- if enabled:
82
- if interface.DEBUGGENERAL:
83
- st.write(check)
84
- try:
85
- # Call the method with the same name as the check.
86
- # I used the 'getattr' function to call the functions in the 'checks' dictionary instead of using 'eval'.
87
- getattr(self, check)()
88
- except AttributeError as ae:
89
- st.error(f"La funzione {check} non esiste; errore: {ae}.")
90
- if interface.TOLLERANZAZERO or interface.DEBUGWARNINGS:
91
- st.error("Elaborazione interrotta")
92
- st.stop()
93
- except TypeError as te:
94
- st.error(
95
- f"Il metodo {check} ha problemi con i tipi di dati; errore: {te}."
96
- )
97
- if interface.TOLLERANZAZERO or interface.DEBUGWARNINGS:
98
- st.error("Elaborazione interrotta")
99
- st.stop()
100
- except Exception as e:
101
- st.error(
102
- f"La funzione {check} non è stata eseguita; errore generico: {e}."
103
- )
104
- if interface.TOLLERANZAZERO or interface.DEBUGWARNINGS:
105
- st.error("Elaborazione interrotta")
106
- st.stop()
107
-
108
- # da qui in poi sono i controlli da eseguire
109
- def errCodFisc1(self) -> None: # refactored
110
- """
111
- Controlla la validità del codice fiscale.
112
-
113
- Questa funzione rimuove eventuali spazi bianchi all'inizio e alla fine del codice fiscale,
114
- quindi controlla se il formato è corretto utilizzando una regex. Se un codice fiscale
115
- non è valido, viene segnalato ed aggiunto a una tabella temporanea.
116
- """
117
-
118
- # rimuove eventuali spazi bianchi all'inizio e alla fine del codice fiscale
119
- self.df["Codice fiscale"] = self.df["Codice fiscale"].str.strip()
120
-
121
- # definiamo condizione logica per trovare codice fiscale invalido usando regex
122
- codinvalido = (
123
- self.df["Codice fiscale"].str.match(
124
- r"[A-Z|a-z][A-Z|a-z][A-Z|a-z][A-Z|a-z][A-Z|a-z][A-Z|a-z]\d\d[A-Z|a-z]\d\d[A-Z|a-z]\d\d\d[A-Z|a-z]"
125
- )
126
- == False
127
- )
128
- cond_mask = codinvalido & NO_ZERO
129
-
130
- if cond_mask.any():
131
- # create a temporary dataframe for display
132
- dftemp = self.df.loc[cond_mask].copy()
133
-
134
- dftemp = helper.convert_dates_if_needed(dftemp, [])
135
-
136
- interface.make_expander(
137
- "errore formato del codice fiscale",
138
- "Elenco dei bambini che hanno un codice fiscale nel formato non corretto",
139
- len(self.df[cond_mask]),
140
- dftemp,
141
- )
142
-
143
- # settiamo il flag bool per la tabella finale
144
- self.df.loc[cond_mask, "errCodFisc1"] = True
145
-
146
- def errCodFisc2(self) -> None: # refactored
147
- """
148
- Validates the correctness of the "Codice fiscale" (fiscal code) for each child in the DataFrame.
149
-
150
- This method performs the following operations:
151
- 1. Validates the format of the "Codice fiscale" using a regular expression.
152
- 2. Filters records with valid "Codice fiscale".
153
- 3. Determines gender based on the "Codice fiscale" and validates the day of birth.
154
- 4. Validates the month of birth encoded in the "Codice fiscale" against the actual birth date.
155
- 5. Validates that the year of birth encoded in the "Codice fiscale" matches the actual birth date.
156
- 6. Aggregates and identifies records with inconsistencies.
157
- 7. Displays the errors in an expandable section within Streamlit.
158
- 8. Provides options to download the error data and displays it in an interactive grid.
159
- 9. Flags the erroneous records in the main DataFrame.
160
-
161
- Raises:
162
- KeyError: If expected columns are missing from `self.df`.
163
- ValueError: If data types are incompatible for the operations.
164
- Exception: For any other exceptions that occur during execution.
165
- """
166
- try:
167
- # Step 1: Precompile the regex pattern for performance
168
- codice_fiscale_pattern: re.Pattern = re.compile(
169
- r"^[A-Za-z]{6}\d{2}[A-Za-z]\d{2}[A-Za-z]\d{3}[A-Za-z]$"
170
- )
171
-
172
- # Step 2: Validate the format of "Codice fiscale" using the compiled regex
173
- codvalido: pd.Series = self.df["Codice fiscale"].str.match(
174
- codice_fiscale_pattern, na=False # Treat NaN as non-matching
175
- )
176
-
177
- # Step 3: Filter records with valid "Codice fiscale"
178
- dfcod: pd.DataFrame = self.df.loc[codvalido].copy()
179
-
180
- # Ensure required columns are present
181
- required_columns = {"Codice fiscale", "Data di nascita", "Cognome", "Nome"}
182
- if not required_columns.issubset(dfcod.columns):
183
- missing = required_columns - set(dfcod.columns)
184
- raise KeyError(f"Colonne mancanti: {missing}")
185
-
186
- # Step 4: Extract components from "Codice fiscale"
187
- cf = dfcod["Codice fiscale"]
188
-
189
- # Extract and convert day code
190
- giorno_nascita_code: pd.Series = cf.str[9:11].astype(int)
191
-
192
- # Determine gender based on day code
193
- is_male: pd.Series = giorno_nascita_code < 40
194
- is_female: pd.Series = giorno_nascita_code >= 40
195
-
196
- # Extract actual day from "Data di nascita"
197
- data_nascita = pd.to_datetime(dfcod["Data di nascita"], errors="coerce")
198
- actual_day: pd.Series = data_nascita.dt.day
199
- actual_month: pd.Series = data_nascita.dt.month
200
- actual_year: pd.Series = data_nascita.dt.year
201
-
202
- # Step 5: Validate day of birth
203
- day_mismatch_male: pd.Series = is_male & (actual_day != giorno_nascita_code)
204
- day_mismatch_female: pd.Series = is_female & (
205
- actual_day != (giorno_nascita_code - 40)
206
- )
207
-
208
- # Step 6: Validate year of birth
209
- year_code: pd.Series = 2000 + cf.str[6:8].astype(int)
210
- year_mismatch: pd.Series = actual_year != year_code
211
-
212
- # Step 7: Validate month of birth
213
- mese_mapping: Dict[str, int] = {
214
- "A": 1,
215
- "B": 2,
216
- "C": 3,
217
- "D": 4,
218
- "E": 5,
219
- "H": 6,
220
- "L": 7,
221
- "M": 8,
222
- "P": 9,
223
- "R": 10,
224
- "S": 11,
225
- "T": 12,
226
- # Invalid or unused letters mapped to 99
227
- "F": 99,
228
- "G": 99,
229
- "I": 99,
230
- "J": 99,
231
- "K": 99,
232
- "N": 99,
233
- "O": 99,
234
- "Q": 99,
235
- "U": 99,
236
- "V": 99,
237
- "W": 99,
238
- "X": 99,
239
- "Y": 99,
240
- "Z": 99,
241
- }
242
-
243
- mese_codice: pd.Series = (
244
- cf.str[8].str.upper().map(mese_mapping).fillna(99).astype(int)
245
- )
246
- month_mismatch: pd.Series = actual_month != mese_codice
247
-
248
- # Step 8: Aggregate all mismatches
249
- cond_mask: pd.Series = (
250
- day_mismatch_male | day_mismatch_female | month_mismatch | year_mismatch
251
- )
252
- dob_col = "Data di nascita"
253
- start_col = "Data inizio contratto (o data inizio assistenza se diversa)"
254
- # Check if any errors are found
255
- if cond_mask.any():
256
- num_errori: int = cond_mask.sum()
257
- # Sort the dataframe and create a temporary dataframe for display
258
- results: pd.DataFrame = dfcod.loc[cond_mask].copy()
259
-
260
- # Convert date columns to strings if required by the interface
261
- results = helper.convert_dates_if_needed(results, [])
262
-
263
- # st.write(results)
264
- interface.make_expander(
265
- "errore data nascita per codice fiscale",
266
- "Elenco dei bambini per cui risulta un'incongruenza relativa alla data di nascita dichiarata e il codice fiscale.\nATTENZIONE: il bambino potrebbe apparire più volte, a seconda di quanti errori vengono trovati.",
267
- num_errori,
268
- results,
269
- )
270
-
271
- self.df.loc[
272
- self.df["Codice fiscale"].isin(results["Codice fiscale"]),
273
- "errCodFisc2",
274
- ] = True
275
-
276
- except KeyError as ke:
277
- st.error(f"Chiave mancante nel DataFrame: {ke}")
278
- st.stop() # Stop further execution
279
-
280
- except ValueError as ve:
281
- st.error(f"Valore non valido o incompatibile: {ve}")
282
- st.stop() # Stop further execution
283
-
284
- except Exception as e:
285
- st.error(f"Si è verificato un errore inaspettato: {e}")
286
- st.stop() # Stop further execution
287
-
288
- def errCalcoloNumeroBimbi(self) -> None: # not refactored
289
- """
290
- Identifies and processes errors related to the calculation of the number of children
291
- and their corresponding assistance hours for the reference year.
292
-
293
- This method performs the following operations:
294
- 1. Resets the index of the main DataFrame.
295
- 2. Filters records for the reference year (2023).
296
- 3. Creates a DataFrame (`df666`) excluding duplicate tax codes ("Codice fiscale")
297
- while ensuring that the assistance hours are greater than zero.
298
- 4. Aggregates data to calculate the sum of assistance days and counts of children per municipality.
299
- 5. Merges aggregated data to form a consolidated DataFrame (`dfx`).
300
- 6. Merges with additional datasets (`df_gemeinden` and `df_allegato666`) for comprehensive analysis.
301
- 7. Cleans and formats the consolidated DataFrame.
302
- 8. Inserts comparison columns to verify consistency between different data sources.
303
- 9. Calculates and appends summary rows to the DataFrame.
304
- 10. Displays the final table in an expandable section within Streamlit and provides download options.
305
-
306
- Raises:
307
- KeyError: If expected columns are missing from `self.df`, `df_gemeinden`, or `df_allegato666`.
308
- Exception: For any other exceptions that occur during DataFrame operations.
309
- """
310
- # Step 1: Reset the index of the main DataFrame to ensure it's clean
311
- self.df = self.df.reset_index(drop=True)
312
-
313
- # Step 2: Proceed only if the reference year is 2023
314
- if self.ANNO_RIFERIMENTO in ["2023","2024","2025"]:
315
- # Initialize an empty DataFrame `df666` with specified columns
316
- df666: pd.DataFrame = pd.DataFrame(
317
- columns=[
318
- "Comune",
319
- "Cognome e nome bambino",
320
- "Codice fiscale",
321
- "Ore di assistenza ai sensi della delibera n. 666/2019",
322
- ]
323
- )
324
-
325
- # Iterate through each record to populate `df666`
326
- for ind in self.df.index:
327
- # Check if the assistance hours are greater than zero
328
- if (
329
- self.df["Ore di assistenza ai sensi della delibera n. 666/2019"][
330
- ind
331
- ]
332
- > 0
333
- ):
334
- if df666.empty:
335
- # If `df666` is empty, add the current record
336
- df666 = self.df.loc[[ind]]
337
- else:
338
- # Check for duplicate "Codice fiscale" and "Com_code" to avoid duplicates
339
- is_duplicate: bool = (
340
- (self.df["Codice fiscale"][ind] == df666["Codice fiscale"])
341
- & (self.df["Com_code"][ind] == df666["Com_code"])
342
- ).any()
343
- if not is_duplicate:
344
- # Concatenate the current record if it's not a duplicate
345
- df666 = pd.concat([df666, self.df.loc[[ind]]])
346
- else:
347
- continue # Skip duplicate records
348
-
349
- # Step 3: Aggregate the sum of assistance days per municipality
350
- df1: pd.DataFrame = self.df.groupby("Com_code", as_index=False)[
351
- "GiorniAssistenzaAnnoRiferimento"
352
- ].apply(lambda x: x[x > 0].sum())
353
-
354
- # Step 4: Count the number of children per municipality with assistance hours > 0
355
- df2: pd.DataFrame = df666.groupby("Com_code", as_index=False)[
356
- "Ore di assistenza ai sensi della delibera n. 666/2019"
357
- ].count()
358
-
359
- # Step 5: Sum the assistance hours per municipality
360
- df2bis: pd.DataFrame = self.df.groupby("Com_code", as_index=False)[
361
- "Ore di assistenza ai sensi della delibera n. 666/2019"
362
- ].apply(lambda x: x[x > 0].sum())
363
-
364
- # Step 6: Sum the assistance hours per municipality again (redundant step)
365
- df4: pd.DataFrame = self.df.groupby("Com_code", as_index=False)[
366
- "Ore di assistenza ai sensi della delibera n. 666/2019"
367
- ].apply(lambda x: x[x > 0].sum())
368
-
369
- # Step 7: Merge all aggregated DataFrames into a consolidated DataFrame `dfx`
370
- dfx: pd.DataFrame = pd.merge(df1, df2, on="Com_code", how="outer")
371
- dfx = pd.merge(dfx, df2bis, on="Com_code", how="outer")
372
- dfx = pd.merge(dfx, df4, on="Com_code", how="outer")
373
-
374
- # Step 8: Rename the columns for clarity
375
- dfx.columns = [
376
- "Com_code",
377
- f"Somma Giorni Assistenza Anno Riferimento {self.ANNO_RIFERIMENTO}",
378
- "Numero bambini report Comuni",
379
- "Ore di assistenza report Comuni",
380
- f"Ore totali rendicontate per il {self.ANNO_RIFERIMENTO}",
381
- ]
382
- # Sort the DataFrame by "Com_code" in ascending order
383
- dfx = dfx.sort_values(by="Com_code", ascending=True)
384
-
385
- # Step 9: Merge with `df_gemeinden` on "Com_code"
386
- dfx = pd.merge(dfx, df_gemeinden, on="Com_code", how="outer")
387
-
388
- # Step 10: Merge with `df_allegato666` on "Com_code"
389
- lingua: str = df_allegato666.iloc[
390
- 0, 8
391
- ] # Assuming column index 8 contains language code
392
- dfx = pd.merge(dfx, df_allegato666, on="Com_code", how="outer")
393
-
394
- # Step 11: Drop unnecessary columns and rename others for consistency
395
- columns_to_drop: list = [
396
- "Comune provenienza bambino",
397
- f"Somma Giorni Assistenza Anno Riferimento {self.ANNO_RIFERIMENTO}",
398
- f"Ore totali rendicontate per il {self.ANNO_RIFERIMENTO}",
399
- "PLZ",
400
- "Bezirk",
401
- "Costo orario",
402
- "Costo del servizio",
403
- "Entrate compartecipazione",
404
- "Ore tariffa maggiorata",
405
- "Entrate maggiorazione",
406
- ]
407
- dfx = dfx.drop(columns=columns_to_drop, axis=1)
408
- dfx = dfx.rename(
409
- columns={
410
- "Numero utenti": "Numero utenti allegato 666",
411
- "Ore di servizio": "Ore di servizio allegato 666",
412
- }
413
- )
414
-
415
- # Step 12: Sort and rename columns based on the language code
416
- if lingua == "001":
417
- dfx = dfx.sort_values(by="Comune")
418
- dfx = dfx.rename(columns={"Comune": "Comune provenienza bambino"})
419
- dfx = dfx.drop("Gemeinde", axis=1)
420
- elif lingua == "006":
421
- dfx = dfx.sort_values(by="Gemeinde")
422
- dfx = dfx.rename(columns={"Gemeinde": "Comune provenienza bambino"})
423
- dfx = dfx.drop("Comune", axis=1)
424
-
425
- # Step 13: Reorder columns to the desired sequence
426
- columns_order: list = dfx.columns.tolist()
427
- columns_order.insert(
428
- 1, columns_order.pop(columns_order.index("Comune provenienza bambino"))
429
- )
430
- columns_order.insert(
431
- 2, columns_order.pop(columns_order.index("Numero utenti allegato 666"))
432
- )
433
- columns_order.insert(
434
- 3,
435
- columns_order.pop(columns_order.index("Numero bambini report Comuni")),
436
- )
437
- columns_order.insert(
438
- 4,
439
- columns_order.pop(columns_order.index("Ore di servizio allegato 666")),
440
- )
441
- columns_order.insert(
442
- 5,
443
- columns_order.pop(
444
- columns_order.index("Ore di assistenza report Comuni")
445
- ),
446
- )
447
- dfx = dfx[columns_order]
448
-
449
- # Step 14: Replace NaN or empty values with zero
450
- dfx = dfx.fillna(0).infer_objects(copy=False)
451
-
452
- # Step 15: Insert comparison columns to verify consistency between data sources
453
- dfx.insert(
454
- 4,
455
- "Confronto bambini",
456
- dfx["Numero utenti allegato 666"]
457
- == dfx["Numero bambini report Comuni"],
458
- )
459
- dfx.insert(
460
- 7,
461
- "Confronto ore",
462
- dfx["Ore di servizio allegato 666"]
463
- == dfx["Ore di assistenza report Comuni"],
464
- )
465
-
466
- # Step 16: Calculate the sum for specified columns and append as a summary row
467
- columns_to_sum: list = [
468
- "Numero utenti allegato 666",
469
- "Numero bambini report Comuni",
470
- "Ore di servizio allegato 666",
471
- "Ore di assistenza report Comuni",
472
- ]
473
- sum_values: pd.Series = dfx[columns_to_sum].sum()
474
- sum_df: pd.DataFrame = pd.DataFrame([sum_values], index=["Somma"])
475
- dfx = pd.concat([dfx, sum_df])
476
-
477
- # For columns not included in the sum, add NaN in the summary row
478
- for col in dfx.columns:
479
- if col not in columns_to_sum:
480
- dfx.loc["Somma", col] = pd.NA
481
-
482
- # Step 17: Display the final table in an expandable section within Streamlit
483
-
484
- expndr = st.expander("Tabella conteggio bimbi per comune e tipologia ore")
485
- with expndr:
486
- st.info("Conteggio dei bambini per tipologia di ora e per comune")
487
-
488
- # Add an empty row for formatting purposes
489
- riga_vuota: dict = {"Annotazione": " "}
490
- nuova_riga_df: pd.DataFrame = pd.DataFrame(riga_vuota, index=[0])
491
- dfx = pd.concat([dfx, nuova_riga_df], ignore_index=True)
492
-
493
- # Add a row indicating the generation date of the file
494
- riga_vuota = {
495
- "Annotazione": f"file generato in data: {date.today().strftime('%d/%m/%Y')}"
496
- }
497
- nuova_riga_df = pd.DataFrame(riga_vuota, index=[0])
498
- dfx = pd.concat([dfx, nuova_riga_df], ignore_index=True)
499
-
500
- # Build grid options for AgGrid
501
- gridOptions = interface.buildGrid(dfx)
502
-
503
- # Provide an option to download the consolidated DataFrame as an Excel file
504
- interface.download_excel_file(
505
- dfx,
506
- "calcolo_bimbi_per_tipologia_comune_ore.xlsx",
507
- "mantieniColonneErrore",
508
- )
509
-
510
- # Display the DataFrame in an interactive grid
511
- AgGrid(dfx, gridOptions=gridOptions, enable_enterprise_modules=True)
512
- # Note: No flag is set here as it's not required
513
-
514
- # we take only the rows where we found an inconsistency
515
- self.df_calcolo_666 = dfx[
516
- (dfx["Confronto bambini"] == False)
517
- | (dfx["Confronto ore"] == False)
518
- ]
519
-
520
- # Note: Ensure that all referenced variables and methods (e.g., df_gemeinden, df_allegato666, NO_ZERO, etc.) are defined within the class or passed appropriately.
521
-
522
- def errOreRendicontateZero(self) -> None: # refactored
523
- """
524
- Metodo per trovare e elencare i bambini con ore totali rendicontate a zero.
525
-
526
- Parameters:
527
- None
528
-
529
- Returns:
530
- None
531
- """
532
-
533
- condizione: pd.Series = (
534
- self.df["Ore di assistenza ai sensi della delibera n. 666/2019"] == 0
535
- )
536
- cond_mask: pd.Series = condizione & NO_ZERO
537
-
538
- if cond_mask.any():
539
- # create a temporary dataframe for display
540
- dftemp = self.df.loc[cond_mask].copy()
541
- dftemp = helper.convert_dates_if_needed(dftemp, [])
542
-
543
- interface.make_expander(
544
- "bambini con ore totali rendicontate uguali a zero",
545
- "Elenco dei bambini che hanno un valore di zero nelle ore rendicontate per l'anno {self.ANNO_RIFERIMENTO}",
546
- len(self.df[cond_mask]),
547
- dftemp,
548
- )
549
-
550
- # settiamo la colonna bool
551
- self.df.loc[condizione, "errOreRendicontateZero"] = True
552
-
553
- def errDateAnnoRiferimento(self) -> None: # refactored
554
- """
555
- Check for incongruences in the start and end dates of the contract compared to the reference year.
556
- """
557
- # Check if the start date is after the reference year
558
- condizione_anno_inizio: pd.Series = self.df[
559
- "Data inizio contratto (o data inizio assistenza se diversa)"
560
- ].dt.year > int(self.ANNO_RIFERIMENTO)
561
-
562
- # Check if the end date is before the reference year
563
- condizione_anno_fine: pd.Series = self.df[
564
- "Data fine contratto (o data fine assistenza se diversa)"
565
- ].dt.year < int(self.ANNO_RIFERIMENTO)
566
-
567
- # Combine the conditions and apply the NO_ZERO filter
568
- cond_mask: pd.Series = (condizione_anno_inizio | condizione_anno_fine) & NO_ZERO
569
-
570
- # If there are any incongruences, create an expander section to display them
571
-
572
- if cond_mask.any():
573
- # create a temporary dataframe for display
574
- dftemp = self.df.loc[cond_mask].copy()
575
- dftemp = helper.convert_dates_if_needed(dftemp, [])
576
-
577
- interface.make_expander(
578
- f"incongruenze data inizio o data fine in relazione all'anno di riferimento",
579
- "Elenco delle righe per cui sono state trovate incongruenze di data inizio o fine con anno di riferimento\n\r(data inizio > {self.ANNO_RIFERIMENTO} oppure data fine < {self.ANNO_RIFERIMENTO}",
580
- len(self.df[cond_mask]),
581
- dftemp,
582
- )
583
-
584
- # Set the flag for incongruences in the original dataframe
585
- self.df.loc[condizione_logica, "errDateAnnoRiferimento"] = True
586
-
587
- def errInizioMinoreFine(self) -> None: # refactored
588
- """
589
- Check if start date > end date.
590
- """
591
- start_col = "Data inizio contratto (o data inizio assistenza se diversa)"
592
- end_col = "Data fine contratto (o data fine assistenza se diversa)"
593
- if start_col not in self.df.columns or end_col not in self.df.columns:
594
- return
595
-
596
- bad_mask = self.df[start_col] > self.df[end_col]
597
- cond_mask = bad_mask & NO_ZERO
598
-
599
- if cond_mask.any():
600
-
601
- # create a temporary dataframe for display
602
- dftemp = self.df.loc[cond_mask].copy()
603
-
604
- dftemp = helper.convert_dates_if_needed(dftemp, [])
605
-
606
- interface.make_expander(
607
- "errore date contrattuali",
608
- "Elenco dei bambini per cui è stato trovato un errore secondo la condizione: (dataInizio > dataFine)",
609
- len(self.df[cond_mask]),
610
- dftemp,
611
- )
612
-
613
- # Set the flag for errors in the original dataframe
614
- self.df.loc[cond_mask, "errInizioMinoreFine"] = True
615
-
616
- def errAgeChild(self) -> None: # refactored
617
- """
618
- Check if the child is younger than 90 days at the start of the contract.
619
- Sets 'errAgeChild' = True if discovered.
620
- """
621
- start_col = "Data inizio contratto (o data inizio assistenza se diversa)"
622
- dob_col = "Data di nascita"
623
- if start_col not in self.df.columns or dob_col not in self.df.columns:
624
- return
625
-
626
- # Convert to datetime
627
- self.df[start_col] = pd.to_datetime(self.df[start_col], errors="coerce")
628
- self.df[dob_col] = pd.to_datetime(self.df[dob_col], errors="coerce")
629
-
630
- age_mask = (self.df[start_col] - self.df[dob_col]).dt.days < 90
631
- cond_mask = age_mask & NO_ZERO
632
-
633
- if cond_mask.any():
634
- # create a temporary dataframe for display
635
- dftemp = self.df.loc[cond_mask].copy()
636
-
637
- dftemp = helper.convert_dates_if_needed(dftemp, [])
638
-
639
- interface.make_expander(
640
- "errore età bambino (< 90 giorni)",
641
- "Elenco dei bambini per cui è stato trovato l'errore secondo la condizione: il bambino ha meno di 3 mesi",
642
- len(self.df[cond_mask]),
643
- dftemp,
644
- )
645
-
646
- self.df.loc[cond_mask, "errAgeChild"] = True
647
-
648
- def errFineAssistenzaMax4Anni(self) -> None: # refactored
649
- """
650
- End date must not exceed 4 years (1464 days) from birthdate.
651
- """
652
- end_col = "Data fine contratto (o data fine assistenza se diversa)"
653
- dob_col = "Data di nascita"
654
- if end_col not in self.df.columns or dob_col not in self.df.columns:
655
- return
656
-
657
- too_long = (self.df[end_col] - self.df[dob_col]).dt.days > 1464
658
- cond_mask = too_long & NO_ZERO
659
-
660
- if cond_mask.any():
661
- # create a temporary dataframe for display
662
- dftemp = self.df.loc[cond_mask].copy()
663
-
664
- dftemp = helper.convert_dates_if_needed(dftemp, [])
665
-
666
- interface.make_expander(
667
- "errore fine contratto assistenza",
668
- "Elenco dei bambini per cui è stato trovato l'errore secondo la condizione: data fine contratto non può essere oltre 4 anni da data di nascita",
669
- len(self.df[cond_mask]),
670
- dftemp,
671
- )
672
-
673
- # Set the flag for incongruences in the original dataframe
674
- self.df.loc[cond_mask, "errFineAssistenzaMax4Anni"] = True
675
-
676
- def errKindergarten(self) -> None: # refactored
677
- """
678
- Validates kindergarten-related data for each child in the DataFrame.
679
-
680
- This method performs the following operations:
681
- 1. Checks if the child's birth date is earlier than a specified control date.
682
- 2. Verifies that the contract end date is within an acceptable range based on the birth year.
683
- 3. Applies additional conditions based on the reference year.
684
- 4. Aggregates and identifies records with inconsistencies.
685
- 5. Displays the errors in an expandable section within Streamlit.
686
- 6. Provides options to download the error data and displays it in an interactive grid.
687
- 7. Flags the erroneous records in the main DataFrame.
688
-
689
- --> #modificato in '04.09' il 24/6/2024 su richiesta RobertaB
690
-
691
- If child's birth date < KONTROLLEKINDERGARTEN_DATANASCITA_2,
692
- end date must not exceed a certain limit (04.09. of birth_year + 3).
693
-
694
- Raises:
695
- KeyError: If expected columns are missing from `self.df`.
696
- ValueError: If data types are incompatible for the operations.
697
- Exception: For any other exceptions that occur during execution.
698
- """
699
- try:
700
- dob_col = "Data di nascita"
701
- end_col = "Data fine contratto (o data fine assistenza se diversa)"
702
- # Ensure necessary columns are present
703
- required_columns = {dob_col, end_col, "Cognome", "Nome"}
704
- if not required_columns.issubset(self.df.columns):
705
- missing = required_columns - set(self.df.columns)
706
- raise KeyError(
707
- f"Colonne mancanti: {missing}. Impossibile proseguire con il controllo 'Kindergarten'"
708
- )
709
- return
710
-
711
- # Convert
712
- self.df[dob_col] = pd.to_datetime(self.df[dob_col], errors="coerce")
713
- self.df[end_col] = pd.to_datetime(self.df[end_col], errors="coerce")
714
-
715
- # Define control date from constants
716
- kontrolle_date: pd.Timestamp = pd.to_datetime(
717
- self.COSTANTI["KONTROLLEKINDERGARTEN_DATANASCITA_2"], format="%d.%m.%Y"
718
- )
719
-
720
- # Step 1: Check if "Data di nascita" is earlier than the control date
721
- # Condition: child born before this date
722
- older_kids = self.df[dob_col] < kontrolle_date
723
-
724
- # Step 2: Compute the acceptable contract end date ("04.09.{birth_year + 3}")
725
- # Extract birth year and compute birth_year + 3
726
- # Acceptable end date: 04.09.(birth_year + 3)
727
- birth_year_plus_3 = self.df[dob_col].dt.year + 3
728
-
729
- # Create date '04.09.YYYY'
730
- acceptance_date = pd.to_datetime(
731
- "04.09." + birth_year_plus_3.astype(str),
732
- format="%d.%m.%Y",
733
- errors="coerce",
734
- )
735
-
736
- # Compare "Data fine contratto..." with the acceptable end date
737
- bad_end_date = self.df[end_col] > acceptance_date
738
-
739
- cond_mask = older_kids & bad_end_date & NO_ZERO
740
-
741
- # Step 3: Check if any errors are found
742
- if cond_mask.any():
743
- num_errori: int = cond_mask.sum()
744
-
745
- # Sort the dataframe and create a temporary dataframe for display
746
- dftemp = self.df.loc[cond_mask].copy()
747
-
748
- dftemp = helper.convert_dates_if_needed(dftemp, [])
749
-
750
- interface.make_expander(
751
- "errore Kindergarten",
752
- "Elenco dei bambini per cui è stato trovato l'errore secondo la condizione: dataFine > 04.09 dell'anno di nascita + 3 anni",
753
- num_errori,
754
- dftemp,
755
- )
756
-
757
- # Flag the erroneous records in the main DataFrame
758
- # erroneous_cf = dftemp_sorted["Data di nascita"].unique() # Adjust if "Codice fiscale" is needed
759
- self.df.loc[
760
- cond_mask,
761
- "errKindergarten",
762
- ] = True
763
-
764
- except KeyError as ke:
765
- st.error(f"Chiave mancante nel DataFrame: {ke}")
766
- st.stop() # Stop further execution
767
-
768
- except ValueError as ve:
769
- st.error(f"Valore non valido o incompatibile: {ve}")
770
- st.stop() # Stop further execution
771
-
772
- except Exception as e:
773
- st.error(f"Si è verificato un errore inaspettato: {e}")
774
- st.stop() # Stop further execution
775
-
776
- def errGesamtstundenVertragszeitraum(self) -> None: # not refactored
777
- """
778
- Identifies and handles errors related to overlapping contract periods
779
- across different municipalities for the same "Codice fiscale" (tax code).
780
-
781
- This method performs the following steps:
782
- 1. Finds tax codes present in multiple municipalities.
783
- 2. Identifies overlapping contract periods for these tax codes across different municipalities.
784
- 3. Filters and sorts the relevant data for reporting.
785
- 4. Checks if the calculated hours exceed the reported hours based on a reference year.
786
- 5. If errors are found, it displays warnings, provides detailed information, and
787
- allows downloading the error data as an Excel file.
788
-
789
- The method updates the DataFrame `self.df` by setting a flag for rows where
790
- the proportion of calculated hours is less than the sum of reported hours.
791
-
792
- Raises:
793
- KeyError: If expected columns are missing from `self.df`.
794
- ValueError: If data types are incompatible for calculations.
795
- """
796
- # Step 1: Identify tax codes ("Codice fiscale") present in multiple municipalities
797
- # Group by "Codice fiscale" and count the number of unique "Comune" (municipality) entries
798
- multi_city_codes: List[str] = self.df.groupby("Codice fiscale")[
799
- "Comune"
800
- ].nunique()
801
- multi_city_codes = multi_city_codes[multi_city_codes > 1].index.tolist()
802
-
803
- # Filter the DataFrame to include only rows with tax codes in multiple municipalities
804
- df_multi = self.df[self.df["Codice fiscale"].isin(multi_city_codes)]
805
-
806
- # Step 2: Find overlapping contract periods in different municipalities
807
- # Merge the DataFrame with itself on "Codice fiscale" to compare different records
808
- merged_df = df_multi.merge(df_multi, on="Codice fiscale", suffixes=("_1", "_2"))
809
- # Keep only rows where the municipality is different
810
- merged_df = merged_df[merged_df["Comune_1"] != merged_df["Comune_2"]]
811
-
812
- # Define the condition for overlapping contract periods
813
- overlap_condition = (
814
- merged_df["Data inizio contratto (o data inizio assistenza se diversa)_1"]
815
- <= merged_df["Data fine contratto (o data fine assistenza se diversa)_2"]
816
- ) & (
817
- merged_df["Data fine contratto (o data fine assistenza se diversa)_1"]
818
- >= merged_df[
819
- "Data inizio contratto (o data inizio assistenza se diversa)_2"
820
- ]
821
- )
822
-
823
- # Apply the overlap condition to filter overlapping pairs
824
- overlapping_pairs = merged_df[overlap_condition]
825
-
826
- # Extract unique tax codes that have overlapping periods
827
- result_codes_list = overlapping_pairs["Codice fiscale"].unique().tolist()
828
-
829
- # Step 3: Filter the original DataFrame for the identified tax codes
830
- filtered_df = self.df[self.df["Codice fiscale"].isin(result_codes_list)]
831
- result_df = filtered_df[
832
- [
833
- "Cognome",
834
- "Nome",
835
- "Comune",
836
- "Data inizio contratto (o data inizio assistenza se diversa)",
837
- "Data fine contratto (o data fine assistenza se diversa)",
838
- "filename",
839
- ]
840
- ].reset_index(drop=True)
841
-
842
- # Sort the result DataFrame by "Cognome" and "Nome"
843
- result_df = result_df.sort_values(by=["Cognome", "Nome"])
844
- self.df_bambini_che_impediscono_controllo_superamento_ore = result_df
845
-
846
- # Step 4: Calculate the number of days in the reference year
847
- # Assuming self.ANNO_RIFERIMENTO is a string representing the year, e.g., "2023"
848
- nrGiorniAnnoRiferimento = pd.Period(
849
- f"{self.ANNO_RIFERIMENTO}-12-31", freq="D"
850
- ).day_of_year
851
-
852
- # Calculate the condition where calculated hours exceed reported hours
853
- # Note: 1920 is a constant multiplier; adjust as necessary
854
- condizioneerrore2 = (
855
- (
856
- 1920
857
- * (
858
- self.df.groupby("Codice fiscale")[
859
- "GiorniAssistenzaAnnoRiferimento"
860
- ].transform("sum")
861
- )
862
- / nrGiorniAnnoRiferimento # Note: 365 only if not a leap year
863
- )
864
- ) < self.df.groupby("Codice fiscale")[
865
- "Ore di assistenza ai sensi della delibera n. 666/2019"
866
- ].transform(
867
- "sum"
868
- ).astype(
869
- "float"
870
- )
871
-
872
- # Combine the above condition with another condition (NO_ZERO)
873
- # Assuming NO_ZERO is defined elsewhere in the class
874
- cond_mask = condizioneerrore2 & NO_ZERO
875
-
876
- # Step 5: If any rows meet the error condition, process and report them
877
- if cond_mask.any():
878
- dfx = self.df[cond_mask].copy()
879
- # Insert a new column "Ore calcolate" with the calculated hours
880
- dfx.insert(
881
- loc=11,
882
- column="Ore calcolate",
883
- value=round(
884
- (dfx["GiorniAssistenzaAnnoRiferimento"] * 1920)
885
- / nrGiorniAnnoRiferimento,
886
- 2,
887
- ),
888
- )
889
-
890
- # create a temporary dataframe for display
891
- dftemp = dfx.loc[cond_mask].copy()
892
-
893
- dftemp = helper.convert_dates_if_needed(dftemp, [])
894
-
895
- # non usiamo la funzione perche' vogliamo la lista dei bimbi che impediscono il controllo nell'expander
896
- with st.expander(
897
- f"Trovate {len(dftemp)} occorrenze per errore Proportion Maximalstunden überschritten"
898
- ):
899
- if not self.df_bambini_che_impediscono_controllo_superamento_ore.empty:
900
- st.warning(
901
- "Attenzione: i seguenti bambini non sono stati considerati per il controllo poiché hanno periodi uguali o sovrapposti per comuni diversi."
902
- )
903
- st.write(self.df_bambini_che_impediscono_controllo_superamento_ore)
904
-
905
- st.info(
906
- f"Elenco dei bambini per cui la proporzione delle ore per i giorni di assistenza è inferiore alla somma delle ore rendicontate per il {self.ANNO_RIFERIMENTO}. Secondo la formula: (1920*(giorniAssistenzaAnnoRiferimento)/{nrGiorniAnnoRiferimento}) < (oreTotaliRendicontate {self.ANNO_RIFERIMENTO})"
907
- )
908
- # st.write(dftemp)
909
- # Create a grid display of the sorted dataframe
910
- interface.make_grid(
911
- dftemp.sort_values(by=["Cognome", "Nome"]),
912
- "errore Proportion Maximalstunden überschritten",
913
- )
914
-
915
- interface.download_excel_file(
916
- dftemp.sort_values(by=["Cognome", "Nome"]),
917
- "errore Proportion Maximalstunden überschritten.xlsx",
918
- )
919
-
920
- # Set a boolean flag in the original DataFrame for the identified errors
921
- self.df.loc[cond_mask, "errGesamtstundenVertragszeitraum"] = True
922
-
923
- def errSuperatoOreMassime1920(self) -> None: # refactored
924
- """
925
- Identifies and handles cases where the total rendered hours per child exceed 1920.
926
-
927
- This method performs the following operations:
928
- 1. Calculates the total hours rendered per child based on the "Codice fiscale".
929
- 2. Identifies records where the total hours exceed 1920 and meet the `NO_ZERO` condition.
930
- 3. If such records exist, it:
931
- a. Displays an expandable section in Streamlit with relevant information.
932
- b. Provides an option to download the error data as an Excel file.
933
- c. Displays the error data in a grid format.
934
- d. Flags the identified records in the main DataFrame.
935
-
936
- Raises:
937
- KeyError: If expected columns are missing from `self.df`.
938
- AttributeError: If required attributes or methods are not defined in the class.
939
- """
940
- # Step 1: Calculate the total rendered hours per child ("Codice fiscale")
941
- # Group by "Codice fiscale" and sum the "Ore di assistenza ai sensi della delibera n. 666/2019"
942
- total_hours = (
943
- self.df.groupby("Codice fiscale")[
944
- "Ore di assistenza ai sensi della delibera n. 666/2019"
945
- ]
946
- .transform("sum")
947
- .astype(float)
948
- )
949
-
950
- # Define the condition where total hours exceed 1920
951
- condizionelogica2: pd.Series = total_hours > 1920
952
-
953
- # Combine with another condition `NO_ZERO` to filter relevant records
954
- # Assuming `NO_ZERO` is a boolean Series defined elsewhere in the class
955
- cond_mask: pd.Series = condizionelogica2 & NO_ZERO
956
-
957
- # Step 2: Check if any records meet the error condition
958
-
959
- if cond_mask.any():
960
- # create a temporary dataframe for display
961
- dftemp = self.df.loc[cond_mask].copy()
962
-
963
- dftemp = helper.convert_dates_if_needed(dftemp, [])
964
-
965
- interface.make_expander(
966
- "errore ore complessive maggiore di 1920",
967
- f"Elenco dei bambini per cui la somma delle ore totali rendicontate per il {self.ANNO_RIFERIMENTO} è maggiore di 1920.",
968
- len(dftemp[cond_mask]),
969
- dftemp,
970
- )
971
-
972
- # Step 4: Flag the identified records in the main DataFrame
973
- # This sets a boolean flag 'errSuperatoOreMassime1920' to True for affected rows
974
- self.df.loc[cond_mask, "errSuperatoOreMassime1920"] = True
975
-
976
- def errBambinoInPiuComuni(self) -> None: # refactored
977
- """
978
- Identifies and handles cases where a child ("Bambino") is present in more than one municipality.
979
-
980
- This method performs the following operations:
981
- 1. Determines if any child is associated with multiple municipalities based on the "Codice fiscale".
982
- 2. If such cases exist, it:
983
- a. Displays an expandable section in Streamlit with relevant information.
984
- b. Provides an option to download the error data as an Excel file.
985
- c. Displays the error data in a grid format.
986
- d. Flags the identified records in the main DataFrame.
987
-
988
- Raises:
989
- KeyError: If expected columns ("Codice fiscale", "Comune", "Cognome", "Nome", etc.) are missing from `self.df`.
990
- AttributeError: If required attributes or methods (e.g., `converti_date_in_stringhe`, `download_excel_file`, `make_grid`) are not defined in the class.
991
- """
992
- # Step 1: Identify children present in multiple municipalities
993
- # Group the DataFrame by "Codice fiscale" and count the number of unique "Comune" entries
994
- condizionlogica1: pd.Series = (
995
- self.df.groupby("Codice fiscale")["Comune"].transform("nunique") > 1
996
- )
997
-
998
- # Note: Previously combined with `NO_ZERO`, but removed to include all cases of multiple municipalities
999
- cond_mask: pd.Series = condizionlogica1
1000
-
1001
- # Step 2: Check if any records meet the condition of being in multiple municipalities
1002
- if cond_mask.any():
1003
- # create a temporary dataframe for display
1004
- dftemp = self.df.loc[cond_mask].copy()
1005
-
1006
- dftemp = helper.convert_dates_if_needed(dftemp, [])
1007
-
1008
- interface.make_expander(
1009
- "bambini presenti in più comuni",
1010
- "Elenco dei bambini trovati in più comuni (in più file Excel)",
1011
- len(dftemp[cond_mask]),
1012
- dftemp,
1013
- )
1014
-
1015
- # Step 4: Flag the identified records in the main DataFrame
1016
- # This sets a boolean flag 'errBambinoInPiuComuni' to True for affected rows
1017
- self.df.loc[cond_mask, "errBambinoInPiuComuni"] = True
1018
-
1019
- def errPresentiAnnotazioni(self) -> None: # refactored
1020
- """
1021
- Identifies and handles cases where a child ("Bambino") has annotations in their "Cognome" (Surname) or "Nome" (Name).
1022
-
1023
- This method performs the following operations:
1024
- 1. Detects special characters in the "Cognome" and "Nome" columns indicating annotations.
1025
- 2. Filters the DataFrame based on the detection and an additional condition `NO_ZERO`.
1026
- 3. If such records exist, it:
1027
- a. Displays an expandable section in Streamlit with relevant information.
1028
- b. Provides an option to download the error data as an Excel file.
1029
- c. Displays the error data in a grid format.
1030
- d. Flags the identified records in the main DataFrame.
1031
-
1032
- Raises:
1033
- KeyError: If expected columns ("Cognome", "Nome", etc.) are missing from `self.df`.
1034
- AttributeError: If required attributes or methods (e.g., `converti_date_in_stringhe`, `download_excel_file`, `make_grid`) are not defined in the class.
1035
- """
1036
- # Step 1: Detect annotations in the "Cognome" and "Nome" columns using regex for special characters
1037
- # The regex pattern looks for any of the specified special characters
1038
- annotation_pattern: str = r"[@_!#$%^&*()<>?/|}{~:]"
1039
- condizione: pd.Series = self.df["Cognome"].str.contains(
1040
- annotation_pattern, regex=True, na=False
1041
- ) & self.df["Nome"].str.contains(annotation_pattern, regex=True, na=False)
1042
-
1043
- # Step 2: Combine the annotation condition with an additional condition `NO_ZERO`
1044
- # `NO_ZERO` is assumed to be a boolean Series defined elsewhere in the class
1045
- cond_mask: pd.Series = condizione & NO_ZERO
1046
-
1047
- if cond_mask.any():
1048
- # create a temporary dataframe for display
1049
- dftemp = self.df.loc[cond_mask].copy()
1050
-
1051
- dftemp = helper.convert_dates_if_needed(dftemp, [])
1052
-
1053
- interface.make_expander(
1054
- "bambini con annotazioni",
1055
- "Elenco dei bambini che hanno una annotazione, o direttamente nel nome o nella colonna del numero progressivo (dal quale viene cancellato e aggiunto al nome)",
1056
- len(dftemp[cond_mask]),
1057
- dftemp,
1058
- )
1059
-
1060
- # Step 4d: Flag the identified records in the main DataFrame
1061
- # This sets a boolean flag 'errPresentiAnnotazioni' to True for affected rows
1062
- self.df.loc[cond_mask, "errPresentiAnnotazioni"] = True
1063
-
1064
- def errNomeComuneTagesmutter(self) -> None: # refactored
1065
- """
1066
- Identifies and handles cases where a child's residence city does not match the official list of cities.
1067
-
1068
- This method performs the following operations:
1069
- 1. Reads the official list of cities from an Excel file.
1070
- 2. Checks if the city in the "Comune di residenza assistente domiciliare all'infanzia" column
1071
- matches either the "Gemeinde" or "Comune" column in the official list.
1072
- 3. If mismatches are found, it:
1073
- a. Displays an expandable section in Streamlit with relevant information.
1074
- b. Provides an option to download the error data as an Excel file.
1075
- c. Displays the error data in a grid format.
1076
- d. Flags the identified records in the main DataFrame.
1077
-
1078
- Raises:
1079
- FileNotFoundError: If the "GemeindenComuni.xlsx" file is not found.
1080
- KeyError: If expected columns are missing from either the main DataFrame or the official list.
1081
- Exception: For any other exceptions that occur during the execution.
1082
- """
1083
- # Step 1: Read the official list of cities from an Excel file
1084
- try:
1085
- dfGemeinden: pd.DataFrame = pd.read_excel("GemeindenComuni.xlsx")
1086
- except FileNotFoundError as fnf_error:
1087
- st.error(f"File not found: {fnf_error}")
1088
- return
1089
- except Exception as e:
1090
- st.error(f"An error occurred while reading 'GemeindenComuni.xlsx': {e}")
1091
- return
1092
-
1093
- # Ensure that the necessary columns exist in dfGemeinden
1094
- required_columns_gemeinden = {"Gemeinde", "Comune"}
1095
- if not required_columns_gemeinden.issubset(dfGemeinden.columns):
1096
- st.error(
1097
- f"The official list must contain the columns: {required_columns_gemeinden}"
1098
- )
1099
- return
1100
-
1101
- # Ensure that the necessary column exists in the main DataFrame
1102
- required_columns_main = {
1103
- "Comune di residenza assistente domiciliare all'infanzia",
1104
- "Cognome",
1105
- "Nome",
1106
- "filename",
1107
- }
1108
- if not required_columns_main.issubset(self.df.columns):
1109
- st.error(
1110
- f"The main DataFrame must contain the columns: {required_columns_main}"
1111
- )
1112
- return
1113
-
1114
- # Step 2: Check if the city matches either "Gemeinde" or "Comune" in dfGemeinden
1115
- # Create boolean Series indicating matches
1116
- condizione1: pd.Series = self.df[
1117
- "Comune di residenza assistente domiciliare all'infanzia"
1118
- ].isin(dfGemeinden["Gemeinde"])
1119
- condizione2: pd.Series = self.df[
1120
- "Comune di residenza assistente domiciliare all'infanzia"
1121
- ].isin(dfGemeinden["Comune"])
1122
- condizione: pd.Series = condizione1 | condizione2
1123
-
1124
- # Step 3: Identify records where the city does not match the official list
1125
- cond_mask: pd.Series = ~condizione
1126
-
1127
- if cond_mask.any():
1128
- # create a temporary dataframe for display
1129
- dftemp = self.df.loc[cond_mask].copy()
1130
-
1131
- dftemp = helper.convert_dates_if_needed(dftemp, [])
1132
-
1133
- interface.make_expander(
1134
- "casi con un nome comune Tagesmutter non corrispondente alla lista ufficiale",
1135
- "Elenco delle occorrenze con comune Tagesmutter non corrispondente",
1136
- len(dftemp[cond_mask]),
1137
- dftemp,
1138
- )
1139
-
1140
- # Step 4d: Flag the identified records in the main DataFrame
1141
- # This sets a boolean flag 'errNomeComuneTagesmutter' to True for affected rows
1142
- self.df.loc[condizione_logica, "errNomeComuneTagesmutter"] = True
1143
-
1144
- # Optional: Clean up by deleting the official list DataFrame to free memory
1145
- del dfGemeinden
1146
-
1147
-
1148
- class caricaEtControlla:
1149
- def __init__(self, interface, ERRORDICT, df_gemeinden, df_traeger):
1150
- self.filelist = interface.uploaded_files
1151
- self.ANNO_RIFERIMENTO = interface.ANNO_RIFERIMENTO
1152
- self.checks = interface.checks
1153
- self.ERRORDICT = ERRORDICT
1154
- self.df_gemeinden = df_gemeinden
1155
- self.df_traeger = df_traeger
1156
- self.errori = {}
1157
- self.errori["critici"] = {}
1158
- self.errori["non critici"] = {}
1159
- self.ComuneTedesco = interface.COMUNETEDESCO
1160
-
1161
- interface.STATUS.info(
1162
- "Sono stati caricati " + str(len(self.filelist)) + " files"
1163
- )
1164
-
1165
- def process_data(self):
1166
- """
1167
- Method to load, clean, check for errors, remove duplicates, convert datatypes and rename columns
1168
- """
1169
- self.pippo1 = ""
1170
- self.pippo2 = ""
1171
- self.dataframes = []
1172
- contatorefile = 0
1173
- df = pd.DataFrame
1174
- with interface.TAB1:
1175
- for file in self.filelist:
1176
- c1, c2 = st.columns([3, 2])
1177
- try:
1178
- interface.STATUS2.info("[*] " + file.name + " caricato")
1179
- if self.ANNO_RIFERIMENTO in ["2023","2024","2025","2026"]:
1180
- # i file excel contengono due sheet nascosti (lista comuni)
1181
- # quindi dobbiamo caricare solo quello che si chiama o "deu" o "ita"
1182
- try:
1183
- df = pd.read_excel(file, sheet_name="ita", usecols="A:M")
1184
- except:
1185
- self.pippo1 = "ita"
1186
- pass
1187
- try:
1188
- df = pd.read_excel(file, sheet_name="deu", usecols="A:M")
1189
- except:
1190
- self.pippo2 = "deu"
1191
- pass
1192
- if self.pippo1 == "ita" and self.pippo2 == "deu":
1193
- st.error(
1194
- f"file {file.name} non ha il foglio con nome o ita o deu e non viene usato"
1195
- )
1196
- # alla prima occorrenza di un errore per un dato filename, inizializziamo la lista di errori
1197
- if file.name not in self.errori["critici"]:
1198
- self.errori["critici"][file.name] = []
1199
-
1200
- self.errori["critici"][file.name].append(
1201
- "Non ha il foglio con nome o ita o deu e non viene usato"
1202
- )
1203
-
1204
- self.pippo1 = ""
1205
- self.pippo2 = ""
1206
- if interface.TOLLERANZAZERO:
1207
- st.error("Elaborazione interrotta")
1208
- st.stop()
1209
- continue
1210
- # st.write(df)
1211
-
1212
- except Exception as e:
1213
- st.error(
1214
- file.name
1215
- + " non è un file Excel o non corrisponde alle aspettative"
1216
- )
1217
-
1218
- if file.name not in self.errori:
1219
- self.errori["critici"][file.name] = []
1220
- self.errori["critici"][file.name].append(
1221
- "Non è un file Excel o non corrisponde alle aspettative"
1222
- )
1223
-
1224
- st.error(e)
1225
- if interface.TOLLERANZAZERO:
1226
- st.error("Elaborazione interrotta")
1227
- st.stop()
1228
- continue
1229
-
1230
- contatorefile += 1
1231
- c1.markdown("<div style='margin-top: 8px; margin-bottom: 20px; border-top: 3px solid #cccccc; width: 100%;'></div>", unsafe_allow_html=True)
1232
- c2.markdown("<div style='margin-top: 8px; margin-bottom: 20px; border-top: 3px solid #cccccc; width: 100%;'></div>", unsafe_allow_html=True)
1233
-
1234
- c1.info(f"**Sto elaborando {file.name}...**")
1235
-
1236
- df, flag = self.prepare_data(df, file, c2)
1237
- if flag == 1:
1238
- pass
1239
- elif flag == 2:
1240
- c2.error("----> ERRORE - vedi riga sotto...")
1241
- else:
1242
- c2.success("----> OK")
1243
-
1244
- if df is not None:
1245
- df = self.compute_hours(df)
1246
- df = self.calcola_giorni_esatti(df)
1247
- df = self.make_bool_columns(df)
1248
- # df = self.check_data(df)
1249
- # st.write(df)
1250
- self.dataframes.append(df)
1251
- # st.write(df)
1252
- # c2.success("Elaborazione OK")
1253
-
1254
- try:
1255
- # all loaded excel data gets concatenated in a new dataframe
1256
- self.data = pd.concat(self.dataframes, ignore_index=True)
1257
- except Exception as e:
1258
- st.error(
1259
- f"Errore durante concatenamento dei dataframe caricati: errore: {e}"
1260
- )
1261
- st.error("Elaborazione interrotta")
1262
- st.stop()
1263
- if interface.TOLLERANZAZERO:
1264
- st.error("Elaborazione interrotta")
1265
- st.stop()
1266
- pass
1267
-
1268
- interface.STATUS2.info(
1269
- f"Vengono usati {contatorefile} file Excel per l'elaborazione"
1270
- )
1271
-
1272
- def prepare_data(self, df, file, c2):
1273
- # estraiamo comune e nome ente da dove ci aspettiamo che siano
1274
- # nel dataframe creato dal singolo file Excel
1275
- flag = None
1276
- # st.write(df)
1277
- if self.ANNO_RIFERIMENTO in ["2023","2024", "2025"]:
1278
- df.columns = [
1279
- "Numero progressivo",
1280
- "Cognome",
1281
- "Nome",
1282
- "Data di nascita",
1283
- "Codice fiscale",
1284
- "CognomeAssDom",
1285
- "NomeAssDom",
1286
- "Comune di residenza assistente domiciliare all'infanzia",
1287
- "Data inizio contratto (o data inizio assistenza se diversa)",
1288
- "Data fine contratto (o data fine assistenza se diversa)",
1289
- "Ore di assistenza ai sensi della delibera n. 666/2019",
1290
- "Colonna_L_da_cancellare_dopo",
1291
- "Codice Comune_TM",
1292
- ]
1293
-
1294
- codice_ente = df.iloc[2, 12]
1295
- #st.write(f"codice_ente {df.iloc[2, 12]}")
1296
- codice_comune = df.iloc[4, 12]
1297
- #st.write(f"codice_comune {df.iloc[4, 12]}")
1298
- anno = df.iloc[0, 5]
1299
-
1300
- #st.write("pippo" + self.ComuneTedesco)
1301
- try:
1302
- #st.write("pippo" + self.ComuneTedesco)
1303
- if self.ComuneTedesco:
1304
- nome_comune = df_gemeinden[df_gemeinden["Com_code"] == codice_comune][
1305
- "Gemeinde"
1306
- ].values[0]
1307
- else:
1308
- nome_comune = df_gemeinden[df_gemeinden["Com_code"] == codice_comune][
1309
- "Comune"
1310
- ].values[0]
1311
- except:
1312
- st.error(
1313
- f"Errore: manca il codice comune dell'ente nel file {file.name}, il file non verra' usato"
1314
- )
1315
-
1316
- if file.name not in self.errori:
1317
- self.errori["critici"][file.name] = []
1318
- self.errori["critici"][file.name].append("Manca il codice comune dell'ente")
1319
-
1320
- flag = 2
1321
- return None, flag
1322
-
1323
- try:
1324
- nome_ente = df_traeger[df_traeger["Coop_code"] == codice_ente][
1325
- "Körperschaft"
1326
- ].values[0]
1327
- except:
1328
- st.error(
1329
- f"Errore: manca il codice ente nel file {file.name}, il file non verra' usato"
1330
- )
1331
-
1332
-
1333
- if file.name not in self.errori:
1334
- self.errori["critici"][file.name] = []
1335
- self.errori["critici"][file.name].append("Manca il codice dell'ente")
1336
-
1337
- flag = 2
1338
- return None, flag
1339
-
1340
- if codice_comune not in self.df_gemeinden["Com_code"].values:
1341
- # c2.warning("ATTENZIONE!")
1342
- st.warning(
1343
- f"L'ente '{nome_ente}' ha inserito un codice comune non corretto: '{codice_comune}'. \n\r Il file non verra' usato. \n\rIl nome file e': {file.name}"
1344
- )
1345
-
1346
- if file.name not in self.errori:
1347
- self.errori["critici"][file.name] = []
1348
- self.errori["critici"][file.name].append(
1349
- "Trovato codice comune non corretto"
1350
- )
1351
-
1352
- flag = 2
1353
- if interface.TOLLERANZAZERO or interface.DEBUGWARNINGS:
1354
- st.error("Elaborazione terminata")
1355
- st.stop()
1356
-
1357
- return None, flag
1358
-
1359
- # creiamo due nuove colonne e le riempiamo con comune ed ente
1360
- # estratti prima
1361
- df.insert(1, "Comune", nome_comune)
1362
- df.insert(1, "Ente", nome_ente)
1363
- df.insert(1, "Com_code", codice_comune)
1364
- df.insert(1, "Anno", anno)
1365
-
1366
- # scriviamo anche il nome del file perché non si sa mai che non possa servire
1367
- df.insert(0, "filename", file.name)
1368
-
1369
- # cancelliamo le righe che non ci servono
1370
- df = df.drop(labels=range(0, 8), axis=0)
1371
-
1372
- # dobbiamo intercettare se c'è un * nella colonna del numero progressivo
1373
- # sostituire l'asterisco con 99 e aggiungere l'asterisco al nome
1374
- cond = df["Numero progressivo"] == "*"
1375
- df.loc[cond, "Cognome"] += " *"
1376
- df.loc[cond, "Numero \nprogressivo"] = 99
1377
-
1378
- # prima convertiamo la colonna in numerica, forzando NaN sui non numerici
1379
- df["Numero progressivo"] = pd.to_numeric(
1380
- df["Numero progressivo"], errors="coerce"
1381
- )
1382
-
1383
- # selezioniamo solo le righe che hanno un valore nella colonna del **Codice fiscale**
1384
- # in questo modo eliminiamo le righe inutili
1385
- validi = df["Codice fiscale"].notna()
1386
-
1387
- # teniamo solo record validi
1388
- if not df[validi].empty:
1389
- df = df[validi]
1390
- else:
1391
- c2.warning("Attenzione!")
1392
- st.error(
1393
- f"Il file Excel '{file.name}' non contiene dati validi - il file non verra' usato"
1394
- )
1395
-
1396
- if file.name not in self.errori:
1397
- self.errori["critici"][file.name] = []
1398
- self.errori["critici"][file.name].append("Non contiene dati validi")
1399
-
1400
- flag = 2
1401
- if interface.DEBUGWARNINGS or interface.TOLLERANZAZERO:
1402
- st.error("Elaborazione interrotta")
1403
- st.stop()
1404
- return None, flag
1405
-
1406
- # controlliamo subito se mancano valore per le ore rendicontate
1407
- # se mancano, non usiamo l'Excel
1408
- if df["Ore di assistenza ai sensi della delibera n. 666/2019"].isnull().sum():
1409
- somma = (
1410
- df["Ore di assistenza ai sensi della delibera n. 666/2019"]
1411
- .isnull()
1412
- .sum()
1413
- )
1414
- c2.warning("Attenzione!")
1415
- st.error(
1416
- f"Il file Excel '{file.name}' contiene {somma} valori mancanti per le ore rendicontate - il file non verra' usato"
1417
- )
1418
-
1419
- if file.name not in self.errori:
1420
- self.errori["critici"][file.name] = []
1421
- self.errori["critici"][file.name].append(
1422
- "Trovati {somma} valori mancanti per le ore rendicontate"
1423
- )
1424
-
1425
- flag = 2
1426
- if interface.DEBUGWARNINGS or interface.TOLLERANZAZERO:
1427
- st.error("Elaborazione interrotta")
1428
- st.stop()
1429
- return None, flag
1430
-
1431
-
1432
- # Check if numbers in "Numero progressivo" are not continuous, i.e. a number is missing, like the "3" in "1, 2, 4, 5, etc."
1433
- # Only apply to rows with valid numeric values in "Numero progressivo"
1434
- valid_rows = df["Numero progressivo"].notna()
1435
- missing_nums = np.array([]) # Initialize missing_nums here to avoid UnboundLocalError
1436
-
1437
- if valid_rows.any():
1438
- # Get unique valid numeric values and sort them
1439
- unique_nums = df.loc[valid_rows, "Numero progressivo"].unique()
1440
- unique_nums = np.sort(unique_nums)
1441
-
1442
- # Check if the sequence is continuous
1443
- if len(unique_nums) >= 2: # Need at least 2 numbers to check continuity
1444
- expected_sequence = np.arange(unique_nums[0], unique_nums[-1] + 1)
1445
- missing_nums = np.setdiff1d(expected_sequence, unique_nums)
1446
-
1447
- if len(missing_nums) > 0:
1448
- if interface.MOSTRAERRORIINTEGRITA:
1449
- c2.warning("ATTENZIONE! Vedi riga sotto...")
1450
- flag = 1
1451
- st.warning(
1452
- f"Attenzione: l'ente '{nome_ente.upper()}' ha inserito numeri progressivi non continui nel file {file.name}. "
1453
- f"Mancano i numeri: {', '.join(map(str, missing_nums))}"
1454
- )
1455
- if file.name not in self.errori:
1456
- self.errori["non critici"][file.name] = []
1457
- self.errori["non critici"][file.name].append(
1458
- f"Trovati numeri progressivi non continui. Mancano: {', '.join(map(str, missing_nums.astype(int)))}"
1459
- )
1460
-
1461
- with st.expander(f"Numeri progressivi non continui nel file {file.name}"):
1462
- st.write(
1463
- f"Attenzione: nella sequenza dei numeri progressivi mancano i seguenti numeri: {', '.join(map(str, missing_nums.astype(int)))}"
1464
- )
1465
- # Display the sequence found in the file
1466
- st.write("Sequenza trovata nel file:")
1467
- st.write(unique_nums)
1468
-
1469
-
1470
-
1471
- # Check only rows where "Numero progressivo" is present
1472
- valid_progressivi = df["Numero progressivo"].notna()
1473
- duplicates = df[valid_progressivi]["Numero progressivo"].duplicated(keep=False)
1474
-
1475
- if not df[valid_progressivi][duplicates].empty:
1476
- if interface.MOSTRAERRORIINTEGRITA:
1477
- c2.warning("ATTENZIONE! Vedi riga sotto...")
1478
- flag = 1
1479
- st.warning(
1480
- f"Attenzione: l'ente '{nome_ente.upper()}' ha inserito numeri progressivi duplicati nel file {file.name}"
1481
- )
1482
- if file.name not in self.errori:
1483
- self.errori["non critici"][file.name] = []
1484
- self.errori["non critici"][file.name].append(
1485
- "Trovati numeri non progressivi"
1486
- )
1487
-
1488
- df_no_index = (
1489
- df.loc[
1490
- valid_progressivi & duplicates,
1491
- ["Numero progressivo", "Cognome", "Nome"],
1492
- ]
1493
- .groupby("Numero progressivo", as_index=False)
1494
- .first()
1495
- )
1496
- df_no_index["Numero progressivo"] = df_no_index[
1497
- "Numero progressivo"
1498
- ].astype(int)
1499
-
1500
- if interface.DEBUGWARNINGS or interface.TOLLERANZAZERO:
1501
- st.error("Elaborazione terminata")
1502
- st.stop()
1503
-
1504
-
1505
- with st.expander(
1506
- f"Record con numeri progressivi duplicati: {len(df_no_index)} occorrenze"
1507
- ):
1508
- st.write(
1509
- "Attenzione: i seguenti record hanno numeri progressivi duplicati e potrebbero richiedere attenzione."
1510
- )
1511
- st.write(df_no_index)
1512
-
1513
- condizione_righe_vuote = (
1514
- ~df["Numero progressivo"].isna() & df["Codice fiscale"].isna()
1515
- )
1516
-
1517
- if not df[condizione_righe_vuote].empty:
1518
- conta_righe = condizione_righe_vuote.sum()
1519
- # c2.warning("Attenzione!")
1520
- st.warning(
1521
- f"Attenzione: l'ente '{nome_ente.upper()}' ha inserito numeri progressivi per righe vuote \n\r{conta_righe} righe vuote sono state eliminate e l'elaborazione prosegue"
1522
- )
1523
- if file.name not in self.errori:
1524
- self.errori["non critici"][file.name] = []
1525
- self.errori["non critici"][file.name].append(
1526
- "Trovati numeri progressivi per righe vuote. Le righe vuote sono state eliminate."
1527
- )
1528
-
1529
- flag = 1
1530
-
1531
- df = df[~condizione_righe_vuote]
1532
- if interface.DEBUGGENERAL:
1533
- st.write(df)
1534
-
1535
- condizione_nr_progressivo_assente_ma_righe_con_dati = (
1536
- df["Numero progressivo"].isna() & ~df["Codice fiscale"].isna()
1537
- )
1538
-
1539
- if not df[condizione_nr_progressivo_assente_ma_righe_con_dati].empty:
1540
- conta_righe = condizione_nr_progressivo_assente_ma_righe_con_dati.sum()
1541
- c2.warning("Attenzione!")
1542
- st.warning(
1543
- f"Attenzione: l'ente '{nome_ente.upper()}' ha inserito righe con dati ma senza numero progressivo" # nel file {file.name}"
1544
- )
1545
- if file.name not in self.errori:
1546
- self.errori["non critici"][file.name] = []
1547
- self.errori["non critici"][file.name].append(
1548
- "Trovate righe con dati ma senza numero progressivo"
1549
- )
1550
-
1551
- flag = 1
1552
-
1553
-
1554
- # Check for duplicate children with same Tagesmütter and same start date
1555
- same_child_same_tagesmutter_same_startdate = (
1556
- df.duplicated(
1557
- subset=[
1558
- "Cognome",
1559
- "Nome",
1560
- "CognomeAssDom",
1561
- "NomeAssDom",
1562
- "Data inizio contratto (o data inizio assistenza se diversa)",
1563
- ],
1564
- keep=False,
1565
- )
1566
- )
1567
-
1568
- if same_child_same_tagesmutter_same_startdate.any():
1569
- count_duplicates = same_child_same_tagesmutter_same_startdate.sum()
1570
- #c2.warning("Attenzione!")
1571
- st.warning(
1572
- f"Attenzione: l'ente '{nome_ente.upper()}' ha inserito lo stesso bambino con la stessa Tagesmutter e stessa data di inizio {count_duplicates} volte"
1573
- )
1574
-
1575
- if file.name not in self.errori:
1576
- self.errori["non critici"][file.name] = []
1577
- self.errori["non critici"][file.name].append(
1578
- f"Trovati {count_duplicates} record duplicati con lo stesso bambino, stessa Tagesmutter e stessa data di inizio"
1579
- )
1580
-
1581
- # Display the duplicate records for debugging
1582
- # if interface.DEBUGGENERAL
1583
- with st.expander(
1584
- f"Record duplicati (stesso bambino, stessa Tagesmutter e stessa data di inizio): {count_duplicates} occorrenze"
1585
- ):
1586
- st.write(
1587
- "Attenzione: i seguenti record sono duplicati e potrebbero richiedere attenzione."
1588
- )
1589
- dups = df[same_child_same_tagesmutter_same_startdate].sort_values(
1590
- by=[
1591
- "Cognome",
1592
- "Nome",
1593
- "CognomeAssDom",
1594
- "NomeAssDom",
1595
- "Data inizio contratto (o data inizio assistenza se diversa)",
1596
- ]
1597
- )
1598
- st.write(dups)
1599
-
1600
- flag = 1
1601
-
1602
-
1603
- df = df[~condizione_nr_progressivo_assente_ma_righe_con_dati]
1604
- if interface.DEBUGGENERAL:
1605
- st.write(df)
1606
-
1607
- # elimiamo colonne che sono servono più
1608
- df = df.drop(["Numero \nprogressivo"], axis=1)
1609
-
1610
- # se troviamo data fine vuota la mettiamo al 31/12 dell'anno riferimento
1611
- condizione = pd.isnull(
1612
- df["Data fine contratto (o data fine assistenza se diversa)"]
1613
- )
1614
- df.loc[
1615
- condizione, "Data fine contratto (o data fine assistenza se diversa)"
1616
- ] = datetime(
1617
- int(self.ANNO_RIFERIMENTO), 12, 31, 0, 0, 0
1618
- ) # str(self.ANNO_RIFERIMENTO) + "-12-31 00:00:00"
1619
-
1620
- try:
1621
- df = self.validate_dates(df, "Data di nascita", file)
1622
- if df is None:
1623
- return None, flag
1624
-
1625
- except Exception as e:
1626
- c2.error("Errore!")
1627
- st.error(
1628
- f"{file.name} --> Unexpected error occurred: {e}.\n\rIl file Excel non verra' usato"
1629
- )
1630
-
1631
- if file.name not in self.errori:
1632
- self.errori["critici"][file.name] = []
1633
- self.errori["critici"][file.name].append(
1634
- "Trovato errore critico non identificato"
1635
- )
1636
-
1637
- flag = 2
1638
- if interface.DEBUGWARNINGS or interface.TOLLERANZAZERO:
1639
- st.stop()
1640
- return None, flag
1641
-
1642
- try:
1643
- df = self.validate_dates(
1644
- df, "Data fine contratto (o data fine assistenza se diversa)", file
1645
- )
1646
- if df is None:
1647
- return None, flag
1648
-
1649
- except Exception as e:
1650
- c2.warning("Errore!")
1651
- st.error(
1652
- f"{file.name} --> data fine contratto contiene valori non data; errore: {e}.\n\rIl file Excel non verra' usato"
1653
- )
1654
-
1655
- if file.name not in self.errori:
1656
- self.errori["critici"][file.name] = []
1657
- self.errori["critici"][file.name].append(
1658
- "Data fine contratto contiene valori non data"
1659
- )
1660
-
1661
- flag = 2
1662
- if interface.DEBUGWARNINGS or interface.TOLLERANZAZERO:
1663
- st.stop()
1664
- return None, flag
1665
-
1666
- try:
1667
- df = self.validate_dates(
1668
- df, "Data inizio contratto (o data inizio assistenza se diversa)", file
1669
- )
1670
- if df is None:
1671
- return None, flag
1672
- except Exception as e:
1673
- c2.warning("Errore!")
1674
- st.error(
1675
- f"{file.name} --> data inizio contratto contiene valori non data; errore: {e}.\n\rIl file Excel non verra' usato"
1676
- )
1677
-
1678
- if file.name not in self.errori:
1679
- self.errori["critici"][file.name] = []
1680
- self.errori["critici"][file.name].append(
1681
- "Data inizio contratto contiene valori non data"
1682
- )
1683
-
1684
- flag = 2
1685
- if interface.DEBUGWARNINGS or interface.TOLLERANZAZERO:
1686
- st.stop()
1687
- return None, flag
1688
-
1689
- # sostituiamo tutti i NaN con *0*
1690
- df = df.fillna(0)
1691
-
1692
- # assicuriamo che codice fiscale sia in maiuscolo
1693
- df["Codice fiscale"] = df["Codice fiscale"].str.upper()
1694
-
1695
- if "Colonna_L_da_cancellare_dopo" in df.columns:
1696
- del df["Colonna_L_da_cancellare_dopo"]
1697
-
1698
- # c2.success("OK")
1699
- return df, flag
1700
-
1701
- # Function to check for incorrect year format
1702
- def is_incorrect_year(self, date_str: str) -> bool:
1703
- """
1704
- Check if the year part of a date string is in the correct format.
1705
-
1706
- Args:
1707
- date_str (str): The date string to be checked.
1708
-
1709
- Returns:
1710
- bool: True if the year length is not 4, False otherwise.
1711
- """
1712
- year_part = str(date_str).split("-")[-3]
1713
- return len(year_part) != 4 # Check if the year length is not 4
1714
-
1715
- # Function to check and convert date formats with year validation
1716
- def validate_dates(
1717
- self, df: pd.DataFrame, date_column: str, file
1718
- ) -> Optional[pd.DataFrame]:
1719
- """
1720
- Validate and convert date formats in a specified column of a DataFrame.
1721
-
1722
- Args:
1723
- df (pd.DataFrame): The input DataFrame containing the date column.
1724
- date_column (str): The name of the column to be validated and converted.
1725
- file (FileStorage): The uploaded file object for displaying errors.
1726
-
1727
- Returns:
1728
- pd.DataFrame or None: The modified DataFrame if all dates are valid, otherwise None.
1729
- """
1730
-
1731
- if not isinstance(df, pd.DataFrame):
1732
- st.write(type(df))
1733
- raise ValueError("The input 'df' must be a pandas DataFrame.")
1734
-
1735
- if date_column not in df.columns:
1736
- st.write(df.columns)
1737
- raise KeyError(
1738
- f"The specified date_column '{date_column}' does not exist in the DataFrame."
1739
- )
1740
-
1741
- # Create a new column for the parsed dates
1742
- df["Parsed Date"] = pd.to_datetime(
1743
- df[date_column], format="%d-%m-%Y", errors="coerce"
1744
- ) # .dt.strftime('%d-%m-%Y')#.dt.date
1745
-
1746
- # Identify rows with invalid dates in the new column
1747
- invalid_dates_df = df[df["Parsed Date"].isna()]
1748
-
1749
- if not invalid_dates_df.empty:
1750
- # Display the error message with original wrong dates and additional row information
1751
- st.error(
1752
- f"{file.name} --> {date_column} contiene valori non data; errore: dati non validi.\n\rIl file Excel non verra' usato"
1753
- )
1754
-
1755
- # st.write(f"Righe con {date_column} invalide:", invalid_dates_df) # Show the full rows
1756
- with st.expander(f"Righe con {date_column} invalide:"):
1757
- st.write(invalid_dates_df)
1758
- if file.name not in self.errori:
1759
- self.errori["critici"][file.name] = []
1760
- self.errori["critici"][file.name].append(
1761
- f"{date_column} contiene valori non data"
1762
- )
1763
-
1764
- if interface.DEBUGWARNINGS or interface.TOLLERANZAZERO:
1765
- st.stop()
1766
-
1767
- df.drop("Parsed Date", axis=1, inplace=True)
1768
- flag = 1
1769
- # st.write(df)
1770
- return None
1771
-
1772
- df[date_column] = df["Parsed Date"]
1773
- df.drop("Parsed Date", axis=1, inplace=True)
1774
-
1775
- return df
1776
-
1777
- def compute_hours(self, df: pd.DataFrame) -> pd.DataFrame:
1778
- """
1779
- Compute the number of hours based on contract start and end dates.
1780
-
1781
- Args:
1782
- df (pd.DataFrame): The input DataFrame containing the date columns.
1783
-
1784
- Returns:
1785
- pd.DataFrame: Modified DataFrame with computed hours.
1786
- """
1787
- ar = int(self.ANNO_RIFERIMENTO) # Convert anno riferimento to an integer
1788
-
1789
- df["inizioNorm"] = df[
1790
- "Data inizio contratto (o data inizio assistenza se diversa)"
1791
- ] # intanto inseriamo i valori che ci sono
1792
-
1793
- df["fineNorm"] = df["Data fine contratto (o data fine assistenza se diversa)"]
1794
-
1795
- # If the start date is before the reference year, set it to January 1st of the reference year
1796
- iniz = df["inizioNorm"].dt.year < ar # Logical condition
1797
- df.loc[iniz, "inizioNorm"] = "01-01-" + str(
1798
- ar
1799
- ) # Find values less than the reference year and replace with 01/01
1800
- fin = df["fineNorm"].dt.year > ar # Logical condition
1801
- df.loc[fin, "fineNorm"] = "12-31-" + str(ar) # Replace
1802
- df.insert(
1803
- 9, "GiorniAssistenzaAnnoRiferimento", 0
1804
- ) # Add a column and set the reference year
1805
- return df
1806
-
1807
- def calcola_giorni_esatti(self, df: pd.DataFrame) -> pd.DataFrame:
1808
- """
1809
- Calculates the exact number of days of assistance for each person in the given DataFrame.
1810
-
1811
- Args:
1812
- - df (pandas.DataFrame): The DataFrame containing the assistance data for each person.
1813
-
1814
- Returns:
1815
- - pandas.DataFrame: The input DataFrame with an additional column containing the exact number of days of assistance for each person.
1816
- """
1817
-
1818
- df = df.reset_index(drop=True)
1819
- df = df.sort_values(
1820
- by="Data inizio contratto (o data inizio assistenza se diversa)"
1821
- ) # importante sort, altrimento non calcola giusto
1822
- # Group the DataFrame by Codice fiscale
1823
- for codice_fiscale, group in df.groupby("Codice fiscale"):
1824
- if interface.DEBUGGENERAL:
1825
- st.info(f"codice fiscale={codice_fiscale}")
1826
-
1827
- # Initialize variables for the earliest startdate and latest enddate
1828
- earliest_startdate = group.iloc[0]["inizioNorm"]
1829
- latest_enddate = group.iloc[0]["fineNorm"]
1830
-
1831
- if interface.DEBUGGENERAL:
1832
- st.info(f"earliest startdate={earliest_startdate}")
1833
- st.info(f"latest enddate={latest_enddate}")
1834
-
1835
- # Initialize variable for total effective days
1836
- total_effective_days = 0
1837
-
1838
- # Iterate through each row in the group
1839
- for index, row in group.iterrows():
1840
- if interface.DEBUGGENERAL:
1841
- st.info(f"index={index}")
1842
- # If the enddate is earlier than the latest enddate, skip this row
1843
- if row["fineNorm"] < latest_enddate:
1844
- continue
1845
-
1846
- # If the startdate is later than the latest enddate, add the effective days between the earliest startdate and latest enddate to the total effective days
1847
- elif row["inizioNorm"] > latest_enddate:
1848
- total_effective_days += (
1849
- latest_enddate - earliest_startdate
1850
- ).days + 1
1851
- earliest_startdate = row["inizioNorm"]
1852
-
1853
- # Update the latest enddate
1854
- latest_enddate = row["fineNorm"]
1855
- indx = index
1856
- if interface.DEBUGGENERAL:
1857
- st.info(f"temp total effective days={total_effective_days}")
1858
- st.info(f"temp earliest startdate{earliest_startdate}")
1859
-
1860
- # Add the effective days between the earliest startdate and latest enddate to the total effective days
1861
- total_effective_days += (latest_enddate - earliest_startdate).days
1862
-
1863
- # Insert the number of days of assistance into the appropriate row of the DataFrame
1864
- # We use the last index in the group because it is the only one that we need to update, and the function that sums the values is correct because it will sum the value with zeros
1865
- if interface.DEBUGGENERAL:
1866
- st.info(f"last index={indx}")
1867
- if interface.DEBUGGENERAL:
1868
- st.info(f"giorni totali={total_effective_days}")
1869
-
1870
- # df["GiorniAssistenzaAnnoRiferimento"][indx] = total_effective_days + 1
1871
- # OR IT WORKS ALSO WITH -->
1872
- df.loc[group.index[-1], "GiorniAssistenzaAnnoRiferimento"] = (
1873
- total_effective_days + 1
1874
- )
1875
-
1876
- return df
1877
-
1878
- def get_data(self) -> pd.DataFrame:
1879
- """
1880
- Method to return the processed data.
1881
- Returns:
1882
- pd.DataFrame: The DataFrame containing the processed data.
1883
- """
1884
- return self.data
1885
-
1886
- def make_bool_columns(self, df: pd.DataFrame) -> pd.DataFrame:
1887
- """
1888
- Adds boolean columns for each error in ERRORDICT for the given year of reference.
1889
-
1890
- Args:
1891
- df (pd.DataFrame): The DataFrame to add boolean columns to.
1892
- Returns:
1893
- pd.DataFrame: The DataFrame with added boolean columns.
1894
- """
1895
- for error_key in self.ERRORDICT[self.ANNO_RIFERIMENTO].keys():
1896
- df[error_key] = np.nan
1897
- df[error_key] = df[error_key].astype("boolean")
1898
-
1899
- return df
1900
-
1901
-
1902
- if __name__ == "__main__":
1903
-
1904
- valori = valoriEtErrori()
1905
- interface = makeInterface(valori)
1906
- helper = helpers(interface, valori.ERRORDICT)
1907
- # trova_errori = trovaErrori()
1908
-
1909
- dtype_spec = {
1910
- "Com_code": str # non vogliamo convertire questa colonna to number perche' nell'Excel trovaimo come stringa
1911
- }
1912
- try:
1913
-
1914
- # Get the directory where the script is located
1915
- # script_dir = os.path.dirname(os.path.abspath(__file__))
1916
-
1917
- # Build the full path to the Excel file
1918
- # file_path = os.path.join(script_dir, "GemeindenMitKodex.xlsx")
1919
- #st.write(file_path)
1920
-
1921
- # Read the file
1922
- try:
1923
- df_gemeinden = pd.read_excel("src/GemeindenMitKodex.xlsx", dtype=dtype_spec)
1924
- #st.write(f"Columns 1: {df_gemeinden.columns} ({type(df_gemeinden.columns)})")
1925
- _df_gemeinden_backup = df_gemeinden.copy()
1926
- except Exception as e:
1927
- st.error(f"Primo; errore: {e}")
1928
-
1929
- try:
1930
- df_gemeinden = df_gemeinden[df_gemeinden["PLZ"] != 9999]
1931
- #st.write(f"Columns 2: {df_gemeinden.columns} ({type(df_gemeinden.columns)})")
1932
- if not isinstance(df_gemeinden, pd.DataFrame):
1933
- st.error("ATTENZIONE: df_gemeinden è stato sovrascritto — non è più un DataFrame!")
1934
- df_gemeinden = _df_gemeinden_backup.copy()
1935
- st.warning("df_gemeinden è stato ripristinato dalla copia di backup.")
1936
- except Exception as e:
1937
- st.write(df_gemeinden)
1938
- st.error(f"Secondo; errore: {e}")
1939
- #df_gemeinden = pd.read_excel(
1940
- # "GemeindenMitKodex.xlsx", dtype=dtype_spec
1941
- #)
1942
- #df_gemeinden = df_gemeinden[df_gemeinden["PLZ"] != 9999]
1943
-
1944
- except Exception as e:
1945
- #st.write(file_path)
1946
- #st.write(f"dtype_spec: {dtype_spec} ({type(dtype_spec)})")
1947
-
1948
- st.error(f"Errore durante caricamento file dei comuni; Errore: {e}")
1949
- st.error("Chiamare Stefan")
1950
- st.stop()
1951
-
1952
- try:
1953
- #script_dir = os.path.dirname(os.path.abspath(__file__))
1954
- # Build the full path to the Excel file
1955
- #file_path = os.path.join(script_dir, "TM_TraegerMitKodex.xlsx")
1956
- df_traeger = pd.read_excel("src/TM_TraegerMitKodex.xlsx", dtype=dtype_spec)
1957
-
1958
- except Exception as e:
1959
- st.error(f"Errore durante caricamento anagrafica enti; errore: {e}")
1960
- st.error("Chiamare Stefan")
1961
- st.stop()
1962
-
1963
- if interface.submit:
1964
- if not interface.uploaded_allegato666:
1965
- st.error("L'allegato 666 non e' stato caricato - elaborazione interrotta")
1966
- df_allegato666 = None
1967
- st.stop()
1968
- else:
1969
- try:
1970
- df_allegato666 = pd.read_excel(interface.uploaded_allegato666)
1971
- if df_allegato666.columns[24] != 666:
1972
- st.error(
1973
- f"Non e' stato trovato il valore di controllo nell'allegato 666; l'elaborazione viene terminata."
1974
- )
1975
- st.stop()
1976
- df_allegato666 = df_allegato666.drop(labels=range(0, 8), axis=0)
1977
- df_allegato666 = df_allegato666.drop(labels=range(124, 129), axis=0)
1978
- cols_to_drop = df_allegato666.columns[10:]
1979
- df_allegato666 = df_allegato666.drop(columns=cols_to_drop)
1980
- df_allegato666 = df_allegato666.drop(columns="Unnamed: 8")
1981
-
1982
- # ridefiniamo i nomi delle colonne
1983
- df_allegato666.columns = [
1984
- "Comune provenienza bambino",
1985
- "Numero utenti",
1986
- "Ore di servizio",
1987
- "Costo orario",
1988
- "Costo del servizio",
1989
- "Entrate compartecipazione",
1990
- "Ore tariffa maggiorata",
1991
- "Entrate maggiorazione",
1992
- "Com_code",
1993
- ]
1994
- if interface.DEBUGGENERAL:
1995
- st.write(df_allegato666)
1996
- except Exception as e:
1997
- st.error(
1998
- f"Errore durante il caricamento dell'allegato 666: errore: {e}"
1999
- )
2000
- st.error("Impossibile proseguire")
2001
- st.stop()
2002
-
2003
- if len(interface.uploaded_files) == 0:
2004
- st.error("Nessun file caricato!")
2005
- else:
2006
- interface.make_tabs()
2007
- processor = caricaEtControlla(
2008
- interface, valori.ERRORDICT, df_gemeinden, df_traeger
2009
- )
2010
- processor.process_data()
2011
-
2012
- trova_errori = trovaErrori(
2013
- processor.get_data(), interface, df_gemeinden, df_allegato666
2014
- )
2015
- # st.write(processor.errori)
2016
- with interface.TAB3:
2017
- reportmaker = ReportGenerator(processor, trova_errori, interface)
2018
- reportmaker.genera_report()
2019
- reportmaker.display_final_table()
2020
-
2021
- interface.STATUS2.write("")
2022
- interface.STATUS.write("")
2023
- interface.STATUS3.success(
2024
- "[*] Tutti i dati sono stati elaborati - PER INIZIARE DA CAPO: RICARICARE LA PAGINA"
2025
- )
2026
- # st.balloons()