careermate 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +256 -0
- package/THIRD_PARTY_NOTICES.md +40 -0
- package/apps/mcp/src/index.ts +66 -0
- package/apps/web/DESIGN_GUIDE.md +105 -0
- package/apps/web/UI_CONTRACT.md +44 -0
- package/apps/web/public/app.js +118 -0
- package/apps/web/public/fonts/PretendardVariable.woff2 +0 -0
- package/apps/web/public/index.html +41 -0
- package/apps/web/public/lib.js +282 -0
- package/apps/web/public/pages/applications.js +98 -0
- package/apps/web/public/pages/documents.js +446 -0
- package/apps/web/public/pages/home.js +263 -0
- package/apps/web/public/pages/interview.js +230 -0
- package/apps/web/public/pages/jobs.js +494 -0
- package/apps/web/public/pages/profile.js +576 -0
- package/apps/web/public/pages/settings.js +233 -0
- package/apps/web/public/styles.css +426 -0
- package/apps/web/src/exports.ts +68 -0
- package/apps/web/src/http.ts +180 -0
- package/apps/web/src/index.ts +49 -0
- package/apps/web/src/info.ts +50 -0
- package/apps/web/src/routes.ts +350 -0
- package/apps/web/src/security.ts +102 -0
- package/apps/web/src/server.ts +141 -0
- package/apps/web/src/settings.ts +88 -0
- package/bin/careermate.mjs +74 -0
- package/dist/careermate.mcpb +0 -0
- package/dist/install-page/index.html +474 -0
- package/dist/install-page/style.css +391 -0
- package/dist/install-page/vercel.json +20 -0
- package/dist/mcp-smoke.err +3 -0
- package/dist/mcp.mjs +23704 -0
- package/dist/mcpb-stage/README.md +219 -0
- package/dist/mcpb-stage/dist/install-page/index.html +434 -0
- package/dist/mcpb-stage/dist/install-page/style.css +407 -0
- package/dist/mcpb-stage/dist/install-page/vercel.json +20 -0
- package/dist/mcpb-stage/dist/mcp.mjs +23704 -0
- package/dist/mcpb-stage/dist/public/app.js +118 -0
- package/dist/mcpb-stage/dist/public/fonts/PretendardVariable.woff2 +0 -0
- package/dist/mcpb-stage/dist/public/index.html +41 -0
- package/dist/mcpb-stage/dist/public/lib.js +282 -0
- package/dist/mcpb-stage/dist/public/pages/applications.js +98 -0
- package/dist/mcpb-stage/dist/public/pages/documents.js +446 -0
- package/dist/mcpb-stage/dist/public/pages/home.js +263 -0
- package/dist/mcpb-stage/dist/public/pages/interview.js +230 -0
- package/dist/mcpb-stage/dist/public/pages/jobs.js +494 -0
- package/dist/mcpb-stage/dist/public/pages/profile.js +576 -0
- package/dist/mcpb-stage/dist/public/pages/settings.js +233 -0
- package/dist/mcpb-stage/dist/public/styles.css +420 -0
- package/dist/mcpb-stage/dist/web.mjs +7240 -0
- package/dist/mcpb-stage/manifest.json +40 -0
- package/dist/public/app.js +118 -0
- package/dist/public/fonts/PretendardVariable.woff2 +0 -0
- package/dist/public/index.html +41 -0
- package/dist/public/lib.js +282 -0
- package/dist/public/pages/applications.js +98 -0
- package/dist/public/pages/documents.js +446 -0
- package/dist/public/pages/home.js +263 -0
- package/dist/public/pages/interview.js +230 -0
- package/dist/public/pages/jobs.js +494 -0
- package/dist/public/pages/profile.js +576 -0
- package/dist/public/pages/settings.js +233 -0
- package/dist/public/styles.css +426 -0
- package/dist/web.mjs +7240 -0
- package/docs/ARCHITECTURE.md +208 -0
- package/docs/CHANGES_V1.md +103 -0
- package/docs/DATA_MODEL.md +460 -0
- package/docs/DECISIONS.md +277 -0
- package/docs/DEMO.md +242 -0
- package/docs/INSTALL.md +148 -0
- package/docs/INSTALL_AND_USAGE.md +99 -0
- package/docs/MCP_TOOLS.md +233 -0
- package/docs/ROADMAP.md +134 -0
- package/docs/START_WORKFLOW.md +125 -0
- package/docs/SUPPORTED_AI_APPS.md +60 -0
- package/docs/TODO.md +57 -0
- package/docs/UX_NOTES.md +247 -0
- package/docs/WORKFLOWS.md +200 -0
- package/install-page/index.html +474 -0
- package/install-page/style.css +391 -0
- package/install-page/vercel.json +20 -0
- package/package.json +68 -0
- package/packages/core/src/context.ts +74 -0
- package/packages/core/src/index.ts +8 -0
- package/packages/core/src/onboarding.ts +81 -0
- package/packages/core/src/services.ts +146 -0
- package/packages/core/src/summary.ts +104 -0
- package/packages/db/src/connection.ts +46 -0
- package/packages/db/src/index.ts +22 -0
- package/packages/db/src/paths.ts +41 -0
- package/packages/db/src/repositories.ts +828 -0
- package/packages/db/src/runtime.ts +58 -0
- package/packages/db/src/schema.ts +189 -0
- package/packages/exporters/src/html.ts +113 -0
- package/packages/exporters/src/index.ts +364 -0
- package/packages/exporters/src/markdown.ts +178 -0
- package/packages/mcp-tools/src/bridge.ts +83 -0
- package/packages/mcp-tools/src/index.ts +8 -0
- package/packages/mcp-tools/src/result.ts +49 -0
- package/packages/mcp-tools/src/tools.ts +455 -0
- package/packages/parsers/src/html.ts +86 -0
- package/packages/parsers/src/index.ts +228 -0
- package/packages/parsers/src/keywords.ts +151 -0
- package/packages/prompts/src/humanize.ts +59 -0
- package/packages/prompts/src/index.ts +82 -0
- package/packages/prompts/src/install.ts +43 -0
- package/packages/prompts/src/onboarding.ts +35 -0
- package/packages/prompts/src/system.ts +53 -0
- package/packages/shared/src/enums.ts +103 -0
- package/packages/shared/src/index.ts +18 -0
- package/packages/shared/src/schemas.ts +398 -0
- package/packages/workflows/src/definitions.ts +107 -0
- package/packages/workflows/src/index.ts +39 -0
- package/scripts/build-dist.mjs +62 -0
- package/scripts/build-mcpb.mjs +70 -0
- package/scripts/doctor.ts +81 -0
- package/scripts/init.ts +342 -0
- package/scripts/mcp-probe.ts +55 -0
- package/scripts/migrate.ts +6 -0
- package/scripts/run.mjs +33 -0
- package/scripts/seed.ts +129 -0
- package/scripts/test.ts +117 -0
- package/scripts/ui-smoke.ts +73 -0
- package/tsconfig.json +29 -0
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
# 데이터 모델 (Data Model)
|
|
2
|
+
|
|
3
|
+
CareerMate는 모든 데이터를 사용자 컴퓨터의 단일 SQLite 파일(`careermate.sqlite`)에 저장한다. 클라우드 전송도, 외부 서버도 없다. 대시보드(`apps/web`)와 MCP 서버(`apps/mcp`)는 **같은 DB 파일을 공유**하며, 각자 부팅 시 마이그레이션을 호출해도 안전하다(멱등).
|
|
4
|
+
|
|
5
|
+
이 문서는 실제 스키마(`packages/db/src/schema.ts`)와 zod 스키마(`packages/shared/src/schemas.ts`), enum 정의(`packages/shared/src/enums.ts`)에 정확히 일치한다.
|
|
6
|
+
|
|
7
|
+
관련 문서:
|
|
8
|
+
- 데이터 저장 경로/환경변수: 이 문서의 [데이터 저장 위치](#데이터-저장-위치) 절, `packages/db/src/paths.ts`
|
|
9
|
+
- MCP 도구로의 입출력 형태: zod의 `*Input` / `*Record` 레이어 (`packages/shared/src/schemas.ts`)
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 목차
|
|
14
|
+
|
|
15
|
+
1. [설계 원칙](#설계-원칙)
|
|
16
|
+
2. [테이블 목록](#테이블-목록)
|
|
17
|
+
3. [테이블 상세](#테이블-상세)
|
|
18
|
+
4. [엔티티 관계 (ER)](#엔티티-관계-er)
|
|
19
|
+
5. [JSON 텍스트 컬럼](#json-텍스트-컬럼)
|
|
20
|
+
6. [마이그레이션](#마이그레이션)
|
|
21
|
+
7. [인덱스](#인덱스)
|
|
22
|
+
8. [데이터 저장 위치](#데이터-저장-위치)
|
|
23
|
+
9. [백업 / 내보내기](#백업--내보내기)
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 설계 원칙
|
|
28
|
+
|
|
29
|
+
- **타입 표기**: SQLite의 동적 타입을 사용한다. 컬럼 선언 타입은 `TEXT`, `INTEGER`, `REAL` 세 가지만 등장한다.
|
|
30
|
+
- `TEXT` — 문자열. ID, 본문, 날짜(ISO 문자열) 모두 `TEXT`.
|
|
31
|
+
- `INTEGER` — 정수. boolean은 `INTEGER`로 저장(`0`/`1`)되며, 레포지토리에서 boolean으로 변환한다. 정렬용 `order_index`, 버전 번호도 `INTEGER`.
|
|
32
|
+
- `REAL` — 부동소수. `skills.years`, `fit_analyses.score`.
|
|
33
|
+
- **날짜/시간**: 모든 `created_at` / `updated_at`은 ISO 8601 문자열(`TEXT`)이다. `start_date`, `end_date`, `deadline` 등은 `YYYY-MM` 또는 `YYYY-MM-DD` 자유 문자열이다.
|
|
34
|
+
- **JSON 텍스트 컬럼**: 배열/객체 형태의 중첩 데이터는 JSON 문자열로 `TEXT` 컬럼에 저장하고, 레포지토리가 파싱해 배열/객체로 돌려준다. 본문에서 **(JSON)** 으로 표기한다.
|
|
35
|
+
- **NULL 가능성**: zod `*Record` 스키마의 `.nullable()` 여부가 NULL 허용을 나타낸다. 본문 표의 "NULL" 열에 반영했다.
|
|
36
|
+
- **입력 vs 저장**: MCP 도구/HTTP API가 받는 형태는 `*Input`(부분/선택 필드 다수), DB에 저장되어 반환되는 형태는 `*Record`(id + 타임스탬프 포함)다.
|
|
37
|
+
|
|
38
|
+
> 참고: SQLite 컬럼 선언상의 `NOT NULL DEFAULT '[]'` 같은 기본값은 빈 JSON 배열을 의미한다. 즉 "값이 없으면 빈 배열"로 저장되며, 레코드에서는 항상 배열로 나타난다.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 테이블 목록
|
|
43
|
+
|
|
44
|
+
데이터 테이블 12개와 스키마 버전 메타 테이블 1개(`_meta`)로 구성된다.
|
|
45
|
+
|
|
46
|
+
| # | 테이블 | 의미 |
|
|
47
|
+
|---|--------|------|
|
|
48
|
+
| 1 | `profile` | 사용자 기본 프로필(이름/연락처/희망 직무/작성 선호 등). 단일 레코드 운용. |
|
|
49
|
+
| 2 | `experiences` | 경력(회사별 재직 이력). |
|
|
50
|
+
| 3 | `projects` | 프로젝트 이력. |
|
|
51
|
+
| 4 | `skills` | 보유 기술/역량. |
|
|
52
|
+
| 5 | `documents` | 이력서·경력기술서·포트폴리오 등 본문 문서. |
|
|
53
|
+
| 6 | `cover_letters` | 자기소개서(헤더/메타. 본문은 버전 테이블에). |
|
|
54
|
+
| 7 | `cover_letter_versions` | 자기소개서 버전별 본문(이력 추적). |
|
|
55
|
+
| 8 | `jobs` | 채용 공고. |
|
|
56
|
+
| 9 | `fit_analyses` | 공고에 대한 적합도 분석 결과. |
|
|
57
|
+
| 10 | `applications` | 지원 현황(공고당 1건, 상태 8단계). |
|
|
58
|
+
| 11 | `interview_preps` | 면접 준비 자료(공고당 1건). |
|
|
59
|
+
| 12 | `activities` | 활동 피드(타임라인). |
|
|
60
|
+
| — | `_meta` | 스키마 버전 등 메타 키-값. |
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 테이블 상세
|
|
65
|
+
|
|
66
|
+
각 표의 컬럼/타입은 `packages/db/src/schema.ts`의 `CREATE TABLE`과 정확히 일치한다. "NULL" 열은 `*Record` zod 스키마의 nullable 여부 기준이다.
|
|
67
|
+
|
|
68
|
+
### 1. `profile`
|
|
69
|
+
|
|
70
|
+
단일 사용자 프로필. 보통 한 개의 레코드만 존재한다.
|
|
71
|
+
|
|
72
|
+
| 컬럼 | 타입 | NULL | 의미 |
|
|
73
|
+
|------|------|------|------|
|
|
74
|
+
| `id` | TEXT | PK | 기본키 |
|
|
75
|
+
| `name` | TEXT | Y | 이름 |
|
|
76
|
+
| `email` | TEXT | Y | 이메일 |
|
|
77
|
+
| `phone` | TEXT | Y | 연락처 |
|
|
78
|
+
| `location` | TEXT | Y | 지역 |
|
|
79
|
+
| `headline` | TEXT | Y | 한 줄 소개 / 직무 타이틀 |
|
|
80
|
+
| `summary` | TEXT | Y | 자기소개 요약 |
|
|
81
|
+
| `desired_roles` | TEXT **(JSON)** | N | 희망 직무 문자열 배열. 기본 `[]` |
|
|
82
|
+
| `desired_conditions` | TEXT | Y | 희망 근무 조건(연봉/지역/근무형태 등) |
|
|
83
|
+
| `preferred_tone` | TEXT | Y | 자기소개서 선호 문체 |
|
|
84
|
+
| `emphasis_points` | TEXT **(JSON)** | N | 강조 포인트 문자열 배열. 기본 `[]` |
|
|
85
|
+
| `links` | TEXT **(JSON)** | N | `{label, url}` 객체 배열(포트폴리오/깃허브/링크드인). 기본 `[]` |
|
|
86
|
+
| `created_at` | TEXT | N | 생성 시각(ISO) |
|
|
87
|
+
| `updated_at` | TEXT | N | 수정 시각(ISO) |
|
|
88
|
+
|
|
89
|
+
### 2. `experiences`
|
|
90
|
+
|
|
91
|
+
| 컬럼 | 타입 | NULL | 의미 |
|
|
92
|
+
|------|------|------|------|
|
|
93
|
+
| `id` | TEXT | PK | 기본키 |
|
|
94
|
+
| `company` | TEXT | N | 회사명(필수) |
|
|
95
|
+
| `role` | TEXT | Y | 직무/직책 |
|
|
96
|
+
| `employment_type` | TEXT | Y | 정규직/계약직/인턴/프리랜서 등 |
|
|
97
|
+
| `start_date` | TEXT | Y | 시작일(`YYYY-MM` 또는 `YYYY-MM-DD`) |
|
|
98
|
+
| `end_date` | TEXT | Y | 종료일(재직 중이면 비움) |
|
|
99
|
+
| `is_current` | INTEGER | N | 재직 중 여부(0/1 → boolean). 기본 `0` |
|
|
100
|
+
| `description` | TEXT | Y | 업무 설명 |
|
|
101
|
+
| `achievements` | TEXT **(JSON)** | N | 성과/업적 문자열 배열. 기본 `[]` |
|
|
102
|
+
| `tech` | TEXT **(JSON)** | N | 사용 기술/도구 문자열 배열. 기본 `[]` |
|
|
103
|
+
| `order_index` | INTEGER | N | 정렬 순서. 기본 `0` |
|
|
104
|
+
| `created_at` | TEXT | N | 생성 시각(ISO) |
|
|
105
|
+
| `updated_at` | TEXT | N | 수정 시각(ISO) |
|
|
106
|
+
|
|
107
|
+
### 3. `projects`
|
|
108
|
+
|
|
109
|
+
| 컬럼 | 타입 | NULL | 의미 |
|
|
110
|
+
|------|------|------|------|
|
|
111
|
+
| `id` | TEXT | PK | 기본키 |
|
|
112
|
+
| `name` | TEXT | N | 프로젝트명(필수) |
|
|
113
|
+
| `role` | TEXT | Y | 담당 역할 |
|
|
114
|
+
| `description` | TEXT | Y | 설명 |
|
|
115
|
+
| `highlights` | TEXT **(JSON)** | N | 핵심 성과 문자열 배열. 기본 `[]` |
|
|
116
|
+
| `tech` | TEXT **(JSON)** | N | 사용 기술 문자열 배열. 기본 `[]` |
|
|
117
|
+
| `url` | TEXT | Y | 링크 |
|
|
118
|
+
| `start_date` | TEXT | Y | 시작일 |
|
|
119
|
+
| `end_date` | TEXT | Y | 종료일 |
|
|
120
|
+
| `order_index` | INTEGER | N | 정렬 순서. 기본 `0` |
|
|
121
|
+
| `created_at` | TEXT | N | 생성 시각(ISO) |
|
|
122
|
+
| `updated_at` | TEXT | N | 수정 시각(ISO) |
|
|
123
|
+
|
|
124
|
+
### 4. `skills`
|
|
125
|
+
|
|
126
|
+
| 컬럼 | 타입 | NULL | 의미 |
|
|
127
|
+
|------|------|------|------|
|
|
128
|
+
| `id` | TEXT | PK | 기본키 |
|
|
129
|
+
| `name` | TEXT | N | 기술명(필수) |
|
|
130
|
+
| `category` | TEXT | Y | 언어/프레임워크/툴/소프트스킬 등 |
|
|
131
|
+
| `level` | TEXT | Y | 상/중/하 또는 자유 서술 |
|
|
132
|
+
| `years` | REAL | Y | 경력 연수 |
|
|
133
|
+
| `order_index` | INTEGER | N | 정렬 순서. 기본 `0` |
|
|
134
|
+
| `created_at` | TEXT | N | 생성 시각(ISO) |
|
|
135
|
+
| `updated_at` | TEXT | N | 수정 시각(ISO) |
|
|
136
|
+
|
|
137
|
+
### 5. `documents`
|
|
138
|
+
|
|
139
|
+
이력서/경력기술서/포트폴리오 등 본문을 가진 문서. (자기소개서는 별도 테이블.)
|
|
140
|
+
|
|
141
|
+
| 컬럼 | 타입 | NULL | 의미 |
|
|
142
|
+
|------|------|------|------|
|
|
143
|
+
| `id` | TEXT | PK | 기본키 |
|
|
144
|
+
| `kind` | TEXT | N | 문서 종류: `resume` / `career_description` / `portfolio` / `other` |
|
|
145
|
+
| `title` | TEXT | N | 제목(필수) |
|
|
146
|
+
| `content` | TEXT | N | 본문(Markdown 또는 일반 텍스트). 기본 `''` |
|
|
147
|
+
| `source` | TEXT | N | 출처: `manual` / `upload` / `ai` / `edit`. 기본 `'manual'` |
|
|
148
|
+
| `is_primary` | INTEGER | N | 대표 문서 여부(0/1 → boolean). 기본 `0` |
|
|
149
|
+
| `tags` | TEXT **(JSON)** | N | 태그 문자열 배열. 기본 `[]` |
|
|
150
|
+
| `created_at` | TEXT | N | 생성 시각(ISO) |
|
|
151
|
+
| `updated_at` | TEXT | N | 수정 시각(ISO) |
|
|
152
|
+
|
|
153
|
+
### 6. `cover_letters`
|
|
154
|
+
|
|
155
|
+
자기소개서의 헤더/메타. **본문은 `cover_letter_versions`에 버전별로 저장**된다.
|
|
156
|
+
|
|
157
|
+
| 컬럼 | 타입 | NULL | 의미 |
|
|
158
|
+
|------|------|------|------|
|
|
159
|
+
| `id` | TEXT | PK | 기본키 |
|
|
160
|
+
| `title` | TEXT | N | 제목(필수) |
|
|
161
|
+
| `job_id` | TEXT | Y | 연결된 공고(`jobs.id`). 없으면 범용 자기소개서 |
|
|
162
|
+
| `is_primary` | INTEGER | N | 대표 여부(0/1 → boolean). 기본 `0` |
|
|
163
|
+
| `current_version_id` | TEXT | Y | 현재 버전(`cover_letter_versions.id`) |
|
|
164
|
+
| `created_at` | TEXT | N | 생성 시각(ISO) |
|
|
165
|
+
| `updated_at` | TEXT | N | 수정 시각(ISO) |
|
|
166
|
+
|
|
167
|
+
> 참고: 레코드(`CoverLetterRecord`)로 조회할 때는 `version_count`, `current_content`, `versions[]` 같은 파생/조인 필드가 추가로 채워진다(저장 컬럼이 아니라 레포지토리 조립 결과).
|
|
168
|
+
|
|
169
|
+
### 7. `cover_letter_versions`
|
|
170
|
+
|
|
171
|
+
자기소개서 한 건의 버전별 본문 이력.
|
|
172
|
+
|
|
173
|
+
| 컬럼 | 타입 | NULL | 의미 |
|
|
174
|
+
|------|------|------|------|
|
|
175
|
+
| `id` | TEXT | PK | 기본키 |
|
|
176
|
+
| `cover_letter_id` | TEXT | N | 소속 자기소개서(`cover_letters.id`). 필수 |
|
|
177
|
+
| `version_no` | INTEGER | N | 버전 번호(1부터 증가) |
|
|
178
|
+
| `content` | TEXT | N | 이 버전의 본문(필수) |
|
|
179
|
+
| `note` | TEXT | Y | 변경 요약 메모(예: 지원동기 보강) |
|
|
180
|
+
| `source` | TEXT | N | 출처: `manual` / `upload` / `ai` / `edit`. 기본 `'ai'` |
|
|
181
|
+
| `created_at` | TEXT | N | 생성 시각(ISO) |
|
|
182
|
+
|
|
183
|
+
> `updated_at`이 없다. 버전 본문은 불변(append-only) 이력으로 다룬다.
|
|
184
|
+
|
|
185
|
+
### 8. `jobs`
|
|
186
|
+
|
|
187
|
+
채용 공고.
|
|
188
|
+
|
|
189
|
+
| 컬럼 | 타입 | NULL | 의미 |
|
|
190
|
+
|------|------|------|------|
|
|
191
|
+
| `id` | TEXT | PK | 기본키 |
|
|
192
|
+
| `company` | TEXT | N | 회사명(필수) |
|
|
193
|
+
| `position` | TEXT | N | 직무/포지션명(필수) |
|
|
194
|
+
| `url` | TEXT | Y | 공고 URL |
|
|
195
|
+
| `location` | TEXT | Y | 근무지 |
|
|
196
|
+
| `employment_type` | TEXT | Y | 고용 형태 |
|
|
197
|
+
| `description` | TEXT | Y | 공고 원문 또는 정리된 텍스트 |
|
|
198
|
+
| `requirements` | TEXT **(JSON)** | N | 자격요건/우대사항 핵심 문자열 배열. 기본 `[]` |
|
|
199
|
+
| `keywords` | TEXT **(JSON)** | N | 핵심 키워드 문자열 배열. 기본 `[]` |
|
|
200
|
+
| `deadline` | TEXT | Y | 마감일(`YYYY-MM-DD`) |
|
|
201
|
+
| `source` | TEXT | Y | 출처(사람인/원티드/직접 입력 등) |
|
|
202
|
+
| `created_at` | TEXT | N | 생성 시각(ISO) |
|
|
203
|
+
| `updated_at` | TEXT | N | 수정 시각(ISO) |
|
|
204
|
+
|
|
205
|
+
### 9. `fit_analyses`
|
|
206
|
+
|
|
207
|
+
특정 공고에 대한 적합도 분석. **공고당 여러 건 가능**(분석 이력 누적). `job_id`로 연결된다.
|
|
208
|
+
|
|
209
|
+
| 컬럼 | 타입 | NULL | 의미 |
|
|
210
|
+
|------|------|------|------|
|
|
211
|
+
| `id` | TEXT | PK | 기본키 |
|
|
212
|
+
| `job_id` | TEXT | N | 분석 대상 공고(`jobs.id`). 필수 |
|
|
213
|
+
| `score` | REAL | Y | 종합 적합도 `0~100` |
|
|
214
|
+
| `summary` | TEXT | Y | 한두 문단 요약 |
|
|
215
|
+
| `strengths` | TEXT **(JSON)** | N | 강점/잘 맞는 부분 문자열 배열. 기본 `[]` |
|
|
216
|
+
| `gaps` | TEXT **(JSON)** | N | 부족한 부분/보완 필요 문자열 배열. 기본 `[]` |
|
|
217
|
+
| `matched_keywords` | TEXT **(JSON)** | N | 매칭 키워드 문자열 배열. 기본 `[]` |
|
|
218
|
+
| `missing_keywords` | TEXT **(JSON)** | N | 누락 키워드 문자열 배열. 기본 `[]` |
|
|
219
|
+
| `recommendations` | TEXT **(JSON)** | N | 자기소개서/지원 전략 제안 문자열 배열. 기본 `[]` |
|
|
220
|
+
| `created_at` | TEXT | N | 생성 시각(ISO) |
|
|
221
|
+
| `updated_at` | TEXT | N | 수정 시각(ISO) |
|
|
222
|
+
|
|
223
|
+
### 10. `applications`
|
|
224
|
+
|
|
225
|
+
지원 현황. **공고당 정확히 1건**(`job_id`에 `UNIQUE` 제약).
|
|
226
|
+
|
|
227
|
+
| 컬럼 | 타입 | NULL | 의미 |
|
|
228
|
+
|------|------|------|------|
|
|
229
|
+
| `id` | TEXT | PK | 기본키 |
|
|
230
|
+
| `job_id` | TEXT | N, **UNIQUE** | 대상 공고(`jobs.id`). 공고당 1건 |
|
|
231
|
+
| `status` | TEXT | N | 지원 상태(8단계). 기본 `'draft'`. → [지원 상태](#지원-상태-8단계) |
|
|
232
|
+
| `resume_id` | TEXT | Y | 사용한 이력서(`documents.id`) |
|
|
233
|
+
| `cover_letter_id` | TEXT | Y | 사용한 자기소개서(`cover_letters.id`) |
|
|
234
|
+
| `applied_at` | TEXT | Y | 지원 완료 시각 |
|
|
235
|
+
| `notes` | TEXT | Y | 메모 |
|
|
236
|
+
| `created_at` | TEXT | N | 생성 시각(ISO) |
|
|
237
|
+
| `updated_at` | TEXT | N | 수정 시각(ISO) |
|
|
238
|
+
|
|
239
|
+
#### 지원 상태 8단계
|
|
240
|
+
|
|
241
|
+
`packages/shared/src/enums.ts`의 `APPLICATION_STATUSES`.
|
|
242
|
+
|
|
243
|
+
| 코드 | 라벨 |
|
|
244
|
+
|------|------|
|
|
245
|
+
| `draft` | 작성 중 |
|
|
246
|
+
| `planned` | 지원 예정 |
|
|
247
|
+
| `applied` | 지원 완료 |
|
|
248
|
+
| `document_passed` | 서류 합격 |
|
|
249
|
+
| `interview` | 면접 진행 |
|
|
250
|
+
| `final_passed` | 최종 합격 |
|
|
251
|
+
| `rejected` | 불합격 |
|
|
252
|
+
| `on_hold` | 보류 |
|
|
253
|
+
|
|
254
|
+
- 칸반 보드 표시 순서(`APPLICATION_BOARD_ORDER`): `draft → planned → applied → document_passed → interview → final_passed → on_hold → rejected`.
|
|
255
|
+
- **면접 준비 해금**(`INTERVIEW_UNLOCK_STATUSES`): `document_passed`, `interview`, `final_passed` 중 하나에 도달하면 면접 준비 단계가 열린다.
|
|
256
|
+
|
|
257
|
+
### 11. `interview_preps`
|
|
258
|
+
|
|
259
|
+
면접 준비 자료. **공고당 정확히 1건**(`job_id`에 `UNIQUE` 제약).
|
|
260
|
+
|
|
261
|
+
| 컬럼 | 타입 | NULL | 의미 |
|
|
262
|
+
|------|------|------|------|
|
|
263
|
+
| `id` | TEXT | PK | 기본키 |
|
|
264
|
+
| `job_id` | TEXT | N, **UNIQUE** | 대상 공고(`jobs.id`). 공고당 1건 |
|
|
265
|
+
| `questions` | TEXT **(JSON)** | N | 예상 질문 객체 배열. 기본 `[]` |
|
|
266
|
+
| `star_guides` | TEXT **(JSON)** | N | STAR 정리 객체 배열. 기본 `[]` |
|
|
267
|
+
| `self_introduction` | TEXT | Y | 1분 자기소개 초안 |
|
|
268
|
+
| `notes` | TEXT | Y | 면접 후기/메모 |
|
|
269
|
+
| `created_at` | TEXT | N | 생성 시각(ISO) |
|
|
270
|
+
| `updated_at` | TEXT | N | 수정 시각(ISO) |
|
|
271
|
+
|
|
272
|
+
JSON 컬럼 내부 객체 구조(zod):
|
|
273
|
+
|
|
274
|
+
- `questions[]` = `{ question, intent?, followups?: string[], answer_outline? }`
|
|
275
|
+
- `star_guides[]` = `{ question, situation?, task?, action?, result? }`
|
|
276
|
+
|
|
277
|
+
### 12. `activities`
|
|
278
|
+
|
|
279
|
+
활동 피드(타임라인). `list_recent_activity` 도구와 대시보드 Home에 표시된다.
|
|
280
|
+
|
|
281
|
+
| 컬럼 | 타입 | NULL | 의미 |
|
|
282
|
+
|------|------|------|------|
|
|
283
|
+
| `id` | TEXT | PK | 기본키 |
|
|
284
|
+
| `type` | TEXT | N | 활동 유형(아래) |
|
|
285
|
+
| `entity_type` | TEXT | Y | 관련 엔티티 종류(아래) |
|
|
286
|
+
| `entity_id` | TEXT | Y | 관련 엔티티 ID |
|
|
287
|
+
| `summary` | TEXT | N | 요약 문구(필수) |
|
|
288
|
+
| `created_at` | TEXT | N | 생성 시각(ISO) |
|
|
289
|
+
|
|
290
|
+
활동 유형(`ACTIVITY_TYPES`): `profile_updated`, `resume_added`, `cover_letter_added`, `cover_letter_version_saved`, `job_saved`, `fit_analysis_saved`, `application_status_changed`, `interview_prep_saved`, `document_exported`.
|
|
291
|
+
|
|
292
|
+
엔티티 종류(`ENTITY_TYPES`): `profile`, `experience`, `project`, `skill`, `document`, `cover_letter`, `job`, `application`, `fit_analysis`, `interview_prep`.
|
|
293
|
+
|
|
294
|
+
### `_meta` (메타)
|
|
295
|
+
|
|
296
|
+
| 컬럼 | 타입 | NULL | 의미 |
|
|
297
|
+
|------|------|------|------|
|
|
298
|
+
| `key` | TEXT | PK | 메타 키(예: `schema_version`) |
|
|
299
|
+
| `value` | TEXT | N | 값(문자열) |
|
|
300
|
+
|
|
301
|
+
마이그레이션 러너가 `schema_version` 키로 현재 스키마 버전을 추적한다. → [마이그레이션](#마이그레이션)
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
## 엔티티 관계 (ER)
|
|
306
|
+
|
|
307
|
+
핵심 관계는 **공고(`jobs`)를 중심**으로 적합도 분석·지원·면접 준비가 연결되고, **자기소개서(`cover_letters`)가 버전 이력(`cover_letter_versions`)을 가진다**는 두 축이다.
|
|
308
|
+
|
|
309
|
+
연결은 애플리케이션 레벨의 외래키 의미(`*_id` 컬럼)로 표현된다. SQLite 스키마에 명시적 `FOREIGN KEY` 제약은 선언되어 있지 않으며, 무결성은 코어 use-case 계층에서 관리한다. 단, `applications.job_id`와 `interview_preps.job_id`는 `UNIQUE` 제약으로 "공고당 1건"을 DB가 직접 보장한다.
|
|
310
|
+
|
|
311
|
+
```
|
|
312
|
+
┌──────────────────┐
|
|
313
|
+
│ jobs │ (채용 공고)
|
|
314
|
+
│ PK id │
|
|
315
|
+
└───────┬──────────┘
|
|
316
|
+
│ job_id
|
|
317
|
+
┌────────────────────────┼───────────────────────────┐
|
|
318
|
+
│ (1 : N) │ (1 : 1, UNIQUE) │ (1 : 1, UNIQUE)
|
|
319
|
+
▼ ▼ ▼
|
|
320
|
+
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
|
|
321
|
+
│ fit_analyses │ │ applications │ │ interview_preps │
|
|
322
|
+
│ PK id │ │ PK id │ │ PK id │
|
|
323
|
+
│ FK job_id │ │ FK job_id (UQ) │ │ FK job_id (UQ) │
|
|
324
|
+
│ (공고당 N건, │ │ resume_id ─────────┐ │ (서류합격↑ 해금) │
|
|
325
|
+
│ 분석 이력) │ │ cover_letter_id ───┼──┐ └──────────────────┘
|
|
326
|
+
└──────────────────┘ └──────────────────┘ │ │
|
|
327
|
+
│ │
|
|
328
|
+
┌──────────────────┐ │ │
|
|
329
|
+
│ documents │◄──┘ │ resume_id → documents.id
|
|
330
|
+
│ PK id │ │ (이력서/경력기술서 등)
|
|
331
|
+
└──────────────────┘ │
|
|
332
|
+
│
|
|
333
|
+
┌──────────────────┐ │
|
|
334
|
+
│ cover_letters │◄────────────────────┘ cover_letter_id
|
|
335
|
+
│ PK id │ → cover_letters.id
|
|
336
|
+
│ job_id ─────────────► jobs.id (선택 연결)
|
|
337
|
+
│ current_version_id ─┐
|
|
338
|
+
└─────────┬────────────┘
|
|
339
|
+
│ (1 : N) │ current_version_id → cover_letter_versions.id
|
|
340
|
+
▼ │ (현재 버전 포인터)
|
|
341
|
+
┌────────────────────────┐
|
|
342
|
+
│ cover_letter_versions │ (버전별 본문 이력, append-only)
|
|
343
|
+
│ PK id │
|
|
344
|
+
│ FK cover_letter_id │
|
|
345
|
+
│ version_no (1,2,3…) │
|
|
346
|
+
└────────────────────────┘
|
|
347
|
+
|
|
348
|
+
독립 테이블(특정 공고에 직접 묶이지 않음):
|
|
349
|
+
profile · experiences · projects · skills · activities
|
|
350
|
+
→ 프로필/경력/프로젝트/기술은 지원 컨텍스트(get_application_context)로 모여
|
|
351
|
+
AI가 분석·작성 시 함께 읽힌다. activities는 전 엔티티의 활동 로그.
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
관계 요약:
|
|
355
|
+
|
|
356
|
+
| 관계 | 카디널리티 | 연결 컬럼 | 비고 |
|
|
357
|
+
|------|-----------|-----------|------|
|
|
358
|
+
| `jobs` → `fit_analyses` | 1 : N | `fit_analyses.job_id` | 공고당 분석 이력 누적 |
|
|
359
|
+
| `jobs` → `applications` | 1 : 1 | `applications.job_id` (UNIQUE) | 공고당 지원 1건 |
|
|
360
|
+
| `jobs` → `interview_preps` | 1 : 1 | `interview_preps.job_id` (UNIQUE) | 공고당 면접준비 1건 |
|
|
361
|
+
| `jobs` → `cover_letters` | 1 : N | `cover_letters.job_id` (선택) | 공고에 자기소개서 연결(선택적) |
|
|
362
|
+
| `cover_letters` → `cover_letter_versions` | 1 : N | `cover_letter_versions.cover_letter_id` | 버전 이력 |
|
|
363
|
+
| `cover_letters` → 현재 버전 | 1 : 1 | `cover_letters.current_version_id` | 현재 버전 포인터 |
|
|
364
|
+
| `applications` → `documents` | N : 1 | `applications.resume_id` | 사용한 이력서 |
|
|
365
|
+
| `applications` → `cover_letters` | N : 1 | `applications.cover_letter_id` | 사용한 자기소개서 |
|
|
366
|
+
|
|
367
|
+
---
|
|
368
|
+
|
|
369
|
+
## JSON 텍스트 컬럼
|
|
370
|
+
|
|
371
|
+
다음 컬럼은 SQLite에 **JSON 문자열(`TEXT`)** 로 저장되고, 레포지토리가 파싱해 배열/객체로 반환한다. SQLite 선언상 기본값은 빈 배열 `'[]'`이다.
|
|
372
|
+
|
|
373
|
+
| 테이블 | 컬럼 | 파싱 후 형태 |
|
|
374
|
+
|--------|------|--------------|
|
|
375
|
+
| `profile` | `desired_roles` | `string[]` |
|
|
376
|
+
| `profile` | `emphasis_points` | `string[]` |
|
|
377
|
+
| `profile` | `links` | `{ label, url }[]` |
|
|
378
|
+
| `experiences` | `achievements` | `string[]` |
|
|
379
|
+
| `experiences` | `tech` | `string[]` |
|
|
380
|
+
| `projects` | `highlights` | `string[]` |
|
|
381
|
+
| `projects` | `tech` | `string[]` |
|
|
382
|
+
| `documents` | `tags` | `string[]` |
|
|
383
|
+
| `jobs` | `requirements` | `string[]` |
|
|
384
|
+
| `jobs` | `keywords` | `string[]` |
|
|
385
|
+
| `fit_analyses` | `strengths` / `gaps` / `matched_keywords` / `missing_keywords` / `recommendations` | 각 `string[]` |
|
|
386
|
+
| `interview_preps` | `questions` | `{ question, intent?, followups?, answer_outline? }[]` |
|
|
387
|
+
| `interview_preps` | `star_guides` | `{ question, situation?, task?, action?, result? }[]` |
|
|
388
|
+
|
|
389
|
+
> boolean 컬럼(`is_current`, `documents.is_primary`, `cover_letters.is_primary`)은 JSON이 아니라 `INTEGER`(0/1)로 저장되고 boolean으로 변환된다.
|
|
390
|
+
|
|
391
|
+
---
|
|
392
|
+
|
|
393
|
+
## 마이그레이션
|
|
394
|
+
|
|
395
|
+
마이그레이션 러너는 `packages/db/src/schema.ts`에 있다.
|
|
396
|
+
|
|
397
|
+
- **버전 추적**: `_meta` 테이블의 `schema_version` 키에 현재 적용된 버전 번호를 저장한다.
|
|
398
|
+
- **마이그레이션 정의**: `MIGRATIONS` 배열의 각 원소가 하나의 버전(순서대로 적용되는 SQL 문자열)이다. 현재 v1(초기 스키마) 한 개이며, `MIGRATIONS.length`가 목표 버전이 된다.
|
|
399
|
+
- **멱등성**: 모든 `CREATE TABLE` / `CREATE INDEX`는 `IF NOT EXISTS`로 작성되어 있다. 따라서 `migrate()`는 **매 프로세스 시작마다 호출해도 안전**하다. 웹 서버와 MCP 서버가 각자 부팅 시 호출해도 충돌 없이 같은 DB 파일을 공유한다.
|
|
400
|
+
- **동작**:
|
|
401
|
+
1. `_meta`를 없으면 생성.
|
|
402
|
+
2. 저장된 `schema_version`을 `from`으로 읽는다(없으면 0).
|
|
403
|
+
3. `from`부터 `MIGRATIONS.length`까지 미적용 마이그레이션을 순서대로 `exec`한다.
|
|
404
|
+
4. 버전이 바뀌었으면 `schema_version`을 `to = MIGRATIONS.length`로 upsert한다.
|
|
405
|
+
5. `{ from, to }`를 반환한다.
|
|
406
|
+
- **새 버전 추가 방법**: `MIGRATIONS` 배열 끝에 새 SQL 문자열을 추가한다. 기존 원소는 절대 수정/삭제하지 않는다(이미 적용된 환경과의 호환을 위해). 컬럼 변경은 `ALTER TABLE ... ADD COLUMN` 등 전방향(forward-only)으로 작성한다.
|
|
407
|
+
|
|
408
|
+
마이그레이션은 `npm run migrate` 스크립트로 단독 실행할 수도 있다.
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
412
|
+
## 인덱스
|
|
413
|
+
|
|
414
|
+
`packages/db/src/schema.ts` v1에서 생성하는 인덱스:
|
|
415
|
+
|
|
416
|
+
| 인덱스 | 대상 | 목적 |
|
|
417
|
+
|--------|------|------|
|
|
418
|
+
| `idx_fit_job` | `fit_analyses(job_id)` | 공고별 적합도 분석 조회 |
|
|
419
|
+
| `idx_clv_letter` | `cover_letter_versions(cover_letter_id)` | 자기소개서별 버전 조회 |
|
|
420
|
+
| `idx_cl_job` | `cover_letters(job_id)` | 공고에 연결된 자기소개서 조회 |
|
|
421
|
+
| `idx_activities_created` | `activities(created_at DESC)` | 최근 활동 정렬 조회 |
|
|
422
|
+
|
|
423
|
+
---
|
|
424
|
+
|
|
425
|
+
## 데이터 저장 위치
|
|
426
|
+
|
|
427
|
+
경로 결정 로직은 `packages/db/src/paths.ts`에 있다.
|
|
428
|
+
|
|
429
|
+
- **데이터 디렉터리** (`getDataDir`):
|
|
430
|
+
1. 환경변수 `CAREERMATE_DATA_DIR`(절대 경로)가 있으면 그 값을 사용.
|
|
431
|
+
2. 없으면 기본값 `~/.careermate`(홈 디렉터리 하위).
|
|
432
|
+
- 디렉터리는 없으면 자동 생성된다(`mkdir -p` 동작).
|
|
433
|
+
- **DB 파일** (`getDbPath`): `{dataDir}/careermate.sqlite`
|
|
434
|
+
- **하위 디렉터리**(자동 생성):
|
|
435
|
+
|
|
436
|
+
| 함수 | 경로 | 용도 |
|
|
437
|
+
|------|------|------|
|
|
438
|
+
| `getExportsDir` | `{dataDir}/exports` | 내보낸 문서(이력서/자기소개서/면접 준비 등) |
|
|
439
|
+
| `getUploadsDir` | `{dataDir}/uploads` | 업로드 원본 파일 |
|
|
440
|
+
| `getBackupsDir` | `{dataDir}/backups` | 백업(SQLite 스냅샷 + JSON 덤프) |
|
|
441
|
+
|
|
442
|
+
런타임 핸드셰이크용 `server.json`도 데이터 디렉터리에 놓인다(대시보드/MCP가 같은 DB·포트를 찾도록).
|
|
443
|
+
|
|
444
|
+
> 모든 데이터는 사용자 컴퓨터에만 저장된다. 클라우드 전송이나 외부 서버는 없다.
|
|
445
|
+
|
|
446
|
+
---
|
|
447
|
+
|
|
448
|
+
## 백업 / 내보내기
|
|
449
|
+
|
|
450
|
+
데이터 관리 로직은 `apps/web/src/settings.ts`(Settings 페이지가 호출)에 있다.
|
|
451
|
+
|
|
452
|
+
- **전체 덤프** (`exportAll`): 12개 테이블 전체를 SELECT해 `{ exported_at, version, tables }` 형태의 기계 판독 JSON으로 반환한다(포터블 백업 / 데이터 이동용).
|
|
453
|
+
- **백업 생성** (`createBackup`): `{backups}/careermate-{timestamp}.sqlite` + `{backups}/careermate-{timestamp}.json` 두 파일을 만든다.
|
|
454
|
+
- SQLite 일관 스냅샷은 `VACUUM INTO`로 생성하며, 실패 시 파일 복사(`copyFileSync`)로 폴백한다.
|
|
455
|
+
- 타임스탬프는 ISO 문자열의 `:`·`.`을 `-`로 치환한 형태.
|
|
456
|
+
- **백업 목록** (`listBackups`): backups 디렉터리의 `.sqlite`/`.json` 파일을 최신순으로 나열(파일명/경로/크기/생성시각).
|
|
457
|
+
- **전체 초기화** (`resetAll`): 확인 문자열 `"DELETE"`가 정확히 일치할 때만 동작. **삭제 전 항상 자동 백업**을 만든 뒤 12개 테이블을 `DELETE`(트랜잭션, 실패 시 롤백)한다.
|
|
458
|
+
- **저장 위치 표시** (`getDataLocation`): 현재 `data_dir`/`db_path`를 반환(Settings 페이지에서 노출).
|
|
459
|
+
|
|
460
|
+
문서 형태의 내보내기(이력서/자기소개서/면접 준비 → Markdown 또는 인쇄용 HTML)는 `packages/exporters`가 담당한다. HTML은 브라우저의 "인쇄 → PDF로 저장" 전략을 위한 독립형·인쇄 최적화 문서다. 내보낸 파일은 `exports/` 디렉터리에 보관된다.
|