From 00ae084aad9c2bf7ed9d8e239dc0ebd9d1b6b975 Mon Sep 17 00:00:00 2001 From: "go.unhi" Date: Mon, 22 Sep 2025 11:54:41 +0900 Subject: [PATCH] =?UTF-8?q?app/Http/Controllers/RegularContractCreateContr?= =?UTF-8?q?oller.php=20=E3=82=92=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 自動本人確認テストコード削除 --- .../RegularContractCreateController.php | 613 +----------------- 1 file changed, 20 insertions(+), 593 deletions(-) diff --git a/app/Http/Controllers/RegularContractCreateController.php b/app/Http/Controllers/RegularContractCreateController.php index 4611457..f6a408f 100644 --- a/app/Http/Controllers/RegularContractCreateController.php +++ b/app/Http/Controllers/RegularContractCreateController.php @@ -16,7 +16,7 @@ use function redirect; class RegularContractCreateController extends Controller { // 新規作成画面表示 - public function show(Request $request) + public function show() { $user_id = session('user_id'); if (!$user_id) { @@ -111,24 +111,12 @@ class RegularContractCreateController extends Controller ->get() ->groupBy('park_id'); - // ルート名で画面表示を切り替え(新規定期契約 or 駐輪場検索) - $isRegularContract = $request->route()->getName() === 'regular_contract.create'; - - // ヘッダーの選択状態を分岐 - $active_menu = $isRegularContract ? 'SWC-8-1' : 'SWC-10-1'; - - if ($isRegularContract) { - \Log::info('新規定期契約-駐輪場選択画面にアクセス', [ - 'user_id' => $user_id, - ]); - } else { - \Log::info('駐輪場検索-駐輪場選択画面にアクセス', [ - 'user_id' => $user_id, - ]); - } + \Log::info('新規定期契約-駐輪場選択画面にアクセス', [ + 'user_id' => $user_id, + ]); return view('regular_contract.create', [ - 'active_menu' => $active_menu, // この画面のID + 'active_menu' => 'SWC-8-1', // この画面のID 'user_name' => $user ? $user->user_name : '', // ユーザー名(ヘッダー用) 'cities' => $cities, 'stations' => $stations, @@ -140,7 +128,6 @@ class RegularContractCreateController extends Controller 'zones' => $zones, 'city_grace_periods' => $city_grace_periods, 'reserve' => $reserve, - 'isRegularContract' => $isRegularContract ]); } @@ -699,34 +686,7 @@ class RegularContractCreateController extends Controller 'park_id' => $park_id ]); - // 処理結果に基づいて遷移先を決定 - if ($exitCode === 0) { - // 成功の場合 - return redirect("/regular-contract/upload_identity_success?contract_id={$contract_id}"); - // return view('regular_contract.create_confirm', [ - // 'contract_id' => $request->contract_id, - // 'user' => $user, - // 'park' => $park, - // 'psection' => $psection, - // 'usertype' => $usertype, - // 'user_name' => $user->user_name, - // 'active_menu' => 'SWC-8-1' - // ]); - } else { - // 失敗の場合 または、学生証 と その他 の場合 - return redirect("/regular-contract/upload_identity_fail?contract_id={$contract_id}"); - - // return view('regular_contract.create_confirm', [ - // 'contract_id' => $request->contract_id, - // 'user' => $user, - // 'park' => $park, - // 'psection' => $psection, - // 'usertype' => $usertype, - // 'user_name' => $user->user_name, - // 'active_menu' => 'SWC-8-1' - // ]); - - } + } catch (\Exception $e) { \Log::error('SHJ-1バッチ処理でエラー発生', [ @@ -735,8 +695,20 @@ class RegularContractCreateController extends Controller 'park_id' => $park_id ]); - return redirect("/regular-contract/upload_identity_fail?contract_id={$contract_id}"); + } + + // 処理結果に基づいて遷移 + return view('regular_contract.create_confirm', [ + 'contract_id' => $request->contract_id, + 'user' => $user, + 'park' => $park, + 'psection' => $psection, + 'usertype' => $usertype, + 'user_name' => $user->user_name, + 'active_menu' => 'SWC-8-1' + ]); + } public function createConfirmNext($contract_id) @@ -856,553 +828,8 @@ class RegularContractCreateController extends Controller return redirect()->route('wellnet.payment'); } - /** - * SHJ-1本人確認処理成功ページ表示!!!!テスト後に削除して!!!!viewも!!!!! - */ - public function showUploadIdentitySuccess(Request $request) - { - $contractId = $request->get('contract_id'); - $userId = Session::get('user_id'); - - // 詳細情報を取得 - $debugInfo = $this->getShjDebugInfo($userId, $contractId); - - return view('regular_contract.upload_identity_success', compact('debugInfo', 'contractId')); - } - /** - * SHJ-1本人確認処理失敗ページ表示!!!!テスト後に削除して!!!!viewも!!!!! - */ - public function showUploadIdentityFail(Request $request) - { - $contractId = $request->get('contract_id'); - $userId = Session::get('user_id'); - - // 詳細情報を取得 - $debugInfo = $this->getShjDebugInfo($userId, $contractId); - - return view('regular_contract.upload_identity_fail', compact('debugInfo', 'contractId')); - } - /** - * SHJ-1デバッグ情報取得!!!!テスト後に削除して!!!! - */ - private function getShjDebugInfo($userId, $contractId) - { - try { - // ユーザー情報取得 - $user = DB::table('user')->where('user_id', $userId)->first(); - - // 契約情報取得 - $contract = DB::table('regular_contract')->where('user_id', $user->user_id ?? 0)->first(); - - // 駐輪場情報取得 - $park = null; - if ($contract) { - $park = DB::table('park')->where('park_id', $contract->park_id)->first(); - } - - // 最新のSHJ-1バッチログ取得 - $batchLog = DB::table('batch_log') - ->where('process_name', 'SHJ-1本人確認自動処理') - ->orderBy('created_at', 'desc') - ->first(); - - // 最新のログファイルから詳細ログを取得 - $logContent = $this->getRecentShjLogs(); - - // ログからAPI結果情報を解析 - $apiResults = $this->parseApiResultsFromLogs($logContent); - - return [ - 'user' => $user, - 'contract' => $contract, - 'park' => $park, - 'batch_log' => $batchLog, - 'detailed_logs' => $logContent, - 'recent_logs' => $logContent, // 为调试页面提供日志内容访问 - 'timestamp' => now()->format('Y-m-d H:i:s'), - // API結果情報 - 'ocr_text_length' => $apiResults['ocr_text_length'], - 'ocr_text_preview' => $apiResults['ocr_text_preview'], - 'ocr_full_text' => $apiResults['ocr_full_text'], - 'ocr_threshold' => $apiResults['ocr_threshold'], - 'name_similarity' => $apiResults['name_similarity'], - 'address_similarity' => $apiResults['address_similarity'], - 'name_passed' => $apiResults['name_passed'], - 'address_passed' => $apiResults['address_passed'], - 'matched_address_type' => $apiResults['matched_address_type'] ?? null, // 新增:匹配成功的地址类型 - 'name_match_attempts' => $apiResults['name_match_attempts'], - 'address_match_attempts' => $apiResults['address_match_attempts'], - 'best_name_match' => $apiResults['best_name_match'], - 'best_address_match' => $apiResults['best_address_match'], - 'name_match_details' => $apiResults['name_match_details'], - 'address_match_details' => $apiResults['address_match_details'], - 'ocr_debug_info' => $apiResults['ocr_debug_info'] ?? null, - // 移除重复分析逻辑,只显示SHJ-1的结果 - 'calculated_distance' => $apiResults['calculated_distance'], - 'distance_text' => $apiResults['distance_text'] ?? null, - 'distance_limit' => $apiResults['distance_limit'] ?? null, - 'distance_start_address' => $apiResults['distance_start_address'] ?? null, - 'distance_end_address' => $apiResults['distance_end_address'] ?? null, - 'distance_passed' => $apiResults['distance_passed'], - 'maps_api_status' => $apiResults['maps_api_status'], - 'maps_api_error' => $apiResults['maps_api_error'] - ]; - - } catch (Exception $e) { - \Log::error('Debug info error: ' . $e->getMessage()); - return [ - 'error' => 'デバッグ情報の取得に失敗しました: ' . $e->getMessage(), - 'timestamp' => now()->format('Y-m-d H:i:s') - ]; - } - } - - /** - * 最新のSHJ-1関連ログを取得!!!!テスト後に削除して!!!! - */ - private function getRecentShjLogs() - { - try { - $logPath = storage_path('logs/laravel.log'); - if (!file_exists($logPath)) { - return '日志文件未找到'; - } - - $logs = file_get_contents($logPath); - $lines = explode("\n", $logs); - - // 最新の1000行からSHJ-1関連ログを抽出(さらに範囲拡大) - $recentLines = array_slice($lines, -1000); - $shjLogs = []; - - foreach ($recentLines as $line) { - if (strpos($line, 'SHJ-1') !== false || - strpos($line, 'GoogleVision') !== false || - strpos($line, 'Google Maps') !== false || - strpos($line, 'バッチ処理') !== false) { - $shjLogs[] = $line; - } - } - - return implode("\n", array_slice($shjLogs, -200)); // 最新200行(さらに拡大) - - } catch (Exception $e) { - return 'ログ取得エラー: ' . $e->getMessage(); - } - } - - /** - * ログからAPI結果情報を解析!!!!テスト後に削除して!!!! - */ - private function parseApiResultsFromLogs($logContent) - { - $results = [ - 'ocr_text_length' => 'N/A', - 'ocr_text_preview' => 'N/A', - 'ocr_threshold' => 'N/A', - 'name_similarity' => 'N/A', - 'address_similarity' => 'N/A', - 'name_passed' => false, - 'address_passed' => false, - 'calculated_distance' => 'N/A', - 'distance_passed' => false, - 'maps_api_status' => '成功', - 'maps_api_error' => 'なし', - // 新增详细OCR信息 - 'ocr_full_text' => 'N/A', - 'name_match_attempts' => [], - 'address_match_attempts' => [], - 'best_name_match' => 'N/A', - 'best_address_match' => 'N/A', - 'match_details' => 'N/A' - ]; - - try { - // OCR処理完了ログから文字数とプレビューを抽取(清理格式) - if (preg_match('/GoogleVision OCR処理完了.*"text_length":(\d+).*"text_preview":"([^"]*)"/', $logContent, $matches)) { - $results['ocr_text_length'] = $matches[1]; - // 清理OCR文本,移除可能的日志污染 - $cleanText = $matches[2]; - // 移除可能混入的日志时间戳和标记 - $cleanText = preg_replace('/\[\d{4}-\d{2}-\d{2}.*?\].*?local\.INFO.*?$/', '', $cleanText); - // 移除末尾的不完整JSON片段 - $cleanText = preg_replace('/\s*\{?\s*$/', '', $cleanText); - // 清理换行和多余空格 - $cleanText = trim($cleanText); - $results['ocr_text_preview'] = $cleanText; - } - - // 尝试从GoogleVision日志提取完整的OCR文本 - if (preg_match('/GoogleVision OCR処理完了.*"text_full":"([^"]*)"/', $logContent, $matches)) { - $fullText = $matches[1]; - // 解码JSON转义字符 - $fullText = str_replace('\n', "\n", $fullText); - $fullText = str_replace('\r', "\r", $fullText); - $fullText = str_replace('\"', '"', $fullText); - $fullText = str_replace('\\\\', '\\', $fullText); - $results['ocr_full_text'] = $fullText; - } - - // 尝试从SHJ-1日志提取完整的OCR文本(多种模式匹配) - $patterns = [ - '/SHJ-1 完全OCR認識結果.*"ocr_full_text":"([^"]*)"/', // 原始日文版本 - '/螳悟・OCR隱崎ュ倡オ先棡.*"ocr_full_text":"([^"]*)"/', // 日文编码版本 - '/OCR認識結果.*"ocr_full_text":"([^"]*)"/', // 简化匹配 - '/"ocr_full_text":"([^"]*)"/', // 最宽泛匹配 - ]; - - foreach ($patterns as $pattern) { - if (empty($results['ocr_full_text']) && preg_match($pattern, $logContent, $matches)) { - $encodedText = $matches[1]; - // Base64解码 - $fullText = base64_decode($encodedText); - if ($fullText !== false && strlen($fullText) > 10) { - $results['ocr_full_text'] = $fullText; - - // 简化OCR信息显示 - 只显示基本信息 - $results['ocr_debug_info'] = [ - 'decoded_length' => strlen($fullText), - 'preview' => substr($fullText, 0, 200) - ]; - break; // 找到后停止尝试其他模式 - } - } - } - - // 尝试从OCR认识内容详细日志提取(备选方案,适配日文编码) - if (empty($results['ocr_full_text']) && preg_match('/OCR隱崎ュ伜・螳ケ隧ウ邏ー.*"raw_text":"OCR_TEXT_START>([^<]*) $match[1], - 'score' => round(floatval($match[2]), 2), - 'found_in_ocr' => $match[3] - ]; - } - } - $results['name_match_details'] = $nameMatchDetails; - - // 提取住所匹配详细信息(适配日文编码) - $addressMatchDetails = []; - if (preg_match_all('/菴乗園繝槭ャ繝√Φ繧ー隧ウ邏ー.*"target_address":"([^"]*)".*"address_type":"([^"]*)".*"similarity_score":([^,}]*).*"ocr_contains_address":"([^"]*)"/', $logContent, $addressMatches, PREG_SET_ORDER)) { - foreach ($addressMatches as $match) { - $addressMatchDetails[] = [ - 'target' => $match[1], - 'type' => $match[2], - 'score' => round(floatval($match[3]), 2), - 'found_in_ocr' => $match[4] - ]; - } - } - $results['address_match_details'] = $addressMatchDetails; - - // 从新的SHJ-1日志格式中提取信息 - - // 提取OCR抽出结果 - if (preg_match('/SHJ-1 OCR抽出成功.*"extracted_name":"([^"]*)".*"extracted_address":"([^"]*)".*"ocr_value":"([^"]*)"/', $logContent, $matches)) { - $results['extracted_name'] = $matches[1]; - $results['extracted_address'] = $matches[2]; - $results['extracted_ocr_value'] = $matches[3]; - } - - // 提取居住住所照合结果 - if (preg_match('/SHJ-1 居住住所照合.*"resident_address":"([^"]*)".*"similarity":([^,}]*)/', $logContent, $matches)) { - $results['resident_address'] = $matches[1]; - $results['resident_similarity'] = round(floatval($matches[2]), 2); - } - - // 提取関連住所照合结果 - if (preg_match('/SHJ-1 関連住所照合.*"related_address":"([^"]*)".*"similarity":([^,}]*)/', $logContent, $matches)) { - $results['related_address'] = $matches[1]; - $results['related_similarity'] = round(floatval($matches[2]), 2); - } - - // 提取最終OCR結果 - if (preg_match('/SHJ-1 OCR照合(成功|失敗)/', $logContent, $matches)) { - $results['final_ocr_result'] = $matches[1]; - $results['address_passed'] = ($matches[1] === '成功'); - $results['name_passed'] = ($matches[1] === '成功'); // 新SHJ-1逻辑中,成功表示整体成功 - } - - // 如果OCR文本为空或太短,提供说明 - if (empty($results['ocr_text_preview']) || strlen($results['ocr_text_preview']) < 5) { - $results['ocr_text_preview'] = '(OCR認識内容が短いか、表示できない文字が含まれています)'; - } - - // 表面画像処理完了の詳細結果を抽取 - if (preg_match('/SHJ-1 表面画像処理完了.*"front_result":\{"name_matches":\[([^\]]*)\],"address_matches":\[([^\]]*)\]\}/', $logContent, $matches)) { - $nameMatches = explode(',', $matches[1]); - $addressMatches = explode(',', $matches[2]); - - $results['name_match_attempts'] = array_map(function($val) { - return round(floatval($val), 2); - }, $nameMatches); - - $results['address_match_attempts'] = array_map(function($val) { - return round(floatval($val), 2); - }, $addressMatches); - } - - // OCR類似度計算結果の詳細情報を抽取 - if (preg_match('/SHJ-1 OCR類似度計算結果.*"best_name_match":([^,}]*).*"best_address_match":([^,}]*).*"all_name_matches":\[([^\]]*)\].*"all_address_matches":\[([^\]]*)\]/', $logContent, $matches)) { - $results['best_name_match'] = round(floatval($matches[1]), 2); - $results['best_address_match'] = round(floatval($matches[2]), 2); - - $allNameMatches = explode(',', $matches[3]); - $allAddressMatches = explode(',', $matches[4]); - - $results['name_match_attempts'] = array_map(function($val) { - return round(floatval($val), 2); - }, $allNameMatches); - - $results['address_match_attempts'] = array_map(function($val) { - return round(floatval($val), 2); - }, $allAddressMatches); - } - - // OCR閾値チェックログから類似度情報を抽出(新しい順序匹配対応) - if (preg_match('/SHJ-1 OCR閾値チェック.*"threshold":"?([^",}]*)"?.*"name_match":([^,}]*).*"address_match":([^,}]*).*"name_passed":([^,}]*).*"address_passed":([^,}]*).*"matched_address_type":"?([^",}]*)"?/', $logContent, $matches)) { - $results['ocr_threshold'] = $matches[1]; - $results['name_similarity'] = round(floatval($matches[2]), 2); - $results['address_similarity'] = round(floatval($matches[3]), 2); - $results['name_passed'] = $matches[4] === 'true'; - $results['address_passed'] = $matches[5] === 'true'; - $results['matched_address_type'] = $matches[6] ?: null; - } - - // Google Maps API エラーチェック - if (strpos($logContent, 'Google Maps distance calculation error') !== false) { - $results['maps_api_status'] = 'エラー'; - if (preg_match('/Google Maps distance calculation error.*"error":"([^"]*)"/', $logContent, $matches)) { - $results['maps_api_error'] = $matches[1]; - } - } else if (strpos($logContent, 'Distance calculation failed') !== false) { - $results['maps_api_status'] = 'アドレス未発見'; - $results['maps_api_error'] = 'NOT_FOUND'; - } - - // 距離計算結果を抽取(最新の詳細ログから) - if (preg_match_all('/SHJ-1 距離計算完了.*"calculated_distance_meters":(\d+).*"distance_text":"([^"]*)".*"limit_meters":"?([^",}]*)"?.*"within_limit":([^,}]*)/', $logContent, $allMatches, PREG_SET_ORDER)) { - // 最後の(最新の)マッチを使用 - $matches = end($allMatches); - $results['calculated_distance'] = $matches[1]; // 距離メートル - $results['distance_text'] = $matches[2]; // Google Mapsテキスト - $results['distance_limit'] = $matches[3]; // 制限値 - $results['distance_passed'] = $matches[4] === 'true'; - } else { - // ログから具体的な結果が取得できない場合はデフォルト値を設定 - // ※重要:API成功≠距離制限内ではないため、明示的にfalseにする - $results['distance_passed'] = false; - if (strpos($logContent, 'Distance check error') === false && - strpos($logContent, 'Google Maps distance calculation error') === false) { - $results['calculated_distance'] = '計算成功(制限値詳細はログで確認)'; - $results['maps_api_status'] = '成功(但制限確認要)'; - } else { - $results['calculated_distance'] = '計算失敗'; - $results['maps_api_status'] = 'エラー'; - } - } - - // 距離計算開始ログから起点・終点住所を抽取 - if (preg_match('/SHJ-1 距離計算開始.*"user_address":"([^"]*)".*"park_address":"([^"]*)"/', $logContent, $matches)) { - $results['distance_start_address'] = $matches[1]; - $results['distance_end_address'] = $matches[2]; - } - - } catch (Exception $e) { - \Log::error('API結果解析エラー: ' . $e->getMessage()); - } - - return $results; - } - - // 不再需要的分析方法已移除 - 只显示SHJ-1的结果 - /** - * 废弃的方法(已不再使用) - */ - private function manualOcrAnalysis($logContent, $user = null) - { - $analysis = [ - 'found_base64' => false, - 'decoded_success' => false, - 'decoded_text' => '', - 'contains_yamada' => false, - 'contains_taro' => false, - 'contains_tokyo' => false, - 'contains_osaka' => false, - 'full_analysis' => 'Analysis failed', - 'corrected_matching' => null - ]; - - try { - // 分割日志内容为行数组,按时间倒序搜索最新的OCR结果 - $logLines = explode("\n", $logContent); - $logLines = array_reverse($logLines); // 从最新的开始搜索 - - // 查找最新的Base64编码OCR结果 - $patterns = [ - '/SHJ-1 完全OCR認識結果.*"ocr_full_text":"([^"]*)"/', - '/SHJ-1.*OCR.*結果.*"ocr_full_text":"([^"]*)"/', - '/"ocr_full_text":"([^"]*)"/' - ]; - - foreach ($logLines as $line) { - foreach ($patterns as $pattern) { - if (preg_match($pattern, $line, $matches)) { - $analysis['found_base64'] = true; - $base64Text = $matches[1]; - - // Base64解码 - $decodedText = base64_decode($base64Text); - if ($decodedText !== false && strlen($decodedText) > 10) { - $analysis['decoded_success'] = true; - $analysis['decoded_text'] = $decodedText; - - // 内容分析 - $analysis['contains_yamada'] = strpos($decodedText, '山田') !== false; - $analysis['contains_taro'] = strpos($decodedText, '太郎') !== false; - $analysis['contains_tokyo'] = strpos($decodedText, '東京') !== false; - $analysis['contains_osaka'] = strpos($decodedText, '大阪') !== false; - - // ユーザー提案:空白と改行を除去して比較 - $analysis['corrected_matching'] = $this->performCorrectedMatching($decodedText, $user); - - // 詳細分析(実際のユーザーデータから期待値を取得) - if ($user) { - $expectedName = $user->user_name ?? ""; - $expectedAddress = ($user->user_regident_pre ?? '') . - ($user->user_regident_city ?? '') . - ($user->user_regident_add ?? '') ?: ""; - } else { - $expectedName = ""; - $expectedAddress = ""; - } - - $analysis['full_analysis'] = - "OCR認識テキスト長: " . strlen($decodedText) . "文字\n" . - "期待氏名: '$expectedName'\n" . - "期待住所: '$expectedAddress'\n" . - "山田を含む: " . ($analysis['contains_yamada'] ? 'YES' : 'NO') . "\n" . - "太郎を含む: " . ($analysis['contains_taro'] ? 'YES' : 'NO') . "\n" . - "東京を含む: " . ($analysis['contains_tokyo'] ? 'YES' : 'NO') . "\n" . - "大阪を含む: " . ($analysis['contains_osaka'] ? 'YES' : 'NO') . "\n" . - "認識完全内容: " . substr($decodedText, 0, 200) . "..."; - - // 成功解码最新OCR结果后立即返回 - return $analysis; - } - // 解码失败时,继续搜索其他条目 - } - } - } - - } catch (Exception $e) { - $analysis['full_analysis'] = 'OCR分析エラー: ' . $e->getMessage(); - } - - return $analysis; - } - - /** - * 修正マッチングアルゴリズム実行(空白・改行除去) - */ - private function performCorrectedMatching($ocrText, $user = null) - { - // ユーザー情報から期待値を取得、デフォルトはテスト用 - if ($user) { - $expectedName = $user->user_name ?? ""; - $expectedAddress = ($user->user_regident_pre ?? '') . - ($user->user_regident_city ?? '') . - ($user->user_regident_add ?? '') ?: ""; - } else { - $expectedName = ""; - $expectedAddress = ""; - } - - // 統一的テキスト正規化関数 - $normalize = function($text) { - // 全空白文字と改行を除去(全角スペース含む) - $text = preg_replace('/[\s\x{3000}]+/u', '', $text); // \x{3000}は全角スペース - // 一般的な空白文字を明示的に除去 - $text = str_replace([' ', ' ', "\t", "\n", "\r"], '', $text); - // 全角→半角変換 - $text = mb_convert_kana($text, 'rnask', 'UTF-8'); - // 住所統一 - $text = str_replace(['東京市', '東京府'], '東京都', $text); - $text = str_replace(['の'], '', $text); - // 数字統一 - $text = str_replace(['1','2','3','4','5','6','7','8','9','0'], - ['1','2','3','4','5','6','7','8','9','0'], $text); - return $text; - }; - - // 正規化処理 - $normalizedOcr = $normalize($ocrText); - $normalizedExpectedName = $normalize($expectedName); - $normalizedExpectedAddr = $normalize($expectedAddress); - - // 使用"住所"分割OCR文本 - $addressKeyword = '住所'; - $ocrParts = explode($addressKeyword, $normalizedOcr, 2); - - $personalInfoSection = $ocrParts[0] ?? ''; // "住所"前の個人情報欄 - $addressSection = isset($ocrParts[1]) ? $addressKeyword . $ocrParts[1] : ''; // "住所"後の住所欄 - - // 分区マッチング計算 - $nameMatch = $this->calculateSimpleMatch($normalizedExpectedName, $personalInfoSection); - $addrMatch = $this->calculateSimpleMatch($normalizedExpectedAddr, $addressSection); - - return [ - 'original_ocr' => substr($ocrText, 0, 100) . '...', - 'normalized_ocr' => substr($normalizedOcr, 0, 100) . '...', - 'normalized_expected_name' => $normalizedExpectedName, - 'normalized_expected_addr' => $normalizedExpectedAddr, - 'personal_info_section' => substr($personalInfoSection, 0, 80) . '...', - 'address_section' => substr($addressSection, 0, 80) . '...', - 'name_match_score' => $nameMatch, - 'addr_match_score' => $addrMatch, - 'name_passed' => $nameMatch >= 70, - 'addr_passed' => $addrMatch >= 70, - 'overall_result' => ($nameMatch >= 70 && $addrMatch >= 70) ? 'PASS' : 'FAIL' - ]; - } - - /** - * シンプルマッチング計算 - */ - private function calculateSimpleMatch($expected, $haystack) - { - if (empty($expected)) return 0; - - // 1. 完全包含チェック - if (strpos($haystack, $expected) !== false) { - return 100; - } - - // 2. 文字包含率 - $expectedChars = mb_str_split($expected, 1, 'UTF-8'); - $foundChars = 0; - - foreach ($expectedChars as $char) { - if (mb_strpos($haystack, $char, 0, 'UTF-8') !== false) { - $foundChars++; - } - } - - $charRate = ($foundChars / count($expectedChars)) * 100; - - // 3. 類似度計算 - similar_text($expected, $haystack, $similarRate); - - // 最高スコアを返す - return max($charRate, $similarRate); - } + }