- Switch OCR engine config to google_vision primary / paddleocr fallback
- Mount Auth0 OCR secrets and WIF config into all OCR containers
- Add WIF config to repo (not a secret, contains no credentials)
- Remove obsolete google-vision-key.json.example
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add AUTH0_OCR_CLIENT_ID and AUTH0_OCR_CLIENT_SECRET to inject-secrets.sh
- Add new secrets to staging and production workflow env blocks
- Create .example files for new secret documentation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Create fetch-auth0-token.sh for Auth0 M2M -> GCP WIF token exchange
- Add jq to Dockerfile system dependencies
- Ensure script is executable in container image
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add VISION_MONTHLY_LIMIT config setting (default 1000)
- Update CloudEngine to use WIF credential config via ADC
- Rewrite HybridEngine to support cloud-primary with Redis counter
- Pass monthly_limit through engine factory
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Redis only supports debug|verbose|notice|warning -- not info or error.
The command was using ${LOG_LEVEL:-info} which resolved to INFO in
production (from workflow env), causing Redis to crash loop. Hardcode
the correct Redis-native levels (debug for dev, warning for prod) and
add available log level comments above every container's log setting.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
VIN OCR confidence now reflects recognition accuracy only (not match quality).
Review modal replaces read-only fields with editable cascade dropdowns
pre-populated from NHTSA decode, with NHTSA reference hints for unmatched fields.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The retake button failed because the stream tracks could become inactive
during the crop phase, but handleRetake never re-acquired the camera.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Bridge guidance overlay position to crop tool initial coordinates so the
crop box appears centered matching the viewfinder guide. Increase handle
touch targets to 44px (32px on compact viewports) for mobile usability.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add engine abstraction tests and update docs to reflect PaddleOCR primary
architecture with optional Google Vision cloud fallback.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Three bugs fixed in the draw-first crop tool introduced by PR #114:
1. Stale cropAreaRef: replaced useEffect-based ref sync with direct
synchronous updates in handleMove and handleDrawStart. The useEffect
ran after browser paint, so handleDragEnd read stale values (often
{width:0, height:0}), preventing cropDrawn from being set.
2. Aspect ratio minSize: when aspectRatio=6 (VIN mode), height=width/6
required width>=60% to pass the height>=10% check. Now only checks
width>=minSize when aspect ratio constrains height.
3. Bounds clamping: aspect-ratio-forced height could push crop area
past 100% of container. Now clamps y position to keep within bounds.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace libtesseract-dev with libgomp1 (OpenMP for PaddlePaddle)
- Pre-download PP-OCRv4 models during Docker build
- Add OCR engine env vars to all compose files (base, staging, prod)
- Add optional Google Vision secret mount (commented, enable on demand)
- Create google-vision-key.json.example placeholder
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CloudEngine wraps Google Vision TEXT_DETECTION with lazy init.
HybridEngine runs primary engine, falls back to cloud when confidence
is below threshold. Disabled by default (OCR_FALLBACK_ENGINE=none).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace direct pytesseract calls with OcrEngine interface in vin_extractor.py,
receipt_extractor.py, and ocr_service.py. PSM mode fallbacks replaced with
engine-agnostic single-line/single-word configs. Dead _process_ocr_data removed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Introduce pluggable OcrEngine ABC with PaddleOCR PP-OCRv4 as primary
engine and Tesseract wrapper for backward compatibility. Engine factory
reads OCR_PRIMARY_ENGINE config to instantiate the correct engine.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When OCR reads extra characters (e.g. sticker border as 'C', spurious
'Z' insertion), the raw text exceeds 17 chars and the old first-17
trim produced wrong VINs. New strategy tries all 17-char sliding
windows and single/double character deletions, validating each via
check digit. For 'CWVGGNPE2Z4NP069500', this finds the correct VIN
'WVGGNPE24NP069500' (valid check digit) instead of 'CWVGGNPE2Z4NP0695'
(invalid).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
tessedit_char_whitelist does not work with OEM 1 (LSTM engine) and
causes empty/erratic output. This was the root cause of Tesseract
returning empty text despite clear, well-preprocessed images.
Character filtering is already handled post-OCR by the VIN validator's
correct_ocr_errors() method (I->1, O->0, Q->0, etc).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The min-channel correctly extracts contrast (white text=255 vs green
sticker bg=130), but Tesseract expects dark text on light background.
Without inversion, the grayscale-only path returned empty text for
every PSM mode because Tesseract couldn't see bright-on-dark text.
Invert via bitwise_not: text becomes 0 (black), sticker bg becomes
125 (gray). Fixes all three OCR paths (adaptive, grayscale, Otsu).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two fixes:
1. Always use min-channel for color images instead of gated comparison
that was falling back to standard grayscale (which has only 23%
contrast for white-on-green VIN stickers).
2. Add grayscale-only OCR path (CLAHE + denoise, no thresholding)
between adaptive and Otsu attempts. Tesseract's LSTM engine is
designed to handle grayscale input directly and often outperforms
binarized input where thresholding creates artifacts.
Pipeline order: adaptive threshold → grayscale-only → Otsu threshold
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace std-based channel selection (which incorrectly picked green for
green-tinted VIN stickers) with per-pixel min(B,G,R). White text stays
255 in all channels while colored backgrounds drop to their weakest
channel value, giving 2x contrast improvement. Add morphological
opening after thresholding to remove noise speckles from car body
surface that were confusing Tesseract's page segmentation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
White text on green VIN stickers has only ~12% contrast in standard
grayscale conversion because the green channel dominates luminance.
The new _best_contrast_channel method evaluates each RGB channel's
standard deviation and selects the one with highest contrast, giving
~2x improvement for green-tinted VIN stickers. Falls back to standard
grayscale for neutral-colored images.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Save original, adaptive, and Otsu preprocessed images to
/tmp/vin-debug/{timestamp}/ when LOG_LEVEL is set to debug.
No images saved at info level. Volume mount added for access.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace filesystem-based debug system (VIN_DEBUG_DIR) with standard
logger.debug() calls that flow through Loki when LOG_LEVEL=DEBUG.
Use .env.logging variable for OCR LOG_LEVEL. Increase image capture
quality to 0.95 for better OCR accuracy.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>