DocUA commited on
Commit
1ca7c3e
·
1 Parent(s): 9dcb638

Refactor app.py to use ClassifierApp and improve application structure

Browse files
Files changed (3) hide show
  1. app.py +56 -222
  2. classifier_app.py +278 -0
  3. model_info.json +5 -5
app.py CHANGED
@@ -1,128 +1,15 @@
1
  import gradio as gr
2
- from sdc_classifier import SDCClassifier
3
- from dotenv import load_dotenv
4
  import torch
5
- import json
6
  import os
 
7
 
8
- # Load environment variables
9
- load_dotenv()
10
-
11
- def initialize_environment():
12
- """Ініціалізація середовища при першому запуску"""
13
- DEFAULT_CLASSES_FILE = "classes.json"
14
- DEFAULT_SIGNATURES_FILE = "signatures.npz"
15
- CACHE_FILE = "embeddings_cache.db"
16
-
17
- initial_info = {
18
- "status": "initializing",
19
- "model_info": {},
20
- "classes_info": {},
21
- "errors": []
22
- }
23
-
24
- # Перевіряємо наявність необхідних файлів
25
- if not os.path.exists(DEFAULT_CLASSES_FILE):
26
- initial_info["errors"].append(f"ПОМИЛКА: Файл {DEFAULT_CLASSES_FILE} не знайдено!")
27
- initial_info["status"] = "error"
28
- return initial_info
29
-
30
- # Створюємо класифікатор та завантажуємо класи
31
- try:
32
- classifier = SDCClassifier()
33
- classes = classifier.load_classes(DEFAULT_CLASSES_FILE)
34
-
35
- # Збираємо інформацію про класи
36
- initial_info["classes_info"] = {
37
- "total_classes": len(classes),
38
- "classes_list": list(classes.keys()),
39
- "hints_per_class": {cls: len(hints) for cls, hints in classes.items()}
40
- }
41
-
42
- # Якщо signatures не існують, створюємо нові
43
- if not os.path.exists(DEFAULT_SIGNATURES_FILE):
44
- initial_info["status"] = "creating_signatures"
45
- result = classifier.initialize_signatures(
46
- force_rebuild=True,
47
- signatures_file=DEFAULT_SIGNATURES_FILE
48
- )
49
-
50
- if isinstance(result, str) and "error" in result.lower():
51
- initial_info["errors"].append(result)
52
- initial_info["status"] = "error"
53
- return initial_info
54
-
55
- # Завантажуємо інформацію про модель
56
- classifier.save_model_info("model_info.json")
57
- with open("model_info.json", "r") as f:
58
- initial_info["model_info"] = json.load(f)
59
-
60
- initial_info["status"] = "success"
61
- return initial_info, classifier
62
-
63
- except Exception as e:
64
- initial_info["errors"].append(f"ПОМИЛКА при ініціалізації: {str(e)}")
65
- initial_info["status"] = "error"
66
- return initial_info, None
67
-
68
- def create_classifier(model_type, openai_model=None, local_model=None, device=None):
69
- """
70
- Створення класифікатора з відповідними параметрами
71
-
72
- Args:
73
- model_type: тип моделі ("OpenAI" або "Local")
74
- openai_model: назва моделі OpenAI
75
- local_model: шлях до локальної моделі
76
- device: пристрій для локальної моделі
77
-
78
- Returns:
79
- SDCClassifier: налаштований класифікатор
80
- """
81
- if model_type == "OpenAI":
82
- return SDCClassifier()
83
- else:
84
- return SDCClassifier(local_model=local_model, device=device)
85
-
86
- def main():
87
- # Константи файлів
88
- DEFAULT_CLASSES_FILE = "classes.json"
89
- DEFAULT_SIGNATURES_FILE = "signatures.npz"
90
- CACHE_FILE = "embeddings_cache.db"
91
-
92
- # Перевіряємо та ініціалізуємо середовище
93
- init_result = initialize_environment()
94
- if isinstance(init_result, tuple):
95
- initial_info, classifier = init_result
96
- else:
97
- initial_info = init_result
98
- print("Не вдалося ініціалізувати середовище")
99
- return
100
-
101
  with gr.Blocks() as demo:
