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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +271 -248
app.py CHANGED
@@ -1,370 +1,393 @@
 
 
 
 
1
  import gradio as gr
2
  import pandas as pd
3
  import pdfplumber
4
- import os
5
  import tempfile
6
  import re
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
 
15
- # ========== АВТОРИЗАЦИЯ ==========
16
- AUTH_USERNAME = "admin"
17
- AUTH_PASSWORD = "12345"
18
- # =================================
19
-
20
- print("=" * 40)
21
- print(f"App Start: {datetime.now()}")
22
- print(f"Gradio: {gr.__version__}")
23
- print("=" * 40)
24
-
25
 
26
  class KDChecker:
27
  def __init__(self):
28
  self.excel_db = pd.DataFrame()
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:
47
- return "❌ Файл не выбран", gr.update(choices=[], value=None)
48
-
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')
58
 
59
  for sheet_name, df_raw in xls.items():
60
- header_row = -1
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):
68
- if "шкаф" in val or "cabinet" in val:
69
- cab_col = idx
70
- if "примечание" in val or "remark" in val:
71
- rem_col = idx
72
- if cab_col != -1 and rem_col != -1:
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"]
82
- df_sub["Cabinet"] = df_sub["Cabinet"].ffill()
83
- df_sub = df_sub.dropna(subset=["Remark"]).astype(str)
84
- df_sub["Cabinet_Clean"] = df_sub["Cabinet"].apply(
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:
103
- text = ""
104
  with pdfplumber.open(pdf_path) as pdf:
105
  for page in pdf.pages:
106
  text += (page.extract_text() or "") + "\n"
107
- return text
108
- except:
109
- return ""
110
 
111
- def find_decimal_numbers(self, text):
112
  pattern = r"(РЛТ|ЛДАР|ВНАР|ШТМ)[\s\.]*\d{1}[\s\.]*\d{3}[\s\.]*[А-ЯA-Z]{1,4}[\s\.]*\d{3}(-[\d]+)?"
113
  matches = []
114
- for m in re.finditer(pattern, text):
115
- clean = m.group(0).replace(" ", "").replace("\n", "")
116
- if clean not in matches:
117
- matches.append(clean)
118
  return matches
119
 
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", "МОНТАЖ"]
127
- }
128
- for doc_type, patterns in doc_map.items():
129
- if any(p in fname for p in patterns):
130
- return doc_type
 
 
 
 
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(" ", "")
138
  mask = self.excel_db['Cabinet_Clean'].str.contains(re.escape(target), case=False, na=False)
139
  else:
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)
146
- text = re.sub(r'(\d+)\.([А-ЯA-Z])', r'\1. \2', text)
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+)(.*)'
154
- match = re.match(pattern, no_num, re.IGNORECASE | re.DOTALL)
155
-
156
- docs = []
157
  final_text = clean_item
158
  if match:
159
- potential = match.group(1).upper()
160
- cleaned = potential.replace("(", " ").replace(")", " ").replace(",", " ")
161
- parts = cleaned.split()
162
- valid = [p for p in parts if p in self.known_docs]
163
- if valid:
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 = ""
181
  is_manual = False
182
 
183
  if manual_cabinet and manual_cabinet.strip():
184
  detected_cabinet = manual_cabinet
185
- method = "manual"
186
  is_manual = True
187
  else:
188
- all_text = "".join(self.extract_text(fp) for fp in file_paths)
189
- numbers = self.find_decimal_numbers(all_text)
190
- db_keys = set(self.excel_db["Cabinet_Clean"].tolist())
191
-
192
- for num in numbers:
193
- if num in db_keys:
194
- detected_cabinet = num
195
- method = "number"
 
 
 
 
 
 
196
  break
197
 
198
  if detected_cabinet == "Не определен":
199
- for cab in self.excel_db["Cabinet"].unique():
200
- parts = [n.strip() for n in cab.split(',')]
201
- for part in parts:
202
- if len(part) >= 5 and part.lower() in all_text.lower():
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:
217
- fname = os.path.basename(fp)
218
  dtype = self.determine_doc_type(fname)
