pyflutter-app 0.3.12__tar.gz → 0.3.14__tar.gz
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.
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/PKG-INFO +1 -1
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/cartas/__init__.py +273 -17
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/config_app.py +63 -9
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_forms/controller_forms.py +15 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_forms/view_forms.py +13 -8
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_image/__init__.py +2 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_image/common.py +4 -1
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_image/controller_image.py +172 -50
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_image/model_image.py +18 -14
- pyflutter_app-0.3.14/converter/page_image/progress.py +77 -0
- pyflutter_app-0.3.14/converter/page_image/renderer.py +240 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_image/view_image.py +31 -39
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_image/widgets.py +135 -18
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_ocr/controller_ocr.py +14 -3
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_ocr/view_ocr.py +10 -9
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/table_widget.py +62 -60
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/pyflutter_ui/ui/buttons.py +18 -16
- pyflutter_app-0.3.14/pyflutter_ui/ui/progress.py +115 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/pyproject.toml +1 -1
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/table_stream/__init__.py +1 -1
- pyflutter_app-0.3.14/teste.py +27 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/uv.lock +1 -1
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/whatsapp_files/__init__.py +103 -1
- pyflutter_app-0.3.12/cartas/origin.py +0 -1196
- pyflutter_app-0.3.12/converter/page_image/renderer.py +0 -92
- pyflutter_app-0.3.12/pyflutter_ui/ui/progress.py +0 -80
- pyflutter_app-0.3.12/teste.py +0 -9
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/.gitignore +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/.idea/.gitignore +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/.idea/inspectionProfiles/Project_Default.xml +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/.idea/inspectionProfiles/profiles_settings.xml +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/.idea/misc.xml +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/.idea/modules.xml +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/.idea/pyflutter_app.iml +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/.idea/vcs.xml +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/README.md +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/convert_stream/__init__.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/__init__.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/__main__.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/assets/forms_config.json +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/home_page/__init__.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/home_page/_page_edit.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/home_page/controller.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/home_page/model.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/home_page/page_sheets.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/home_page/view.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/lib_toi.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_circuit/__init__.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_circuit/common.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_circuit/controller_circuit.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_circuit/model_circuit.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_circuit/service.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_circuit/view_circuit.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_forms/Diagnorstico.txt +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_forms/__init__.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_forms/common.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_forms/config.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_forms/diagnose_forms.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_forms/master_forms.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_forms/model_forms.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_forms/service.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_ocr/__init__.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_ocr/common.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_ocr/model_ocr.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_whatsapp/PATCH_whatsapp_files.md +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_whatsapp/__init__.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_whatsapp/common.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_whatsapp/controller_whatsapp.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_whatsapp/model_whatsapp.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_whatsapp/view_whatsapp.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/run_app_convert.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/util.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/local_build/linux.sh +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/local_build/send.sh +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/main.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/mvc/controller.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/mvc/model.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/mvc/view.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/new-export_app.sh +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/ocr_stream/__init__.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/pyflutter_ui/__init__.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/pyflutter_ui/app.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/pyflutter_ui/file_picker.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/pyflutter_ui/navigator.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/pyflutter_ui/scheduler.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/pyflutter_ui/theme.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/pyflutter_ui/ui/__init__.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/pyflutter_ui/ui/container.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/pyflutter_ui/ui/core.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/pyflutter_ui/ui/list_builder.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/pyflutter_ui/ui/tables.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/pyflutter_ui/ui/widgets.py +0 -0
- {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/update.sh +0 -0
|
@@ -9,6 +9,7 @@ from abc import ABC, abstractmethod
|
|
|
9
9
|
from os import rename as rename_file
|
|
10
10
|
from os import replace as replace_file
|
|
11
11
|
from datetime import datetime
|
|
12
|
+
import threading
|
|
12
13
|
|
|
13
14
|
import pandas as pd
|
|
14
15
|
|
|
@@ -33,7 +34,7 @@ from table_stream import (
|
|
|
33
34
|
load_file_hash,
|
|
34
35
|
)
|
|
35
36
|
|
|
36
|
-
from ocr_stream import HeaderOcr, BuilderConfigOcr, CreateRelatorioOcr
|
|
37
|
+
# from ocr_stream import HeaderOcr, BuilderConfigOcr, CreateRelatorioOcr
|
|
37
38
|
|
|
38
39
|
from whatsapp_files import (
|
|
39
40
|
RowWhatsApp,
|
|
@@ -41,10 +42,11 @@ from whatsapp_files import (
|
|
|
41
42
|
HeaderWhatsApp,
|
|
42
43
|
BuilderTableWhatsApp,
|
|
43
44
|
ControllerWhatsApp,
|
|
45
|
+
BuilderConfigWhatsApp, # NOVO: base RFA única (substitui BuilderConfigOcr)
|
|
44
46
|
move_path,
|
|
45
47
|
)
|
|
46
48
|
|
|
47
|
-
__version__ = "0.
|
|
49
|
+
__version__ = "0.3"
|
|
48
50
|
|
|
49
51
|
|
|
50
52
|
@dataclass
|
|
@@ -60,6 +62,111 @@ class ConfigTOI:
|
|
|
60
62
|
work_dir: Directory
|
|
61
63
|
|
|
62
64
|
|
|
65
|
+
# Mesmos nomes de arquivo gravados por ocr_stream. Se mudar lá, mude aqui.
|
|
66
|
+
_OCR_FILE_IMAGE = "tabela_ocr_imagens.xlsx"
|
|
67
|
+
_OCR_FILE_PDF = "tabela_ocr_pdf.xlsx"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class BuilderConfigTOI(object):
|
|
71
|
+
"""
|
|
72
|
+
Configuração de cartas SEM depender de ocr_stream.
|
|
73
|
+
Base única: <Documentos>/RFA (BuilderConfigWhatsApp.dir_base_root),
|
|
74
|
+
tabelas em <Documentos>/RFA/Tabelas (mesmos caminhos do ocr_stream).
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
_instance = None # Atributo de classe para armazenar a instância singleton
|
|
78
|
+
|
|
79
|
+
def __new__(cls, *args, **kwargs):
|
|
80
|
+
if cls._instance is None:
|
|
81
|
+
cls._instance = super(BuilderConfigTOI, cls).__new__(cls)
|
|
82
|
+
return cls._instance
|
|
83
|
+
|
|
84
|
+
def __init__(self) -> None:
|
|
85
|
+
|
|
86
|
+
# Garante que __init__ não será executado mais de uma vez
|
|
87
|
+
if hasattr(self, "_initialized") and self._initialized:
|
|
88
|
+
return
|
|
89
|
+
|
|
90
|
+
super().__init__()
|
|
91
|
+
self._initialized: bool = True
|
|
92
|
+
self._builder_wp = BuilderConfigWhatsApp()
|
|
93
|
+
self._config = self._create()
|
|
94
|
+
|
|
95
|
+
# ---------------------------------------------------------- base RFA
|
|
96
|
+
@property
|
|
97
|
+
def dir_base_work(self) -> Directory:
|
|
98
|
+
return self._builder_wp.dir_base_root
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def dir_table(self) -> Directory:
|
|
102
|
+
"""<Documentos>/RFA/Tabelas"""
|
|
103
|
+
return self._builder_wp.dir_table
|
|
104
|
+
|
|
105
|
+
# ---------------------------------------------------------- tabelas OCR (somente leitura)
|
|
106
|
+
@property
|
|
107
|
+
def file_table_ocr_image(self) -> File:
|
|
108
|
+
return self.dir_table.join_file(_OCR_FILE_IMAGE)
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def file_table_ocr_pdf(self) -> File:
|
|
112
|
+
return self.dir_table.join_file(_OCR_FILE_PDF)
|
|
113
|
+
|
|
114
|
+
# ---------------------------------------------------------- pastas TOI
|
|
115
|
+
@property
|
|
116
|
+
def dir_retorno_de_campo(self) -> Directory:
|
|
117
|
+
return self.dir_base_work.concat("Cartas-TOI", True).concat(
|
|
118
|
+
"Retorno-de-campo", True
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def dir_log_excel(self) -> Directory:
|
|
123
|
+
return self.dir_base_work.concat("Cartas-TOI", True).concat("Logs", True)
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def dir_relatorios(self) -> Directory:
|
|
127
|
+
return self.dir_base_work.concat("Cartas-TOI", True).concat(
|
|
128
|
+
"Relatorios-toi", True
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
# ---------------------------------------------------------- arquivos TOI
|
|
132
|
+
@property
|
|
133
|
+
def file_relatorio_toi(self) -> File:
|
|
134
|
+
return self.dir_table.join_file("Relatorio-tois.xlsx")
|
|
135
|
+
|
|
136
|
+
@property
|
|
137
|
+
def file_relatorio_forms(self) -> File:
|
|
138
|
+
return self.dir_table.join_file("cartas-toi-lancadas.xlsx")
|
|
139
|
+
|
|
140
|
+
@property
|
|
141
|
+
def file_master_forms(self) -> File:
|
|
142
|
+
return self.dir_table.join_file("master-forms-toi.xlsx")
|
|
143
|
+
|
|
144
|
+
@property
|
|
145
|
+
def file_table_toi(self) -> File:
|
|
146
|
+
return self.dir_table.join_file("tabela-entregas-tois.xlsx")
|
|
147
|
+
|
|
148
|
+
def _create(self) -> ConfigTOI:
|
|
149
|
+
return ConfigTOI(
|
|
150
|
+
work_dir=self.dir_base_work,
|
|
151
|
+
dir_base_toi=self.dir_retorno_de_campo,
|
|
152
|
+
file_relatorio_toi=self.file_relatorio_toi,
|
|
153
|
+
file_table_toi=self.file_table_toi,
|
|
154
|
+
file_cartas_lancadas=self.file_relatorio_forms,
|
|
155
|
+
dir_log_excel=self.dir_log_excel,
|
|
156
|
+
dir_relatorios=self.dir_relatorios,
|
|
157
|
+
file_master_forms=self.file_master_forms,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
def reset_config(self) -> None:
|
|
161
|
+
self._config = None
|
|
162
|
+
|
|
163
|
+
def builder(self) -> ConfigTOI:
|
|
164
|
+
if self._config is None:
|
|
165
|
+
self._config = self._create()
|
|
166
|
+
return self._config
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
"""
|
|
63
170
|
class BuilderConfigTOI(object):
|
|
64
171
|
|
|
65
172
|
_instance = None # Atributo de classe para armazenar a instância singleton
|
|
@@ -136,6 +243,131 @@ class BuilderConfigTOI(object):
|
|
|
136
243
|
if self._config is None:
|
|
137
244
|
self._config = self._create()
|
|
138
245
|
return self._config
|
|
246
|
+
"""
|
|
247
|
+
# ============================================================#
|
|
248
|
+
# Leitura das tabelas OCR direto dos .xlsx (sem ocr_stream)
|
|
249
|
+
# ============================================================#
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
class HeaderOcr(StrEnum):
|
|
253
|
+
"""Colunas das tabelas OCR (mesmos nomes gravados pelo ocr_stream)."""
|
|
254
|
+
|
|
255
|
+
ID = "ID"
|
|
256
|
+
HASH_FILE = HeaderMetaData.HASH_FILE.value
|
|
257
|
+
NUM_PAG = "PÁGINA"
|
|
258
|
+
NUM_LINHA = "LINHA"
|
|
259
|
+
TEXT = "TEXTO"
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
_OCR_COLUMNS: list[str] = [h.value for h in HeaderOcr]
|
|
263
|
+
_OCR_C_HASH = HeaderOcr.HASH_FILE.value
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
class ReadTablesOcr(object):
|
|
267
|
+
"""
|
|
268
|
+
Leitor SOMENTE-LEITURA de RFA/Tabelas/tabela_ocr_imagens.xlsx e
|
|
269
|
+
tabela_ocr_pdf.xlsx. Equivale ao que cartas usava de CreateRelatorioOcr,
|
|
270
|
+
mas sem importar ocr_stream/tesseract/cv2.
|
|
271
|
+
|
|
272
|
+
Cache em memória por arquivo, invalidado quando o .xlsx muda
|
|
273
|
+
(mtime + tamanho). Assim, se outro processo/PC rodar o OCR, a próxima
|
|
274
|
+
consulta já enxerga o dado novo.
|
|
275
|
+
|
|
276
|
+
As tabelas devolvidas por load_table_* são o objeto do cache: trate
|
|
277
|
+
como SOMENTE LEITURA (nunca chame merge()/set() nelas).
|
|
278
|
+
"""
|
|
279
|
+
|
|
280
|
+
KIND_IMAGE = "imagem"
|
|
281
|
+
KIND_PDF = "pdf"
|
|
282
|
+
|
|
283
|
+
def __init__(self) -> None:
|
|
284
|
+
self._lock = threading.RLock()
|
|
285
|
+
self._cache: dict[str, tuple[tuple[int, int] | None, TableData[HeaderOcr]]] = {}
|
|
286
|
+
|
|
287
|
+
# ---------------------------------------------------------- infra
|
|
288
|
+
@staticmethod
|
|
289
|
+
def _file_of(kind: str) -> File:
|
|
290
|
+
cfg = BuilderConfigTOI()
|
|
291
|
+
if kind == ReadTablesOcr.KIND_IMAGE:
|
|
292
|
+
return cfg.file_table_ocr_image
|
|
293
|
+
return cfg.file_table_ocr_pdf
|
|
294
|
+
|
|
295
|
+
@staticmethod
|
|
296
|
+
def _stamp(f: File) -> tuple[int, int] | None:
|
|
297
|
+
try:
|
|
298
|
+
st = os.stat(f.absolute())
|
|
299
|
+
except OSError:
|
|
300
|
+
return None
|
|
301
|
+
return (st.st_mtime_ns, st.st_size)
|
|
302
|
+
|
|
303
|
+
@staticmethod
|
|
304
|
+
def _void() -> TableData[HeaderOcr]:
|
|
305
|
+
return TableData(pd.DataFrame(columns=_OCR_COLUMNS))
|
|
306
|
+
|
|
307
|
+
@staticmethod
|
|
308
|
+
def _read(f: File) -> TableData[HeaderOcr]:
|
|
309
|
+
# dtype=str + fillna(""): célula vazia vira "" (e não o texto "nan").
|
|
310
|
+
df = pd.read_excel(f.absolute(), dtype=str).fillna("")
|
|
311
|
+
for c in _OCR_COLUMNS:
|
|
312
|
+
if c not in df.columns:
|
|
313
|
+
df[c] = ""
|
|
314
|
+
return TableData.create_from_data(
|
|
315
|
+
df[_OCR_COLUMNS].astype(str).reset_index(drop=True)
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
def _load(self, kind: str) -> TableData[HeaderOcr]:
|
|
319
|
+
f = self._file_of(kind)
|
|
320
|
+
stamp = self._stamp(f)
|
|
321
|
+
|
|
322
|
+
with self._lock:
|
|
323
|
+
cached = self._cache.get(kind)
|
|
324
|
+
if cached is not None and cached[0] == stamp:
|
|
325
|
+
return cached[1]
|
|
326
|
+
|
|
327
|
+
if stamp is None: # arquivo ainda não existe (OCR nunca rodou)
|
|
328
|
+
tb = self._void()
|
|
329
|
+
else:
|
|
330
|
+
try:
|
|
331
|
+
print(f"Lendo tabela OCR ({kind}) ... {f.absolute()}")
|
|
332
|
+
tb = self._read(f)
|
|
333
|
+
except Exception as e:
|
|
334
|
+
print(f"[AVISO] falha ao ler {f.absolute()}: {e}")
|
|
335
|
+
# mantém o cache anterior (se houver) e tenta de novo depois
|
|
336
|
+
return cached[1] if cached is not None else self._void()
|
|
337
|
+
|
|
338
|
+
self._cache[kind] = (stamp, tb)
|
|
339
|
+
return tb
|
|
340
|
+
|
|
341
|
+
# ---------------------------------------------------------- READ
|
|
342
|
+
def load_table_image(self) -> TableData[HeaderOcr]:
|
|
343
|
+
return self._load(self.KIND_IMAGE)
|
|
344
|
+
|
|
345
|
+
def load_table_pdf(self) -> TableData[HeaderOcr]:
|
|
346
|
+
return self._load(self.KIND_PDF)
|
|
347
|
+
|
|
348
|
+
def create_from_hashs(self, hashes: Iterable[str]) -> TableData[HeaderOcr] | None:
|
|
349
|
+
"""
|
|
350
|
+
Linhas OCR (imagem + pdf) das hashs informadas, SEM aplicar OCR.
|
|
351
|
+
None se nada for encontrado. O resultado é uma cópia (pode alterar).
|
|
352
|
+
"""
|
|
353
|
+
wanted = {str(h) for h in hashes}
|
|
354
|
+
if not wanted:
|
|
355
|
+
return None
|
|
356
|
+
|
|
357
|
+
frames: list[pd.DataFrame] = []
|
|
358
|
+
for kind in (self.KIND_IMAGE, self.KIND_PDF):
|
|
359
|
+
df = self._load(kind).get_data()
|
|
360
|
+
if df.empty:
|
|
361
|
+
continue
|
|
362
|
+
sub = df[df[_OCR_C_HASH].isin(wanted)]
|
|
363
|
+
if not sub.empty:
|
|
364
|
+
frames.append(sub)
|
|
365
|
+
|
|
366
|
+
if not frames:
|
|
367
|
+
return None
|
|
368
|
+
|
|
369
|
+
final = pd.concat(frames, ignore_index=True)
|
|
370
|
+
return TableData.create_from_data(final[_OCR_COLUMNS].astype(str))
|
|
139
371
|
|
|
140
372
|
|
|
141
373
|
# ============================================================#
|
|
@@ -296,7 +528,8 @@ class BuilderEntregaTOI(BuilderTOI):
|
|
|
296
528
|
|
|
297
529
|
def __init__(self) -> None:
|
|
298
530
|
super().__init__()
|
|
299
|
-
self.relatorio_ocr = CreateRelatorioOcr()
|
|
531
|
+
# self.relatorio_ocr = CreateRelatorioOcr()
|
|
532
|
+
self.relatorio_ocr = ReadTablesOcr()
|
|
300
533
|
self.relatorio_wp = ControllerWhatsApp()
|
|
301
534
|
|
|
302
535
|
self.default_cols_toi = ArrayList[HeaderEntregaTOI].from_list(
|
|
@@ -1257,7 +1490,7 @@ class ControllerTOI(object):
|
|
|
1257
1490
|
return self.get_factory_toi().relatorio_wp
|
|
1258
1491
|
|
|
1259
1492
|
@property
|
|
1260
|
-
def relatorio_ocr(self) ->
|
|
1493
|
+
def relatorio_ocr(self) -> ReadTablesOcr:
|
|
1261
1494
|
return self.get_factory_toi().relatorio_ocr
|
|
1262
1495
|
|
|
1263
1496
|
def get_factory_toi(self) -> BuilderEntregaTOI:
|
|
@@ -1375,11 +1608,9 @@ class ControllerTOI(object):
|
|
|
1375
1608
|
return None
|
|
1376
1609
|
|
|
1377
1610
|
# Filtrar
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
cache_ocr: TableData[HeaderOcr] | None = tb_cache_ocr.search_all(
|
|
1382
|
-
HeaderOcr.HASH_FILE, hash_files.tolist()
|
|
1611
|
+
# Filtrar: imagem + pdf, só as hashs pedidas (cópia, sem mexer no cache)
|
|
1612
|
+
cache_ocr: TableData[HeaderOcr] | None = self.relatorio_ocr.create_from_hashs(
|
|
1613
|
+
hash_files
|
|
1383
1614
|
)
|
|
1384
1615
|
|
|
1385
1616
|
if cache_ocr is None:
|
|
@@ -1762,6 +1993,7 @@ def load_files_toi_v2(
|
|
|
1762
1993
|
print("[DEBUG]: load_files_toi_v2() lista de arquivos vazia!")
|
|
1763
1994
|
return None
|
|
1764
1995
|
|
|
1996
|
+
f: File
|
|
1765
1997
|
for f in files:
|
|
1766
1998
|
if (not f.is_image()) and (not f.is_pdf()):
|
|
1767
1999
|
raise ValueError(
|
|
@@ -1836,7 +2068,7 @@ class RenameFilesTOI:
|
|
|
1836
2068
|
nome de destino (acrescenta -1, -2...). modo_v2=False mantém o fluxo antigo.
|
|
1837
2069
|
"""
|
|
1838
2070
|
|
|
1839
|
-
def __init__(self, files: Set[File], *, modo_v2: bool =
|
|
2071
|
+
def __init__(self, files: Set[File], *, modo_v2: bool = True) -> None:
|
|
1840
2072
|
self.config: ConfigTOI = BuilderConfigTOI().builder()
|
|
1841
2073
|
self.files = files
|
|
1842
2074
|
self.modo_v2: bool = modo_v2
|
|
@@ -1844,10 +2076,10 @@ class RenameFilesTOI:
|
|
|
1844
2076
|
|
|
1845
2077
|
self.base_cartas: Directory = self.config.dir_base_toi
|
|
1846
2078
|
self.error_uc_dir: Directory = self.base_cartas.concat(
|
|
1847
|
-
"
|
|
2079
|
+
"UCS_NAO_IDENTIFICADAS", True
|
|
1848
2080
|
)
|
|
1849
2081
|
self.error_date_dir: Directory = self.base_cartas.concat(
|
|
1850
|
-
"
|
|
2082
|
+
"DATA_NAO_ENCONTRADA", True
|
|
1851
2083
|
)
|
|
1852
2084
|
|
|
1853
2085
|
self.map_files: HashMapDict[hashFile, File] = HashMapDict()
|
|
@@ -1874,6 +2106,27 @@ class RenameFilesTOI:
|
|
|
1874
2106
|
BuilderTableWhatsApp().create_from_data(content_chat.to_dataframe()).rows()
|
|
1875
2107
|
)
|
|
1876
2108
|
|
|
2109
|
+
r: RowWhatsApp
|
|
2110
|
+
for r in rows:
|
|
2111
|
+
h = r.get(HeaderWhatsApp.hash_md5)
|
|
2112
|
+
file_path: File = content_map.get_value(h)
|
|
2113
|
+
|
|
2114
|
+
dt: datetime = r.get_date_hour()
|
|
2115
|
+
y = str(dt.year)
|
|
2116
|
+
m = f"{dt.month:02d}"
|
|
2117
|
+
|
|
2118
|
+
# ano/ano-mes/arquivo
|
|
2119
|
+
current_dir = self.error_uc_dir.concat(y).concat(f"{y}-{m}", True)
|
|
2120
|
+
final_path: File = _resolver_colisao_arquivo(
|
|
2121
|
+
current_dir.join_file(file_path.basename())
|
|
2122
|
+
)
|
|
2123
|
+
|
|
2124
|
+
try:
|
|
2125
|
+
move_path(file_path, final_path)
|
|
2126
|
+
except Exception as e:
|
|
2127
|
+
print(f"{__class__.__name__}(): {e}")
|
|
2128
|
+
|
|
2129
|
+
"""
|
|
1877
2130
|
r: RowWhatsApp
|
|
1878
2131
|
for r in rows:
|
|
1879
2132
|
h = r.get(HeaderWhatsApp.hash_md5)
|
|
@@ -1892,6 +2145,7 @@ class RenameFilesTOI:
|
|
|
1892
2145
|
move_path(file_path, final_path)
|
|
1893
2146
|
except Exception as e:
|
|
1894
2147
|
print(f"{__class__.__name__}(): {e}")
|
|
2148
|
+
"""
|
|
1895
2149
|
|
|
1896
2150
|
def _index_files(self) -> None:
|
|
1897
2151
|
"""Indexa os arquivos originais por hash."""
|
|
@@ -1963,11 +2217,13 @@ class RenameFilesTOI:
|
|
|
1963
2217
|
return self.error_date_dir, f"{uc} {toi}"
|
|
1964
2218
|
|
|
1965
2219
|
year, month, day = date
|
|
1966
|
-
dest_dir = (
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
)
|
|
2220
|
+
# dest_dir = (
|
|
2221
|
+
# self.base_cartas.concat(year)
|
|
2222
|
+
# .concat(f"{year}-{month}")
|
|
2223
|
+
# .concat(f"{year}-{month}-{day}", True)
|
|
2224
|
+
# )
|
|
2225
|
+
|
|
2226
|
+
dest_dir = self.base_cartas.concat(year).concat(f"{year}-{month}", True)
|
|
1971
2227
|
return dest_dir, f"{year} {month} {day} {uc} {toi}"
|
|
1972
2228
|
|
|
1973
2229
|
def _destino_livre(self, dest: File, src: File) -> File:
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
+
from typing import Any
|
|
2
3
|
from dataclasses import dataclass
|
|
3
|
-
from table_stream import
|
|
4
|
+
from table_stream import (
|
|
5
|
+
File,
|
|
6
|
+
Directory,
|
|
7
|
+
UserFileSystem,
|
|
8
|
+
JsonConvert,
|
|
9
|
+
HashMapDict,
|
|
10
|
+
HashMap,
|
|
11
|
+
ArrayList,
|
|
12
|
+
)
|
|
4
13
|
from pyflutter_ui.theme import ThemeMode
|
|
5
14
|
from pyflutter_ui.ui.core import ValueNotifier
|
|
6
15
|
|
|
@@ -13,29 +22,42 @@ class MapConfig:
|
|
|
13
22
|
work_dir: Directory
|
|
14
23
|
documents_dir: Directory
|
|
15
24
|
theme_mode: ThemeMode
|
|
25
|
+
initial_dir_map: HashMap[str, Directory]
|
|
16
26
|
|
|
17
27
|
|
|
18
28
|
class FactoryMapConfig(object):
|
|
19
29
|
|
|
20
30
|
def __init__(self) -> None:
|
|
31
|
+
self._conf: MapConfig = self._create()
|
|
32
|
+
|
|
33
|
+
def _create(self) -> MapConfig:
|
|
34
|
+
|
|
21
35
|
self._conf: MapConfig = MapConfig(
|
|
22
36
|
initial_dir=UserFileSystem().get_user_downloads(),
|
|
23
37
|
last_dir=UserFileSystem().get_user_downloads(),
|
|
24
38
|
work_dir=UserFileSystem().get_user_downloads().concat("RFA", True),
|
|
25
39
|
documents_dir=UserFileSystem().get_user_downloads().concat("RFA", True),
|
|
26
40
|
theme_mode=ThemeMode.DARK,
|
|
41
|
+
initial_dir_map=HashMapDict(),
|
|
27
42
|
)
|
|
43
|
+
return self._conf
|
|
44
|
+
|
|
45
|
+
def set_name_initial_dir(self, name: str, d: Directory):
|
|
46
|
+
self._conf.initial_dir_map.set_value(name, d)
|
|
28
47
|
|
|
29
48
|
def create_default(self) -> MapConfig:
|
|
30
49
|
return self._conf
|
|
31
50
|
|
|
32
|
-
def create_from_dict(self, h: HashMapDict[str,
|
|
51
|
+
def create_from_dict(self, h: HashMapDict[str, Any]) -> MapConfig:
|
|
33
52
|
if h.header().contains("initial_dir"):
|
|
34
53
|
self._conf.initial_dir = Directory(h["initial_dir"])
|
|
54
|
+
|
|
35
55
|
if h.header().contains("last_dir"):
|
|
36
56
|
self._conf.last_dir = Directory(h["last_dir"])
|
|
57
|
+
|
|
37
58
|
if h.header().contains("documents_dir"):
|
|
38
59
|
self._conf.last_dir = Directory(h["documents_dir"])
|
|
60
|
+
|
|
39
61
|
if h.header().contains("theme_mode"):
|
|
40
62
|
if h["theme_mode"] == "dark":
|
|
41
63
|
self._conf.theme_mode = ThemeMode.DARK
|
|
@@ -43,6 +65,17 @@ class FactoryMapConfig(object):
|
|
|
43
65
|
self._conf.theme_mode = ThemeMode.LIGHT
|
|
44
66
|
else:
|
|
45
67
|
self._conf.theme_mode = ThemeMode.SYSTEM
|
|
68
|
+
|
|
69
|
+
if h.header().contains("initial_dir_map"):
|
|
70
|
+
_map_initial_dict: dict[str, str] = h.get_value("initial_dir_map")
|
|
71
|
+
final_map = HashMapDict[str, Directory]()
|
|
72
|
+
keys_initial_dir = ArrayList.from_list(list(_map_initial_dict.keys()))
|
|
73
|
+
|
|
74
|
+
keys_initial_dir.for_each(
|
|
75
|
+
lambda k: final_map.set_value(k, Directory(_map_initial_dict[k]))
|
|
76
|
+
)
|
|
77
|
+
self._conf.initial_dir_map = final_map
|
|
78
|
+
|
|
46
79
|
return self._conf
|
|
47
80
|
|
|
48
81
|
def create_from_file(self, file_json: File) -> MapConfig:
|
|
@@ -71,18 +104,39 @@ class AppConfigNotifier(ValueNotifier[MapConfig]):
|
|
|
71
104
|
self._value.documents_dir = d
|
|
72
105
|
self.notify_listeners()
|
|
73
106
|
|
|
107
|
+
def set_initial_dir_named(self, name: str, d: Directory):
|
|
108
|
+
cfg = self._value
|
|
109
|
+
cfg.initial_dir_map[name] = d
|
|
110
|
+
self._value = cfg
|
|
111
|
+
self.notify_listeners()
|
|
112
|
+
|
|
74
113
|
def get_file_config(self) -> File:
|
|
75
114
|
return AppConfigNotifier.file_config
|
|
76
115
|
|
|
77
|
-
def
|
|
78
|
-
final = dict()
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
116
|
+
def config_to_map(self) -> dict[str, Any]:
|
|
117
|
+
final = dict[str, Any]()
|
|
118
|
+
cfg = self.value
|
|
119
|
+
|
|
120
|
+
# Converter initial_dir_map para dict/json
|
|
121
|
+
json_initial_dir_map = dict[str, str]()
|
|
122
|
+
header_initial_dir = cfg.initial_dir_map.header()
|
|
123
|
+
header_initial_dir.for_each(
|
|
124
|
+
lambda h_name: json_initial_dir_map.setdefault(
|
|
125
|
+
h_name, cfg.initial_dir_map.get_value(h_name).absolute()
|
|
126
|
+
)
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
final["initial_dir_map"] = json_initial_dir_map
|
|
130
|
+
final["initial_dir"] = cfg.initial_dir.absolute()
|
|
131
|
+
final["last_dir"] = cfg.last_dir.absolute()
|
|
132
|
+
final["work_dir"] = cfg.work_dir.absolute()
|
|
133
|
+
final["documents_dir"] = cfg.documents_dir.absolute()
|
|
134
|
+
final["theme_mode"] = cfg.theme_mode.value
|
|
84
135
|
return final
|
|
85
136
|
|
|
137
|
+
def get_dict_config(self) -> dict[str, Any]:
|
|
138
|
+
return self.config_to_map()
|
|
139
|
+
|
|
86
140
|
def save_config(self):
|
|
87
141
|
converter = JsonConvert.from_dict(self.get_dict_config())
|
|
88
142
|
converter.data.to_file(self.get_file_config())
|
|
@@ -71,6 +71,21 @@ class ControllerForms(ChangeNotifier):
|
|
|
71
71
|
def get_config(self) -> AppConfigNotifier:
|
|
72
72
|
return Provider.read(AppConfigNotifier)
|
|
73
73
|
|
|
74
|
+
def get_initial_dir(self) -> Directory:
|
|
75
|
+
cfg = self.get_config()
|
|
76
|
+
map_initial_dir = cfg.value.initial_dir_map
|
|
77
|
+
|
|
78
|
+
if map_initial_dir.header().contains("page_forms"):
|
|
79
|
+
return map_initial_dir.get_value("page_forms")
|
|
80
|
+
return cfg.value.initial_dir
|
|
81
|
+
|
|
82
|
+
def set_initial_dir(self, d: Directory):
|
|
83
|
+
k = "page_forms"
|
|
84
|
+
cfg = self.get_config().value
|
|
85
|
+
|
|
86
|
+
cfg.initial_dir_map[k] = d
|
|
87
|
+
self.get_config().value = cfg
|
|
88
|
+
|
|
74
89
|
def show_info(self, i: str) -> None:
|
|
75
90
|
if self.fn_show_info is not None:
|
|
76
91
|
_ui(lambda: self.fn_show_info(i))
|
|
@@ -88,15 +88,18 @@ class _PageRespondsFormsState(StatelessWidget):
|
|
|
88
88
|
self.show_info("Existe um lançamento em andamento.")
|
|
89
89
|
return
|
|
90
90
|
|
|
91
|
-
conf = controller.get_config()
|
|
92
|
-
|
|
91
|
+
# conf = controller.get_config()
|
|
92
|
+
conf_initial_dir = controller.get_initial_dir()
|
|
93
|
+
|
|
94
|
+
self.file_picker.initial_directory = conf_initial_dir.absolute()
|
|
93
95
|
|
|
94
96
|
result: FilePickerResult | None = self.file_picker.pick_files()
|
|
95
97
|
if result is None or not result.files:
|
|
96
98
|
return
|
|
97
99
|
|
|
98
100
|
if self.file_picker.initial_directory is not None:
|
|
99
|
-
conf.set_initial_dir(Directory(self.file_picker.initial_directory))
|
|
101
|
+
# conf.set_initial_dir(Directory(self.file_picker.initial_directory))
|
|
102
|
+
controller.set_initial_dir(Directory(self.file_picker.initial_directory))
|
|
100
103
|
|
|
101
104
|
pf: PlatformFile = result.files[0]
|
|
102
105
|
controller.set_selected_table_toi(File(pf.path))
|
|
@@ -118,6 +121,13 @@ class _PageRespondsFormsState(StatelessWidget):
|
|
|
118
121
|
[
|
|
119
122
|
Row(
|
|
120
123
|
[
|
|
124
|
+
Row(
|
|
125
|
+
[
|
|
126
|
+
ElevatedButton(
|
|
127
|
+
"<- Voltar", lambda: context.navigator.pop()
|
|
128
|
+
),
|
|
129
|
+
]
|
|
130
|
+
),
|
|
121
131
|
ElevatedButton(
|
|
122
132
|
"Gerar tabela (pasta)",
|
|
123
133
|
lambda: self.action_generate_table(controller),
|
|
@@ -148,11 +158,6 @@ class _PageRespondsFormsState(StatelessWidget):
|
|
|
148
158
|
Divider(),
|
|
149
159
|
Expanded(TableOrProgress(running=False, df=df)),
|
|
150
160
|
Divider(),
|
|
151
|
-
Row(
|
|
152
|
-
[
|
|
153
|
-
ElevatedButton("<- Voltar", lambda: context.navigator.pop()),
|
|
154
|
-
]
|
|
155
|
-
),
|
|
156
161
|
]
|
|
157
162
|
)
|
|
158
163
|
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
from .common import FieldError, RenameRequest, ResultRename
|
|
2
|
+
from .progress import ProgressReporter, ProgressState
|
|
3
|
+
from .renderer import DocumentViewer, PageSource, PageSourceFactory
|
|
2
4
|
from .model_image import ModelImage
|
|
3
5
|
from .controller_image import ControllerImage
|
|
4
6
|
from .view_image import PageImage
|
|
@@ -142,7 +142,10 @@ class ResultRename:
|
|
|
142
142
|
|
|
143
143
|
|
|
144
144
|
# Extensões aceitas no REGISTRO em cache (o visualizador só abre imagens).
|
|
145
|
-
REGISTER_EXTENSIONS: Final[frozenset[str]] = IMAGE_EXTENSIONS | {".pdf"}
|
|
145
|
+
# REGISTER_EXTENSIONS: Final[frozenset[str]] = IMAGE_EXTENSIONS | {".pdf"}
|
|
146
|
+
# Imagens + PDF: o visualizador e o registro em cache aceitam os mesmos tipos.
|
|
147
|
+
DOCUMENT_EXTENSIONS: Final[frozenset[str]] = IMAGE_EXTENSIONS | {".pdf"}
|
|
148
|
+
REGISTER_EXTENSIONS: Final[frozenset[str]] = DOCUMENT_EXTENSIONS # alias antigo
|
|
146
149
|
|
|
147
150
|
_RE_NAME_UC_TOI: Final = re.compile(r"(\d+)[\s_]+(\d+)")
|
|
148
151
|
_RE_COLLISION: Final = re.compile(r"-\d{1,2}$")
|