# ocr-api Full Instructions

## Overview

Patterns and guidelines for Google Cloud Vision API integration for business card OCR functionality.

## API Key Configuration

### Priority Order

API keys are checked in this order:

1. **Environment Variable:** `GOOGLE_VISION_API_KEY`
2. **PHP Constant:** `GOOGLE_VISION_API_KEY`
3. **Fallback:** `GOOGLE_MAPS_API_KEY` (same key used by ShiftOps - includes default fallback)

**Note:** Vision API uses the same API key source as ShiftOps (`v2/api/shiftops.php`, `v2/pages/shiftops.php`) for consistency. If no Vision-specific key is configured, it automatically uses the Google Maps API key.

### Configuration Pattern

```php
// Always use the helper function
$apiKey = ordio_get_google_vision_api_key();

if (empty($apiKey)) {
    throw new Exception('Google Vision API key not configured');
}

// Log key source for debugging (never log full key)
ordio_log('DEBUG', 'API key retrieved', [
    'key_preview' => substr($apiKey, 0, 10) . '...',
    'key_source' => 'Environment Variable', // or 'PHP Constant', 'Fallback'
    'key_length' => strlen($apiKey)
]);
```

### Key Source Detection

Always log which source provided the key:

```php
$keySource = 'Unknown';
$envKey = getenv('GOOGLE_VISION_API_KEY');
if (!empty($envKey) && $apiKey === $envKey) {
    $keySource = 'Environment Variable';
} elseif (defined('GOOGLE_VISION_API_KEY') && $apiKey === GOOGLE_VISION_API_KEY) {
    $keySource = 'PHP Constant';
} elseif ($apiKey === ordio_get_google_maps_api_key()) {
    $keySource = 'Google Maps API Key (Fallback)';
}
```

## API Request Pattern

### Standard Request Structure

```php
$apiUrl = 'https://vision.googleapis.com/v1/images:annotate?key=' . urlencode($apiKey);

$requestData = [
    'requests' => [
        [
            'image' => [
                'content' => $imageBase64 // Base64 encoded image
            ],
            'features' => [
                [
                    'type' => 'DOCUMENT_TEXT_DETECTION' // For structured text
                ]
            ],
            'imageContext' => [
                'languageHints' => ['de', 'en'] // German and English
            ]
        ]
    ]
];

// Make request with proper error handling
$ch = curl_init($apiUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($requestData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);

$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
curl_close($ch);
```

## Error Handling

### HTTP Status Code Handling

```php
if ($httpCode === 200) {
    // Success - process response
} elseif ($httpCode === 400) {
    // Bad Request - invalid image or request format
    $userFriendlyMessage = 'Invalid image format or corrupted image file.';
} elseif ($httpCode === 403) {
    // Forbidden - API not enabled, billing, or permissions
    $errorData = json_decode($response, true);
    $errorMessage = $errorData['error']['message'] ?? 'Unknown error';
    
    // Extract project number from error details
    $projectNumber = null;
    if (isset($errorData['error']['details'])) {
        foreach ($errorData['error']['details'] as $detail) {
            if ($detail['@type'] === 'type.googleapis.com/google.rpc.ErrorInfo') {
                $projectNumber = $detail['metadata']['consumer'] ?? null;
                break;
            }
        }
    }
    
    // Provide specific guidance based on error
    if (stripos($errorMessage, 'has not been used') !== false || 
        stripos($errorMessage, 'disabled') !== false) {
        $activationUrl = $projectNumber 
            ? "https://console.cloud.google.com/apis/api/vision.googleapis.com/overview?project={$projectNumber}"
            : "https://console.cloud.google.com/apis/library/vision.googleapis.com?project=ordio-256916";
        $userFriendlyMessage = "Vision API not enabled. Enable it at: {$activationUrl}";
    } elseif (stripos($errorMessage, 'billing') !== false) {
        $billingUrl = $projectNumber
            ? "https://console.cloud.google.com/billing?project={$projectNumber}"
            : "https://console.cloud.google.com/billing?project=ordio-256916";
        $userFriendlyMessage = "Billing not enabled. Enable it at: {$billingUrl}";
    } else {
        $userFriendlyMessage = 'Invalid API key or insufficient permissions.';
    }
} elseif ($httpCode === 413) {
    // Payload Too Large
    $userFriendlyMessage = 'Image file too large. Maximum size is 4MB after base64 encoding.';
} elseif ($httpCode === 429) {
    // Too Many Requests
    $userFriendlyMessage = 'API quota exceeded. Please try again later.';
} elseif ($httpCode >= 500) {
    // Server errors
    $userFriendlyMessage = 'Vision API server error. Please try again later.';
}
```

### Error Logging Pattern

```php
ordio_log('ERROR', 'Google Vision API error', [
    'http_code' => $httpCode,
    'error_code' => $errorCode,
    'error_message' => $errorMessage,
    'project_number' => $projectNumber, // Extract from error details
    'key_source' => $keySource, // Log which key source was used
    'user_friendly_message' => $userFriendlyMessage,
    'response_preview' => substr($response, 0, 1000), // Never log full response
    'endpoint' => 'ocr-business-card'
]);
```

## Image Validation

### Pre-API Validation

Always validate images before sending to API:

```php
// File size check (3MB max before encoding)
$maxSize = 3 * 1024 * 1024; // 3MB
if ($imageFile['size'] > $maxSize) {
    throw new Exception('File size exceeds limit. Maximum is 3MB.');
}

// Image dimensions check (20 megapixels max)
$imageInfo = @getimagesize($imagePath);
$totalPixels = $imageInfo[0] * $imageInfo[1];
$maxMegapixels = 20000000; // 20MP
if ($totalPixels > $maxMegapixels) {
    throw new Exception("Image dimensions exceed limit. Maximum is 20 megapixels.");
}

// Base64 size check (4MB max)
$imageBase64 = base64_encode($imageContent);
$base64Size = strlen($imageBase64);
$maxBase64Size = 4 * 1024 * 1024; // 4MB
if ($base64Size > $maxBase64Size) {
    throw new Exception("Image too large after encoding. Maximum base64 size is 4MB.");
}
```

## Response Processing

### Extract Text from Response

```php
$result = json_decode($response, true);

if (!isset($result['responses'][0])) {
    throw new Exception('Invalid API response structure');
}

$responseData = $result['responses'][0];

// Check for errors in response
if (isset($responseData['error'])) {
    throw new Exception('Vision API error: ' . $responseData['error']['message']);
}

// Extract text (prefer structured response)
$text = '';
if (isset($responseData['fullTextAnnotation'])) {
    $text = $responseData['fullTextAnnotation']['text'] ?? '';
} elseif (isset($responseData['textAnnotations'][0])) {
    $text = $responseData['textAnnotations'][0]['description'] ?? '';
}
```

## Security Guidelines

### Never Expose API Keys

- ✅ **DO:** Use server-side only
- ✅ **DO:** Log key previews (first 10 chars)
- ❌ **DON'T:** Include keys in JavaScript
- ❌ **DON'T:** Log full API keys
- ❌ **DON'T:** Expose keys in error messages

### API Key Restrictions

Always configure API key restrictions:

1. **API Restrictions:** Restrict to Cloud Vision API only
2. **HTTP Referrer:** Set referrer restrictions for web usage
3. **IP Restrictions:** Set IP restrictions if possible (for server-only usage)

### Error Message Security

Never expose sensitive information in error messages:

```php
// ✅ GOOD: User-friendly message
$userFriendlyMessage = 'OCR processing failed. Please try again.';

// ❌ BAD: Exposes API key
$userFriendlyMessage = "API key {$apiKey} is invalid.";
```

## Testing Patterns

### Unit Test Pattern

```php
// Test API key retrieval
$apiKey = ordio_get_google_vision_api_key();
$this->assertNotEmpty($apiKey);
$this->assertStringStartsWith('AIza', $apiKey);
```

### Integration Test Pattern

```php
// Test with minimal image
$testImageBase64 = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';
$result = processBusinessCardOCR($testImagePath, $imageInfo);
$this->assertIsArray($result);
```

### Diagnostic Script Pattern

Always create diagnostic scripts for troubleshooting:

```php
// Show API key source
echo "API Key Source: " . $keySource . "\n";
echo "API Key Preview: " . substr($apiKey, 0, 20) . "...\n";

// Test API connectivity
$testResult = testVisionAPI($apiKey);
echo "API Status: " . ($testResult['success'] ? 'OK' : 'ERROR') . "\n";
```

## Logging Patterns

### Structured Logging

Always use structured logging:

```php
ordio_log('DEBUG', 'Google Vision API request', [
    'http_code' => $httpCode,
    'response_time_ms' => $duration * 1000,
    'key_source' => $keySource,
    'image_size' => strlen($imageContent),
    'base64_size' => strlen($imageBase64),
    'endpoint' => 'ocr-business-card'
]);
```

### Error Logging

Include all relevant context:

```php
ordio_log('ERROR', 'Google Vision API error', [
    'http_code' => $httpCode,
    'error_code' => $errorCode,
    'error_message' => $errorMessage,
    'project_number' => $projectNumber,
    'key_source' => $keySource,
    'user_friendly_message' => $userFriendlyMessage,
    'response_preview' => substr($response, 0, 1000),
    'endpoint' => 'ocr-business-card'
]);
```

## Performance Considerations

### Timeout Settings

```php
curl_setopt($ch, CURLOPT_TIMEOUT, 30); // 30 seconds total
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); // 10 seconds connection
```

### Image Optimization

- Resize large images before sending
- Use appropriate image quality (JPEG quality 0.9)
- Consider image preprocessing for better OCR accuracy

## Related Files

- `v2/api/ocr-business-card.php` - Main OCR endpoint
- `v2/config/google-vision.php` - API key configuration
- `v2/scripts/ocr-diagnose-api-key.php` - Diagnostic script
- `v2/scripts/test-vision-api-direct.php` - Direct API test
- `v2/admin/ocr-diagnostics.php` - Web diagnostics dashboard
- `docs/systems/ocr/GOOGLE_VISION_API_SETUP.md` - Setup guide

## Best Practices Summary

1. **Always use helper function** `ordio_get_google_vision_api_key()`
2. **Log key source** for debugging
3. **Never log full API keys** - only previews
4. **Extract project numbers** from error responses
5. **Provide actionable error messages** with links
6. **Validate images** before API calls
7. **Handle all HTTP status codes** appropriately
8. **Use structured logging** with context
9. **Test API connectivity** before processing
10. **Monitor API usage** and costs