219
- tasks = remarks.get(dtype, []) + (remarks.get("ALL", []) if dtype != "С2" else [])
 
 
220
  if tasks:
221
  checklist[fname] = list(dict.fromkeys(tasks))
 
 
 
 
222
 
223
- title = detected_cabinet + (" (ручной)" if is_manual else "")
224
-
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
250
 
251
  if not data:
252
- c.drawString(50, y, "No remarks.")
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)
266
- c.drawString(50, y, f"File: {fname}")
267
  c.setFillColor(colors.black)
268
- y -= 18
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
 
 
 
 
 
312
 
313
  c.save()
314
  return path
315
 
316
 
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
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
364
 
365
  if __name__ == "__main__":
366
- app.launch(
367
- server_name="0.0.0.0",
368
- server_port=7860,
369
- auth=(AUTH_USERNAME, AUTH_PASSWORD)
370
- )
 
1
+ import os
2
+ # Говорим системе игнорировать прокси для локального адреса
3
+ os.environ['NO_PROXY'] = '127.0.0.1,localhost'
4
+
5
  import gradio as gr
6
  import pandas as pd
7
  import pdfplumber
 
8
  import tempfile
9
  import re
10
  from datetime import datetime
11
  from reportlab.pdfgen import canvas
12
  from reportlab.lib.pagesizes import A4
 
 
13
  from reportlab.pdfbase import pdfmetrics
14
  from reportlab.pdfbase.ttfonts import TTFont
15
+ from reportlab.lib import colors
16
 
17
+ # --- ЛОГИКА (Ваш код, без изменений) ---
 
 
 
 
 
 
 
 
 
18
 
19
  class KDChecker:
20
  def __init__(self):
21
  self.excel_db = pd.DataFrame()
22
  self.cabinet_list = []
23
  self.known_docs = ["Э3", "В4", "ПЭ3", "ВО", "ТЭ5", "СБ", "С5", "ОЛ", "Э1", "Э4", "Э7", "Д3", "Э6"]
 
 
 
 
 
 
 
 
 
 
 
 
 
24
 
25
+ def load_excel_db(self, excel_path):
26
+ print(f"--- Загрузка Excel: {excel_path} ---")
27
+ if excel_path is None:
28
+ return "Файл не выбран", gr.update(choices=[], value=None)
 
 
 
 
29
 
30
  all_data = []
31
+ sheets_log = []
32
 
33
  try:
34
+ xls = pd.read_excel(excel_path, sheet_name=None, header=None)
35
 
36
  for sheet_name, df_raw in xls.items():
37
+ # print(f"Обработка листа: {sheet_name}") # Скрыл спам в консоль
38
+ header_row_index = -1
39
+ cab_col_idx = -1
40
+ rem_col_idx = -1
41
 
 
42
  for i in range(min(20, len(df_raw))):
43
+ row_values = [str(x).lower().strip() for x in df_raw.iloc[i].values]
44
+ c_idx = -1
45
+ r_idx = -1
46
+ for idx, val in enumerate(row_values):
47
+ if "шкаф" in val or "cabinet" in val: c_idx = idx
48
+ if "примечание" in val or "remark" in val: r_idx = idx
49
+ if c_idx != -1 and r_idx != -1:
50
+ header_row_index = i
51
+ cab_col_idx = c_idx
52
+ rem_col_idx = r_idx
53
  break
54
 
55
+ if header_row_index != -1:
56
+ df = pd.read_excel(excel_path, sheet_name=sheet_name, header=header_row_index)
57
+ df_subset = df.iloc[:, [cab_col_idx, rem_col_idx]]
58
+ df_subset.columns = ["Cabinet", "Remark"]
59
+ df_subset["Cabinet"] = df_subset["Cabinet"].ffill()
60
+ df_subset = df_subset.dropna(subset=["Remark"]).astype(str)
61
+ df_subset["Cabinet_Clean"] = df_subset["Cabinet"].apply(
62
+ lambda x: x.strip().replace(" ", "").replace("\n", "").replace("\r", "")
63
+ )
64
+ all_data.append(df_subset)
65
+ sheets_log.append(f"'{sheet_name}': {len(df_subset)}")
 
