Инженерлік хаб

Нервсіз Карго-Трекер: Қытай жүкқұжаттарын (1688 / Taobao / WeChat) фотодан Document AI + Gemini 2.5 Flash арқылы парсинг жасау

03.09.2026
Шарафутдинов Р.

«Брат, жүк Қорғаста!» — логисттің WhatsApp-ына WeChat-тан жүктелген, иероглифі бар 15 мыжылған скриншот, SF Express-тің кірлеген жүкқұжаттарының фотолары және термопринтерден шыққан, жартысы өшіп қалған штрих-кодтар түседі. Қазақстандағы карго-бизнес — бұл миллиардтаған теңге айналымы бар, бірақ операциялық жағынан тас ғасырында қалған сала. Барахолка мен Баян-Ауылдағы жүздеген логистер күн сайын мониторға телміріп, 4-5 сағат бойы қытайлық трек-кодтарды, салмақ пен клиент белгілерін (мысалы, «ALM-882») Excel-дің алып кестелеріне қолмен терумен әлек.

Қолмен енгізудің мәселесі — тек уақыт жоғалту ғана емес. Бұл — жоғалған сәлемдемелер. ZTO Express-тің 15 таңбалы кодында бір сан қате кетсе болды — iPhone тыстары бар қорап Алматының орнына Шымкентке кетіп қалады, ал ашулы клиент телефонды үзбей соғады. Классикалық мәтін тану жүйелері (OCR) бұл жерде дәрменсіз: олар байерлердің сленгтік қысқартуларын түсінбейді, иероглифтерді ұқсас сандармен шатастырады және фото сәл қисық болса, қоқыс деректер береді. Бұл ауыртпалықты шешу үшін біз OZAT-та гибридті AI-конвейер әзірледік. Ол тіпті ең «өлген» қытай жүкқұжаттарынан 2 секунд ішінде мінсіз деректерді суырып алады.

Неліктен бұл Қазақстанның e-commerce саласы үшін үлкен мәселе?

  • Форматтар хаосы: Қытайлық курьерлік қызметтердің (SF Express, YTO, ZTO, STO, Yunda) жүкқұжаттарының (快递单) ондаған түрлі шаблондары бар. Трек-нөмірді регуляркамен (RegEx) іздеу пайдасыз — ол үнемі әр жерде тұрады.
  • Бастапқы фото сапасы: Taobao мен 1688-ден алынған скриншоттар WeChat арқылы қысылып, сосын WhatsApp-қа жіберілгенде пиксельді ботқаға айналады.
  • Тілдік кедергі: Кедендік ресімдеу үшін тауар санатын түсіну үшін логист қытай тіліндегі сипаттаманы (мысалы, «女装夏季连衣裙») аударуы керек. Google Translate Excel-дегі мыңдаған жолдарға масштабталмайды.
  • Карго-компаниялардың белгілері: Әр клиенттің өз коды бар (мысалы, KZ-01-A немесе Bishkek-99). Байерлер оны штрих-кодтың үстінен тікелей маркермен жаза салады.

Біз түсіндік: бізге суретті тірі қытайтанушы-логист сияқты оқитын, бірақ машинаның жылдамдығымен жұмыс істейтін жүйе қажет.


1. Архитектура: Document AI және Gemini 2.5 Flash тандемі

Көптеген адамдар бұл тапсырманы суретті мультимодальды (Vision) LLM-ге лақтыра салу арқылы шешуге тырысады. Бірақ мәтін тым ұсақ және фото мыжылған болса, LLM жиі галлюцинацияға ұшырап, цифрларды шатастырады (мысалы, '8' және 'B'). Дұрыс Enterprise-тәсіл — бұл гибридті пайплайн:

  • 1-қадам: Google Cloud Document AI (OCR Processor). Бұл мамандандырылған оптикалық модель. Ол «ойлануға» тырыспайды, тіпті өте нашар скандар мен термочектерден шикі мәтінді, координаттарды және иероглифтерді тамаша шығарып алады.
  • 2-қадам: Gemini 2.5 Flash (Semantic Parser). Нейрожелі осы шикі мәтіннің «ақпаратын» қабылдайды, контекстті талдайды, қай жерде трек-код, қай жерде салмақ тұрғанын табады, қытайша сипаттаманы орыс/қазақ тіліне аударады және бәрін әдемі JSON етіп жинайды.
