DinaFatikh commited on
Commit
e9c9c75
·
verified ·
1 Parent(s): 4526072

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +1 -251
app.py CHANGED
@@ -1,251 +1 @@
1
- import gradio as gr
2
- import pdfplumber
3
- import re
4
- import pandas as pd
5
- import matplotlib.pyplot as plt
6
- from io import BytesIO
7
- import tempfile
8
- import os
9
- from PIL import Image
10
-
11
- # Функция для обработки PDF и извлечения данных
12
- def process_pdf(file):
13
- data_list = []
14
-
15
- try:
16
- # Открываем PDF-файл с помощью pdfplumber
17
- with pdfplumber.open(file) as pdf:
18
- text = ''
19
- for page_num, page in enumerate(pdf.pages, start=1):
20
- page_text = page.extract_text()
21
- if page_text:
22
- text += page_text + '\n'
23
-
24
- # Функция для извлечения значений по ключевым словам
25
- def extract_value(label, text):
26
- pattern = rf'{label}\s*тенге\s*([\d\s,]+)' # Поиск ключевого слова и числового значения
27
- match = re.search(pattern, text, flags=re.IGNORECASE)
28
- if match:
29
- value = match.group(1)
30
- value = re.sub(r'[^\d]', '', value) # Убираем все, кроме цифр
31
- return int(value) if value else 0
32
- return 0
33
-
34
- # Функция для извлечения значения "ВСЕГО ПО СМЕТЕ"
35
- def extract_total_value(label, text):
36
- pattern = rf'{label}:\s*([\d\s]+)' # Учёт двоеточия после "ВСЕГО ПО СМЕТЕ"
37
- match = re.search(pattern, text, flags=re.IGNORECASE)
38
- if match:
39
- value = match.group(1)
40
- value = re.sub(r'[^\d]', '', value) # Убираем все символы, кроме цифр
41
- return int(value) if value else 0
42
-
43
- # Разбиваем текст на части по ключевому слову "ЛОКАЛЬНАЯ СМЕТА №"
44
- estimates = re.split(r'ЛОКАЛЬНАЯ СМЕТА №', text, flags=re.IGNORECASE)[1:]
45
-
46
- for idx, estimate_text in enumerate(estimates, start=1):
47
- # Обрезаем текст до появления слова "Раздел"
48
- estimate_text_clean = re.split(r'Раздел', estimate_text, flags=re.IGNORECASE)[0]
49
-
50
- # Извлекаем номер сметы
51
- number_match = re.match(r'(\S+)', estimate_text_clean.strip())
52
- estimate_number = number_match.group(1) if number_match else ''
53
-
54
- # Извлекаем наименование сметы после "на"
55
- name_match = re.search(r'на\s+(.+?)(?:\n|$)', estimate_text_clean, flags=re.IGNORECASE)
56
- estimate_name = name_match.group(1).strip() if name_match else ''
57
-
58
- # Извлекаем затраты в указанном порядке
59
- labor_cost = extract_value('Затраты на труд рабочих', estimate_text_clean)
60
- machine_cost = extract_value('Машины и механизмы', estimate_text_clean)
61
- material_cost = extract_value('Материалы, изделия и конструкции', estimate_text_clean)
62
- transport_cost = extract_value('Перевозки', estimate_text_clean)
63
- equipment_cost = extract_value('Оборудование', estimate_text_clean)
64
- total_cost = extract_total_value('ВСЕГО ПО СМЕТЕ', estimate_text_clean)
65
-
66
- # Сохраняем данные в список с обновленными названиями столбцов
67
- data_list.append({
68
- '№ локальной сметы': estimate_number,
69
- 'Наименование': estimate_name,
70
- 'Рабочие': labor_cost,
71
- 'Техника': machine_cost,
72
- 'Материалы': material_cost,
73
- 'Перевозки': transport_cost,
74
- 'Оборудование': equipment_cost,
75
- 'ВСЕГО ПО СМЕТЕ': total_cost
76
- })
77
-
78
- except Exception as e:
79
- print(f"Ошибка при обработке PDF-файла: {e}")
80
- return pd.DataFrame(), pd.DataFrame(), None, None, None # Возвращаем два пустых DataFrame и None для диаграммы и файлов Excel в случае ошибки
81
-
82
- # Преобразуем список данных в DataFrame
83
- df = pd.DataFrame(data_list)
84
-
85
- # Убираем лишние пробелы и символы из названий столбцов
86
- df.columns = df.columns.str.strip()
87
-
88
- # Рассчитываем общую сумму по всем сметам для каждой категории
89
- total_row = pd.DataFrame({
90
- '№ локальной сметы': ['Итого'],
91
- 'Наименование': [''],
92
- 'Рабочие': [df['Рабочие'].sum()],
93
- 'Техника': [df['Техника'].sum()],
94
- 'Материалы': [df['М��териалы'].sum()],
95
- 'Перевозки': [df['Перевозки'].sum()],
96
- 'Оборудование': [df['Оборудование'].sum()],
97
- 'ВСЕГО ПО СМЕТЕ': [df['ВСЕГО ПО СМЕТЕ'].sum()]
98
- })
99
-
100
- # Добавляем строку "Итого" в конец таблицы
101
- df = pd.concat([df, total_row], ignore_index=True)
102
-
103
- # Создаём таблицу с процентным соотношением, округленным до двух знаков после запятой
104
- total_sum = df['ВСЕГО ПО СМЕТЕ'].iloc[:-1].sum() # исключаем строку "Итого"
105
- df_percentage = df[['№ локальной сметы', 'Наименование']].copy()
106
- df_percentage['Процент от общей суммы'] = ((df['ВСЕГО ПО СМЕТЕ'].iloc[:-1] / total_sum) * 100).round(2)
107
-
108
- # Создание круговой диаграммы
109
- category_sums = {
110
- 'Категория': ['Рабочие', 'Техника', 'Материалы', 'Перевозки', 'Оборудование'],
111
- 'Сумма': [
112
- df['Рабочие'].iloc[:-1].sum(),
113
- df['Техника'].iloc[:-1].sum(),
114
- df['Материалы'].iloc[:-1].sum(),
115
- df['Перевозки'].iloc[:-1].sum(),
116
- df['Оборудование'].iloc[:-1].sum()
117
- ]
118
- }
119
-
120
- df_category_percentage = pd.DataFrame(category_sums)
121
- df_category_percentage['Процент от общей суммы'] = ((df_category_percentage['Сумма'] / total_sum) * 100).round(2)
122
-
123
- # Создание словаря сопоставления категорий и цветов
124
- color_map = {
125
- 'Рабочие': '#DCEDFC',
126
- 'Техника': '#94B4D4',
127
- 'Материалы': '#3C47D6',
128
- 'Перевозки': '#94B4D4',
129
- 'Оборудование': '#0E86D4'
130
- }
131
-
132
- # Фильтрация категорий с процентом >=1%
133
- filtered_df = df_category_percentage[df_category_percentage['Процент от общей суммы'] >= 1].reset_index(drop=True)
134
-
135
- # Генерация списка цветов в соответствии с порядком категорий
136
- colors = [color_map.get(category, '#FFFFFF') for category in filtered_df['Категория']]
137
-
138
- # Отладочная информация (можно удалить после проверки)
139
- print("Категории:", filtered_df['Категория'].tolist())
140
- print("Цвета:", colors)
141
-
142
- # Сохраняем диаграмму в BytesIO с добавлением пустого пространства
143
- fig, ax = plt.subplots(figsize=(7, 7)) # Увеличенный размер фигуры
144
- ax.pie(
145
- filtered_df['Сумма'],
146
- labels=filtered_df['Категория'],
147
- autopct='%1.2f%%',
148
- startangle=90,
149
- colors=colors, # Используем пользовательские цвета
150
- labeldistance=1.1, # Расстояние до надписей
151
- pctdistance=0.8 # Расстояние до процентных надписей
152
- )
153
- plt.title('')
154
- plt.axis('equal') # Круговая диаграмма круглая по форме
155
- plt.tight_layout() # Оптимизация расположения элементов
156
-
157
- pie_chart = BytesIO()
158
- plt.savefig(pie_chart, format='png', bbox_inches='tight') # bbox_inches='tight' для оптимизации
159
- plt.close(fig)
160
- pie_chart.seek(0)
161
-
162
- # Форматирование чисел с пробелами в качестве разделителей тысяч
163
- def format_number(x):
164
- return f"{x:,}".replace(",", " ")
165
-
166
- df_formatted = df.copy()
167
- for col in ['Рабочие', 'Техника', 'Материалы', 'Перевозки', 'Оборудование', 'ВСЕГО ПО СМЕТЕ']:
168
- df_formatted[col] = df_formatted[col].apply(format_number)
169
-
170
- df_percentage_formatted = df_percentage.copy()
171
- df_percentage_formatted['Процент от общей суммы'] = df_percentage_formatted['Процент от общей суммы'].astype(str) + '%'
172
-
173
- # Создаём временные Excel-файлы
174
- temp_dir = tempfile.mkdtemp()
175
- detailed_report_path = os.path.join(temp_dir, "detailed_report.xlsx")
176
- percentage_report_path = os.path.join(temp_dir, "percentage_report.xlsx")
177
-
178
- df.to_excel(detailed_report_path, index=False)
179
- df_percentage.to_excel(percentage_report_path, index=False)
180
-
181
- # Возвращаем форматированные DataFrame и диаграмму
182
- return df_formatted, df_percentage_formatted, pie_chart, detailed_report_path, percentage_report_path
183
-
184
- # CSS для блокировки копирования текста
185
- custom_css = """
186
- * {
187
- -webkit-user-select: none; /* Safari */
188
- -moz-user-select: none; /* Firefox */
189
- -ms-user-select: none; /* Internet Explorer/Edge */
190
- user-select: none; /* Standard */
191
- }
192
- """
193
-
194
- # Создаем интерфейс Gradio с блоками и адаптивным дизайном
195
- with gr.Blocks(css=custom_css) as demo:
196
- # Основной заголовок и подзаголовок
197
- with gr.Row():
198
- gr.Markdown("### **AIGineer | ИИ УПРАВЛЕНИЕ СТРОИТЕЛЬСТВОМ**", elem_id="header")
199
- gr.Markdown("**На текущий момент система считает сметы в ценах от 2023**", elem_id="subheader")
200
-
201
- # Загрузка файла (текст выравнивается по центру)
202
- with gr.Row():
203
- upload_button = gr.File(label="ЗАГРУЗИТЕ СМЕТУ В PDF", elem_id="upload")
204
-
205
- # Первая таблица с заголовком и описанием
206
- with gr.Row():
207
- gr.Markdown("<br>") # Добавляем расстояние перед таблицей
208
- with gr.Row():
209
- gr.Markdown("<h3 style='text-align: center;'>ДЕТАЛИЗИРОВАННЫЙ БЮДЖЕТ</h3>", elem_id="title1")
210
- with gr.Row():
211
- gr.Markdown("Отчет дает полное представление о том, как распределяются средства по каждому разделу сметной документации. Он разбивает затраты на ключевые категории — оплата труда, техника, материалы, логистика и оборудование. Это позволяет держать под контролем каждую статью расходов и моментально видеть, где происходят основные затраты.", elem_id="desc1")
212
- with gr.Row():
213
- output_table_1 = gr.Dataframe(headers=["№ локальной сметы", "Наименование", "Рабочие", "Техника", "Материалы", "Перевозки", "Оборудование", "ВСЕГО ПО СМЕТЕ"], elem_id="table1")
214
-
215
- # Расстояние между таблицами
216
- with gr.Row():
217
- gr.Markdown("<br>") # Добавляем расстояние между таблицами
218
-
219
- # Вторая таблица с заголовком и описанием
220
- with gr.Row():
221
- gr.Markdown("<h3 style='text-align: center;'>ПРОЦЕНТНОЕ РАСПРЕДЕЛЕНИЕ ЛОКАЛЬНЫХ СМЕТ</h3>", elem_id="title2")
222
- with gr.Row():
223
- gr.Markdown("Важный инструмент для стратегического планирования, который помогает легко сравнивать между собой разделы проекта, понимая, какие из них требуют большего финансирования, а какие — меньшего. Это позволяет эффективно распределять ресурсы и достигать максимальной отдачи.", elem_id="desc2")
224
- with gr.Row():
225
- output_table_2 = gr.Dataframe(headers=["№ локальной сметы", "Наименование", "Процент от общей суммы"], elem_id="table2")
226
-
227
- # Третья таблица с диаграммой
228
- with gr.Row():
229
- gr.Markdown("<h3 style='text-align: center;'>СТРУКТУРА РАСХОДОВ ПО КАТЕГОРИЯМ</h3>", elem_id="title3")
230
- with gr.Row():
231
- gr.Markdown("<br>") # Добавляем пустое пространство после заголовка
232
- with gr.Row():
233
- output_plot = gr.Image(type="pil", elem_id="plot")
234
-
235
- # Кнопки для скачивания Excel-файлов
236
- with gr.Row():
237
- download_excel_1 = gr.File(label="Скачать детализированный отчет")
238
- download_excel_2 = gr.File(label="Скачать процентное соотношение")
239
-
240
- def update_download_buttons(file):
241
- df_formatted, df_percentage_formatted, pie_chart, detailed_report_path, percentage_report_path = process_pdf(file)
242
- return df_formatted, df_percentage_formatted, Image.open(pie_chart), detailed_report_path, percentage_report_path
243
-
244
- upload_button.change(
245
- update_download_buttons,
246
- inputs=upload_button,
247
- outputs=[output_table_1, output_table_2, output_plot, download_excel_1, download_excel_2]
248
- )
249
-
250
- # Запуск приложения
251
- demo.launch(share=True)
 
1
+ 1