def determine_extractor_auto(pdf_text: str) -> type:
    """
    Function determines which extractor to use with this particular text representation of PDF extract

    pdf_text:str: text representation of PDF extract

    returns:
        reference to a calss of a supported extractor
    """
    global extractors_list  # type list[Extractor]

    supported_extractors = [
        extractor for extractor in extractors_list
        if extractor(pdf_text).check_support()
    ]

    if len(supported_extractors) == 0:
        raise exceptions.InputFileStructureError(
            "Неизвecтный формат выписки, ни один из экстракторов не подходят")

    if len(supported_extractors) > 1:
        raise exceptions.InputFileStructureError(
            f"Непонятный формат выписки. Больше чем один экстрактор говорят, что понимают его"
        )

    # If only one supported extractor if found - then all OK
    return supported_extractors[0]
    def get_period_balance(self) -> str:
        """
        Function gets information about transaction balance from the header of the banl extract
        This balance is then returned as a float


        ---------------------------------------------------
        СУММА ПОПОЛНЕНИЙ -> СУММА СПИСАНИЙ -> СУММА СПИСАНИЙ БАНКА
        1 040,00 -> 601,80 -> 437,46
        -------------------------------------------------------
        :param :
        :return:
        """

        res = re.search(
            r'СУММА\sПОПОЛНЕНИЙ\tСУММА\sСПИСАНИЙ\tСУММА\sСПИСАНИЙ БАНКА\n(.*?)\n',
            self.pdf_text, re.MULTILINE)
        if not res:
            pass
            raise exceptions.InputFileStructureError(
                'Не найдена структура с пополнениями и списаниями')

        line_parts = res.group(1).split('\t')

        summa_popolneniy = get_float_from_money(line_parts[0])
        summa_spisaniy = get_float_from_money(line_parts[1])
        summa_spisaniy_banka = get_float_from_money(line_parts[2])

        return summa_popolneniy - summa_spisaniy - summa_spisaniy_banka
Beispiel #3
0
    def split_text_on_entries(self) -> list[str]:
        """
        Function splits the text on individual entries
        If no entries are found, the exceptions.InputFileStructureError() is raised

        """
        # extracting entries (operations) from text file on
        individual_entries = re.findall(
            r"""
            \d\d\.\d\d\.\d\d                                              # Date like '02.06.21' Дата операции 
            \t                                                            # tab    
            \d\d\.\d\d\.\d\d                                              # Date like '02.06.21' Дата обработки
            [\s\S]*?                                                      # any character, including new line. !!None-greedy!!
            (?=\d\d\.\d\d\.\d\d|                                           # Lookahead Start of new transaction
            117997,\sМосква,\sул\.\sВавилова,\sд\.\s19|                    # or till "117997, Москва, ул. Вавилова, д. 19"
             ___EOF)                                                       # or till artificial __EOF
            """, self.pdf_text, re.VERBOSE)

        if len(individual_entries) == 0:
            raise exceptions.InputFileStructureError(
                "Не обнаружена ожидаемая структора данных: не найдено ни одной трасакции"
            )

        # for entry in individual_entries:
        #     print(entry)

        return individual_entries