102
  gr.Markdown("# SDC Classifier")
103
-
104
- # Додаємо інформаційний блок про модель та класи
105
- with gr.Accordion("Інформація про систему", open=True):
106
- system_info = gr.JSON(
107
- value=initial_info,
108
- label="Статус системи"
109
- )
110
-
111
- if initial_info["status"] == "success":
112
- gr.Markdown(f"""
113
- ### Поточна конфігурація:
114
- - Модель: {initial_info['model_info'].get('using_local', 'OpenAI')}
115
- - Кількість класів: {initial_info['classes_info']['total_classes']}
116
- - Класи: {', '.join(initial_info['classes_info']['classes_list'])}
117
- """)
118
- else:
119
- gr.Markdown(f"""
120
- ### Помилки ініціалізації:
121
- {chr(10).join('- ' + err for err in initial_info['errors'])}
122
- """)
123
-
124
  with gr.Tabs():
125
- # Вкладка 1: Single Text Testing
126
  with gr.TabItem("Тестування одного тексту"):
127
  with gr.Row():
128
  with gr.Column():
@@ -143,7 +30,6 @@ def main():
143
  with gr.Column():
144
  result_text = gr.JSON(label="Результати аналізу")
145
 
146
- # Налаштування моделі
147
  with gr.Accordion("Налаштування моделі", open=False):
148
  with gr.Row():
149
  model_type = gr.Radio(
@@ -152,16 +38,13 @@ def main():
152
  label="Тип моделі"
153
  )
154
  model_choice = gr.Dropdown(
155
- choices=[
156
- "text-embedding-3-large",
157
- "text-embedding-3-small"
158
- ],
159
- value="text-embedding-3-large",
160
  label="OpenAI model",
161
  visible=True
162
  )
163
  local_model_path = gr.Textbox(
164
- value="cambridgeltl/SapBERT-from-PubMedBERT-fulltext",
165
  label="Шлях до локальної моделі",
166
  visible=False
167
  )
@@ -186,8 +69,17 @@ def main():
186
  build_btn = gr.Button("Оновити signatures")
187
  build_out = gr.Label(label="Статус signatures")
188
  cache_stats = gr.JSON(label="Статистика кешу", value={})
 
 
 
 
 
 
 
 
 
189
 
190
- # Вкладка 2: Batch Processing
191
  with gr.TabItem("Пакетна обробка"):
192
  gr.Markdown("## 1) Завантаження даних")
193
  with gr.Row():
@@ -233,101 +125,29 @@ def main():
233
  4. На вкладці "Пакетна обробка" можна аналізувати багато повідомлень
234
  5. Результати можна зберегти в CSV файл
