No.004 · 2026

오늘어디

ExpoNestJSGemini

사실은 API가, 규칙은 코드가, 취향 판단만 AI가 — 데이트 코스 추천 앱

▶ 도감 · No.004 오늘어디
역할
기획 · 풀스택 개발 (개인)
기간
2026.09 —

01개요

지역·성향·예산·차 여부·약속 시각을 넣으면 코스 5개를 짜 주고, 코스마다 대중교통·자동차 경로(버스 번호·정거장·소요 시간), 주차장, 도착 시각을 붙이는 앱입니다. 모바일은 Expo(React Native), 서버는 NestJS로 만들었고 요청·응답 타입은 zod 스키마로 공유합니다. 외부 서비스는 카카오 로컬·경로 API, Tavily, Gemini로 모두 카드 없이 쓰는 무료 등급입니다. 네이버 검색 API는 개정 약관이 검색 결과의 AI 입력·저장을 금지해 쓰지 않았고, 인스타그램은 검색엔진에 색인된 공개 글만 읽습니다.

02작업 사례

  1. 사례 1

    AI에게 "추천해줘"라고 시키지 않는다

    문제
    코스 추천을 AI에게 통째로 맡기면 없는 가게, 지어낸 버스 시간, 예산을 넘는 코스가 섞여 나옵니다.
    파악
    좌표·경로·시간 같은 사실과 예산·주차·영업 시간대 같은 규칙은 AI가 지어내거나 매번 다르게 판단하는 영역이었습니다. AI가 잘하는 일은 비정형 글을 구조화하는 것과 후보 안에서 취향에 맞게 고르는 것 두 가지였습니다.
    해결
    장소와 경로는 카카오 API, 필터는 코드, AI는 추천 한 번에 추출 3번과 조합 1번만 맡겼습니다. AI가 낸 코스는 코드가 다시 검사해 후보에 없는 장소를 지우고, 알려진 가격 합 × 2가 예산을 넘거나 시간표가 안 맞는 코스를 탈락시킵니다.
  2. 사례 2

    출처 번호로 지어낸 링크를 없애다

    문제
    웹 조사를 AI 한 번에 맡기면 느렸고, 힌트가 어느 글에서 왔는지 남지 않았습니다.
    파악
    검색·읽기·정리를 한 호출에 몰아넣어 프롬프트가 컸고, 출처를 AI가 기억해 내는 구조라 링크를 지어낼 수 있었습니다.
    해결
    검색은 Tavily가 블로그·인스타·웹 출처별로 따로 하고, 본문에 [1] 같은 번호를 붙여 AI가 번호로 출처를 돌려주게 했습니다. 추출을 출처별 3개로 나눠 동시에 보내, 한 번에 넣으면 20초 넘게 걸리던 호출이 무료 등급 모델에서도 3~5초에 끝납니다.
  3. 사례 3

    모르는 값은 "모름"으로 표시한다

    문제
    가격·주차·경로 정보가 없는 장소가 많았습니다.
    파악
    없는 값을 0이나 빈 값으로 두면 예산·주차 필터가 거짓으로 통과시키고, 정보가 없는 장소를 빼 버리면 후보가 남지 않았습니다.
    해결
    가격을 모르면 합계에서 빼고 "+α"로, 주차 정보가 없으면 "정보 없음"으로 표시하고 차가 있어도 "불가"만 제외합니다. 경로 API가 실패하면 직선거리 × 1.3으로 어림하고 어림값이라고 표시합니다.

03핵심 기능

AI 호출은 네 번뿐

입력 → 좌표 변환 → 지도 검색과 웹 조사 동시 수집 → 이름 매칭 → 코드 필터 → AI 조합 → 코드 검증 → 경로·시간표. 한 번의 추천에서 AI 호출은 추출 3번과 조합 1번이고, 나머지는 결정적인 코드입니다.

지역 캐시

같은 좌표 격자와 반경을 일주일 동안 캐시해, 첫 조사 13~20초가 캐시 적중 시 4~5초로 줄었습니다. 웹 조사가 실패한 반쪽 결과는 캐시하지 않아 다음 요청이 다시 조사합니다.

경로·주차·도착 시각

장소 사이 구간마다 카카오 경로 API로 버스 번호·환승·요금을 받고, 차가 있으면 장소마다 가장 가까운 주차장을 붙입니다. 같은 구간은 한 번만 묻고, 시간표 검사는 어림값으로 한 번, 실제 경로로 한 번 더 합니다.

판정 로직만 테스트

예산 계산, 주차 필터, 이름 매칭, 시간표, 후보 선별을 순수 함수로 두어 API 없이 도는 테스트 56개로 묶었습니다. AI 호출 자체는 테스트하지 않습니다.

04스택

Expo (React Native)TypeScriptNestJSPostgreSQLPrismazodGeminiTavilyKakao Local · Mobility APIRender
◀ 도감으로B
START
SELECT