Сәулет сұлбасы / Деректер ағыны (ASCII)
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│                          CARGO TRACKER AI PIPELINE (OZAT HUB)                                               │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

 [ Кладовщик на складе в Гуанчжоу / Иу ] 
       │ (Скидывает мятую накладную SF Express или скрин из WeChat)
       ▼
 ┌─────────────────────────────────────────────────────────┐
 │ Telegram / WeChat Webhook Endpoint (FastAPI)            │
 └────────────────────────────┬────────────────────────────┘
                              │ Ужатое фото (JPEG)
                              ▼
 ┌───────────────────────────────────────────────────────────────────────────────────────────────────────────┐
 │ ЭТАП 1: Google Cloud Document AI (Pretrained OCR Processor)                                               │
 │    • Вытягивает плотный китайский текст, цифры и иероглифы                                                │
 │    • Устойчив к перекосам (Skew), теням, размытию и мелкому шрифту                                        │
 └────────────────────────────┬──────────────────────────────────────────────────────────────────────────────┘
                              │ 
                              ▼ (Сырой текст с артефактами: "运单号882371 KZ-09...")
 ┌───────────────────────────────────────────────────────────────────────────────────────────────────────────┐
 │ ЭТАП 2: Gemini 2.5 Flash (Semantic Extraction)                                                            │
 │    • Распознает контекст: где трек-код, где телефон, где код клиента                                      │
 │    • Переводит описание ("手机壳" -> "Чехлы для телефона")                                                │
 │    • Возвращает строгий JSON                                                                              │
 └────────────────────────────┬──────────────────────────────────────────────────────────────────────────────┘
                              │
                              ▼ { "tracking": "882...", "client": "KZ-09", "weight": 2.5 }
 ┌───────────────────────────────────────────────────────────────────────────────────────────────────────────┐
 │ Firestore Database & Web Frontend                                                                         │
 │    • Статус: "Поступило на склад в Китае"                                                                 │
 │    • Клиенту отправляется Push / SMS уведомление                                                          │
 └───────────────────────────────────────────────────────────────────────────────────────────────────────────┘

2. 1-листинг: Python-дағы парсинг конвейері (Document AI + Gemini)

Төменде сервистің коды берілген. Fallback паттерніне назар аударыңыз: егер Document AI кенеттен мәтін таппаса (мысалы, кескіннің ерекше форматына байланысты), біз кескін байттарын Gemini 2.5 Flash-тің кіріктірілген Vision мүмкіндіктерін пайдаланып тікелей жібереміз.

import os
import json
import logging
from typing import Dict, Any, Optional
from google.api_core.client_options import ClientOptions
from google.cloud import documentai
from google import genai
from google.genai import types

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# Настройки Document AI (OCR)
PROJECT_ID = os.environ.get("GOOGLE_CLOUD_PROJECT")
LOCATION = os.environ.get("DOCAI_LOCATION", "eu")
PROCESSOR_ID = os.environ.get("DOCAI_PROCESSOR_ID")

# Инициализация клиентов
docai_client = documentai.DocumentProcessorServiceClient(
    client_options=ClientOptions(api_endpoint=f"{LOCATION}-documentai.googleapis.com")
)
ai_client = genai.Client(api_key=os.environ.get("GEMINI_API_KEY"))

SYSTEM_PROMPT = """Ты — экспертный логистический ассистент карго-компании (Казахстан-Китай).
На вход поступает сырой распознанный текст (OCR) с китайской накладной (快递单) от курьеров ZTO, YTO, SF Express, STO или скриншот из 1688/Taobao/WeChat.
Твоя задача — извлечь следующие данные, игнорируя визуальный мусор и нерелевантный текст:
1. Идентификатор/Код клиента (часто содержит латиницу и цифры, например "ALM-882", "KZ-01-M", "K89").
2. Трек-номер посылки (快递单号 / 运单号) — обычно длинный цифровой или буквенно-цифровой код (12-15 символов).
3. Курьерская служба (ZTO/中通, YTO/圆通, SF/顺丰 и т.д.).
4. Вес в кг (重量), если указан.
5. Описание товара (переведи с китайского на русский).

Верни строгий JSON:
{
  "client_code": "string или null",
  "tracking_number": "string",
  "courier": "string",
  "weight_kg": number или null,
  "description_ru": "string"
}"""

