PD-103 — Agent Developer — Module capture-ocr (M3)
1. Identite
| Attribut | Valeur |
| Module | M3 — capture-ocr |
| Agent | Agent Developer (Claude) |
| Story | PD-103 |
| Date | 2026-04-03 |
| Spec version | v3 |
2. Fichiers produits
| Fichier | Type | Lignes |
src/capture/ocr-service.ts | Implementation | ~195 |
src/__tests__/capture/ocr-service.test.ts | Tests contractuels | ~310 |
3. Invariants couverts
| Invariant | Mecanisme | Observable |
| INV-103-04 — OCR local, non-bloquant, sans appel IA externe | Interface OcrEngine locale uniquement (pas de parametre URL/endpoint) ; try/catch absorbant dans extract() | Aucun appel reseau dans les traces ; echec absorbe |
| INV-103-05 — OCR enrichit metadonnees, ne remplace jamais l'image probatoire | OCR retourne OcrResult \| null ; imageBytes jamais mute ; champs OCR optionnels dans payload POST | Tests verifient bytes image identiques pre/post extraction |
4. Criteres d'acceptation couverts
| Critere | Mecanisme | Observable |
| CA-103-05 — OCR desactivable par l'utilisateur | Parametre enabled: boolean dans extract() ; retourne null immediatement si false | TC-NOM-02 : aucun appel moteur quand desactive |
| CA-103-06 — OCR local et non bloquant | try/catch absorbe toute erreur du moteur ; aucun appel reseau | TC-NOM-03, TC-ERR-03 : flux continue sur echec |
5. Matrice de couverture tests contractuels
| Test-ID | Fichier | Description |
| TC-NOM-02 | src/__tests__/capture/ocr-service.test.ts (ligne ~45) | OCR desactive retourne null, 0 appels moteur |
| TC-NOM-03 | src/__tests__/capture/ocr-service.test.ts (ligne ~65) | Erreur Vision absorbee, flux continue |
| TC-ERR-03 | src/__tests__/capture/ocr-service.test.ts (ligne ~95) | Echec OCR force ne bloque pas le flux probatoire |
| TC-NEG-07 | src/__tests__/capture/ocr-service.test.ts (ligne ~221) | Texte > 20000 chars rejete (erreur absorbee) |
6. Tests additionnels (non contractuels)
| Test | Description |
| Flux nominal extraction reussie | OcrResult valide avec text, confidence, language |
| sanitizeOcrText — C0/C1 stripping | Control chars supprimes sauf TAB/LF/CR |
| sanitizeOcrText — NFC normalization | NFD -> NFC |
| clampOcrConfidence | Bornes [0, 1], NaN, Infinity |
| validateOcrLanguage | BCP-47 valide/invalide |
| Language fallback | Tag invalide -> "und" |
| NoOpOcrEngine | Environnement sans Vision natif |
| INV-103-05 integrite image | Bytes inchanges apres extraction reussie et echouee |
7. Couverture
| Metrique | Valeur | Seuil |
| Statements | 97.67% | >= 80% |
| Branches | 90.00% | >= 80% |
| Functions | 100.00% | >= 80% |
| Lines | 97.61% | >= 80% |
| Tests passes | 34/34 | 100% |
8. Architecture
8.1 Interface abstraite OcrEngine
interface OcrEngine {
recognizeText(imageBytes: Uint8Array): Promise<RawOcrResult>;
}
Pattern identique a KekProvider (M2) : interface abstraite permettant : - MockOcrEngine pour les tests (resultat deterministe configurable) - NoOpOcrEngine pour les environnements sans Vision natif (Expo Go, simulateur) - Implementation native Apple Vision a venir (native module React Native)
8.2 OcrService
Service principal consomme par l'orchestrateur (M6) :
OcrService.extract(imageBytes, enabled)
|
+-- enabled=false → null (CA-103-05)
+-- imageBytes vide → null
+-- try {
| engine.recognizeText(imageBytes) → RawOcrResult
| sanitizeOcrText(raw.text) → NFC + strip C0/C1
| clampOcrConfidence(raw.confidence) → [0.0, 1.0]
| validateOcrLanguage(raw.language) → BCP-47 ou fallback
| → OcrResult { ocrText, ocrConfidence, ocrLanguage }
| }
+-- catch → null (INV-103-04, INV-103-05)
8.3 Sanitisation contractuelle (§5.1)
- NFC :
String.prototype.normalize("NFC") — normalisation Unicode - C0 strip :
[\x00-\x08\x0B\x0C\x0E-\x1F] supprime (conserve TAB \x09, LF \x0A, CR \x0D) - C1 strip :
[\x7F-\x9F] supprime - Longueur max : 20000 chars post-sanitisation, rejet si depasse (pas de troncature — §5.1)
9. Decisions architecturales
Decision 1 : Interface abstraite OcrEngine
- Decision : Abstraire le moteur OCR derriere une interface
OcrEngine - Rationale : Apple Vision n'est disponible qu'en environnement natif iOS ; les tests unitaires tournent en Jest/jsdom sans acces au framework natif
- Alternatives considerees : (1) Mock inline du module natif dans jest.config.js, (2) Conditionnel
Platform.OS dans le service - Trade-offs : Indirection supplementaire vs testabilite complete + swap facile de l'implementation native
Decision 2 : Absorption des erreurs dans le service (pas dans l'orchestrateur)
- Decision : Le
try/catch absorbant est dans OcrService.extract(), pas dans l'orchestrateur M6 - Rationale : INV-103-04 et INV-103-05 sont des invariants du module OCR ; la responsabilite de non-blocage appartient au service OCR, pas a l'appelant
- Alternatives considerees : (1) Laisser l'orchestrateur catch, (2) Pattern Result
- Trade-offs : L'orchestrateur ne voit jamais d'erreur OCR, simplifie M6 ; perte de visibilite erreur (acceptable car OCR est optionnel)
10. Hypotheses
| ID | Hypothese | Impact si faux |
| HM3-01 | Apple Vision est accessible via un native module React Native (bridge ou JSI) | L'implementation OcrEngine native devra etre creee comme module natif Expo |
| HM3-02 | recognizeText retourne un texte brut UTF-8 sans necessiter de post-traitement structurel (bounding boxes, etc.) | Adapter RawOcrResult si des coordonnees sont necessaires pour l'UI |
| HM3-03 | La sanitisation NFC + C0/C1 est suffisante pour le texte OCR Vision | Si Vision retourne des caracteres hors plan BMP necessitant un traitement specifique, adapter sanitizeOcrText |
11. Dependances
Consomme par ce module
| Dependance | Source | Usage |
OcrResult, OcrText, OcrLanguage | src/capture/types.ts (M1/T1) | Types branded et interface resultat |
OCR_TEXT_MAX_LENGTH | src/capture/types.ts (M1/T1) | Constante contractuelle 20000 chars |
CAPTURE_ERROR_CODES | src/capture/types.ts (M1/T1) | Code erreur OCR_FAILED |
CAPTURE_VALIDATION_PATTERNS | src/capture/types.ts (M1/T1) | Regex BCP-47 pour validation langue |
Consomme ce module
| Consommateur | Usage |
M6 — capture-orchestrator | Appel OcrService.extract() a l'etape 6 du flux nominal A |
12. Verification qualite
| Critere | Statut |
| TypeScript strict : 0 erreur | OK |
| Coverage >= 80% (lignes + branches) | OK (97.61% / 90%) |
| Tests contractuels TC-NOM-02, TC-NOM-03, TC-ERR-03, TC-NEG-07 | OK (4/4) |
| 0 regression sur tests existants (state-machine) | OK (90/90) |
| Aucun appel reseau dans le module | OK (structurel) |
| Aucune donnee sensible dans les logs | OK (aucun log) |
| INV-103-04, INV-103-05 respectes | OK |