shj1 shj2 shj3 改修
All checks were successful
Deploy api / deploy (push) Successful in 21s

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Your Name 2026-01-30 22:23:42 +09:00
parent f139a3f608
commit e3a60540be
3 changed files with 100 additions and 94 deletions

View File

@ -545,9 +545,9 @@ class ShjOneService
/** /**
* SHJ-1文書通りOCRテキストから氏名と住所を抽出 * SHJ-1文書通りOCRテキストから氏名と住所を抽出
* *
* 文書仕様 * 文書仕様2026/01更新)
* 1. "氏名"があるかチェック * 1. "氏名"があるかチェック
* 2. "氏名"の次から年号和暦(昭和|平成)まで [氏名] * 2. "氏名"の次から年号和暦(昭和|平成|令和)まで [氏名]
* 3. "住所"があるかチェック * 3. "住所"があるかチェック
* 4. "住所"の次から和暦(昭和|平成|令和)または"交付"まで [住所] * 4. "住所"の次から和暦(昭和|平成|令和)または"交付"まで [住所]
* 5. [OCR値] = [氏名] + [住所] * 5. [OCR値] = [氏名] + [住所]
@ -568,8 +568,8 @@ class ShjOneService
]; ];
} }
// 2. 氏名抽出:「氏名」の次から「住所」まで // 2. 氏名抽出:「氏名」の次から年号和暦(昭和|平成|令和)まで
$namePattern = '/氏名([^住]*?)住所/u'; $namePattern = '/氏名(.+?)(?:昭和|平成|令和)/u';
if (!preg_match($namePattern, $cleanedOcr, $nameMatches)) { if (!preg_match($namePattern, $cleanedOcr, $nameMatches)) {
return [ return [
'success' => false, 'success' => false,
@ -632,9 +632,9 @@ class ShjOneService
/** /**
* SHJ-1文書通り:[OCR值][利用者住所]の照合処理 * SHJ-1文書通り:[OCR值][利用者住所]の照合処理
* *
* 文書仕様 * 文書仕様2026/01更新)
* 1. [利用者居住所][OCR値]を照合 閾値以下なら次へ * - [利用者居住所][OCR値]を照合 閾値(75%)以上なら成功、未満なら失敗
* 2. [利用者関連住所][OCR値]を照合 閾値以下ならOCR照合失敗 * - ※関連住所の照合は削除(運転免許証には居住所のみ記載のため)
*/ */
private function performOcrMatching(User $user, string $ocrValue): array private function performOcrMatching(User $user, string $ocrValue): array
{ {
@ -643,7 +643,7 @@ class ShjOneService
// [利用者居住所]を構築 // [利用者居住所]を構築
$residentAddress = $user->user_regident_pre . $user->user_regident_city . $user->user_regident_add; $residentAddress = $user->user_regident_pre . $user->user_regident_city . $user->user_regident_add;
// 1. [利用者居住所]と[OCR値]を照合 // [利用者居住所]と[OCR値]を照合
similar_text($residentAddress, $ocrValue, $residentSimilarity); similar_text($residentAddress, $ocrValue, $residentSimilarity);
Log::info('SHJ-1 居住住所照合', [ Log::info('SHJ-1 居住住所照合', [
@ -671,43 +671,10 @@ class ShjOneService
]; ];
} }
// 2. [利用者関連住所]と[OCR値]を照合 // 閾値未満OCR照合失敗
$relatedAddress = $user->user_relate_pre . $user->user_relate_city . $user->user_relate_add;
if (!empty(trim($relatedAddress))) {
similar_text($relatedAddress, $ocrValue, $relatedSimilarity);
Log::info('SHJ-1 関連住所照合', [
'user_seq' => $user->user_seq,
'related_address' => $relatedAddress,
'ocr_value' => $ocrValue,
'similarity' => $relatedSimilarity,
'threshold' => $threshold
]);
if ($relatedSimilarity >= $threshold) {
// 関連住所で照合成功
Log::info('SHJ-1 OCR照合成功関連住所', [
'user_seq' => $user->user_seq,
'matched_address_type' => '関連住所',
'similarity' => $relatedSimilarity
]);
return [
'success' => true,
'matched_address_type' => '関連住所',
'matched_address' => $relatedAddress,
'similarity' => $relatedSimilarity,
'reason' => 'related_address_matched'
];
}
}
// 両方とも閾值以下OCR照合失敗
Log::info('SHJ-1 OCR照合失敗', [ Log::info('SHJ-1 OCR照合失敗', [
'user_seq' => $user->user_seq, 'user_seq' => $user->user_seq,
'resident_similarity' => $residentSimilarity, 'resident_similarity' => $residentSimilarity,
'related_similarity' => $relatedAddress ? ($relatedSimilarity ?? 0) : 'N/A',
'threshold' => $threshold 'threshold' => $threshold
]); ]);
@ -715,13 +682,18 @@ class ShjOneService
'success' => false, 'success' => false,
'matched_address_type' => null, 'matched_address_type' => null,
'matched_address' => null, 'matched_address' => null,
'similarity' => max($residentSimilarity, $relatedSimilarity ?? 0), 'similarity' => $residentSimilarity,
'reason' => 'threshold_not_met' 'reason' => 'threshold_not_met'
]; ];
} }
/** /**
* 距離チェック処理設計書通りpark.distance_between_two_pointsを使用 * 距離チェック処理2026/01更新:近傍駅座標を使用)
*
* 文書仕様:
* - 近傍駅マスタ(station)から座標を取得
* - 近傍駅~自宅間の直線距離を計算
* - 駐輪場マスタ.二点間距離と比較
*/ */
private function performDistanceCheck(User $user, Park $park): array private function performDistanceCheck(User $user, Park $park): array
{ {
@ -729,35 +701,71 @@ class ShjOneService
// ユーザー住所を構築 // ユーザー住所を構築
$userAddress = $user->user_regident_pre . $user->user_regident_city . $user->user_regident_add; $userAddress = $user->user_regident_pre . $user->user_regident_city . $user->user_regident_add;
// 駐輪場住所 // 近傍駅座標を取得LIMIT 1で最初の1件
$parkAddress = $park->park_adrs; $station = DB::table('station')
->where('park_id', $park->park_id)
->first();
Log::info('SHJ-1 距離計算開始', [ // 近傍駅データがない、または座標がNULLの場合
if (!$station || empty($station->station_latitude) || empty($station->station_longitude)) {
Log::warning('SHJ-1 近傍駅座標データなし', [
'user_seq' => $user->user_seq,
'park_id' => $park->park_id,
'station_exists' => !empty($station),
'has_latitude' => !empty($station->station_latitude ?? null),
'has_longitude' => !empty($station->station_longitude ?? null)
]);
// オペレーターキューに追加するためエラーとして返す
return [
'within_limit' => false,
'distance_meters' => 999999,
'limit_meters' => $park->distance_between_two_points ?? config('shj1.distance.default_limit_meters'),
'error' => '近傍駅の座標データが見つかりません',
'distance_detail' => $park->park_id . '/' . $park->park_id . '/近傍駅座標データなし',
'station_coordinate_missing' => true
];
}
$stationLat = (float) $station->station_latitude;
$stationLng = (float) $station->station_longitude;
$stationName = $station->station_neighbor_station ?? '不明';
Log::info('SHJ-1 距離計算開始(近傍駅ベース)', [
'user_seq' => $user->user_seq, 'user_seq' => $user->user_seq,
'park_id' => $park->park_id, 'park_id' => $park->park_id,
'user_address' => $userAddress, 'user_address' => $userAddress,
'park_address' => $parkAddress 'station_name' => $stationName,
'station_lat' => $stationLat,
'station_lng' => $stationLng
]); ]);
// Google Maps APIで距離計算 // Google Maps Geocoding APIでユーザー住所から座標を取得
$distanceResult = $this->googleMapsService->calculateDistance($userAddress, $parkAddress); $userCoords = $this->googleMapsService->geocodeAddress($userAddress);
$distanceMeters = $distanceResult['distance_meters'];
// 駐輪場の二点間距離制限を取得(設計書の要求) if (!$userCoords || !isset($userCoords['lat']) || !isset($userCoords['lng'])) {
throw new Exception('ユーザー住所の座標取得に失敗しました');
}
// Haversine公式で2点間の直線距離を計算
$distanceMeters = $this->googleMapsService->calculateStraightLineDistance(
$stationLat,
$stationLng,
$userCoords['lat'],
$userCoords['lng']
);
// 駐輪場の二点間距離制限を取得
$limitMeters = $park->distance_between_two_points ?? config('shj1.distance.default_limit_meters'); $limitMeters = $park->distance_between_two_points ?? config('shj1.distance.default_limit_meters');
$withinLimit = $distanceMeters <= $limitMeters; $withinLimit = $distanceMeters <= $limitMeters;
$distanceDetail = $this->googleMapsService->generateDistanceDetailString( $distanceDetail = $park->park_id . '/' . $stationName . '/二点間距離:' . round($distanceMeters) . 'm';
$park->park_id,
$distanceMeters,
'google_maps'
);
Log::info('SHJ-1 距離計算完了', [ Log::info('SHJ-1 距離計算完了(近傍駅ベース)', [
'user_seq' => $user->user_seq, 'user_seq' => $user->user_seq,
'park_id' => $park->park_id, 'park_id' => $park->park_id,
'calculated_distance_meters' => $distanceMeters, 'station_name' => $stationName,
'distance_text' => $distanceResult['distance_text'] ?? null, 'calculated_distance_meters' => round($distanceMeters),
'limit_meters' => $limitMeters, 'limit_meters' => $limitMeters,
'within_limit' => $withinLimit, 'within_limit' => $withinLimit,
'distance_detail' => $distanceDetail 'distance_detail' => $distanceDetail
@ -765,11 +773,11 @@ class ShjOneService
return [ return [
'within_limit' => $withinLimit, 'within_limit' => $withinLimit,
'distance_meters' => $distanceMeters, 'distance_meters' => round($distanceMeters),
'limit_meters' => $limitMeters, 'limit_meters' => $limitMeters,
'distance_detail' => $distanceDetail, 'distance_detail' => $distanceDetail,
'user_address' => $userAddress, 'user_address' => $userAddress,
'park_address' => $parkAddress 'station_name' => $stationName
]; ];
} catch (Exception $e) { } catch (Exception $e) {
@ -779,13 +787,13 @@ class ShjOneService
'error' => $e->getMessage() 'error' => $e->getMessage()
]); ]);
// API失敗時は距離NGとして処理(設計書の要求) // API失敗時は距離NGとして処理
return [ return [
'within_limit' => false, 'within_limit' => false,
'distance_meters' => 999999, 'distance_meters' => 999999,
'limit_meters' => $park->distance_between_two_points ?? config('shj1.distance.default_limit_meters'), 'limit_meters' => $park->distance_between_two_points ?? config('shj1.distance.default_limit_meters'),
'error' => $e->getMessage(), 'error' => $e->getMessage(),
'distance_detail' => $park->park_id . "/" . $park->park_id . "/API Error: " . $e->getMessage() 'distance_detail' => $park->park_id . '/' . $park->park_id . '/API Error: ' . $e->getMessage()
]; ];
} }
} }

