# Store Profiles - OCR Extraction Sistem de profile specifice pentru extracție OCR cu hot-reload. --- ## Quick Start: Adaugă un profil nou ```bash # 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 ```bash # Lista profile curl http://localhost:8000/api/data-entry/ocr/profiles \ -H "Authorization: Bearer " # 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 " # 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 ```python 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 ```bash # 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 " \ "http://localhost:8000/api/data-entry/ocr/extract?engine=doctr_plus" ``` --- ## Script generare profile `scripts/generate_store_profile.py` - generator automat de profile ```bash # 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 ```