Certified/app/Ocr/Providers/KlippaProvider.php
Joël van de Wouw ac340d125c
Some checks failed
linter / quality (push) Failing after 13s
tests / ci (8.3) (push) Failing after 2s
tests / ci (8.4) (push) Failing after 2s
tests / ci (8.5) (push) Failing after 3s
Initial commit
Certificate manager built on Laravel 13, Livewire 4, and Flux.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 14:44:58 +02:00

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;
}
}