235
  """)
236
-
237
  # Підключення обробників подій
238
- def update_model_inputs(model_type):
239
- """Оновлення видимості полів в залежності від типу моделі"""
240
- return {
241
- model_choice: gr.update(visible=model_type == "OpenAI"),
242
- local_model_path: gr.update(visible=model_type == "Local"),
243
- device_choice: gr.update(visible=model_type == "Local")
244
- }
245
-
246
- def update_classifier_settings(json_file, model_type, openai_model,
247
- local_model, device, force_rebuild):
248
- """Оновлення налаштувань класифікатора"""
249
- try:
250
- # Створюємо новий класифікатор з вибраними параметрами
251
- nonlocal classifier
252
- classifier = create_classifier(
253
- model_type=model_type,
254
- openai_model=openai_model if model_type == "OpenAI" else None,
255
- local_model=local_model if model_type == "Local" else None,
256
- device=device if model_type == "Local" else None
257
- )
258
-
259
- # Завантажуємо класи
260
- if json_file is not None:
261
- with open(json_file.name, 'r', encoding='utf-8') as f:
262
- new_classes = json.load(f)
263
- classifier.load_classes(new_classes)
264
- else:
265
- classifier.restore_base_state()
266
-
267
- # Ініціалізуємо signatures
268
- result = classifier.initialize_signatures(
269
- force_rebuild=force_rebuild,
270
- signatures_file=DEFAULT_SIGNATURES_FILE if not force_rebuild else None
271
- )
272
-
273
- # Оновлюємо інформацію про систем��
274
- classifier.save_model_info("model_info.json")
275
- with open("model_info.json", "r") as f:
276
- model_info = json.load(f)
277
-
278
- system_info.update(value={
279
- "status": "success",
280
- "model_info": model_info,
281
- "classes_info": {
282
- "total_classes": len(classifier.classes_json),
283
- "classes_list": list(classifier.classes_json.keys()),
284
- "hints_per_class": {cls: len(hints)
285
- for cls, hints in classifier.classes_json.items()}
286
- },
287
- "errors": []
288
- })
289
-
290
- return result, classifier.get_cache_stats()
291
- except Exception as e:
292
- return f"Помилка: {str(e)}", classifier.get_cache_stats()
293
-
294
- def process_single_text(text, threshold):
295
- """Обробка одного тексту"""
296
- try:
297
- return classifier.process_single_text(text, threshold)
298
- except Exception as e:
299
- return {"error": str(e)}
300
-
301
- def load_data(csv_path, emb_path):
302
- """Завантаження даних для пакетної обробки"""
303
- try:
304
- return classifier.load_data(csv_path, emb_path)
305
- except Exception as e:
306
- return f"Помилка: {str(e)}"
307
-
308
- def classify_batch(filter_str, threshold):
309
- """Пакетна класифікація"""
310
- try:
311
- return classifier.classify_rows(filter_str, threshold)
312
- except Exception as e:
313
- return None
314
-
315
- def save_results():
316
- """Збереження результатів"""
317
- try:
318
- return classifier.save_results()
319
- except Exception as e:
320
- return f"Помилка: {str(e)}"
321
-
322
- # Підключення подій
323
  model_type.change(
324
- fn=update_model_inputs,
325
- inputs=[model_type],
326
- outputs=[model_choice, local_model_path, device_choice]
 
 
 
 
 
 
 
 
 
 
 
 
 
327
  )
328
 
329
  build_btn.click(
330
- fn=update_classifier_settings,
331
  inputs=[
332
  json_file,
333
  model_type,
@@ -336,34 +156,48 @@ def main():
336
  device_choice,
337
  force_rebuild
338
  ],
339
- outputs=[build_out, cache_stats]
340
  )
341
 
342
  single_process_btn.click(
343
- fn=process_single_text,
344
  inputs=[text_input, threshold_slider],
345
  outputs=result_text
346
  )
347
 
348
  load_btn.click(
349
- fn=load_data,
350
  inputs=[csv_input, emb_input],
351
  outputs=load_output
352
  )
353
 
354
  classify_btn.click(
355
- fn=classify_batch,
356
  inputs=[filter_in, batch_threshold],
357
  outputs=classify_out
358
  )
359
 
360
  save_btn.click(
361
- fn=save_results,
362
  inputs=[],
363
  outputs=save_out
364
  )
365
 
366
- # Запуск веб-інтерфейсу
 
 
 
 
 
 
 
 
 
 
 
 
 
 
367
  demo.launch(server_name="0.0.0.0", server_port=7860, share=True)
368
 
369
  if __name__ == "__main__":
 
1
  import gradio as gr
 
 
2
  import torch
 
3
  import os
4
+ from classifier_app import ClassifierApp, config
5
 
6
+ def create_interface(app: ClassifierApp) -> gr.Blocks:
7
+ """Створення веб-інтерфейсу"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  with gr.Blocks() as demo:
9
  gr.Markdown("# SDC Classifier")
10
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  with gr.Tabs():
12
+ # Вкладка 1: Тестування одного тексту
13
  with gr.TabItem("Тестування одного тексту"):
14
  with gr.Row():
15
  with gr.Column():
 
30
  with gr.Column():
31
  result_text = gr.JSON(label="Результати аналізу")
32
 
 
33
  with gr.Accordion("Налаштування моделі", open=False):