66
 
67
  if not all_data:
68
+ return "❌ Ошибка: Не найдены заголовки 'Шкаф' и 'Примечание'.", gr.update(choices=[], value=None)
69
 
70
  self.excel_db = pd.concat(all_data, ignore_index=True)
71
  self.cabinet_list = sorted(self.excel_db["Cabinet"].unique().tolist())
72
+
73
+ print(f"Excel загружен. Всего строк: {len(self.excel_db)}")
74
+ msg = f"✅ База загружена!\nЗаписей: {len(self.excel_db)}\nЛисты: {', '.join(sheets_log)}"
75
+ return msg, gr.update(choices=self.cabinet_list, value=None, interactive=True)
76
 
77
  except Exception as e:
78
+ print(f"Ошибка Excel: {e}")
79
+ return f"❌ Ошибка: {e}", gr.update(choices=[], value=None)
80
 
81
  def extract_text(self, pdf_path):
82
+ text = ""
83
  try:
 
84
  with pdfplumber.open(pdf_path) as pdf:
85
  for page in pdf.pages:
86
  text += (page.extract_text() or "") + "\n"
87
+ except Exception as e:
88
+ print(f"Ошибка чтения PDF {pdf_path}: {e}")
89
+ return text
90
 
91
+ def find_all_decimal_numbers(self, text):
92
  pattern = r"(РЛТ|ЛДАР|ВНАР|ШТМ)[\s\.]*\d{1}[\s\.]*\d{3}[\s\.]*[А-ЯA-Z]{1,4}[\s\.]*\d{3}(-[\d]+)?"
93
  matches = []
94
+ for match in re.finditer(pattern, text):
95
+ clean_num = match.group(0).replace(" ", "").replace("\n", "")
96
+ if clean_num not in matches:
97
+ matches.append(clean_num)
98
  return matches
99
 
100
  def determine_doc_type(self, filename):
101
  fname = filename.upper()
102
+ if "С2" in fname: return "С2"
103
+ if "ПЭ3" in fname or "ПЕРЕЧЕНЬ" in fname: return "ПЭ3"
104
+ if3" in fname or "СХЕМА ЭЛЕКТРИЧЕСКАЯ" in fname: return3"
105
+ if "Э4" in fname: return "Э4"
106
+ if "В4" in fname or "СПЕЦИФИКАЦИЯ" in fname: return "В4"
107
+ if "ВО" in fname or "Э7" in fname or "ГАБАРИТ" in fname: return "ВО"
108
+ if "ТЭ5" in fname or "ТАБЛИЦА" in fname: return "ТЭ5"
109
+ if "СБ" in fname: return "СБ"
110
+ if "С5" in fname: return "С5"
111
+ if "ОЛ" in fname: return "ОЛ"
112
+ if "Э1" in fname: return "Э1"
113
+ if "Э6" in fname or "ЗАЗЕМЛЕНИЯ" in fname: return "Э6"
114
+ if "Д3" in fname or "МОНТАЖ" in fname: return "Д3"
115
  return "UNKNOWN"
116
 
117
+ def get_remarks(self, cabinet_key, is_clean_key=True):
118
  if self.excel_db.empty: return {}
119
+ if is_clean_key:
 
120
  target = cabinet_key.replace(" ", "")
121
  mask = self.excel_db['Cabinet_Clean'].str.contains(re.escape(target), case=False, na=False)
122
  else:
123
  mask = self.excel_db['Cabinet'] == cabinet_key
124
 
125
  rows = self.excel_db[mask]
126
+ if rows.empty: return {}
127
  parsed = {}
128
+ for remark_cell in rows['Remark']:
129
+ cell_text = str(remark_cell)
130
+ cell_text = re.sub(r'(\d+)\.([А-ЯA-Z])', r'\1. \2', cell_text)
131
+ items = re.split(r'(?:^|\n)\s*(?=\d+[\.\)])', cell_text)
 