View File

@ -416,9 +416,8 @@ class ShjThreeService
$elapsedDays = $todayDay - $startDay; $elapsedDays = $todayDay - $startDay;
} elseif ($endDay >= $todayDay) { } elseif ($endDay >= $todayDay) {
// 駐輪場マスタ.更新期間終了日 >= [本日の日付]の日 の場合 // 駐輪場マスタ.更新期間終了日 >= [本日の日付]の日 の場合
// 仕様書の計算式: ([先月の月末日]の日 駐輪場マスタ.更新期間開始日) [本日の日付]の日 // 対象外翌月1日以降の猶予期間はメール送信しない
$lastMonthEnd = $now->copy()->subMonth()->endOfMonth()->day; $elapsedDays = 99;
$elapsedDays = ($lastMonthEnd - $startDay) + $todayDay;
} else { } else {
// その他の場合 // その他の場合
$elapsedDays = 99; // 対象外 $elapsedDays = 99; // 対象外
@ -548,10 +547,9 @@ class ShjThreeService
if ($startDay <= $todayDay) { if ($startDay <= $todayDay) {
// 処理0.更新期間開始日 <= [本日の日付]の日 の場合 // 処理0.更新期間開始日 <= [本日の日付]の日 の場合
$query->where('T1.contract_periode', '=', $thisMonthEnd); $query->where('T1.contract_periode', '=', $thisMonthEnd);
} elseif ($endDay >= $todayDay) {
// 処理0.更新期間終了日 >= [本日の日付]の日 の場合
$query->where('T1.contract_periode', '=', $lastMonthEnd);
} }
// 翌月1日以降の猶予期間はメール送信対象外のため、
// 処理0.更新期間終了日 >= [本日の日付]の日 の条件は削除
} }
$targetUsers = $query->get()->toArray(); $targetUsers = $query->get()->toArray();

View File

@ -36,8 +36,8 @@ return [
| OCR処理関連設定 | OCR処理関連設定
*/ */
'ocr' => [ 'ocr' => [
// 文字列類似度の閾値70% // 文字列類似度の閾値75%
'similarity_threshold' => env('SHJ1_OCR_SIMILARITY_THRESHOLD', 70), 'similarity_threshold' => env('SHJ1_OCR_SIMILARITY_THRESHOLD', 75),
// 対応身分証明書タイプ // 対応身分証明書タイプ
'supported_id_types' => [ 'supported_id_types' => [