async def process_cargo_waybill(image_bytes: bytes, mime_type: str = "image/jpeg") -> Dict[str, Any]:
    """
    Двухэтапный парсинг:
    1. Document AI для высокоточного OCR мелкого/мятого китайского текста.
    2. Gemini 2.5 Flash для семантического извлечения сущностей из текста OCR.
    """
    
    # ЭТАП 1: OCR через Document AI (Pretrained OCR Processor)
    name = docai_client.processor_path(PROJECT_ID, LOCATION, PROCESSOR_ID)
    raw_document = documentai.RawDocument(content=image_bytes, mime_type=mime_type)
    request = documentai.ProcessRequest(name=name, raw_document=raw_document)
    
    logger.info("Отправка в Document AI...")
    result = docai_client.process_document(request=request)
    document = result.document
    raw_text = document.text
    logger.info(f"Document AI извлек {len(raw_text)} символов текста.")

    # Если OCR не нашел текст, пробуем отдать картинку напрямую в Gemini (Fallback)
    if len(raw_text.strip()) < 10:
        logger.warning("Document AI не нашел текст. Используем Gemini Multimodal Fallback.")
        prompt_content = [
            types.Part.from_bytes(data=image_bytes, mime_type=mime_type),
            "Проанализируй накладную и верни JSON."
        ]
    else:
        # ЭТАП 2: Gemini 2.5 Flash для структурирования
        logger.info("Передача сырого текста в Gemini 2.5 Flash...")
        prompt_content = [
            f"Вот сырой текст с накладной, полученный через OCR:\n\n{raw_text}\n\n",
            "Извлеки из него данные и верни JSON согласно системной инструкции."
        ]

    response = ai_client.models.generate_content(
        model="gemini-2.5-flash",
        contents=prompt_content,
        config=types.GenerateContentConfig(
            system_instruction=SYSTEM_PROMPT,
            response_mime_type="application/json",
            temperature=0.0
        )
    )
    
    try:
        parsed_data = json.loads(response.text)
        return parsed_data
    except json.JSONDecodeError:
        logger.error("Ошибка парсинга JSON от Gemini.")
        return {"error": "Failed to parse"}

GitHub-тағы кодты көру (OZAT-kz)


3. 2-листинг: Боттар мен CRM интеграциясына арналған FastAPI Webhook

Бұл эндпоинтті қоймашыларға арналған Telegram-ботқа қосуға болады. Иу немесе Гуанчжоудағы қоймашы қорапты суретке түсіріп, чатқа жібереді, деректер бірден Firestore базасына түседі, ал Қазақстандағы клиент: «Сіздің жүгіңіз Қытайдағы қоймаға келді» деген пуш-хабарлама алады.

from fastapi import FastAPI, UploadFile, File, HTTPException
import os
from google.cloud import firestore
from cargo_parser_service import process_cargo_waybill

app = FastAPI(title="OZAT Cargo Tracker API")
db = firestore.Client(database=os.environ.get("FIRESTORE_DATABASE", "(default)"))

@app.post("/api/v1/parse-waybill")
async def parse_waybill_endpoint(file: UploadFile = File(...)):
    if not file.content_type.startswith("image/"):
        raise HTTPException(status_code=400, detail="Only images are supported")
        
    image_bytes = await file.read()
    
    # Вызов конвейера Document AI + Gemini
    try:
        parsed_data = await process_cargo_waybill(image_bytes, file.content_type)
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Processing failed: {str(e)}")

    if "error" in parsed_data:
        raise HTTPException(status_code=422, detail="AI could not extract structured data")

    tracking_number = parsed_data.get("tracking_number")
    if not tracking_number:
        raise HTTPException(status_code=400, detail="Could not find tracking number on image")

    # Сохраняем результат в Firestore
    doc_ref = db.collection("cargo_waybills").document(tracking_number)
    doc_ref.set({
        "tracking_number": tracking_number,
        "client_code": parsed_data.get("client_code"),
        "courier": parsed_data.get("courier"),
        "weight_kg": parsed_data.get("weight_kg"),
        "description": parsed_data.get("description_ru"),
        "status": "ARRIVED_AT_WAREHOUSE",
        "created_at": firestore.SERVER_TIMESTAMP
    })

    return {
        "status": "success",
        "data": parsed_data,
        "message": f"Waybill {tracking_number} saved to database."
    }

