data/ 는 부산 투어 프로젝트의 Python FastAPI 서버입니다.
현재 이 서버가 담당하는 기능은 크게 3가지입니다.
- 장소 카테고리 / 테마 분류
- 일정 미리보기 / 일정 생성 / 일정 조회 / 일정 수정 / 지도 응답
- 즉흥 여행 목적지 추천 / 즉흥 코스 생성
이 문서는 2026년 9월 4일 기준 현재 구조와 운영 상태를 빠르게 파악하기 위한 요약 문서입니다.
server/- 프론트가 직접 호출하는 공개 API 서버
- 인증, 에러 포맷, 기존 API 계약 유지
- 일정 API는 FastAPI(
data-ai)에 위임 가능
data/- FastAPI 서버
- 분류 모델, 일정 생성 로직, 교통 응답 조립, 즉흥 여행 추천 담당
현재 운영 흐름은 아래와 같습니다.
- 프론트는 기본적으로
serverAPI를 호출합니다. server는 일정 관련 요청을data-aiFastAPI로 위임할 수 있습니다.data/는 개발 DB와 외부 교통 API를 사용해 응답을 만듭니다.
GET /healthPOST /predictPOST /predict/batch
역할:
- 장소 텍스트 기반 1차 카테고리 분류
- Spring 호환 응답 필드 제공
- 일정/추천용 보조 테마 정보 생성
주요 응답 필드:
primary_categorytheme_answer_idmodel_categorydecision_sourceprimary_themesecondary_themessemantic_tagsmood_tagsis_meal_placeis_low_mobility_friendly
POST /api/v1/schedule-previewsGET /api/v1/schedule-previews/{preview_id}POST /api/v1/schedulesGET /api/v1/schedulesGET /api/v1/schedules/{schedule_id}PATCH /api/v1/schedules/{schedule_id}GET /api/v1/schedules/{schedule_id}/map
현재 상태:
- preview 생성 / 조회 가능
- preview 기반 일정 생성 가능
- 직접 일정 생성 가능
- 일정 목록 / 단건 조회 가능
- 일정 수정 가능
- 지도 응답 생성 가능
POST /api/v1/spontaneous-trips/destinationsPOST /api/v1/spontaneous-trips/coursePOST /api/v1/spontaneous-trips/schedules
현재 상태:
- 출발 위치/시간/희망 테마 기준 목적지 추천 가능
- 선택한 목적지 기준 즉흥 코스 미리보기 생성 가능
- 사용자가 저장을 확정한 미리보기를 공통 일정 구조로 저장 가능
- 교통수단별 이동 가능 여부 반영
- stop별 장소·이동 구간·지도선과 마지막 복귀 시간 포함
/course는 계산 전용이다. schedules, schedule_*, transit_*, places,
place_images 등 비즈니스 테이블을 쓰지 않으며, 응답의 previewToken에 서버가 계산한
코스 스냅샷을 HMAC으로 서명한다. Spring이 전달한 현재 사용자 ID도 서명 범위에 포함된다.
클라이언트가 저장 버튼을 누를 때만 /spontaneous-trips/schedules를 호출한다. 요청 본문은
previewId, previewToken, 헤더는 Idempotency-Key와 Spring 내부
X-Auth-User-Id를 사용한다. 저장은 장소 식별, 공통 일정/정류장/이동 경로 저장,
멱등성 완료 처리를 한 DB 트랜잭션으로 커밋한다. 저장 과정에서 코스 생성기를 다시
실행하지 않는다.
운영 환경의 모든 data 인스턴스에는 같은 고엔트로피
SPONTANEOUS_PREVIEW_SECRET을 설정해야 한다. 기본 만료 시간은 20분이며
SPONTANEOUS_PREVIEW_TTL_SECONDS로 조정한다. 설정하지 않은 로컬 실행은 프로세스별
임시 키를 사용하므로 재시작 또는 다른 인스턴스를 거친 저장은 의도적으로 실패한다.
일정 생성은 아래 우선순위로 후보 장소를 읽습니다.
SPRING_DATASOURCE_URL,SPRING_DATASOURCE_USERNAME,SPRING_DATASOURCE_PASSWORDLOCAL_POSTGRES_*candidate_places.jsonfallback
즉, 정상 env가 있으면 JSON이 아니라 개발 DB를 우선 사용합니다.
/health 확인값:
schedule_candidate_sourceschedule_candidate_countschedule_db_enabledschedule_db_host
현재 교통 경로 생성은 아래 공급자를 사용합니다.
- ODSAY 대중교통 경로
- TMAP 보행 경로
- 내부 도보 fallback
관련 env:
ODSAY_ENABLEDODSAY_API_KEYODSAY_BASE_URLTMAP_WALKING_ENABLEDTMAP_BASE_URLSKT_API_KEY
참고:
- 사용자 응답에서는
TMAP,INTERNAL_WALK,FASTAPI_MIGRATION같은 공급자명을 최대한 숨기도록 정리했습니다. - 기존 DB에 저장된 일정 조회도 같은 기준으로 정규화합니다.
ODSAY_ENABLED=false이거나ODSAY_API_KEY가 없으면 대중교통 경로를 호출하지 않습니다.TMAP_WALKING_ENABLED=false이거나SKT_API_KEY가 실제 값이 아니면 TMAP 보행 경로를 호출하지 않습니다.- ODSAY/TMAP을 모두 사용할 수 없거나 외부 API가 실패하면 내부 거리 기반 도보 fallback이 사용됩니다.
- 긴 구간이
도보 90분처럼 보이면 우선data-ai컨테이너 env와transit odsay_skipped,transit tmap_skipped,transit fallback=estimated_walk로그를 확인해야 합니다.
현재 일정 생성은 하루가 너무 일찍 끝나는 경우를 줄이기 위해 아래 보정을 사용합니다.
- 목표 방문지 수 계산
- underfilled day 판정
- 방문지 추가 재배치 시도
- 일부 방문지 체류 시간 연장
주의:
10:00 ~ 20:00는 활동 가능 시간 창입니다.- 항상 20시까지 꽉 차는 것은 아닙니다.
- 다만 긴 일정은 방문지 수와 체류시간을 더 적극적으로 채우도록 보정하고 있습니다.
현재 사용하지 않습니다.
이전 실험용 후보 재정렬 경로는 제거했고, 지금 일정 생성은 규칙 기반 점수와 시간/이동 가능 여부 검증으로 동작합니다.
Spring 서버가 일정 API를 FastAPI에 위임하려면 아래 값이 필요합니다.
SCHEDULE_FASTAPI_ENABLED=true
SCHEDULE_FASTAPI_BASE_URL=http://data-ai:8010주의:
SCHEDULE_FASTAPI_ENABLED가 없으면 위임이 비활성화될 수 있습니다.SCHEDULE_FASTAPI_BASE_URL은 끝에 슬래시 없이 넣는 것을 권장합니다.- EC2에서 컨테이너를 수동 재실행할 때 env 누락 시 preview/create가 실패할 수 있습니다.
FastAPI 컨테이너는 최소 아래 값들이 필요합니다.
SPRING_DATASOURCE_URL=...
SPRING_DATASOURCE_USERNAME=...
SPRING_DATASOURCE_PASSWORD=...
ODSAY_ENABLED=true
ODSAY_API_KEY=...
TMAP_WALKING_ENABLED=true
SKT_API_KEY=...현재 deploy-dev.yml 은 위 값을 .env.dev 로 만든 뒤 EC2의 data-ai 컨테이너를 --env-file /opt/hackathon-dev/.env.dev 로 실행합니다.
POST /api/v1/spontaneous-trips/destinations와 POST /api/v1/spontaneous-trips/course는
같은 시간 검증 함수를 사용한다. 기존 요청·응답 필드는 바꾸지 않는다.
startAt과returnBy는+09:00,Z등 timezone offset이 있는 datetime이어야 한다.- 요청 시점의 서버 시각을 한 번 읽고, 모든 날짜·시간 비교는
ZoneInfo("Asia/Seoul")로 변환해서 수행한다. startAt의 KST 날짜는 서버 요청 시점의 KST 오늘과 같아야 한다. 00:00~08:59 자체는 금지하지 않는다.startAt >= now - 5분을 허용한다. 정확히 5분 전은 허용하지만, 자정 직후라도 전날 출발은 허용하지 않는다.returnBy > startAt이어야 하며, 상한은 출발일의 다음날 03:00:00 KST다. 03:00:00 초과는 거부한다.now는 유효성 검증에만 사용한다. 추천 프로필은 사용자가 보낸startAt, 코스 방문 프로필은 예상/실제 방문시간을 사용한다. 예를 들어 now=15:00, startAt=19:00이면 NIGHT이고, 다음날 00:30 방문은 LATE_NIGHT다.- 검증은 각 요청에서 다시 수행한다. 목적지 조회 후 시간이 지나 허용 오차나 KST 날짜 경계를 넘으면 동일한 출발시간도 더 이상 유효하지 않을 수 있다.
시간 정책 위반은 HTTP 422를 유지하면서 detail을
SPONTANEOUS_TIMEZONE_REQUIRED, SPONTANEOUS_START_DATE_NOT_TODAY,
SPONTANEOUS_START_TIME_IN_PAST, SPONTANEOUS_RETURN_TIME_BEFORE_START,
SPONTANEOUS_RETURN_TIME_TOO_LATE로 구분한다. 시간대 변환 자체가 불가능한 예외 상황은
기존 INVALID_TIME_RANGE를 유지한다. SERVER는 각 detail을 사용자 행동 지침이 포함된
공개 오류 코드로 변환한다.
호환성 변경: DATA의 기존 Pydantic datetime 필드는 naive 입력도 파싱했지만,
이제 두 엔드포인트에서 이를 명시적으로 거부한다. DATA 직접 호출자는 offset을 포함해야 한다.
Spring 요청 DTO는 이미 OffsetDateTime을 사용한다. API 필드 구조는 유지하며 SERVER 코드는 수정하지 않는다.
테스트는 validate_spontaneous_time_window(..., now=fixed_now) 또는
spontaneous.time_window.current_kst_time의 고정 시각으로 실제 현재시간 의존성을 제거한다.
POST /api/v1/spontaneous-trips/destinations
SPONTANEOUS_DESTINATION_ROUTE_NOT_FOUND: 모든 라우팅 후보가NO_ROUTE로 탈락SPONTANEOUS_DESTINATION_TIME_TOO_SHORT: 모든 라우팅 후보가 최소 체류시간 부족으로 탈락SPONTANEOUS_DESTINATION_TRANSPORT_CONSTRAINT: 경로 없음과 체류시간 부족이 함께 발생SPONTANEOUS_DESTINATION_CANDIDATES_NOT_FOUND: 장소 또는 테마 조건을 만족하는 라우팅 후보가 없음DESTINATIONS_NOT_FOUND: 분류되지 않은 기존 추천 실패
POST /api/v1/spontaneous-trips/course
DESTINATION_NOT_FOUNDTRANSPORT_MODE_UNAVAILABLEPLACE_SEARCH_FAILEDCOURSE_CANDIDATES_NOT_FOUNDCOURSE_GENERATION_EMPTYCOURSE_SEGMENT_ROUTE_UNAVAILABLECOURSE_RETURN_ROUTE_UNAVAILABLE
프론트 문구 예시:
TRANSPORT_MODE_UNAVAILABLE- 선택한 이동수단으로 이동 가능한 코스를 찾지 못했습니다.
COURSE_CANDIDATES_NOT_FOUND- 주변에서 추천할 장소를 찾지 못했습니다.
COURSE_SEGMENT_ROUTE_UNAVAILABLE- 추천 코스의 이동 경로를 계산하지 못했습니다. 다시 시도해 주세요.
COURSE_RETURN_ROUTE_UNAVAILABLE- 복귀 경로를 계산하지 못했습니다. 다른 이동수단으로 다시 시도해 주세요.
cd /Users/miju/test_1/data
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
uvicorn app:app --reload --host 0.0.0.0 --port 8010curl -s http://127.0.0.1:8010/healthcurl -s -X POST http://127.0.0.1:8010/predict \
-H 'Content-Type: application/json' \
--data '{
"title": "광안리해수욕장",
"overview": "부산 대표 해변과 야경 명소",
"contenttypeid": "12",
"cat3": "A01011100",
"addr1": "부산 수영구 광안해변로 219",
"mapx": 129.1186,
"mapy": 35.1532
}'curl -s -X POST http://127.0.0.1:8010/api/v1/schedule-previews \
-H 'Content-Type: application/json' \
-d '{
"startDate": "2026-08-10",
"endDate": "2026-08-11",
"startLocation": {
"name": "부산역",
"longitude": 129.0403,
"latitude": 35.1151
},
"lodgingPlan": {
"mode": "UNDECIDED",
"nightStays": []
},
"selectedAnswers": [
{ "questionId": "THEME", "answerIds": ["THEME_NATURE"] },
{ "questionId": "COMPANION", "answerIds": ["COMPANION_FRIENDS"] },
{ "questionId": "MOBILITY", "answerIds": ["MOBILITY_NORMAL"] },
{ "questionId": "PACE", "answerIds": ["PACE_RELAXED"] },
{ "questionId": "TRANSIT", "answerIds": ["TRANSIT_SIMPLE"] }
],
"mustVisitPlaceIds": [],
"fixedEvents": [],
"dayOverrides": []
}'- app.py
- FastAPI 진입점
- schedule/service.py
- 일정 생성 핵심 로직
- schedule/preview_service.py
- 일정 preview 생성 / 조회
- schedule/persistence.py
- preview / schedule DB 저장 및 조회
- transit/routing.py
- ODSAY / TMAP / 도보 fallback 경로 생성
- spontaneous/course.py
- 즉흥 코스 조합
- spontaneous/routing.py
- 즉흥 여행 이동시간 계산
- spontaneous/places.py
- 즉흥 여행 후보 장소 조회 / 필터링
- core/runtime_env.py
- env 파일 로딩
현재 data/ 는 단순 분류 서버가 아니라, 분류 + 일정 preview/create/list/get/update/map + 즉흥 여행 추천/코스를 담당하는 FastAPI 서버입니다.
다만 공개 API의 최종 진입점은 아직 Spring이 일부 맡고 있으므로, 현재 단계는 하이브리드 운영 상태입니다.