34
  with gr.Row():
35
  model_type = gr.Radio(
 
38
  label="Тип моделі"
39
  )
40
  model_choice = gr.Dropdown(
41
+ choices=config.DEFAULT_OPENAI_MODELS,
42
+ value=config.DEFAULT_OPENAI_MODELS[0],
 
 
 
43
  label="OpenAI model",
44
  visible=True
45
  )
46
  local_model_path = gr.Textbox(
47
+ value=config.DEFAULT_LOCAL_MODEL,
48
  label="Шлях до локальної моделі",
49
  visible=False
50
  )
 
69
  build_btn = gr.Button("Оновити signatures")
70
  build_out = gr.Label(label="Статус signatures")
71
  cache_stats = gr.JSON(label="Статистика кешу", value={})
72
+
73
+ # Вкладка 2: Інформація про систему
74
+ with gr.TabItem("Інформація про систему"):
75
+ system_info = gr.JSON(
76
+ value=app.initial_info,
77
+ label="Статус системи"
78
+ )
79
+ system_md = gr.Markdown()
80
+ system_md.value = app.update_system_markdown(app.initial_info)
81
 
82
+ # Вкладка 3: Пакетна обробка
83
  with gr.TabItem("Пакетна обробка"):
84
  gr.Markdown("## 1) Завантаження даних")
85
  with gr.Row():
 
125
  4. На вкладці "Пакетна обробка" можна аналізувати багато повідомлень
126
  5. Результати можна зберегти в CSV файл
