출퇴근 영어 회화 앱 · 제품 문서

출퇴근 영어 회화 앱 — 개발 계획서 v0.2

작성일: 2026-09-13 · 기준 문서: PRD v0.2, 제품검토 회의록(2026-09-13) 표기: [결정] 확정 · [제안] 검토 필요 · [확인 필요] 정보 요청 개발 방식: 구현은 Claude(Claude Code), 리뷰·실기기 테스트·배포 승인은 오너(개발자)


0. v0.1 대비 변경 요약

# 변경 절
C1 마일스톤 재배치 — 걷기 모드를 M6 → M3으로 전진 7
C2 M1 스파이크 1주 → 2주, 블루투스 오디오 매트릭스 추가 6.1, 7
C3 M1 게이트 신설 — 6개 조건 미충족 시 스코프 재설계 8
C4 선행 검증 2주를 개발 전 단계로 편입 (M0과 병렬) 7
C5 로컬 알림을 MVP 범위로 승격 2
C6 STT를 인터페이스로 추상화, 엔진 교체 가능 구조 3, 6.3
C7 오디오 상태 머신 명세 추가 (인터럽트 정책 표 포함) 6.2
C8 Android 14 포그라운드 서비스 타입·권한 요건 명시 6.5
C9 SRS(간격 반복) 모듈 신설 — 순수 함수, 단위 테스트 대상 6.6
C10 인앱 결제 모듈 추가 2, 3
C11 일정 산정 전제 명문화 — 오너 주당 10시간 7.4
C12 데이터 모델에 Curveball / Mastery / StreakState / Subscription 추가 5

1. 개발 방향


2. MVP 범위 [변경 v0.2]

기능 v0.1 v0.2 비고
온보딩 (출퇴근 정보, 상황 선택, 여행 목표) ✅ ✅ 레벨 진단 제거
데일리 루틴 자동 생성 ✅ ✅
간격 반복(SRS) — ✅ 신규 루틴 생성기 내장
지하철 모드 — 화면 ✅ ✅ 스와이프 → 큰 탭 타겟
지하철 모드 — 오디오 모드 — ✅ 신규 혼잡 시 분기
Curveball 훈련 — ✅ 신규 리스닝 강화
오프라인 다운로드·동기화 ✅ ✅
걷기 모드: 백그라운드 재생 + 이어폰 제어 ✅ ✅ M3으로 전진
걷기 모드: 따라 말하기 + 판정 ✅ ✅ 점수 → 이진 판정
걷기 모드: 화면 잠금 상태 녹음 ⏳ 2단계 ⏳ 2단계 M1에서 가능성만 검증
스트릭 + 보호 장치 ✅ ✅ 프리즈·대체 모드 추가
이어폰 없는 날 모드 — ✅ 신규
로컬 알림 ⏳ 2단계 ✅ 승격 서버 불필요, 반나절 작업
원격 푸시 (FCM/APNs) ⏳ 2단계 ⏳ 2단계
인앱 결제 — ✅ 신규 무료/Pro/여행 패스
AI 롤플레이 ⏳ 3단계 ⏳ 3단계

