
부제: AI 서버에 GPS가 안 오게 된 뒤, 지명 문자열 하나로 국가·행정구역·좌표를 동시에 풀어간 경로
헬스 코치 에이전트에 약국·병원 위치 검색을 붙이면서 위치정보법 제약을 만났다.
AI 서버에는 GPS 좌표가 오지 않는다. 백엔드 계약상 위치는 "서울시 강남구" 같은 대략적 지역 문자열로만 넘어온다.
그리고 이 문자열 하나로 서로 다른 두 외부 API를 상대해야 했는데, 둘 다 지명을 곧이곧대로 못 믿는 API였다.
위치정보법: GPS가 아예 안 온다
가장 먼저 포기한 건 근접 정렬이다.
"가장 가까운 약국"을 하려면 사용자 좌표와 약국 좌표 사이 거리를 재야 하는데, 애초에 사용자 좌표가 없다.
그래서 서버는 "가장 가까운"이 아니라 "지금 상황에 맞는"(영업중·응급 우선) 추천만 하고, 진짜 근접 정렬은 프론트엔드가 온디바이스 GPS로 처리하도록 넘겼다.
문제는 그다음이다. 국내 약국·병원 데이터는 공공데이터포털(data.go.kr) API로 조회하는데, 이 API는 시도(Q0)·시군구(Q1) 파라미터를 정확히 받아야 한다.
그런데 "서울시 강남구"라는 문자열을 그대로 Q0="서울시"에 넣으면 조용히 틀린 답이 나온다.
Q0="서울시"→ LIKE 부분일치라 우연히 7건 매칭Q0="강원도"→ 정식명이 아니라서 0건Q1만 넣고Q0를 비우면 → 전국 25,000여 건이 그대로 반환
셋 다 에러 코드 없이 resultCode=00(정상)으로 응답한다. 실패인데 실패라고 안 알려주는 부류다.
지명 문자열, 국가부터 안 믿긴다
같은 문자열 하나로 날씨 API도 상대해야 했는데, 여기선 아예 지명 자체가 못 미더웠다.
WeatherAPI.com에 도시명을 그대로 넘겼을 때 실측한 결과다.
| 입력 | 곧이곧대로 넘겼을 때 | 실제 |
|---|---|---|
| Suwon | 제주도 좌표 | 한국 수원 |
| Sokcho | 북한 지역 | 한국 속초 |
| Kaesong | 충북(남한) | 북한 개성 |
| Dokdo | 미얀마 도시 | 독도 |
한글은 아예 안 받는다 — 서울을 그대로 넣으면 에러(코드 1006)다.
동명이지(同名異地) 지명이 전 세계에 널려 있고, 이 API의 자체 지명 매칭은 그걸 구분 못 한다.
해법: 지오코더 하나로 한 번에
두 문제 다 근본 원인이 같았다 — 지명 문자열을 최종 목적지 API에 직접 믿기고 넘긴 것.
그래서 둘 다 앞단에 지오코딩(Nominatim)을 하나 세우고, 그 뒤로는 좌표만 다니게 했다.
날씨는 단순하다. 도시명이 들어오면 무조건 Nominatim을 거쳐 좌표로 바꾼 다음 WeatherAPI에 넘긴다. 이미 좌표 형식이면 그대로 통과.
한글 14개 도시(서울·수원·속초·독도·울릉도 등)와 영문 한국 지명(Suwon·Jeju·Sokcho·Kaesong) 전수를 실측해 정확도를 확인했다.
장소 검색은 한 걸음 더 필요했다.
지오코더가 좌표만 주면 안 되고, 공공데이터 API가 요구하는 Q0(시도)·Q1(시군구)까지 같이 줘야 한다.
그래서 정지오코딩(forward geocode) 호출 한 번으로 국가·시도·시군구를 동시에 뽑아내는 쪽을 택했다.
country← 응답의 국가 코드(ISO2)Q0(시도) ←province또는city필드Q1(시군구) ←city_district/borough/county/city후보 중 접미가구·시·군인 첫 값('동'같은 하위 행정단위나 관광지 POI는 걸러야 한다 — 안 걸러내면 0건이 난다)
화이트리스트는 검증용이지 별칭 테이블이 아니다
지오코더 응답을 시도 17개 정식명과 대조하는 테이블(OFFICIAL_17)을 뒀는데, 처음엔 "별칭→정식명" 매핑 테이블로 생각했다가 방향을 바꿨다.
별칭 정규화(서울시→서울특별시, 강원도→강원특별자치도)는 지오코더가 이미 해준다 — 지도 데이터가 현행 행정구역명을 유지하기 때문에, 개편이 나도 우리가 별도로 관리할 필요가 없다.
이 테이블이 진짜 막는 건 다른 문제다."광주시 서구"를 지오코딩하면 city="광주"가 나오는데, 이건 정식명(광주광역시)이 아니다.
이걸 그대로 Q0="광주"로 내보내면 경기도 광주시까지 섞여 LIKE 오염이 난다.
그래서 화이트리스트에 없으면 유일하게 매칭되는 접두어로 승격을 시도하고, 그래도 안 맞으면 조용히 넘기지 않고 에러를 던진다.
낡은 별칭 테이블의 실패 모드는 "조용한 0건"인데, 이 화이트리스트의 실패 모드는 "시끄러운 에러"다. 둘의 차이가 설계의 핵심이다.
최후에도 한국으로 조용히 기본값 잡지 않는다
마지막 원칙 하나. 국가 판별이 애매할 때 "그냥 한국으로 하자"는 유혹이 있었다.
서버 기본 타임존이 Asia/Seoul이고, 사용자 발화가 한글이면 그럴듯해 보인다.
하지만 한글로 "도쿄"라고 쳐도 지오코딩 결과는 jp로 정확히 해소된다 — 문자열의 언어가 장소를 정하는 게 아니라, 그 문자열이 가리키는 실제 장소가 정한다.
그래서 신뢰 신호는 전부 언어 독립적인 것만 쓰기로 했다: 국가 코드, IANA 타임존, 지오코딩 결과.
한글이라서, 기본 타임존이 Asia/Seoul이라서 한국으로 판정하는 경로는 어디에도 남겨두지 않았다.
정리
- AI 서버엔 GPS가 안 온다(위치정보법 제약) — 근접 정렬은 포기하고 프론트엔드로 넘겼다
- 지역 문자열을 정부 API에 그대로 넣으면 조용히 틀린다(
resultCode=00인데 0건·7건·전국반환) - 같은 문자열을 날씨 API에 그대로 넣어도 틀린다(Suwon→제주 좌표) — 지명 매칭을 API 자체에 안 믿긴다
- 지오코더 하나를 공통 관문으로 세워 문자열을 좌표(+국가·행정구역)로 정규화한 뒤에만 각 API를 부른다
- 화이트리스트는 별칭 정규화용이 아니라 "지오코더 출력이 정식명이라는 보장이 없다"를 잡는 검증용 — 실패는 조용한 0건이 아니라 명시적 에러로
- 한글·기본 타임존만으로 국가를 추정하는 경로는 남기지 않았다
법이 좌표를 막았다고 위치 기반 기능을 포기할 필요는 없었다.
남은 건 지명 문자열 하나였고, 그 문자열을 믿을 수 있는 형태로 바꿔주는 관문을 하나 세우는 것으로 충분했다.
'AI' 카테고리의 다른 글
| [DB] MongoDB로 시작해 PostgreSQL로 — DB를 고른 게 아니라 스택이 골라줬다 (0) | 2026.08.07 |
|---|---|
| [RAG] OCR 대신 LLM — 서버보다 API 호출이 쌌다 (1) | 2026.08.05 |
| [에이전트] 대화 선제 요약 트리거 백필 함정 반복 실패 쿨다운 (0) | 2026.08.04 |
| [메모리] 내부 신호와 대화 노출 분리 category tags 직렬화 제외 (0) | 2026.08.04 |
| [에이전트] LLM 카드 플래그 없는 설계 환각 방지 서버 계산 (0) | 2026.08.02 |