fix: 맛집 중복 체크 및 카테고리 필터 로직 개선

1. placeId 기반 중복 체크 제거
   - 이전 대화에서 명확히 한 대로 placeId는 매칭에 사용하지 않음

2. 주소 기반 매칭 개선
   - 주소가 있을 때만 주소 기반 중복 체크 수행

3. 위치 기반 매칭 추가
   - 50m 이내 동일한 이름의 맛집 중복 체크 추가

4. 검색 결과 선택 로직 개선
   - 주소가 없을 때 가장 가까운 거리의 업체 선택

5. 카테고리 필터 버그 수정
   - 카테고리 표시명과 실제 값 불일치 문제 해결
   - 부분 일치 및 정규화된 비교 지원

6. 빈 상태 메시지 개선
   - 필터링 중일 때 적절한 안내 메시지 표시
   - 필터 초기화 버튼 추가

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
JiWoong Sul
2025-07-30 19:27:04 +09:00
parent 85fde36157
commit 9a61e2f391
4 changed files with 126 additions and 24 deletions

View File

@@ -4,6 +4,7 @@ import '../../api/naver_api_client.dart';
import '../../api/naver/naver_local_search_api.dart';
import '../../../domain/entities/restaurant.dart';
import '../../../core/errors/network_exceptions.dart';
import '../../../core/utils/distance_calculator.dart';
import 'naver_map_parser.dart';
/// 네이버 검색 서비스
@@ -111,8 +112,14 @@ class NaverSearchService {
return null;
}
// 가장 유사한 결과 찾기
final bestMatch = _findBestMatch(name, searchResults);
// 가장 유사한 결과 찾기 (주소가 없으면 거리 기반 선택 포함)
final bestMatch = _findBestMatch(
name,
searchResults,
latitude: latitude,
longitude: longitude,
address: address,
);
if (bestMatch != null) {
final restaurant = bestMatch.toRestaurant(id: _uuid.v4());
@@ -172,8 +179,11 @@ class NaverSearchService {
/// 가장 유사한 검색 결과 찾기
NaverLocalSearchResult? _findBestMatch(
String targetName,
List<NaverLocalSearchResult> results,
) {
List<NaverLocalSearchResult> results, {
double? latitude,
double? longitude,
String? address,
}) {
if (results.isEmpty) return null;
// 정확히 일치하는 결과 우선
@@ -186,6 +196,28 @@ class NaverSearchService {
return exactMatch;
}
// 주소가 없고 위치 정보가 있는 경우 - 가장 가까운 업체 선택
if ((address == null || address.isEmpty) && latitude != null && longitude != null) {
NaverLocalSearchResult? closestResult;
double minDistance = double.infinity;
for (final result in results) {
final distance = DistanceCalculator.calculateDistance(
lat1: latitude,
lon1: longitude,
lat2: result.latitude,
lon2: result.longitude,
);
if (distance < minDistance) {
minDistance = distance;
closestResult = result;
}
}
return closestResult ?? results.first;
}
// 유사도 계산 (간단한 버전)
NaverLocalSearchResult? bestMatch;
double bestScore = 0.0;
@@ -239,9 +271,18 @@ class NaverSearchService {
@visibleForTesting
NaverLocalSearchResult? findBestMatchForTesting(
String targetName,
List<NaverLocalSearchResult> results,
) {
return _findBestMatch(targetName, results);
List<NaverLocalSearchResult> results, {
double? latitude,
double? longitude,
String? address,
}) {
return _findBestMatch(
targetName,
results,
latitude: latitude,
longitude: longitude,
address: address,
);
}
@visibleForTesting

View File

@@ -194,20 +194,49 @@ class RestaurantRepositoryImpl implements RestaurantRepository {
}
}
// 중복 체크 - Place ID가 있는 경우
if (restaurant.naverPlaceId != null) {
final existingRestaurant = await getRestaurantByNaverPlaceId(restaurant.naverPlaceId!);
if (existingRestaurant != null) {
throw Exception('이미 등록된 맛집입니다: ${existingRestaurant.name}');
// 중복 체크 개선
final restaurants = await getAllRestaurants();
// 1. 주소 기반 중복 체크
if (restaurant.roadAddress.isNotEmpty || restaurant.jibunAddress.isNotEmpty) {
final addressDuplicate = restaurants.firstWhere(
(r) => r.name == restaurant.name &&
(r.roadAddress == restaurant.roadAddress ||
r.jibunAddress == restaurant.jibunAddress),
orElse: () => Restaurant(
id: '',
name: '',
category: '',
subCategory: '',
roadAddress: '',
jibunAddress: '',
latitude: 0,
longitude: 0,
source: DataSource.USER_INPUT,
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
),
);
if (addressDuplicate.id.isNotEmpty) {
throw Exception('동일한 이름과 주소의 맛집이 이미 존재합니다: ${addressDuplicate.name}');
}
}
// 중복 체크 - 이름과 주소로 추가 확인
final restaurants = await getAllRestaurants();
final duplicate = restaurants.firstWhere(
(r) => r.name == restaurant.name &&
(r.roadAddress == restaurant.roadAddress ||
r.jibunAddress == restaurant.jibunAddress),
// 2. 위치 기반 중복 체크 (50m 이내 같은 이름)
final locationDuplicate = restaurants.firstWhere(
(r) {
if (r.name != restaurant.name) return false;
final distanceInKm = DistanceCalculator.calculateDistance(
lat1: r.latitude,
lon1: r.longitude,
lat2: restaurant.latitude,
lon2: restaurant.longitude,
);
final distanceInMeters = distanceInKm * 1000;
return distanceInMeters < 50; // 50m 이내
},
orElse: () => Restaurant(
id: '',
name: '',
@@ -223,8 +252,8 @@ class RestaurantRepositoryImpl implements RestaurantRepository {
),
);
if (duplicate.id.isNotEmpty) {
throw Exception('동일한 이름과 주소의 맛집이 이미 존재합니다: ${duplicate.name}');
if (locationDuplicate.id.isNotEmpty) {
throw Exception('50m 이내에 동일한 이름의 맛집이 이미 존재합니다: ${locationDuplicate.name}');
}
// 새 맛집 추가