Beispiel #4
0
    def split_text_on_entries(self) -> list[str]:
        """
        разделяет текстовый файл на отдельные записи

        пример одной записи
        ---------------------------------------------------------------------------------------------------------
        29.08.2019 10:04     GETT     1 189,40     8 087,13
        29.08.2019 / 278484     Отдых и развлечения
        ----------------------------------------------------------------------------------------------------------

        ещё один пример (с 3 линиями)
        ---------------------------------------------------------------------------------------------------------
        26.07.2019 02:04      ПЛАТА ЗА ОБСЛУЖИВАНИЕ БАНКОВСКОЙ     750,00     -750,00
        КАРТЫ  (ЗА ПЕРВЫЙ ГОД)
        05.08.2019 / -     Прочие операции
        ---------------------------------------------------------------------------------------------------------

        """
        # extracting entries (operations) from text file on
        individual_entries = re.findall(
            r"""
        \d\d\.\d\d\.\d\d\d\d\s\d\d:\d\d               # Date and time like 25.04.1991 18:31                                        
        [\s\S]*?                                      # any character, including new line. !!None-greedy!! See URL why [\s\S] is used https://stackoverflow.com/a/33312193
        \d\d\.\d\d\.\d\d\d\d\s/                       # date with forward stash like '25.12.2019 /' 
        .*?\n                                         # everything till end of the line
        """, self.pdf_text, re.VERBOSE)

        if len(individual_entries) == 0:
            raise exceptions.InputFileStructureError(
                "Не обнаружена ожидаемая структора данных: не найдено ни одной трасакции"
            )

        return individual_entries
    def split_text_on_entries(self) -> list[str]:
        """
        Function splits the text on indovidual entries
        If no entries are found, the exceptions.InputFileStructureError() is raised



        разделяет текстовый файл на отдельные записи

        пример одной записи
    ------------------------------------------------------------------------------------------------------
        03.07.2021 12:52 -> Перевод с карты -> 3 500,00
        03.07.2021 123456 -> SBOL перевод 1234****1234 Н. ИГОРЬ РОМАНОВИЧ
    ------------------------------------------------------------------------------------------------------

        либо такой
    --------------------------------------------------------------------------------------------------
        28.06.2021 00:00 -> Неизвестная категория(+) -> +21107,75
        28.06.2021 - -> Прочие выплаты
    ----------------------------------------------------------------------------------------------------

        либо такой с иностранной вылютой
    ---------------------------------------------------------------------------------------------------------
        08.07.2021 18:27 -> Все для дома     193,91
        09.07.2021 254718 -> XXXXX XXXXX -> 2,09 €
    ---------------------------------------------------------------------------------------------------------

        ещё один пример (с 3 линиями)
        ---------------------------------------------------------------------------------------------------------
        03.07.2021 11:54 -> Перевод с карты -> 4 720,00
        03.07.2021 258077 -> SBOL перевод 1234****5678 А. ВАЛЕРИЯ
        ИГОРЕВНА
        ----------------------------------------------------------------------------------------------------------

        """
        # extracting entries (operations) from text file on
        individual_entries = re.findall(
            r"""
            \d\d\.\d\d\.\d\d\d\d\s{1}\d\d:\d\d                             # Date and time like '06.07.2021 15:46'                                        
            .*?\n                                                          # Anything till end of the line including a line break
            \d\d\.\d\d\.\d\d\d\d\s{1}                                      # дата обработки и 1 пробел 
            (?=\d{3,8}|-)                                                  # код авторизации либо "-". Код авторизациии который я видел всегда состоит и 6 цифр, но на всякий случай укажим с 3 до 8
            [\s\S]*?                                                       # any character, including new line. !!None-greedy!!
            (?=Продолжение\sна\sследующей\sстранице|                       # lookahead до "Продолжение на следующей странице"
             \d\d\.\d\d\.\d\d\d\d\s{1}\d\d:\d\d|                           # Либо до начала новой страницы
              Реквизиты\sдля\sперевода)                                    # Либо да конца выписки
            """, self.pdf_text, re.VERBOSE)

        if len(individual_entries) == 0:
            raise exceptions.InputFileStructureError(
                "Не обнаружена ожидаемая структора данных: не найдено ни одной трасакции"
            )

        # for entry in individual_entries:
        #     print(entry)

        return individual_entries
Beispiel #6
0
    def get_period_balance(self) -> str:
        test1 = re.search(r'www.sberbank.ru', self.pdf_text, re.IGNORECASE)
        # print(f"{test1=}")

        if not test1:
            raise exceptions.InputFileStructureError(
                "Не найдены паттерны, соответствующие выписке")

        return 0.0
Beispiel #7
0
    def check_specific_signatures(self):
        """
        Function tries to find some spesific signatures in the text (e.g. sberbank)
        If these signatures are not found, then exceptions.InputFileStructureError() is raised
        """

        test1 = re.search(r'www.sberbank.ru', self.pdf_text, re.IGNORECASE)
        # print(f"{test1=}")

        if not test1:
            raise exceptions.InputFileStructureError(
                "Не найдены паттерны, соответствующие выписке")
Beispiel #8
0
    def check_specific_signatures(self):

        test1 = re.search(r'сбербанк', self.pdf_text, re.IGNORECASE)
        # print(f"{test1=}")

        test2 = re.search(r'Выписка по счёту дебетовой карты', self.pdf_text,
                          re.IGNORECASE)
        # print(f"{test2=}")

        if not test1 or not test2:
            raise exceptions.InputFileStructureError(
                "Не найдены паттерны, соответствующие выписке")