132
  for item in items:
133
  if len(item) < 3: continue
134
  clean_item = item.strip()
135
+ clean_item_no_num = re.sub(r'^\d+[\.\)]\s*', '', clean_item)
136
+ doc_pattern = r'^(?:Документ\s+|В\s+)?([А-ЯA-Z0-9\s,\(\)\-]+?)(?:[\.\:\-]|\s+)(.*)'
137
+ match = re.match(doc_pattern, clean_item_no_num, re.IGNORECASE | re.DOTALL)
138
+ detected_docs = []
 
139
  final_text = clean_item
140
  if match:
141
+ potential_docs_str = match.group(1).upper()
142
+ cleaned_codes = potential_docs_str.replace("(", " ").replace(")", " ").replace(",", " ")
143
+ parts = cleaned_codes.split()
144
+ valid_parts = [p for p in parts if p in self.known_docs]
145
+ if valid_parts:
146
+ detected_docs = valid_parts
147
  final_text = match.group(2).strip()
148
+ if not detected_docs: detected_docs = ["ALL"]
149
+ for doc in detected_docs:
150
+ if doc not in parsed: parsed[doc] = []
151
+ parsed[doc].append(final_text)
 
152
  return parsed
153
 
154
+ def check_files(self, files, manual_cabinet, progress=gr.Progress()):
155
+ print("\n--- Начало проверки ---")
156
+ if not files: return "Файлы не загружены", None
157
+ if self.excel_db.empty: return "Сначала загрузите Excel базу!", None
 
158
 
159
+ checklist = {}
160
  detected_cabinet = "Не определен"
161
+ found_by_method = ""
162
  is_manual = False
163
 
164
  if manual_cabinet and manual_cabinet.strip():
165
  detected_cabinet = manual_cabinet
166
+ found_by_method = "manual"
167
  is_manual = True
168
  else:
169
+ all_pdf_text = ""
170
+ for file_path in progress.tqdm(files, desc="Чтение PDF"):
171
+ # print(f"Читаю: {os.path.basename(file_path)}")
172
+ all_pdf_text += self.extract_text(file_path) + "\n"
173
+
174
+ pdf_numbers = self.find_all_decimal_numbers(all_pdf_text)
175
+ print(f"Найдено номеров: {pdf_numbers}")
176
+
177
+ db_clean_keys = set(self.excel_db["Cabinet_Clean"].tolist())
178
+
179
+ for cand in pdf_numbers:
180
+ if cand in db_clean_keys:
181
+ detected_cabinet = cand
182
+ found_by_method = "number"
183
  break
184
 
185
  if detected_cabinet == "Не определен":
186
+ unique_cabinets = self.excel_db["Cabinet"].unique()
187
+ for cab_name in unique_cabinets:
188
+ sub_names = [n.strip() for n in cab_name.split(',')]
189
+ for sub_name in sub_names:
190
+ if len(sub_name) < 5: continue
191
+ if sub_name.lower() in all_pdf_text.lower():
192
+ detected_cabinet = cab_name
193
+ found_by_method = "name"
194
  break
195
+ if found_by_method == "name": break
196
+
197
+ print(f"Определен шкаф: {detected_cabinet}")
198
 
199
  if detected_cabinet == "Не определен":
200
+ return f"⚠️ Шкаф не опознан автоматически.\nВыберите шкаф вручную.", None
201
+
202
+ is_clean_search = (found_by_method == "number")
203
+ remarks = self.get_remarks(detected_cabinet, is_clean_key=is_clean_search)
204
 
 
205
  if not remarks:
206
+ return f"⚠️ Для шкафа '{detected_cabinet}' нет замечаний в базе.", None
207
 
208
+ processed_count = 0
209
+ for file_path in files:
210
+ fname = os.path.basename(file_path)
211
  dtype = self.determine_doc_type(fname)
212
+ tasks = []
213
+ if dtype in remarks: tasks.extend(remarks[dtype])
214
+ if "ALL" in remarks and dtype != "С2": tasks.extend(remarks["ALL"])
215
  if tasks:
