Spaces:
Sleeping
Sleeping
File size: 14,569 Bytes
7748b7d |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 |
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("<br>") # Добавляем расстояние перед таблицей
with gr.Row():
gr.Markdown("<h3 style='text-align: center;'>ДЕТАЛИЗИРОВАННЫЙ БЮДЖЕТ</h3>", elem_id="title1")
with gr.Row():
gr.Markdown("Отчет дает полное представление о том, как распределяются средства по каждому разделу сметной документации. Он разбивает затраты на ключевые категории — оплата труда, техника, материалы, логистика и оборудование. Это позволяет держать под контролем каждую статью расходов и моментально видеть, где происходят основные затраты.", elem_id="desc1")
with gr.Row():
output_table_1 = gr.Dataframe(headers=["№ локальной сметы", "Наименование", "Рабочие", "Техника", "Материалы", "Перевозки", "Оборудование", "ВСЕГО ПО СМЕТЕ"], elem_id="table1")
# Расстояние между таблицами
with gr.Row():
gr.Markdown("<br>") # Добавляем расстояние между таблицами
# Вторая таблица с заголовком и описанием
with gr.Row():
gr.Markdown("<h3 style='text-align: center;'>ПРОЦЕНТНОЕ РАСПРЕДЕЛЕНИЕ ЛОКАЛЬНЫХ СМЕТ</h3>", elem_id="title2")
with gr.Row():
gr.Markdown("Важный инструмент для стратегического планирования, который помогает легко сравнивать между собой разделы проекта, понимая, какие из них требуют большего финансирования, а какие — меньшего. Это позволяет эффективно распределять ресурсы и достигать максимальной отдачи.", elem_id="desc2")
with gr.Row():
output_table_2 = gr.Dataframe(headers=["№ локальной сметы", "Наименование", "Процент от общей суммы"], elem_id="table2")
# Третья таблица с диаграммой
with gr.Row():
gr.Markdown("<h3 style='text-align: center;'>СТРУКТУРА РАСХОДОВ ПО КАТЕГОРИЯМ</h3>", elem_id="title3")
with gr.Row():
gr.Markdown("<br>") # Добавляем пустое пространство после заголовка
with gr.Row():
output_plot = gr.Image(type="pil", elem_id="plot")
# Кнопки для скачивания Excel-файлов
with gr.Row():
download_excel_1 = gr.File(label="Скачать детализированный отчет")
download_excel_2 = gr.File(label="Скачать процентное соотношение")
def update_download_buttons(file):
df_formatted, df_percentage_formatted, pie_chart, detailed_report_path, percentage_report_path = process_pdf(file)
return df_formatted, df_percentage_formatted, Image.open(pie_chart), detailed_report_path, percentage_report_path
upload_button.change(
update_download_buttons,
inputs=upload_button,
outputs=[output_table_1, output_table_2, output_plot, download_excel_1, download_excel_2]
)
# Запуск приложения
demo.launch(share=True)
|