← 목록으로

크루 100명 전면 재설계 — 작업 보고

2026-08-26 새벽 · 커밋 16건 · → 크루 100명 프로필 보기


한 줄

크루 211명을 100명으로 다시 짜고, 이름 체계·관계 축·성격·사진을 전부 새로 만들었다.
기존에 쌓아 둔 지식 자산은 버리지 않고 1,266행을 새 크루에게 넘겼다.


왜 갈아엎었나 — 실측으로 드러난 것

이미지를 뽑기 전에 크루 구성부터 보자는 대표 지시로 DB를 전수 조사했다. 상태가 이랬다.

무엇 실측
닉네임 유니크 55종 / 211명 — 156명이 남의 이름을 썼다
"망하기 일보직전" 하나를 헤드헌터·커피전문가·업무효율화·HR인사이더 등 15명이 공유
본명·호칭 0명. 부를 수 있는 이름을 가진 크루가 하나도 없었다
카테고리 연애 크루 13명이 "커리어" 로 분류 (첫데이트 코치·짝사랑 상담·프렌드존 탈출…)
성별 값 male 70 / 여성 51 / 남성 46 / female 44 — 네 값이 섞임
hook_messages 211명 전부 빈값
스키마 문서 CLAUDE.md 의 knowledge_text·skill_ids 칼럼이 실제 테이블에 없음

닉네임 중복은 증상이지 원인이 아니었다. nickname 은 처음부터 이름이 아니라
캐치프레이즈였다 — "망하기 일보직전아"라고 부를 수 없다. 진짜 결손은 본명·호칭이 0명인 것.


1. 명부 — 211명 → 100명

기획 에이전트 4명(사용빈도·신규유입·정보구조·참신성)이 각각 푼 뒤 종합했다.

동행 34 · 몸마음 12 · 일 11 · 돈 9 · 배움 9 · 살림 8 · 각계각층 6 · 만들기 6 · 연애 5

대표가 못 박은 조건이 전부 반영됐다.

기계 검증 전항목 통과: 총원 100 / slug 중복 0 / 성비 여60:남40(목표 정확히 일치) /
이성친구 8명 여4:남4 / 미성년 4명 전원 친구 / 나이 앵커 외 값 0 /
인용한 기존 slug 178개 전부 실재(허수 0)


2. 이름은 네 겹이다

만나기 전에는 간판, 만난 뒤에는 호칭.

겹 칼럼 예 쓰는 자리
L1 본명 person_name 박서진 자기소개·프롬프트
L2 호칭 call_name + call_style 박 차장 / 은주 선배 / 상철 형 대화에서 부를 때
L3 간판 handle 연봉부스터 목록·알림·결제
L4 한 줄 tagline "올려 달라고 말하지 마세요" 카드·상세

call_style 은 직함 24 · 이름 48 · 관계 28. 대표가 말한 "박 차장", "영이 엄마"가 그대로 축이 됐다.

나이는 UI에서 뺀다. Flutter Crew 모델에 age 필드가 애초에 없어 이미 노출되지 않고 있었다.
DB에는 남겨 말투 생성에만 쓴다. 동행 크루만 또래대(peer_band)를 노출한다.


3. 관계 축 — "영어 선생을 이성친구로 쓰지 않는다"

카테고리(무엇을 해주는가)와 별개로 relations(어떤 사이인가)를 뒀다.

전문가 54 · 멘토 20 · 친구 10 · 이성친구 8 · 가족같은 5 · 말벗 3

동행 34명 — 이 축이 유일하게 재방문을 만든다

전문가는 문제가 풀리면 안 온다. 친구는 이유 없이 온다.

또래 친구   초등·중고등·20대·30-40대·50대+ × 남녀   10명
이성친구    20대·30대·40대·50대+ × 남녀            8명 (남4:여4)
말벗·가족   할머니·삼촌·언니·형                     8명
멘토형 친구                                        8명

"영입"이 아니라 "사귀기"다. 문턱(거절·승낙)은 두지 않는다 — 만나는 건 즉시고,
관계가 대화로 자란다. 처음엔 존댓말과 거리, 쌓이면 말이 편해지고, 가까워지면 부르는 말이 바뀐다.
user_crews.closeness(0~3)와 turn_count 로 저장한다. 동행은 정액(access='subscription').


4. 성격 100개 — 전부 직접 썼다

대표: "네가 직접 써야지 왜 오픈AI 키를 이용해서 쓰냐, 너도 LLM인데."

