# TTS 기능 및 경로 안내 검증 기준 ## 1. 제공 TTS와 출력 기준 모든 러닝 음성은 웹 브라우저 기본 음성이 아닌 `myshell-ai/MeloTTS-Korean`의 WAV 응답을 사용한다. 프런트엔드는 `POST /api/v1/ai/tts/korean`에 `{ "text": "...", "speed": 1.5 }`를 요청하고, 응답을 Object URL로 캐시한 뒤 하나의 재생 대기열에서 순서대로 재생한다. 모델 오류 시 해당 안내만 건너뛰며 OS TTS로 대체하지 않는다. | 기능 | 출력 기준 | 음성 텍스트 | | --- | --- | --- | | 경로 갈림길 안내 | 생성된 OSM 도로 그래프에서 진입 방향을 제외한 선택지가 2개 이상이고, 선택 경로가 좌/우로 30~150도 꺾이는 지점만 등록한다. 선택 경로에서 250m 이내에 들어오면 WAV를 미리 준비하고, GPS가 경로에서 45m 이내이며 해당 지점 65m 전~12m 지난 범위에 있을 때 1회 안내한다. 60m 이내에 연속된 분기점은 중복 등록하지 않는다. | `잠시 후 갈림길에서 좌회전하세요.` 또는 `잠시 후 갈림길에서 우회전하세요.` | | 실시간 센서 코칭 | 워치 `live-state`를 5초마다 읽는다. 같은 항목은 120초, 다른 항목은 90초 재안내하지 않는다. 심박 상승 → 케이던스 → 좌우 비대칭 → 접지 시간 → 상하 움직임 순서다. | 아래 센서 코칭 문구 참조 | | 1km 완료 | 실제/시뮬레이션 이동 거리로 새 정수 km를 넘을 때 각 km마다 1회. | `{n}킬로미터를 완료했어요. 걸린 시간은 {시간}, 평균 속도는 {속도}입니다.` | | 절반 | 선택 코스 목표 거리의 50%를 처음 넘을 때 1회. | `절반 지점에 도달했어요! 지금처럼만 유지해서 끝까지 이어가보세요!` | | 목표 500m 전 | 목표 거리가 0.5km보다 길고 `목표 - 0.5km`를 처음 넘을 때 1회. | `목표치에 거의 다 도달했어요! 조금만 더 힘내세요!` | | 목표 달성 | 선택 코스 목표 거리를 처음 넘을 때 1회. | `축하드립니다! 목표를 달성했어요! 총 거리 {거리}, 걸린 시간은 {시간}, 평균 속도는 {속도}입니다.` | 센서 코칭 문구는 다음과 같다. - 현재 심박이 평균보다 12bpm 이상 높고 평균이 120bpm 이상: `현재 심박이 이번 러닝 평균보다 올라갔어요. 보폭을 조금 줄이고 편한 호흡으로 속도를 조절해 보세요.` - 평균 케이던스가 150spm 미만: `러닝 리듬이 느려졌어요. 보폭을 무리하게 늘리지 말고 발을 짧고 가볍게 옮겨 보세요.` - 좌우 비대칭이 8% 이상: `좌우 리듬이 한쪽으로 치우쳐 기록되고 있어요. 속도를 조금 낮추고 균형 있는 발걸음을 확인해 보세요.` - 평균 접지 시간이 310ms 이상: `발이 지면에 머무는 시간이 길어졌어요. 지면을 가볍게 밀고 다음 발로 넘어가 보세요.` - 평균 상하 움직임이 10.5cm 이상: `위아래 움직임이 커졌어요. 시선은 정면에 두고 앞으로 부드럽게 나아가 보세요.` ## 2. 경로 생성부터 종료까지의 검증 시나리오 1. `/route-results`에서 1km 이상 경로를 생성한다. 요청은 사용자 인터페이스나 API 게이트웨이의 고정 시간으로 취소되지 않아야 한다. 2. 생성 응답의 각 `routes[].maneuvers`가 `id`, `point`, `turn`, `branch_count`, `distance_from_start_m`을 갖는지 확인한다. `branch_count`는 2 이상이어야 하고 `turn`은 `left` 또는 `right`여야 한다. 3. 추천 경로 상세 화면에서 `달리기 시작`을 선택한다. 선택 경로와 `maneuvers`가 `sessionStorage`에 함께 보관되고, TWA/Wear 전달 payload에도 포함되는지 확인한다. 4. 실제 GPS 또는 `simulate=1`으로 선택 경로를 진행한다. 첫 갈림길 약 65m 전에 한 번만 해당 좌/우회전 음성이 재생되고, 지나간 뒤에는 늦은 안내가 재생되지 않는지 확인한다. 다음 갈림길도 같은 기준으로 확인한다. 5. 워치가 연결된 경우 `live-state`를 보내 센서 코칭의 우선순위와 90/120초 재안내 제한을 확인한다. 경로 안내와 센서 안내가 겹치면 WAV 재생 대기열에서 순차 재생되는지 확인한다. 6. 1km, 절반, 목표 500m 전, 목표 달성 기준을 차례로 넘겨 거리 안내 문구와 실제 표시 거리/시간이 일치하는지 확인한다. 7. 정지 버튼을 길게 눌러 종료한다. TWA/Wear 실행은 워치의 `POST /runs` 저장본을 기다리고, 결과·캘린더·실제 이동 경로가 동일한 `run_id`로 한 번만 저장되는지 확인한다. ## 3. 현재 검증 상태 - 프런트엔드 `npm run build`: 통과. - 경로 안내 판단과 TTS 호출 연결: TypeScript 프로덕션 빌드에서 통과. - 로컬 API 게이트웨이 경로 생성: `HTTP 200`으로 경로 2개와 `maneuvers` 15개를 반환했다. 표본의 모든 갈림길은 `branch_count` 2 이상, `turn`은 `left` 또는 `right`였다. - MeloTTS: `잠시 후 갈림길에서 우회전하세요.` 요청이 `audio/wav`(222,120바이트)로 정상 응답했다. - 배포 API 게이트웨이 경로 생성: `HTTP 200`으로 경로 1개와 좌 1·우 2, 총 3개의 갈림길을 반환했다. - 배포 MeloTTS: `잠시 후 갈림길에서 좌회전하세요.` 요청이 `audio/wav`(225,192바이트)로 정상 응답했다. - 워치-휴대폰 Data Layer: Galaxy Watch6 연결, 러닝 시작 명령 및 진행 상태 수신을 실기기로 확인했다. - 워치 종료: 잠금을 우회하지 않는 공식 Data Layer 제어 딥링크로 `stop` 명령을 전송했다. 다만 휴대폰이 보안 잠금 상태가 되어 종료 화면·요약 업로드 완료 ACK와 실제 GPS 갈림길에서의 스피커 청취는 이 세션에서 판정할 수 없었다. 이 두 항목만 다음 잠금 해제 상태의 실외 러닝에서 위 시나리오 4~7단계로 마무리한다.