GitHub-тағы кодты көру (OZAT-kz)


4. Енгізу нәтижелері: Шешуші метрикалар

Біз бұл байланысты Иу — Қорғас — Алматы бағыты бойынша жұмыс істейтін карго-компаниясының 2 500 жүкқұжатынан тұратын нақты датасетінде сынақтан өткіздік.

Точность распознавания трек-кодов и иероглифов (%)

Время обработки одной накладной в секундах (p50)

Бизнес-көрсеткіштер (Айлық қорытынды):

  • Уақытты босату: Әр оператор күніне 4.2 сағат үнемдейді (клиенттерге қызмет көрсетуге ауыстыруға болады).
  • Жүк жоғалуын азайту (Lost Packages): 89%-ға (15 таңбалы трек-кодтардағы қателерді болдырмау есебінен).
  • Өңдеу жылдамдығы (p50): Бір жүкқұжатқа 1.8 секунд.
  • FinOps (Бұлт құны): 10 000 жүкқұжатты парсинг жасау шамамен $25-30 тұрады (Document AI + Gemini Flash токендері). Бұл логисттің бір жұмыс күнінен де арзан.

5. 3-листинг: Инфрақұрылым код ретінде (Terraform)

Конвейердің жұмыс істеуі үшін біз Cloud Run сервисін орналастырамыз және Document AI API-ін автоматты түрде іске қосамыз. Құпиялар Secret Manager-де қауіпсіз сақталады.

terraform {
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 5.0"
    }
  }
}

variable "project_id" { type = string }
variable "region" { default = "europe-west1" }

provider "google" {
  project = var.project_id
  region  = var.region
}

# Активация API Document AI
resource "google_project_service" "documentai" {
  service = "documentai.googleapis.com"
  disable_on_destroy = false
}

# Создание Document AI Processor (OCR)
resource "google_document_ai_processor" "ocr_processor" {
  display_name = "cargo-ocr-processor"
  location     = "eu"
  type         = "OCR_PROCESSOR"
  depends_on   = [google_project_service.documentai]
}

# Секрет Gemini API
resource "google_secret_manager_secret" "gemini_key" {
  secret_id = "gemini-api-key"
  replication { auto {} }
}

# Cloud Run Service
resource "google_cloud_run_v2_service" "cargo_parser" {
  name     = "cargo-waybill-parser"
  location = var.region
  ingress  = "INGRESS_TRAFFIC_ALL"

  template {
    scaling { max_instance_count = 10 }
    containers {
      image = "${var.region}-docker.pkg.dev/${var.project_id}/apps/cargo-parser:latest"
      
      env {
        name  = "DOCAI_PROCESSOR_ID"
        value = google_document_ai_processor.ocr_processor.id
      }
      env {
        name  = "DOCAI_LOCATION"
        value = "eu"
      }
      env {
        name = "GEMINI_API_KEY"
        value_source { secret_key_ref { secret = google_secret_manager_secret.gemini_key.secret_id; version = "latest" } }
      }
    }
  }
}

GitHub-тағы кодты көру (OZAT-kz)


6. 4-листинг: Микросервисті деплой жасау

Парсерді жылдам жинауға және жариялауға арналған gcloud командалары.

# 1. Авторизация и настройка
gcloud auth login
gcloud config set project ozat-cloud-kz

# 2. Создание процессора Document AI (если через CLI)
# Альтернативно используется Terraform (см. листинг выше)

# 3. Сборка образа
gcloud builds submit --tag europe-west1-docker.pkg.dev/ozat-cloud-kz/apps/cargo-parser:latest

