Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -36,7 +36,7 @@ class KDChecker:
|
|
| 36 |
import openpyxl
|
| 37 |
log.append("Библиотека openpyxl найдена.")
|
| 38 |
except ImportError:
|
| 39 |
-
return "ОШИБКА: Библиотека openpyxl не
|
| 40 |
|
| 41 |
all_data = []
|
| 42 |
sheets_log = []
|
|
@@ -56,8 +56,10 @@ class KDChecker:
|
|
| 56 |
c_idx = -1
|
| 57 |
r_idx = -1
|
| 58 |
for idx, val in enumerate(row_values):
|
| 59 |
-
if "шкаф" in val or "cabinet" in val:
|
| 60 |
-
|
|
|
|
|
|
|
| 61 |
if c_idx != -1 and r_idx != -1:
|
| 62 |
header_row_index = i
|
| 63 |
cab_col_idx = c_idx
|
|
@@ -69,14 +71,11 @@ class KDChecker:
|
|
| 69 |
if cab_col_idx < len(df.columns) and rem_col_idx < len(df.columns):
|
| 70 |
df_subset = df.iloc[:, [cab_col_idx, rem_col_idx]]
|
| 71 |
df_subset.columns = ["Cabinet", "Remark"]
|
| 72 |
-
|
| 73 |
df_subset["Cabinet"] = df_subset["Cabinet"].ffill()
|
| 74 |
df_subset = df_subset.dropna(subset=["Remark"]).astype(str)
|
| 75 |
-
|
| 76 |
df_subset["Cabinet_Clean"] = df_subset["Cabinet"].apply(
|
| 77 |
lambda x: x.strip().replace(" ", "").replace("\n", "").replace("\r", "")
|
| 78 |
)
|
| 79 |
-
|
| 80 |
all_data.append(df_subset)
|
| 81 |
sheets_log.append(f"Лист '{sheet_name}': {len(df_subset)} стр.")
|
| 82 |
else:
|
|
@@ -94,7 +93,6 @@ class KDChecker:
|
|
| 94 |
log.append("Успешно объединили данные.")
|
| 95 |
msg = f"✅ База знаний загружена успешно!\nВсего записей: {len(self.excel_db)}\nОбработаны листы: {', '.join(sheets_log)}"
|
| 96 |
|
| 97 |
-
# ИЗМЕНЕНО: gr.update() вместо gr.Dropdown()
|
| 98 |
return msg, gr.update(choices=self.cabinet_list, value=None)
|
| 99 |
|
| 100 |
except Exception as e:
|
|
@@ -140,7 +138,8 @@ class KDChecker:
|
|
| 140 |
return "UNKNOWN"
|
| 141 |
|
| 142 |
def get_remarks(self, cabinet_key, is_clean_key=True):
|
| 143 |
-
if self.excel_db.empty:
|
|
|
|
| 144 |
if is_clean_key:
|
| 145 |
target = cabinet_key.replace(" ", "")
|
| 146 |
mask = self.excel_db['Cabinet_Clean'].str.contains(re.escape(target), case=False, na=False)
|
|
@@ -148,14 +147,17 @@ class KDChecker:
|
|
| 148 |
mask = self.excel_db['Cabinet'] == cabinet_key
|
| 149 |
|
| 150 |
rows = self.excel_db[mask]
|
| 151 |
-
if rows.empty:
|
|
|
|
|
|
|
| 152 |
parsed = {}
|
| 153 |
for remark_cell in rows['Remark']:
|
| 154 |
cell_text = str(remark_cell)
|
| 155 |
cell_text = re.sub(r'(\d+)\.([А-ЯA-Z])', r'\1. \2', cell_text)
|
| 156 |
items = re.split(r'(?:^|\n)\s*(?=\d+[\.\)])', cell_text)
|
| 157 |
for item in items:
|
| 158 |
-
if len(item) < 3:
|
|
|
|
| 159 |
clean_item = item.strip()
|
| 160 |
clean_item_no_num = re.sub(r'^\d+[\.\)]\s*', '', clean_item)
|
| 161 |
doc_pattern = r'^(?:Документ\s+|В\s+)?([А-ЯA-Z0-9\s,\(\)\-]+?)(?:[\.\:\-]|\s+)(.*)'
|
|
@@ -170,15 +172,19 @@ class KDChecker:
|
|
| 170 |
if valid_parts:
|
| 171 |
detected_docs = valid_parts
|
| 172 |
final_text = match.group(2).strip()
|
| 173 |
-
if not detected_docs:
|
|
|
|
| 174 |
for doc in detected_docs:
|
| 175 |
-
if doc not in parsed:
|
|
|
|
| 176 |
parsed[doc].append(final_text)
|
| 177 |
return parsed
|
| 178 |
|
| 179 |
def check_files(self, files, manual_cabinet):
|
| 180 |
-
if not files:
|
| 181 |
-
|
|
|
|
|
|
|
| 182 |
|
| 183 |
checklist = {}
|
| 184 |
detected_cabinet = "Не определен"
|
|
@@ -208,15 +214,17 @@ class KDChecker:
|
|
| 208 |
for cab_name in unique_cabinets:
|
| 209 |
sub_names = [n.strip() for n in cab_name.split(',')]
|
| 210 |
for sub_name in sub_names:
|
| 211 |
-
if len(sub_name) < 5:
|
|
|
|
| 212 |
if sub_name.lower() in all_pdf_text.lower():
|
| 213 |
detected_cabinet = cab_name
|
| 214 |
found_by_method = "name"
|
| 215 |
break
|
| 216 |
-
if found_by_method == "name":
|
|
|
|
| 217 |
|
| 218 |
if detected_cabinet == "Не определен":
|
| 219 |
-
return
|
| 220 |
|
| 221 |
is_clean_search = (found_by_method == "number")
|
| 222 |
remarks = self.get_remarks(detected_cabinet, is_clean_key=is_clean_search)
|
|
@@ -230,15 +238,18 @@ class KDChecker:
|
|
| 230 |
dtype = self.determine_doc_type(fname)
|
| 231 |
|
| 232 |
tasks = []
|
| 233 |
-
if dtype in remarks:
|
| 234 |
-
|
|
|
|
|
|
|
| 235 |
|
| 236 |
if tasks:
|
| 237 |
checklist[fname] = list(dict.fromkeys(tasks))
|
| 238 |
processed_count += 1
|
| 239 |
|
| 240 |
pdf_title = detected_cabinet
|
| 241 |
-
if is_manual:
|
|
|
|
| 242 |
|
| 243 |
try:
|
| 244 |
pdf = self.create_pdf(pdf_title, checklist)
|
|
@@ -252,7 +263,7 @@ class KDChecker:
|
|
| 252 |
return f"✅ Готово!\n\n📂 Шкаф: {detected_cabinet}\n🔍 Метод: {method_str}\n📄 Обработано файлов: {processed_count}\n🚩 Всего замечаний: {total}", pdf
|
| 253 |
|
| 254 |
def create_pdf(self, cabinet, data):
|
| 255 |
-
fname =
|
| 256 |
path = os.path.join(tempfile.gettempdir(), fname)
|
| 257 |
c = canvas.Canvas(path, pagesize=A4)
|
| 258 |
form = c.acroForm
|
|
@@ -272,11 +283,11 @@ class KDChecker:
|
|
| 272 |
c.setFont(font_name, 16)
|
| 273 |
|
| 274 |
try:
|
| 275 |
-
c.drawString(50, y,
|
| 276 |
except:
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
|
| 281 |
y -= 25
|
| 282 |
c.setFont(font_name, 12)
|
|
@@ -285,9 +296,9 @@ class KDChecker:
|
|
| 285 |
try:
|
| 286 |
c.drawString(50, y, f"Шкаф: {disp_cab}")
|
| 287 |
except:
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
|
| 292 |
c.drawString(400, y, f"Дата: {datetime.now().strftime('%d.%m.%Y')}")
|
| 293 |
y -= 20
|
|
@@ -301,7 +312,10 @@ class KDChecker:
|
|
| 301 |
|
| 302 |
cb_id = 0
|
| 303 |
for filename, tasks in data.items():
|
| 304 |
-
if y < 100:
|
|
|
|
|
|
|
|
|
|
| 305 |
|
| 306 |
c.setFillColor(colors.darkblue)
|
| 307 |
c.setFont(font_name, 11)
|
|
@@ -368,6 +382,7 @@ class KDChecker:
|
|
| 368 |
|
| 369 |
def create_app():
|
| 370 |
checker = KDChecker()
|
|
|
|
| 371 |
with gr.Blocks(title="Генератор чек-листов КД") as app:
|
| 372 |
gr.Markdown("# ✅ Генератор чек-листов КД")
|
| 373 |
gr.Markdown("Автоматическая проверка конструкторской документации по базе знаний Excel.")
|
|
@@ -375,18 +390,13 @@ def create_app():
|
|
| 375 |
with gr.Row():
|
| 376 |
with gr.Column():
|
| 377 |
gr.Markdown("### 1. База знаний")
|
| 378 |
-
|
| 379 |
db_in = gr.File(label="Загрузить Excel (.xlsx)", type="filepath")
|
| 380 |
-
|
| 381 |
-
# ИЗМЕНЕНО: choices=[] вместо choices=None
|
| 382 |
manual_cab = gr.Dropdown(
|
| 383 |
label="Или выберите шкаф-аналог вручную",
|
| 384 |
choices=[],
|
| 385 |
interactive=True
|
| 386 |
)
|
| 387 |
-
|
| 388 |
-
db_out = gr.Textbox(label="Статус загрузки", lines=8)
|
| 389 |
-
|
| 390 |
db_in.upload(checker.load_excel_db, inputs=[db_in], outputs=[db_out, manual_cab])
|
| 391 |
|
| 392 |
with gr.Column():
|
|
@@ -395,14 +405,21 @@ def create_app():
|
|
| 395 |
btn = gr.Button("Сформировать чек-лист", variant="primary")
|
| 396 |
|
| 397 |
with gr.Row():
|
| 398 |
-
res_txt = gr.Textbox(label="Результат проверки", lines=
|
| 399 |
res_pdf = gr.File(label="Скачать PDF чек-лист")
|
| 400 |
|
| 401 |
btn.click(checker.check_files, inputs=[files_in, manual_cab], outputs=[res_txt, res_pdf])
|
|
|
|
| 402 |
return app
|
| 403 |
|
| 404 |
|
|
|
|
| 405 |
app = create_app()
|
| 406 |
|
|
|
|
| 407 |
if __name__ == "__main__":
|
| 408 |
-
app.launch(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
import openpyxl
|
| 37 |
log.append("Библиотека openpyxl найдена.")
|
| 38 |
except ImportError:
|
| 39 |
+
return "ОШИБКА: Библиотека openpyxl не установлена!", gr.update(choices=[], value=None)
|
| 40 |
|
| 41 |
all_data = []
|
| 42 |
sheets_log = []
|
|
|
|
| 56 |
c_idx = -1
|
| 57 |
r_idx = -1
|
| 58 |
for idx, val in enumerate(row_values):
|
| 59 |
+
if "шкаф" in val or "cabinet" in val:
|
| 60 |
+
c_idx = idx
|
| 61 |
+
if "примечание" in val or "remark" in val:
|
| 62 |
+
r_idx = idx
|
| 63 |
if c_idx != -1 and r_idx != -1:
|
| 64 |
header_row_index = i
|
| 65 |
cab_col_idx = c_idx
|
|
|
|
| 71 |
if cab_col_idx < len(df.columns) and rem_col_idx < len(df.columns):
|
| 72 |
df_subset = df.iloc[:, [cab_col_idx, rem_col_idx]]
|
| 73 |
df_subset.columns = ["Cabinet", "Remark"]
|
|
|
|
| 74 |
df_subset["Cabinet"] = df_subset["Cabinet"].ffill()
|
| 75 |
df_subset = df_subset.dropna(subset=["Remark"]).astype(str)
|
|
|
|
| 76 |
df_subset["Cabinet_Clean"] = df_subset["Cabinet"].apply(
|
| 77 |
lambda x: x.strip().replace(" ", "").replace("\n", "").replace("\r", "")
|
| 78 |
)
|
|
|
|
| 79 |
all_data.append(df_subset)
|
| 80 |
sheets_log.append(f"Лист '{sheet_name}': {len(df_subset)} стр.")
|
| 81 |
else:
|
|
|
|
| 93 |
log.append("Успешно объединили данные.")
|
| 94 |
msg = f"✅ База знаний загружена успешно!\nВсего записей: {len(self.excel_db)}\nОбработаны листы: {', '.join(sheets_log)}"
|
| 95 |
|
|
|
|
| 96 |
return msg, gr.update(choices=self.cabinet_list, value=None)
|
| 97 |
|
| 98 |
except Exception as e:
|
|
|
|
| 138 |
return "UNKNOWN"
|
| 139 |
|
| 140 |
def get_remarks(self, cabinet_key, is_clean_key=True):
|
| 141 |
+
if self.excel_db.empty:
|
| 142 |
+
return {}
|
| 143 |
if is_clean_key:
|
| 144 |
target = cabinet_key.replace(" ", "")
|
| 145 |
mask = self.excel_db['Cabinet_Clean'].str.contains(re.escape(target), case=False, na=False)
|
|
|
|
| 147 |
mask = self.excel_db['Cabinet'] == cabinet_key
|
| 148 |
|
| 149 |
rows = self.excel_db[mask]
|
| 150 |
+
if rows.empty:
|
| 151 |
+
return {}
|
| 152 |
+
|
| 153 |
parsed = {}
|
| 154 |
for remark_cell in rows['Remark']:
|
| 155 |
cell_text = str(remark_cell)
|
| 156 |
cell_text = re.sub(r'(\d+)\.([А-ЯA-Z])', r'\1. \2', cell_text)
|
| 157 |
items = re.split(r'(?:^|\n)\s*(?=\d+[\.\)])', cell_text)
|
| 158 |
for item in items:
|
| 159 |
+
if len(item) < 3:
|
| 160 |
+
continue
|
| 161 |
clean_item = item.strip()
|
| 162 |
clean_item_no_num = re.sub(r'^\d+[\.\)]\s*', '', clean_item)
|
| 163 |
doc_pattern = r'^(?:Документ\s+|В\s+)?([А-ЯA-Z0-9\s,\(\)\-]+?)(?:[\.\:\-]|\s+)(.*)'
|
|
|
|
| 172 |
if valid_parts:
|
| 173 |
detected_docs = valid_parts
|
| 174 |
final_text = match.group(2).strip()
|
| 175 |
+
if not detected_docs:
|
| 176 |
+
detected_docs = ["ALL"]
|
| 177 |
for doc in detected_docs:
|
| 178 |
+
if doc not in parsed:
|
| 179 |
+
parsed[doc] = []
|
| 180 |
parsed[doc].append(final_text)
|
| 181 |
return parsed
|
| 182 |
|
| 183 |
def check_files(self, files, manual_cabinet):
|
| 184 |
+
if not files:
|
| 185 |
+
return "Файлы не загружены", None
|
| 186 |
+
if self.excel_db.empty:
|
| 187 |
+
return "Сначала загрузите Excel базу!", None
|
| 188 |
|
| 189 |
checklist = {}
|
| 190 |
detected_cabinet = "Не определен"
|
|
|
|
| 214 |
for cab_name in unique_cabinets:
|
| 215 |
sub_names = [n.strip() for n in cab_name.split(',')]
|
| 216 |
for sub_name in sub_names:
|
| 217 |
+
if len(sub_name) < 5:
|
| 218 |
+
continue
|
| 219 |
if sub_name.lower() in all_pdf_text.lower():
|
| 220 |
detected_cabinet = cab_name
|
| 221 |
found_by_method = "name"
|
| 222 |
break
|
| 223 |
+
if found_by_method == "name":
|
| 224 |
+
break
|
| 225 |
|
| 226 |
if detected_cabinet == "Не определен":
|
| 227 |
+
return "⚠️ Шкаф не опознан автоматически.\nСовет: Выберите шкаф из выпадающего списка вручную.", None
|
| 228 |
|
| 229 |
is_clean_search = (found_by_method == "number")
|
| 230 |
remarks = self.get_remarks(detected_cabinet, is_clean_key=is_clean_search)
|
|
|
|
| 238 |
dtype = self.determine_doc_type(fname)
|
| 239 |
|
| 240 |
tasks = []
|
| 241 |
+
if dtype in remarks:
|
| 242 |
+
tasks.extend(remarks[dtype])
|
| 243 |
+
if "ALL" in remarks and dtype != "С2":
|
| 244 |
+
tasks.extend(remarks["ALL"])
|
| 245 |
|
| 246 |
if tasks:
|
| 247 |
checklist[fname] = list(dict.fromkeys(tasks))
|
| 248 |
processed_count += 1
|
| 249 |
|
| 250 |
pdf_title = detected_cabinet
|
| 251 |
+
if is_manual:
|
| 252 |
+
pdf_title += " (Выбор вручную)"
|
| 253 |
|
| 254 |
try:
|
| 255 |
pdf = self.create_pdf(pdf_title, checklist)
|
|
|
|
| 263 |
return f"✅ Готово!\n\n📂 Шкаф: {detected_cabinet}\n🔍 Метод: {method_str}\n📄 Обработано файлов: {processed_count}\n🚩 Всего замечаний: {total}", pdf
|
| 264 |
|
| 265 |
def create_pdf(self, cabinet, data):
|
| 266 |
+
fname = "CheckList_Result.pdf"
|
| 267 |
path = os.path.join(tempfile.gettempdir(), fname)
|
| 268 |
c = canvas.Canvas(path, pagesize=A4)
|
| 269 |
form = c.acroForm
|
|
|
|
| 283 |
c.setFont(font_name, 16)
|
| 284 |
|
| 285 |
try:
|
| 286 |
+
c.drawString(50, y, "ЧЕК-ЛИСТ ПРОВЕРКИ КД")
|
| 287 |
except:
|
| 288 |
+
c.setFont("Helvetica", 16)
|
| 289 |
+
c.drawString(50, y, "CHECK-LIST")
|
| 290 |
+
c.setFont(font_name, 16)
|
| 291 |
|
| 292 |
y -= 25
|
| 293 |
c.setFont(font_name, 12)
|
|
|
|
| 296 |
try:
|
| 297 |
c.drawString(50, y, f"Шкаф: {disp_cab}")
|
| 298 |
except:
|
| 299 |
+
c.setFont("Helvetica", 12)
|
| 300 |
+
c.drawString(50, y, "Cabinet name error (font)")
|
| 301 |
+
c.setFont(font_name, 12)
|
| 302 |
|
| 303 |
c.drawString(400, y, f"Дата: {datetime.now().strftime('%d.%m.%Y')}")
|
| 304 |
y -= 20
|
|
|
|
| 312 |
|
| 313 |
cb_id = 0
|
| 314 |
for filename, tasks in data.items():
|
| 315 |
+
if y < 100:
|
| 316 |
+
c.showPage()
|
| 317 |
+
y = height - 50
|
| 318 |
+
c.setFont(font_name, 12)
|
| 319 |
|
| 320 |
c.setFillColor(colors.darkblue)
|
| 321 |
c.setFont(font_name, 11)
|
|
|
|
| 382 |
|
| 383 |
def create_app():
|
| 384 |
checker = KDChecker()
|
| 385 |
+
|
| 386 |
with gr.Blocks(title="Генератор чек-листов КД") as app:
|
| 387 |
gr.Markdown("# ✅ Генератор чек-листов КД")
|
| 388 |
gr.Markdown("Автоматическая проверка конструкторской документации по базе знаний Excel.")
|
|
|
|
| 390 |
with gr.Row():
|
| 391 |
with gr.Column():
|
| 392 |
gr.Markdown("### 1. База знаний")
|
|
|
|
| 393 |
db_in = gr.File(label="Загрузить Excel (.xlsx)", type="filepath")
|
|
|
|
|
|
|
| 394 |
manual_cab = gr.Dropdown(
|
| 395 |
label="Или выберите шкаф-аналог вручную",
|
| 396 |
choices=[],
|
| 397 |
interactive=True
|
| 398 |
)
|
| 399 |
+
db_out = gr.Textbox(label="Статус загрузки", lines=6)
|
|
|
|
|
|
|
| 400 |
db_in.upload(checker.load_excel_db, inputs=[db_in], outputs=[db_out, manual_cab])
|
| 401 |
|
| 402 |
with gr.Column():
|
|
|
|
| 405 |
btn = gr.Button("Сформировать чек-лист", variant="primary")
|
| 406 |
|
| 407 |
with gr.Row():
|
| 408 |
+
res_txt = gr.Textbox(label="Результат проверки", lines=6)
|
| 409 |
res_pdf = gr.File(label="Скачать PDF чек-лист")
|
| 410 |
|
| 411 |
btn.click(checker.check_files, inputs=[files_in, manual_cab], outputs=[res_txt, res_pdf])
|
| 412 |
+
|
| 413 |
return app
|
| 414 |
|
| 415 |
|
| 416 |
+
# Создаём приложение
|
| 417 |
app = create_app()
|
| 418 |
|
| 419 |
+
# Запуск для Hugging Face Spaces
|
| 420 |
if __name__ == "__main__":
|
| 421 |
+
app.launch(
|
| 422 |
+
server_name="0.0.0.0",
|
| 423 |
+
server_port=7860,
|
| 424 |
+
share=False
|
| 425 |
+
)
|