맞는 지적이었다(게다가 OPENAI_API_KEY 는 401로 죽어 있었다). 서브에이전트 4명이 25명씩 썼다.

구조 — [나는 누구인가] → [말투] → [내가 아는 것] → [내가 모르는 것] → [답변 규칙]

검증: 100개 / 721~1076자(평균 891) / 이모지 0 / 자기 간판 호명 0 / 실명 0 /
5개 섹션 전원 존재 / 동행 34명 전원 친밀도 3줄

[내가 모르는 것]을 필수로 넣은 이유는 하나다 — 여기서 선을 긋지 않으면 크루가 아무거나 아는 척한다.


5. 기존 자산은 버리지 않았다

대표: "많은 시간을 투여해서 작업했던 거고 중고차 딜러도 그렇고… 버리기보다 가져다 쓰라고."

1,266행을 승계했다.

물려준 쪽 받은 쪽 행수
stock-aga-house stock-101 주린이탈출 416
heyenka car-guide 중고차형님 318
start-with-10k saving-starter 종잣돈반 171
yummy-zero-cal diet-realist 다이어트현실 143
money-talk money-talk 세상물정 76
ano-sensei japanese-sensei 일본어센세 58
brand-young design-basic 브랜드디자이너 35
toss-interview-king interview-coach 면접왕 30
knee-slap-master youtube-coach 유튜브수익화 19

동양 고전·부의 사고방식 199행은 대응 주제가 없어 지우지 않고 _parked_classics 로 보관했다.
나중에 그 크루가 생기면 slug만 바꿔 붙는다.

그리고 성격을 쓸 때 이 자료를 읽고 "무엇을 아는 사람인지"만 가져왔다. 문장은 베끼지 않았다.

실명 세탁 — 453행

crew_knowledge 에 실존 인물 이름이 남아 있었다(이광수 415건·노희영 34건 등).
지식 방침대로 "누가 말했나"가 아니라 "그 사람으로 오인되는가" 에 선을 긋고 역할 표현으로 바꿨다.

검증: title·content 전수 재조회 결과 실명 잔존 0건.

source_url 1,465건이 전부 로컬 파일 경로여서 원 출처 역추적·삭제 요청 대응이 불가능했다.
로컬 경로는 지웠다(남은 0건). 앞으로 적재할 때는 원 URL을 넣는다.


6. 사진 — 한 장만 만들고 얼굴은 잘라낸다

대표: "작은 원 안의 얼굴도 이미 있는 이미지를 갖는 거지? 얼굴 하나만 갖고 쓰는 게 맞느냐?"

맞다. 한 장이면 된다. 실제로 돌려서 확인했다.

  1. creagen(nano-banana-2, 3:4 1K)로 원본 1장 생성 — fal.ai 는 키가 죽어 있다
  2. macOS Vision 으로 얼굴 정사각 크롭 (tool/faceavatar) — 100/100 검출 성공
  3. Supabase Storage 업로드 → full_body_image_url / avatar_url 연결

고정 크롭으로는 안 된다 — 얼굴 중심 Y가 0.198 ~ 0.281 로 흩어져 크루마다 눈높이가 어긋난다.
자르는 규칙은 정사각 변 = 얼굴 높이 × 2.35, 중심은 얼굴 중심에서 위로 10%.

작은 원은 44px이 맞다 — 22·26은 색점으로 읽히고, 34부터 사람이 보이고, 44에서 누구인지 안다.
무대 아바타(26)·캐스트(22)·대화 카드(34)를 44로 올릴 것을 제안한다.

다섯 번 뽑았다 — 네 번은 내 실수다

1차 카테고리별로 옷·배경을 하나로 고정 → 100명이 다 비슷해 보였다.

2차 다양성은 살았으나 전부 가로 사진. aspect_ratio 로 넘겼는데 라이브러리는
aspectRatio 를 받는다. 이름이 안 맞아 조용히 무시되고 기본값 16:9 가 걸렸다.
에러가 안 났다 — 해시 유니크 100, 얼굴 검출 100, 업로드 100, 외부 200 을 전부 통과했다.
이미지를 직접 열어 보고서야 알았다. 숫자로만 검증하면 이런 게 빠져나간다.

3차 세로(3:4)로 고쳐 뽑았다. 그런데 대표 지적 — "얼굴이 왜 이렇게 비슷해?"
확인해 보니 옷·머리·안경은 갈렸는데 얼굴 골격이 전부 같은 계열이었다.

