import gradio as gr import pandas as pd import pdfplumber import os import tempfile import re from datetime import datetime from reportlab.pdfgen import canvas from reportlab.lib.pagesizes import A4 from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase.ttfonts import TTFont from reportlab.lib import colors # ========== АВТОРИЗАЦИЯ ========== AUTH_USERNAME = "admin" AUTH_PASSWORD = "12345" # ================================= print("=" * 40) print(f"App Start: {datetime.now()}") print(f"Gradio: {gr.__version__}") print(f"Auth: {AUTH_USERNAME} / *****") print("=" * 40) class KDChecker: def __init__(self): self.excel_db = pd.DataFrame() self.cabinet_list = [] self.known_docs = ["Э3", "В4", "ПЭ3", "ВО", "ТЭ5", "СБ", "С5", "ОЛ", "Э1", "Э4", "Э7", "Д3", "Э6"] def load_excel_db(self, excel_file): """Загрузка Excel базы""" if excel_file is None: return "❌ Файл не выбран", gr.update(choices=[], value=None) excel_path = excel_file if isinstance(excel_file, str) else getattr(excel_file, 'name', str(excel_file)) if not os.path.exists(excel_path): return f"❌ Файл не найден: {excel_path}", gr.update(choices=[], value=None) all_data = [] sheets_info = [] try: xls = pd.read_excel(excel_path, sheet_name=None, header=None, engine='openpyxl') for sheet_name, df_raw in xls.items(): header_row = -1 cab_col = -1 rem_col = -1 for i in range(min(20, len(df_raw))): row_vals = [str(x).lower().strip() for x in df_raw.iloc[i].values] for idx, val in enumerate(row_vals): if "шкаф" in val or "cabinet" in val: cab_col = idx if "примечание" in val or "remark" in val: rem_col = idx if cab_col != -1 and rem_col != -1: header_row = i break if header_row != -1 and cab_col < len(df_raw.columns) and rem_col < len(df_raw.columns): df = pd.read_excel(excel_path, sheet_name=sheet_name, header=header_row, engine='openpyxl') if cab_col < len(df.columns) and rem_col < len(df.columns): df_sub = df.iloc[:, [cab_col, rem_col]] df_sub.columns = ["Cabinet", "Remark"] df_sub["Cabinet"] = df_sub["Cabinet"].ffill() df_sub = df_sub.dropna(subset=["Remark"]).astype(str) df_sub["Cabinet_Clean"] = df_sub["Cabinet"].apply( lambda x: x.strip().replace(" ", "").replace("\n", "").replace("\r", "") ) all_data.append(df_sub) sheets_info.append(f"'{sheet_name}': {len(df_sub)}") if not all_data: return "❌ Не найдены колонки 'Шкаф' и 'Примечание'", gr.update(choices=[], value=None) self.excel_db = pd.concat(all_data, ignore_index=True) self.cabinet_list = sorted(self.excel_db["Cabinet"].unique().tolist()) msg = f"✅ База загружена!\n\n" msg += f"📊 Записей: {len(self.excel_db)}\n" msg += f"🗄️ Шкафов: {len(self.cabinet_list)}\n" msg += f"📋 Листы: {', '.join(sheets_info)}" return msg, gr.update(choices=self.cabinet_list, value=None) except Exception as e: import traceback return f"❌ Ошибка: {e}\n\n{traceback.format_exc()}", gr.update(choices=[], value=None) def extract_text(self, pdf_path): try: text = "" with pdfplumber.open(pdf_path) as pdf: for page in pdf.pages: text += (page.extract_text() or "") + "\n" return text except: return "" def find_decimal_numbers(self, text): pattern = r"(РЛТ|ЛДАР|ВНАР|ШТМ)[\s\.]*\d{1}[\s\.]*\d{3}[\s\.]*[А-ЯA-Z]{1,4}[\s\.]*\d{3}(-[\d]+)?" matches = [] for m in re.finditer(pattern, text): clean = m.group(0).replace(" ", "").replace("\n", "") if clean not in matches: matches.append(clean) return matches def determine_doc_type(self, filename): fname = filename.upper() doc_map = { "С2": ["С2"], "ПЭ3": ["ПЭ3", "ПЕРЕЧЕНЬ"], "Э3": ["Э3", "СХЕМА ЭЛЕКТРИЧЕСКАЯ"], "Э4": ["Э4"], "В4": ["В4", "СПЕЦИФИКАЦИЯ"], "ВО": ["ВО", "Э7", "ГАБАРИТ"], "ТЭ5": ["ТЭ5", "ТАБЛИЦА"], "СБ": ["СБ"], "С5": ["С5"], "ОЛ": ["ОЛ"], "Э1": ["Э1"], "Э6": ["Э6", "ЗАЗЕМЛЕНИЯ"], "Д3": ["Д3", "МОНТАЖ"] } for doc_type, patterns in doc_map.items(): if any(p in fname for p in patterns): return doc_type return "UNKNOWN" def get_remarks(self, cabinet_key, is_clean=True): if self.excel_db.empty: return {} if is_clean: target = cabinet_key.replace(" ", "") mask = self.excel_db['Cabinet_Clean'].str.contains(re.escape(target), case=False, na=False) else: mask = self.excel_db['Cabinet'] == cabinet_key rows = self.excel_db[mask] if rows.empty: return {} parsed = {} for remark in rows['Remark']: text = str(remark) text = re.sub(r'(\d+)\.([А-ЯA-Z])', r'\1. \2', text) items = re.split(r'(?:^|\n)\s*(?=\d+[\.\)])', text) for item in items: if len(item) < 3: continue clean_item = item.strip() no_num = re.sub(r'^\d+[\.\)]\s*', '', clean_item) pattern = r'^(?:Документ\s+|В\s+)?([А-ЯA-Z0-9\s,\(\)\-]+?)(?:[\.\:\-]|\s+)(.*)' match = re.match(pattern, no_num, re.IGNORECASE | re.DOTALL) docs = [] final_text = clean_item if match: potential = match.group(1).upper() cleaned = potential.replace("(", " ").replace(")", " ").replace(",", " ") parts = cleaned.split() valid = [p for p in parts if p in self.known_docs] if valid: docs = valid final_text = match.group(2).strip() if not docs: docs = ["ALL"] for d in docs: if d not in parsed: parsed[d] = [] parsed[d].append(final_text) return parsed def check_files(self, files, manual_cabinet): if not files: return "❌ Загрузите PDF файлы", None if self.excel_db.empty: return "❌ Сначала загрузите Excel!", None file_paths = [] for f in files: fp = f if isinstance(f, str) else getattr(f, 'name', str(f)) file_paths.append(fp) detected_cabinet = "Не определен" method = "" is_manual = False if manual_cabinet and manual_cabinet.strip(): detected_cabinet = manual_cabinet method = "manual" is_manual = True else: all_text = "".join(self.extract_text(fp) for fp in file_paths) numbers = self.find_decimal_numbers(all_text) db_keys = set(self.excel_db["Cabinet_Clean"].tolist()) for num in numbers: if num in db_keys: detected_cabinet = num method = "number" break if detected_cabinet == "Не определен": for cab in self.excel_db["Cabinet"].unique(): parts = [n.strip() for n in cab.split(',')] for part in parts: if len(part) >= 5 and part.lower() in all_text.lower(): detected_cabinet = cab method = "name" break if method: break if detected_cabinet == "Не определен": return "⚠️ Шкаф не определён. Выберите вручную.", None remarks = self.get_remarks(detected_cabinet, is_clean=(method == "number")) if not remarks: return f"⚠️ Для '{detected_cabinet}' замечаний нет.", None checklist = {} for fp in file_paths: fname = os.path.basename(fp) dtype = self.determine_doc_type(fname) tasks = remarks.get(dtype, []) + (remarks.get("ALL", []) if dtype != "С2" else []) if tasks: checklist[fname] = list(dict.fromkeys(tasks)) title = detected_cabinet + (" (ручной)" if is_manual else "") try: pdf_path = self.create_pdf(title, checklist) except Exception as e: return f"❌ Ошибка PDF: {e}", None total = sum(len(v) for v in checklist.values()) method_name = {"manual": "Ручной", "number": "По номеру", "name": "По названию"}.get(method, "?") return f"✅ Готово!\n\n📂 {detected_cabinet}\n🔍 {method_name}\n📄 Файлов: {len(file_paths)}\n🚩 Замечаний: {total}", pdf_path def create_pdf(self, cabinet, data): path = os.path.join(tempfile.gettempdir(), "CheckList.pdf") c = canvas.Canvas(path, pagesize=A4) w, h = A4 font = 'Helvetica' y = h - 50 c.setFont(font, 16) c.drawString(50, y, "CHECK-LIST") y -= 25 c.setFont(font, 12) c.drawString(50, y, f"Cabinet: {cabinet[:50]}") c.drawString(400, y, datetime.now().strftime('%d.%m.%Y')) y -= 20 c.line(50, y, w - 50, y) y -= 30 if not data: c.drawString(50, y, "No remarks.") c.save() return path for fname, tasks in data.items(): if y < 100: c.showPage() y = h - 50 c.setFillColor(colors.darkblue) c.setFont(font, 11) c.drawString(50, y, f"File: {fname}") c.setFillColor(colors.black) y -= 18 c.setFont(font, 10) for task in tasks: if y < 60: c.showPage() y = h - 50 c.setFont(font, 10) c.rect(50, y - 2, 8, 8, stroke=1, fill=0) words = task.replace('\n', ' ').split() lines, line = [], "" for word in words: if len(line) + len(word) < 85: line += word + " " else: lines.append(line.strip()) line = word + " " if line: lines.append(line.strip()) for ln in lines: if y < 40: c.showPage() y = h - 50 c.setFont(font, 10) c.drawString(65, y, ln) y -= 12 y -= 5 y -= 15 c.save() return path # ========== ИНТЕРФЕЙС ========== checker = KDChecker() with gr.Blocks(title="КД Checker", theme=gr.themes.Soft()) as app: gr.Markdown("# ✅ Генератор чек-листов КД") with gr.Row(): with gr.Column(): gr.Markdown("### 📁 База знаний") db_input = gr.File(label="Excel (.xlsx)", file_types=[".xlsx", ".xls"], type="filepath") cabinet_dd = gr.Dropdown(label="Шкаф вручную", choices=[], interactive=True) db_status = gr.Textbox(label="Статус", lines=5) with gr.Column(): gr.Markdown("### 📄 Чертежи") pdf_input = gr.File(label="PDF файлы", file_count="multiple", file_types=[".pdf"], type="filepath") run_btn = gr.Button("🔍 Сформировать", variant="primary") with gr.Row(): result_txt = gr.Textbox(label="Результат", lines=5) result_pdf = gr.File(label="📥 PDF") db_input.change(checker.load_excel_db, [db_input], [db_status, cabinet_dd]) run_btn.click(checker.check_files, [pdf_input, cabinet_dd], [result_txt, result_pdf]) if __name__ == "__main__": app.launch( server_name="0.0.0.0", server_port=7860, auth=(AUTH_USERNAME, AUTH_PASSWORD) )