Время прочтения: 4 мин.

Строительный аудит включает в себя независимую оценку экономических показателей выполненных работ, проектной и исходно-разрешительной документации. Основная задача проверяющего – выявить максимальное количество недостатков в кратчайшие сроки.

Нередки случаи, когда на проверку крупного объекта отводится несколько дней. Одним из этапов выявления отклонений и завышений является проверка актов КС2. Для оптимизации этого процесса мы используем инструменты Python.

После получения комплекта документов для проверки объекта (в том числе проекта, смет, журнала работ (форма КС6) и актов на скрытые работы) мы приступаем к проверке соответствия их актам КС2.

Что проверяем:

  • расценки, индексы пересчета в текущие цены, повышающие/понижающие коэффициенты, надбавки;
  • применяемые материалы
  • объемы и стоимость каждой позиции.

Цель – выявление недостатков / отклонений.

При стоимости проекта свыше 50 млн. руб., проверяется от 100 до 200 актов КС2, общим объемом свыше 100 страниц. Большинство файлов при этом является pdf без возможности поиска. Для того, чтобы не запутаться в разнообразии расценок, индексов и квадратных метров мы применяем технологию обработки pdf без возможности поиска (OCR) и на предварительном этапе можем сверить объемы работ в актах КС2 на предмет их соответствия утвержденным заказчиком документам и выявить завышенные расценки на выполняемые работы.

Какие недостатки / отклонения можем выявить:

  • применяемая расценка не должна быть изменена на более дорогую (например: улучшенная окраска на высококачественную);
  • не применены дополнительные (не согласованные) коэффициенты, материалы оплачены по накладным с приложением счетов-фактур по стоимости, не превышающей согласованную в смете;
  • не изменен согласованный на этапе составления сметы материал.
  • оплаченные объемы работ соответствуют проекту, журналу работ, актам на скрытые работы.

Для того, чтобы сверитьобъем интересующих работ по всему объекту за максимально короткий промежуток времени, мы написали скрипт на Python, который с помощью технологии распознавания OCR может получить распознанный текст с изображения документа.

Импортируем библиотеки, необходимые для работы:

import os
from lxml import etree
from datetime import datetime as dt
from datetime import timedelta
import pytz

#Для работы скрипта необходимо установить FineReader версии 14.00 или выше

path_OCR = 'text' # Папка с распознанными файлами
path_files = 'files' # Папка с файлами для распознания
path_user = os.getenv('USERPROFILE') # Считываем путь к папке пользователя
path_HF = '\\AppData\\Local\\ABBYY\\FineReader\\14.00\\HotFolder' # Путь для сохранения файла задач
path_HF_Temp = '\\AppData\\Local\\Temp\\ABBYY\\FineReader\\14.00\\HotFolder' # Путь к временной папке задач
path_from = os.getcwd() # Текущее расположение файлов

with open('пдф_пдф_текст.hft', encoding='utf-16') as task: # Открываем шаблон для генерации нового задания
    text = task.read()
    tree = etree.fromstring(text)

tree.attrib['name'] = 'pdf_to_text' # Переименовываем название задачи
tree.attrib['status'] = 'scheduled'

# Время запуска задания
tree.attrib['startTime'] = (dt.now(pytz.UTC) + timedelta(minutes=1)).strftime('%H:%M:%S.000 %d.%m.%Y UTC') 

# Папка для сохранения файла задач для импорта
for level_1 in tree.getchildren():
    for level_2 in level_1.getchildren():
        if 'batchToCreateFolder' in level_2.keys():
            level_2.attrib['batchToCreateFolder'] = path_user + path_HF_Temp

# Папка для сохранения результатов распознания
for level_1 in tree.getchildren():
    for level_2 in level_1.getchildren():
        for level_3 in level_2.getchildren():
            if 'savePath' in level_3.keys():
                level_3.attrib['savePath'] = path_from + '\\' + path_OCR

# Папка с файлами для распознания
for level_1 in tree.getchildren():
    for level_2 in level_1.getchildren():
        if 'folderPath' in level_2.keys():
            level_2.attrib['folderPath'] = path_from + '\\' + path_files

if not os.path.exists(path_user + path_HF):
    os.makedirs(path_user + path_HF)
    
if not os.path.exists(path_user + path_HF_Temp):
    os.makedirs(path_user + path_HF_Temp)
    
if not os.path.exists(path_from + '\\' + path_OCR):
    os.makedirs(path_from + '\\' + path_OCR)
    
if not os.path.exists(path_from + '\\' + path_files):
    os.makedirs(path_from + '\\' + path_files)

with open(path_user + path_HF + '\\' + 'OCR_HF_task.hft', 'w', encoding='utf-16') as task:
    for_save = etree.tostring(tree, encoding='utf-16', pretty_print=True)
    task.write(for_save.decode('utf-16'))

# Закрываем HotFolder чтобы подгрузить задачу при следующем открытии
os.system("TASKKILL /F /IM \"HotFolder.exe\"");

# Запускаем HotFolder
os.popen(r'C:\Program Files (x86)\ABBYY FineReader 14\HotFolder.exe');

Отбор необходимых актов из общего числа по словам-триггерам:

from os import listdir
import pandas as pd
import re
path = 'txt'

df = pd.DataFrame(columns=['file_name','text'])

keyword = 'ФЕР 15-04-005-05'
keywords=keyword
keywords=re.sub('-',' ',keywords)
keywords=re.sub(r'[ ]{1,}','',keywords)
keywords = re.sub('','[ ]{0,1}',keywords)  

for file in listdir(path):
    if '.txt' in file and 'Hot Folder Log' not in file: 
        with open(path+'\\'+file, 'r', encoding='utf-8') as f:
            x = f.read()

        x=re.sub('-',' ',x)
        x=re.sub(r'[ ]{1,}',' ',x)
     
        a = re.findall(r'(?i)'+keywords+'[\w\W]*?\n',x)

        base=file.split('.')[0]
        for i in range (0, len(a)):
            #сводим название и текст
            q = pd.DataFrame([[base,a[i]]],columns=['file_name','text'])
            #добавляем в общую таблицу
            df = df.append(q, ignore_index=True)        
    else:
        pass

b = df['text'].str.split('\t', expand=True)
df = pd.DataFrame(zip(df['file_name'],*[b[col] for col in b]))

keyword = keyword.replace(':','')
df.to_excel(path+'\\'+keyword+'.xlsx')

Поиск слов-триггеров использует технологию выделения подстроки в строке документа. Если необходимая подстрока встречается в txt документе, то мы берём найденную строку, в которую входит подстрока.

Поскольку алгоритмы HotFolder не всегда точно распознают наличие тире и пробелов, шаблон подразумевает возможность наличия пробелов между любыми двумя символами. На выходе мы получим таблицу преобразования строки с необходимыми для нас данными, включая название документа, из которого получена строка.

Таким образом, после обработки документов инструментами Python мы получим таблицу в формате xlsx.

Для того чтобы сверить данные по оплате кв.м остаётся построить сводные суммы по каждому столбцу.

Для последующей проверки остальной площади применим скрипт поиска подстроки в документе для другого слова-триггера.

Этот метод позволит сверить данные по выполненным работам и в автоматизированном режиме решить проблему распознания текста с изображений pdf документов с уменьшением временных затрат на ручную обработку.