import gradio as gr
import pdfplumber
import re
import pandas as pd
import matplotlib.pyplot as plt
from io import BytesIO
import tempfile
import os
from PIL import Image
# Функция для обработки PDF и извлечения данных
def process_pdf(file):
data_list = []
try:
# Открываем PDF-файл с помощью pdfplumber
with pdfplumber.open(file) as pdf:
text = ''
for page_num, page in enumerate(pdf.pages, start=1):
page_text = page.extract_text()
if page_text:
text += page_text + '\n'
# Функция для извлечения значений по ключевым словам
def extract_value(label, text):
pattern = rf'{label}\s*тенге\s*([\d\s,]+)' # Поиск ключевого слова и числового значения
match = re.search(pattern, text, flags=re.IGNORECASE)
if match:
value = match.group(1)
value = re.sub(r'[^\d]', '', value) # Убираем все, кроме цифр
return int(value) if value else 0
return 0
# Функция для извлечения значения "ВСЕГО ПО СМЕТЕ"
def extract_total_value(label, text):
pattern = rf'{label}:\s*([\d\s]+)' # Учёт двоеточия после "ВСЕГО ПО СМЕТЕ"
match = re.search(pattern, text, flags=re.IGNORECASE)
if match:
value = match.group(1)
value = re.sub(r'[^\d]', '', value) # Убираем все символы, кроме цифр
return int(value) if value else 0
# Разбиваем текст на части по ключевому слову "ЛОКАЛЬНАЯ СМЕТА №"
estimates = re.split(r'ЛОКАЛЬНАЯ СМЕТА №', text, flags=re.IGNORECASE)[1:]
for idx, estimate_text in enumerate(estimates, start=1):
# Обрезаем текст до появления слова "Раздел"
estimate_text_clean = re.split(r'Раздел', estimate_text, flags=re.IGNORECASE)[0]
# Извлекаем номер сметы
number_match = re.match(r'(\S+)', estimate_text_clean.strip())
estimate_number = number_match.group(1) if number_match else ''
# Извлекаем наименование сметы после "на"
name_match = re.search(r'на\s+(.+?)(?:\n|$)', estimate_text_clean, flags=re.IGNORECASE)
estimate_name = name_match.group(1).strip() if name_match else ''
# Извлекаем затраты в указанном порядке
labor_cost = extract_value('Затраты на труд рабочих', estimate_text_clean)
machine_cost = extract_value('Машины и механизмы', estimate_text_clean)
material_cost = extract_value('Материалы, изделия и конструкции', estimate_text_clean)
transport_cost = extract_value('Перевозки', estimate_text_clean)
equipment_cost = extract_value('Оборудование', estimate_text_clean)
total_cost = extract_total_value('ВСЕГО ПО СМЕТЕ', estimate_text_clean)
# Сохраняем данные в список с обновленными названиями столбцов
data_list.append({
'№ локальной сметы': estimate_number,
'Наименование': estimate_name,
'Рабочие': labor_cost,
'Техника': machine_cost,
'Материалы': material_cost,
'Перевозки': transport_cost,
'Оборудование': equipment_cost,
'ВСЕГО ПО СМЕТЕ': total_cost
})
except Exception as e:
print(f"Ошибка при обработке PDF-файла: {e}")
return pd.DataFrame(), pd.DataFrame(), None, None, None # Возвращаем два пустых DataFrame и None для диаграммы и файлов Excel в случае ошибки
# Преобразуем список данных в DataFrame
df = pd.DataFrame(data_list)
# Убираем лишние пробелы и символы из названий столбцов
df.columns = df.columns.str.strip()
# Рассчитываем общую сумму по всем сметам для каждой категории
total_row = pd.DataFrame({
'№ локальной сметы': ['Итого'],
'Наименование': [''],
'Рабочие': [df['Рабочие'].sum()],
'Техника': [df['Техника'].sum()],
'Материалы': [df['Материалы'].sum()],
'Перевозки': [df['Перевозки'].sum()],
'Оборудование': [df['Оборудование'].sum()],
'ВСЕГО ПО СМЕТЕ': [df['ВСЕГО ПО СМЕТЕ'].sum()]
})
# Добавляем строку "Итого" в конец таблицы
df = pd.concat([df, total_row], ignore_index=True)
# Создаём таблицу с процентным соотношением, округленным до двух знаков после запятой
total_sum = df['ВСЕГО ПО СМЕТЕ'].iloc[:-1].sum() # исключаем строку "Итого"
df_percentage = df[['№ локальной сметы', 'Наименование']].copy()
df_percentage['Процент от общей суммы'] = ((df['ВСЕГО ПО СМЕТЕ'].iloc[:-1] / total_sum) * 100).round(2)
# Создание круговой диаграммы
category_sums = {
'Категория': ['Рабочие', 'Техника', 'Материалы', 'Перевозки', 'Оборудование'],
'Сумма': [
df['Рабочие'].iloc[:-1].sum(),
df['Техника'].iloc[:-1].sum(),
df['Материалы'].iloc[:-1].sum(),
df['Перевозки'].iloc[:-1].sum(),
df['Оборудование'].iloc[:-1].sum()
]
}
df_category_percentage = pd.DataFrame(category_sums)
df_category_percentage['Процент от общей суммы'] = ((df_category_percentage['Сумма'] / total_sum) * 100).round(2)
# Создание словаря сопоставления категорий и цветов
color_map = {
'Рабочие': '#DCEDFC',
'Техника': '#94B4D4',
'Материалы': '#3C47D6',
'Перевозки': '#94B4D4',
'Оборудование': '#0E86D4'
}
# Фильтрация категорий с процентом >=1%
filtered_df = df_category_percentage[df_category_percentage['Процент от общей суммы'] >= 1].reset_index(drop=True)
# Генерация списка цветов в соответствии с порядком категорий
colors = [color_map.get(category, '#FFFFFF') for category in filtered_df['Категория']]
# Отладочная информация (можно удалить после проверки)
print("Категории:", filtered_df['Категория'].tolist())
print("Цвета:", colors)
# Сохраняем диаграмму в BytesIO с добавлением пустого пространства
fig, ax = plt.subplots(figsize=(7, 7)) # Увеличенный размер фигуры
ax.pie(
filtered_df['Сумма'],
labels=filtered_df['Категория'],
autopct='%1.2f%%',
startangle=90,
colors=colors, # Используем пользовательские цвета
labeldistance=1.1, # Расстояние до надписей
pctdistance=0.8 # Расстояние до процентных надписей
)
plt.title('')
plt.axis('equal') # Круговая диаграмма круглая по форме
plt.tight_layout() # Оптимизация расположения элементов
pie_chart = BytesIO()
plt.savefig(pie_chart, format='png', bbox_inches='tight') # bbox_inches='tight' для оптимизации
plt.close(fig)
pie_chart.seek(0)
# Форматирование чисел с пробелами в качестве разделителей тысяч
def format_number(x):
return f"{x:,}".replace(",", " ")
df_formatted = df.copy()
for col in ['Рабочие', 'Техника', 'Материалы', 'Перевозки', 'Оборудование', 'ВСЕГО ПО СМЕТЕ']:
df_formatted[col] = df_formatted[col].apply(format_number)
df_percentage_formatted = df_percentage.copy()
df_percentage_formatted['Процент от общей суммы'] = df_percentage_formatted['Процент от общей суммы'].astype(str) + '%'
# Создаём временные Excel-файлы
temp_dir = tempfile.mkdtemp()
detailed_report_path = os.path.join(temp_dir, "detailed_report.xlsx")
percentage_report_path = os.path.join(temp_dir, "percentage_report.xlsx")
df.to_excel(detailed_report_path, index=False)
df_percentage.to_excel(percentage_report_path, index=False)
# Возвращаем форматированные DataFrame и диаграмму
return df_formatted, df_percentage_formatted, pie_chart, detailed_report_path, percentage_report_path
# CSS для блокировки копирования текста
custom_css = """
* {
-webkit-user-select: none; /* Safari */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* Internet Explorer/Edge */
user-select: none; /* Standard */
}
"""
# Создаем интерфейс Gradio с блоками и адаптивным дизайном
with gr.Blocks(css=custom_css) as demo:
# Основной заголовок и подзаголовок
with gr.Row():
gr.Markdown("### **AIGineer | ИИ УПРАВЛЕНИЕ СТРОИТЕЛЬСТВОМ**", elem_id="header")
gr.Markdown("**На текущий момент система считает сметы в ценах от 2023**", elem_id="subheader")
# Загрузка файла (текст выравнивается по центру)
with gr.Row():
upload_button = gr.File(label="ЗАГРУЗИТЕ СМЕТУ В PDF", elem_id="upload")
# Первая таблица с заголовком и описанием
with gr.Row():
gr.Markdown("
") # Добавляем расстояние перед таблицей
with gr.Row():
gr.Markdown("