원인이 두 겹이었다.
1. 모델 특성. Nano Banana 는 얼굴을 일관되게 유지하도록 튜닝된 모델이다
(구글 설명: "preserves facial structure, character identity", "no more random faces").
여러 장면에 같은 인물을 넣는 게 강점인 모델이라, 다양성이 필요한 우리 용도와 정반대다.
2. 프롬프트에 얼굴을 서술하는 말이 없었다. "attractive Korean woman in her late thirties"
뿐이면 모델이 학습한 평균 얼굴이 나온다. 옷을 바꿔도 같은 사람이 옷만 갈아입는다.

통제 실험으로 확인했다(같은 조건 8장): ①② 특징 없음 → 사실상 같은 사람 /
③④ 이름만 넣음 → 머리만 조금 갈림 / ⑤⑥ 얼굴 특징 서술 → 확실히 다름 /
⑦⑧ 이름+특징 → 가장 뚜렷.
얼굴 특징 서술이 결정적이고 이름은 보조다.

4차 얼굴을 7축으로 벌려 뽑았다(얼굴형·눈매·코·턱·피부·특이점·눈썹 = 245,000 조합,
100명 전원 다른 조합). 얼굴은 갈렸는데 대표 지적 — "모델 같지 않고 일반인 스냅 같다."
NOT a fashion model · Real everyday person · documentary 를 100명 전부에 넣고
붉은 볼 23명·모공 15명·삐뚠 앞니 10명을 겹친 탓이었다. 반대쪽으로 과하게 갔다.

5차(확정) 얼굴 7축은 그대로 두고 서술의 성격만 바꿨다 — "결점"이 아니라 "개성".
high-end fashion magazine quality + beauty lighting + no redness on cheeks +
flawless professional makeup. 다른 사람이면서 촬영된 인물로 보인다.

최종: 9:16(768×1376) 100장 / 서로 다른 파일 100 / 얼굴 검출 100 / 업로드 100 / 외부 200.

배운 것


7. 코드에서 고친 것

정규식 @멘션 주입 폐기 (93줄) — post-processor.ts 가 크루 이름과 역할 키워드를
정규식으로 훑어 @닉네임 을 문장에 꽂아 넣고 있었다. 프로젝트 룰 정면 위반이고,
새 호칭이 "박 차장"처럼 짧아지면 오탐이 터진다. 팀 명부를 프롬프트에 실어
LLM이 스스로 부르게 하고 라우팅은 crew_id 로 한다.

클라이언트 8곳 호명 통일 — 서버는 고쳐졌는데 클라이언트가 아직 nickname ?? name 이라
말풍선과 팀 슬롯에 "16년째 연습중" 같은 캐치프레이즈가 사람 이름 자리에 떴다.

191명 내리기 전 조회 코드 2곳 선제 수정 — 딥링크로 대화를 열 때와 영입 크루 목록이
is_active 만 가져오고 있었다. 그대로 뒀으면 939개 대화방과 영입 55건이 깨졌다.
"고르는 자리"(마켓·추천·검색)에만 is_active 를 걸고, "여는 자리"에는 걸지 않는다.


8. 남은 것 — 대표 확인 필요

🔴 키 두 개가 죽어 있다

오늘 밤 작업엔 지장이 없었다(이미지는 creagen, 성격은 직접 작성).
다만 앱 런타임이 OpenAI를 쓴다면 크루 대화 자체가 안 된다. 크루를 100명으로 새로 깔았으니
이것부터 확인해야 한다.

🟡 KNOWLEDGE_SEARCH_ENABLED

코드 기본값은 false라 안 켰으면 실사용자에게 안 나간다. 다만 Supabase 시크릿 조회가 403이라
실제 값을 확인하지 못했다. 대시보드에서 값만 봐 주면 된다.

🟡 폐기 191명

is_active=false 로 내렸다. 지우지 않았고 되돌릴 SQL 194줄이 백업에 있다.

🟢 다음 작업


검증 요약

항목 결과
명부 100명 / slug 중복 0 / 성비 여60:남40
성격 100개 / 이모지 0 / 간판 호명 0 / 실명 0
활성 크루 100명 (임시 프롬프트 0건)
지식 승계 1,266행 / 보관 199행 / 실명 잔존 0
사진 생성 100 / 얼굴 검출 100 / 업로드 100 / 외부 200 확인
코드 flutter analyze 새 경고 0 / build web 성공 / 배포 완료

백업은 전부 claudedocs/migration/backup/ 에 있다 —
gender 원본 211행 + 롤백 SQL, 폐기 191명 롤백 SQL, crew_knowledge 세탁 전 1,465행.