시즌 번호가 적힌 리포트는 166편 중 24편 — 제목 표기 규약 전수 감사
칼라톤의 시즌 리포트는 사람이 쓰지 않는다. 시즌이 끝나면 파이프라인이 데이터베이스를 읽어 국문·영문 한 쌍의 HTML을 찍고, 목록 페이지에 카드를 한 장 얹는다. 지금까지 그렇게 쌓인 국문 리포트가 166편, 영문까지 더하면 332편이다.
이 시리즈를 쓰면서 리포트를 수백 번 열었는데, 그때마다 사소하지만 반복되는 불편이 있었다. 브라우저 탭만 봐서는 지금 어느 시즌을 보고 있는지 알 수 없다는 것이다. 탭 세 개에 똑같이 "청순의 정점"이라고 적혀 있으면, 어느 탭이 시즌 21이고 어느 탭이 시즌 191인지 URL을 다시 눌러 봐야 한다. 반대로 트레이딩 리포트는 탭 제목이 "시즌1: AI 트레이더, 비트코인 대전"이라 한눈에 구분된다.
왜 모듈마다 다를까. 리포트를 찍는 코드는 하나의 저장소 안에 있고 바깥 껍데기도 똑같이 생겼는데, 제목만 제각각인 이유가 있을까. 이번에는 그 제목 자체를 데이터로 삼았다. 국문 166편·영문 166편, 합계 332편의 <title>·h1·히어로 윗줄·메타 설명·JSON-LD 헤드라인을 전부 꺼내 서로 맞댔다. 질문, 방법, 결과, 한계의 순서로 적는다.
질문 — 규약은 하나인가, 모듈 수만큼인가
검증 가능한 형태로 다듬으면 질문은 셋이다. 첫째, 바깥 틀(제목의 고정 부분)은 전 모듈이 같은 공식을 쓰는가. 둘째, 안쪽 작품명은 어떤 문형을 쓰며 시즌 번호를 담는가. 셋째, 그 결과로 제목만으로 시즌이 구분되는가.
셋째 질문이 실무적으로 가장 중요하다. 제목은 검색 결과에 뜨는 줄이고, 브라우저 탭에 들어가는 줄이고, 다른 사이트가 우리 리포트를 링크할 때 기본으로 가져가는 줄이다. 같은 제목을 가진 페이지가 스물한 장이면 그 스물한 장은 서로의 색인 품질을 깎아먹는다.
방법 — 332편을 파싱해 다섯 자리를 대조한다
집계 대상은 2026년 10월 6일(KST) 기준 저장소의 공개 리포트 전부다. 외부 API 없이 저장소의 HTML만 읽었다. 각 파일에서 다섯 자리를 꺼냈다 — <title>, 본문 <h1>, 히어로 상단의 작은 윗줄(이하 '키커'), meta description, JSON-LD의 headline. 여기에 목록 페이지 네 장(리포트 대문 국문·영문, 인물 전체목록 국문·영문)의 카드 텍스트 332건을 더해 대조군으로 썼다.
| 모듈 | 국문 편수 | 고유 제목 | 제목에 시즌 번호 | 가시 텍스트에 시즌 번호 | 작품명 평균 길이 |
|---|---|---|---|---|---|
| AI 인물 생성 | 142 | 11 | 0 | 0 | 6.7자 |
| AI 유머 대결 | 15 | 15 | 4 | 15 | 15.4자 |
| AI 커플매칭 | 6 | 6 | 6 | 6 | 17.7자 |
| AI 트레이딩 | 2 | 2 | 2 | 2 | 21.0자 |
| AI 마인드게임 | 1 | 1 | 1 | 1 | 19.0자 |
| 합계 | 166 | 35 | 13 | 24 | 8.1자 |
'가시 텍스트에 시즌 번호'는 제목뿐 아니라 페이지 본문 어디에든 "시즌 N"이라는 표기가 있는지를 뜻한다. 유머·마인드게임은 제목에 없어도 키커가 번호를 적어 주기 때문에 제목 열보다 수가 크다. 인물 리포트는 두 열이 모두 0이다 — 142편 전부, 페이지를 끝까지 읽어도 몇 번째 시즌인지 알 수 없다. 번호는 URL과 이미지 경로에만 있다.
결과 ① — 바깥 틀은 166편 전부 같다
먼저 좋은 소식. 제목의 고정 부분은 예외가 없다. 국문 166편이 전부 {작품명} — {모듈 라벨} 시즌 리포트 | 칼라톤 한 공식을 따르고, 모듈 라벨도 다섯 종(AI 인물 생성·AI 유머 대결·AI 커플매칭·AI 트레이딩·AI 마인드게임)에서 한 글자도 흔들리지 않는다. 영문 166편도 마찬가지로 {작품명} — {모듈 라벨} — Season Report | ColorTone를 전수로 지킨다.
딸려 있는 자리들도 거의 깨끗하다. og:title이 <title>과 다른 페이지는 332편 중 0편, 목록 페이지의 카드 텍스트가 해당 리포트의 h1과 다른 경우도 국문 166·영문 166 모두 0건이다. JSON-LD headline이 '제목에서 브랜드 꼬리만 뗀 값'과 다른 페이지는 331편 중 0편, 나머지 한 편이 다음 단락의 유일한 예외다. 한 번 정해진 자리는 대체로 정확히 복제된다.
그 한 편은 영문 유머 시즌 2다. 제목 원문이 Bright &amp; Pretty AI Humor로 적혀 있다 — 앰퍼샌드가 두 번 이스케이프됐다. 브라우저 탭과 검색 스니펫에는 "Bright & Pretty AI Humor"라는 글자가 그대로 뜬다. 같은 페이지의 h1과 JSON-LD headline, 목록 카드는 모두 정상이라 화면 본문에서는 보이지 않고, 오직 제목 자리 둘(<title>·og:title)에서만 어긋난다. 332편의 제목 가운데 앰퍼샌드를 담은 제목은 이 한 편뿐이니, 이 결함은 기회가 한 번 있었고 그 한 번에 발생했다. 비(非)ASCII 문자가 대부분인 국문 제목 166편에서는 한 건도 나오지 않았다.
다만 국문과 영문의 공식이 완전히 같지는 않다. 국문 제목의 줄표(—)는 166편 전부 정확히 한 개인데, 영문은 모듈 라벨 뒤에 줄표가 한 번 더 들어가 157편이 두 개, 작품명 안에 줄표를 품은 유머 9편은 세 개다. 영문 제목 AI Humor — July 2026, Week 1 — AI Humor Battle — Season Report | ColorTone은 줄표 세 개로 네 토막이 난다. 같은 자료를 국문은 "2026년 7월 1주차 AI 유머"라는 한 덩어리로 적는다.
결과 ② — 안쪽은 문형 6종으로 갈린다
바깥이 같은 만큼 안쪽은 다르다. 166편의 작품명을 문형으로 묶으면 여섯 가지가 나온다.
| 문형 | 예시 | 편수 | 쓰는 모듈 |
|---|---|---|---|
| {테마}의 정점 | 청순의 정점 | 142 | 인물 |
| 시즌N: {부제} | 시즌2: AI 트레이더, 비트코인 대전 | 9 | 매칭 6 · 트레이딩 2 · 마인드게임 1 |
| {연·월·주차} AI 유머 | 2026년 8월 3주차 AI 유머 | 9 | 유머 |
| AI 유머 시즌 N | AI 유머 시즌 27 | 3 | 유머 |
| {수식어} AI 유머 | 여름 한 컷 AI 유머 | 2 | 유머 |
| AI 유머 배틀 시즌 N | AI 유머 배틀 시즌 1 | 1 | 유머 |
여섯 중 넷이 유머 모듈 하나에서 나왔다. 그러나 이것은 난맥이 아니라 연표다. 발행일 순으로 세우면 문형이 뒤섞이지 않고 한 번씩만 바뀐다 — 2026-06-20 시즌 1이 "AI 유머 배틀 시즌 1", 06-26과 07-03의 두 시즌이 수식어형("밝고 예쁜 AI 유머", "여름 한 컷 AI 유머"), 07-10부터 09-03까지 아홉 시즌이 전부 주차형, 그리고 09-11의 시즌 23부터 세 시즌이 번호형이다. 네 구간이 날짜로 깔끔하게 끊긴다.
마지막 전환에는 저장소에 남은 근거가 있다. html2pdf/src/humor-season-title.js는 2026-09-06에 "제목에서 날짜 삭제" 정책이 적용되면서 모든 신규 유머 시즌의 제목이 "AI 유머" 하나로 같아졌고, 그래서 제목만으로 시즌을 구분해야 하는 표면에서는 시즌 번호를 붙인다고 적고 있다. 파일 머리말은 그 이유를 "시즌마다 <title>·앵커 텍스트가 같아지면 색인 품질이 떨어진다(중복 콘텐츠)"라고 명시한다. 우리 저장소에서 제목 중복 문제를 정면으로 다룬 규칙은 이 파일 하나뿐이다.
결과 ③ — 인물 리포트 142편이 제목 11개를 나눠 쓴다
그 규칙이 없는 쪽이 어떻게 되는지는 바로 옆 모듈이 보여 준다. 인물 생성 리포트 142편의 제목은 11종뿐이고, 11종 모두 "{테마}의 정점"이다.
| 제목 | 같은 제목을 쓰는 리포트 | 제목 | 같은 제목을 쓰는 리포트 |
|---|---|---|---|
| 청순의 정점 | 21편 | 카리스마의 정점 | 9편 |
| 예쁨의 정점 | 21편 | 미남의 정점 | 9편 |
| 귀여움의 정점 | 19편 | 청량의 정점 | 9편 |
| 매력의 정점 | 16편 | 훈훈함의 정점 | 8편 |
| 아름다움의 정점 | 15편 | 톰보이시의 정점 | 6편 |
| 소년미의 정점 | 9편 | 합계 | 142편 |
메타 설명도 같이 복제된다. 인물 리포트 142편의 meta description은 11종뿐이다 — 테마가 같으면 설명문이 바이트까지 동일하다. 즉 검색엔진이 보는 제목·설명·헤드라인 세 줄이 스물한 장씩 겹친다.
목록 페이지에서는 더 또렷하다. 인물 전체목록의 카드 142장은 제목 아래에 "테마: 청순 · 최고 75.0%" 같은 한 줄을 달고 있는데, 테마는 이미 제목에 들어 있으므로 실질적으로 구분에 쓰이는 값은 최고 일치율 하나뿐이다. 그 결과 카드 142장 중 64장은 다른 카드와 보이는 글자가 완전히 같다(26개 조합). 번호도 날짜도 없으니 "아까 본 그 시즌"을 목록에서 다시 찾으려면 링크를 하나씩 눌러 보는 수밖에 없다.
결과 ④ — 배지 넷까지 동원해도 18편은 구분되지 않는다
그렇다면 리포트를 열면 구분이 될까. 페이지가 상단에 내놓는 식별 정보는 제목, 대상 인물 이름, 그리고 배지 네 개(테마·라운드 수·최고 일치율·최종 일치율)다. 이 여섯 값을 모두 이어 붙여도 겹치는 쌍이 아홉 있다.
| 제목 | 대상 인물 | 라운드 | 최고 = 최종 | 같은 값을 가진 시즌 |
|---|---|---|---|---|
| 청순의 정점 | 윤하늘 | 15 | 75.0% | 71 · 191 |
| 청순의 정점 | 윤하늘 | 15 | 77.0% | 21 · 31 |
| 매력의 정점 | 신유나 | 15 | 67.0% | 29 · 79 |
| 매력의 정점 | 신유나 | 15 | 75.0% | 39 · 49 |
| 예쁨의 정점 | 백채원 | 15 | 76.0% | 27 · 137 |
| 귀여움의 정점 | 이서윤 | 15 | 68.0% | 23 · 53 |
| 아름다움의 정점 | 정세아 | 15 | 74.0% | 25 · 35 |
| 소년미의 정점 | 백승원 | 15 | 76.0% | 86 · 116 |
| 훈훈함의 정점 | 김태율 | 15 | 75.0% | 145 · 155 |
열여덟 편이다. 이 쌍들을 가르는 것은 URL과, 그 아래에 실린 이미지·심사평 본문뿐이다. 대상 인물 이름이 도움이 되지 않는 이유는 그 이름도 돌려쓰기 때문이다 — 142편의 대상 인물 이름은 20종이고, "윤하늘"만 열다섯 번 나온다.
결과 ⑤ — 히어로 윗줄은 모듈마다 다른 축을 적는다
제목 바로 위의 작은 줄(키커)은 전 모듈에 있지만, 둘째 칸에 무엇을 넣을지는 합의가 없다.
| 모듈 | 키커 형태 | 둘째 칸의 축 | 편수 |
|---|---|---|---|
| AI 인물 생성 | AI 인물 생성 · 청순 | 테마 | 142 |
| AI 유머 대결 | AI 유머 대결 · 시즌 27 | 시즌 번호 | 15 |
| AI 마인드게임 | AI 마인드게임 · 시즌 1 | 시즌 번호 | 1 |
| AI 트레이딩 | AI 트레이딩 · VIRTUAL_RANK | 내부 모드 코드 | 2 |
| AI 커플매칭 | AI 커플매칭 | 없음 | 6 |
트레이딩 두 편이 눈에 띈다. VIRTUAL과 VIRTUAL_RANK는 트레이딩 백엔드가 시즌의 진행 방식을 구분하려고 쓰는 trading_mode 컬럼의 값이다. 사람에게 보여 주려고 만든 문자열이 아니라 코드가 분기에 쓰는 값인데, 그대로 독자 눈앞에 나와 있다. 유머 리포트가 그 자리에 "시즌 27"을 적는 것과 같은 자리다.
결과 ⑥ — 번호를 적는 쪽에도 규약은 둘로 갈린다
시즌 번호를 제목에 적는 13편 안에서도 표기가 나뉜다. 매칭·트레이딩·마인드게임 9편은 "시즌1:"처럼 숫자를 붙여 쓰고 콜론으로 부제를 끊는 반면, 유머 4편은 "AI 유머 시즌 23"처럼 띄어 쓰고 부제가 없다. 서수 표기도 한 모듈 안에서 흔들린다 — 매칭 6편 중 "첫 번째·두 번째"는 띄어 쓰고 "세번째"는 붙여 쓴다(2편).
더 성가신 건 매칭의 번호가 URL의 번호와 다르다는 점이다. 여섯 편 전부 그렇다.
| URL | 국문 제목 | 영문 제목 |
|---|---|---|
| /reports/matching/season-3.html | 시즌1: 칼라톤, 첫 번째 시그널 | Season 1: Colortone, The First Signal |
| /reports/matching/season-4.html | 시즌1: 칼라톤, 두 번째 시그널 | Season 1: Colortone, The Second Signal |
| /reports/matching/season-5.html | 시즌1: 칼라톤, 세번째 시그널 | Season 1: Colortone, The Third Signal |
| /reports/matching/season-6.html | 시즌2: 칼라톤, 첫 번째 시그널 | Season 1: Colortone, The Third Signal |
| /reports/matching/season-7.html | 시즌2: 칼라톤, 두 번째 시그널 | Season 2: Colortone, The Second Signal |
| /reports/matching/season-8.html | 시즌2: 칼라톤, 세번째 시그널 | Season 2: Colortone, The Third Signal |
굵게 표시한 줄이 이번 감사에서 나온 유일한 국문–영문 불일치다. season-6의 영문 제목은 국문이 말하는 "시즌2 첫 번째"가 아니라 바로 앞 편인 season-5의 영문 제목과 글자까지 같다. 번호도 서수도 어긋났으니 영문 독자에게는 같은 회차가 두 번 올라온 것처럼 보인다. 국문과 영문 양쪽에 시즌 번호가 적힌 13편 중 1편이다.
매칭 리포트는 JSON-LD에서도 혼자 다르다. 160편이 datePublished를 담는데 매칭 6편에는 그 항목이 없다. 제목의 "시즌1/시즌2"는 매칭이 자체적으로 세는 리그 회차이고 URL의 3~8은 데이터베이스가 세는 시즌 번호다. 둘 다 '시즌'이라 부르는 탓에 발행일조차 없는 페이지에서 독자가 순서를 복원할 단서는 사실상 없다.
한계 — 이 감사가 말할 수 없는 것
첫째, 이 글이 센 것은 현재 저장소에 있는 출력물이다. 제목의 원재료는 데이터베이스의 시즌 제목 컬럼이고, 리포트 생성기는 그 값을 받아 틀에 끼울 뿐이다. 따라서 "문형이 여섯 가지"는 생성기가 여섯 갈래라는 뜻이 아니라, 그동안 데이터베이스에 들어간 제목이 여섯 모양이었다는 뜻이다. 어느 쪽을 고쳐야 하는지는 이 집계만으로 결정되지 않는다.
둘째, 표본이 모듈마다 극단적으로 비대칭이다. 인물 142편과 마인드게임 1편을 같은 표에 놓았지만, 1편짜리 모듈의 "고유 제목 1종·번호 표기 100%"는 규약의 증거가 아니라 산술의 결과다. 모듈 간 비교로 읽을 수 있는 것은 인물(142)·유머(15)·매칭(6) 정도까지다.
셋째, '구분 가능성'을 가시 텍스트로만 쟀다. 실제 독자는 URL을 보고, 이미지를 보고, 북마크 이력을 본다. 열여덟 편이 "구분 불가"라는 말은 페이지의 글자만 비교했을 때 그렇다는 뜻이지, 사람이 두 페이지를 나란히 놓고 구별하지 못한다는 뜻이 아니다. 반대로 검색엔진과 링크 공유는 대체로 그 글자만 가져간다.
넷째, 중복 제목이 색인에 실제로 얼마나 손해인지는 이 데이터로 알 수 없다. 우리는 "같은 제목이 21장"이라는 사실만 확인했고, 그것이 노출·클릭에 미친 영향은 검색 콘솔의 영역이라 이 글의 바깥이다. 다만 저장소 안의 규칙 파일이 같은 위험을 이유로 유머 제목에 번호를 붙이기로 한 것은 사실이다.
다섯째, 이스케이프 결함의 모집단이 1이다. 앰퍼샌드를 담은 제목이 332편 중 한 편뿐이므로 "한 번의 기회에 한 번 발생"은 발생률이 아니라 사례 보고다. 같은 결함이 다른 특수문자(<, 따옴표 등)에서도 일어나는지는 그런 제목이 아직 없어 확인할 수 없었다.
여섯째, 영문 불일치 1건은 원인까지 규명하지 못했다. 국문·영문 제목이 데이터베이스에서 별도 컬럼으로 관리되는 구조라면 한쪽만 갱신되고 다른 쪽이 앞 시즌 값을 그대로 물려받는 일이 가능하지만, 그 경로를 이 감사에서 확인하지는 않았다.
그래서 규약은 있는가
있다. 다만 한 층에만 있다. 제목의 바깥 틀, 목록 카드와 h1의 일치, og:title과 JSON-LD 헤드라인의 파생 — 자동화가 책임지는 자리의 예외는 332편에서 이스케이프 1건뿐이다. 기계가 복제하기로 한 것은 거의 완벽하게 복제된다.
규약이 없는 층은 그 안쪽, 즉 "무엇을 작품명이라 부를 것인가"다. 인물은 테마를 적고, 유머는 시기 또는 번호를 적고, 매칭은 리그 회차와 부제를 적고, 트레이딩은 내부 모드 코드를 키커에 흘린다. 모듈마다 자기 모듈 안에서는 자연스러운 선택이지만, 166편을 한 줄로 세우면 같은 자리에 서로 다른 축이 들어가 있다. 제목이 시즌을 가리키는 모듈과, 테마를 가리키는 모듈과, 주차를 가리키는 모듈이 한 사이트에 공존한다.
고칠 방향은 이미 저장소 안에 적혀 있다. 유머가 2026년 9월에 내린 결론 — 제목만으로 시즌을 구분해야 하는 표면에서는 번호를 붙인다 — 을 인물 리포트에 그대로 적용하면 제목 11종은 142종이 되고, 목록의 중복 카드 64장과 구분 불가 18편이 동시에 사라진다. 바깥 틀을 건드리지 않고 작품명 한 자리만 바꾸면 되는 변경이다. 이 글은 그 변경을 제안하기 위한 계수이지, 변경 자체는 아니다. 언제 어떻게 반영할지는 따로 적을 일이다.
그동안 리포트를 읽으실 분들께는 이렇게 권한다. 인물 리포트를 여러 편 비교할 때는 탭 제목 대신 주소창의 번호를 보시라. 적어도 그 번호는 166편 전부에서 유일하다.
집계 기준. 2026년 10월 6일(KST) 기준 저장소의 공개 시즌 리포트 전부 — 국문 166편(인물 142 · 유머 15 · 매칭 6 · 트레이딩 2 · 마인드게임 1)과 영문 166편, 목록 페이지 4장의 카드 332건. 외부 API를 쓰지 않고 HTML의 <title>·<h1>·히어로 키커·meta description·og:title·JSON-LD를 파싱해 집계했다. '작품명'은 제목에서 모듈 라벨과 브랜드 꼬리를 뗀 앞부분이고, 평균 길이는 공백 포함 문자 수다. '가시 텍스트에 시즌 번호'는 <body>에서 태그를 제거한 문자열에 "시즌 N" 표기가 있는지로 판정했으며 URL·이미지 경로는 제외했다. 문자열 비교는 HTML 엔티티를 한 번 복원한 뒤 수행했고, 이중 이스케이프 1건은 그 복원 이후에도 남은 차이다. 리포트는 계속 발행되므로 편수와 제목 종류는 집계 시점 이후 달라진다.