216
  checklist[fname] = list(dict.fromkeys(tasks))
217
+ processed_count += 1
218
+
219
+ pdf_title = detected_cabinet
220
+ if is_manual: pdf_title += " (Выбор вручную)"
221
 
222
+ print("Генерация PDF...")
 
223
  try:
224
+ pdf = self.create_pdf(pdf_title, checklist)
225
  except Exception as e:
226
+ print(f"ОШИБКА PDF: {e}")
227
+ return f"Ошибка создания PDF: {e}", None
228
 
229
  total = sum(len(v) for v in checklist.values())
230
+ method_str = "Ручной выбор" if is_manual else ("По номеру" if is_clean_search else "По имени")
231
+
232
+ return f"✅ Готово!\n📂 Шкаф: {detected_cabinet}\n🔍 Метод: {method_str}\n📄 Файлов: {processed_count}\n🚩 Замечаний: {total}", pdf
233
 
234
  def create_pdf(self, cabinet, data):
235
+ fname = f"CheckList_Result.pdf"
236
+ path = os.path.join(tempfile.gettempdir(), fname)
237
  c = canvas.Canvas(path, pagesize=A4)
238
+ form = c.acroForm
239
+ width, height = A4
240
+
241
+ # --- АВТОПОИСК ШРИФТА WINDOWS ---
242
+ font_name = 'Helvetica'
243
+ sys_font = "C:\\Windows\\Fonts\\arial.ttf"
244
+ local_font = "arial.ttf"
245
 
246
+ try:
247
+ # Ищем шрифт сначала рядом со скриптом, потом в системе
248
+ if os.path.exists(local_font):
249
+ pdfmetrics.registerFont(TTFont('Arial', local_font))
250
+ font_name = 'Arial'
251
+ elif os.path.exists(sys_font):
252
+ pdfmetrics.registerFont(TTFont('Arial', sys_font))
253
+ font_name = 'Arial'
254
+ except:
255
+ pass # Если не нашли, будет Helvetica
256
+
257
+ y = height - 50
258
+ c.setFont(font_name, 16)
259
+ c.drawString(50, y, f"ЧЕК-ЛИСТ ПРОВЕРКИ КД")
260
  y -= 25
261
+ c.setFont(font_name, 12)
262
+ disp_cab = cabinet[:60] + "..." if len(cabinet) > 60 else cabinet
263
+ c.drawString(50, y, f"Шкаф: {disp_cab}")
264
+ c.drawString(400, y, f"Дата: {datetime.now().strftime('%d.%m.%Y')}")
265
  y -= 20
266
+ c.line(50, y, width - 50, y)
267
  y -= 30
268
 
269
  if not data:
270
+ c.drawString(50, y, "Нет замечаний.")
271
  c.save()
272
  return path
273
 
274
+ cb_id = 0
275
+ for filename, tasks in data.items():
276
+ if y < 100: c.showPage(); y = height - 50; c.setFont(font_name, 12)
 
 
 
 
 
277
  c.setFillColor(colors.darkblue)
278
+ c.setFont(font_name, 11)
279
+ c.drawString(50, y, f"Файл: {filename}")
280
  c.setFillColor(colors.black)
281
+ y -= 15
282
+ c.setFont(font_name, 10)
283
 
284
  for task in tasks:
285
+ paragraphs = task.split('\n')
286
+ if y < 80: c.showPage(); y = height - 50; c.setFont(font_name, 10)
 
 
 
287
 
288
+ # Рисуем интерактивный чекбокс
289
+ form.checkbox(name=f"cb_{cb_id}", x=50, y=y - 10, size=10, buttonStyle='check', forceBorder=True, fillColor=colors.white)
290
+ cb_id += 1
 
 
 
 
 
 
 
 
 
 
291
 
