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.
Files changed (93) hide show
  1. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/PKG-INFO +1 -1
  2. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/cartas/__init__.py +273 -17
  3. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/config_app.py +63 -9
  4. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_forms/controller_forms.py +15 -0
  5. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_forms/view_forms.py +13 -8
  6. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_image/__init__.py +2 -0
  7. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_image/common.py +4 -1
  8. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_image/controller_image.py +172 -50
  9. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_image/model_image.py +18 -14
  10. pyflutter_app-0.3.14/converter/page_image/progress.py +77 -0
  11. pyflutter_app-0.3.14/converter/page_image/renderer.py +240 -0
  12. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_image/view_image.py +31 -39
  13. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_image/widgets.py +135 -18
  14. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_ocr/controller_ocr.py +14 -3
  15. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_ocr/view_ocr.py +10 -9
  16. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/table_widget.py +62 -60
  17. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/pyflutter_ui/ui/buttons.py +18 -16
  18. pyflutter_app-0.3.14/pyflutter_ui/ui/progress.py +115 -0
  19. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/pyproject.toml +1 -1
  20. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/table_stream/__init__.py +1 -1
  21. pyflutter_app-0.3.14/teste.py +27 -0
  22. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/uv.lock +1 -1
  23. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/whatsapp_files/__init__.py +103 -1
  24. pyflutter_app-0.3.12/cartas/origin.py +0 -1196
  25. pyflutter_app-0.3.12/converter/page_image/renderer.py +0 -92
  26. pyflutter_app-0.3.12/pyflutter_ui/ui/progress.py +0 -80
  27. pyflutter_app-0.3.12/teste.py +0 -9
  28. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/.gitignore +0 -0
  29. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/.idea/.gitignore +0 -0
  30. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/.idea/inspectionProfiles/Project_Default.xml +0 -0
  31. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/.idea/inspectionProfiles/profiles_settings.xml +0 -0
  32. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/.idea/misc.xml +0 -0
  33. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/.idea/modules.xml +0 -0
  34. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/.idea/pyflutter_app.iml +0 -0
  35. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/.idea/vcs.xml +0 -0
  36. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/README.md +0 -0
  37. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/convert_stream/__init__.py +0 -0
  38. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/__init__.py +0 -0
  39. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/__main__.py +0 -0
  40. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/assets/forms_config.json +0 -0
  41. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/home_page/__init__.py +0 -0
  42. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/home_page/_page_edit.py +0 -0
  43. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/home_page/controller.py +0 -0
  44. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/home_page/model.py +0 -0
  45. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/home_page/page_sheets.py +0 -0
  46. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/home_page/view.py +0 -0
  47. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/lib_toi.py +0 -0
  48. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_circuit/__init__.py +0 -0
  49. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_circuit/common.py +0 -0
  50. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_circuit/controller_circuit.py +0 -0
  51. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_circuit/model_circuit.py +0 -0
  52. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_circuit/service.py +0 -0
  53. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_circuit/view_circuit.py +0 -0
  54. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_forms/Diagnorstico.txt +0 -0
  55. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_forms/__init__.py +0 -0
  56. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_forms/common.py +0 -0
  57. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_forms/config.py +0 -0
  58. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_forms/diagnose_forms.py +0 -0
  59. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_forms/master_forms.py +0 -0
  60. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_forms/model_forms.py +0 -0
  61. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_forms/service.py +0 -0
  62. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_ocr/__init__.py +0 -0
  63. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_ocr/common.py +0 -0
  64. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_ocr/model_ocr.py +0 -0
  65. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_whatsapp/PATCH_whatsapp_files.md +0 -0
  66. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_whatsapp/__init__.py +0 -0
  67. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_whatsapp/common.py +0 -0
  68. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_whatsapp/controller_whatsapp.py +0 -0
  69. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_whatsapp/model_whatsapp.py +0 -0
  70. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/page_whatsapp/view_whatsapp.py +0 -0
  71. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/run_app_convert.py +0 -0
  72. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/converter/util.py +0 -0
  73. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/local_build/linux.sh +0 -0
  74. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/local_build/send.sh +0 -0
  75. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/main.py +0 -0
  76. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/mvc/controller.py +0 -0
  77. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/mvc/model.py +0 -0
  78. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/mvc/view.py +0 -0
  79. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/new-export_app.sh +0 -0
  80. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/ocr_stream/__init__.py +0 -0
  81. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/pyflutter_ui/__init__.py +0 -0
  82. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/pyflutter_ui/app.py +0 -0
  83. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/pyflutter_ui/file_picker.py +0 -0
  84. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/pyflutter_ui/navigator.py +0 -0
  85. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/pyflutter_ui/scheduler.py +0 -0
  86. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/pyflutter_ui/theme.py +0 -0
  87. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/pyflutter_ui/ui/__init__.py +0 -0
  88. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/pyflutter_ui/ui/container.py +0 -0
  89. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/pyflutter_ui/ui/core.py +0 -0
  90. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/pyflutter_ui/ui/list_builder.py +0 -0
  91. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/pyflutter_ui/ui/tables.py +0 -0
  92. {pyflutter_app-0.3.12 → pyflutter_app-0.3.14}/pyflutter_ui/ui/widgets.py +0 -0
  93. {pyflutter_app-0.3.12 → 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.12
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,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.2"
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) -> 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:
@@ -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 = False) -> None:
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
- "CARTAS_UC_NAO_IDENTIFICADAS", True
2079
+ "UCS_NAO_IDENTIFICADAS", True
1848
2080
  )
1849
2081
  self.error_date_dir: Directory = self.base_cartas.concat(
1850
- "CARTAS_DATA_NAO_ENCONTRADA", True
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
- self.base_cartas.concat(year)
1968
- .concat(f"{year}-{month}")
1969
- .concat(f"{year}-{month}-{day}", True)
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 File, Directory, UserFileSystem, JsonConvert, HashMapDict
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, str]) -> MapConfig:
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 get_dict_config(self) -> dict[str, str]:
78
- final = dict()
79
- final["initial_dir"] = self.value.initial_dir.absolute()
80
- final["last_dir"] = self.value.last_dir.absolute()
81
- final["work_dir"] = self.value.work_dir.absolute()
82
- final["documents_dir"] = self.value.documents_dir.absolute()
83
- final["theme_mode"] = self.value.theme_mode.value
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
- self.file_picker.initial_directory = conf.value.initial_dir.absolute()
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}$")