Ed5's picture
Update app.py
ada24b4 verified
raw
history blame
13.3 kB
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)
)