[Developer] #533 언급 적은 식당 제외 필터 (min_mentions + video_count)

- backend: findAll 응답에 video_count(strong/unknown 링크수) + min_mentions 서버필터
  - Restaurant.videoCount, RestaurantMapper(xml/iface), Service, Controller(+cache key)
- frontend: Restaurant.video_count 타입 + 데스크톱 툴바 "언급"(2/3/5회+) 클라 필터
- 검증: min_mentions=2→116, =3→36 (위반 0). 빌드/tsc 통과
- 설계서 docs/design/533-min-mentions-filter/README.md

Refs #533
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
joungmin
2026-06-28 16:23:46 +09:00
parent 4760b5a284
commit 7639fd009d
9 changed files with 100 additions and 10 deletions

View File

@@ -56,17 +56,18 @@ public class RestaurantController {
@RequestParam(defaultValue = "0") int offset,
@RequestParam(required = false) String cuisine,
@RequestParam(required = false) String region,
@RequestParam(required = false) String channel) {
@RequestParam(required = false) String channel,
@RequestParam(name = "min_mentions", required = false) Integer minMentions) {
if (limit > 500) limit = 500;
String key = cache.makeKey("restaurants", "l=" + limit, "o=" + offset,
"c=" + cuisine, "r=" + region, "ch=" + channel);
"c=" + cuisine, "r=" + region, "ch=" + channel, "mm=" + minMentions);
String cached = cache.getRaw(key);
if (cached != null) {
try {
return objectMapper.readValue(cached, new TypeReference<List<Restaurant>>() {});
} catch (Exception e) { log.warn("Cache deserialize failed, evicting: {}", e.getMessage()); cache.del(key); }
}
var result = restaurantService.findAll(limit, offset, cuisine, region, channel);
var result = restaurantService.findAll(limit, offset, cuisine, region, channel, minMentions, false);
cache.set(key, result);
return result;
}

View File

@@ -39,4 +39,7 @@ public class Restaurant {
// Transient enrichment fields
private List<String> channels;
private List<String> foodsMentioned;
// #533 — 의미있는(strong/unknown) 영상 언급 수
private Integer videoCount;
}

View File

@@ -15,6 +15,7 @@ public interface RestaurantMapper {
@Param("cuisine") String cuisine,
@Param("region") String region,
@Param("channel") String channel,
@Param("minMentions") Integer minMentions,
@Param("includeHidden") boolean includeHidden);
// #322 LLM 검증: hidden 표시 갱신

View File

@@ -21,11 +21,13 @@ public class RestaurantService {
}
public List<Restaurant> findAll(int limit, int offset, String cuisine, String region, String channel) {
return findAll(limit, offset, cuisine, region, channel, false);
return findAll(limit, offset, cuisine, region, channel, null, false);
}
public List<Restaurant> findAll(int limit, int offset, String cuisine, String region, String channel, boolean includeHidden) {
List<Restaurant> restaurants = mapper.findAll(limit, offset, cuisine, region, channel, includeHidden);
// #533 — minMentions: 의미있는 언급 수가 N 미만인 식당 제외 (null/0이면 미적용)
public List<Restaurant> findAll(int limit, int offset, String cuisine, String region, String channel,
Integer minMentions, boolean includeHidden) {
List<Restaurant> restaurants = mapper.findAll(limit, offset, cuisine, region, channel, minMentions, includeHidden);
enrichRestaurants(restaurants);
return restaurants;
}

View File

@@ -25,6 +25,7 @@
<result property="hidden" column="hidden" javaType="java.lang.Boolean"/>
<result property="hiddenReason" column="hidden_reason"/>
<result property="verifiedAt" column="verified_at"/>
<result property="videoCount" column="video_count"/>
</resultMap>
<!-- ===== Queries ===== -->
@@ -33,7 +34,9 @@
SELECT DISTINCT r.id, r.name, r.address, r.region, r.latitude, r.longitude,
r.cuisine_type, r.price_range, r.google_place_id, r.tabling_url, r.catchtable_url,
r.business_status, r.rating, r.rating_count, r.updated_at,
r.hidden, r.hidden_reason, r.verified_at
r.hidden, r.hidden_reason, r.verified_at,
(SELECT COUNT(*) FROM video_restaurants vrc
WHERE vrc.restaurant_id = r.id AND vrc.relevance IN ('strong','unknown')) AS video_count
FROM restaurants r
<if test="channel != null and channel != ''">
JOIN video_restaurants vr_f ON vr_f.restaurant_id = r.id
@@ -55,6 +58,10 @@
<if test="channel != null and channel != ''">
AND c_f.channel_name = #{channel}
</if>
<if test="minMentions != null and minMentions > 0">
AND (SELECT COUNT(*) FROM video_restaurants vrc
WHERE vrc.restaurant_id = r.id AND vrc.relevance IN ('strong','unknown')) &gt;= #{minMentions}
</if>
</where>
ORDER BY r.updated_at DESC
OFFSET #{offset} ROWS FETCH NEXT #{limit} ROWS ONLY