responds-forms 0.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cartas/__init__.py +2060 -0
- cartas/origin.py +1196 -0
- pyflutter_ui/__init__.py +30 -0
- pyflutter_ui/app.py +289 -0
- pyflutter_ui/file_picker.py +274 -0
- pyflutter_ui/navigator.py +93 -0
- pyflutter_ui/scheduler.py +154 -0
- pyflutter_ui/theme.py +1058 -0
- pyflutter_ui/ui/__init__.py +27 -0
- pyflutter_ui/ui/buttons.py +219 -0
- pyflutter_ui/ui/container.py +119 -0
- pyflutter_ui/ui/core.py +820 -0
- pyflutter_ui/ui/list_builder.py +396 -0
- pyflutter_ui/ui/progress.py +80 -0
- pyflutter_ui/ui/tables.py +109 -0
- pyflutter_ui/ui/widgets.py +1418 -0
- responds_forms-0.4.dist-info/METADATA +15 -0
- responds_forms-0.4.dist-info/RECORD +38 -0
- responds_forms-0.4.dist-info/WHEEL +4 -0
- responds_ui/__init__.py +1 -0
- responds_ui/__main__.py +25 -0
- responds_ui/assets/forms_config.json +57 -0
- responds_ui/config_app.py +109 -0
- responds_ui/lib_toi.py +864 -0
- responds_ui/page_forms/Diagnorstico.txt +108 -0
- responds_ui/page_forms/__init__.py +5 -0
- responds_ui/page_forms/common.py +192 -0
- responds_ui/page_forms/config.py +68 -0
- responds_ui/page_forms/controller_forms.py +390 -0
- responds_ui/page_forms/diagnose_forms.py +64 -0
- responds_ui/page_forms/master_forms.py +113 -0
- responds_ui/page_forms/model_forms.py +329 -0
- responds_ui/page_forms/service.py +347 -0
- responds_ui/page_forms/view_forms.py +164 -0
- responds_ui/table_widget.py +140 -0
- responds_ui/util.py +74 -0
- responds_ui/version.py +2 -0
- whatsapp_files/__init__.py +1092 -0
cartas/origin.py
ADDED
|
@@ -0,0 +1,1196 @@
|
|
|
1
|
+
# arquivo cartas/__init__.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
import re
|
|
5
|
+
from enum import StrEnum
|
|
6
|
+
from typing import Any
|
|
7
|
+
from abc import ABC, abstractmethod
|
|
8
|
+
from os import rename as rename_file
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
|
|
11
|
+
import pandas as pd
|
|
12
|
+
|
|
13
|
+
from table_stream import (
|
|
14
|
+
# Array
|
|
15
|
+
HashMapDict,
|
|
16
|
+
HashMap,
|
|
17
|
+
ArrayList,
|
|
18
|
+
Set,
|
|
19
|
+
# Tabelas
|
|
20
|
+
TableData,
|
|
21
|
+
ArrayTables,
|
|
22
|
+
ColumnV,
|
|
23
|
+
RowH,
|
|
24
|
+
HeadersSizeNotIqualError,
|
|
25
|
+
# Arquivos.
|
|
26
|
+
HeaderMetaData,
|
|
27
|
+
hashFile,
|
|
28
|
+
File,
|
|
29
|
+
Directory,
|
|
30
|
+
ContentFiles,
|
|
31
|
+
load_file_hash,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
from ocr_stream import HeaderOcr, BuilderConfigOcr, CreateRelatorioOcr
|
|
35
|
+
|
|
36
|
+
from whatsapp_files import (
|
|
37
|
+
RowWhatsApp,
|
|
38
|
+
TableWhatsApp,
|
|
39
|
+
HeaderWhatsApp,
|
|
40
|
+
BuilderTableWhatsApp,
|
|
41
|
+
ControllerWhatsApp,
|
|
42
|
+
move_path,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
__version__ = "0.1"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
class ConfigTOI:
|
|
50
|
+
|
|
51
|
+
file_relatorio_toi: File
|
|
52
|
+
file_relatorio_forms: File
|
|
53
|
+
file_table_toi: File
|
|
54
|
+
file_master_forms: File # NOVO
|
|
55
|
+
dir_base_toi: Directory
|
|
56
|
+
dir_log_excel: Directory
|
|
57
|
+
dir_relatorios: Directory
|
|
58
|
+
work_dir: Directory
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class BuilderConfigTOI(object):
|
|
62
|
+
|
|
63
|
+
_instance = None # Atributo de classe para armazenar a instância singleton
|
|
64
|
+
|
|
65
|
+
def __new__(cls, *args, **kwargs):
|
|
66
|
+
if cls._instance is None:
|
|
67
|
+
cls._instance = super(BuilderConfigTOI, cls).__new__(cls)
|
|
68
|
+
return cls._instance
|
|
69
|
+
|
|
70
|
+
def __init__(self) -> None:
|
|
71
|
+
|
|
72
|
+
# Garante que __init__ não será executado mais de uma vez
|
|
73
|
+
if hasattr(self, "_initialized") and self._initialized:
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
super().__init__()
|
|
77
|
+
self._initialized: bool = True
|
|
78
|
+
self._builder_ocr = BuilderConfigOcr.get_instance()
|
|
79
|
+
self._config = self._create()
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def dir_base_work(self) -> Directory:
|
|
83
|
+
return self._builder_ocr.builder().dir_base_work
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def dir_retorno_de_campo(self) -> Directory:
|
|
87
|
+
return self.dir_base_work.concat("Cartas-TOI", True).concat(
|
|
88
|
+
"Retorno-de-campo", True
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def dir_log_excel(self) -> Directory:
|
|
93
|
+
return self.dir_base_work.concat("Cartas-TOI", True).concat("Logs", True)
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def dir_relatorios(self) -> Directory:
|
|
97
|
+
return self.dir_base_work.concat("Cartas-TOI", True).concat(
|
|
98
|
+
"Relatorios-toi", True
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def file_relatorio_toi(self) -> File:
|
|
103
|
+
d = self._builder_ocr.builder().dir_table
|
|
104
|
+
return d.join_file("Relatorio-tois.xlsx")
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def file_relatorio_forms(self) -> File:
|
|
108
|
+
d = self._builder_ocr.builder().dir_table
|
|
109
|
+
return d.join_file("cartas-toi-lancadas.xlsx")
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def file_master_forms(self) -> File:
|
|
113
|
+
d = self._builder_ocr.builder().dir_table
|
|
114
|
+
return d.join_file("master-forms-toi.xlsx")
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def file_table_toi(self) -> File:
|
|
118
|
+
d = self._builder_ocr.builder().dir_table
|
|
119
|
+
return d.join_file("tabela-entregas-tois.xlsx")
|
|
120
|
+
|
|
121
|
+
def _create(self) -> ConfigTOI:
|
|
122
|
+
return ConfigTOI(
|
|
123
|
+
work_dir=self.dir_base_work,
|
|
124
|
+
dir_base_toi=self.dir_retorno_de_campo,
|
|
125
|
+
file_relatorio_toi=self.file_relatorio_toi,
|
|
126
|
+
file_table_toi=self.file_table_toi,
|
|
127
|
+
file_relatorio_forms=self.file_relatorio_forms,
|
|
128
|
+
dir_log_excel=self.dir_log_excel,
|
|
129
|
+
dir_relatorios=self.dir_relatorios,
|
|
130
|
+
file_master_forms=self.file_master_forms,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
def builder(self) -> ConfigTOI:
|
|
134
|
+
if self._config is None:
|
|
135
|
+
self._config = self._create()
|
|
136
|
+
return self._config
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
# ============================================================#
|
|
140
|
+
# Extração de UC/TOI tolerante a falhas de OCR
|
|
141
|
+
# ============================================================#
|
|
142
|
+
#
|
|
143
|
+
# UC: 1305934 TOI: 193166923 ...
|
|
144
|
+
# UC: 1305934 TO! 193166923 ... (TOI virou TO!, ":" some)
|
|
145
|
+
# UC| 1305934 T0I: 193166923 ... (0 no lugar do O)
|
|
146
|
+
#
|
|
147
|
+
|
|
148
|
+
UC_TOI_PATTERN = re.compile(
|
|
149
|
+
r"U[C0]\s*[:;|!]?\s*(\d{4,10})" r".{0,60}?" r"T[O0][I1L!l]?\s*[:;|!]?\s*(\d{4,10})",
|
|
150
|
+
re.IGNORECASE | re.DOTALL,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def extrair_uc_toi(texto: str) -> tuple[str, str] | None:
|
|
155
|
+
"""
|
|
156
|
+
Retorna (uc, toi) a partir do texto OCR completo de um arquivo,
|
|
157
|
+
ou None se o padrão não for encontrado.
|
|
158
|
+
"""
|
|
159
|
+
m = UC_TOI_PATTERN.search(texto)
|
|
160
|
+
if not m:
|
|
161
|
+
return None
|
|
162
|
+
return m.group(1), m.group(2)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
# ========================================================#
|
|
166
|
+
# Tabelas TOI.
|
|
167
|
+
# ========================================================#
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class HeaderEntregaTOI(StrEnum):
|
|
171
|
+
"""
|
|
172
|
+
Enumerador que representa a relação entre um documento e os dados
|
|
173
|
+
de entrega:
|
|
174
|
+
UC, TOI, DAY, MON, YEAR, DATE E HASH_FILE.
|
|
175
|
+
|
|
176
|
+
a relação/key de uma entrega e a hash do documento.
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
UC = "uc"
|
|
180
|
+
TOI = "toi"
|
|
181
|
+
DAY = "day"
|
|
182
|
+
MON = "mon"
|
|
183
|
+
YEAR = "year"
|
|
184
|
+
DATE = "data_entrega"
|
|
185
|
+
HASH_FILE = HeaderMetaData.HASH_FILE.value
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class HeaderFilesTOI(StrEnum):
|
|
189
|
+
"""
|
|
190
|
+
Dados completos de um arquivo TOI, incluindo os metadados e
|
|
191
|
+
caminho absoluto.
|
|
192
|
+
"""
|
|
193
|
+
|
|
194
|
+
UC = HeaderEntregaTOI.UC
|
|
195
|
+
TOI = HeaderEntregaTOI.TOI
|
|
196
|
+
DAY = HeaderEntregaTOI.DAY
|
|
197
|
+
MON = HeaderEntregaTOI.MON
|
|
198
|
+
YEAR = HeaderEntregaTOI.YEAR
|
|
199
|
+
DATE = HeaderEntregaTOI.DATE
|
|
200
|
+
HASH_FILE = HeaderEntregaTOI.HASH_FILE
|
|
201
|
+
|
|
202
|
+
LOCAL_ENTREGA = "local_entrega"
|
|
203
|
+
TIPO_DOC = "tipo_carta"
|
|
204
|
+
|
|
205
|
+
IS_FILE = "arquivo?"
|
|
206
|
+
FULL_PATH = HeaderMetaData.FILE_PATH.value
|
|
207
|
+
SRC = HeaderMetaData.PARENT_DIR.value
|
|
208
|
+
FILE_EXTENSION = HeaderMetaData.FILE_EXTENSION.value
|
|
209
|
+
FILE_BASENAME = "basename"
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
class TipoCarta(StrEnum):
|
|
213
|
+
|
|
214
|
+
CARTA_CALCULO = "CARTA-CALCULO"
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
class LocalEntregaCarta(StrEnum):
|
|
218
|
+
|
|
219
|
+
OUTRO = "OUTRO"
|
|
220
|
+
MAOS = "MÃOS"
|
|
221
|
+
CORREIOS = "CORREIOS"
|
|
222
|
+
PORTAO = "PORTÃO"
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
# ========================================================#
|
|
226
|
+
# Criar Tabelas.
|
|
227
|
+
# ========================================================#
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
class BuilderTOI(ABC):
|
|
231
|
+
"""
|
|
232
|
+
Criar dados de uma entrega TOI apartir de um arquivo ou do texto
|
|
233
|
+
de um documento.
|
|
234
|
+
"""
|
|
235
|
+
|
|
236
|
+
def __init__(self) -> None:
|
|
237
|
+
super().__init__()
|
|
238
|
+
|
|
239
|
+
@abstractmethod
|
|
240
|
+
def create_void(self) -> TableData[Any]:
|
|
241
|
+
pass
|
|
242
|
+
|
|
243
|
+
@abstractmethod
|
|
244
|
+
def from_data(self, df: pd.DataFrame) -> TableData[Any]:
|
|
245
|
+
pass
|
|
246
|
+
|
|
247
|
+
@abstractmethod
|
|
248
|
+
def from_path_name(self, path: File, md5: hashFile = None) -> TableData[Any] | None:
|
|
249
|
+
pass
|
|
250
|
+
|
|
251
|
+
@abstractmethod
|
|
252
|
+
def from_hash_files(
|
|
253
|
+
self, data: HashMap[hashFile, File], *, ignore_null: bool = True
|
|
254
|
+
) -> TableData[Any] | None:
|
|
255
|
+
pass
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
class BuilderEntregaTOI(BuilderTOI):
|
|
259
|
+
"""
|
|
260
|
+
Criar dados de uma entrega TOI apartir de um arquivo ou do texto
|
|
261
|
+
de um documento.
|
|
262
|
+
"""
|
|
263
|
+
|
|
264
|
+
def __init__(self) -> None:
|
|
265
|
+
super().__init__()
|
|
266
|
+
self.relatorio_ocr = CreateRelatorioOcr()
|
|
267
|
+
self.relatorio_wp = ControllerWhatsApp()
|
|
268
|
+
|
|
269
|
+
self.default_cols_toi = ArrayList[HeaderEntregaTOI].from_list(
|
|
270
|
+
[
|
|
271
|
+
HeaderEntregaTOI.UC,
|
|
272
|
+
HeaderEntregaTOI.TOI,
|
|
273
|
+
HeaderEntregaTOI.DATE,
|
|
274
|
+
HeaderEntregaTOI.YEAR,
|
|
275
|
+
HeaderEntregaTOI.MON,
|
|
276
|
+
HeaderEntregaTOI.DAY,
|
|
277
|
+
HeaderEntregaTOI.HASH_FILE,
|
|
278
|
+
]
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
def create_void(self) -> TableData[HeaderEntregaTOI]:
|
|
282
|
+
"""
|
|
283
|
+
Cria uma tabela TOI vazia com todos os itens do cabeçalho HeaderTOI
|
|
284
|
+
"""
|
|
285
|
+
tb = TableData[HeaderEntregaTOI]()
|
|
286
|
+
self.default_cols_toi.for_each(lambda c: tb.add(ColumnV(c)))
|
|
287
|
+
return tb
|
|
288
|
+
|
|
289
|
+
def from_data(self, df: pd.DataFrame) -> TableData[HeaderEntregaTOI]:
|
|
290
|
+
"""
|
|
291
|
+
Criar tabela TOI apartir de um DataFrame().
|
|
292
|
+
"""
|
|
293
|
+
df = df.astype(str)
|
|
294
|
+
df_cols = ArrayList(df.columns.tolist())
|
|
295
|
+
tb = TableData[HeaderEntregaTOI]()
|
|
296
|
+
|
|
297
|
+
c: HeaderEntregaTOI
|
|
298
|
+
for c in self.default_cols_toi:
|
|
299
|
+
if df_cols.contains(c.value):
|
|
300
|
+
tb.add(
|
|
301
|
+
ColumnV.create_from_list(c, df[c.value].astype(str).values.tolist())
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
if tb.header().size() != self.default_cols_toi.size():
|
|
305
|
+
raise HeadersSizeNotIqualError(
|
|
306
|
+
f"DataFrame() de entrega toi com colunas diferente do padrão: \n{tb.header()}"
|
|
307
|
+
)
|
|
308
|
+
return tb
|
|
309
|
+
|
|
310
|
+
def from_path_name(
|
|
311
|
+
self, path: File, md5: hashFile = None
|
|
312
|
+
) -> TableData[HeaderEntregaTOI] | None:
|
|
313
|
+
"""
|
|
314
|
+
Criar tabela apartir do nome de uma arquivo que esteja no padrão TOI.
|
|
315
|
+
"""
|
|
316
|
+
|
|
317
|
+
if not path.exists():
|
|
318
|
+
return None
|
|
319
|
+
|
|
320
|
+
mp = self.create_map_from_filename(path.name())
|
|
321
|
+
if mp is None:
|
|
322
|
+
return None
|
|
323
|
+
if md5 is None:
|
|
324
|
+
md5 = load_file_hash(path)
|
|
325
|
+
|
|
326
|
+
mp.set_value(HeaderEntregaTOI.HASH_FILE, md5)
|
|
327
|
+
tb = TableData[HeaderEntregaTOI]()
|
|
328
|
+
c: HeaderEntregaTOI
|
|
329
|
+
|
|
330
|
+
for c in self.default_cols_toi:
|
|
331
|
+
tb.add(ColumnV.create_from_list(c, [mp.get_value(c)]))
|
|
332
|
+
return tb
|
|
333
|
+
|
|
334
|
+
def create_map_from_filename(
|
|
335
|
+
self,
|
|
336
|
+
filename: str,
|
|
337
|
+
) -> HashMapDict[HeaderEntregaTOI, str] | None:
|
|
338
|
+
"""
|
|
339
|
+
Retorna um Map de chave/valor com as seguintes chaves:
|
|
340
|
+
|
|
341
|
+
UC, TOI, DAY, MON, YEAR e DATE.
|
|
342
|
+
"""
|
|
343
|
+
|
|
344
|
+
try:
|
|
345
|
+
values = filename.split(" ")
|
|
346
|
+
if len(values) < 5:
|
|
347
|
+
# Nome incompleto ou inválido.
|
|
348
|
+
return None
|
|
349
|
+
except:
|
|
350
|
+
return None
|
|
351
|
+
else:
|
|
352
|
+
for x in values:
|
|
353
|
+
if not x.isnumeric():
|
|
354
|
+
return None
|
|
355
|
+
|
|
356
|
+
data = HashMapDict()
|
|
357
|
+
data[HeaderEntregaTOI.UC] = values[0] # UC
|
|
358
|
+
data[HeaderEntregaTOI.TOI] = values[1] # TOI
|
|
359
|
+
data[HeaderEntregaTOI.DAY] = values[2] # DIA
|
|
360
|
+
data[HeaderEntregaTOI.MON] = values[3] # MÊS
|
|
361
|
+
data[HeaderEntregaTOI.YEAR] = values[4] # ANO
|
|
362
|
+
data[HeaderEntregaTOI.DATE] = f"{values[2]}/{values[3]}/{values[4]}"
|
|
363
|
+
return data
|
|
364
|
+
|
|
365
|
+
def from_hash_files(
|
|
366
|
+
self, data: HashMap[hashFile, File], *, ignore_null: bool = True
|
|
367
|
+
) -> TableData[HeaderEntregaTOI] | None:
|
|
368
|
+
|
|
369
|
+
if data.is_empty():
|
|
370
|
+
return None
|
|
371
|
+
|
|
372
|
+
header_hashs = data.header().to_set()
|
|
373
|
+
table_ocr: TableData[HeaderOcr] | None = self.relatorio_ocr.create_from_hashs(
|
|
374
|
+
header_hashs
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
if table_ocr is None:
|
|
378
|
+
return None
|
|
379
|
+
if table_ocr.is_empty():
|
|
380
|
+
return None
|
|
381
|
+
|
|
382
|
+
# builder_wp = BuilderTableWhatsApp()
|
|
383
|
+
|
|
384
|
+
table_whatsapp: TableData[HeaderWhatsApp] = self.relatorio_wp.load_cache_table()
|
|
385
|
+
if table_whatsapp.is_empty():
|
|
386
|
+
if ignore_null:
|
|
387
|
+
return None
|
|
388
|
+
|
|
389
|
+
key_hash: hashFile
|
|
390
|
+
sequence_tables = ArrayTables[TableData[HeaderEntregaTOI]]()
|
|
391
|
+
|
|
392
|
+
for key_hash in header_hashs:
|
|
393
|
+
date_chat_file: str = TableData.NULL_STRING
|
|
394
|
+
day = TableData.NULL_STRING
|
|
395
|
+
mon = TableData.NULL_STRING
|
|
396
|
+
year = TableData.NULL_STRING
|
|
397
|
+
uc = TableData.NULL_STRING
|
|
398
|
+
toi = TableData.NULL_STRING
|
|
399
|
+
|
|
400
|
+
# =========================================================#
|
|
401
|
+
# Data de envio do arquivo no chat
|
|
402
|
+
# =========================================================#
|
|
403
|
+
if not table_whatsapp.is_empty():
|
|
404
|
+
tb_chat = table_whatsapp.search(HeaderWhatsApp.hash_md5, key_hash)
|
|
405
|
+
|
|
406
|
+
if tb_chat.is_empty() and ignore_null:
|
|
407
|
+
continue
|
|
408
|
+
|
|
409
|
+
if not tb_chat.is_empty():
|
|
410
|
+
# date_chat_file: datetime = tb_chat.get_row_at(0).get_date_hour()
|
|
411
|
+
# day = str(date_chat_file.day)
|
|
412
|
+
# mon = str(date_chat_file.month)
|
|
413
|
+
# year = str(date_chat_file.year)
|
|
414
|
+
date_chat_file: str = tb_chat.get_row_at(0).get_date().get_first()
|
|
415
|
+
day = date_chat_file.split(" ")[0]
|
|
416
|
+
mon = date_chat_file.split(" ")[1]
|
|
417
|
+
year = date_chat_file.split(" ")[2]
|
|
418
|
+
|
|
419
|
+
# =========================================================#
|
|
420
|
+
# Extrair UC e TOI
|
|
421
|
+
# =========================================================#
|
|
422
|
+
tb_ocr = table_ocr.search(HeaderOcr.HASH_FILE, key_hash)
|
|
423
|
+
if tb_ocr.is_empty() and ignore_null:
|
|
424
|
+
continue
|
|
425
|
+
|
|
426
|
+
if not tb_ocr.is_empty():
|
|
427
|
+
txt_ocr: str = tb_ocr.to_string(HeaderOcr.TEXT)
|
|
428
|
+
value_uc_toi: tuple[str, str] | None = extrair_uc_toi(txt_ocr)
|
|
429
|
+
|
|
430
|
+
if value_uc_toi is None and ignore_null:
|
|
431
|
+
continue
|
|
432
|
+
|
|
433
|
+
if value_uc_toi is not None:
|
|
434
|
+
uc = value_uc_toi[0]
|
|
435
|
+
toi = value_uc_toi[1]
|
|
436
|
+
|
|
437
|
+
row_toi = RowH[HeaderEntregaTOI, Any](0)
|
|
438
|
+
row_toi.set(HeaderEntregaTOI.UC, uc)
|
|
439
|
+
row_toi.set(HeaderEntregaTOI.TOI, toi)
|
|
440
|
+
row_toi.set(HeaderEntregaTOI.DAY, day)
|
|
441
|
+
row_toi.set(HeaderEntregaTOI.MON, mon)
|
|
442
|
+
row_toi.set(HeaderEntregaTOI.YEAR, year)
|
|
443
|
+
row_toi.set(HeaderEntregaTOI.HASH_FILE, key_hash)
|
|
444
|
+
row_toi.set(HeaderEntregaTOI.DATE, date_chat_file)
|
|
445
|
+
|
|
446
|
+
t = self.create_void()
|
|
447
|
+
t.add_row(row_toi)
|
|
448
|
+
sequence_tables.add(t)
|
|
449
|
+
|
|
450
|
+
if sequence_tables.empty():
|
|
451
|
+
return None
|
|
452
|
+
|
|
453
|
+
return sequence_tables.concat(TableData).drop_duplicates()
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
class BuilderTableFilesTOI(BuilderTOI):
|
|
457
|
+
|
|
458
|
+
def __init__(self) -> None:
|
|
459
|
+
super().__init__()
|
|
460
|
+
|
|
461
|
+
self.default_cols_cartas = ArrayList[HeaderFilesTOI].from_list(
|
|
462
|
+
[
|
|
463
|
+
HeaderFilesTOI.UC,
|
|
464
|
+
HeaderFilesTOI.TOI,
|
|
465
|
+
HeaderFilesTOI.DATE,
|
|
466
|
+
HeaderFilesTOI.YEAR,
|
|
467
|
+
HeaderFilesTOI.MON,
|
|
468
|
+
HeaderFilesTOI.DAY,
|
|
469
|
+
HeaderFilesTOI.LOCAL_ENTREGA,
|
|
470
|
+
HeaderFilesTOI.TIPO_DOC,
|
|
471
|
+
HeaderFilesTOI.HASH_FILE,
|
|
472
|
+
HeaderFilesTOI.IS_FILE,
|
|
473
|
+
HeaderFilesTOI.FILE_BASENAME,
|
|
474
|
+
HeaderFilesTOI.FILE_EXTENSION,
|
|
475
|
+
HeaderFilesTOI.FULL_PATH,
|
|
476
|
+
HeaderFilesTOI.SRC,
|
|
477
|
+
]
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
self.factory_toi: BuilderEntregaTOI = BuilderEntregaTOI()
|
|
481
|
+
|
|
482
|
+
def get_factory_toi(self) -> BuilderEntregaTOI:
|
|
483
|
+
return self.factory_toi
|
|
484
|
+
|
|
485
|
+
def create_void(self) -> TableData[HeaderFilesTOI]:
|
|
486
|
+
"""
|
|
487
|
+
Cria uma tabela com os cabeçalhos de HeaderFilesTOI.
|
|
488
|
+
"""
|
|
489
|
+
tb = TableData()
|
|
490
|
+
self.default_cols_cartas.for_each(lambda c: tb.add(ColumnV(c)))
|
|
491
|
+
return tb
|
|
492
|
+
|
|
493
|
+
def from_data(self, df: pd.DataFrame) -> TableData[HeaderFilesTOI]:
|
|
494
|
+
df = df.astype(str)
|
|
495
|
+
df_cols = ArrayList(df.columns.tolist())
|
|
496
|
+
final_table = TableData()
|
|
497
|
+
|
|
498
|
+
c: HeaderFilesTOI
|
|
499
|
+
for c in self.default_cols_cartas:
|
|
500
|
+
|
|
501
|
+
if df_cols.contains(c.value):
|
|
502
|
+
final_table.add(
|
|
503
|
+
ColumnV.create_from_list(c, df[c.value].astype(str).values.tolist())
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
if final_table.header().size() != self.default_cols_cartas.size():
|
|
507
|
+
msg = "DataFrame() FORMS toi com colunas diferente do padrão:"
|
|
508
|
+
msg_header = f"Cabeçalho FORMS {final_table.header()}"
|
|
509
|
+
raise HeadersSizeNotIqualError(
|
|
510
|
+
f"{msg} \n\n{msg_header}\nCabeçalho externo {df.columns}"
|
|
511
|
+
)
|
|
512
|
+
return final_table
|
|
513
|
+
|
|
514
|
+
def decode_table_toi(
|
|
515
|
+
self, tb: TableData[HeaderEntregaTOI], data: HashMap[hashFile, File]
|
|
516
|
+
) -> TableData[HeaderFilesTOI] | None:
|
|
517
|
+
|
|
518
|
+
if data.is_empty():
|
|
519
|
+
return None
|
|
520
|
+
if tb.is_empty():
|
|
521
|
+
return None
|
|
522
|
+
|
|
523
|
+
_df_toi = tb.to_dataframe().drop_duplicates(
|
|
524
|
+
subset=[HeaderEntregaTOI.HASH_FILE.value]
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
tb = self.get_factory_toi().from_data(_df_toi)
|
|
528
|
+
final_tb = TableData[HeaderFilesTOI].create_void(self.default_cols_cartas)
|
|
529
|
+
|
|
530
|
+
rows = tb.rows()
|
|
531
|
+
r: RowH[HeaderEntregaTOI, Any]
|
|
532
|
+
row_tb: RowH[HeaderFilesTOI, Any]
|
|
533
|
+
|
|
534
|
+
for r in rows:
|
|
535
|
+
row_tb = RowH(0)
|
|
536
|
+
|
|
537
|
+
row_tb.set(HeaderFilesTOI.UC, r.get(HeaderEntregaTOI.UC))
|
|
538
|
+
row_tb.set(HeaderFilesTOI.TOI, r.get(HeaderEntregaTOI.TOI))
|
|
539
|
+
row_tb.set(HeaderFilesTOI.DATE, r.get(HeaderEntregaTOI.DATE))
|
|
540
|
+
row_tb.set(HeaderFilesTOI.DAY, r.get(HeaderEntregaTOI.DAY))
|
|
541
|
+
row_tb.set(HeaderFilesTOI.MON, r.get(HeaderEntregaTOI.MON))
|
|
542
|
+
row_tb.set(HeaderFilesTOI.YEAR, r.get(HeaderEntregaTOI.YEAR))
|
|
543
|
+
row_tb.set(HeaderFilesTOI.HASH_FILE, r.get(HeaderEntregaTOI.HASH_FILE))
|
|
544
|
+
|
|
545
|
+
row_tb.set(HeaderFilesTOI.LOCAL_ENTREGA, LocalEntregaCarta.OUTRO)
|
|
546
|
+
row_tb.set(HeaderFilesTOI.TIPO_DOC, TipoCarta.CARTA_CALCULO)
|
|
547
|
+
|
|
548
|
+
# Incluir colunas adicionais.
|
|
549
|
+
_hash_file = r.get(HeaderEntregaTOI.HASH_FILE)
|
|
550
|
+
path = data.get_value(_hash_file)
|
|
551
|
+
|
|
552
|
+
row_tb.set(HeaderFilesTOI.FILE_BASENAME, path.basename())
|
|
553
|
+
row_tb.set(
|
|
554
|
+
HeaderFilesTOI.FILE_EXTENSION,
|
|
555
|
+
path.extension() or ".nan",
|
|
556
|
+
)
|
|
557
|
+
row_tb.set(HeaderFilesTOI.FULL_PATH, path.absolute())
|
|
558
|
+
|
|
559
|
+
row_tb.set(HeaderFilesTOI.SRC, path.parent().absolute())
|
|
560
|
+
row_tb.set(HeaderFilesTOI.IS_FILE, "S" if path.is_file() else "N")
|
|
561
|
+
|
|
562
|
+
final_tb.add_row(row_tb)
|
|
563
|
+
|
|
564
|
+
if final_tb.is_empty():
|
|
565
|
+
return None
|
|
566
|
+
|
|
567
|
+
_final_df = final_tb.to_dataframe().drop_duplicates(
|
|
568
|
+
subset=[HeaderFilesTOI.HASH_FILE.value]
|
|
569
|
+
)
|
|
570
|
+
return self.from_data(_final_df)
|
|
571
|
+
|
|
572
|
+
def from_path_name(
|
|
573
|
+
self, path: File, md5: hashFile = None
|
|
574
|
+
) -> TableData[HeaderFilesTOI] | None:
|
|
575
|
+
|
|
576
|
+
if md5 is None:
|
|
577
|
+
md5 = load_file_hash(path)
|
|
578
|
+
|
|
579
|
+
tb_toi = self.get_factory_toi().from_path_name(path, md5)
|
|
580
|
+
if tb_toi is None:
|
|
581
|
+
return None
|
|
582
|
+
|
|
583
|
+
return self.decode_table_toi(tb_toi, HashMapDict({md5: path}))
|
|
584
|
+
|
|
585
|
+
def from_hash_files(
|
|
586
|
+
self, data: HashMap[hashFile, File], *, ignore_null: bool = True
|
|
587
|
+
) -> TableData[HeaderFilesTOI] | None:
|
|
588
|
+
tb_toi = self.get_factory_toi().from_hash_files(data, ignore_null=ignore_null)
|
|
589
|
+
if tb_toi is None:
|
|
590
|
+
return None
|
|
591
|
+
|
|
592
|
+
return self.decode_table_toi(tb_toi, data)
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
@dataclass
|
|
596
|
+
class IdNames:
|
|
597
|
+
"""
|
|
598
|
+
Identificar os índices de cada elemento do nome de uma carta.
|
|
599
|
+
|
|
600
|
+
2026 3 03 1488594 193258476 e 1488594 193258476 03 03 2026 são
|
|
601
|
+
|
|
602
|
+
a mesma coisa porém nomeado com datas diferentes.
|
|
603
|
+
"""
|
|
604
|
+
|
|
605
|
+
year: int = 0
|
|
606
|
+
mon: int = 1
|
|
607
|
+
day: int = 2
|
|
608
|
+
uc: int = 3
|
|
609
|
+
toi: int = 4
|
|
610
|
+
|
|
611
|
+
|
|
612
|
+
def create_table_from_ids(
|
|
613
|
+
filename: str, md5: hashFile, *, ids: IdNames = IdNames()
|
|
614
|
+
) -> TableData[HeaderEntregaTOI]:
|
|
615
|
+
|
|
616
|
+
# Separar os nomes em uma lista
|
|
617
|
+
list_name: list[str] = filename.split(" ")
|
|
618
|
+
|
|
619
|
+
# Gerar a data no formato dia mês ano.
|
|
620
|
+
dt = f"{list_name[ids.day]} {list_name[ids.mon]} {list_name[ids.year]}"
|
|
621
|
+
|
|
622
|
+
row = RowH[HeaderEntregaTOI, str](0)
|
|
623
|
+
row.set(HeaderEntregaTOI.HASH_FILE, md5)
|
|
624
|
+
row.set(HeaderEntregaTOI.UC, list_name[ids.uc])
|
|
625
|
+
row.set(HeaderEntregaTOI.TOI, list_name[ids.toi])
|
|
626
|
+
row.set(HeaderEntregaTOI.YEAR, list_name[ids.year])
|
|
627
|
+
row.set(HeaderEntregaTOI.MON, list_name[ids.mon])
|
|
628
|
+
row.set(HeaderEntregaTOI.DAY, list_name[ids.day])
|
|
629
|
+
row.set(HeaderEntregaTOI.DATE, dt)
|
|
630
|
+
tb = BuilderEntregaTOI().create_void()
|
|
631
|
+
tb.add_row(row)
|
|
632
|
+
return tb
|
|
633
|
+
|
|
634
|
+
|
|
635
|
+
# ========================================================#
|
|
636
|
+
# Relatórios TOI.
|
|
637
|
+
# ========================================================#
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
class ReadWriteTableTOI(object):
|
|
641
|
+
"""
|
|
642
|
+
Leitura e escrita de dados na TOI em cache. A tabela em cache contém
|
|
643
|
+
o cabeçalho HeaderEntregaTOI:
|
|
644
|
+
UC, TOI, DAY, MON, YEAR, DATE E HASH_FILE.
|
|
645
|
+
|
|
646
|
+
"""
|
|
647
|
+
|
|
648
|
+
_instance = None # Atributo de classe para armazenar a instância singleton
|
|
649
|
+
|
|
650
|
+
def __new__(cls, *args, **kwargs):
|
|
651
|
+
if cls._instance is None:
|
|
652
|
+
cls._instance = super(ReadWriteTableTOI, cls).__new__(cls)
|
|
653
|
+
return cls._instance
|
|
654
|
+
|
|
655
|
+
def __init__(self) -> None:
|
|
656
|
+
|
|
657
|
+
# Garante que __init__ não será executado mais de uma vez
|
|
658
|
+
if hasattr(self, "_initialized") and self._initialized:
|
|
659
|
+
return
|
|
660
|
+
super().__init__()
|
|
661
|
+
|
|
662
|
+
self._initialized: bool = True
|
|
663
|
+
self.builder: BuilderConfigTOI = BuilderConfigTOI()
|
|
664
|
+
self._tb_toi: TableData[HeaderEntregaTOI] = None
|
|
665
|
+
|
|
666
|
+
@property
|
|
667
|
+
def file_table_toi(self) -> File:
|
|
668
|
+
return BuilderConfigTOI().builder().file_table_toi
|
|
669
|
+
|
|
670
|
+
def set_table_toi(self, tb: TableData[HeaderEntregaTOI]):
|
|
671
|
+
self._tb_toi = tb
|
|
672
|
+
|
|
673
|
+
def get_table_toi(self) -> TableData[HeaderEntregaTOI]:
|
|
674
|
+
if self._tb_toi is not None:
|
|
675
|
+
return self._tb_toi
|
|
676
|
+
|
|
677
|
+
f = self.file_table_toi
|
|
678
|
+
if not f.exists():
|
|
679
|
+
self._tb_toi = BuilderTableFilesTOI().get_factory_toi().create_void()
|
|
680
|
+
return self._tb_toi
|
|
681
|
+
|
|
682
|
+
print(f"Lendo tabela TOI em cache ... {f.absolute()}")
|
|
683
|
+
data = pd.read_excel(f.absolute())
|
|
684
|
+
data = data.drop_duplicates(subset=[HeaderFilesTOI.HASH_FILE.value])
|
|
685
|
+
|
|
686
|
+
# self._tb_toi = TableTOI.create_from_data(data)
|
|
687
|
+
self._tb_toi = BuilderEntregaTOI().from_data(data)
|
|
688
|
+
return self._tb_toi
|
|
689
|
+
|
|
690
|
+
def merge(self, tb: TableData[HeaderEntregaTOI]) -> None:
|
|
691
|
+
if tb.is_empty():
|
|
692
|
+
return None
|
|
693
|
+
|
|
694
|
+
print(f"Mergeando tabela TOI ... {self.file_table_toi.absolute()}")
|
|
695
|
+
tb_data = tb.to_dataframe().drop_duplicates(subset=[HeaderEntregaTOI.HASH_FILE])
|
|
696
|
+
|
|
697
|
+
content_data = (
|
|
698
|
+
self.get_table_toi()
|
|
699
|
+
.to_dataframe()
|
|
700
|
+
.drop_duplicates(subset=[HeaderEntregaTOI.HASH_FILE])
|
|
701
|
+
)
|
|
702
|
+
|
|
703
|
+
final = list()
|
|
704
|
+
if not tb_data.empty:
|
|
705
|
+
final.append(tb_data)
|
|
706
|
+
if not content_data.empty:
|
|
707
|
+
final.append(content_data)
|
|
708
|
+
|
|
709
|
+
if len(final) > 0:
|
|
710
|
+
df: pd.DataFrame = pd.concat(final)
|
|
711
|
+
self._tb_toi = BuilderTableFilesTOI().get_factory_toi().from_data(df)
|
|
712
|
+
self.save()
|
|
713
|
+
|
|
714
|
+
def save(self) -> None:
|
|
715
|
+
data = self.get_table_toi().to_dataframe()
|
|
716
|
+
path = self.file_table_toi.absolute()
|
|
717
|
+
tmp_file = f"{path}-tmp.xlsx"
|
|
718
|
+
|
|
719
|
+
print(f"Salvando tabela TOI ... {path}")
|
|
720
|
+
data.to_excel(tmp_file, index=False)
|
|
721
|
+
rename_file(tmp_file, path)
|
|
722
|
+
|
|
723
|
+
|
|
724
|
+
class ControllerTOI(object):
|
|
725
|
+
"""
|
|
726
|
+
Cria uma tabela de Cartas TOI apartir dos chats do WhatsApp.
|
|
727
|
+
Conectando os arquivos das conversas aos textos extraídos via OCR.
|
|
728
|
+
|
|
729
|
+
TableWhatsApp() é usado para obter DATA e HORA do envio de um arquivo
|
|
730
|
+
no chat, pois a data de envio no chat e a data de entrega de uma carta em campo.
|
|
731
|
+
"""
|
|
732
|
+
|
|
733
|
+
def __init__(self) -> None:
|
|
734
|
+
self.builder_toi_files: BuilderTableFilesTOI = BuilderTableFilesTOI()
|
|
735
|
+
self.builder_config: BuilderConfigTOI = BuilderConfigTOI()
|
|
736
|
+
self.rw: ReadWriteTableTOI = ReadWriteTableTOI()
|
|
737
|
+
|
|
738
|
+
@property
|
|
739
|
+
def relatorio_whatsapp(self) -> ControllerWhatsApp:
|
|
740
|
+
return self.get_factory_toi().relatorio_wp
|
|
741
|
+
|
|
742
|
+
@property
|
|
743
|
+
def relatorio_ocr(self) -> CreateRelatorioOcr:
|
|
744
|
+
return self.get_factory_toi().relatorio_ocr
|
|
745
|
+
|
|
746
|
+
def get_factory_toi(self) -> BuilderEntregaTOI:
|
|
747
|
+
return self.builder_toi_files.get_factory_toi()
|
|
748
|
+
|
|
749
|
+
# --------------------------------------------------#
|
|
750
|
+
# Consultar dados - READ
|
|
751
|
+
# --------------------------------------------------#
|
|
752
|
+
def load_table_whatsapp(self) -> TableWhatsApp:
|
|
753
|
+
return self.relatorio_whatsapp.load_cache_table()
|
|
754
|
+
|
|
755
|
+
def load_table_toi(self) -> TableData[HeaderEntregaTOI]:
|
|
756
|
+
return self.rw.get_table_toi()
|
|
757
|
+
|
|
758
|
+
def find_on_cache(self, data: HashMap[hashFile, File]) -> TableData[HeaderFilesTOI]:
|
|
759
|
+
"""
|
|
760
|
+
Procura os textos de cada arquivo na tabela OCR apartir da hash e retorna
|
|
761
|
+
os dados no formato TableData()
|
|
762
|
+
"""
|
|
763
|
+
|
|
764
|
+
if data.is_empty():
|
|
765
|
+
return self.builder_toi_files.create_void()
|
|
766
|
+
|
|
767
|
+
tb_cache: TableData[HeaderEntregaTOI] = self.load_table_toi()
|
|
768
|
+
if tb_cache.is_empty():
|
|
769
|
+
return self.builder_toi_files.create_void()
|
|
770
|
+
|
|
771
|
+
content_cache: TableData | None = tb_cache.search_all(
|
|
772
|
+
HeaderEntregaTOI.HASH_FILE, data.header()
|
|
773
|
+
)
|
|
774
|
+
|
|
775
|
+
if content_cache is None:
|
|
776
|
+
return self.builder_toi_files.create_void()
|
|
777
|
+
|
|
778
|
+
df_cache = content_cache.to_dataframe()
|
|
779
|
+
builder = BuilderEntregaTOI()
|
|
780
|
+
|
|
781
|
+
final = self.builder_toi_files.decode_table_toi(
|
|
782
|
+
builder.from_data(df_cache), data
|
|
783
|
+
)
|
|
784
|
+
|
|
785
|
+
if final is None:
|
|
786
|
+
return self.builder_toi_files.create_void()
|
|
787
|
+
return final
|
|
788
|
+
|
|
789
|
+
def load_from_map(
|
|
790
|
+
self, data_map: HashMapDict[hashFile, File], *, ignore_null: bool = True
|
|
791
|
+
) -> TableData[HeaderFilesTOI] | None:
|
|
792
|
+
"""
|
|
793
|
+
I) Usa a HASH MD5 dos arquivos para buscar a data de envio do arquivo na
|
|
794
|
+
tabela whatsapp;
|
|
795
|
+
|
|
796
|
+
II) Gera um relatório OCR com os metadados básicos
|
|
797
|
+
de cada arquivo;
|
|
798
|
+
|
|
799
|
+
III) Obtem a UC e TOI de cada arquivo.
|
|
800
|
+
|
|
801
|
+
IV) Retorna uma tabela apartir dos dados gerados.
|
|
802
|
+
|
|
803
|
+
Observação:
|
|
804
|
+
Se o parametro ignore_null for True as linhas que contém UC e TOI inválidos
|
|
805
|
+
serão ignorados.
|
|
806
|
+
"""
|
|
807
|
+
|
|
808
|
+
if data_map.is_empty():
|
|
809
|
+
return None
|
|
810
|
+
|
|
811
|
+
final_table: TableData[HeaderFilesTOI] | None = (
|
|
812
|
+
self.builder_toi_files.from_hash_files(data_map, ignore_null=ignore_null)
|
|
813
|
+
)
|
|
814
|
+
if final_table is None:
|
|
815
|
+
return None
|
|
816
|
+
|
|
817
|
+
df = final_table.to_dataframe().drop_duplicates(
|
|
818
|
+
subset=[HeaderFilesTOI.HASH_FILE.value]
|
|
819
|
+
)
|
|
820
|
+
|
|
821
|
+
if ignore_null:
|
|
822
|
+
df = df[df[HeaderFilesTOI.UC] != TableData.NULL_STRING]
|
|
823
|
+
|
|
824
|
+
return self.builder_toi_files.from_data(df)
|
|
825
|
+
|
|
826
|
+
# --------------------------------------------------#
|
|
827
|
+
# Criar dados - CREATE
|
|
828
|
+
# --------------------------------------------------#
|
|
829
|
+
def add_hashs_on_table(
|
|
830
|
+
self, hash_files: Set[hashFile], *, ignore_null: bool = True
|
|
831
|
+
) -> None:
|
|
832
|
+
"""
|
|
833
|
+
I) Analisa arquivos TOI apartir da HASH MD5, pesquisa o texto de cada
|
|
834
|
+
hash na tabela OCR e extrai UC e TOI;
|
|
835
|
+
|
|
836
|
+
II) Na sequência, usa a hash para encontrar a data de envio do arquivo no chat
|
|
837
|
+
do whatsapp;
|
|
838
|
+
|
|
839
|
+
III) Inclui os dados TOI de cada arquivo na tabela TOI em cache.
|
|
840
|
+
"""
|
|
841
|
+
if hash_files.empty():
|
|
842
|
+
print("Lista de arquivos vazia, nada a fazer!")
|
|
843
|
+
return None
|
|
844
|
+
|
|
845
|
+
# Filtrar
|
|
846
|
+
tb_cache_ocr: TableData[HeaderOcr] = self.relatorio_ocr.load_table_image()
|
|
847
|
+
tb_cache_ocr.merge(self.relatorio_ocr.load_table_pdf())
|
|
848
|
+
|
|
849
|
+
cache_ocr: TableData[HeaderOcr] | None = tb_cache_ocr.search_all(
|
|
850
|
+
HeaderOcr.HASH_FILE, hash_files.tolist()
|
|
851
|
+
)
|
|
852
|
+
|
|
853
|
+
if cache_ocr is None:
|
|
854
|
+
return None
|
|
855
|
+
|
|
856
|
+
# cache_ocr = cache_ocr.drop_duplicates()
|
|
857
|
+
table_whatsapp: TableWhatsApp = self.load_table_whatsapp()
|
|
858
|
+
|
|
859
|
+
# Cada arquivo gera/extrai a sua própria tabela, ao final essa
|
|
860
|
+
# lista de tabelas serão concatenadas em uma tabela única.
|
|
861
|
+
list_tables_toi = ArrayTables[TableData[HeaderEntregaTOI]]()
|
|
862
|
+
total = hash_files.size()
|
|
863
|
+
k: hashFile
|
|
864
|
+
|
|
865
|
+
print(f"[Pesquisando texto OCR] em {total} arquivos")
|
|
866
|
+
for n, k in enumerate(hash_files):
|
|
867
|
+
|
|
868
|
+
current_table_ocr: TableData[HeaderOcr] = cache_ocr.search(
|
|
869
|
+
HeaderOcr.HASH_FILE, k
|
|
870
|
+
)
|
|
871
|
+
|
|
872
|
+
text_ocr: str = None
|
|
873
|
+
|
|
874
|
+
if not current_table_ocr.is_empty():
|
|
875
|
+
text_ocr = current_table_ocr.to_string(HeaderOcr.TEXT)
|
|
876
|
+
|
|
877
|
+
txt_uc_toi: tuple | None = (
|
|
878
|
+
extrair_uc_toi(text_ocr) if text_ocr is not None else None
|
|
879
|
+
)
|
|
880
|
+
|
|
881
|
+
if txt_uc_toi is None:
|
|
882
|
+
if ignore_null:
|
|
883
|
+
continue
|
|
884
|
+
|
|
885
|
+
uc = TableData.NULL_STRING
|
|
886
|
+
toi = TableData.NULL_STRING
|
|
887
|
+
year = TableData.NULL_STRING
|
|
888
|
+
mon = TableData.NULL_STRING
|
|
889
|
+
day = TableData.NULL_STRING
|
|
890
|
+
dt: str = TableData.NULL_STRING
|
|
891
|
+
|
|
892
|
+
if txt_uc_toi is not None:
|
|
893
|
+
uc = txt_uc_toi[0]
|
|
894
|
+
toi = txt_uc_toi[1]
|
|
895
|
+
|
|
896
|
+
# Obter os dados no arquivo no chat whatsapp apartir da hash md5
|
|
897
|
+
current_table_whatsapp = table_whatsapp.search(HeaderWhatsApp.hash_md5, k)
|
|
898
|
+
|
|
899
|
+
if not current_table_whatsapp.is_empty():
|
|
900
|
+
# Arquivo encontrado no chat. Extrair dados da data de envio.
|
|
901
|
+
dt = current_table_whatsapp.get_row_at(0).get_date().get_first()
|
|
902
|
+
day = dt.split(" ")[0]
|
|
903
|
+
mon = dt.split(" ")[1]
|
|
904
|
+
year = dt.split(" ")[2]
|
|
905
|
+
|
|
906
|
+
if ignore_null:
|
|
907
|
+
if dt == TableData.NULL_STRING:
|
|
908
|
+
continue
|
|
909
|
+
|
|
910
|
+
tb_toi: TableData[HeaderEntregaTOI] = self.get_factory_toi().create_void()
|
|
911
|
+
row_forms = RowH[HeaderEntregaTOI, Any](0)
|
|
912
|
+
|
|
913
|
+
row_forms.set(HeaderEntregaTOI.UC, uc)
|
|
914
|
+
row_forms.set(HeaderEntregaTOI.TOI, toi)
|
|
915
|
+
row_forms.set(HeaderEntregaTOI.YEAR, year)
|
|
916
|
+
row_forms.set(HeaderEntregaTOI.MON, mon)
|
|
917
|
+
row_forms.set(HeaderEntregaTOI.DAY, day)
|
|
918
|
+
row_forms.set(HeaderEntregaTOI.DATE, dt)
|
|
919
|
+
row_forms.set(HeaderEntregaTOI.HASH_FILE, k)
|
|
920
|
+
|
|
921
|
+
tb_toi.add_row(row_forms)
|
|
922
|
+
list_tables_toi.add(tb_toi)
|
|
923
|
+
|
|
924
|
+
if list_tables_toi.empty():
|
|
925
|
+
return None
|
|
926
|
+
|
|
927
|
+
final = list_tables_toi.concat(TableData).drop_duplicates()
|
|
928
|
+
self.rw.merge(final)
|
|
929
|
+
|
|
930
|
+
@staticmethod
|
|
931
|
+
def filter_uc_toi(
|
|
932
|
+
hf: hashFile, table_ocr_file: TableData[HeaderOcr]
|
|
933
|
+
) -> tuple[str, str] | None:
|
|
934
|
+
"""
|
|
935
|
+
Filtra UC e TOI de uma carta apartir do texto OCR extraído.
|
|
936
|
+
"""
|
|
937
|
+
tb_current_hash = table_ocr_file.search(HeaderOcr.HASH_FILE, hf)
|
|
938
|
+
if tb_current_hash.is_empty():
|
|
939
|
+
return None
|
|
940
|
+
|
|
941
|
+
values = tb_current_hash.get(HeaderOcr.TEXT)._elements
|
|
942
|
+
|
|
943
|
+
for v in values:
|
|
944
|
+
txt = extrair_uc_toi(v)
|
|
945
|
+
if txt is not None:
|
|
946
|
+
return txt
|
|
947
|
+
return None
|
|
948
|
+
|
|
949
|
+
|
|
950
|
+
def load_files_toi(files: Set[File]) -> TableData[HeaderFilesTOI] | None:
|
|
951
|
+
"""
|
|
952
|
+
I) Analisa arquivos TOI apartir da HASH MD5;
|
|
953
|
+
II) Compara o texto na tabela OCR e inclui, UC e TOI na tabela TOI principal;
|
|
954
|
+
III) Retorna os dados no formato de tabela TOI.
|
|
955
|
+
"""
|
|
956
|
+
|
|
957
|
+
if files.empty():
|
|
958
|
+
print(f"[DEBUG]: load_files_toi() lista de arquivos vazia!")
|
|
959
|
+
return None
|
|
960
|
+
|
|
961
|
+
f: File
|
|
962
|
+
for f in files:
|
|
963
|
+
if (not f.is_image()) and (not f.is_pdf()):
|
|
964
|
+
raise ValueError(
|
|
965
|
+
f"Tipo de documento TOI inválido, use PDF ou Imagem não: {f.extension()}"
|
|
966
|
+
)
|
|
967
|
+
|
|
968
|
+
data_map = HashMapDict[hashFile, File]()
|
|
969
|
+
files.for_each(lambda f: data_map.set_value(load_file_hash(f), f))
|
|
970
|
+
|
|
971
|
+
controller = ControllerTOI()
|
|
972
|
+
heder_hash = data_map.header()
|
|
973
|
+
controller.add_hashs_on_table(heder_hash.to_set())
|
|
974
|
+
return controller.find_on_cache(data_map)
|
|
975
|
+
|
|
976
|
+
|
|
977
|
+
def get_file_log(base_dir: Directory) -> File:
|
|
978
|
+
_dt = datetime.now()
|
|
979
|
+
y = str(_dt.year)
|
|
980
|
+
m = str(_dt.month)
|
|
981
|
+
day = str(_dt.day)
|
|
982
|
+
current_date = f"{y}-{m}-{day}"
|
|
983
|
+
dir_log: Directory = base_dir.concat(y, True).concat(f"{y}-{m}", True)
|
|
984
|
+
return dir_log.join_file(f"{current_date}.xlsx")
|
|
985
|
+
|
|
986
|
+
|
|
987
|
+
def get_date_from_row(r: RowH[HeaderFilesTOI, Any]) -> tuple[str, str, str] | None:
|
|
988
|
+
"""
|
|
989
|
+
Retorna uma tupla de data no formato (ANO, MÊS, DIA)
|
|
990
|
+
"""
|
|
991
|
+
|
|
992
|
+
y = r.get(HeaderFilesTOI.YEAR)
|
|
993
|
+
m = r.get(HeaderFilesTOI.MON)
|
|
994
|
+
d = r.get(HeaderFilesTOI.DAY)
|
|
995
|
+
|
|
996
|
+
if len(m) > 1:
|
|
997
|
+
if m[0] == "0":
|
|
998
|
+
m = m[1]
|
|
999
|
+
|
|
1000
|
+
values = (y, m, d)
|
|
1001
|
+
i: str
|
|
1002
|
+
for i in values:
|
|
1003
|
+
if not i.isnumeric():
|
|
1004
|
+
return None
|
|
1005
|
+
return values
|
|
1006
|
+
|
|
1007
|
+
|
|
1008
|
+
class RenameFilesTOI:
|
|
1009
|
+
"""Organiza cartas TOI e registra o resultado de cada movimentação."""
|
|
1010
|
+
|
|
1011
|
+
def __init__(self, files: Set[File]) -> None:
|
|
1012
|
+
self.config: ConfigTOI = BuilderConfigTOI().builder()
|
|
1013
|
+
self.files = files
|
|
1014
|
+
self.file_log: File = get_file_log(self.config.dir_log_excel)
|
|
1015
|
+
|
|
1016
|
+
self.base_cartas: Directory = self.config.dir_base_toi
|
|
1017
|
+
self.error_uc_dir: Directory = self.base_cartas.concat(
|
|
1018
|
+
"CARTAS_UC_NAO_IDENTIFICADAS", True
|
|
1019
|
+
)
|
|
1020
|
+
self.error_date_dir: Directory = self.base_cartas.concat(
|
|
1021
|
+
"CARTAS_DATA_NAO_ENCONTRADA", True
|
|
1022
|
+
)
|
|
1023
|
+
|
|
1024
|
+
self.map_files: HashMapDict[hashFile, File] = HashMapDict()
|
|
1025
|
+
self.table_status = TableData.create_void(
|
|
1026
|
+
[HeaderFilesTOI.HASH_FILE.value, "src", "dest", "status", "exception"]
|
|
1027
|
+
)
|
|
1028
|
+
|
|
1029
|
+
def process_error_ucs(self):
|
|
1030
|
+
tb_wp = ControllerWhatsApp().load_cache_table()
|
|
1031
|
+
content = ContentFiles(self.error_uc_dir)
|
|
1032
|
+
files = content.get_files()
|
|
1033
|
+
|
|
1034
|
+
content_map = HashMapDict[hashFile, File]()
|
|
1035
|
+
files.for_each(lambda f: content_map.set_value(load_file_hash(f), f))
|
|
1036
|
+
|
|
1037
|
+
content_chat = tb_wp.search_all(HeaderWhatsApp.hash_md5, content_map.header())
|
|
1038
|
+
|
|
1039
|
+
if content_chat is None:
|
|
1040
|
+
return
|
|
1041
|
+
if content_chat.is_empty():
|
|
1042
|
+
return
|
|
1043
|
+
|
|
1044
|
+
rows = (
|
|
1045
|
+
BuilderTableWhatsApp().create_from_data(content_chat.to_dataframe()).rows()
|
|
1046
|
+
)
|
|
1047
|
+
|
|
1048
|
+
r: RowWhatsApp
|
|
1049
|
+
for r in rows:
|
|
1050
|
+
h = r.get(HeaderWhatsApp.hash_md5)
|
|
1051
|
+
file_path: File = content_map.get_value(h)
|
|
1052
|
+
|
|
1053
|
+
dt: datetime = r.get_date_hour()
|
|
1054
|
+
y = str(dt.year)
|
|
1055
|
+
m = str(dt.month)
|
|
1056
|
+
d = str(dt.day)
|
|
1057
|
+
_date = f"{y}-{m}-{d}"
|
|
1058
|
+
|
|
1059
|
+
current_dir = self.error_uc_dir.concat(y).concat(m).concat(_date, True)
|
|
1060
|
+
final_path: File = current_dir.join_file(file_path.basename())
|
|
1061
|
+
|
|
1062
|
+
try:
|
|
1063
|
+
move_path(file_path, final_path)
|
|
1064
|
+
except Exception as e:
|
|
1065
|
+
print(f"{__class__.__name__}(): {e}")
|
|
1066
|
+
|
|
1067
|
+
def _index_files(self) -> None:
|
|
1068
|
+
"""Indexa os arquivos originais por hash."""
|
|
1069
|
+
for file in self.files:
|
|
1070
|
+
self.map_files.set_value(load_file_hash(file), file)
|
|
1071
|
+
|
|
1072
|
+
def _create_status_row(self, file_hash: hashFile, src: File, dest: File) -> RowH:
|
|
1073
|
+
row = RowH(0)
|
|
1074
|
+
row.set(HeaderFilesTOI.HASH_FILE.value, file_hash)
|
|
1075
|
+
row.set("src", src.absolute())
|
|
1076
|
+
row.set("dest", dest.absolute())
|
|
1077
|
+
return row
|
|
1078
|
+
|
|
1079
|
+
def _move(self, file_hash: hashFile, src: File, dest: File) -> bool:
|
|
1080
|
+
"""Move o arquivo e registra o resultado real da operação."""
|
|
1081
|
+
row = self._create_status_row(file_hash, src, dest)
|
|
1082
|
+
|
|
1083
|
+
try:
|
|
1084
|
+
if not move_path(src, dest):
|
|
1085
|
+
raise OSError(
|
|
1086
|
+
f"move_path retornou False: {src.absolute()} -> {dest.absolute()}"
|
|
1087
|
+
)
|
|
1088
|
+
except Exception as exc:
|
|
1089
|
+
row.set("status", "FALHA")
|
|
1090
|
+
row.set("exception", str(exc))
|
|
1091
|
+
success = False
|
|
1092
|
+
else:
|
|
1093
|
+
row.set("status", "SUCESSO")
|
|
1094
|
+
row.set("exception", "")
|
|
1095
|
+
success = True
|
|
1096
|
+
|
|
1097
|
+
self.table_status.add_row(row)
|
|
1098
|
+
return success
|
|
1099
|
+
|
|
1100
|
+
@staticmethod
|
|
1101
|
+
def _is_missing(value: Any) -> bool:
|
|
1102
|
+
return (
|
|
1103
|
+
value is None
|
|
1104
|
+
or pd.isna(value)
|
|
1105
|
+
or value == TableData.NULL_STRING
|
|
1106
|
+
or str(value).strip() == ""
|
|
1107
|
+
)
|
|
1108
|
+
|
|
1109
|
+
def _get_destination(self, row: RowH) -> tuple[Directory, str]:
|
|
1110
|
+
"""Define o destino com prioridade para a ausência de UC."""
|
|
1111
|
+
uc = row.get(HeaderFilesTOI.UC)
|
|
1112
|
+
toi = row.get(HeaderFilesTOI.TOI)
|
|
1113
|
+
date = get_date_from_row(row)
|
|
1114
|
+
|
|
1115
|
+
if self._is_missing(uc):
|
|
1116
|
+
return self.error_uc_dir, str(row.get(HeaderFilesTOI.HASH_FILE))
|
|
1117
|
+
|
|
1118
|
+
if date is None:
|
|
1119
|
+
return self.error_date_dir, f"{uc} {toi}"
|
|
1120
|
+
|
|
1121
|
+
year, month, day = date
|
|
1122
|
+
dest_dir = (
|
|
1123
|
+
self.base_cartas.concat(year)
|
|
1124
|
+
.concat(f"{year}-{month}")
|
|
1125
|
+
.concat(f"{year}-{month}-{day}", True)
|
|
1126
|
+
)
|
|
1127
|
+
return dest_dir, f"{year} {month} {day} {uc} {toi}"
|
|
1128
|
+
|
|
1129
|
+
def _process_identified_files(self) -> Set[hashFile]:
|
|
1130
|
+
"""Processa os arquivos que possuem registro na tabela TOI."""
|
|
1131
|
+
controller = ControllerTOI()
|
|
1132
|
+
controller.add_hashs_on_table(self.map_files.header().to_set())
|
|
1133
|
+
table_files = controller.find_on_cache(self.map_files)
|
|
1134
|
+
|
|
1135
|
+
processed = Set[hashFile]()
|
|
1136
|
+
|
|
1137
|
+
for row in table_files.rows():
|
|
1138
|
+
file_hash: hashFile = row.get(HeaderFilesTOI.HASH_FILE)
|
|
1139
|
+
src: File = self.map_files.get_value(file_hash)
|
|
1140
|
+
|
|
1141
|
+
if src is None:
|
|
1142
|
+
continue
|
|
1143
|
+
|
|
1144
|
+
processed.add(file_hash)
|
|
1145
|
+
dest_dir, output_name = self._get_destination(row)
|
|
1146
|
+
dest = dest_dir.join_file(f"{output_name}{src.extension()}")
|
|
1147
|
+
|
|
1148
|
+
self._move(file_hash, src, dest)
|
|
1149
|
+
|
|
1150
|
+
return processed
|
|
1151
|
+
|
|
1152
|
+
def _process_unidentified_files(self, processed: Set[hashFile]) -> None:
|
|
1153
|
+
"""Encaminha para a pasta de UC os arquivos sem registro TOI."""
|
|
1154
|
+
for file_hash in self.map_files.header():
|
|
1155
|
+
if processed.contains(file_hash):
|
|
1156
|
+
continue
|
|
1157
|
+
|
|
1158
|
+
src: File = self.map_files.get_value(file_hash)
|
|
1159
|
+
extension = src.extension() or ".nan"
|
|
1160
|
+
dest = self.error_uc_dir.join_file(f"{file_hash}{extension}")
|
|
1161
|
+
|
|
1162
|
+
self._move(file_hash, src, dest)
|
|
1163
|
+
|
|
1164
|
+
def _save_log(self) -> pd.DataFrame:
|
|
1165
|
+
"""Acumula os resultados no relatório existente."""
|
|
1166
|
+
sequence = ArrayTables[TableData]()
|
|
1167
|
+
sequence.add(self.table_status)
|
|
1168
|
+
|
|
1169
|
+
if self.file_log.exists():
|
|
1170
|
+
df_previous = pd.read_excel(self.file_log.absolute())
|
|
1171
|
+
sequence.add(TableData(df_previous))
|
|
1172
|
+
|
|
1173
|
+
df_final = sequence.concat(TableData).to_dataframe()
|
|
1174
|
+
df_final.drop_duplicates(inplace=True)
|
|
1175
|
+
|
|
1176
|
+
print(f"Salvando: {self.file_log}")
|
|
1177
|
+
df_final.to_excel(self.file_log.absolute(), index=False)
|
|
1178
|
+
return df_final
|
|
1179
|
+
|
|
1180
|
+
def execute(self) -> pd.DataFrame:
|
|
1181
|
+
if self.files.empty():
|
|
1182
|
+
return self.table_status.to_dataframe()
|
|
1183
|
+
|
|
1184
|
+
self._index_files()
|
|
1185
|
+
processed = self._process_identified_files()
|
|
1186
|
+
self._process_unidentified_files(processed)
|
|
1187
|
+
return self._save_log()
|
|
1188
|
+
|
|
1189
|
+
|
|
1190
|
+
def rename_files_toi(files: Set[File]) -> RenameFilesTOI:
|
|
1191
|
+
"""Renomear arquivos TOI"""
|
|
1192
|
+
|
|
1193
|
+
rn = RenameFilesTOI(files)
|
|
1194
|
+
rn.execute()
|
|
1195
|
+
rn.process_error_ucs()
|
|
1196
|
+
return rn
|