app/Http/Controllers/RegularContractCreateController.php を更新
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 22s

自動本人確認テストコード削除
This commit is contained in:
go.unhi 2025-09-22 11:54:41 +09:00
parent 5ba63ace77
commit 00ae084aad

View File

@ -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>([^<]*)<OCR_TEXT_END"/', $logContent, $matches)) {
$results['ocr_full_text'] = $matches[1];
}
// 提取氏名匹配详细信息(适配日文编码)
$nameMatchDetails = [];
if (preg_match_all('/豌丞錐繝槭ャ繝√Φ繧ー隧ウ邏ー.*"target_name":"([^"]*)".*"similarity_score":([^,}]*).*"ocr_contains_name":"([^"]*)"/', $logContent, $nameMatches, PREG_SET_ORDER)) {
foreach ($nameMatches as $match) {
$nameMatchDetails[] = [
'target' => $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'], $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);
}
}