Files
roa2web-service-auto/backend/modules/data_entry/services/ocr/profiles/README.md
Claude Agent 099556213d feat(ocr): Add modular store profiles with hot-reload support
## Store Profiles System
- Add ProfileRegistry for CUI-based profile lookup
- Add BaseStoreProfile with generic extraction patterns
- Implement hot-reload via POST /api/data-entry/ocr/profiles/reload

## 12 Store Profiles
- LIDL: Multi-rate TVA (A, B, C, D codes)
- OMV, SOCAR: B2B with client CUI, YYYY.MM.DD dates
- BRICK, DEDEMAN: Standard TVA, e-factura support
- KINETERRA, BEST PRINT: Non-VAT payers (returns [])
- STEPOUT MARKET: TVA 5% (books/reduced rate)
- UNLIMITED KEYS: NUMERAR payment detection
- GAMA INK, ELECTROBERING, PICTUS VELUM: Standard TVA

## Flexible TVA Patterns
- All patterns use (\d{1,2})% to accept any rate
- Supports historical (19%, 9%, 5%) and current (21%, 11%)

## Payment Methods Fix
- Fixed base.py to support multiple payments of same type
- Changed deduplication from method-only to (method, amount) tuple
- Returns separate entries for split payments

## Tools
- Add generate_store_profile.py for automatic profile generation
- Analyzes PDFs via OCR API and detects patterns

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 23:07:07 +00:00

8.4 KiB

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

  1. Hot-reload manual - endpoint /profiles/reload apelat când se modifică fișiere
  2. Persistență în Python - profile în Git, version controlled
  3. Fallback graceful - dacă nu există profil, folosește logica generică
  4. CUI normalization - gestionează automat prefixul "RO" și whitespace
  5. 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