Jak zbudować backend OCR
Цей контент ще не доступний вашою мовою.
Ten przewodnik jest dla zespołu, który pisze usługę OCR. Sam kontrakt HTTP opisuje strona Kontrakt API — tutaj chodzi o to, jak go spełnić.
Budujesz usługę zleceń: przyjmujesz plik, natychmiast zwracasz identyfikator, przetwarzasz w tle, a wynik wydajesz przez odpytanie. Sancho nie zna Twojej technologii — liczy się tylko zgodność z dwoma endpointami i schematem JSON.
Przebieg przetwarzania
Section titled “Przebieg przetwarzania”-
Użytkownik wgrywa PDF lub zdjęcie w Sancho. Plik trafia do S3, a w bazie powstaje wpis o statusie
pending. -
Sancho wysyła plik na Twój endpoint przyjęcia (
POST). Odpowiadasz od razu identyfikatorem zlecenia — nie czekasz na zakończenie OCR. -
Twój worker przetwarza dokument w tle.
-
Sancho odpytuje endpoint statusu co
OCR_POLL_INTERVAL_MS(domyślnie 3 s). Dopóki zwracaszpending/processing/queued, odpytywanie trwa. -
Gdy zwrócisz
readywraz zdata, Sancho waliduje wynik schematem, zapisuje go i uruchamia dalsze kroki (podpięcie dokumentu, uzupełnienie formularza). -
error, przekroczenie limitu odpytań albo niezgodność ze schematem kończą się oznaczeniem dokumentu jako błędny. Użytkownik może wgrać plik ponownie — Sancho nie ponawia zlecenia samodzielnie.
Budżet czasowy
Section titled “Budżet czasowy”| Etap | Limit |
|---|---|
Odpowiedź na POST (przyjęcie) |
60 s, ale celuj w kilka sekund |
Odpowiedź na GET (status) |
15 s |
| Całe rozpoznanie | OCR_POLL_INTERVAL_MS × OCR_POLL_MAX_ATTEMPTS, domyślnie 3 minuty |
Domyślne 3000 ms × 60 to dokładnie te 3 minuty. Możesz rozłożyć budżet inaczej (np. 5000 ms × 36), ale nie da się go wydłużyć. Dokumenty wymagające dłuższej obróbki trzeba przetworzyć wstępnie, zanim trafią do Sancho.
Pojedyncza nieudana próba odpytania nie przerywa przetwarzania — Sancho ponawia ją w ramach pozostałego budżetu. Chwilowa awaria sieci nie zepsuje więc całego zlecenia.
Szkic implementacji
Section titled “Szkic implementacji”Minimalna usługa w FastAPI. Trzyma zlecenia w pamięci i nie ma uwierzytelniania — to szkielet pokazujący kształt kontraktu, nie kod produkcyjny.
import uuidfrom fastapi import BackgroundTasks, FastAPI, Form, HTTPException, UploadFile
app = FastAPI()jobs: dict[str, dict] = {}
def recognize(job_id: str, content: bytes, document_type: str) -> None: try: # Tu wchodzi Docling / Twój pipeline. Wynik musi być zgodny ze schematem. jobs[job_id] = {"status": "ready", "data": parse(content, document_type)} except Exception as error: jobs[job_id] = {"status": "error", "message": str(error)}
@app.post("/ocr/documents")async def submit(background: BackgroundTasks, file: UploadFile, documentType: str = Form(...)) -> dict: if documentType not in {"purchase_order", "goods_received_source"}: raise HTTPException(status_code=400, detail="unsupported documentType")
job_id = str(uuid.uuid4()) jobs[job_id] = {"status": "pending"} background.add_task(recognize, job_id, await file.read(), documentType)
return {"id": job_id, "status": "pending"}
@app.get("/ocr/documents/{job_id}")async def status(job_id: str) -> dict: job = jobs.get(job_id) if job is None: raise HTTPException(status_code=404, detail="unknown job")
return {"id": job_id, **job}Do produkcji dołóż trwałe przechowywanie zleceń (restart usługi nie może gubić wyników), kolejkę zamiast BackgroundTasks oraz weryfikację nagłówka Authorization, jeśli ustawisz OCR_CUSTOM_API_KEY.
Wypełnianie schematu
Section titled “Wypełnianie schematu”Pełne definicje: purchase-order.schema.json i goods-received-source.schema.json. Poniżej rzeczy, które najczęściej sprawiają kłopot.
Trzy różne numery na dokumencie źródłowym
Section titled “Trzy różne numery na dokumencie źródłowym”| Pole | Co to jest | Przykład |
|---|---|---|
documentNumber |
Numer samego dokumentu (WZ, faktury, dowodu dostawy) | WZ/0612/24/10/MG |
order |
Wewnętrzny numer zamówienia / zlecenia wystawcy (ZS, ZO, ZAD, „nasz nr dokumentu”) | ZS/1420/2026 |
externalOrder |
Numer zamówienia po stronie nabywcy (Customer PO) | ZAM_W/2025/508 |
Pomylenie ich powoduje, że Sancho nie połączy dostawy z właściwym zamówieniem. Jeśli nie masz pewności, które jest które, zostaw pole puste — brak wartości jest lepszy niż wartość w złym polu.
Liczby
Section titled “Liczby”Wartości muszą być liczbami JSON, nie napisami. Polskie zapisy trzeba znormalizować:
| Na dokumencie | W JSON |
|---|---|
1 234,56 |
1234.56 |
12,000 (ilość) |
12 |
1.234,56 |
1234.56 |
Uwaga na separator tysięcy: spacja zwykła, spacja nierozdzielająca i kropka występują wymiennie.
Format YYYY-MM-DD. 12.06.2026 → 2026-06-12. Jeśli nie umiesz jednoznacznie odczytać daty, zwróć null.
Najlepiej same cyfry (5261040828). Sancho i tak usuwa spacje i myślniki, ale nie rozdziela prefiksu kraju — PL5261040828 zostanie zapisane w całości.
Pozycje
Section titled “Pozycje”items to jedna pozycja na wiersz tabeli. Pola, których nie odczytałeś, po prostu pomiń — pominięta wartość jest traktowana jak null, a pominięta tablica jak []. Nie wstawiaj pustych napisów ani zer zastępczych: 0 w quantity znaczy „ilość zero”, a nie „nie wiem”.
W schemacie goods-received-source pozycje usługowe transportu / wysyłki nie są pozycjami towarowymi — pomiń je w items, a ich wartość netto zwróć w polu deliveryCostNet dokumentu.
Weryfikacja przed wdrożeniem
Section titled “Weryfikacja przed wdrożeniem”-
Wyślij dokument i zapamiętaj identyfikator:
Terminal window curl -sS -X POST https://ocr.firma.local/ocr/documents \-F "file=@zamowienie.pdf" \-F "documentType=purchase_order"Oczekiwane: kod 2xx i
{"id": "...", "status": "pending"}w czasie poniżej kilku sekund. -
Odpytaj o wynik:
Terminal window curl -sS https://ocr.firma.local/ocr/documents/<id>Oczekiwane: najpierw
{"status": "processing"}, potem{"status": "ready", "data": {...}}. -
Sprawdź wynik schematem:
Terminal window curl -sS https://ocr.firma.local/ocr/documents/<id> | jq .data > wynik.jsoncurl -sS https://sancho-wms.pl/schemas/purchase-order.schema.json > schemat.jsoncheck-jsonschema --schemafile schemat.json wynik.json -
Sprawdź nieznane zlecenie —
GET /ocr/documents/nie-istniejemusi zwrócić404, a nie200ze statusempending. -
Sprawdź dokument nieczytelny — Twoja usługa musi zwrócić
{"status": "error", "message": "..."}, a nie wisieć wprocessingw nieskończoność.
Najczęstsze błędy
Section titled “Najczęstsze błędy”datajako napis z JSON-em zamiast obiektu. Sancho nie parsuje napisu drugi raz.- Liczby jako napisy (
"1 234,56"). Walidacja schematu odrzuci taki wynik i dokument zostanie oznaczony błędem. status: "ready"bez poladata. Sancho potraktuje to jako pusty wynik i odrzuci na walidacji.- Blokująca odpowiedź na
POST— trzymanie połączenia do końca OCR. Limit to 60 sekund, a i tak marnuje budżet odpytywania. 404dla zlecenia, które jeszcze się przetwarza. Zwracaj status, nie błąd — inaczej marnujesz budżet na ponawianie.- Zgubienie wyniku po restarcie usługi. Zlecenie w toku, którego stan zniknął, kończy się błędem po stronie Sancho.
- Wartości zastępcze —
0,"brak","-"w polach, których nie odczytano. Pomijaj takie pola.