Upload 2 files
Browse files
src/01_TagesMuetter_RobertaBovoEdition.py
ADDED
|
@@ -0,0 +1,2026 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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()
|