pyflutter-app 0.3.13__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.
Files changed (89) hide show
  1. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/PKG-INFO +1 -1
  2. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/cartas/__init__.py +239 -8
  3. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/pyproject.toml +1 -1
  4. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/uv.lock +1 -1
  5. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/whatsapp_files/__init__.py +103 -1
  6. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/.gitignore +0 -0
  7. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/.idea/.gitignore +0 -0
  8. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/.idea/inspectionProfiles/Project_Default.xml +0 -0
  9. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/.idea/inspectionProfiles/profiles_settings.xml +0 -0
  10. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/.idea/misc.xml +0 -0
  11. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/.idea/modules.xml +0 -0
  12. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/.idea/pyflutter_app.iml +0 -0
  13. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/.idea/vcs.xml +0 -0
  14. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/README.md +0 -0
  15. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/convert_stream/__init__.py +0 -0
  16. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/__init__.py +0 -0
  17. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/__main__.py +0 -0
  18. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/assets/forms_config.json +0 -0
  19. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/config_app.py +0 -0
  20. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/home_page/__init__.py +0 -0
  21. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/home_page/_page_edit.py +0 -0
  22. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/home_page/controller.py +0 -0
  23. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/home_page/model.py +0 -0
  24. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/home_page/page_sheets.py +0 -0
  25. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/home_page/view.py +0 -0
  26. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/lib_toi.py +0 -0
  27. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/page_circuit/__init__.py +0 -0
  28. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/page_circuit/common.py +0 -0
  29. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/page_circuit/controller_circuit.py +0 -0
  30. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/page_circuit/model_circuit.py +0 -0
  31. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/page_circuit/service.py +0 -0
  32. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/page_circuit/view_circuit.py +0 -0
  33. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/page_forms/Diagnorstico.txt +0 -0
  34. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/page_forms/__init__.py +0 -0
  35. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/page_forms/common.py +0 -0
  36. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/page_forms/config.py +0 -0
  37. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/page_forms/controller_forms.py +0 -0
  38. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/page_forms/diagnose_forms.py +0 -0
  39. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/page_forms/master_forms.py +0 -0
  40. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/page_forms/model_forms.py +0 -0
  41. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/page_forms/service.py +0 -0
  42. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/page_forms/view_forms.py +0 -0
  43. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/page_image/__init__.py +0 -0
  44. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/page_image/common.py +0 -0
  45. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/page_image/controller_image.py +0 -0
  46. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/page_image/model_image.py +0 -0
  47. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/page_image/progress.py +0 -0
  48. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/page_image/renderer.py +0 -0
  49. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/page_image/view_image.py +0 -0
  50. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/page_image/widgets.py +0 -0
  51. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/page_ocr/__init__.py +0 -0
  52. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/page_ocr/common.py +0 -0
  53. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/page_ocr/controller_ocr.py +0 -0
  54. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/page_ocr/model_ocr.py +0 -0
  55. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/page_ocr/view_ocr.py +0 -0
  56. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/page_whatsapp/PATCH_whatsapp_files.md +0 -0
  57. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/page_whatsapp/__init__.py +0 -0
  58. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/page_whatsapp/common.py +0 -0
  59. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/page_whatsapp/controller_whatsapp.py +0 -0
  60. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/page_whatsapp/model_whatsapp.py +0 -0
  61. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/page_whatsapp/view_whatsapp.py +0 -0
  62. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/run_app_convert.py +0 -0
  63. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/table_widget.py +0 -0
  64. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/converter/util.py +0 -0
  65. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/local_build/linux.sh +0 -0
  66. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/local_build/send.sh +0 -0
  67. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/main.py +0 -0
  68. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/mvc/controller.py +0 -0
  69. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/mvc/model.py +0 -0
  70. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/mvc/view.py +0 -0
  71. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/new-export_app.sh +0 -0
  72. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/ocr_stream/__init__.py +0 -0
  73. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/pyflutter_ui/__init__.py +0 -0
  74. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/pyflutter_ui/app.py +0 -0
  75. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/pyflutter_ui/file_picker.py +0 -0
  76. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/pyflutter_ui/navigator.py +0 -0
  77. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/pyflutter_ui/scheduler.py +0 -0
  78. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/pyflutter_ui/theme.py +0 -0
  79. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/pyflutter_ui/ui/__init__.py +0 -0
  80. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/pyflutter_ui/ui/buttons.py +0 -0
  81. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/pyflutter_ui/ui/container.py +0 -0
  82. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/pyflutter_ui/ui/core.py +0 -0
  83. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/pyflutter_ui/ui/list_builder.py +0 -0
  84. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/pyflutter_ui/ui/progress.py +0 -0
  85. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/pyflutter_ui/ui/tables.py +0 -0
  86. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/pyflutter_ui/ui/widgets.py +0 -0
  87. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/table_stream/__init__.py +0 -0
  88. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/teste.py +0 -0
  89. {pyflutter_app-0.3.13 → pyflutter_app-0.3.14}/update.sh +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pyflutter_app
