MSG-492 코스 미션 포토스팟 표시 이름¶
티켓: MSG-492 · 작성일: 2026-08-26 · 작성: prd-writer 상태: 검토됨 (2026-08-26 팀원 K 승인)
SRS 대조: FR-MISSION-19(계획, 2026-08-13 확정)를 상세화하는 PRD다. 신규 요구가 아니라 이미 등재된 요구의 구현 범위를 정한다. 다만 그 요구의 뒷 문장("정식 명소가 아님을 구분한다")은 부분 충족으로 남는다. 대체 이름이 행정동 형태라 이름만으로 구분이 서지만 서버가 명시 신호를 주지는 않는다(2026-08-26 결정). 구현 뒤 요구 상태를 올릴 때 이 차이를 함께 적는다. 원본 명소 이름을 적재해 보여주는 부분(FR-2)은 SRS 어디에도 없어 별도 등재가 필요하다.
1. 문제 상황¶
지도 홈에서 코스 미션을 열면 포토스팟[^1] 목록이 나오는데, 지금은 항목 제목이 전부 "이름 없는 경유 지점"이다. 2026-08-26 운영 화면(해파랑길 1코스)에서 7곳 전부가 그랬다. 사용자는 어디로 가야 하는지 알 수 없고, 옆에 붙은 "영상 9" 같은 숫자만 보고 찍어야 한다.
원인은 두 겹이다.
첫째, 서버가 이름을 아예 안 보낸다. 미션 응답의 스팟 항목은 gridId와 좌표와 순번뿐이다.
그래서 화면은 스팟마다 좌표를 역지오코딩해 동 이름을 얻고 있는데, 그 조회는 좌표를 품는 행정동이
없으면 빈 값을 돌려준다. 해파랑길 1코스 7곳이 전부 "이름 없는 경유 지점"이던 것이 이 경우다 —
바다에 걸친 지점이라 품는 행정동이 없다. 내륙 코스에서는 동 이름이 나오므로, 지금 화면은 코스에
따라 되기도 하고 안 되기도 하는 상태다.
격자 쪽으로 이름을 얻는 길은 아예 막혀 있다. 코스 스팟 972칸 중 951칸(98%)은 grids 테이블에
행 자체가 없다. 격자 행은 누군가 영상을 올릴 때 비로소 만들어지는 구조라, 아무도 안 다녀간 경유
지점은 조회할 대상이 존재하지 않는다.
둘째, 이름이 이미 손에 있는데 버리고 있다. 코스 산출물 courses-seed.json의 스팟 989개는
전부 name을 갖고 있는데 적재 단계에서 읽지 않는다. 그중 417개는 "다대포 꿈의 낙조분수",
"광안리해수욕장" 같은 실제 명소 이름이다. 화면에서 본 해파랑길 1코스 7곳도 전부 여기 해당해서,
읽기만 했으면 오륙도해맞이공원부터 송림공원까지 그대로 나왔을 자리다.
나머지 572개는 원본에 명소가 없어 기하 폴백[^2]으로 좌표만 잡은 지점이고, 이름이 "경유점 3" 같은 자리표시다. 두루누비 원본과 GPX 파일을 전수로 뒤졌지만 지점 이름은 어디에도 없었고, POI 검색 반경을 넓히는 우회로도 닫혀 있다. 이 572개의 최근접 POI 거리를 재보니 반경 안(400m)이 34개뿐이고 절반이 1.5km 밖이라, 반경을 늘리면 엉뚱한 이름이 붙는다.
다만 이 지점들도 자리 자체는 이름을 갖고 있다. 스팟 좌표를 역지오코딩[^3]하면 972칸 중 910칸(94%)이 곧바로 행정동으로 떨어진다. 나머지 62칸은 해안선 바깥이라 어떤 행정동에도 안 들어가는데, 그중 55칸은 경계에서 300m 안이고 300m를 넘는 지점은 7칸뿐이다(최대 2.2km). 두루누비 걷기길이 해안을 따라 나 있어 격자 중심이 물 쪽으로 떨어지는 경우다.
2. 목적 · 목표¶
목적: 코스 미션의 모든 경유 지점이 사람이 읽고 찾아갈 수 있는 이름을 갖게 한다.
목표
- 코스 포토스팟 972칸 전부가 빈 이름 없이 표시된다(지금은 0칸)
- 원본에 명소 이름이 있는 417칸은 그 이름을 그대로 보여준다
- 명소가 없는 555칸은 격자 표시명[^4]으로 대신한다. 대체 이름은 형태만으로 명소가 아님이 드러난다
- 목록에서 보든 상세에서 보든 같은 지점은 같은 이름이다
비목표(스코프 제외)
- 코스 미션의 대표 격자 업로드는 이번 범위가 아니다. 축제와 팝업에는 좌표 없이 올리는 경로가 있지만(FR-MISSION-22) 코스는 대상에서 빠져 있고, 그 확장은 이름 문제와 독립이라 별도로 판단한다
- POI 재수집과 검색 반경 조정은 하지 않는다. 위에 적은 실측으로 효과가 없다는 것이 닫혔다
- 축제와 팝업 미션의 이름은 건드리지 않는다. 둘은 미션 제목이 곧 장소라 같은 문제가 없다
- 영상이 올라온 일반 격자의 행정동 라벨 공백은 다루지 않는다. dev에 7,470칸이 있지만 원인도 해법도 다른 문제다
- 지점 이름을 사용자나 운영자가 고치는 기능은 만들지 않는다
- 이름 출처를 별도 필드로 내려주지 않는다(2026-08-26 팀원 K 결정). 대체 이름이 행정동 형태라 이름만으로 구분이 서고, 화면이 둘을 시각적으로 가를지는 디자인이 아직 정하지 않았다. 필요해지면 그때 필드를 더한다
3. 기능 요구사항¶
| ID | 요구사항 | 우선순위 |
|---|---|---|
| FR-1 | 사용자는 코스 미션의 포토스팟 목록에서 지점마다 표시 이름을 본다. 이름이 빈 지점이 없다 | Must |
| FR-2 | 원본 산출물이 명소 이름을 가진 지점은 그 이름을 글자 그대로 보여준다. 서버가 다듬거나 자르지 않는다 | Must |
| FR-3 | 명소 이름이 없는 지점은 그 격자의 표시명으로 대신한다. 구역 안이면 구역 이름과 칸 번호이고, 밖이면 행정동 이름이다. 순번은 붙이지 않는다 | Must |
| FR-4 | 대체 이름은 그 형태만으로 정식 명소가 아님이 드러난다. 서버가 출처를 별도 필드로 주지는 않는다 | Must |
| FR-5 | 어떤 행정동에도 속하지 않는 지점(해안·해상)은 가장 가까운 행정동 이름을 받는다 | Must |
| FR-6 | 같은 지점은 미션 목록과 미션 상세 어디서 조회해도 같은 이름이다. 예외는 시더가 이름을 갱신한 직후 최대 1시간이다. 목록은 조립된 결과를 그 주기로 캐시하는데, 이 창은 미션 제목·기간·이미지 등 캐시가 담는 모든 값이 이미 갖고 있는 성질이라 이름 때문에 새로 생기는 것이 아니다 (2026-08-26 스펙 리뷰에서 명시) | Must |
| FR-7 | 이름을 아직 채우지 못한 지점은 응답에서 빈 값으로 오고, 화면은 기존 안내 문구를 그대로 쓸 수 있다. 오류가 아니다 | Must |
| FR-8 | 이미 등재된 코스 148개에도 이름이 채워진다. 코스를 지웠다 다시 넣지 않아도 된다 | Must |
| FR-9 | 이름은 표시 전용이다. 미션 판정, 진행도, 스탬프, 뱃지 어디에도 영향을 주지 않는다 | Must |
| FR-10 | 산출물과 구역·행정동 데이터가 그대로면 적재를 다시 돌려도 이름이 달라지지 않는다. 구역이나 행정동이 바뀌면 재실행이 그 변경을 이름에 반영하는 것이 정상 동작이다 | Must |
| FR-11 | 한 코스가 같은 행정동을 여러 번 지나 대체 이름이 겹쳐도 화면이 순번으로 지점을 가릴 수 있다 | Should |
4. 비기능 요구사항¶
| 분류 | 요구사항 |
|---|---|
| 성능 | 미션 상세 조회의 왕복 쿼리 수가 스팟 개수에 비례해 늘지 않는다. 지금은 5회 고정이다 |
| 성능 | 미션 목록 조회는 뷰포트에 코스가 여러 개 걸려도 이름 때문에 응답이 느려지지 않는다. 목록은 스냅숏을 재계산할 때만 비용을 치른다 |
| 데이터 정합 | 격자 표시명 규칙은 기존 정본을 그대로 따른다. 명명 규칙의 실행형 정본은 src/test/resources/fixtures/zone-naming.json이고 코스라고 다르게 짓지 않는다 |
| 데이터 정합 | 이름이 바뀌어도 이미 발급된 스탬프와 진행도는 영향을 받지 않는다 |
| 운영 | 마이그레이션 한 건이 필요하고, 반영 뒤 코스 시더를 한 번 돌려야 기존 148개가 채워진다 |
| 운영 | 되돌리면 이름만 사라지고 미션 동작은 그대로다 |
| 보안/인가 | 미션 상세와 목록은 이미 비로그인에 열려 있다. 이름도 같은 공개 범위이고 사용자에 따라 달라지지 않는다 |
5. 시퀀스 다이어그램¶
적재 시점에 이름 재료를 채우는 흐름이다.
조회 시점에는 저장된 값을 통과시키기만 한다. 쿼리가 늘지 않는다.
6. 클래스 다이어그램¶
7. 변경 파일 목록¶
| 파일 | 변경 | Owner |
|---|---|---|
src/main/resources/db/migration/V43__mission_grid_spot_name.sql |
신규. mission_grids에 이름 컬럼 추가 |
- |
src/main/java/com/msg/fillmap/mission/entity/MissionGrid.java |
수정. 이름 필드와 시드 생성자 | B |
src/main/java/com/msg/fillmap/mission/seed/CourseRecord.java |
수정. 스팟에 이름과 선정 방식 추가 | B |
src/main/java/com/msg/fillmap/mission/seed/CourseSeedReader.java |
수정. 산출물에서 이름과 방식을 읽는다 | B |
src/main/java/com/msg/fillmap/mission/seed/CourseMissionSeeder.java |
수정. 이름 없는 스팟의 표시명을 계산해 채우고, 이미 등재된 코스는 이름만 갱신한다 | B |
src/main/java/com/msg/fillmap/mission/dto/MissionShape.java |
수정. 스팟에 이름 필드 하나 추가 | B |
src/main/java/com/msg/fillmap/mission/service/impl/MissionQueryServiceImpl.java |
수정. 저장된 이름을 응답에 통과 | B |
src/main/java/com/msg/fillmap/zone/service/ZoneNameQueryService.java |
소비만(시더에서). 기존 계약 그대로 | A |
src/main/java/com/msg/fillmap/region/service/RegionQueryService.java |
수정. 최근접 행정동 조회 메서드 추가 (기존 resolveByPoint의 "없음" 의미를 보존하려면 폴백을 안에 넣을 수 없다) |
A |
src/main/java/com/msg/fillmap/region/service/impl/RegionQueryServiceImpl.java |
수정. 위 메서드 구현 | A |
src/main/java/com/msg/fillmap/region/repository/RegionRepository.java |
수정. 최근접 KNN 조회 추가 (동률은 region_code로 단일화) |
A |
src/main/java/com/msg/fillmap/mission/seed/CourseSpotNameResolver.java |
신규. 이름 결정 사다리를 담는 순수 로직 | B |
.claude/rules/glossary.md |
수정. 표시명 항목에 코스 스팟 예외와 해상 최근접 폴백 등재 | - |
ZoneNameQueryService는 변경하지 않는다. RegionQueryService에는 최근접 조회 메서드가 하나
늘어 Owner A 합의가 필요하다. 기존 resolveByPoint에 폴백을 얹지 않는 이유는 그 계약의
"포함하는 행정동 없음" 신호를 지금 다른 소비처가 쓰고 있기 때문이다.
8. 미해결 질문¶
- [x] 대체 이름의 형태. 행정동 이름만 쓰고 순번은 붙이지 않는다(2026-08-26 팀원 K 확정). 화면이 이미 항목 부제로 "3번째 스팟"을 찍고 있어 순번을 붙이면 같은 말이 두 번 나온다
- [x] 이름 출처 필드. 내려주지 않는다(2026-08-26 팀원 K 확정). 위 비목표 참조
- [x] 대체 이름을 언제 계산할지. 적재 때 굳혀 저장한다(2026-08-26 스펙 D-2에서 확정). 조회 때 계산하는 초안은 스팟마다 행정동 판정 왕복이 붙어 위 성능 요구를 깼고, 그걸 막으려 메모와 TTL을 넣자 목록·상세 이름 불일치가 새로 생겼다. 대가는 구역·행정동 데이터가 바뀌면 시더 재실행이 필요하다는 것인데, 그 재실행은 이미 운영 절차에 있다
- [ ] 기하 폴백 지점 34곳은 POI가 반경 안(400m)에 있는데도 명소로 안 잡혔다. 산출 파이프라인의 타입 필터나 지점 간 최소 간격 때문으로 보이는데, 이번 범위에서 들여다볼지
- [ ] 코스 경유 지점을 눌러 그 자리에 영상을 올리는 동선. 지금은 화면이 스팟 좌표를 업로드 요청에 그대로 실으면 되지만, 축제·팝업처럼 서버가 격자를 정해 주는 경로를 코스에도 열지는 정하지 않았다. 비목표로 뺐으나 화면 설계와 함께 결정될 사안이다
[^1]: 포토스팟. 코스 미션의 판정 대상 격자다. 코스 하나에 5~8곳을 두고 그중 3곳에 영상을 올리면 완료다. 표시용 경로(폴리라인)와 판정용 스팟을 따로 저장하는 구조에서 판정 쪽에 해당한다. [^2]: 기하 폴백. 코스 경로 위에서 관광 API로 명소를 못 찾았을 때 시작점과 끝점과 중간점을 기계적으로 골라 지점을 만드는 방식이다. 좌표만 있고 이름이 없다. [^3]: 역지오코딩. 위도와 경도를 받아 그 자리가 어느 행정동인지 알아내는 조회다. 필맵은 행정동 경계 도형에 점이 들어가는지 보는 방식으로 판정한다. [^4]: 표시명. 격자를 사람이 읽는 이름이다. 구역 안이면 "서면 A-14"처럼 구역 이름과 칸 번호로, 구역 밖이면 행정동 이름으로 정한다. 계산 주체는 서버다.