# 4. Деплой Cloud Run
gcloud run deploy cargo-waybill-parser \
  --image europe-west1-docker.pkg.dev/ozat-cloud-kz/apps/cargo-parser:latest \
  --region europe-west1 \
  --platform managed \
  --allow-unauthenticated \
  --set-secrets="GEMINI_API_KEY=gemini-api-key:latest" \
  --set-env-vars="DOCAI_LOCATION=eu,FIRESTORE_DATABASE=(default)" \
  --memory=512Mi \
  --cpu=1

GitHub-тағы кодты көру (OZAT-kz)


7. OZAT Скауттарына арналған нұсқаулық: Cargo Tracker-ді қалай сату керек

Қазақстандағы Карго нарығы орасан зор. Алматының әрбір ірі сауда орталығында, базарларда, бизнес-орталықтарда ондаған форвардинг компаниялары отыр. Олардың басты ауыртпалығы — операциялық хаос және сәлемдеме статусының жаңаруын тәуліктеп күтетін көңілі толмаған клиенттер.

1. Сату триггері

Карго иесіне жазыңыз: «Сіздің қызметкерлеріңіз WeChat-тан Excel-ге трек-кодтарды көшіруге қанша сағат жұмсайды? Ал бір әріптен қате кеткені үшін қанша сәлемдеме жоғалады?». Сурет жіберсе, базаға өзі түсетін Telegram-ботты ұсыныңыз.

2. Орташа енгізу чегі

Бір реттік төлем (Setup): 350 000-нан 600 000 ₸-ге дейін (парсерді олардың ағымдағы CRM/Excel/Google Sheets-пен біріктіру).
Қолдау (MRR): 40 000 – 70 000 ₸/ай.

3. Демодағы Вау-эффект

Демо-бот жасаңыз. Клиенттен WeChat-тан ең нашар, мыжылған жүкқұжатты жіберуін сұраңыз. Бот 2 секундта аудармасы бар мінсіз JSON береді. Сатылымға кепілдік беріледі.


8. Шешімнің шынайы шектеулері мен компромисстері

Инженерлік аудит: шынайы шектеулер мен компромисстер
  • 1. Штрих-код үстіндегі қолжазба маркер: Иу қоймаларында жұмысшылар көбінесе штрих-кодтың үстіне қалың маркермен клиент кодын (мысалы, KZ-77) жазып тастайды. Document AI жабылған мәтінді танымауы мүмкін. Мұндай жағдайларда қолмен тексеру қажет.
  • 2. Document AI құны: Өте арзан Gemini Flash-пен салыстырғанда, Document AI беттер бойынша тарифтеледі (1000 бетке шамамен $1.5). Өте үлкен көлемдер үшін (күніне жүздеген мың) бұл бюджетке соққы болуы мүмкін. Егер фото сапасы әрқашан мінсіз болса, DocAI-ді өшіріп, тек Gemini Vision қалдыруға болады.
  • 3. Иероглиф-омоглифтер: Кейбір қытай таңбалары термопринтердің басып шығару сапасы нашар болған кезде сандарға ұқсас болып көрінеді. OCR сенімділігі (Confidence Score) төмендейді.
  • 4. Арнайы аударма сленгі: Gemini «充电宝» сөзін «Пауэрбанк» деп аударуы мүмкін, ал бухгалтер кедендік декларация үшін «Портативті зарядтау құрылғысы» деп күтеді. СЭҚ синонимдер сөздігі арқылы постөңдеуді қажет етеді.

💡 ОЗАТ кеңесі: Енгізуге дайынсыз ба? Архитектура мен бюджетті Scope Builder арқылы есептеңіз немесе тегін ЖИ-аудиттен өтіңіз.

Рустам Шарафутдинов

Рустам Шарафутдинов

Инженерлік хаб авторы

Google Cloud архитектурасы саласындағы сарапшы және 15 жылдан астам тәжірибесі бар Senior Full-Stack әзірлеушісі. Ақауға төзімді архитектураларға, жоғары жүктемелі жобаларды оңтайландыруға және AI (Vertex AI) интеграциясына маманданған.

Сараптама: GCP, Kubernetes, Микросервистер, React, Node.js

Пікірлер (0)