Certificate manager built on Laravel 13, Livewire 4, and Flux. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
97 lines
2.6 KiB
PHP
97 lines
2.6 KiB
PHP
<?php
|
|
|
|
namespace App\Ocr\Providers;
|
|
|
|
use App\Ocr\OcrProviderInterface;
|
|
use Carbon\CarbonImmutable;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Throwable;
|
|
|
|
/**
|
|
* Klippa OCR — a European (NL) document parsing API.
|
|
*
|
|
* @see https://custom-ocr.klippa.com/docs
|
|
*/
|
|
class KlippaProvider implements OcrProviderInterface
|
|
{
|
|
private const ENDPOINT = 'https://custom-ocr.klippa.com/api/v1/parseDocument';
|
|
|
|
/**
|
|
* @return array{title?: string|null, expires_at?: string|null}
|
|
*/
|
|
public function analyze(string $filePath, ?string $apiKey): array
|
|
{
|
|
if (blank($apiKey) || ! is_readable($filePath)) {
|
|
return [];
|
|
}
|
|
|
|
$contents = file_get_contents($filePath);
|
|
|
|
if ($contents === false) {
|
|
Log::warning('Klippa OCR could not read the document.', ['path' => $filePath]);
|
|
|
|
return [];
|
|
}
|
|
|
|
try {
|
|
$response = Http::withHeaders(['X-Auth-Key' => $apiKey])
|
|
->timeout(30)
|
|
->attach('document', $contents, basename($filePath))
|
|
->post(self::ENDPOINT, ['template' => 'financial_full']);
|
|
|
|
if ($response->failed()) {
|
|
Log::warning('Klippa OCR request failed.', ['status' => $response->status()]);
|
|
|
|
return [];
|
|
}
|
|
|
|
$parsed = $response->json('data.parsed', []);
|
|
|
|
return array_filter([
|
|
'title' => $this->extractTitle($parsed),
|
|
'expires_at' => $this->extractDate($parsed),
|
|
], fn ($value) => $value !== null);
|
|
} catch (Throwable $e) {
|
|
Log::warning('Klippa OCR analysis threw an exception.', ['message' => $e->getMessage()]);
|
|
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $parsed
|
|
*/
|
|
private function extractTitle(array $parsed): ?string
|
|
{
|
|
foreach (['document_subject', 'merchant_name', 'title'] as $key) {
|
|
$value = data_get($parsed, $key);
|
|
|
|
if (filled($value) && is_string($value)) {
|
|
return trim($value);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $parsed
|
|
*/
|
|
private function extractDate(array $parsed): ?string
|
|
{
|
|
foreach (['expiry_date', 'valid_until', 'date'] as $key) {
|
|
$value = data_get($parsed, $key);
|
|
|
|
if (filled($value) && is_string($value)) {
|
|
try {
|
|
return CarbonImmutable::parse($value)->toDateString();
|
|
} catch (Throwable) {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|