Major fixes to OCR store profiles for Romanian receipt extraction: - Fix ProfileRegistry module path resolution (was loading 0 profiles) - Add multiline TVA extraction for Brick, Electrobering, Gama Ink - Add "CARTE CREDIT" payment detection for OMV/SOCAR gas stations - Handle OCR artifacts: TVA→TUA, "-"→"4", I→L in CUI markers - Add client CUI patterns for Brick receipts - Add profile selection logging to ocr_extractor.py - Create test script for all 29 PDFs (test_all_profiles.py) Test results: 13/29 passing (improved from 9/29) Remaining failures are primarily OCR quality issues. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Store Profiles - OCR Extraction
Sistem de profile specifice pentru extracție OCR cu hot-reload.
Quick Start: Adaugă un profil nou
# 1. Generează profil din PDF-uri (dry-run pentru preview)
python scripts/generate_store_profile.py \
--name "Magazin Nou SRL" \
--cui "12345678" \
--receipts "docs/data-entry/MagazinNou*.pdf" \
--dry-run
# 2. Generează și salvează
python scripts/generate_store_profile.py \
--name "Magazin Nou SRL" \
--cui "12345678" \
--receipts "docs/data-entry/MagazinNou*.pdf" \
--output backend/modules/data_entry/services/ocr/profiles/magazin_nou.py
# 3. Hot-reload (fără restart server)
curl -X POST http://localhost:8000/api/data-entry/ocr/profiles/reload
# 4. Verifică
curl http://localhost:8000/api/data-entry/ocr/profiles
Structura directorului
profiles/
├── __init__.py # ProfileRegistry + hot-reload (~390 linii)
├── base.py # BaseStoreProfile + pattern-uri generice (~410 linii)
├── lidl.py # Multi-rate TVA (A/B)
├── omv.py # B2B, date YYYY.MM.DD
├── socar.py # B2B, date YYYY.MM.DD
├── brick.py # Standard TVA
├── dedeman.py # E-factura support
├── kineterra.py # Non-VAT payer
├── gama_ink.py # Standard TVA (toner/cartușe)
├── electrobering.py # Standard TVA (electronice)
├── pictus_velum.py # Standard TVA (rechizite)
├── unlimited_keys.py # Standard TVA, NUMERAR payment
├── best_print.py # Non-VAT payer (neplătitor TVA)
├── stepout_market.py # TVA 5% (cărți/librărie)
└── README.md # Acest fișier
Profile existente (12 profile)
Note
: Pattern-urile TVA sunt flexibile și acceptă ORICE cotă (5%, 9%, 11%, 19%, 21%, etc.) pentru a gestiona atât datele istorice cât și schimbările viitoare ale legislației.
| Magazin | CUI | Fișier | Caracteristici |
|---|---|---|---|
| LIDL DISCOUNT S.R.L. | 22891860 | lidl.py |
Multi-rate TVA (coduri A, B, C, D) |
| OMV PETROM MARKETING S.R.L. | 11201891 | omv.py |
B2B (client CUI), date YYYY.MM.DD |
| SOCAR PETROLEUM S.A. | 12546600 | socar.py |
B2B (client CUI), date YYYY.MM.DD |
| FIVE-HOLDING S.A. (BRICK) | 10562600 | brick.py |
Standard TVA |
| DEDEMAN SRL | 2816464 | dedeman.py |
E-factura support |
| KINETERRA CONCEPT SRL | 31180432 | kineterra.py |
Non-VAT payer (returnează []) |
| GAMA INK SERVICE SRL | 17741882 | gama_ink.py |
Standard TVA (toner, cartușe) |
| ELECTROBERING S.R.L. | 2744937 | electrobering.py |
Standard TVA (electronice) |
| PICTUS VELUM SRL | 39634534 | pictus_velum.py |
Standard TVA (rechizite) |
| UNLIMITED KEYS S.R.L. | 18993187 | unlimited_keys.py |
Standard TVA, NUMERAR plată |
| BEST PRINT TRADE ACTIV SRL | 45417955 | best_print.py |
Non-VAT payer (neplătitor TVA) |
| STEPOUT MARKET SRL | 35532655 | stepout_market.py |
TVA 5% (cărți, librărie) |
API Endpoints
| Endpoint | Metodă | Descriere |
|---|---|---|
/api/data-entry/ocr/profiles |
GET | Lista toate profilele |
/api/data-entry/ocr/profiles/{cui} |
GET | Detalii profil (acceptă RO prefix) |
/api/data-entry/ocr/profiles/reload |
POST | Hot-reload toate profilele |
Exemple API
# Lista profile
curl http://localhost:8000/api/data-entry/ocr/profiles \
-H "Authorization: Bearer <token>"
# Detalii profil (cu sau fără RO prefix)
curl http://localhost:8000/api/data-entry/ocr/profiles/22891860
curl http://localhost:8000/api/data-entry/ocr/profiles/RO22891860
# Hot-reload după modificări
curl -X POST http://localhost:8000/api/data-entry/ocr/profiles/reload \
-H "Authorization: Bearer <token>"
# Response reload:
{
"success": true,
"reloaded_modules": 12,
"profiles_count": 12,
"registered_cuis": ["22891860", "11201891", "12546600", "10562600", ...],
"last_reload": "2026-01-06T22:37:05.000000"
}
Cum funcționează sistemul
Flow de extracție
ReceiptExtractor.extract()
│
├─► STEP 1: Extrage vendor + CUI
│ └─► _extract_vendor(), _extract_cui()
│
├─► ProfileRegistry.get_profile(cui)
│ └─► Returnează profil specific sau None
│
├─► STEP 2: Extracție cu profil (dacă există)
│ ├─► profile.extract_total()
│ ├─► profile.extract_date()
│ ├─► profile.extract_receipt_number()
│ ├─► profile.extract_tva_entries()
│ ├─► profile.extract_payment_methods()
│ └─► profile.extract_client_cui()
│
└─► STEP 3-4: Validare + post-procesare
Fallback
Dacă nu există profil pentru CUI, se folosește logica generică din ReceiptExtractor.
Structura unui profil
from .base import BaseStoreProfile
from . import ProfileRegistry
@ProfileRegistry.register
class MagazinNouProfile(BaseStoreProfile):
"""Docstring cu descriere magazin."""
CUI_LIST = ["12345678"] # Poate avea mai multe CUI-uri
NAME_PATTERNS = ["MAGAZIN", "MAGAZIN NOU", "MAG4ZIN"] # OCR variants
STORE_NAME = "Magazin Nou SRL"
# Override doar ce e diferit de base class
def extract_tva_entries(self, text: str) -> List[dict]:
# Pattern-uri specifice magazinului
...
def get_validation_hints(self) -> Dict[str, Any]:
return {
"has_multi_rate_tva": False,
"card_equals_total": True,
"has_client_cui": False,
"has_efactura": False,
"is_non_vat_payer": False,
}
Pattern-uri disponibile în base.py
BaseStoreProfile include pattern-uri generice OCR-tolerant:
| Pattern | Descriere |
|---|---|
TOTAL_PATTERNS |
8 variante pentru TOTAL (TOTAL:, TOTAL DE PLATA, etc.) |
DATE_PATTERNS |
6 variante (DD.MM.YYYY, YYYY-MM-DD, DD/MM/YYYY) |
DATE_PATTERNS_OCR_SPACES |
4 variante cu spații OCR ("2025. 08. 14") |
NUMBER_PATTERNS |
11 variante pentru număr bon (NDS, BF, C3POS) |
PAYMENT_PATTERNS |
8 variante pentru CARD/NUMERAR |
CLIENT_MARKERS |
6 variante pentru secțiune CLIENT |
CLIENT_CUI_PATTERNS |
7 variante pentru CUI client |
Metode implementate în base class
extract_total(text)→Tuple[Decimal, float]extract_date(text)→Tuple[date, float]extract_receipt_number(text)→Tuple[str, float]extract_payment_methods(text)→List[dict]extract_client_cui(text)→Tuple[str, float]extract_client_name(text)→Tuple[str, float]
Când ai nevoie de profil custom?
| Situație | Exemplu | Ce trebuie override |
|---|---|---|
| Multi-rate TVA | Lidl (TVA A, TVA B) | extract_tva_entries() |
| Format dată special | OMV/Socar (YYYY.MM.DD) | DATE_PATTERNS_OCR_SPACES |
| B2B receipts | Benzinării (au client CUI) | extract_client_cui() |
| Non-VAT payer | Kineterra | extract_tva_entries() returnează [] |
| E-factura | Dedeman | extract_efactura_reference() |
Decizii de design
- Hot-reload manual - endpoint
/profiles/reloadapelat când se modifică fișiere - Persistență în Python - profile în Git, version controlled
- Fallback graceful - dacă nu există profil, folosește logica generică
- CUI normalization - gestionează automat prefixul "RO" și whitespace
- Deduplicare TVA - folosește
seen = set()pentru a evita duplicate
Comenzi utile
# Verifică syntax Python pentru toate profilele
for f in backend/modules/data_entry/services/ocr/profiles/*.py; do
python3 -m py_compile "$f" && echo "✓ $(basename $f)"
done
# Lista profile
ls -la backend/modules/data_entry/services/ocr/profiles/
# Pornește backend pentru testare
cd backend && source venv/bin/activate
uvicorn main:app --host 0.0.0.0 --port 8000 --workers 1
# Test OCR pe un PDF
curl -X POST -F "file=@docs/data-entry/test.pdf" \
-H "Authorization: Bearer <token>" \
"http://localhost:8000/api/data-entry/ocr/extract?engine=doctr_plus"
Script generare profile
scripts/generate_store_profile.py - generator automat de profile
# Vezi help
python scripts/generate_store_profile.py --help
# Funcționalități:
# - Analizează PDF-uri via OCR API
# - Detectează: TVA format, date format, payment patterns, B2B
# - Generează cod Python cu OCR error variants
# - Suportă glob patterns (*.pdf)
# - Verifică sintaxa după generare