Beispiel #9
0
def sberbankPDF2Excel(input_file_name:str,
                      output_excel_file_name:Union[str, None] =None,
                      format:str= 'auto',
                      leave_intermediate_txt_file:str = False,
                      perform_balance_check = True) ->str:
    """
    function converts pdf or text file with Sperbank extract to Excel format
    input_file_name:
    output_excel_file_name:
    format: str - format of the Sberbank extract. If "auto" then tool tryes to work out the format itself
    leave_intermediate_txt_file: if True, does not delete intermediate txt file
    """

    print(f"{format=}")

    print("*"*30)
    print("Конвертируем файл " + input_file_name)

    path, extension = os.path.splitext(input_file_name)

    extension = extension.lower()

    if extension == ".pdf":
        tmp_txt_file_name = os.path.splitext(input_file_name)[0] + ".txt"

    elif extension == ".txt":
        tmp_txt_file_name = input_file_name

    else:
        raise exceptions.InputFileStructureError("Неподдерживаемое расширение файла: "+ extension)


    if not output_excel_file_name:
        output_excel_file_name = path + ".xlsx"

    try:
        if extension == ".pdf":
            pdf_2_txt_file(input_file_name, tmp_txt_file_name)

        result = sberbankPDFtext2Excel(tmp_txt_file_name,
                                       output_excel_file_name,
                                       format=format,
                                       perform_balance_check = perform_balance_check)

        if (not leave_intermediate_txt_file) and (not extension == ".txt"):
            os.remove(tmp_txt_file_name)

    except:
        raise


    return result
    def decompose_entry_to_dict(self, entry: str) -> dict:
        """
        Function decomposes individual entry text to an information structure in a form of dictionary
        If something unexpected is found, exception exceptions.InputFileStructureError() is raised
        Naming of the dictionary keys is not hard fixed, but shall correspond to what is returned by the function get_columns_info(self)

        All numerical fields shall be returned as float

        All dates / dates and times shall be returned as python datetime.datetime


        Выделяем данные из одной записи в dictionary

    ------------------------------------------------------------------------------------------------------
        03.07.2021 12:52 -> Перевод с карты -> 3 500,00
        03.07.2021 123456 -> SBOL перевод 1234****1234 Н. ИГОРЬ РОМАНОВИЧ
    ------------------------------------------------------------------------------------------------------

        либо такой
    --------------------------------------------------------------------------------------------------
        28.06.2021 00:00 -> Неизвестная категория(+)     +21107,75
        28.06.2021 - -> Прочие выплаты
    ----------------------------------------------------------------------------------------------------

        ещё один пример (с 3 линиями)
        ---------------------------------------------------------------------------------------------------------
        03.07.2021 11:54 -> Перевод с карты -> 4 720,00
        03.07.2021 258077 -> SBOL перевод 1234****5678 А. ВАЛЕРИЯ
        ИГОРЕВНА
        ----------------------------------------------------------------------------------------------------------

        либо такой с иностранной вылютой
    ---------------------------------------------------------------------------------------------------------
        08.07.2021 18:27 -> Все для дома -> 193,91
        09.07.2021 -> 254718 -> XXXXX XXXXX -> 2,09 €
    ---------------------------------------------------------------------------------------------------------

        В последнем примере:

    {'authorisation_code': '254718',
     'category': 'Все для дома',
     'description': 'XXXXX XXXXX',
     'operation_date': datetime.datetime(2021,7,8,18,27),
     'processing_date': datetime.datetime(2021,7,9,0,0),
     'value_account_currency': -193.91б
     'operational_currency': '€'
     }
        """
        lines = entry.split('\n')
        lines = list(filter(None, lines))

        if len(lines) < 2 or len(lines) > 3:
            raise exceptions.InputFileStructureError(
                "entry is expected to have from 2 to 3 lines\n" + str(entry))

        result = {}
        # ************** looking at the 1st line
        line_parts = split_Sberbank_line(lines[0])

        result['operation_date'] = line_parts[0] + " " + line_parts[1]
        # https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior
        result['operation_date'] = datetime.strptime(result['operation_date'],
                                                     '%d.%m.%Y %H:%M')

        result['category'] = line_parts[2]
        result['value_account_currency'] = get_float_from_money(
            line_parts[3], True)
        # result['remainder_account_currency'] = get_float_from_money(line_parts[4])

        # ************** looking at the 2nd line
        line_parts = split_Sberbank_line(lines[1])

        if len(line_parts) != 3 or len(line_parts) > 4:
            raise exceptions.SberbankPDF2ExcelError(
                "Line is expected to have 3 or 4 parts :" + str(lines[1]))

        # print(line_parts[0])

        # processing_date__authorisation_code = re.search(r'(dd\.dd\.dddd)\s(.*)', line_parts[0])
        result['processing_date'] = line_parts[0]
        # https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior
        result['processing_date'] = datetime.strptime(
            result['processing_date'], '%d.%m.%Y')

        result['authorisation_code'] = line_parts[1]
        result['description'] = line_parts[2]

        # Выделяем сумму в валюте оперции, если присуиствует
        if len(line_parts) == 4:
            found = re.search(r'(.*?)\s(\S*)',
                              line_parts[3])  # processing string like '6,79 €'
            if found:
                result['value_operational_currency'] = get_float_from_money(
                    found.group(1), True)
                result['operational_currency'] = found.group(2)
            else:
                raise exceptions.InputFileStructureError(
                    "Ошибка в обработке текста. Ожидалась струтура типа '6,79 €', получено: "
                    + line_parts[3])

        # ************** looking at the 3rd line
        if len(lines) == 3:
            line_parts = split_Sberbank_line(lines[2])
            result['description'] = result['description'] + ' ' + line_parts[0]

        # print(result)

        return result
Beispiel #11
0
    def decompose_entry_to_dict(self, entry: str) -> dict:
        """
        Выделяем данные из одной записи в dictionary

        пример одной записи
        ---------------------------------------------------------------------------------------------------------
        29.08.2019 10:04 -> GETT -> 1 189,40 -> 8 087,13
        29.08.2019 / 278484 -> Отдых и развлечения
        ----------------------------------------------------------------------------------------------------------

        ещё один пример (с 3 линиями)
        ---------------------------------------------------------------------------------------------------------
        26.07.2019 02:04 -> ПЛАТА ЗА ОБСЛУЖИВАНИЕ БАНКОВСКОЙ -> 750,00 -> -750,00
        КАРТЫ  (ЗА ПЕРВЫЙ ГОД)
        05.08.2019 / - -> Прочие операции
        ---------------------------------------------------------------------------------------------------------
        В этом примере:

        result['operation_date'] = '26.07.2019 02:04'
        result['description'] = 'ПЛАТА ЗА ОБСЛУЖИВАНИЕ БАНКОВСКОЙ КАРТЫ  (ЗА ПЕРВЫЙ ГОД)'
        result['value_account_currency'] = -750.00
        result['remainder_account_currency'] = - 750.00
        result['processing_date'] = '05.08.2019'
        result['authorisation_code'] = '-'
        """
        lines = entry.split('\n')
        lines = list(filter(None, lines))

        result = {}
        # ************** looking at the 1st line

        line_parts = split_Sberbank_line(lines[0])

        result['operation_date'] = line_parts[0]
        result['description'] = line_parts[1]
        result['value_account_currency'] = get_float_from_money(
            line_parts[2], True)
        result['remainder_account_currency'] = get_float_from_money(
            line_parts[3])

        # ************* looking at lines between 1st and the last
        sublines = lines[1:-1]
        for line in sublines:
            line_parts = split_Sberbank_line(line)
            if len(line_parts) != 1:
                raise exceptions.SberbankPDF2ExcelError(
                    "Line is expected to have only one part :" + line)
            result['description'] = result['description'] + ' ' + line_parts[0]

        # ************* looking at the last line
        line_parts = split_Sberbank_line(lines[-1])

        if len(line_parts) < 2 or len(line_parts) > 3:
            raise exceptions.SberbankPDF2ExcelError(
                "Line is expected to 2 or parts :" + line)

        result['processing_date'] = line_parts[0][0:10]
        result['authorisation_code'] = line_parts[0][13:]
        result['category'] = line_parts[1]

        if len(line_parts) == 3:
            found = re.search(
                r'[(](.*?)(\w\w\w)[)]',
                line_parts[2])  # processing string like (33,31 EUR)
            if found:
                result['value_operational_currency'] = get_float_from_money(
                    found.group(1), True)
                result['operational_currency'] = found.group(2)
            else:
                raise exceptions.InputFileStructureError(
                    "Ошибка в обработке текста. Ожидалась струтура типа (33,31 EUR), получено: "
                    + line)

        return result
Beispiel #12
0
                "Не найдены паттерны, соответствующие выписке")

    def get_period_balance(self) -> str:
        """
        функция ищет в тексте значения "СУММА ПОПОЛНЕНИЙ" и "СУММА СПИСАНИЙ" и возвращает раницу
        используется для контрольной проверки вычислений

        :param PDF_text:
        :return:
        """

        if (res := re.search(r'СУММА ПОПОЛНЕНИЙ\t(\d[\d\s]*\,\d\d)',
                             self.pdf_text, re.MULTILINE)):
            summa_popolneniy = res.group(1)
        else:
            raise exceptions.InputFileStructureError(
                'Не найдено значение "СУММА ПОПОЛНЕНИЙ"')

        if (res := re.search(r'СУММА СПИСАНИЙ\t(\d[\d\s]*\,\d\d)',
                             self.pdf_text, re.MULTILINE)):
            summa_spisaniy = res.group(1)
        else:
            raise exceptions.InputFileStructureError(
                'Не найдено значение "СУММА СПИСАНИЙ "')

        # print(f"{summa_popolneniy=}")
        # print(f"{summa_spisaniy=}")
        summa_popolneniy = get_float_from_money(summa_popolneniy)
        summa_spisaniy = get_float_from_money(summa_spisaniy)

        return summa_popolneniy - summa_spisaniy