182편 중 38편만 첫 화면에 있다 — 리포트 노출 목록 전수 대조
칼라톤의 시즌 리포트 인덱스는 완료된 시즌을 모듈별로 모아 보여 주는 한 장짜리 목록이다. 이 글은 그 목록을 믿을 수 있는지 확인하려고 시작했다. 질문은 단순했다. 인덱스가 보여 주는 목록과 디스크에 실제로 있는 리포트 파일은 같은 집합인가.
이런 점검에서 기대하는 결과는 보통 '누락 몇 편'이다. 리포트는 사람이 손으로 쓰지 않고 매시 돌아가는 러너가 생성하므로, 파일은 만들어졌는데 목록에 못 올라간 시즌이 몇 편쯤 섞여 있을 거라고 예상했다. 인덱스 4장의 링크를 전부 긁어 파일 목록과 차집합을 냈더니 결과는 이랬다. 링크되지 않은 리포트 0편, 파일이 없는데 걸려 있는 링크 0건. 364개 파일이 전부 어딘가에서 링크되고, 걸린 링크는 전부 실재하는 파일을 가리켰다.
그래서 질문을 바꿔야 했다. 목록에 있느냐 없느냐가 아니라, 얼마나 깊이 있고 얼마나 구별되느냐로. 그쪽을 세어 보니 숫자가 전혀 평평하지 않았다.
전수 — 파일 364개, 인덱스 4장
Frontend/public/reports/ 아래에는 시즌 리포트 364개(국문 182편 + 영문 182편)와 인덱스 4장이 있다. 인덱스는 최상위 국·영문 2장(/reports/, /reports/index-en.html)과 인물 모듈 전용 아카이브 국·영문 2장(/reports/character/)이다. 나머지 네 모듈에는 전용 인덱스가 없다 — 최상위 인덱스가 그 모듈의 완전한 목록을 겸한다.
모듈별로 디스크의 국문 편수와 최상위 인덱스가 실제로 링크하는 편수를 나란히 놓으면 이렇다.
| 모듈 | 디스크(국문) | 최상위 인덱스 노출 | 노출률 | 전용 인덱스 | sitemap 등재(국문) |
|---|---|---|---|---|---|
| AI 인물 생성 | 152 | 8 | 5.3% | 있음 (152/152) | 0 |
| AI 유머 | 21 | 21 | 100% | 없음 | 21 |
| AI 커플매칭 | 6 | 6 | 100% | 없음 | 6 |
| AI 트레이딩 | 2 | 2 | 100% | 없음 | 2 |
| AI 마인드게임 | 1 | 1 | 100% | 없음 | 1 |
| 합계 | 182 | 38 | 20.9% | 1개 | 30 (16.5%) |
네 모듈은 100%다. 인물 생성만 5.3%다. 합계가 20.9%로 내려앉은 것은 전부 이 한 줄 때문이고, 그 이유는 인물 모듈이 다른 네 모듈을 합친 것보다 다섯 배 많은 리포트를 갖고 있기 때문이다(152편 대 30편). 영문 쪽도 숫자가 같다 — 최상위 영문 인덱스가 38편, 영문 아카이브가 152편으로 국문과 정확히 대칭이다.
목록은 하나가 아니라 셋이다
'노출'을 한 덩어리로 세면 위 표가 전부지만, 독자가 리포트에 닿는 경로는 세 층으로 갈린다. 같은 364개 파일을 세 층으로 다시 세면 이렇다.
| 층 | 무엇인가 | 국문 | 영문 | 전체 대비 |
|---|---|---|---|---|
| ① 디스크 | 실제로 존재하는 리포트 파일 | 182 | 182 | 100% |
| ② 인덱스 링크 | 인덱스 4장 중 한 곳 이상에서 링크됨 | 182 | 182 | 100% |
| ②-ㄱ 첫 화면 | 최상위 인덱스에서 한 번에 보임 | 38 | 38 | 20.9% |
| ②-ㄴ 두 번째 화면 | 아카이브 한 장을 더 거쳐야 보임 | 144 | 144 | 79.1% |
| ③ sitemap | sitemap.xml에 URL이 올라 있음 | 30 | 30 | 16.5% |
①과 ②가 같다는 것이 이 점검의 좋은 소식이다. 리포트 생성기가 파일과 인덱스를 같은 한 번의 실행에서 같은 목록으로 만들기 때문에, '파일은 있는데 목록에 없는' 상태가 구조적으로 생기지 않는다. 끊긴 링크가 0건인 것도 같은 이유다.
나쁜 소식은 ②-ㄱ과 ③이다. 전체의 79.1%는 클릭 한 번을 더 해야 보이고, 전체의 83.5%는 sitemap.xml에 아예 없다. sitemap에 올라 있는 리포트 URL 60개(국문 30 + 영문 30)는 인물 생성을 제외한 네 모듈의 전량과 정확히 일치한다. 인물 리포트 304개는 단 하나도 sitemap에 없다. 최상위 인덱스 2장은 sitemap에 올라 있지만 인물 아카이브 2장은 올라 있지 않고, 그 아카이브 페이지 자체가 noindex, follow다(최상위 인덱스는 index, follow).
왜 8편인가 — 숫자는 코드에 적혀 있다
인물 모듈만 잘린 것은 사고가 아니다. 리포트 생성기에는 CHARACTER_INDEX_LIMIT = 8이라는 상수가 있고, 바로 위에 이유가 주석으로 달려 있다 — 인물 리포트는 하루 여러 편씩 늘어나는 '근중복 템플릿 리포트'이므로 인덱스에는 최근 N개만 노출하고 나머지는 전용 아카이브로 분리한다는 것이다. 목적은 방문자와 심사 동선에서 얇은 페이지의 비중을 줄이는 것이다.
구현도 그 의도대로 되어 있다. '전체 보기' 링크는 잘린 모듈에만 붙는다(shown.length < sorted.length일 때만 생성되므로 네 모듈에는 애초에 나타나지 않는다). 링크에 적히는 숫자도 하드코딩이 아니라 목록 길이에서 계산된다. 그래서 국문의 "전체 152개 시즌 보기 →"와 영문의 "View all 152 seasons →"는 디스크의 152편과 정확히 일치했다. 인덱스가 자기 분량에 대해 말하는 숫자는 틀리지 않았다.
즉 이 구조는 버그가 아니라 선택이다. 다만 선택의 비용이 어디에 떨어지는지는 코드 주석에 적혀 있지 않다 — 그 비용은 아카이브 목록 한 장에 몰려 있다.
라벨은 리포트가 직접 쓴다 — 190줄 전부 일치
목록의 신뢰도에서 다음으로 확인할 것은 링크 글자다. 목록에 적힌 제목이 실제로 열리는 리포트의 제목과 다르면, 링크가 살아 있어도 목록은 거짓말을 하는 셈이다.
국문 인덱스 2장의 리포트 링크 190개(최상위 38 + 아카이브 152)를 각각 열어 대상 리포트의 <h1>과 대조했다. 190개 전부 글자가 완전히 같았다. 부분 일치나 유사가 아니라 공백 정규화 후 완전 일치이고, 무관한 라벨은 0건이다. 목록의 글자는 사람이 옮겨 적은 것이 아니라 리포트를 만든 그 값에서 같이 나온다.
반대로 대상 리포트의 <title>과 완전히 같은 라벨은 190개 중 0개였다. 모순이 아니라 규약 때문이다. 리포트의 <title>은 {제목} — {모듈} 시즌 리포트 | 칼라톤 꼴이고 목록 라벨은 그 앞부분, 즉 <h1>만 쓴다. 두 값이 어긋난 리포트는 없었다.
그런데 같은 이름이 22번 나온다
라벨이 정확하다는 것과 라벨로 구별이 된다는 것은 다른 말이다. 아카이브 152줄의 라벨을 세어 보니 서로 다른 이름이 11개뿐이었다. 152줄 전부가 중복 라벨이다.
| 아카이브 목록 라벨 | 그 이름을 쓰는 줄 |
|---|---|
| 예쁨의 정점 | 22 |
| 청순의 정점 | 22 |
| 귀여움의 정점 | 21 |
| 매력의 정점 | 16 |
| 아름다움의 정점 | 15 |
| 카리스마의 정점 · 소년미의 정점 · 청량의 정점 · 미남의 정점 | 각 10 |
| 훈훈함의 정점 | 9 |
| 톰보이시의 정점 | 7 |
| 합계 (라벨 11종) | 152 |
목록 줄에는 라벨 아래 보조 문구가 한 줄 더 붙는다. 인물 아카이브의 경우 테마: 귀여움 · 최고 68.0% 꼴이다. 이 보조 문구를 합쳐도 서로 다른 글자는 152개가 아니라 108개다. 줄 단위로 다시 세면, 글자가 완전히 똑같은 줄이 다른 줄과 짝을 이루는 경우가 31개 무리·75줄(49.3%)이고, 가장 큰 무리는 네 줄이 글자 하나까지 같았다(귀여움의 정점 / 테마: 귀여움 · 최고 68.0%). 혼자 유일한 줄은 77줄이다. 영문 아카이브도 같은 구조에서 수치가 동일했다(라벨 11종 · 글자 108종 · 충돌 75줄).
구별에 쓸 수 있을 만한 값이 하나 더 있는데, 목록에는 적히지 않는다. 시즌 번호다. 152줄 중 번호가 눈에 보이는 글자로 적힌 줄은 0줄이었다. 번호는 링크 주소(season-204.html)에만 있다. 마우스를 올려 상태 표시줄을 읽거나 링크를 열지 않으면, 네 줄 중 어느 것이 어느 시즌인지 알 길이 목록 위에 없다.
최상위 인덱스는 같은 문제가 없다. 38줄의 라벨이 38개 전부 달랐고 글자가 겹치는 줄도 0줄이었다. 다만 그건 규약이 좋아서가 아니라 표본이 작아서다 — 최상위에 올라온 인물 8편이 때마침 서로 다른 테마 8종이었고, 다른 네 모듈의 제목은 애초에 시즌마다 다르게 붙는다.
번호 204번은 204편을 뜻하지 않는다
목록을 읽을 때 생기는 또 하나의 착시가 시즌 번호다. 공개된 인물 리포트는 152편인데 가장 큰 번호는 204번이다. 그 사이 번호 중 52개는 공개된 국문 리포트가 없다. 유머도 같다 — 21편인데 번호는 1번부터 38번까지 흩어져 있고 17개 번호가 비어 있다.
| 모듈 | 공개 편수 | 번호 범위 | 범위 폭 | 비어 있는 번호 |
|---|---|---|---|---|
| AI 인물 생성 | 152 | 1 ~ 204 | 204 | 52 |
| AI 유머 | 21 | 1 ~ 38 | 38 | 17 |
| AI 커플매칭 | 6 | 3 ~ 8 | 6 | 0 |
| AI 트레이딩 | 2 | 1 ~ 2 | 2 | 0 |
| AI 마인드게임 | 1 | 1 ~ 1 | 1 | 0 |
번호는 시즌이 만들어질 때 매겨지고, 공개 리포트는 완료되어 공개 대상이 된 시즌만 받는다. 성인 레인처럼 공개하지 않는 시즌도 번호는 소비하므로 두 수가 어긋나는 것은 정상이다. 다만 목록을 보는 사람 입장에서는 결론이 하나다 — 가장 큰 시즌 번호를 분량으로 읽으면 인물은 34%, 유머는 81% 부풀려 읽게 된다. 인덱스가 "전체 152개 시즌"이라고 편수로 적은 선택은 그래서 옳다.
목록 바깥의 경로
마지막으로, 아카이브로 들어가는 문이 몇 개인지 세어 봤다. Frontend/public의 HTML 480장 중 본문에서 인물 아카이브를 링크하는 페이지는 16장이고, 그중 2장은 아카이브 국·영문판이 서로를 가리키는 언어 전환 링크다. 실제 유입 경로는 14장이다 — 최상위 인덱스 국·영문 2장과 아티클 12편이다.
개별 인물 리포트를 직접 링크하는 아티클은 20편이다. 즉 이 아카이브 한 장에 대한 사이트 내부 링크의 다수는 인덱스가 아니라 데이터 칼럼이 만들고 있다. 이 글이 올라가면 아티클 쪽이 13편, 전체 유입 경로가 15장이 된다.
한계 — 이 분석이 말할 수 없는 것
첫째, 이 글은 저장소에 있는 정적 파일만 셌다. 실제 방문자가 어떤 경로로 리포트에 닿는지, 두 번째 클릭에서 몇 명이 이탈하는지는 전혀 모른다. '20.9%'는 노출 구조의 수치이지 트래픽의 수치가 아니다. 깊이가 한 칸 늘어난 비용이 얼마인지는 이 데이터로 계산할 수 없다.
둘째, 검색엔진이 실제로 무엇을 색인했는지도 모른다. sitemap 등재와 noindex 표기는 우리가 보내는 신호일 뿐이고, 색인 결과는 Search Console에서만 확인된다. 아카이브가 follow이므로 그 아래 리포트가 크롤링 자체에서 막히는 것은 아니지만, 그 경로가 실제로 얼마나 쓰이는지는 별도 문제다. 리포트별 robots 메타와 sitemap 등재의 교차 분포는 이 글에서 다루지 않았다.
셋째, 비어 있는 번호 69개(인물 52 + 유머 17) 각각이 왜 비었는지는 확인하지 않았다. 공개하지 않는 레인, 중단된 시즌, 아직 끝나지 않은 시즌이 모두 같은 모양으로 비어 보이고, 목록만으로는 구분되지 않는다. 이 글이 말할 수 있는 것은 '번호와 편수가 다르다'는 사실까지다.
넷째, '구별 가능성'을 글자 완전 일치로만 판정했다. 보조 문구의 점수가 68.0%와 68.1%로 다르면 이 집계에서는 서로 다른 줄로 세었지만, 실제로 읽는 사람에게 그 차이가 식별 단서가 되는지는 다른 문제다. 그 기준을 느슨하게 잡으면 49.3%는 더 올라간다.
다섯째, 성인 시즌 리포트는 공개 대상이 아니므로 이 집계에 들어 있지 않다. 디스크의 364개는 공개 리포트의 전량이지 제작된 시즌의 전량이 아니다.
그래서 무엇을 할 것인가
점검의 결론은 둘로 갈린다. 목록의 정확성은 통과다. 누락 0편, 끊긴 링크 0건, 라벨 190개 전부 대상 리포트와 일치, 인덱스가 적은 편수까지 디스크와 일치. 생성기가 파일과 목록을 한 번에 만드는 구조가 이 결과를 거의 자동으로 보장한다.
통과하지 못한 쪽은 구별 가능성이다. 152줄 중 75줄이 옆 줄과 글자가 같고, 번호는 목록에 없다. 이건 상한 8이라는 선택의 문제가 아니라 아카이브 한 줄에 무엇을 적는지의 문제이고, 고치는 비용도 훨씬 싸다 — 줄마다 시즌 번호 하나를 더 적으면 충돌 75줄은 그 자리에서 0줄이 된다. 같은 값이 이미 링크 주소 안에 들어 있으니 새로 집계할 것도 없다.
그래서 우리가 다음에 손볼 곳은 상한이 아니라 줄이다. 그리고 그 전까지, 인물 시즌을 번호로 찾으려는 독자에게 정직하게 적어 둔다 — 인물 리포트 전체 보기의 목록에서 시즌 번호는 링크 주소에만 있다. 같은 이름이 네 번 나오는 자리에서는 네 개를 다 열어 보는 것이 지금으로서는 가장 빠른 방법이다.
리포트는 오늘도 매시 늘어난다. 늘어나는 쪽이 전부 인물 모듈이므로, 위 표의 20.9%와 16.5%는 아무것도 바꾸지 않으면 내일 더 내려간다.
집계 기준. 2026년 10월 28일(KST) 기준 저장소 Frontend/public/reports/의 공개 시즌 리포트 364개(국문 182 + 영문 182 — 인물 생성 152 · 유머 21 · 커플매칭 6 · 트레이딩 2 · 마인드게임 1)와 인덱스 4장(/reports/, /reports/index-en.html, /reports/character/, /reports/character/index-en.html)을 전수 파싱했다. '노출'은 각 인덱스 본문(<body> 이후)의 href가 /reports/{모듈}/season-{번호}.html 형태로 그 리포트를 가리키는 경우로 셌고, 내비게이션·푸터 링크는 제외했다. 라벨 일치는 목록 앵커의 글자와 대상 파일 <h1>의 글자를 태그 제거·공백 정규화 후 완전 일치로 판정했다. 줄 충돌은 라벨과 보조 문구(meta)를 이어 붙인 글자의 완전 일치 기준이다. sitemap 등재는 Frontend/public/sitemap.xml의 <loc> 전수(이 글의 URL을 등록한 뒤 152개, 그중 리포트 62개)에서 셌다. 상한 값과 '전체 보기' 링크의 생성 조건은 리포트 생성기 소스에서 확인했다. 비율은 소수점 아래 첫째 자리에서 반올림했다. 성인 시즌 리포트는 공개 대상이 아니므로 집계에 포함되지 않는다. 사이트 HTML 480장과 아카이브 유입 경로 14장은 이 글이 추가되기 전의 상태이며, 이 글이 발행되면 각각 481장·15장이 된다.