3
- Version: 0.3.13
3
+ Version: 0.3.14
4
4
  Summary: Bibliotecas Python para conversão de documentos, interface e processamento de tabelas.
5
5
  Requires-Python: >=3.12
6
6
  Requires-Dist: opencv-python>=5.0.0.93
@@ -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,6 +42,7 @@ 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
 
@@ -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) -> CreateRelatorioOcr:
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
- tb_cache_ocr: TableData[HeaderOcr] = self.relatorio_ocr.load_table_image()
1379
- tb_cache_ocr.merge(self.relatorio_ocr.load_table_pdf())
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:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pyflutter_app"
3
- version = "0.3.13"
3
+ version = "0.3.14"
4
4
  description = "Bibliotecas Python para conversão de documentos, interface e processamento de tabelas."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -779,7 +779,7 @@ wheels = [
779
779
 
780
780
  [[package]]
781
781
  name = "pyflutter-app"
782
- version = "0.3.13"
782
+ version = "0.3.14"
783
783
  source = { editable = "." }
784
784
  dependencies = [
785
785
  { name = "opencv-python" },
@@ -20,6 +20,7 @@ from table_stream import (
20
20
  HeaderMetaData,
21
21
  ContentFiles,
22
22
  EnumDocuments,
23
+ UserFileSystem,
23
24
  extract_zip_files,
24
25
  load_file_hash,
25
26
  # HashMap
@@ -39,7 +40,9 @@ from table_stream import (
39
40
  BaseObject,
40
41
  )
41
42
 
42
- from ocr_stream import BuilderConfigOcr
43
+ # Remover essa dependencia e ocr_stream, mas usar os mesmos caminhos para acesssar
44
+ # as tabelas ocr, apartir da mesma base UserFileSystem().get_user_documents().concat("RFA", True)
45
+ # from ocr_stream import BuilderConfigOcr
43
46
 
44
47
  __version__ = "0.1"
45
48
 
@@ -57,6 +60,7 @@ class InvalidTxtFile(ValueError):
57
60
  def __init__(self, *args: object) -> None:
58
61
  super().__init__(*args)
59
62
 
63
+
60
64
  @dataclass
61
65
  class ResultChatImport:
62
66
  """Resultado da importação de UM chat."""
@@ -68,6 +72,7 @@ class ResultChatImport:
68
72
  hashed: int = 0 # anexos encontrados em disco (hash calculado)
69
73
  added: int = 0 # linhas NOVAS gravadas no cache
70
74
 
75
+
71
76
  def move_path(src: PathElement, dest: PathElement) -> bool:
72
77
 
73
78
  try:
@@ -90,6 +95,97 @@ class ConfigWhatsApp:
90
95
  work_dir: Directory
91
96
 
92
97
 
98
+ # Mesmos nomes usados por ocr_stream.BuilderConfigOcr. Se mudar lá, mude aqui.
99
+ _DIR_ROOT_NAME = "RFA"
100
+ _DIR_TABLES_NAME = "Tabelas"
101
+
102
+
103
+ class BuilderConfigWhatsApp(object):
104
+ """
105
+ Configuração do WhatsApp SEM depender de ocr_stream/tesseract.
106
+
107
+ Usa a mesma base que o ocr_stream:
108
+ <Documentos>/RFA/ -> dir_base_root
109
+ <Documentos>/RFA/Tabelas -> dir_table (onde ficam as tabelas OCR, TOI etc.)
110
+ <Documentos>/RFA/WhatsApp -> dir_base_work
111
+ """
112
+
113
+ _instance = None # Atributo de classe para armazenar a instância singleton
114
+
115
+ def __new__(cls, *args, **kwargs):
116
+ if cls._instance is None:
117
+ cls._instance = super(BuilderConfigWhatsApp, cls).__new__(cls)
118
+ return cls._instance
119
+
120
+ def __init__(self) -> None:
121
+
122
+ # Garante que __init__ não será executado mais de uma vez
123
+ if hasattr(self, "_initialized") and self._initialized:
124
+ return
125
+
126
+ super().__init__()
127
+ self._initialized: bool = True
128
+ self._root_dir: Directory | None = None
129
+ self._config = None
130
+
131
+ # ---------------------------------------------------------- base RFA
132
+ @property
133
+ def dir_base_root(self) -> Directory:
134
+ """<Documentos>/RFA (mesma base do ocr_stream)."""
135
+ if self._root_dir is None:
136
+ self._root_dir = (
137
+ UserFileSystem().get_user_documents().concat(_DIR_ROOT_NAME, True)
138
+ )
139
+ return self._root_dir
140
+
141
+ def set_root_dir(self, d: Directory) -> None:
142
+ """Troca a base (testes / outro PC). Descarta a configuração em cache."""
143
+ d.mkdir()
144
+ self._root_dir = d
145
+ self.reset_config()
146
+
147
+ @property
148
+ def dir_base_work(self) -> Directory:
149
+ return self.dir_base_root.concat("WhatsApp", True)
150
+
151
+ @property
152
+ def dir_table(self) -> Directory:
153
+ """<Documentos>/RFA/Tabelas (mesma pasta das tabelas OCR)."""
154
+ d = self.dir_base_root.concat(_DIR_TABLES_NAME)
155
+ d.mkdir()
156
+ return d
157
+
158
+ @property
159
+ def dir_chats_organizados(self) -> Directory:
160
+ return self.dir_base_work.concat("ConversasOrganizadas", True)
161
+
162
+ @property
163
+ def dir_chats_compactados(self) -> Directory:
164
+ return self.dir_base_work.concat("ConversasCompactadas", True)
165
+
166
+ @property
167
+ def dir_chats_descompactados(self) -> Directory:
168
+ return self.dir_base_work.concat("ConversasDescompactadas", True)
169
+
170
+ def reset_config(self) -> None:
171
+ self._config = None
172
+
173
+ def builder(self) -> ConfigWhatsApp:
174
+ if self._config is None:
175
+
176
+ self._config = ConfigWhatsApp(
177
+ file_relatorio=self.dir_table.join_file("relatorio-whatsapp.xlsx"),
178
+ file_table=self.dir_table.join_file("tabela-whatsapp.xlsx"),
179
+ dir_unzip_chats=self.dir_chats_descompactados,
180
+ dir_zip_chats=self.dir_chats_compactados,
181
+ dir_export_files=self.dir_chats_organizados,
182
+ work_dir=self.dir_base_work,
183
+ )
184
+
185
+ return self._config
186
+
187
+
188
+ """
93
189
  class BuilderConfigWhatsApp(object):
94
190
 
95
191
  _instance = None # Atributo de classe para armazenar a instância singleton
@@ -146,6 +242,7 @@ class BuilderConfigWhatsApp(object):
146
242
  )
147
243
 
148
244
  return self._config
245
+ """
149
246
 
150
247
 
151
248
  class HeaderWhatsApp(StrEnum):
@@ -626,6 +723,7 @@ def get_file_chat_from_dir(d: Directory) -> File | None:
626
723
  return None
627
724
  return txts.get_first()
628
725
 
726
+
629
727
  _NO_HASH = {"", "nan", "NULL_TEXT"}
630
728
 
631
729
  # Uma mensagem é identificada por estes campos + a ocorrência dentro do minuto.
@@ -683,6 +781,8 @@ def merge_chat_frames(old: pd.DataFrame, new: pd.DataFrame) -> pd.DataFrame:
683
781
  both = both.drop_duplicates(subset=sub, keep="first")
684
782
  both = both.sort_values([c_chat, c_dt, "_ord"], kind="stable")
685
783
  return both[cols].reset_index(drop=True)
784
+
785
+
686
786
  class ReadWriteWhatsApp(BaseObject):
687
787
 
688
788
  _instance = None # Atributo de classe para armazenar a instância singleton
@@ -759,6 +859,7 @@ class ReadWriteWhatsApp(BaseObject):
759
859
  df.to_excel(tmp_file, index=False)
760
860
  os.replace(tmp_file, path) # atômico e portável
761
861
 
862
+
762
863
  # ==============================================================#
763
864
  # Commandos
764
865
  # ==============================================================#
@@ -1077,6 +1178,7 @@ class ControllerWhatsApp(BaseObject):
1077
1178
 
1078
1179
  def merge_table_cache(self, tb: TableWhatsApp) -> None:
1079
1180
  self.rw.merge(tb)
1181
+
1080
1182
  def increment_chat_file(self, file_txt: File) -> ResultChatImport:
1081
1183
  """
1082
1184
  Adicionar dados de um CHAT (.txt) na tabela principal.
File without changes
File without changes
File without changes
File without changes