Ed5 commited on
Commit
865492a
·
verified ·
1 Parent(s): b46bb37

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +76 -89
app.py CHANGED
@@ -8,7 +8,7 @@ from datetime import datetime
8
  from reportlab.pdfgen import canvas
9
  from reportlab.lib.pagesizes import A4
10
  from reportlab.lib import colors
11
- # === ИМПОРТЫ ДЛЯ ШРИФТОВ ===
12
  from reportlab.pdfbase import pdfmetrics
13
  from reportlab.pdfbase.ttfonts import TTFont
14
 
@@ -29,20 +29,18 @@ class KDChecker:
29
  self.cabinet_list = []
30
  self.known_docs = ["Э3", "В4", "ПЭ3", "ВО", "ТЭ5", "СБ", "С5", "ОЛ", "Э1", "Э4", "Э7", "Д3", "Э6"]
31
 
32
- # === РЕГИСТРАЦИЯ ШРИФТА (Исправление квадратов) ===
33
- self.font_name = 'Helvetica' # Запасной вариант
34
  try:
35
- # Путь к шрифту относительно папки, где лежит скрипт
36
  font_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "arial.ttf")
37
-
38
  if os.path.exists(font_path):
39
  pdfmetrics.registerFont(TTFont('Arial', font_path))
40
  self.font_name = 'Arial'
41
  print(f"✅ Шрифт загружен: {font_path}")
42
  else:
43
- print(f"⚠️ Файл шрифта не найден: {font_path}")
44
  except Exception as e:
45
- print(f"❌ Ошибка загрузки шрифта: {e}")
46
 
47
  def load_excel_db(self, excel_file):
48
  if excel_file is None:
@@ -51,10 +49,9 @@ class KDChecker:
51
  excel_path = excel_file if isinstance(excel_file, str) else getattr(excel_file, 'name', str(excel_file))
52
 
53
  if not os.path.exists(excel_path):
54
- return f"❌ Файл не найден: {excel_path}", gr.update(choices=[], value=None)
55
 
56
  all_data = []
57
- sheets_info = []
58
 
59
  try:
60
  xls = pd.read_excel(excel_path, sheet_name=None, header=None, engine='openpyxl')
@@ -64,6 +61,7 @@ class KDChecker:
64
  cab_col = -1
65
  rem_col = -1
66
 
 
67
  for i in range(min(20, len(df_raw))):
68
  row_vals = [str(x).lower().strip() for x in df_raw.iloc[i].values]
69
  for idx, val in enumerate(row_vals):
@@ -75,8 +73,9 @@ class KDChecker:
75
  header_row = i
76
  break
77
 
78
- if header_row != -1 and cab_col < len(df_raw.columns) and rem_col < len(df_raw.columns):
79
  df = pd.read_excel(excel_path, sheet_name=sheet_name, header=header_row, engine='openpyxl')
 
80
  if cab_col < len(df.columns) and rem_col < len(df.columns):
81
  df_sub = df.iloc[:, [cab_col, rem_col]]
82
  df_sub.columns = ["Cabinet", "Remark"]
@@ -86,24 +85,18 @@ class KDChecker:
86
  lambda x: x.strip().replace(" ", "").replace("\n", "").replace("\r", "")
87
  )
88
  all_data.append(df_sub)
89
- sheets_info.append(f"'{sheet_name}': {len(df_sub)}")
90
 
91
  if not all_data:
92
- return "❌ Не найдены колонки 'Шкаф' и 'Примечание'", gr.update(choices=[], value=None)
93
 
94
  self.excel_db = pd.concat(all_data, ignore_index=True)
95
  self.cabinet_list = sorted(self.excel_db["Cabinet"].unique().tolist())
96
 
97
- msg = f"✅ База загружена!\n\n"
98
- msg += f"📊 Записей: {len(self.excel_db)}\n"
99
- msg += f"🗄️ Шкафов: {len(self.cabinet_list)}\n"
100
- msg += f"📋 Листы: {', '.join(sheets_info)}"
101
-
102
  return msg, gr.update(choices=self.cabinet_list, value=None)
103
 
104
  except Exception as e:
105
- import traceback
106
- return f"❌ Ошибка: {e}\n\n{traceback.format_exc()}", gr.update(choices=[], value=None)
107
 
108
  def extract_text(self, pdf_path):
