Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -16,13 +16,13 @@ from reportlab.lib import colors
|
|
| 16 |
class KDChecker:
|
| 17 |
def __init__(self):
|
| 18 |
self.excel_db = pd.DataFrame()
|
| 19 |
-
self.cabinet_list = []
|
| 20 |
self.known_docs = ["Э3", "В4", "ПЭ3", "ВО", "ТЭ5", "СБ", "С5", "ОЛ", "Э1", "Э4", "Э7", "Д3", "Э6"]
|
| 21 |
|
| 22 |
def load_excel_db(self, excel_path):
|
| 23 |
print(f"--- Загрузка Excel: {excel_path} ---")
|
| 24 |
if excel_path is None:
|
| 25 |
-
|
|
|
|
| 26 |
|
| 27 |
all_data = []
|
| 28 |
sheets_log = []
|
|
@@ -52,6 +52,9 @@ class KDChecker:
|
|
| 52 |
df = pd.read_excel(excel_path, sheet_name=sheet_name, header=header_row_index)
|
| 53 |
df_subset = df.iloc[:, [cab_col_idx, rem_col_idx]]
|
| 54 |
df_subset.columns = ["Cabinet", "Remark"]
|
|
|
|
|
|
|
|
|
|
| 55 |
df_subset["Cabinet"] = df_subset["Cabinet"].ffill()
|
| 56 |
df_subset = df_subset.dropna(subset=["Remark"]).astype(str)
|
| 57 |
df_subset["Cabinet_Clean"] = df_subset["Cabinet"].apply(
|
|
@@ -61,25 +64,37 @@ class KDChecker:
|
|
| 61 |
sheets_log.append(f"'{sheet_name}': {len(df_subset)}")
|
| 62 |
|
| 63 |
if not all_data:
|
| 64 |
-
return "❌ Ошибка: Не найдены заголовки 'Шкаф' и 'Примечание'.", gr.update(choices=[],
|
| 65 |
|
| 66 |
self.excel_db = pd.concat(all_data, ignore_index=True)
|
| 67 |
-
|
|
|
|
|
|
|
| 68 |
|
| 69 |
print(f"Excel загружен. Всего строк: {len(self.excel_db)}")
|
| 70 |
-
msg = f"✅ База загружена!\nЗаписей: {len(self.excel_db)}\n
|
| 71 |
-
|
|
|
|
|
|
|
| 72 |
|
| 73 |
except Exception as e:
|
| 74 |
print(f"Ошибка Excel: {e}")
|
| 75 |
-
return f"❌ Ошибка: {e}", gr.update(choices=[],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
|
| 77 |
def extract_text(self, pdf_path):
|
| 78 |
text = ""
|
| 79 |
try:
|
| 80 |
with pdfplumber.open(pdf_path) as pdf:
|
| 81 |
-
# Читаем
|
| 82 |
-
# Если нужно читать весь файл - уберите [:2]
|
| 83 |
for page in pdf.pages[:5]:
|
| 84 |
text += (page.extract_text() or "") + "\n"
|
| 85 |
except Exception as e:
|
|
@@ -164,35 +179,27 @@ class KDChecker:
|
|
| 164 |
found_by_method = "manual"
|
| 165 |
is_manual = True
|
| 166 |
else:
|
| 167 |
-
# ===
|
| 168 |
db_clean_keys = set(self.excel_db["Cabinet_Clean"].tolist())
|
| 169 |
|
| 170 |
for file_path in progress.tqdm(files, desc="Поиск номера шкафа"):
|
| 171 |
text = self.extract_text(file_path)
|
| 172 |
pdf_numbers = self.find_all_decimal_numbers(text)
|
| 173 |
|
| 174 |
-
# Проверяем, есть ли найденные номера в базе
|
| 175 |
for cand in pdf_numbers:
|
| 176 |
if cand in db_clean_keys:
|
| 177 |
detected_cabinet = cand
|
| 178 |
found_by_method = "number"
|
| 179 |
-
break
|
| 180 |
|
| 181 |
if found_by_method == "number":
|
| 182 |
print(f"✅ Шкаф найден в файле: {os.path.basename(file_path)}")
|
| 183 |
-
break
|
| 184 |
-
|
| 185 |
-
# Если по номерам не нашли, пробуем по имени (тоже не читаем все подряд)
|
| 186 |
-
if detected_cabinet == "Не определен":
|
| 187 |
-
# Здесь сложнее, так как имя может быть в любом файле.
|
| 188 |
-
# Но если мы уже прочитали часть файлов и не нашли номер,
|
| 189 |
-
# скорее всего это ручной ввод.
|
| 190 |
-
pass
|
| 191 |
|
| 192 |
print(f"Определен шкаф: {detected_cabinet}")
|
| 193 |
|
| 194 |
if detected_cabinet == "Не определен":
|
| 195 |
-
return f"⚠️ Шкаф не опознан автоматически.\nВыберите
|
| 196 |
|
| 197 |
is_clean_search = (found_by_method == "number")
|
| 198 |
remarks = self.get_remarks(detected_cabinet, is_clean_key=is_clean_search)
|
|
@@ -200,7 +207,6 @@ class KDChecker:
|
|
| 200 |
if not remarks:
|
| 201 |
return f"⚠️ Для шкафа '{detected_cabinet}' нет замечаний в базе.", None
|
| 202 |
|
| 203 |
-
# Формируем список задач (здесь уже не читаем PDF, только имена файлов)
|
| 204 |
processed_count = 0
|
| 205 |
for file_path in files:
|
| 206 |
fname = os.path.basename(file_path)
|
|
@@ -329,14 +335,28 @@ def create_app():
|
|
| 329 |
gr.Markdown("## ✅ Генератор чек-листов КД")
|
| 330 |
|
| 331 |
with gr.Row():
|
|
|
|
| 332 |
with gr.Column(scale=1):
|
| 333 |
gr.Markdown("### 1. База знаний")
|
| 334 |
db_in = gr.File(label="Excel (.xlsx)", type="filepath", elem_classes="compact_file")
|
|
|
|
|
|
|
| 335 |
with gr.Group():
|
| 336 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 337 |
db_out = gr.Textbox(label="Статус базы", lines=2, max_lines=3, interactive=False)
|
| 338 |
-
db_in.upload(checker.load_excel_db, inputs=[db_in], outputs=[db_out, manual_cab])
|
| 339 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 340 |
with gr.Column(scale=1):
|
| 341 |
gr.Markdown("### 2. Документация")
|
| 342 |
files_in = gr.File(label="Чертежи (PDF)", file_count="multiple", type="filepath", elem_classes="compact_file")
|
|
@@ -350,7 +370,7 @@ def create_app():
|
|
| 350 |
with gr.Column(scale=1):
|
| 351 |
res_pdf = gr.File(label="Скачать готовый PDF")
|
| 352 |
|
| 353 |
-
btn.click(checker.check_files, inputs=[files_in,
|
| 354 |
|
| 355 |
return app
|
| 356 |
|
|
|
|
| 16 |
class KDChecker:
|
| 17 |
def __init__(self):
|
| 18 |
self.excel_db = pd.DataFrame()
|
|
|
|
| 19 |
self.known_docs = ["Э3", "В4", "ПЭ3", "ВО", "ТЭ5", "СБ", "С5", "ОЛ", "Э1", "Э4", "Э7", "Д3", "Э6"]
|
| 20 |
|
| 21 |
def load_excel_db(self, excel_path):
|
| 22 |
print(f"--- Загрузка Excel: {excel_path} ---")
|
| 23 |
if excel_path is None:
|
| 24 |
+
# Возвращаем: Сообщение, Пустой список авторов, Пустой список шкафов
|
| 25 |
+
return "Файл не выбран", gr.update(choices=[], value=None), gr.update(choices=[], value=None)
|
| 26 |
|
| 27 |
all_data = []
|
| 28 |
sheets_log = []
|
|
|
|
| 52 |
df = pd.read_excel(excel_path, sheet_name=sheet_name, header=header_row_index)
|
| 53 |
df_subset = df.iloc[:, [cab_col_idx, rem_col_idx]]
|
| 54 |
df_subset.columns = ["Cabinet", "Remark"]
|
| 55 |
+
# Добавляем колонку с именем листа (Автором)
|
| 56 |
+
df_subset["Author"] = sheet_name
|
| 57 |
+
|
| 58 |
df_subset["Cabinet"] = df_subset["Cabinet"].ffill()
|
| 59 |
df_subset = df_subset.dropna(subset=["Remark"]).astype(str)
|
| 60 |
df_subset["Cabinet_Clean"] = df_subset["Cabinet"].apply(
|
|
|
|
| 64 |
sheets_log.append(f"'{sheet_name}': {len(df_subset)}")
|
| 65 |
|
| 66 |
if not all_data:
|
| 67 |
+
return "❌ Ошибка: Не найдены заголовки 'Шкаф' и 'Примечание'.", gr.update(choices=[]), gr.update(choices=[])
|
| 68 |
|
| 69 |
self.excel_db = pd.concat(all_data, ignore_index=True)
|
| 70 |
+
|
| 71 |
+
# Получаем список уникальных авторов (листов)
|
| 72 |
+
authors_list = sorted(self.excel_db["Author"].unique().tolist())
|
| 73 |
|
| 74 |
print(f"Excel загружен. Всего строк: {len(self.excel_db)}")
|
| 75 |
+
msg = f"✅ База загружена!\nЗаписей: {len(self.excel_db)}\nАвторы: {', '.join(sheets_log)}"
|
| 76 |
+
|
| 77 |
+
# Возвращаем: Статус, Список авторов, Очищаем список шкафов
|
| 78 |
+
return msg, gr.update(choices=authors_list, value=None, interactive=True), gr.update(choices=[], value=None)
|
| 79 |
|
| 80 |
except Exception as e:
|
| 81 |
print(f"Ошибка Excel: {e}")
|
| 82 |
+
return f"❌ Ошибка: {e}", gr.update(choices=[]), gr.update(choices=[])
|
| 83 |
+
|
| 84 |
+
def get_cabinets_by_author(self, author_name):
|
| 85 |
+
"""Фильтрует шкафы по выбранному автору"""
|
| 86 |
+
if self.excel_db.empty or not author_name:
|
| 87 |
+
return gr.update(choices=[], value=None)
|
| 88 |
+
|
| 89 |
+
# Фильтруем базу по автору
|
| 90 |
+
filtered_cabs = self.excel_db[self.excel_db["Author"] == author_name]["Cabinet"].unique().tolist()
|
| 91 |
+
return gr.update(choices=sorted(filtered_cabs), value=None, interactive=True)
|
| 92 |
|
| 93 |
def extract_text(self, pdf_path):
|
| 94 |
text = ""
|
| 95 |
try:
|
| 96 |
with pdfplumber.open(pdf_path) as pdf:
|
| 97 |
+
# Читаем первые 5 страниц для скорости поиска номера
|
|
|
|
| 98 |
for page in pdf.pages[:5]:
|
| 99 |
text += (page.extract_text() or "") + "\n"
|
| 100 |
except Exception as e:
|
|
|
|
| 179 |
found_by_method = "manual"
|
| 180 |
is_manual = True
|
| 181 |
else:
|
| 182 |
+
# === ОПТИМИЗАЦИЯ ===
|
| 183 |
db_clean_keys = set(self.excel_db["Cabinet_Clean"].tolist())
|
| 184 |
|
| 185 |
for file_path in progress.tqdm(files, desc="Поиск номера шкафа"):
|
| 186 |
text = self.extract_text(file_path)
|
| 187 |
pdf_numbers = self.find_all_decimal_numbers(text)
|
| 188 |
|
|
|
|
| 189 |
for cand in pdf_numbers:
|
| 190 |
if cand in db_clean_keys:
|
| 191 |
detected_cabinet = cand
|
| 192 |
found_by_method = "number"
|
| 193 |
+
break
|
| 194 |
|
| 195 |
if found_by_method == "number":
|
| 196 |
print(f"✅ Шкаф найден в файле: {os.path.basename(file_path)}")
|
| 197 |
+
break
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 198 |
|
| 199 |
print(f"Определен шкаф: {detected_cabinet}")
|
| 200 |
|
| 201 |
if detected_cabinet == "Не определен":
|
| 202 |
+
return f"⚠️ Шкаф не опознан автоматически.\nВыберите Автора и Шкаф вручную.", None
|
| 203 |
|
| 204 |
is_clean_search = (found_by_method == "number")
|
| 205 |
remarks = self.get_remarks(detected_cabinet, is_clean_key=is_clean_search)
|
|
|
|
| 207 |
if not remarks:
|
| 208 |
return f"⚠️ Для шкафа '{detected_cabinet}' нет замечаний в базе.", None
|
| 209 |
|
|
|
|
| 210 |
processed_count = 0
|
| 211 |
for file_path in files:
|
| 212 |
fname = os.path.basename(file_path)
|
|
|
|
| 335 |
gr.Markdown("## ✅ Генератор чек-листов КД")
|
| 336 |
|
| 337 |
with gr.Row():
|
| 338 |
+
# --- ЛЕВАЯ КОЛОНКА: БАЗА ---
|
| 339 |
with gr.Column(scale=1):
|
| 340 |
gr.Markdown("### 1. База знаний")
|
| 341 |
db_in = gr.File(label="Excel (.xlsx)", type="filepath", elem_classes="compact_file")
|
| 342 |
+
|
| 343 |
+
# Группа ручного выбора
|
| 344 |
with gr.Group():
|
| 345 |
+
gr.Markdown("#### Ручной выбор (если автопоиск не сработал):")
|
| 346 |
+
# Сначала выбираем автора
|
| 347 |
+
author_dd = gr.Dropdown(label="1. Автор (Лист Excel)", choices=[], interactive=True)
|
| 348 |
+
# Затем шкаф (зависит от автора)
|
| 349 |
+
cabinet_dd = gr.Dropdown(label="2. Выберите шкаф", choices=[], interactive=True)
|
| 350 |
+
|
| 351 |
db_out = gr.Textbox(label="Статус базы", lines=2, max_lines=3, interactive=False)
|
|
|
|
| 352 |
|
| 353 |
+
# Событие загрузки Excel: обновляет статус и список авторов
|
| 354 |
+
db_in.upload(checker.load_excel_db, inputs=[db_in], outputs=[db_out, author_dd, cabinet_dd])
|
| 355 |
+
|
| 356 |
+
# Событие выбора автора: фильтрует список шкафов
|
| 357 |
+
author_dd.change(checker.get_cabinets_by_author, inputs=[author_dd], outputs=[cabinet_dd])
|
| 358 |
+
|
| 359 |
+
# --- ПРАВАЯ КОЛОНКА: ЧЕРТЕЖИ ---
|
| 360 |
with gr.Column(scale=1):
|
| 361 |
gr.Markdown("### 2. Документация")
|
| 362 |
files_in = gr.File(label="Чертежи (PDF)", file_count="multiple", type="filepath", elem_classes="compact_file")
|
|
|
|
| 370 |
with gr.Column(scale=1):
|
| 371 |
res_pdf = gr.File(label="Скачать готовый PDF")
|
| 372 |
|
| 373 |
+
btn.click(checker.check_files, inputs=[files_in, cabinet_dd], outputs=[res_txt, res_pdf])
|
| 374 |
|
| 375 |
return app
|
| 376 |
|