사용 환경 범위 [2026-09-19 결정 반영] — 만드는 이동 환경은 지하철 2모드(M5) + 걷기(M3) 둘뿐이다. 차량(운전 중)은 재논의 트리거 없이 제외, 러닝은 트리거를 남긴 채 제외한다. 정본은 PRD 2장 "사용 환경 범위" 이고(2026-09-19 #제품 회의·백승현 부대표 결재), 이 계획서는 옮겨 적은 것이다 — 값이 어긋나면 PRD가 이긴다. 러닝 트리거가 M1 게이트 당일 발동하면 일정에 M1 매트릭스 재실측 2주 + 무입력 조작 설계가 추가된다(7.1 일정에 아직 안 들어 있다).


3. 기술 스택

라이브러리는 착수 시점에 New Architecture 호환 여부, 최근 유지보수 상태, 최신 RN 버전 지원을 확인한 뒤 확정한다.

영역 선택 선택 이유
프레임워크 React Native CLI (최신 안정), TypeScript strict 결정 사항
네비게이션 React Navigation (native-stack) 사실상 표준
서버 상태 TanStack Query 캐싱·재시도·오프라인 대응
클라이언트 상태 Zustand 가볍고 보일러플레이트 적음
로컬 저장 (KV) react-native-mmkv 설정·세션·진행 상태
로컬 DB op-sqlite 오프라인 콘텐츠·학습 기록
파일 다운로드 react-native-blob-util 음성 파일 백그라운드 다운로드
오디오 재생 react-native-track-player 백그라운드 재생, 잠금 화면·이어폰 리모트
녹음 후보 비교 후 확정 (M1에서 오디오 세션 전환 동작이 선택 기준) 따라 말하기 녹음
음성 인식 SpeechRecognizer 인터페이스 + 구현체 3종 [변경] 엔진 교체 가능성 확보 → 6.3
알림 notifee (로컬 알림) [신규] 출퇴근 시각 트리거
인앱 결제 react-native-iap 또는 RevenueCat [신규] 구독 + 단품, 영수증 검증
백엔드 Supabase (Auth, Postgres, Storage) 인증·DB·파일 통합
TTS (콘텐츠 제작) 클라우드 TTS 1종 [확인 필요] — 다국 억양 보이스 지원 필수 억양 믹스 요건
원어민 녹음 성우 세션 2~3시간, 핵심 100문장 [신규] TTS로는 대화 리듬 재현 불가
분석 PostHog 또는 Firebase Analytics 리텐션·퍼널·WSU
에러 모니터링 Sentry 크래시·네이티브 오류
테스트 Jest, RNTL, Maestro 단위·컴포넌트·E2E
CI/CD GitHub Actions + Fastlane 빌드·TestFlight·Play 업로드

4. 아키텍처

4.1 원칙

  1. 오프라인 우선: 화면은 항상 로컬 DB를 읽는다. 서버는 동기화 대상일 뿐이다.
  2. 오디오는 앱 전체에서 단일 서비스: 재생·녹음·인식은 audio 모듈 하나가 담당하고 화면은 상태만 구독한다. 상태 전이는 명시적 상태 머신으로 정의한다 → 6.2
  3. 기능 단위 폴더 구조: 기능 하나를 독립적으로 작업·리뷰할 수 있게 한다.
  4. [신규] 불확실한 의존성은 인터페이스 뒤에 둔다: STT 엔진, TTS 제공자, 결제 스토어는 교체 가능해야 한다.
  5. [신규] 학습 로직은 순수 함수로: 루틴 생성, SRS 스케줄링, 발음 판정은 부수 효과 없이 작성해 단위 테스트로 검증한다.

4.2 폴더 구조 [변경 v0.2]

src/
  app/                 # 네비게이션, 프로바이더, 앱 진입점
  features/
    onboarding/
    routine/           # 루틴 생성 + 오늘 화면
    subway/            # 지하철 모드 (화면 / 오디오 2모드)
    walking/           # 걷기 모드 (오디오 플로우)
    progress/          # 스트릭, 여행 진행도
    paywall/           # 결제·구독                        [신규]
  domain/                                                # [신규]
    srs/               # 간격 반복 스케줄러 (순수 함수)
    routine-planner/   # 루틴 배치 알고리즘 (순수 함수)
    scoring/           # 발음 판정 규칙 (순수 함수)
  services/
    audio/
      player.ts        # TrackPlayer 래퍼
      recorder.ts      # 녹음
      session.ts       # 오디오 세션 전환 (iOS/Android)   [신규]
      machine.ts       # 오디오 상태 머신                 [신규]
    speech/                                              # [신규]
      SpeechRecognizer.ts    # 인터페이스
      ios-speech.ts          # SFSpeechRecognizer
      android-speech.ts      # SpeechRecognizer
      whisper.ts             # 폴백 후보 (벤치마크용)
    notifications/     # 로컬 알림 스케줄링               [신규]
    billing/           # 인앱 결제                        [신규]
    sync/              # 콘텐츠 다운로드, 기록 업로드
    db/                # op-sqlite 스키마, 마이그레이션, 쿼리
    api/               # Supabase 클라이언트
    analytics/
  shared/
    ui/                # 공통 컴포넌트, 디자인 토큰
    utils/
native/                # 커스텀 네이티브 모듈 (STT 브리지 등)
scripts/
  content/             # 콘텐츠 생성·검증·TTS 변환·업로드

핵심 변경: domain/을 신설해 학습 로직을 UI·플랫폼에서 완전히 분리한다. SRS·루틴 배치·판정 규칙은 React도 네이티브도 모르는 순수 TypeScript로, Node에서 바로 테스트한다.

4.3 데이터 흐름

[Supabase]  ── 콘텐츠 패키지(JSON + 음성) ──▶ [다운로드 큐] ──▶ [로컬 DB + 파일]
                                                                 │
                                                    ┌────────────┴────────────┐
                                                    ▼                         ▼
                                          [domain/routine-planner]      [화면·오디오]
                                                    ▲                         │
                                                    │                         │
                                          [domain/srs] ◀── Mastery ◀──────────┘
                                                                              │
[Supabase]  ◀── 학습 기록 배치 업로드 ── [미전송 기록 큐] ◀────────────────────┘

5. 데이터 모델 [변경 v0.2]

테이블 서버 로컬 비고
users / profiles ✅ 일부 캐시 출퇴근 정보, 레벨 추정치, 모드 선호
travel_goals ✅ ✅
courses / situations / lessons ✅ ✅ 읽기 전용, 패키지로 내려받음. tier(free\|pro) 추가
expressions ✅ ✅ accent, register 추가
curveballs ✅ ✅ 신규 — 예상 밖 되물음 + 대응
dialogues / quiz_items ✅ ✅ 음성은 파일 경로로 저장
progress ✅ ✅ 로컬 우선 기록
mastery ✅ ✅ 신규 — SRS 상태(level, next_review_at)
speaking_logs ✅ ✅ 녹음 원본은 서버 전송 안 함. result(pass\|unclear), snr_db
daily_routines ✅ ✅ mode, review_expression_ids 추가
streak_state ✅ ✅ 신규 — 프리즈 잔량 포함
subscriptions ✅ 캐시 신규 — 플랜, 만료일, 스토어 영수증
sync_queue — ✅ 미전송 기록

6. 핵심 기술 과제

각 과제는 해당 마일스톤 시작 전에 스파이크로 먼저 확인한다.

6.1 블루투스 오디오 매트릭스 [신규 v0.2 — 최우선 리스크]

문제: 블루투스 이어폰에서 마이크를 켜는 순간 코덱이 A2DP(스테레오, 고음질)에서 HFP/SCO(모노 16kHz, 전화 음질)로 전환된다. 원어민 음성이 갑자기 전화 통화 음질이 되고, 전환에 0.5~2초 무음이 발생한다. AirPods, 갤럭시 버즈 모두 해당된다.

영향: 발음을 따라 하는 앱에서 들려주는 소리가 전화 음질이면 제품의 존재 이유가 없어진다. v0.1에는 "세션 전환 시 끊김·지연 확인" 한 줄로만 있었다.

정본 — 12케이스의 축·케이스 식별자·현장 순서·예외 처리·기록 양식·판정 규칙의 정본은 ogamyeo-app docs/m1-r2-case-matrix.md 이고, 기계가 읽는 같은 표가 같은 저장소 src/lib/m1/cases.ts(R2_CASES)에 있다(두 표가 어긋나면 cases.test.ts 가 실패한다). 아래는 그 정본에서 옮겨 온 것이고, 값이 어긋나면 정본이 이긴다. 회차 당일 손으로 채우는 칸은 이 저장소 docs/실기기_실측_회차_기록양식.md 에 있다.

[이력] 축은 2026-09-16 ogamyeo-app #19(PR #27)로 재확정됐다 — v0.2 초안이 축으로 쓴 이어폰 모델 3종과 조합 4종(A~D)은 폐기되고 OS 2 × 오디오 세션 구성 2 × 상황 3 이 됐다. 분모 12는 그대로다(M1 게이트 판정문이 R2 실패 조합 N / 12 로 나간다).

검증 매트릭스 (OS 2 × 오디오 세션 구성 2 × 상황 3 = 12케이스 · C01~C12)

케이스 OS 세션 구성 상황 반복
C01 iOS A2DP유지 재생만 1
C02 iOS A2DP유지 녹음 전환 왕복 3
C03 iOS A2DP유지 통화 인터럽트 2
C04 iOS HFP허용 재생만 1
C05 iOS HFP허용 녹음 전환 왕복 3
C06 iOS HFP허용 통화 인터럽트 2
C07 Android A2DP유지 재생만 1
C08 Android A2DP유지 녹음 전환 왕복 3
C09 Android A2DP유지 통화 인터럽트 2
C10 Android HFP허용 재생만 1
C11 Android HFP허용 녹음 전환 왕복 3
C12 Android HFP허용 통화 인터럽트 2

측정은 24회, 칸은 12개다. 케이스 이름 C01~C12 는 정본과 같은 글자로만 쓴다 — 현장 기록이 이 식별자로 코드·DB(mode_switch)에 붙는다.

세션 구성 2종 (축은 결과가 아니라 앱이 요청하는 구성이다. 요청대로 되는지가 R2 의 본문이다)

이름 뜻
A2DP유지 재생은 A2DP 유지 · 녹음은 폰 내장 마이크. HFP 를 아예 요청하지 않는다
HFP허용 녹음을 블루투스 헤드셋 마이크로. HFP/SCO 전환을 허용한다

OS별 세션 설정값(iOS AVAudioSession 옵션 · Android setCommunicationDevice/SCO)은 정본 §1 에 있다. 여기 옮겨 적지 않는다 — 두 벌이 되면 갈라진다.

상황 3종: 재생만 — 세션 구성만으로 경로가 내려앉는지 보는 기준선 · 녹음 전환 왕복 — 재생 중 녹음 시작 → 발화 → 녹음 종료 → 재생 지속 · 통화 인터럽트 — 재생·녹음 중 수신 전화.

이어폰은 축이 아니다 — 한 벌 = 회차 하나. 이어폰 모델을 축에 넣으면 12칸이 24·36칸이 되고 N / 12 분모가 깨진다. 그래서 이어폰은 회차 파라미터로 올렸다: 이어폰을 바꾸면 같은 12칸을 한 벌 더 돌린다. 한 칸에 실측이 여러 건이면 하나라도 내려앉으면 그 칸은 실패다(judgeR2 규칙). A2DP 코덱 종류(SBC/AAC/LDAC)·볼륨·걷는 중인지도 축이 아니라 회차 헤더에 적는 값이고, 화면 꺼짐· 백그라운드는 축이 아니라 별도 관찰 항목이다(정본 §1·§7-3).

v0.2 초안의 A~D 조합은 어디로 갔나 — A → 세션 구성 A2DP유지, B → HFP허용, C(전환 지연) → 상황 녹음 전환 왕복(전환 ms 는 축이 아니라 기록 칸으로 잰다). D(LE Audio)는 새 축에 없다 — 정본이 LE Audio 를 다루지 않으므로 여기서 지어내지 않는다. 재려면 정본(m1-r2-case-matrix.md §10 '아직 안 닫힌 것')에 미결로 올려야 한다.

[결정] 이 결과가 걷기 모드의 최종 설계를 결정한다. M1 게이트 조건에 포함.

6.2 오디오 상태 머신 [신규 v0.2]

"오디오는 단일 서비스" 원칙만으로는 구현이 안 된다. 상태 전이를 명세로 못 박는다.

IDLE ──▶ LOADING ──▶ PLAYING_PROMPT ──▶ BEEP ──▶ RECORDING ──▶ EVALUATING ──┐
  ▲                        │                         │             │         │
  │                        ▼                         ▼             ▼         │
  │                   INTERRUPTED ◀──────────────────┴─────────────┘         │
  │                        │                                                 │
  │                        ├── 자동 재개 ──▶ (직전 상태로 복귀)                │
  │                        └── 사용자 재개 대기 ──▶ PAUSED                     │
  └──────────────────────── 세션 종료 ◀─────────────────────────────────────┘

인터럽트 정책 표 [확정 필요 — M1에서 실측 후]

인터럽트 소스 재생 중 녹음 중 종료 후
전화 수신 일시정지 녹음 폐기 사용자 확인 후 재개
타 앱 재생 (음악·영상) 일시정지 녹음 폐기 자동 재개 안 함
알람 일시정지 녹음 폐기 자동 재개
내비 안내음 (ducking) 볼륨 낮춤 녹음 유지 자동 복원
이어폰 분리 즉시 일시정지 녹음 폐기 재연결 시 확인 후 재개
블루투스 끊김 일시정지 녹음 폐기 재연결 대기 30초
앱 백그라운드 전환 재생 유지 녹음 유지 —
화면 켜짐 (걷기 모드) 일시정지 일시정지 명시적 탭으로 재개 (안전)

6.3 음성 인식 — 엔진 추상화 [변경 v0.2]

엔진별 제약

엔진 장점 제약
iOS SFSpeechRecognizer 무료, 통합 쉬움 온디바이스 지원이 기기·언어별로 다름 (supportsOnDeviceRecognition 확인 필수). 네트워크 모드는 1분/요청 및 시간당 요청 제한이 있어 상시 사용 부적합
Android SpeechRecognizer 무료 제조사 구현 편차 큼 (삼성/구글/샤오미 동작 상이). 오프라인 인식은 언어팩 설치 여부에 의존
whisper.cpp + CoreML/NNAPI 정확도 우수, 완전 오프라인 앱 용량 +40~150MB, 발열·배터리 부담. 30분 걷기 기준 배터리 실측 필요

[결정] 인터페이스로 추상화한다.

interface SpeechRecognizer {
  isAvailable(): Promise<boolean>
  supportsOnDevice(): Promise<boolean>
  recognize(opts: { maxDurationMs: number; locale: string }): Promise<{
    transcript: string
    confidence: number   // 엔진이 미제공 시 -1
    snrDb: number        // 자체 측정
  }>
  cancel(): void
}

MVP는 네이티브 엔진, Whisper는 폴백 후보로 벤치마크만 수행한다(M1). 제품 코드는 엔진을 모른다.

6.4 따라 말하기 판정 [변경 v0.2]

흐름: 원어민 음성 재생 → 비프 → 녹음·인식(최대 N초) → 판정 → 음성 피드백

판정 로직 (domain/scoring, 순수 함수)

1. SNR 측정 (RMS 기반)
2. SNR < 임계값(소음 환경) OR confidence < 0.4
     → 이진 판정만. unclear면 "통과" 처리 (관대하게)
3. SNR >= 임계값 (조용한 환경) AND Pro 구독
     → 목표 문장과 토큰 단위 비교, 단어별 하이라이트 제공
4. 어떤 경우에도 숫자 점수는 표시하지 않는다

설계 근거: 도심 인도 70~80dB(버스 지나가면 85dB), 걷기 중 불규칙 호흡, 비원어민 발음 → 온디바이스 STT의 WER이 조용한 환경에서도 15~25%, 소음 환경에서 40% 이상. 사용자가 잘 말해도 틀렸다고 나온다. 점수를 보여주면 그 순간 신뢰가 무너지고, 무조건 관대하면 "대충 본다"가 된다. 점수라는 형식 자체가 이 환경에 맞지 않는다.

[결정] false negative(잘 말했는데 실패 처리)를 false positive보다 훨씬 나쁜 오류로 취급한다. 목표는 평가가 아니라 발화량이다.

6.5 백그라운드 오디오 및 플랫폼 제약 [보강 v0.2]

iOS - Background Modes의 Audio 활성화 - AVAudioSession 카테고리: 재생 전용(.playback) ↔ 재생+녹음(.playAndRecord) 전환 정책 확정 필요 - .playAndRecord 고정 시 블루투스 음질이 계속 HFP에 묶임 → 6.1 매트릭스로 결정 - 심사: 백그라운드 오디오 사용 사유, 마이크 권한 문구 명확화

Android [신규 v0.2] - 포그라운드 서비스 + 미디어 알림 - Android 14 (API 34) 필수 요건: - 서비스 타입 선언: mediaPlayback, microphone - 전용 권한: FOREGROUND_SERVICE_MEDIA_PLAYBACK, FOREGROUND_SERVICE_MICROPHONE - 백그라운드에서 시작한 서비스의 마이크 접근 제한 → 잠금 화면 녹음이 여기서 막힐 가능성 높음. M1 확인 대상 - Doze 모드·배터리 최적화 예외 요청 여부 검토

6.6 SRS (간격 반복) 모듈 [신규 v0.2]

domain/srs — 순수 함수, 플랫폼 무관.

type MasteryLevel = 0 | 1 | 2 | 3 | 4 | 5
const INTERVALS_DAYS = [0, 1, 3, 7, 16, 35]  // level → 다음 복습까지

function schedule(
  current: MasteryLevel,
  result: 'pass' | 'unclear',
  now: Date
): { level: MasteryLevel; nextReviewAt: Date }

function buildReviewQueue(
  mastery: MasteryRecord[],
  now: Date,
  capacity: number
): ExpressionId[]   // 기한 지난 것 우선, 오래된 순

루틴 배분 규칙 - 기본: 신규 40% / 복습 60% - 첫 주: 신규 70%에서 시작해 4주에 걸쳐 40%로 이동 - 여행 D-day 페이스가 부족하면 복습 비중을 40%까지 축소 (진도 우선) - 걷기 구간 문장은 복습 큐에서 우선 추출 (걷기 = 출력 연습 구간)

테스트: 간격 진급·강등, 큐 우선순위, 용량 초과 시 절단, D-day 페이스 조정 — 모두 단위 테스트.

6.7 루틴 생성 알고리즘

6.8 오프라인 다운로드

6.9 로컬 알림 [신규 v0.2]


7. 마일스톤 [재구성 v0.2]

7.1 전체 일정

마일스톤 기간 산출물 완료 기준
V. 선행 검증 2주 랜딩·목업, 설문 30명, 인터뷰 5건, 가격 민감도, 대기자 명단 발화 수용도 60%↑, 대기자 30명↑
M0. 환경 세팅 (V와 병렬) 1주 RN 프로젝트, 린트·포맷, CI, Supabase, 디자인 토큰, CLAUDE.md 실기기에서 빈 앱 실행, CI 통과
M1. 스파이크 2주 블루투스 매트릭스 12케이스, 백그라운드 재생, 녹음+STT 전환, 소음 인식률, 배터리 실측, Whisper 벤치마크 게이트 6개 조건 → 8장
⛳ 게이트 리뷰 — 진행 / 대안 전환 / 중단 판정 전원 참석
M2. 콘텐츠 파이프라인 1주 DB 스키마, 스타일 가이드, 콘텐츠 JSON 형식, TTS·녹음 파이프라인, 샘플 10상황 스크립트 1회 실행으로 레슨 패키지 배포
M3. 걷기 모드 (핵심) 2주 오디오 상태 머신, 따라 말하기, 이진 판정, Curveball, 이어폰 제어, 안전 로직 화면 안 보고 걷기 루틴 완주
M4. 온보딩·루틴·SRS 1.5주 온보딩, 루틴 생성기, SRS 모듈, 오늘 화면 입력에 따라 루틴 변화, 복습 큐 동작, 단위 테스트 통과
M5. 지하철 모드 2주 화면 모드(카드·대화·Curveball·퀴즈), 오디오 모드, 중단 복구 두 모드 모두 레슨 1개 완주
M6. 오프라인·동기화 1주 다운로드 큐, 로컬 DB, 배치 업로드 비행기 모드 완료 → 복구 후 서버 반영
M7. 기록·알림·결제·분석 1.5주 스트릭+보호장치, 이어폰 없는 날 모드, 로컬 알림, 인앱 결제, 분석 이벤트, Sentry 알림 동작, 결제 완주, WSU 수집 확인
M8. 베타 2주 TestFlight / Play 내부 테스트 테스터 30명 2주 사용, 지표 수집

합계: 약 16주 (선행 검증 2주 + 개발 14주. M0는 V와 병렬)

7.2 v0.1 대비 배치 변화

v0.1:  M0 → M1 → M2 → M3 온보딩 → M4 지하철 → M5 오프라인 → M6 걷기 → M7 → M8
                                                              ↑ 9주차에 최대 리스크

v0.2:  V ∥ M0 → M1(2주) → ⛳게이트 → M2 → M3 걷기 → M4 → M5 → M6 → M7 → M8
                            ↑ 3주차에 최대 리스크 판정

7.3 2주 늘어난 것에 대한 근거

선행 검증 2주 + M1 1주 확대 = 4주 증가(M0 병렬로 실질 +3주). 대신: - 9주차에 프로젝트가 무너질 위험을 3주차로 앞당겨 확인한다. - 게이트에서 조기 전환하면 3주만 쓰고 방향을 튼다 (v0.1이라면 9주 소모). - 수요·가격이 개발 전에 확정되어 콘텐츠 분할과 페이월 설계를 다시 하지 않는다.

7.4 일정 산정의 전제 [신규 v0.2 — 확인 필요]

이 구성(구현=Claude, 검증=오너)의 병목은 코드 작성이 아니라 실기기 검증 루프다.

전제: 오너가 주당 리뷰·실기기 테스트에 최소 10시간을 쓸 수 있어야 위 일정이 성립한다. 주 5시간이면 20주로 재산정해야 한다. 착수 전 확정 필요.

7.5 이후


8. M1 게이트 — 진행 판정 기준 [신규 v0.2]

M1 스파이크 종료 시 아래를 충족하지 못하면 걷기 모드 설계를 재검토한다. 이 게이트를 통과하지 못한 채 M2 이후로 진행하지 않는다.

# 조건 기준
G1 블루투스 재생 음질 유지 12케이스 중 최소 1개 조합에서 녹음 전환 후에도 A2DP급 재생 유지
G2 세션 전환 지연 재생 → 녹음 전환 1초 이내
G3 소음 환경 인식률 70dB 배경음에서 이진 판정 정확도 85% 이상
G4 백그라운드 재생 iOS·Android 모두 잠금 화면에서 30분 연속 재생
G5 이어폰 버튼 재생/일시정지 + 다음, 최소 2종 동작
G6 배터리 30분 걷기 모드에서 소모 8% 이하

[확인 필요] G1·G2 의 기준 문장이 R2 정본과 다르다. 2026-09-19 정정에서 6.1 의 축만 정본에 맞췄고, 게이트 조건은 내용 결정이라 건드리지 않았다. 어긋난 자리는 둘이다 — ① G1 "12케이스 중 최소 1개 조합" vs 정본 §6-2 의 회차 판정 G(재생만 칸 + C02·C08 이 두 OS 모두 통과 · 재생만 4칸에서 C04·C10 을 뺄지는 정본 §10 미결), ② G2 "전환 1초 이내" vs 정본 §6-1(이번 회차는 전환 ms 를 판정에 쓰지 않음 · transitionMsBudget: null). 어느 쪽으로 맞출지는 신우섭 CTO 가 M1 게이트(W3) 판정 전에 정한다. 그때까지 회차 판정은 정본 §6-2 가 이긴다(정본은 측정 시작 뒤 변경 금지).

미충족 시 대안 (우선순위 순)

순위 대안 영향
1 유선 이어폰 권장 + 블루투스는 재생 전용 모드로 축소 타깃 축소, 온보딩에 이어폰 안내 추가
2 녹음 생략 → "따라 말하기(판정 없음)" + 자기 평가 탭 발화는 유지, 피드백 상실. WSU 측정은 자가 보고로
3 걷기 모드를 듣기 전용으로 축소, 발화 연습은 귀가 후 홈 세션으로 이동 핵심 차별점 상실 → 제품 포지셔닝 재정의 필요

대안 3에 도달하면 이 제품은 "출퇴근 리스닝 앱"이 된다. 그 시장에서 이길 수 있는지를 별도로 판단해야 한다. 사전에 인지하는 것이 목적이다.


9. Claude와의 작업 방식

역할 분담

Claude 오너
코드 작성, 테스트 작성, 리팩토링 요구사항 확정, 코드 리뷰
콘텐츠 초안·스크립트 작성 실기기 테스트 (오디오·이어폰·지하철·길거리)
문서(CLAUDE.md, README) 유지 계정·키 관리, 스토어 배포 승인
순수 로직(SRS·루틴·판정) 단위 테스트 원어민 성우 섭외·녹음 세션

규칙

한계 인지


10. 테스트 전략

자동화

실기기 체크리스트 (수동) [보강 v0.2]

오디오 - [ ] 화면 잠금 후 30분 연속 재생 유지 - [ ] 이어폰 버튼 3종 동작 (재생/일시정지, 다음, 이전) - [ ] 블루투스 매트릭스 12케이스 (OS 2 × 세션 구성 2 × 상황 3 = C01~C12 · 이어폰은 축이 아니라 한 벌 = 회차 하나) — 6.1 - [ ] 재생 → 녹음 전환 지연 실측 - [ ] 통화 수신 → 종료 후 재개 - [ ] 블루투스 연결 끊김·재연결 - [ ] 타 앱 재생 / 알람 / 내비 안내음 인터럽트 — 6.2 정책 표 검증

환경 - [ ] 지하철 소음 환경 인식률 (실제 탑승) - [ ] 도심 인도 70~80dB 환경 인식률 (실제 보행) - [ ] 속삭임 볼륨 인식률 — 사회적 장벽 대응의 전제 - [ ] 비행기 모드(터널 가정)에서 레슨 진행 - [ ] 만원 지하철에서 오디오 모드 조작 가능 여부

성능·안전 - [ ] 배터리 소모 (30분 걷기 모드, 목표 8% 이하) - [ ] 발열 (Whisper 사용 시) - [ ] 걷기 모드 중 화면 켜짐 → 자동 일시정지 동작

알림·결제 - [ ] 출퇴근 시각 알림 정확도, 주말 제외 - [ ] 인앱 결제 구매·복원·만료


11. 배포


12. 리스크 [보강 v0.2]

# 리스크 영향 대응
R1 [신규] 블루투스 녹음 전환 시 재생 음질 급락 (A2DP → HFP) 치명 M1 매트릭스 12케이스(C01~C12 · 6.1) 실측, G1 게이트 조건. 미충족 시 8장 대안
R2 [신규] 길에서 발화하는 사회적 장벽으로 걷기 모드 미사용 치명 선행 검증 2주. "통화하는 척" UX, 속삭임 허용
R3 소음 환경 STT 품질·기기 편차 높음 이진 판정 + 관대한 통과, SNR 기반 분기, 엔진 추상화로 교체 가능
R4 [신규] Android 14 백그라운드 마이크 제한 높음 M1에서 확인. FGS 타입·권한 선언. 불가 시 잠금 녹음은 2단계에서도 제외
R5 [신규] 오너 실기기 검증 시간 부족 높음 주당 10시간 전제 명시. 미달 시 20주로 재산정
R6 RN 버전 업·New Architecture 라이브러리 비호환 중간 M0에서 핵심 라이브러리 호환 확인, 버전 고정
R7 네이티브 이슈를 Claude가 실기기 없이 디버깅 중간 오너가 로그·재현 절차 공유, Sentry 활용
R8 콘텐츠 제작이 개발보다 늦어짐 중간 M2 이후 콘텐츠 제작을 개발과 병행. 스타일 가이드로 검수 속도 확보
R9 iOS SFSpeechRecognizer 네트워크 모드 요청 제한 중간 온디바이스 우선, 미지원 기기는 Whisper 폴백 검토
R10 인앱 결제 심사 반려 (구독 정보 미비) 낮음 스토어 요구 항목 체크리스트화

13. 착수 전 확인 필요

최우선 (일정·범위를 좌우)

예산

계정·환경

제품 결정