292
+ text_start_y = y - 2
293
+
294
+ for paragraph in paragraphs:
295
+ max_len = 95
296
+ lines = []
297
+ words = paragraph.split(' ')
298
+ cur_line = ""
299
+ for w in words:
300
+ if len(cur_line) + len(w) + 1 <= max_len:
301
+ cur_line += w + " "
302
+ else:
303
+ lines.append(cur_line);
304
+ cur_line = w + " "
305
+ lines.append(cur_line)
306
+
307
+ for l in lines:
308
+ if text_start_y < 40: c.showPage(); text_start_y = height - 50; c.setFont(font_name, 10)
309
+ c.drawString(65, text_start_y, l.strip())
310
+ text_start_y -= 12
311
+ y = text_start_y - 8
312
+ y -= 10
313
+ c.setStrokeColor(colors.lightgrey)
314
+ c.line(50, y, width - 50, y)
315
+ c.setStrokeColor(colors.black)
316
+ y -= 20
317
 
318
  c.save()
319
  return path
320
 
321
 
322
+ # --- НОВЫЙ ИНТЕРФЕЙС С CSS ---
323
+
324
+ # CSS стили для компактности
325
+ css = """
326
+ /* Убираем лишние отступы у контейнера */
327
+ .gradio-container { max-width: 95% !important; }
328
+
329
+ /* Делаем зону загрузки компактной */
330
+ .compact_file {
331
+ height: 150px !important;
332
+ min-height: 150px !important;
333
+ max-height: 150px !important;
334
+ overflow: hidden !important;
335
+ }
336
+
337
+ /* Оранжевая кнопка */
338
+ .orange_btn {
339
+ background: #FF7F27 !important;
340
+ border: none !important;
341
+ color: white !important;
342
+ font-weight: bold;
343
+ }
344
+ .orange_btn:hover { background: #E06010 !important; }
345
 
346
+ /* Убираем футер */
 
 
 
 
347
  footer { display: none !important; }
348
  """
349
 
350
+ def create_app():
351
+ checker = KDChecker()
352
 
353
+ with gr.Blocks(title="Генератор чек-листов КД", theme=gr.themes.Soft(), css=css) as app:
354
+ gr.Markdown("## Генератор чек-листов КД")
355
+
356
+ with gr.Row():
357
+ # === КОЛОНКА 1: БАЗА ===
358
+ with gr.Column(scale=1):
359
+ gr.Markdown("### 1. База знаний")
360
+ # elem_classes применяет наш CSS класс
361
+ db_in = gr.File(label="Excel (.xlsx)", type="filepath", elem_classes="compact_file")
362
+
363
+ with gr.Group():
364
+ manual_cab = gr.Dropdown(label="Или шкаф вручную", choices=[], interactive=True)
365
+ db_out = gr.Textbox(label="Статус базы", lines=2, max_lines=3, interactive=False)
366
+
367
+ # Привязка событий
368
+ db_in.upload(checker.load_excel_db, inputs=[db_in], outputs=[db_out, manual_cab])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
369
 
370
+ # === КОЛОНКА 2: ДОКУМЕНТЫ ===
371
+ with gr.Column(scale=1):
372
+ gr.Markdown("### 2. Документация")
373
+ files_in = gr.File(label="Чертежи (PDF)", file_count="multiple", type="filepath", elem_classes="compact_file")
374
+
375
+ gr.Markdown("") # Пустой разделитель
376
+ btn = gr.Button("Сформировать чек-лист", variant="primary", elem_classes="orange_btn")
377
+
378
+ # === РЕЗУЛЬТАТ ===
379
+ gr.Markdown("### 3. Результат")
380
+ with gr.Row():
381
+ with gr.Column(scale=1):
382
+ res_txt = gr.Textbox(label="Лог проверки", lines=5)
383
+ with gr.Column(scale=1):
384
+ res_pdf = gr.File(label="Скачать готовый PDF")
385
+
386
+ # Запуск обработки
387
+ btn.click(checker.check_files, inputs=[files_in, manual_cab], outputs=[res_txt, res_pdf])
388
+
389
+ return app
390
 
391
  if __name__ == "__main__":
392
+ app = create_app()
393
+ app.launch(inbrowser=True, server_name="127.0.0.1", server_port=7861)