Skip to content

feat: 사용자 취업 프로필(희망 직군·산업·경력) + 면접 생성 프리필 - #267

Merged
i3months merged 1 commit into
devfrom
feat/user-job-profile
Sep 29, 2026
Merged

i3months merged 1 commit into
devfrom
feat/user-job-profile

Conversation

@i3months

Copy link
Copy Markdown
Member

"산업을 웬만하면 다 반영하는게 어떤지? 반도체 건설 토목 등등.. 아니면 애초에 회원가입 시
내정보에 관련 정보를 받고, 면접 시뮬레이션 시 그 정보 기반으로 보여주는게 좋아보인다"

두 아이디어는 대안이 아니라 짝이라 둘 다 했다.

산업은 열거하지 않는다

직군 × 산업은 곱집합이라(20 × 30 = 600) 열거로 못 덮는다. 그런데 둘은 하는 일이 다르다:

역할 그래서
직군 평가 관점(_DOMAIN_TECH_GUIDE)을 결정 — 큐레이션 필요 유한 enum
산업 질문의 맥락·어휘만 결정 자유 입력 + 제안

산업을 enum 으로 만들면 "IT 직군만 있던" 문제가 그대로 반복된다. 반도체·건설·토목을 넣어도
조선·방산·바이오·수직농업은 빠지고, 빠진 사람은 또 고를 게 없다. datalist 로 30개를 제안하되
목록 밖 값도 그대로 저장한다. CHECK 도, Literal 도, 4곳 동기화도 필요 없다.

경력 수준만 CHECK 로 막는다 — 질문 난이도를 가르는 값이라 오타가 조용히 흘러가면 안 된다.

profile 슬라이스를 따로 둔 이유

처음엔 User 에 필드를 붙였다. ArchUnit 이 막았다:

Cycle between top-level domains found: user -> session -> user

User 가 JobCategory(session 슬라이스)를 참조하면 순환이다. OAuthProvider 를 user.domain
에 둔 것과 같은 함정이고, 이 레포에 이미 문서화된 함정이다. profile → {user, session} 이고
역방향이 없으므로 순환이 없다. users 테이블을 부풀리지 않는 부수 효과도 있다.

프로필 → 세션은 프론트 프리필로

SessionService 가 프로필을 읽으면 session→profile 이 되어 다시 순환이다.

프론트가 프로필을 읽어 기본값을 채우고, 사용자는 그 자리에서 바꿀 수 있다. 세션은 실제로
쓰인 값
만 interview_sessions.industry 에 기록한다 — 프로필은 나중에 바뀌므로 "이 면접이
어떤 맥락이었는지" 는 세션이 스스로 남겨야 한다(직군을 세션에 복사해 두는 것과 같은 이유).

사용자가 이미 건드린 값은 덮지 않는다. 프로필은 기본값이지 강제값이 아니다.

null 과 빈 값의 의미가 다르다

null = 그대로 두기, 빈 목록/빈 문자열 = 지우기.

계정 화면이 일부 항목만 보내도 나머지가 날아가면 안 되는데, 그렇게만 하면 비우는 수단이
사라진다.
그래서 둘을 구분한다.

프롬프트

산업이 있으면 그 현장의 언어로 묻게 하되(반도체는 수율·공정 편차·클린룸, 건설은 공정표·안전관리·
협력업체) 경계를 명시했다:

산업은 질문의 맥락이지 새로운 근거가 아닙니다 — 지원자 자료에 근거가 없는 산업 지식을
지어내 묻지 않습니다.

빈 값은 (지정 없음) 으로 치환한다. 그대로 흘리면 프롬프트에 None 이 박혀 LLM 이 그걸
산업 이름으로 읽을 수 있다.

검증

UserJobProfilePersistenceTest(실제 PostgreSQL):

  • 저장·조회, 제안 목록에 없는 산업("수직농업 스마트팜")도 그대로 저장
  • 개발+비개발 혼합, 모든 경력 수준이 CHECK 통과

AI: 산업이 프롬프트 입력까지 실제로 도달하는지 / 빈 값 3종(None·""·" ")이 모두
(지정 없음) 이 되는지 / 프롬프트에 슬롯과 지침이 둘 다 있는지(슬롯만 있으면 LLM 이
장식으로 흘려보낸다).