109
  try:
@@ -127,7 +120,7 @@ class KDChecker:
127
  def determine_doc_type(self, filename):
128
  fname = filename.upper()
129
  doc_map = {
130
- "С2": ["С2"], "ПЭ3": ["ПЭ3", "ПЕРЕЧЕНЬ"], "Э3": ["Э3", "СХЕМА ЭЛЕКТРИЧЕСКАЯ"],
131
  "Э4": ["Э4"], "В4": ["В4", "СПЕЦИФИКАЦИЯ"], "ВО": ["ВО", "Э7", "ГАБАРИТ"],
132
  "ТЭ5": ["ТЭ5", "ТАБЛИЦА"], "СБ": ["СБ"], "С5": ["С5"], "ОЛ": ["ОЛ"],
133
  "Э1": ["Э1"], "Э6": ["Э6", "ЗАЗЕМЛЕНИЯ"], "Д3": ["Д3", "МОНТАЖ"]
@@ -138,8 +131,7 @@ class KDChecker:
138
  return "UNKNOWN"
139
 
140
  def get_remarks(self, cabinet_key, is_clean=True):
141
- if self.excel_db.empty:
142
- return {}
143
 
144
  if is_clean:
145
  target = cabinet_key.replace(" ", "")
@@ -148,9 +140,6 @@ class KDChecker:
148
  mask = self.excel_db['Cabinet'] == cabinet_key
149
 
150
  rows = self.excel_db[mask]
151
- if rows.empty:
152
- return {}
153
-
154
  parsed = {}
155
  for remark in rows['Remark']:
156
  text = str(remark)
@@ -158,8 +147,7 @@ class KDChecker:
158
  items = re.split(r'(?:^|\n)\s*(?=\d+[\.\)])', text)
159
 
160
  for item in items:
161
- if len(item) < 3:
162
- continue
163
  clean_item = item.strip()
164
  no_num = re.sub(r'^\d+[\.\)]\s*', '', clean_item)
165
  pattern = r'^(?:Документ\s+|В\s+)?([А-ЯA-Z0-9\s,\(\)\-]+?)(?:[\.\:\-]|\s+)(.*)'
@@ -167,7 +155,6 @@ class KDChecker:
167
 
168
  docs = []
169
  final_text = clean_item
170
-
171
  if match:
172
  potential = match.group(1).upper()
173
  cleaned = potential.replace("(", " ").replace(")", " ").replace(",", " ")
@@ -177,26 +164,17 @@ class KDChecker:
177
  docs = valid
178
  final_text = match.group(2).strip()
179
 
180
- if not docs:
181
- docs = ["ALL"]
182
-
183
  for d in docs:
184
- if d not in parsed:
185
- parsed[d] = []
186
  parsed[d].append(final_text)
187
-
188
  return parsed
189
 
190
  def check_files(self, files, manual_cabinet):
191
- if not files:
192
- return " Загрузите PDF файлы", None
193
- if self.excel_db.empty:
194
- return "❌ Сначала загрузите Excel!", None
195
 
196
- file_paths = []
197
- for f in files:
198
- fp = f if isinstance(f, str) else getattr(f, 'name', str(f))
199
- file_paths.append(fp)
200
 
201
  detected_cabinet = "Не определен"
202
  method = ""
@@ -225,15 +203,14 @@ class KDChecker:
225
  detected_cabinet = cab
226
  method = "name"
227
  break
228
- if method:
229
- break
230
 
231
  if detected_cabinet == "Не определен":
232
- return "⚠️ Шкаф не определён. Выберите вручную.", None
233
 
234
  remarks = self.get_remarks(detected_cabinet, is_clean=(method == "number"))
235
  if not remarks:
236
- return f"⚠️ Для '{detected_cabinet}' замечаний нет.", None
237
 
238
  checklist = {}
239
  for fp in file_paths:
@@ -248,34 +225,25 @@ class KDChecker:
248
  try:
249
  pdf_path = self.create_pdf(title, checklist)
250
  except Exception as e:
251
- import traceback
252
- traceback.print_exc() # Пишем в консоль для отладки
253
- return f"❌ Ошибка генерации PDF: {e}", None
254
 
255
  total = sum(len(v) for v in checklist.values())
256
- method_name = {"manual": "Ручной", "number": "По номеру", "name": "По названию"}.get(method, "?")
257
-
258
- return f"✅ Готово!\n\n📂 {detected_cabinet}\n🔍 {method_name}\n📄 Файлов: {len(file_paths)}\n🚩 Замечаний: {total}", pdf_path
259
 
260
  def create_pdf(self, cabinet, data):
261
  path = os.path.join(tempfile.gettempdir(), "CheckList.pdf")
262
  c = canvas.Canvas(path, pagesize=A4)
263
  w, h = A4
264
-
265
- # Используем наш зарегистрированный шрифт
266
- font = self.font_name
267
 
268
  y = h - 50
269
  c.setFont(font, 16)
270
  c.drawString(50, y, "CHECK-LIST")
271
  y -= 25
272
  c.setFont(font, 12)
273
-
274
- # Обрезаем очень длинные названия шкафов для заголовка
275
  cab_title = cabinet[:60] + "..." if len(cabinet) > 60 else cabinet
276
  c.drawString(50, y, f"Cabinet: {cab_title}")
277
-
278
- c.drawString(400, y, datetime.now().strftime('%d.%m.%Y'))
279
  y -= 20
280
  c.line(50, y, w - 50, y)
281
  y -= 30
@@ -285,12 +253,13 @@ class KDChecker:
285
  c.save()
286
  return path
287
 
 
 
288
  for fname, tasks in data.items():
289
  if y < 100:
290
  c.showPage()
291
  y = h - 50
292
- # Важно: на новой странице тоже нужно ставить шрифт
293
- c.setFont(font, 12)
294
 
295
  c.setFillColor(colors.darkblue)
296
  c.setFont(font, 11)
@@ -300,33 +269,43 @@ class KDChecker:
300
  c.setFont(font, 10)
301
 
302
  for task in tasks:
 
303
  if y < 60:
304
  c.showPage()
305
  y = h - 50
306
  c.setFont(font, 10)
307
 
308
- # Рисуем квадратик чекбокса
309
- c.rect(50, y - 2, 8, 8, stroke=1, fill=0)
 
 
 
 
 
 
 
 
 
 
 
310
 
311
- # Перенос строк
312
  words = task.replace('\n', ' ').split()
313
  lines, line = [], ""
314
  for word in words:
315
- # Примерная ширина строки. Arial чуть шире Helvetica, запас 80
316
- if len(line) + len(word) < 80:
317
  line += word + " "
318
  else:
319
  lines.append(line.strip())
320
  line = word + " "
321
- if line:
322
- lines.append(line.strip())
323
 
324
  for ln in lines:
325
  if y < 40:
326
  c.showPage()
327
  y = h - 50
328
  c.setFont(font, 10)
329
- c.drawString(65, y, ln)
330
  y -= 12
331
  y -= 5
332
  y -= 15
@@ -338,39 +317,47 @@ class KDChecker:
338
  # ========== ИНТЕРФЕЙС ==========
339
  checker = KDChecker()
340
 
341
- # Используем CSS для улучшения вида
342
- css = """
343
- .file-upload { height: 100px !important; min-height: 100px !important; }
 
 
 
344
  """
345
 
346
- with gr.Blocks(title="КД Checker", theme=gr.themes.Soft(), css=css) as app:
347
- gr.Markdown("#Генератор чек-листов КД")
348
 
349
  with gr.Row():
350
- # Левая колонка - База
351
- with gr.Column(scale=1):
352
  gr.Markdown("### 1. База знаний")
353
- # height=100 - исправляет гигантский размер
354
- db_input = gr.File(label="Excel (.xlsx)", file_types=[".xlsx", ".xls"], type="filepath", height=100)
355
- db_status = gr.Textbox(label="Статус базы", lines=3, interactive=False)
356
- cabinet_dd = gr.Dropdown(label="Выбрать шкаф вручную (если авто не сработал)", choices=[], interactive=True)
357
 
358
- # Правая колонка - Чертежи
 
 
 
 
 
359
  with gr.Column(scale=1):
360
- gr.Markdown("### 2. Проверка чертежей")
361
- # height=100 - исправляет гигантский размер
362
- pdf_input = gr.File(label="Загрузить PDF чертежи", file_count="multiple", file_types=[".pdf"], type="filepath", height=100)
363
- run_btn = gr.Button("🔍 Сформировать чек-лист", variant="primary", size="lg")
 
 
 
364
 
365
- # Нижняя секция - Результат
366
- gr.Markdown("### 3. Результат")
367
  with gr.Row():
368
  with gr.Column(scale=1):
369
- result_txt = gr.Textbox(label="Лог проверки", lines=5)
370
  with gr.Column(scale=1):
371
  result_pdf = gr.File(label="📥 Скачать PDF")
372
 
373
- # Логика
374
  db_input.change(checker.load_excel_db, [db_input], [db_status, cabinet_dd])
375
  run_btn.click(checker.check_files, [pdf_input, cabinet_dd], [result_txt, result_pdf])
376
 
 
8
  from reportlab.pdfgen import canvas
9
  from reportlab.lib.pagesizes import A4
10
  from reportlab.lib import colors
11
+ # Импорты для шрифтов и форм
12
  from reportlab.pdfbase import pdfmetrics
13
  from reportlab.pdfbase.ttfonts import TTFont
14
 
 
29
  self.cabinet_list = []
30
  self.known_docs = ["Э3", "В4", "ПЭ3", "ВО", "ТЭ5", "СБ", "С5", "ОЛ", "Э1", "Э4", "Э7", "Д3", "Э6"]
31
 
32
+ # === ШРИФТЫ ===
33
+ self.font_name = 'Helvetica'
34
  try:
 
35
  font_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "arial.ttf")
 
