Chapter 04

드디어 코드
— 분석하고, 이해하고, 구현하다

계획과 워크플로우가 완성됐습니다. 이제 진짜 시작입니다.
빌드 환경을 구축하고, 앱을 처음 실행하고,
서버 알고리즘을 역분석해 Android 코드로 바꿉니다.

진행 타임라인

4개 Phase — 여기까지 온 과정

구상과 계획이 끝난 뒤 실제 개발에 진입하기까지, 총 4개 Phase를 순서대로 진행했습니다.

Date
02-27
✓ 완료
Phase 0 — 빌드 환경 구축
Android Studio 설치, 의존성 세팅, 첫 빌드 성공. Build Variants (developStg / productReal) 구조를 파악하고 에뮬레이터와 실기기 실행 방법을 확인했습니다.
Android Studio Build Variants APK 빌드 성공
Date
02-25 ~ 03-03
✓ 완료
Phase 1A — 코드 분석 (CodeArchaeologist)
기존 Android 코드베이스 전체를 AI 에이전트 CodeArchaeologist가 분석. 현재 포토북 생성 프로세스 흐름, 모듈 구조, 핵심 파일을 파악했습니다. 에뮬레이터에서 앱을 처음 실행했고, 내부망 서버 접근 이슈를 Build Variant 변경으로 해결했습니다.
에뮬레이터 첫 실행 코드 구조 파악 CodeArchaeologist
Date
03-04
✓ 완료
Phase 1B 준비 — API 스펙 & 연결 포인트 파악
Claude가 Notion MCP로 API 문서를 자동 조회. AI 포토북 추천 API 스펙을 발견하고, 기존 포토북 제작 플로우의 전체 흐름을 이해했습니다. 핵심 발견: imageKey는 서버가 생성하므로, 우리가 만들어야 할 것은 클러스터링 + 스토리 UI뿐입니다.
Notion MCP 자동 조회 API 스펙 확인 연결 포인트 발견
Date
03-05
→ 진행중
Phase 1B — Domain 레이어 구현 시작
서버 Python 코드(clustering_story 알고리즘)를 역분석하고 Android에 맞게 재설계. Clean Architecture 기반 Domain 레이어 Kotlin 코드를 작성하기 시작했습니다.
알고리즘 역분석 Kotlin Domain 모델 Clean Architecture
첫 실행 에피소드

에뮬레이터에서 앱이 실행됐다 — 근데 에러가?

Android Studio에서 Medium Phone API 36.1 에뮬레이터를 띄우고 앱을 처음 실행했습니다. 앱이 뜨는 순간은 성공처럼 보였지만, 화면에는 에러가 표시됐습니다.

🔴
에러 발생
net::ERR_NAME_NOT_RESOLVED
앱이 연결하려던 스테이징 서버 stg1-en.snaps.com이 DNS 조회에 실패. 회사 내부망에서만 접근 가능한 서버였습니다.
🔍
원인 파악
Build Variants 문제
선택된 빌드가 내부망 스테이징 서버를 바라보고 있었습니다. developStg(내부망) vs productReal(외부 서버) 차이를 이해하게 됩니다.
해결
Build Variant 변경
Android Studio Build Variants 탭에서 developRealDebug로 변경. 외부 접근 가능한 운영 서버에 연결되어 앱이 정상 동작하기 시작했습니다.
AI 활용 포인트
에러 메시지를 그대로 Claude Code에 전달했더니, 즉시 원인을 "내부망 DNS 접근 불가"로 진단하고 Build Variants 개념과 해결책을 안내했습니다. 모바일 개발 경험이 없어도 오류를 막힘없이 해결할 수 있었습니다.
API 스펙 자동 조사

Notion 문서를 AI가 직접 읽다

백엔드 API 스펙이 Notion에 있었습니다. Claude가 Notion MCP를 통해 직접 문서를 조회하고, 핵심 스펙을 파악해 정리해줬습니다.

01
📋
Notion MCP 연결
Claude가 직접 Notion API에 접근해 문서 검색
02
🔍
API 스펙 파악
포토북 추천 API, 요청/응답 구조 자동 분석
03
💡
핵심 발견
imageKey는 서버 업로드 후 자동 생성됨을 확인
04
🎯
범위 확정
우리가 만들 것: 클러스터링 + 스토리 UI만
발견한 핵심 포토북 생성 플로우:
Flow 기존 포토북 생성 프로세스
[사진 선택]
    ↓
[Task 1]  projectCode 발급
    POST /v1/project
    ↓
[Task 2]  썸네일 업로드 (병렬, 최대 3개 동시)
    POST /v1/project/{projectCode}/thumbnailFile
    → 응답: imageYear + imageSequence = imageKey ← 서버가 생성!
    ↓
[Task 3]  AI 추천 API 호출
    POST /v1.0/w/api/rcmdsys/design/photobook
    → imageKey, GPS, exifDate, 얼굴인식 데이터 전송
    → 응답: AI가 생성한 포토북 레이아웃 템플릿
    ↓
[Task 4~7] 템플릿/리소스 다운로드
    ↓
[편집 화면] SmartRecommendBookMainActivity
핵심 인사이트 — 우리가 만들 것
imageKey는 서버가 자동 생성합니다. 우리는 디바이스 갤러리 스캔 → EXIF 클러스터링 → 스토리 UI 표시만 만들면 됩니다. 사용자가 스토리를 선택하면, 기존 포토북 제작 플로우(SmartRecommendBookMakingActivity)에 이미지 리스트를 넘겨주는 것으로 연결됩니다.
서버 알고리즘 역분석

Python 서버 코드를 읽어 Android 알고리즘을 설계하다

