홈 화면 정책 문서
(2026년 3월 11일 updated)
1. 문서 개요
- 목적: 홈 화면의 콘텐츠 노출 방식 및 동작 원칙 정의
- 적용 화면: 앱 내 첫 진입 시 메인 홈 화면
- 대상: 기획, 운영, 개발, QA 등 관련 부서
2. 콘텐츠 노출 정책
- 홈 화면은 운영 큐레이션 중심으로 구성되며, 운영자가 지정한 섹션/콘텐츠 순서에 따라 노출
- 콘텐츠 수급 방식: 수동 등록(CMS)
- 섹션 내 콘텐츠가 부족할 경우:
- 5개 이하일 경우 해당 섹션 비노출 처리
- 콘텐츠 없음 → 섹션 자체 미노출
2.1 종합 홈 API
홈 화면 진입 시 단일 API를 통해 배너, 필터, 랭킹, 큐레이션 등 전체 데이터를 한번에 조회할 수 있다.
| 항목 | 내용 |
|---|---|
| 엔드포인트 | GET /api/home/ |
| 뷰 | ComprehensiveHomeView |
| 설명 | 홈 화면에 필요한 모든 섹션 데이터를 종합적으로 반환 |
| 인증 | 선택 (비로그인 시에도 기본 데이터 제공) |
3. 필터 동작 정책
3.1 필터 모델 (HomeFilter)
홈 화면 필터는 HomeFilter 모델(TranslatableModel)을 기반으로 관리된다.
| 필드 | 타입 | 설명 |
|---|---|---|
filter_type | CharField | 필터 유형: tab, series, reading_level |
name | TranslatedField | 다국어 필터 명칭 |
value | CharField | API 쿼리에 사용되는 필터 값 |
code | CharField (unique) | 클라이언트 측 필터링용 고유 코드 |
display_order | IntegerField | 노출 순서 |
is_active | BooleanField | 활성화 여부 |
3.2 필터 유형 및 코드 체계
| filter_type | 설명 | code 예시 |
|---|---|---|
tab | 브랜드 탭 (디즈니/픽사) | TAB_DISNEY, TAB_PIXAR |
series | 시리즈(프랜차이즈) 필터 | 시리즈별 고유 코드 |
reading_level | 읽기 단계 필터 (연령대, 렉사일) | AGE_0_3, AGE_4_7, AGE_8_10, AGE_FAMILY, LEX_BR_200, LEX_200_500 등 |
3.3 필터 API
| 항목 | 내용 |
|---|---|
| 엔드포인트 | GET /api/home/filters/ |
| 설명 | 활성화된 모든 홈 필터 목록 조회 |
| 정렬 | filter_type → display_order → id 순 |
3.4 필터 동작 방식
-
필터 종류:
- 탭: 디즈니 / 픽사
- 풀스크린 셀렉터 1: 시리즈 (프랜차이즈)
- 풀스크린 셀렉터 2: 읽기 단계 (연령, 렉사일 지수)
-
동작 방식:
- 모든 필터는 AND 연산 (모든 조건을 만족하는 콘텐츠만 노출)
- 필터 조작 시 즉시 반영됨 (Apply 버튼 없음)
- 조합 결과가 0개일 경우 → "검색 결과가 없습니다" 안내
-
초기 진입 시 기본값: 전체 노출 (필터 선택 없음)
-
기준 메타데이터:
- 운영에서 제공 > 드라이브 내 스프레드 시트 참조
-
섹션 적용 여부: 모든 큐레이션 섹션에 필터 조건 적용됨
-
연령 범위:
- 0-3세 / 4-7세 / 8-10세 / 온가족
-
렉사일 범위:
- BR-200L / 200L-500L / 470L-620L / 590L-790L / 695L-910L
4. 배너 정책
4.1 배너 모델 (Banner)
배너는 Banner 모델(TranslatableModel)을 통해 관리되며, 다국어를 지원한다.
| 필드 | 타입 | 설명 |
|---|---|---|
title | TranslatedField | 다국어 배너 제목 |
image_url | TranslatedField (URL) | 다국어 배너 이미지 URL (CDN 또는 업로드) |
link_url | TranslatedField (URL) | 다국어 링크 URL |
target_type | TranslatedField | 언어별 콘텐츠 타입 (book, collection) |
content_id | TranslatedField (BigInteger) | 언어별 Book ID |
curation_id | TranslatedField (BigInteger) | 언어별 Curation ID |
link_type | CharField | 링크 유형: book / external / none / collection |
content_type | CharField | 콘텐츠 유형: book / event / collection |
sequence | IntegerField | 노출 순서 |
is_active | BooleanField | 활성화 여부 |
start_date | DateTimeField | 배너 노출 시작일 |
end_date | DateTimeField (nullable) | 배너 노출 종료일 |
4.2 배너 API
| 항목 | 내용 |
|---|---|
| 엔드포인트 | GET /api/home/banners/ |
| 설명 | 현재 활성화된 배너 목록 조회 |
| 정렬 | sequence 순 |
| 노출 조건 | is_active=True 이며, 현재 시각이 start_date ~ end_date 범위 내 |
4.3 배너 클릭 시 동작
| link_type | 동작 |
|---|---|
book | content_id에 해당하는 도서 상세로 이동 |
collection | curation_id에 해당하는 큐레이션 모아보기로 이동 |
external | link_url 외부 브라우저 또는 인앱 브라우저로 이동 |
none | 클릭 불가 (정보형 배너) |
5. 테마 큐레이션 정책
5.1 큐레이션 모델 (Curation)
큐레이션은 Curation 모델(TranslatableModel)을 통해 관리된다.
| 필드 | 타입 | 설명 |
|---|---|---|
title | TranslatedField | 다국어 큐레이션 제목 |
description | TranslatedField | 다국어 큐레이션 설명 |
curation_type | CharField | 큐레이션 유형: theme (테마) / collection (모아보기) |
books | M2M (through CurationItem) | 큐레이션에 포함된 도서 목록 |
display_order | IntegerField | 노출 순서 |
is_active | BooleanField | 활성화 여부 |
5.2 큐레이션 아이템 (CurationItem)
| 필드 | 타입 | 설명 |
|---|---|---|
curation | ForeignKey | 소속 큐레이션 |
book | ForeignKey | 도서 |
display_order | IntegerField | 아이템 노출 순서 (기본값: 999) |
5.3 큐레이션 API
| 항목 | 내용 |
|---|---|
| 개별 조회 | GET /api/home/curations/<id>/ |
| 설명 | 특정 큐레이션의 상세 정보 및 포함 도서 목록 조회 |
5.4 섹션 노출 방식
-
노출 방식:
- 가로 스크롤 카드 UI (무한 루프) / 갯수 제한 없음
- 카드 수 5개 이하인 경우 메인 미노출 (단, 필터가 걸린 경우에는 카드수 1개도 노출. 0개일 때만 미노출)
-
테마 예시:
- 애니를 읽자!
- 생활 속에서 배워요
- 모험
- 자연 놀이
- 걸파워
- 우정은 나의 힘
- 신비한 마법 이야기
- 따듯한 감동
- 가족 이야기
- 오늘 밤은 어떤 책?
- 마음이 자라는 시간
- 가을 이야기
-
콘텐츠 선정 기준:
theme_tag기반 또는 수동 선택 가능- 중복 도서 제거 여부는 운영에서 수동 설정
-
정렬 기준: PV 점수 기반
PV 기준
- PV 1회 = 1점
- 집계 기준: UTC+9 기준 매일 00시부터 1일 1회 집계
- PV 점수 기준으로 정렬
- 동일 점수일 경우: 최신 콘텐츠 우선
-
운영 제어 항목:
- 섹션명 수정 / 도서 순서 조정 / 특정 도서 제외
-
카드 클릭 시: 해당 도서 상세(홈) 이동
-
필터 조건은 필터 동작 정책에 따라 적용됩니다.
6. 캐릭터 친구들 정책
-
진입 기준:
character_tag기반 -
정렬: 기본 ABC 순, 운영 수동 설정 가능
-
도서 수 부족 시: 1개 미만 → 캐릭터 미노출
-
2단계 화면: 카드 그리드 뷰, 제한 없이 스크롤 가능
-
API:
GET /api/home/characters/ -
2단계 화면 정렬 기준: PV 점수 기반
PV 기준
- PV 1회 = 1점
- 집계 기준: UTC+9 기준 매일 00시부터 1일 1회 집계
- PV 점수 기준으로 정렬
- 동일 점수일 경우: 최신 콘텐츠 우선
7. 실시간 Top10 랭킹 정책
(2026년 4월 20일 updated)
7.1 랭킹 모델 (RealtimeRanking)
실시간 랭킹은 RealtimeRanking 모델을 통해 앱 언어별 / 시간별로 관리된다.
| 필드 | 타입 | 설명 |
|---|---|---|
book | ForeignKey | 랭킹 대상 도서 |
language_code | CharField(5) | 앱 언어 코드 (ko, en, jp, es) |
rank | IntegerField (1~50) | 순위 |
previous_rank | IntegerField (1~50, nullable) | 이전 시간대 순위 |
score | FloatField | 다중 시그널 합산 점수 |
ranking_date | DateField | 랭킹 날짜 |
ranking_hour | IntegerField | 랭킹 시간대 (0~23) |
유니크: (book, language_code, ranking_date, ranking_hour)
저장 깊이: TOP_N = 50 (API는 상위 10개만 노출하지만 데이터는 50까지 저장 — 향후 기능 확장 여지)
7.2 순위 변동 표시 (rank_change)
| 값 | 조건 | 설명 |
|---|---|---|
new | previous_rank가 null | 신규 진입 |
up | 현재 순위 < 이전 순위 | 순위 상승 |
down | 현재 순위 > 이전 순위 | 순위 하락 |
same | 현재 순위 = 이전 순위 | 순위 유지 |
자정 처리: ranking_hour=0 계산 시 같은 날짜에 더 이전 시간이 없으므로 어제 마지막 시간(23 또는 가장 최근)에서 previous_rank를 가져온다.
7.3 랭킹 API
| 항목 | 내용 |
|---|---|
| 엔드포인트 | GET /api/home/rankings/ |
| 설명 | 현재 시간대 기준 앱 언어별 Top10 랭킹 조회 |
| fallback | 현재 시간대 슬롯이 비어 있으면 가장 최근 슬롯 사용 |
7.4 랭킹 점수 산정 방식
다중 시그널을 가중 합산.
score = (
log10(1 + Book.views) * 5.0 # 누적 인기 (log 압축)
+ Book.pv_score * 0.3 # 기존 페이지뷰 점수 활용
+ bookmarks_7d * 2.0 # 강한 의도 신호
+ sticker_collections_7d * 0.5 # 최근 활동
+ reading_completions_7d * 1.0 # 실제 완독
+ new_book_decay_bonus(published_date) # 신간 부스트 (선형 감쇠)
)
가중치 (BookScoreConfig):
| 시그널 | 가중치 | 비고 |
|---|---|---|
VIEWS_LOG_WEIGHT | 5.0 | views=10K → 20점, 100만 → 30점 |
PV_SCORE_WEIGHT | 0.3 | 기존 모델 필드 |
BOOKMARK_WEIGHT_7D | 2.0 | 가장 강한 의도 신호 |
COLLECTION_WEIGHT_7D | 0.5 | 7일 합산 |
COMPLETER_WEIGHT_7D | 1.0 | ReadingHistory.is_completed |
NEW_BOOK_MAX_BONUS | 5.0 | day 0 → +5 |
NEW_BOOK_DECAY_DAYS | 14 | day 14 → 0 |
신간 감쇠 공식:
new_book_decay_bonus = max(0, 5.0 * (1 - days_since_publish / 14))
- day 0 → +5
- day 7 → +2.5
- day 14 → 0
- 출시일 기반 자동 산정 (별도 운영자 토글 불필요)
언어 필터링: bookmarks_7d/collections_7d/completers_7d는 모두 user.viewer_languages에 해당 언어가 포함된 사용자만 카운트.
7.5 정렬 (tie-breaker)
ORDER BY score DESC, pv_score DESC, views DESC, id ASC
결정적 정렬 — 같은 입력 → 같은 순서. 동점 시 pv_score → views → id 순으로 결정.
7.6 갱신 / 운영
- 집계 대상: 앱 언어 기준, 7일 윈도우
- 갱신 주기: 1시간 단위 (cron
calculate_realtime_rankings, 매 정시 +10분) - 동시 실행 방지: Redis 락 (
home:realtime_ranking_calc_lock, TTL 600s) - 데이터 보관: 7일 이상 지난 슬롯은 매 cron 실행마다 자동 삭제
- 운영 개입: Admin에서 row 편집 가능 (rank 1~50)
8. 섹션 구성 정책
-
섹션 구성 및 개수: 배너 1섹션, 실시간 Top10 1섹션, 캐릭터 친구들 1섹션, 테마 큐레이션 다수 (제한 없음)
-
노출 순서: 운영에서 수동 조정 가능
-
섹션 내 카드 수:
- 실시간 Top10: 고정 10개
- 테마, 캐릭터친구들: 개수 제한 없이 가능한 것 전체 노출
- 단, 카드 수 5개 이하인 경우 섹션 미노출 (단, 필터가 걸린 경우에는 카드 수 1개여도 노출함. 0개일 때만 미노출)
-
슬라이드 동작:
- 가로 스크롤, 루프 안함.
9. 카드=표지
-
카드 구성 요소:
- 표지 이미지 (제목 포함 / 도서 1권당 서비스 언어 수만큼 제작됨)
- 배지 (BGM / OST 등 태그 기반 자동 노출, 확장 가능, 필수 아님)
-
클릭 시: 작품 상세(홈) 이동
-
배지 기준:
tag = BGM→ "BGM"tag = OST→ "OST"
10. 예외 및 공백 처리
- 카드 이미지 로딩 실패: 기본 표지 이미지로 대체
11. 하단 GNB 정책
- 탭 구성: 홈 / 인기 / 이벤트 / 내 계정
- 탭 이동 시: 홈 → 타 탭 → 홈 복귀 시 스크롤 초기화
- depth 이동 시: 홈 → 뎁스 이동 → 홈 복귀 시 스크롤 위치 복원
- GNB 고정 여부:
- 1st depth(홈 화면): 상시 고정
- 2nd depth(캐릭터 상세 등): 비노출
12. 로그 및 통계 정책
-
기록 대상:
- 필터 선택 이벤트
- 카드 클릭 이벤트
- 캐릭터 진입/복귀 이벤트
- 배너 클릭 이벤트
-
통계 활용:
- 실시간 인기 Top10 집계
- 사용자 행동 기반 추천 개선 및 A/B 테스트 활용
-
제니 코멘트:
- 시간 내 개발 가능하다면(불가시 우선순위로 최대한 빠른 시간내 개발 부탁합니다) GNB부터 클릭이 가능한 상위 3rd-4th depth 메뉴들까지 모두 로그 트래킹 가능하면 좋겠습니다. user habit이 서비스 진입부터 작품/결제 랜딩까지 어느 플로우로 가는지 보일 수 있도록요. 어느 루트로 작품뷰어까지 진입, 어떤 루트(작품)에서 결제 전환 등.
13. API 요약
| API | 메서드 | 설명 |
|---|---|---|
/api/home/ | GET | 종합 홈 데이터 (ComprehensiveHomeView) |
/api/home/filters/ | GET | 홈 필터 목록 |
/api/home/banners/ | GET | 배너 목록 |
/api/home/rankings/ | GET | 실시간 Top10 랭킹 |
/api/home/curations/<id>/ | GET | 큐레이션 상세 |
/api/home/characters/ | GET | 캐릭터 친구들 목록 |