36
  if os.path.exists(font_path):
37
  pdfmetrics.registerFont(TTFont('Arial', font_path))
38
  self.font_name = 'Arial'
39
  print(f"✅ Шрифт загружен: {font_path}")
40
  else:
41
+ print(f"⚠️ Шрифт не найден (ищем {font_path})")
42
  except Exception as e:
43
+ print(f"❌ Ошибка шрифта: {e}")
44
 
45
  def load_excel_db(self, excel_file):
46
  if excel_file is None:
 
49
  excel_path = excel_file if isinstance(excel_file, str) else getattr(excel_file, 'name', str(excel_file))
50
 
51
  if not os.path.exists(excel_path):
52
+ return f"❌ Файл не найден", gr.update(choices=[], value=None)
53
 
54
  all_data = []
 
55
 
56
  try:
57
  xls = pd.read_excel(excel_path, sheet_name=None, header=None, engine='openpyxl')
 
61
  cab_col = -1
62
  rem_col = -1
63
 
64
+ # Ищем заголовки
65
  for i in range(min(20, len(df_raw))):
66
  row_vals = [str(x).lower().strip() for x in df_raw.iloc[i].values]
67
  for idx, val in enumerate(row_vals):
 
73
  header_row = i
74
  break
75
 
76
+ if header_row != -1:
77
  df = pd.read_excel(excel_path, sheet_name=sheet_name, header=header_row, engine='openpyxl')
