Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ uv run uvicorn app.main.run:make_app --factory --reload
| `GET /health` | 헬스체크 |

요청·콜백 페이로드와 설정 값은 [docs/api.md](docs/api.md) 에 있다.
비개발 PDF의 본문·표·이미지·벡터 도식 읽기와 profile v2 연결은 [문서 분석 1차](docs/document-analysis-v1.md)를 따른다.
Haiku 5.5 후보의 유료 한국어 20쪽 실측(M-1)은 구현 테스트와 별도이며 아직 실행하지 않았다.
질문별 음성 전처리·분석 작업과 별도 워커는 [docs/audio-analysis.md](docs/audio-analysis.md)에 있다.
영상 계약은 [docs/video-api.md](docs/video-api.md), 워커·보관·보안 운영은 [docs/video-analysis.md](docs/video-analysis.md)에 있다.
영상 행동 분석 모델은 미정이며 기본 설정은 유효한 영상도 `ANALYZER_NOT_CONFIGURED`로 끝난다.
Expand Down
54 changes: 53 additions & 1 deletion docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,31 @@ FastAPI 의 자동 문서(`/docs`, `/redoc`, `/openapi.json`)는 꺼져 있다(`

`major` 는 선택이다. 자유 문자열이며 탐색 우선순위 지시에만 쓴다. `githubUrls` 는 1개 이상, public 만.

### 직무·문서 목록 요청 (문서 분석 1차)

```json
{
"jobRole": "QA_ENGINEER",
"documents": [{"type": "TEST_PLAN", "url": "https://example.com/plan.pdf", "title": "결제 테스트 계획"}],
"githubUrls": [],
"supplements": [],
"callbackUrl": "https://api.example.com/callbacks/profile"
}
```

- `jobRole`이 없으면 위의 옛 요청 그대로 처리한다. 개발 직군의 기존 모델 입력과 profile v1은 유지한다.
- 코드 9개: `FRONTEND`, `BACKEND`, `WEB_PUBLISHER`, `DATA_ANALYST`, `DBA_INFRA`, `QA_ENGINEER`, `SECURITY`, `PRODUCT_MANAGER`, `IT_CONSULTANT`.
- `jobRole`이 있으면 `major`·`portfolioUrl`은 무시한다. `documents` 1~5개, 실제 PDF 합계 50쪽 이하. 손상·암호화 PDF는 실패한다.
- 개발 직군 GitHub는 필수, 데이터·DBA·QA·보안은 선택, 기획·컨설턴트는 받지 않는다. 문서 종류와 필수 종류는 `core/common/job_spec` 기준이다.
- `supplements`는 기획·컨설턴트만 최대 3개. `title`(1~80자), `documentIndex`(선택, 문서의 0부터 시작하는 위치), `goal`·`role`·`decision`·`outcome`(각 500자 이하)을 받는다.
- 직무·종류·필수 자료·GitHub·보완 입력 조합이 잘못되면 접수 전 422다. PDF 실제 쪽수·암호화 여부는 다운로드 후 실패 콜백 `INVALID_MATERIAL`이다.
- 비개발은 텍스트·표·그림·벡터 도식을 읽으며, 개발 직군에 새 쪽 읽기를 적용하지 않는다. 여러 개발 문서는 기존 처리 결과를 머리줄과 함께 합치고 이미지 호출 15개 상한을 요청 전체에서 공유한다.
- 비개발 성공은 `profile.schemaVersion=2`다. 아래 기존 성공 콜백 예시는 v1이다. v2 예시는 [문서 분석 계약](document-analysis-v1.md)에 있다.
- v2 `sources`는 문서별 `pageKinds`·`unreadPages`(읽기 실패/불완전)·`droppedPages`(읽은 뒤 모델 입력 예산으로 제외)를 구분한다. 원문·전사·추출 색인은 작업 중 메모리에만 둔다.
- v2 재료 `materials[]`는 `category/title/summary/evidence/sourceKind`다. 근거 경로는 `doc-N/pM`, `supp-N/goal|role|decision|outcome`, 저장소 경로다. 문서·보완 인용 `quote`는 원문 80자 이하이고 NFKC·소문자·공백/문장부호 제거 후 8자 이상이며 원문 색인에 있어야 한다. 읽기 요약과 관계 해석은 인용으로 인정하지 않는다.
- `sourceKind`는 서버가 `DOCUMENT > VISION > CODE > SUPPLEMENT` 순서로 정한다. 카테고리 2종 이상과 문서·비전·코드 근거 카테고리 1종 이상이 필요하다. v2 `claimChecks`는 `claim/reason/evidence`로 불확실한 역할·성과를 남긴다.
- 얼굴·영상 분석, 테일러·채점의 비개발 직무 확장, API·Web의 자료 저장은 이번 1차 구현에 포함하지 않는다.

**응답 202**

```json
Expand Down Expand Up @@ -123,6 +148,12 @@ API 서버는 이 보고서를 프로필과 함께 저장하고 재사용 시
`ApplicantProfile.schemaVersion`은 **1을 유지**한다. 이 저장소의 변경은 Analysis 서버 범위다.
API 서버의 DTO·저장·조회 반영은 별도로 필요하다.

PDF 텍스트 복구와 `documentExtraction` 보고서는 개발 경로의 첫 PDF(옛 요청의 `portfolioUrl`, 개발 `jobRole`
요청의 `documents[0]`)에만 적용한다. 개발 직군 문서가 여러 개면 2번째 이후 문서는 복구 없이 원문 추출·정제만
거치고, 보고서의 페이지 번호와 `pageCount`도 첫 문서 기준이다. 비개발(v2) 성공은 자체 쪽 읽기를 쓰고 복구를
적용하지 않으므로 `documentExtraction`이 기본값(`status: not_evaluated`, `pageCount: 0`)이며, 판독 범위는
`profile.sources[].unreadPages`·`droppedPages`가 나타낸다.

**근거 정리 (Stage 5')** — LLM 이 낸 근거를 실제 저장소 트리와 대조한다. `/questions/cycle` 에는 트리가
없으므로 파일이 실제로 있는지는 여기서만 보장한다.

Expand All @@ -145,6 +176,7 @@ API 서버의 DTO·저장·조회 반영은 별도로 필요하다.
| statusCode | code | 원인 |
|---|---|---|
| `422` | `INVALID_MATERIAL` | 잘못된 PDF·URL |
| `422` | `UNREADABLE_DOCUMENT` | 비개발 필수 종류 문서에서 읽은 내용이 없음. 종합 데이터 생성 모델을 호출하지 않음 |
| `422` | `INSUFFICIENT_EVIDENCE` | 코드에서 확인할 수 있는 근거 부족(이용 가능한 카테고리 2종 미만) |
| `403` | `REPO_NOT_PUBLIC` | private 저장소 |
| `500` | `INTERNAL` | LLM 호출 실패, 제출 형식 오류, API 인증·설정 또는 내부 오류 |
Expand All @@ -158,7 +190,10 @@ PDF 복구를 활성화한 경우 병합 후 사용 가능한 본문이나 그
문서 판독 실패 콜백은 `error.documentExtraction`에 같은 보고서 구조를 포함한다. 그 외 기존 실패에는
해당 필드를 넣지 않는다. 비활성 상태에는 새 문서 실패 정책을 적용하지 않는다.

같은 `422`도 원인이 다르므로 화면 안내는 `code`로 고른다. `message` 는 표시용이며 분기에 쓰지 않는다.
`UNREADABLE_DOCUMENT`(비개발 필수 종류 문서)와 `DOCUMENT_UNREADABLE`(개발 경로 PDF 텍스트 복구 후 판독 불가)은
서로 다른 코드다. `UNREADABLE_DOCUMENT` 실패에는 `error.documentExtraction`을 넣지 않는다.

같은 `422`가 여러 원인을 뜻하므로 화면 안내는 `code`로 고른다. `message`는 표시용이며 분기에 쓰지 않는다.
`code` 이름은 API 서버 `/profile/latest` 의 `failureReason` 과 같다. 다른 작업형 엔드포인트의 실패 콜백에는 `code` 가 없다.

---
Expand Down Expand Up @@ -186,6 +221,7 @@ MULTI 는 6문항을 한 번에 생성한다. 각 호출은 구성 규칙 위반
```

- `profile` 은 `/profile` 성공 콜백의 `result.profile` 을 그대로 넣는다.
- `schemaVersion=2`도 받는다. 비개발 직무 카테고리와 저장된 `materials`만 읽고 PDF를 다시 내려받지 않는다. `VISION`의 숫자·기여를 확정 전제로 확대하지 않고, `SUPPLEMENT`는 지원자의 설명으로 다룬다. 세트 수·문항 수·중복 제외 규칙은 v1과 같다.
- `excludeQuestions` 는 같은 모드의 최근 2사이클 질문 본문이다. 앞에서부터 30개(`EXCLUDE_MAX`)까지만 쓴다.
새 질문이 이 중 하나와 **같은 문장**이면 구성 규칙 위반으로 보고 다시 생성한다. 공백·문장부호·대소문자·전각 차이는
무시하고, 표현만 바꾼 질문은 잡지 않는다. `+`·`#` 같은 기호는 지우지 않으므로 `C++`와 `C`는 다른 문장이다.
Expand Down Expand Up @@ -816,6 +852,22 @@ API 서버의 신규 생성 제한을 먼저 배포해야 한다.

## 설정

문서 분석 1차 전용 설정(기존 개발 호출 설정은 유지):

| 변수 | 기본값 |
|---|---|
| `INTERVIEW_QA_PAGE_READER_MODEL` | `claude-haiku-5-5` (M-1 실측 전 후보) |
| `INTERVIEW_QA_PAGE_READER_CONCURRENCY` / `PAGE_READER_BATCH_SIZE` / `PAGE_READER_MAX_TOKENS` | 5 / 50단위 / 4096 |
| `INTERVIEW_QA_PAGE_RENDER_LONG_EDGE_PX` / `PAGE_RERENDER_LONG_EDGE_PX` | 1568 / 2400 |
| `INTERVIEW_QA_PAGE_TILE_ASPECT` / `PAGE_TILE_MAX` | 3.0 / 8 |
| `INTERVIEW_QA_PAGE_TEXT_RICH_CHARS` / `PAGE_TEXT_POOR_CHARS` | 200 / 50 |
| `INTERVIEW_QA_PAGE_IMAGE_SOME` / `PAGE_IMAGE_FIGURE` / `PAGE_IMAGE_DOMINANT` | 0.15 / 0.3 / 0.5 |
| `INTERVIEW_QA_PAGE_DRAWINGS` / `PAGE_BROKEN_RATIO` | 30 / 0.3 |
| `PROFILE_DOC_INPUT_TOKEN_LIMIT` | 100000 |

표에서 접두사를 생략한 뒤쪽 변수에도 같은 `INTERVIEW_QA_` 접두사를 붙인다.
50단위는 배치 크기이며, 이를 넘는 영역도 다음 배치로 처리한다. SDK 재시도와 확대 재읽기 1회는 따로 기록한다.

전부 환경 변수로 덮어쓴다(`.env` 지원). 기본값과 각 값을 그렇게 정한 이유는
[`src/app/main/config.py`](../src/app/main/config.py) 주석에 있다.

Expand Down
60 changes: 60 additions & 0 deletions docs/document-analysis-v1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# 비개발 문서 분석 1차 구현 계약

기준: `main 6ad7939`, 선행 개발 회귀 `ec7e8c1` / PR #34. 이슈 #33. 2026-10-17.

기준 명세는 작업 디렉터리의 `Doc/job-family-impl-spec/07-document-analysis-v1.md`와 외부 계약 01·04다. 이번 범위는 분석 서버의 J-DOC-1~3이며 영상·API·Web·Chat·일반 직군 구현은 포함하지 않는다. J-0 회귀 골든을 유지한다.

## 구현 순서

1. 새 `job_spec` 레지스트리와 문서 DTO. 기존 요청은 기존 경로로 그대로 보낸다.
2. `stage2_nondev`에서 본문·표·도형·이미지 추출, 문서/쪽/블록 출처, 보수적인 읽기 계획을 만든다.
3. 이미지를 지연 렌더링하고 50개 단위·동시성 5로 전사한다. 실패·잘림·낮은 가독성은 확대 재읽기 1회, 읽힌 일부는 보존한다.
4. profile v2 도구와 직접 원문 인용 검증을 연결한다. 선택 GitHub가 있으면 기존 탐색 루프를 재사용한다.
5. v2 사이클은 저장된 재료만 읽는다. 테일러·채점의 직무 확장 등 04 A-4의 나머지 기능은 이번 1차 범위가 아니다.

## 기술 기본값 [가정]

- 기존 `AnthropicTextClient` 인터페이스는 유지하고, 문서용 확장 포트에 `output_config`·토큰 수 계산을 제공한다. 전용 어댑터는 같은 Anthropic SDK를 사용하며 SDK 재시도 횟수를 원시 응답의 요청 헤더에서 확인한다. 개발 호출 인자에 새 키를 넣지 않는다.
- 디지털 본문의 머리말·꼬리말·목차 노이즈 제거는 기존 추출기의 규칙을 재사용한다. 가리기 표식은 먼저 적용해 이메일·전화가 빈 문자열로 사라지지 않게 한다.
- B형 시각 영역을 확실히 분리하지 못하면 전체 페이지를 읽는다. 불확실한 그림·벡터는 읽기 대상으로 유지한다. 작은 반복 이미지 중 여러 쪽의 상하 여백에서 같은 digest가 확인된 로고만 제외한다.
- 표 원본 행·쪽 출처는 전부 유지한다. row_cap은 모델 입력에만 적용한다. 표 연속 연결은 인접 쪽·동일 머리글·상하 경계·별도 제목 충돌 없음의 조건을 모두 만족할 때만 표시한다.
- 초장형 페이지는 긴 축을 10% 겹치게 최대 8영역으로 나눈다. 이를 초과하면 unreadPages로 기록하며 원문을 억지로 축소해 정상 처리하지 않는다.
- 일반 비율의 빽빽한 표는 추출 표가 40행 초과 또는 4,000자 이상일 때 10% 겹치는 2×2영역으로 읽는다 [가정]. 모델 입력에서는 인접한 겹침 영역의 전사 끝·시작이 같은 부분만 제거하고 원래 근거 블록은 보존한다. E형 추출 글자는 참고 라벨로만 쓰며 직접 인용 근거로 승격하지 않는다.
- 직무 카테고리의 한글·설명과 문서별 힌트는 01 §3.2·04 §2.1의 문구를 사용한다.
- 인용은 직접 추출 텍스트/표/전사에서만 확인한다. 읽기 요약·관계 해석은 인용 근거로 승격하지 않는다. 본문에서 빠진 표 행은 색인에는 남기되 모델 입력에서 생략했다고 표시한다.
- 필수 문서가 모두 읽히지 않으면 모델 생성 전에 `UNREADABLE_DOCUMENT`. 카테고리가 2개 미만 또는 문서·비전·코드 근거 카테고리가 0개면 `INSUFFICIENT_EVIDENCE`.
- profile 입력은 count_tokens로 100,000 이하가 될 때까지 이력서·공통 선택·직무 선택 순서, 각 문서의 뒤쪽부터 축약한다. 필수 문서는 마지막이며 모든 쪽을 먼저 읽는다. 도구와 시스템 지시문도 토큰 예산에 포함한다.
- 모델 후보 `claude-haiku-5-5`, effort low, max_tokens 4096. 별도 샘플 실측 전에는 품질 확정·실연동 완료로 표시하지 않는다. 유료 M-1은 실행하지 않는다.

## 결과

외부 요청과 콜백은 01 C-3 계약을 따른다. 비개발은 `profile.schemaVersion=2`와 `sources/materials/claimChecks/repositories`, 개발은 v1을 반환한다. `sourceKind`는 모델이 아니라 서버가 `DOCUMENT > VISION > CODE > SUPPLEMENT` 순서로 정한다. 임시 PDF·전사·원문 색인은 요청 메모리에만 두고 DB·캐시를 추가하지 않는다.

```json
{
"jobId": "uuid", "status": "succeeded",
"result": {
"profile": {
"schemaVersion": 2, "jobRole": "QA_ENGINEER", "overview": "결제 테스트 경험",
"sources": [{"id": "doc-1", "kind": "DOCUMENT", "type": "TEST_PLAN", "title": "계획서", "pages": 2,
"pageKinds": {"A": 1, "E": 1}, "unreadPages": [], "droppedPages": []}],
"materials": [
{"category": "test_design", "title": "경계값 검증", "summary": "실패 경계를 검증했다", "sourceKind": "DOCUMENT",
"evidence": [{"path": "doc-1/p1", "quote": "경계값과 예외를 검증했습니다", "note": ""}]},
{"category": "defect_reporting", "title": "재현 절차", "summary": "기대·실제 결과를 기록했다", "sourceKind": "VISION",
"evidence": [{"path": "doc-1/p2", "quote": "재현 절차와 기대 결과를 기록했습니다", "note": ""}]}
],
"claimChecks": [], "repositories": []
},
"projectSummary": {"overview": "결제 테스트 경험", "repositories": [], "techStack": [],
"coreFeatures": [{"name": "경계값 검증", "description": "실패 경계를 검증했다", "basedOn": ["doc-1/p1"]},
{"name": "재현 절차", "description": "기대·실제 결과를 기록했다", "basedOn": ["doc-1/p2"]}]}
}
}
```

v2를 받는 API·Chat·Web의 나머지 직군 확장은 별도 선행 명세를 따르며 이 PR에서 구현하지 않는다. M-1 전에는 실제 모델의 전사 정확성·비용·처리 시간이 검증됐다고 표시하지 않는다.

## 검증

생성 PDF와 가짜 클라이언트로 단일 그림·작은 벡터·표·분할·부분 실패·50개 초과 배치·PII 가리기·인용 검증·근거 부족·예산 축약을 검사한다. 개발 회귀 및 전체 ruff/format/mypy/import-linter/pytest를 통과한 뒤 자체 리뷰하고 PR한다. 선행 PR #34보다 먼저 병합하지 않는다.
Loading
Loading