Files
sam-kd/ocr/claude_api.php
hskwon aca1767eb9 초기 커밋: 5130 레거시 시스템
- URL 하드코딩 → .env APP_URL 기반 동적 URL로 변경
- DB 연결 하드코딩 → .env 기반으로 변경
- MySQL strict mode DATE 오류 수정
2025-12-10 20:14:31 +09:00

169 lines
5.0 KiB
PHP

<?php
require_once($_SERVER['DOCUMENT_ROOT'] . "/session.php");
// 권한 체크
if ($level > 5) {
echo json_encode(['ok' => false, 'error' => '접근 권한이 없습니다.']);
exit;
}
// POST 데이터 받기
$input = file_get_contents('php://input');
$data = json_decode($input, true);
if (!isset($data['image'])) {
echo json_encode(['ok' => false, 'error' => '이미지 데이터가 없습니다.']);
exit;
}
$imageBase64 = $data['image'];
$rawText = isset($data['raw_text']) ? $data['raw_text'] : null; // OCR 텍스트는 선택사항
// API 키 읽기
$apiKeyFile = $_SERVER['DOCUMENT_ROOT'] . '/apikey/claude_api.txt';
if (!file_exists($apiKeyFile)) {
echo json_encode(['ok' => false, 'error' => 'API 키 파일이 존재하지 않습니다.']);
exit;
}
$apiKey = trim(file_get_contents($apiKeyFile));
// Claude API 요청
$promptText = "제공된 사업자등록증 이미지를 직접 분석하여 아래 필드를 정확하게 추출해주세요.\n\n";
if ($rawText) {
$promptText .= "참고: OCR 텍스트가 제공되었지만 부정확할 수 있으니, 이미지를 직접 읽어서 정확한 정보를 추출해주세요.\n";
$promptText .= "OCR 텍스트(참고용): {$rawText}\n\n";
}
$promptText .= <<<EOT
추출할 필드:
1. 사업자등록번호 (10자리 숫자, 형식: 000-00-00000)
2. 상호명 (법인명 또는 단체명)
3. 대표자명 (한글 이름)
4. 개업일자 (YYYY-MM-DD 형식)
5. 본점 소재지 (주소)
6. 업태
7. 종목
8. 발급일자 (YYYY-MM-DD 형식)
**중요 지침:**
- 이미지를 직접 읽어서 정확한 텍스트를 추출하세요.
- 사업자등록번호는 정확히 10자리 숫자여야 하며, 하이픈을 포함하여 000-00-00000 형식으로 반환하세요.
- 날짜는 YYYY-MM-DD 형식으로 변환하세요 (예: 2015년 06월 02일 → 2015-06-02).
- 대표자명은 2-4자의 한글 이름이어야 합니다.
- 이미지가 흐리거나 화질이 좋지 않아도 최대한 정확하게 읽어주세요.
- 특수문자나 공백을 정리해주세요.
**응답 형식 (JSON만 반환, 설명 없이):**
{
"biz_no": "123-45-67890",
"company_name": "주식회사 예시",
"representative": "홍길동",
"open_date": "2015-06-02",
"address": "서울특별시 강남구 ...",
"type": "제조업",
"item": "엘리베이터부장품",
"issue_date": "2024-01-15"
}
데이터를 찾을 수 없으면 빈 문자열("")로 반환하세요.
EOT;
// Claude API 호출
$apiUrl = 'https://api.anthropic.com/v1/messages';
// 이미지 데이터 준비 (base64에서 data:image/... 부분 제거)
$imageData = $imageBase64;
if (preg_match('/^data:image\/(\w+);base64,(.+)$/', $imageBase64, $matches)) {
$imageType = $matches[1]; // png, jpeg 등
$imageData = $matches[2]; // 순수 base64
$mediaType = "image/" . $imageType;
} else {
// data: 부분이 없으면 PNG로 가정
$mediaType = "image/png";
}
$requestBody = [
'model' => 'claude-3-haiku-20240307', // haiku 모델 사용 (이미지 직접 분석 가능)
'max_tokens' => 4096,
'messages' => [
[
'role' => 'user',
'content' => [
[
'type' => 'image',
'source' => [
'type' => 'base64',
'media_type' => $mediaType,
'data' => $imageData
]
],
[
'type' => 'text',
'text' => $promptText
]
]
]
]
];
$ch = curl_init($apiUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'x-api-key: ' . $apiKey,
'anthropic-version: 2023-06-01'
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($requestBody));
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200) {
error_log("Claude API Error: HTTP {$httpCode} - {$response}");
echo json_encode([
'ok' => false,
'error' => 'Claude API 호출 실패 (HTTP ' . $httpCode . ')',
'details' => $response
]);
exit;
}
$apiResponse = json_decode($response, true);
if (!isset($apiResponse['content'][0]['text'])) {
echo json_encode(['ok' => false, 'error' => 'Claude API 응답 형식 오류']);
exit;
}
// Claude가 반환한 JSON 파싱
$claudeText = $apiResponse['content'][0]['text'];
// JSON 부분만 추출 (코드블록이나 설명이 포함되어 있을 수 있음)
if (preg_match('/\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}/s', $claudeText, $matches)) {
$jsonText = $matches[0];
} else {
$jsonText = $claudeText;
}
$extractedData = json_decode($jsonText, true);
if (!$extractedData) {
echo json_encode([
'ok' => false,
'error' => 'Claude 응답 JSON 파싱 실패',
'raw_response' => $claudeText
]);
exit;
}
// 성공 응답
echo json_encode([
'ok' => true,
'data' => $extractedData,
'raw_response' => $claudeText
]);