78
+ # Проверяем индексы еще раз
79
  if cab_col < len(df.columns) and rem_col < len(df.columns):
80
  df_sub = df.iloc[:, [cab_col, rem_col]]
81
  df_sub.columns = ["Cabinet", "Remark"]
 
85
  lambda x: x.strip().replace(" ", "").replace("\n", "").replace("\r", "")
86
  )
87
  all_data.append(df_sub)
 
88
 
89
  if not all_data:
90
+ return "❌ Структура Excel не распознана", gr.update(choices=[], value=None)
91
 
92
  self.excel_db = pd.concat(all_data, ignore_index=True)
93
  self.cabinet_list = sorted(self.excel_db["Cabinet"].unique().tolist())
94
 
95
+ msg = f"✅ База загружена!\nВсего записей: {len(self.excel_db)}\nШкафов: {len(self.cabinet_list)}"
 
 
 
 
96
  return msg, gr.update(choices=self.cabinet_list, value=None)
97
 
98
  except Exception as e:
99
+ return f"❌ Ошибка Excel: {e}", gr.update(choices=[], value=None)
 
100
 
101
  def extract_text(self, pdf_path):
102
  try:
 
120
  def determine_doc_type(self, filename):
121
  fname = filename.upper()
122
  doc_map = {
123
+ "С2": ["С2"], "ПЭ3": ["ПЭ3", "ПЕРЕЧЕНЬ"], "��3": ["Э3", "СХЕМА"],
124
  "Э4": ["Э4"], "В4": ["В4", "СПЕЦИФИКАЦИЯ"], "ВО": ["ВО", "Э7", "ГАБАРИТ"],
125
  "ТЭ5": ["ТЭ5", "ТАБЛИЦА"], "СБ": ["СБ"], "С5": ["С5"], "ОЛ": ["ОЛ"],
126
  "Э1": ["Э1"], "Э6": ["Э6", "ЗАЗЕМЛЕНИЯ"], "Д3": ["Д3", "МОНТАЖ"]
 
131
  return "UNKNOWN"
132
 
133
  def get_remarks(self, cabinet_key, is_clean=True):
134
+ if self.excel_db.empty: return {}
 
135
 
136
  if is_clean:
137
  target = cabinet_key.replace(" ", "")
 
140
  mask = self.excel_db['Cabinet'] == cabinet_key
141
 
142
  rows = self.excel_db[mask]
 
 
 
143
  parsed = {}
144
  for remark in rows['Remark']:
145
  text = str(remark)
 
147
  items = re.split(r'(?:^|\n)\s*(?=\d+[\.\)])', text)
148
 
149
  for item in items:
150
+ if len(item) < 3: continue
 
151
  clean_item = item.strip()
152
  no_num = re.sub(r'^\d+[\.\)]\s*', '', clean_item)
153
  pattern = r'^(?:Документ\s+|В\s+)?([А-ЯA-Z0-9\s,\(\)\-]+?)(?:[\.\:\-]|\s+)(.*)'
 
155
 
156
  docs = []
157
  final_text = clean_item
 
158
  if match:
159
  potential = match.group(1).upper()
160
  cleaned = potential.replace("(", " ").replace(")", " ").replace(",", " ")
 
164
  docs = valid
165
  final_text = match.group(2).strip()
166
 
167
+ if not docs: docs = ["ALL"]
 
 
168
  for d in docs:
169
+ if d not in parsed: parsed[d] = []
 
170
  parsed[d].append(final_text)
 
171
  return parsed
172
 
173
  def check_files(self, files, manual_cabinet):
174
+ if not files: return "⚠️ Загрузите PDF", None
175
+ if self.excel_db.empty: return "⚠️ Загрузите базу Excel", None
 
 
176
 
177
+ file_paths = [f if isinstance(f, str) else getattr(f, 'name', str(f)) for f in files]
 
 
 
178
 
179
  detected_cabinet = "Не определен"
180
  method = ""
 
203
  detected_cabinet = cab
204
  method = "name"
205
  break
206
+ if method: break
 
207
 
208
  if detected_cabinet == "Не определен":
209
+ return "⚠️ Шкаф не определён. Выберите вручную в списке слева.", None
210
 
211
  remarks = self.get_remarks(detected_cabinet, is_clean=(method == "number"))
212
  if not remarks:
213
+ return f" Для '{detected_cabinet}' замечаний не найдено.", None
214
 
215
  checklist = {}
216
  for fp in file_paths:
 
225
  try:
226
  pdf_path = self.create_pdf(title, checklist)
227
  except Exception as e:
228
+ return f"❌ Ошибка PDF: {e}", None
 
 
229
 
230
  total = sum(len(v) for v in checklist.values())
231
+ return f" Готово!\nШкаф: {detected_cabinet}\nМетод: {method}\nЗамечаний: {total}", pdf_path
 
 
232
 
233
  def create_pdf(self, cabinet, data):
234
  path = os.path.join(tempfile.gettempdir(), "CheckList.pdf")
235
  c = canvas.Canvas(path, pagesize=A4)
236
  w, h = A4
237
+ font = self.font_name
 
 
238
 
239
  y = h - 50
240
  c.setFont(font, 16)
241
  c.drawString(50, y, "CHECK-LIST")
242
  y -= 25
243
  c.setFont(font, 12)
 
 
244
  cab_title = cabinet[:60] + "..." if len(cabinet) > 60 else cabinet
245
  c.drawString(50, y, f"Cabinet: {cab_title}")
246
+ c.drawString(450, y, datetime.now().strftime('%d.%m.%Y'))
 
247
  y -= 20
248
  c.line(50, y, w - 50, y)
249
  y -= 30
 
253
  c.save()
254
  return path
255
 
256
+ cb_counter = 0 # Счетчик для уникальных имен чекбоксов
257
+
258
  for fname, tasks in data.items():
259
  if y < 100:
260
  c.showPage()
261
  y = h - 50
262
+ c.setFont(font, 12)
 
263
 
264
  c.setFillColor(colors.darkblue)
265
  c.setFont(font, 11)
 
269
  c.setFont(font, 10)
270
 
271
  for task in tasks:
272
+ cb_counter += 1
273
  if y < 60:
274
  c.showPage()
275
  y = h - 50
276
  c.setFont(font, 10)
277
 
278
+ # === ИСПРАВЛЕНИЕ ЧЕКБОКСОВ ===
279
+ # Вместо rect рисуем настоящую форму
280
+ c.acroForm.checkbox(
281
+ name=f'CB_{cb_counter}',
282
+ x=50,
283
+ y=y-4,
284
+ buttonStyle='check',
285
+ borderColor=colors.black,
286
+ fillColor=colors.white,
287
+ textColor=colors.black,
288
+ forceBorder=True,
289
+ size=10
290
+ )
291
 
292
+ # Текст задачи
293
  words = task.replace('\n', ' ').split()
294
  lines, line = [], ""
295
  for word in words:
296
+ if len(line) + len(word) < 80:
 
297
  line += word + " "
298
  else:
299
  lines.append(line.strip())
300
  line = word + " "
301
+ if line: lines.append(line.strip())
 
302
 
303
  for ln in lines:
304
  if y < 40:
305
  c.showPage()
306
  y = h - 50
307
  c.setFont(font, 10)
308
+ c.drawString(70, y, ln) # Отступ 70, чтобы не налезть на чекбокс
309
  y -= 12
310
  y -= 5
311
  y -= 15
 
317
  # ========== ИНТЕРФЕЙС ==========
318
  checker = KDChecker()
319
 
320
+ # CSS для компактности и оранжевой кнопки
321
+ custom_css = """
322
+ .compact_file { height: 120px !important; min-height: 120px !important; }
323
+ .orange_btn { background: #FF7F27 !important; border: none !important; color: white !important; font-size: 16px !important;}
324
+ .gradio-container { max-width: 1400px !important; }
325
+ footer { display: none !important; }
326
  """
327
 
328
+ with gr.Blocks(title="КД Checker", theme=gr.themes.Soft(), css=custom_css) as app:
329
+ gr.Markdown("##Локальный генератор чек-листов КД")
330
 
331
  with gr.Row():
332
+ # --- ЛЕВАЯ КОЛОНКА (БАЗА) ---
333
+ with gr.Column(scale=1):
334
  gr.Markdown("### 1. База знаний")
335
+ db_input = gr.File(label="Загрузить Excel (.xlsx)", file_types=[".xlsx", ".xls"],
336
+ type="filepath", elem_classes="compact_file")
 
 
337
 
338
+ # Поля управления базой под файлом
339
+ with gr.Group():
340
+ cabinet_dd = gr.Dropdown(label="Или выберите шкаф-аналог вручную", choices=[], interactive=True)
341
+ db_status = gr.Textbox(label="Статус базы", lines=2, interactive=False, placeholder="Ожидание загрузки...")
342
+
343
+ # --- ПРАВАЯ КОЛОНКА (ЧЕРТЕЖИ) ---
344
  with gr.Column(scale=1):
345
+ gr.Markdown("### 2. Документация")
346
+ pdf_input = gr.File(label="Чертежи (PDF)", file_count="multiple", file_types=[".pdf"],
347
+ type="filepath", elem_classes="compact_file")
348
+
349
+ # Кнопка под файлами
350
+ gr.Markdown("") # Отступ
351
+ run_btn = gr.Button("Сформировать чек-лист", variant="primary", elem_classes="orange_btn")
352
 
353
+ # --- НИЖНЯЯ СЕКЦИЯ (РЕЗУЛЬТАТ) ---
 
354
  with gr.Row():
355
  with gr.Column(scale=1):
356
+ result_txt = gr.Textbox(label="Результат", lines=6)
357
  with gr.Column(scale=1):
358
  result_pdf = gr.File(label="📥 Скачать PDF")
359
 
360
+ # ЛОГИКА
361
  db_input.change(checker.load_excel_db, [db_input], [db_status, cabinet_dd])
362
  run_btn.click(checker.check_files, [pdf_input, cabinet_dd], [result_txt, result_pdf])
363