127
  """)
128
+
129
  # Підключення обробників подій
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
  model_type.change(
131
+ fn=app.update_model_inputs,
132
+ inputs=[
133
+ model_type,
134
+ model_choice,
135
+ local_model_path,
136
+ device_choice
137
+ ],
138
+ outputs=[
139
+ model_choice,
140
+ local_model_path,
141
+ device_choice,
142
+ system_info,
143
+ system_md,
144
+ build_out,
145
+ cache_stats
146
+ ]
147
  )
148
 
149
  build_btn.click(
150
+ fn=app.update_classifier_settings,
151
  inputs=[
152
  json_file,
153
  model_type,
 
156
  device_choice,
157
  force_rebuild
158
  ],
159
+ outputs=[build_out, cache_stats, system_info, system_md]
160
  )
161
 
162
  single_process_btn.click(
163
+ fn=app.process_single_text,
164
  inputs=[text_input, threshold_slider],
165
  outputs=result_text
166
  )
167
 
168
  load_btn.click(
169
+ fn=app.load_data,
170
  inputs=[csv_input, emb_input],
171
  outputs=load_output
172
  )
173
 
174
  classify_btn.click(
175
+ fn=app.classify_batch,
176
  inputs=[filter_in, batch_threshold],
177
  outputs=classify_out
178
  )
179
 
180
  save_btn.click(
181
+ fn=app.save_results,
182
  inputs=[],
183
  outputs=save_out
184
  )
185
 
186
+ return demo
187
+
188
+ def main():
189
+ app = ClassifierApp()
190
+ init_result, classifier = app.initialize_environment()
191
+
192
+ if classifier is None or init_result["status"] != "success":
193
+ print("Не вдалося ініціалізувати середовище")
194
+ return
195
+
196
+ print(f"Статус ініціалізації: {init_result['status']}")
197
+ print(f"Кількість завантажених класів: {len(init_result['classes_info']['classes_list'])}")
198
+ print(f"Сигнатури: {'Завантажено' if os.path.exists(config.DEFAULT_SIGNATURES_FILE) else 'Створюються'}")
199
+
200
+ demo = create_interface(app)
201
  demo.launch(server_name="0.0.0.0", server_port=7860, share=True)
202
 
203
  if __name__ == "__main__":
classifier_app.py ADDED
@@ -0,0 +1,278 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sdc_classifier import SDCClassifier
2
+ from dotenv import load_dotenv
3
+ import torch
4
+ import json
5
+ import os
6
+ from typing import Dict, Tuple, Optional, Any, List
7
+ from dataclasses import dataclass, field
8
+
9
+ # Load environment variables
10
+ load_dotenv()
11
+
12
+ @dataclass
13
+ class Config:
14
+ DEFAULT_CLASSES_FILE: str = "classes.json"
15
+ DEFAULT_SIGNATURES_FILE: str = "signatures.npz"
16
+ CACHE_FILE: str = "embeddings_cache.db"
17
+ MODEL_INFO_FILE: str = "model_info.json"
18
+ DEFAULT_OPENAI_MODELS: List[str] = field(default_factory=lambda: ["text-embedding-3-large", "text-embedding-3-small"])
19
+ DEFAULT_LOCAL_MODEL: str = "cambridgeltl/SapBERT-from-PubMedBERT-fulltext"
20
+
21
+ config = Config()
22
+
23
+ class ClassifierApp:
24
+ def __init__(self):
25
+ self.classifier = None
26
+ self.initial_info = {
27
+ "status": "initializing",
28
+ "model_info": {},
29
+ "classes_info": {},
30
+ "errors": []
31
+ }
32
+
33
+ def initialize_environment(self) -> Tuple[Dict, Optional[SDCClassifier]]:
34
+ """Ініціалізація середовища при першому запуску"""
35
+
36
+ if not os.path.exists(config.DEFAULT_CLASSES_FILE):
37
+ self.initial_info["errors"].append(
38
+ f"ПОМИЛКА: Файл {config.DEFAULT_CLASSES_FILE} не знайдено!"
39
+ )
40
+ self.initial_info["status"] = "error"
41
+ return self.initial_info, None
42
+
43
+ try:
44
+ self.classifier = SDCClassifier()
45
+ classes = self.classifier.load_classes(config.DEFAULT_CLASSES_FILE)
46
+
47
+ self.initial_info["classes_info"] = {
48
+ "total_classes": len(classes),
49
+ "classes_list": list(classes.keys()),
50
+ "hints_per_class": {cls: len(hints) for cls, hints in classes.items()}
51
+ }
52
+
53
+ if os.path.exists(config.DEFAULT_SIGNATURES_FILE):
54
+ self.classifier.load_signatures(config.DEFAULT_SIGNATURES_FILE)
55
+ self.initial_info["status"] = "success"
56
+ else:
57
+ self.initial_info["status"] = "creating_signatures"
58
+ result = self.classifier.initialize_signatures(
59
+ force_rebuild=True,
60
+ signatures_file=config.DEFAULT_SIGNATURES_FILE
61
+ )
62
+
63
+ if isinstance(result, str) and "error" in result.lower():
64
+ self.initial_info["errors"].append(result)
65
+ self.initial_info["status"] = "error"
66
+ return self.initial_info, None
67
+
68
+ try:
69
+ self.classifier.save_model_info(config.MODEL_INFO_FILE)
70
+ with open(config.MODEL_INFO_FILE, "r") as f:
71
+ self.initial_info["model_info"] = json.load(f)
72
+
73
+ self.initial_info["status"] = "success"
74
+ return self.initial_info, self.classifier
75
+
76
+ except (FileNotFoundError, json.JSONDecodeError) as e:
77
+ self.initial_info["errors"].append(f"Помилка при читанні model_info: {str(e)}")
78
+ self.initial_info["status"] = "error"
79
+ return self.initial_info, None
80
+
81
+ except Exception as e:
82
+ self.initial_info["errors"].append(f"ПОМИЛКА при ініціалізації: {str(e)}")
83
+ self.initial_info["status"] = "error"
84
+ return self.initial_info, None
85
+
86
+ def create_classifier(
87
+ self,
88
+ model_type: str,
89
+ openai_model: Optional[str] = None,
90
+ local_model: Optional[str] = None,
91
+ device: Optional[str] = None
92
+ ) -> SDCClassifier:
93
+ """Створення класифікатора з відповідними параметрами"""
94
+ if model_type == "OpenAI":
95
+ return SDCClassifier(openai_model=openai_model)
96
+ else:
97
+ return SDCClassifier(local_model=local_model, device=device)
98
+
99
+ def update_model_inputs(
100
+ self,
101
+ model_type: str,
102
+ openai_model: str,
103
+ local_model: str,
104
+ device: str
105
+ ) -> Dict[str, Any]:
106
+ """Оновлення моделі та інтерфейсу при зміні типу моделі"""
107
+ try:
108
+ self.classifier = self.create_classifier(
109
+ model_type=model_type,
110
+ openai_model=openai_model if model_type == "OpenAI" else None,
111
+ local_model=local_model if model_type == "Local" else None,
112
+ device=device if model_type == "Local" else None
113
+ )
114
+
115
+ self.classifier.restore_base_state()
116
+ result = self.classifier.initialize_signatures()
117
+
118
+ self.classifier.save_model_info(config.MODEL_INFO_FILE)
119
+ with open(config.MODEL_INFO_FILE, "r") as f:
120
+ model_info = json.load(f)
121
+
122
+ new_system_info = {
123
+ "status": "success",
124
+ "model_info": model_info,
125
+ "classes_info": {
126
+ "total_classes": len(self.classifier.classes_json),
127
+ "classes_list": list(self.classifier.classes_json.keys()),
128
+ "hints_per_class": {cls: len(hints) for cls, hints in self.classifier.classes_json.items()}
129
+ },
130
+ "errors": []
131
+ }
132
+
133
+ return {
134
+ "model_choice": gr.update(visible=model_type == "OpenAI"),
135
+ "local_model_path": gr.update(visible=model_type == "Local"),
136
+ "device_choice": gr.update(visible=model_type == "Local"),
137
+ "system_info": new_system_info,
138
+ "system_md": self.update_system_markdown(new_system_info),
139
+ "build_out": f"Модель змінено на {model_type}",
140
+ "cache_stats": self.classifier.get_cache_stats()
141
+ }
142
+
143
+ except Exception as e:
144
+ error_info = {
145
+ "status": "error",
146
+ "errors": [str(e)],
147
+ "model_info": {},
148
+ "classes_info": {}
149
+ }
150
+ return {
151
+ "model_choice": gr.update(visible=model_type == "OpenAI"),
152
+ "local_model_path": gr.update(visible=model_type == "Local"),
153
+ "device_choice": gr.update(visible=model_type == "Local"),
154
+ "system_info": error_info,
155
+ "system_md": self.update_system_markdown(error_info),
156
+ "build_out": f"Помилка: {str(e)}",
157
+ "cache_stats": {}
158
+ }
159
+
160
+ def update_classifier_settings(
161
+ self,
162
+ json_file: Optional[str],
163
+ model_type: str,
164
+ openai_model: str,
165
+ local_model: str,
166
+ device: str,
167
+ force_rebuild: bool
168
+ ) -> Tuple[str, Dict, Dict, str]:
169
+ """Оновлення налаштувань класифікатора"""
170
+ try:
171
+ self.classifier = self.create_classifier(
172
+ model_type=model_type,
173
+ openai_model=openai_model if model_type == "OpenAI" else None,
174
+ local_model=local_model if model_type == "Local" else None,
175
+ device=device if model_type == "Local" else None
176
+ )
177
+
178
+ if json_file is not None:
179
+ with open(json_file.name, 'r', encoding='utf-8') as f:
180
+ new_classes = json.load(f)
181
+ self.classifier.load_classes(new_classes)
182
+ else:
183
+ self.classifier.restore_base_state()
184
+
185
+ result = self.classifier.initialize_signatures(
186
+ force_rebuild=force_rebuild,
187
+ signatures_file=config.DEFAULT_SIGNATURES_FILE if not force_rebuild else None
188
+ )
189
+
190
+ self.classifier.save_model_info(config.MODEL_INFO_FILE)
191
+ with open(config.MODEL_INFO_FILE, "r") as f:
192
+ model_info = json.load(f)
193
+
194
+ new_system_info = {
195
+ "status": "success",
196
+ "model_info": model_info,
197
+ "classes_info": {
198
+ "total_classes": len(self.classifier.classes_json),
199
+ "classes_list": list(self.classifier.classes_json.keys()),
200
+ "hints_per_class": {
201
+ cls: len(hints)
202
+ for cls, hints in self.classifier.classes_json.items()
203
+ }
204
+ },
205
+ "errors": []
206
+ }
207
+
208
+ return (
209
+ result,
210
+ self.classifier.get_cache_stats(),
211
+ new_system_info,
212
+ self.update_system_markdown(new_system_info)
213
+ )
214
+ except Exception as e:
215
+ error_info = {
216
+ "status": "error",
217
+ "errors": [str(e)],
218
+ "model_info": {},
219
+ "classes_info": {}
220
+ }
221
+ return (
222
+ f"Помилка: {str(e)}",
223
+ self.classifier.get_cache_stats(),
224
+ error_info,
225
+ self.update_system_markdown(error_info)
226
+ )
227
+
228
+ def process_single_text(self, text: str, threshold: float) -> Dict:
229
+ """Обробка одного тексту"""
230
+ try:
231
+ if self.classifier is None:
232
+ raise ValueError("Класифікатор не ініціалізовано")
233
+ return self.classifier.process_single_text(text, threshold)
234
+ except Exception as e:
235
+ return {"error": str(e)}
236
+
237
+ def load_data(self, csv_path: str, emb_path: str) -> str:
238
+ """Завантаження даних для пакетної обробки"""
239
+ try:
240
+ if self.classifier is None:
241
+ raise ValueError("Класифікатор не ініціалізовано")
242
+ return self.classifier.load_data(csv_path, emb_path)
243
+ except Exception as e:
244
+ return f"Помилка: {str(e)}"
245
+
246
+ def classify_batch(self, filter_str: str, threshold: float):
247
+ """Пакетна класифікація"""
248
+ try:
249
+ if self.classifier is None:
250
+ raise ValueError("Класифікатор не ініціалізовано")
251
+ return self.classifier.classify_rows(filter_str, threshold)
252
+ except Exception as e:
253
+ return None
254
+
255
+ def save_results(self) -> str:
256
+ """Збереження результатів"""
257
+ try:
258
+ if self.classifier is None:
259
+ raise ValueError("Класифікатор не ініціалізовано")
260
+ return self.classifier.save_results()
261
+ except Exception as e:
262
+ return f"Помилка: {str(e)}"
263
+
264
+ @staticmethod
265
+ def update_system_markdown(info: Dict) -> str:
266
+ """Оновлення Markdown з системною інформацією"""
267
+ if info["status"] == "success":
268
+ return f"""
269
+ ### Поточна конфігурація:
270
+ - Модель: {info['model_info'].get('using_local', 'OpenAI')}
271
+ - Кількість класів: {info['classes_info']['total_classes']}
272
+ - Класи: {', '.join(info['classes_info']['classes_list'])}
273
+ """
274
+ else:
275
+ return f"""
276
+ ### Помилки ініціалізації:
277
+ {chr(10).join('- ' + err for err in info['errors'])}
278
+ """
model_info.json CHANGED
@@ -1,13 +1,13 @@
1
  {
2
  "using_local": true,
3
- "classes_count": 15,
4
  "signatures_count": 15,
5
  "cache_stats": {
6
- "total_entries": 3647,
7
- "cache_size_mb": 31.64,
8
- "hits": 545,
9
  "misses": 0,
10
- "hit_rate_percent": 100.0
11
  },
12
  "local_model": {
13
  "model_name": "cambridgeltl/SapBERT-from-PubMedBERT-fulltext",
 
1
  {
2
  "using_local": true,
3
+ "classes_count": 131,
4
  "signatures_count": 15,
5
  "cache_stats": {
6
+ "total_entries": 6765,
7
+ "cache_size_mb": 44.05,
8
+ "hits": 0,
9
  "misses": 0,
10
+ "hit_rate_percent": 0
11
  },
12
  "local_model": {
13
  "model_name": "cambridgeltl/SapBERT-from-PubMedBERT-fulltext",