백엔드 353건 통과
AI 434건 통과
프론트 201건 통과

화면

  • 계정 → 취업 프로필: 희망 직군(개발/비개발 구분) · 희망 산업 · 경력 수준
  • 새 면접: 직군·산업이 프로필로 채워짐, 이 면접에서만 바꾸는 것도 가능

남은 것

가입 직후 프로필 안내(온보딩)는 넣지 않았다 — filled 플래그는 응답에 있으니 띄울 위치만
정하면 된다. 가입 흐름을 건드리는 건 별도로 보는 게 맞다고 판단했다.

#266 으로 직군을 20종으로 늘렸지만 두 가지가 남았다.

1. 면접을 만들 때마다 직군을 다시 고르게 한다. 대부분 매번 같은 값이다.
2. 직군만으로는 질문 맥락이 얇다. **같은 '생산·품질' 이라도 반도체 공정과
   건설 현장은 묻는 것이 전혀 다르다.**

## 산업은 열거하지 않는다

직군은 평가 관점(_DOMAIN_TECH_GUIDE)을 큐레이션해야 해서 유한 집합이 맞지만,
산업은 프롬프트 맥락으로만 쓰인다. 열거하면 "IT 직군만 있던" 문제가 그대로
반복된다 — 반도체·건설·토목을 넣어도 조선·방산·바이오·수직농업은 빠지고,
빠진 사람은 또 고를 게 없다.

자유 입력 + datalist 제안으로 둔다. CHECK 제약도, Literal 도, 4곳 동기화도
필요 없다. 경력 수준만 CHECK 로 막는다 — 질문 난이도를 가르는 값이라 오타가
조용히 흘러가면 안 된다.

## profile 슬라이스를 따로 둔 이유

처음엔 User 에 필드를 붙였다. ArchUnit 이 막았다:

    Cycle between top-level domains found: user -> session -> user

User 가 JobCategory(session 슬라이스)를 참조하면 순환이다. OAuthProvider 를
user.domain 에 둔 것과 같은 함정이다. profile 이 user·session 을 의존하고
역방향이 없으므로 순환이 없다. users 테이블을 부풀리지 않는 부수 효과도 있다.

## 프로필 → 세션은 프론트 프리필로

SessionService 가 프로필을 읽으면 session→profile 이 되어 다시 순환이다.
프론트가 프로필을 읽어 기본값을 채우고 사용자가 그 자리에서 바꿀 수 있으며,
세션은 **실제로 쓰인 값**만 interview_sessions.industry 에 기록한다. 프로필은
나중에 바뀌므로 "이 면접이 어떤 맥락이었는지" 는 세션이 스스로 남겨야 한다
(직군을 세션에 복사해 두는 것과 같은 이유). 사용자가 이미 건드린 값은 덮지
않는다 — 프로필은 기본값이지 강제값이 아니다.

## null 과 빈 값의 의미가 다르다

null 은 "그대로 두기", 빈 목록/빈 문자열은 "지우기". 계정 화면이 일부 항목만
보내도 나머지가 날아가면 안 되는데, 그러면 비우는 수단이 사라지기 때문이다.

## 프롬프트

산업이 있으면 그 현장의 언어로 묻게 하되, **"산업은 질문의 맥락이지 새로운
근거가 아니다 — 자료에 없는 산업 지식을 지어내 묻지 않는다"** 를 명시했다.
비어 있으면 "(지정 없음)" 이 들어가 지침이 무시된다. 빈 값을 그대로 흘리면
프롬프트에 None 이 박혀 LLM 이 산업 이름으로 읽을 수 있다.

## 검증

- UserJobProfilePersistenceTest(@PostgresRepositoryTest): 실제 PostgreSQL 에서
  저장·조회, **제안 목록에 없는 산업("수직농업 스마트팜")도 그대로 저장**,
  개발+비개발 혼합, 모든 경력 수준이 CHECK 통과
- AI: 산업이 프롬프트 입력까지 실제로 도달하는지 + 빈 값 3종이 모두
  "(지정 없음)" 이 되는지 + 프롬프트에 슬롯과 지침이 둘 다 있는지
- 백엔드 353 / AI 434 / 프론트 201 통과
@i3months
i3months merged commit e6030a5 into dev Sep 29, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant