Реализация AI-извлечения данных из счетов и инвойсов

Проектируем и внедряем системы искусственного интеллекта: от прототипа до production-ready решения. Наша команда объединяет экспертизу в машинном обучении, дата-инжиниринге и MLOps, чтобы AI работал не в лаборатории, а в реальном бизнесе.
Показано 1 из 1Все 1566 услуг
Реализация AI-извлечения данных из счетов и инвойсов
Средний
~5 дней
Часто задаваемые вопросы

Направления AI-разработки

Этапы разработки AI-решения

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1288
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1198
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    902
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1123
  • image_logo-advance_0.webp
    Разработка логотипа компании B2B Advance
    590
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    860

AI-извлечение данных из счетов-фактур и инвойсов

Ручная обработка инвойсов — типичная точка боли: бухгалтерия тратит 3–8 минут на каждый документ, ошибки при ручном вводе 1–3%, при объёме 500+ инвойсов в месяц это ощутимо. Document AI-решение снижает время до 5–15 секунд на документ с точностью поля >98% на стандартных форматах.

Архитектура Document AI для инвойсов

Три подхода в порядке сложности и точности:

1. Rule-based + OCR — для фиксированных шаблонов (один поставщик, всегда одинаковый layout). Быстро, дёшево, ломается на любом изменении шаблона.

2. LayoutLM/DocTR — учитывает пространственное расположение текста, работает на вариативных шаблонах.

3. Multimodal LLM (GPT-4V, Claude Vision, Gemini) — понимает произвольные форматы, высокая точность, но стоимость per-document выше.

LayoutLMv3 — производственное решение

from transformers import (
    LayoutLMv3Processor,
    LayoutLMv3ForTokenClassification
)
import torch
from PIL import Image

# Метки для invoice extraction
LABEL_LIST = [
    'O',                    # не поле
    'B-INVOICE_NUMBER',
    'I-INVOICE_NUMBER',
    'B-INVOICE_DATE',
    'I-INVOICE_DATE',
    'B-DUE_DATE',
    'B-VENDOR_NAME',
    'I-VENDOR_NAME',
    'B-VENDOR_ADDRESS',
    'I-VENDOR_ADDRESS',
    'B-TOTAL_AMOUNT',
    'I-TOTAL_AMOUNT',
    'B-TAX_AMOUNT',
    'I-TAX_AMOUNT',
    'B-LINE_ITEM_DESC',
    'I-LINE_ITEM_DESC',
    'B-LINE_ITEM_AMOUNT',
]
LABEL2ID = {l: i for i, l in enumerate(LABEL_LIST)}
ID2LABEL = {i: l for l, i in LABEL2ID.items()}

class InvoiceExtractor:
    def __init__(self, model_path: str):
        self.processor = LayoutLMv3Processor.from_pretrained(
            model_path, apply_ocr=True   # встроенный OCR через Tesseract
        )
        self.model = LayoutLMv3ForTokenClassification.from_pretrained(
            model_path,
            num_labels=len(LABEL_LIST),
            id2label=ID2LABEL,
            label2id=LABEL2ID
        ).eval().cuda()

    @torch.no_grad()
    def extract(self, image_path: str) -> dict:
        image = Image.open(image_path).convert('RGB')
        encoding = self.processor(
            image,
            return_tensors='pt',
            truncation=True,
            max_length=512
        ).to('cuda')

        outputs = self.model(**encoding)
        predictions = outputs.logits.argmax(dim=-1).squeeze().cpu()

        # Декодирование токенов → поля
        tokens = self.processor.tokenizer.convert_ids_to_tokens(
            encoding['input_ids'].squeeze().cpu()
        )
        boxes    = encoding['bbox'].squeeze().cpu().numpy()
        pred_ids = predictions.numpy()

        fields = {}
        current_field = None
        current_tokens = []

        for token, pred_id in zip(tokens, pred_ids):
            if token in ['[CLS]', '[SEP]', '[PAD]']:
                continue
            label = ID2LABEL[pred_id]

            if label.startswith('B-'):
                if current_field and current_tokens:
                    fields[current_field] = self._tokens_to_text(current_tokens)
                current_field = label[2:]
                current_tokens = [token]
            elif label.startswith('I-') and current_field:
                current_tokens.append(token)
            else:
                if current_field and current_tokens:
                    fields[current_field] = self._tokens_to_text(current_tokens)
                current_field = None
                current_tokens = []

        return fields

    def _tokens_to_text(self, tokens: list) -> str:
        text = self.processor.tokenizer.convert_tokens_to_string(tokens)
        return text.strip()

Постобработка и валидация

Сырой вывод модели требует нормализации: суммы с разными разделителями, форматы дат, ИНН/VAT number.

import re
from datetime import datetime
from decimal import Decimal

class InvoiceFieldValidator:
    def validate_and_normalize(self, raw_fields: dict) -> dict:
        validated = {}

        # Сумма: '1.234,56 €' → Decimal('1234.56')
        if 'TOTAL_AMOUNT' in raw_fields:
            validated['total_amount'] = self._parse_amount(
                raw_fields['TOTAL_AMOUNT']
            )

        # Дата: разные форматы → ISO 8601
        if 'INVOICE_DATE' in raw_fields:
            validated['invoice_date'] = self._parse_date(
                raw_fields['INVOICE_DATE']
            )

        # Номер инвойса — минимальная валидация (не пустой, алфанумерик)
        if 'INVOICE_NUMBER' in raw_fields:
            inv_num = re.sub(r'\s+', '', raw_fields['INVOICE_NUMBER'])
            validated['invoice_number'] = inv_num if inv_num else None

        return validated

    def _parse_amount(self, text: str) -> Decimal | None:
        # Убираем валютные символы и пробелы
        cleaned = re.sub(r'[€$£₽\s]', '', text)
        # Нормализуем разделители
        if re.match(r'^\d{1,3}(\.\d{3})*,\d{2}$', cleaned):
            # Европейский формат: 1.234,56
            cleaned = cleaned.replace('.', '').replace(',', '.')
        elif re.match(r'^\d{1,3}(,\d{3})*\.\d{2}$', cleaned):
            # Американский: 1,234.56
            cleaned = cleaned.replace(',', '')
        try:
            return Decimal(cleaned)
        except Exception:
            return None

    def _parse_date(self, text: str) -> str | None:
        formats = ['%d.%m.%Y', '%d/%m/%Y', '%Y-%m-%d',
                   '%d %b %Y', '%B %d, %Y', '%d.%m.%y']
        for fmt in formats:
            try:
                return datetime.strptime(text.strip(), fmt).date().isoformat()
            except ValueError:
                continue
        return None

Точность по типам полей (производственные данные)

Поле LayoutLMv3 GPT-4V Rule-based
Номер инвойса 97.3% 99.1% 99.8%*
Дата 96.8% 98.7% 98.2%*
Итоговая сумма 95.1% 98.4% 96.5%*
Позиции (line items) 88.4% 94.2% 40%*
Адрес поставщика 91.2% 96.8% 72%*

*только для фиксированных шаблонов

Сроки

Задача Срок
Настройка DocTR/AWS Textract для стандартных форматов 1–2 недели
Fine-tuning LayoutLMv3 на корпоративный набор 4–6 недель
Полная система с ERP-интеграцией 6–10 недель