기존 AI 추천 서버의 clustering_story 알고리즘을 Claude가 완전히 분석했습니다. 서버 로직을 이해하고, 모바일 환경에 맞게 재설계했습니다.

서버 알고리즘 5단계 구조:
1
유사 이미지 제거 _similarity_filtering
exifDate 기준 30초 이내에 찍힌 사진들을 그룹화, 그룹당 대표 1장만 유지. 연속 촬영(버스트) 중복 제거.
2
저해상도 제거 _lowResolutionCheck
oripqW × oripqH < 200² 픽셀인 이미지 제외. 너무 작은 이미지는 포토북에 적합하지 않음.
3
핵심 클러스터링 _clustering
GPS 있는 이미지 → 지역(국내=province, 해외=state) 기반 그룹핑 / GPS 없는 이미지 → 12~18시간 간격으로 시간 클러스터링. photoRange [40~300장] 필터링 적용.
4
파일명 날짜 보강 story_boost
아직 분류되지 않은 이미지 중 파일명에서 날짜 추출 가능한 것 추가. 조건: 40장 이상, exif 있는 사진 5장 이상.
5
결과 출력
photobooks[] — {rank, title:{nation, city, date}, images[], imagesLength} 형태로 반환.
Android 최적화 재설계

서버 로직을 모바일 환경에 맞게 다시 설계하다

서버 알고리즘을 그대로 쓸 수는 없습니다. 모바일 환경(오프라인, 성능, MediaStore API)에 맞게 차이점을 분석하고 재설계했습니다.

항목 서버 기준 Android 재설계
GPS 처리 역지오코딩된 {state, province} 값 사용 GPS 좌표 거리 계산으로 클러스터링 (오프라인 동작, 10,000장도 빠름). 지역명은 타이틀 표시용으로만 역지오코딩.
최소 사진 수 40장 이상 10~15장 — 짧은 나들이, 소규모 이벤트도 포함
날짜 출처 파일명에서 날짜 추출 (story_boost) MediaStore.DATE_TAKEN — 거의 100% 신뢰 가능. 단순화 가능.
노이즈 필터 저해상도 제거 스크린샷 제거(MIME_TYPE, 파일 경로 기반) + 저해상도 제거
클러스터링 순서 GPS 우선 → 시간 보완 시간 기반 1차 분리 → GPS 기반 2차 보정 (더 빠름)
Domain 레이어 구현

Clean Architecture — Domain 모델부터 잡자

알고리즘 설계가 완성됐습니다. 이제 코드를 작성합니다. Android Clean Architecture 원칙에 따라 가장 먼저 Domain 레이어를 구현했습니다.

📷
PhotoMetadata.kt
사진 1장의 메타데이터. MediaStore에서 읽어올 필드들을 정의합니다.
PhotoMetadata(id, contentUri, dateTaken, latitude?, longitude?, width, height, orientation, displayName)
📚
PhotoStory.kt
클러스터링된 이벤트 묶음. 포토북 1개의 후보 단위입니다.
PhotoStory(id, rank, title, photos, coverPhoto) StoryTitle(location, dateRange) // "제주도", "2024.12.20 ~ 12.23"
🔌
PhotoStoryRepository.kt
인터페이스 — Data 레이어가 구현할 계약. Domain은 구현체에 의존하지 않습니다.
interface PhotoStoryRepository { fun getPhotoStories(): Single<List<PhotoStory>> }
⚙️
GetPhotoStoriesUseCase.kt
UseCase — 비즈니스 로직의 진입점. ViewModel이 이것을 호출합니다.
class GetPhotoStoriesUseCase( private val repo: PhotoStoryRepository ) { operator fun invoke(params: Unit): Single<List<PhotoStory>> }
다음 단계
Domain 레이어 완성. 다음은 Data 레이어PhotoStoryRepositoryImpl 구현입니다. Android MediaStore 쿼리로 사진을 읽어오고, 클러스터링 알고리즘을 Kotlin으로 구현합니다.
이 단계의 AI 활용 포인트

전문가 없이 가능했던 세 가지 이유

🔌
MCP 연동
Notion 문서를 AI가 직접 읽다
Claude가 Notion MCP를 통해 사내 API 문서를 자동 조회했습니다. 문서를 직접 찾아 읽고 정리하는 시간이 없어졌습니다.
🐍
언어 장벽 제거
Python 서버 코드 → Android 설계
Python을 모르더라도 Claude가 서버 코드를 읽어 알고리즘 로직을 한국어로 설명하고, Android Kotlin으로 어떻게 구현할지까지 제안했습니다.
🏗️
아키텍처 가이드
Clean Architecture 올바르게 적용
기존 코드의 아키텍처 패턴을 분석해, 새 기능을 올바른 레이어 구조에 맞게 작성하도록 안내했습니다. 모바일 경험 없이도 좋은 코드 구조를 유지할 수 있습니다.
★ 이 단계의 핵심
서버 코드를 읽고, API를 조사하고,
Android 코드를 쓴다 — 모두 AI와 함께.
도메인 지식도 언어 장벽도 없었습니다. AI가 서버 Python 코드를 분석해 Android 알고리즘을 설계하고, Notion 문서에서 API를 조회하며, Clean Architecture에 맞는 코드를 직접 작성했습니다. 사람은 방향과 판단만 제공했습니다.
🛠️
이 단계의 산출물
Phase 0~1A 완료 + Phase 1B 진행중 (Domain 레이어 작성)
빌드 환경 구축 → 앱 첫 실행 → 코드 분석 → API 조사 → 서버 알고리즘 역분석 → Domain 모델 설계까지 완료. 다음은 Data 레이어(MediaStore + 클러스터링 알고리즘 구현)입니다.