docs: [esign] 전자계약 고도화 기획서 — 필기 문구 확인(Handwriting Verification)

This commit is contained in:
김보곤
2026-03-22 22:21:06 +09:00
parent 7988cd8078
commit 1bcae79b12
2 changed files with 780 additions and 0 deletions

View File

@@ -196,6 +196,7 @@ DB 도메인별:
| [hr/](features/hr/) | 인사관리 |
| [crm/README.md](features/crm/README.md) | CRM |
| [esign/README.md](features/esign/README.md) | 전자서명 |
| [esign/handwriting-verification.md](features/esign/handwriting-verification.md) | 전자서명 고도화 — 필기 문구 확인 (HWR 인식, 유사도 검증, 보험업법 준용) |
| [equipment/README.md](features/equipment/README.md) | 설비관리 (API Phase 1 완료 + DB 스키마) |
| [boards/README.md](features/boards/README.md) | 게시판 |
| [ai/README.md](features/ai/README.md) | AI 기능 (리포트, 토큰사용량, 단가설정, R2 비용추적) |

View File

@@ -0,0 +1,779 @@
# 전자계약 eSign 고도화 — 필기 문구 확인 (Handwriting Verification)
> **작성일**: 2026-03-22
> **상태**: 기획 중
> **담당**: R&D실
> **v1 참조**: [features/esign/README.md](README.md), [projects/e-sign/technical-design.md](../../projects/e-sign/technical-design.md)
---
## 1. 개요
### 1.1 배경
현재 eSign v1은 **서명 캔버스(signature_pad)** 또는 **도장 이미지 업로드**로 본인 확인을 수행한다. 이 방식은 "서명했다"는 증거만 남기며, 서명자가 **계약 내용을 실제로 확인했는지** 증명하기 어렵다.
보험업계에서는 이미 **자필 문구 확인(Handwriting Verification)** 방식을 표준으로 적용하고 있다:
- "본인은 위 내용을 충분히 이해하고 동의합니다"를 **직접 손으로 따라 쓰기**
- 필기 인식(HWR)으로 **일치율을 검증**
- 일정 임계값 이상이어야 다음 단계 진행
### 1.2 목적
기존 서명/도장에 **필기 문구 확인 단계**를 추가하여, 계약 체결의 법적 증거력과 분쟁 방지 능력을 강화한다.
### 1.3 핵심 가치
| 가치 | 설명 |
|------|------|
| **확인 증거** | "읽고 이해했다"를 자필로 증명 → 분쟁 시 강력한 증거 |
| **위변조 방지** | 단순 클릭/터치가 아닌 자필 행위 → 대리 서명 억제 |
| **법적 효력** | 보험업법 시행령 기반 자필확인 → 금감원 권고 사항 준용 |
| **차별화** | 모두싸인/도큐사인에 없는 자필확인 기능 → SAM 전자계약 경쟁력 |
### 1.4 v1 vs 고도화 비교
| 항목 | v1 (현재) | 고도화 (추가) |
|------|----------|-------------|
| 본인확인 | OTP 인증 | OTP + 필기 문구 확인 |
| 서명 방식 | 캔버스 서명 / 도장 업로드 | 기존 유지 + 자필 문구 확인 단계 추가 |
| 확인 행위 | 체크박스 동의 | 지정 문구 자필 작성 + 인식 검증 |
| 증거 | 서명 이미지 + IP/UA | + 자필 이미지 + 인식 결과 + 일치율 |
| 법적 효력 | 전자서명법 준수 | + 보험업법 자필확인 준용 |
---
## 2. 사용자 경험 (UX) 설계
### 2.1 서명 프로세스 흐름 (고도화 후)
```
[기존 v1 유지] [고도화 추가]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
링크 접속
계약 정보 확인
OTP 인증 (이메일/카카오톡)
PDF 문서 열람 ─────────────────→ 필기 확인 단계 (NEW)
├─ Step 1: 문구 표시
│ "본인은 위 내용을 확인하였습니다"
├─ Step 2: 자필 작성 (캔버스)
│ 손가락/펜으로 문구를 따라 씀
├─ Step 3: 인식 & 검증
│ HWR 엔진이 필기 인식
│ 일치율 80% 이상 → 통과
│ 미달 → 재작성 안내
서명/도장 (기존 방식) ←──────── 통과 시
제출 완료
```
### 2.2 필기 확인 화면 구성
```
┌─────────────────────────────────────────────┐
│ [SAM 전자계약] Step 2/3 │
├─────────────────────────────────────────────┤
│ │
│ 아래 문구를 직접 손으로 써 주세요 │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ │ │
│ │ 본인은 위 내용을 확인하였습니다 │ │ ← 가이드 문구 (연한 회색)
│ │ │ │
│ └─────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ │ │
│ │ ~~~~~~~~~~~~~~~~~~~~~~~~ │ │ ← 자필 캔버스 (터치/펜)
│ │ ~~~~~~~~~~~~~~~~~~~~~~~~ │ │
│ │ │ │
│ └─────────────────────────────────────┘ │
│ │
│ 인식 결과: "본인은 위 내용을 확인하였습니다"│
│ 일치율: 92% ✅ │
│ │
│ [초기화] [다음 단계 →] │
│ │
└─────────────────────────────────────────────┘
```
### 2.3 실패 시 화면
```
┌─────────────────────────────────────────────┐
│ │
│ 인식 결과: "본인은 위 을 확인합니다" │
│ 일치율: 54% ❌ │
│ │
│ ⚠️ 문구가 정확히 인식되지 않았습니다. │
│ 좀 더 또박또박 써 주세요. │
│ │
│ 💡 팁: 글자 사이 간격을 넓게 쓰면 │
│ 인식률이 높아집니다. │
│ │
│ [초기화] [다시 쓰기] │
│ │
│ 남은 시도 횟수: 4/5 │
│ │
└─────────────────────────────────────────────┘
```
### 2.4 디바이스별 최적화
| 디바이스 | 캔버스 크기 | 입력 방식 | 특이사항 |
|----------|-----------|----------|---------|
| 모바일 (< 640px) | 전체 너비 × 120px | 손가락 터치 | 가로 모드 권장 안내 |
| 태블릿 (640~1024px) | 전체 너비 × 150px | 스타일러스/터치 | Apple Pencil 최적화 |
| (> 1024px) | 700px × 180px | 마우스/터치패드 | 서명패드 USB 지원 |
---
## 3. 기술 아키텍처
### 3.1 전체 구조
```
┌──────────────────────────────────────────────────────────┐
│ 클라이언트 (브라우저) │
│ │
│ ┌────────────────┐ ┌──────────────────────────────┐ │
│ │ 필기 캔버스 │ │ 인식 엔진 (선택) │ │
│ │ (Canvas API) │ │ │ │
│ │ │ │ A. 클라우드 API (권장) │ │
│ │ - 터치 이벤트 │ │ Google Cloud Vision │ │
│ │ - 스트로크 수집│──→│ or Naver Clova OCR │ │
│ │ - 이미지 생성 │ │ │ │
│ │ │ │ B. 서버 사이드 │ │
│ └────────────────┘ │ Tesseract OCR │ │
│ └──────────────┬───────────────┘ │
│ │ │
└──────────────────────────────────────┼───────────────────┘
┌──────────────────────────────────────────────────────────┐
│ MNG 서버 (Laravel) │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ HandwritingVerificationService │ │
│ │ │ │
│ │ - verifyHandwriting(image, expectedText) │ │
│ │ - calculateSimilarity(recognized, expected) │ │
│ │ - saveVerificationResult(signerId, result) │ │
│ │ - getVerificationHistory(contractId) │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ HWR 어댑터 (전략 패턴) │ │
│ │ │ │
│ │ interface HwrAdapter { │ │
│ │ recognize(image): RecognitionResult │ │
│ │ } │ │
│ │ │ │
│ │ ├─ GoogleVisionAdapter (권장) │ │
│ │ ├─ NaverClovaAdapter (국내 대안) │ │
│ │ └─ TesseractAdapter (무료 폴백) │ │
│ └──────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────┘
```
### 3.2 HWR(Handwriting Recognition) 엔진 비교
| 엔진 | 한글 인식 | 필기 인식 | 비용 | 속도 | 적합도 |
|------|:--------:|:--------:|------|------|:------:|
| **Google Cloud Vision** | ✅ 우수 | ✅ 우수 | $1.5/1000건 | ~1초 | ⭐⭐⭐ |
| **Naver Clova OCR** | ✅ 최우수 | ✅ 우수 | 월 300건 무료 / $0.3~ | ~1초 | ⭐⭐⭐ |
| **Kakao OCR** | ✅ 우수 | 🟡 보통 | 월 10,000건 무료 | ~1.5초 | ⭐⭐ |
| **Tesseract 5** | 🟡 보통 | ❌ 약함 | 무료 | ~3초 | ⭐ |
| **MyScript** | ✅ 우수 | ✅ 최우수 | 유료 (협의) | 실시간 | ⭐⭐⭐ |
**권장 조합**: Naver Clova OCR (1순위) + Google Vision (폴백)
- Naver Clova는 한국어 필기체 인식에 특화 (보험사 사용 실적)
- 월 300건 무료 → SAM 전자계약 볼륨에 충분
- Google Vision은 폴백 및 다국어 대응용
### 3.3 인식 프로세스
```
[1] 캔버스 입력
├─ 스트로크 데이터 수집 (x, y, timestamp, pressure)
├─ 캔버스 → PNG (base64)
[2] 전처리 (서버)
├─ 이미지 리사이즈 (너비 1200px 고정)
├─ 이진화 (Otsu threshold)
├─ 노이즈 제거 (가우시안 블러)
├─ 기울기 보정 (deskew)
[3] HWR 엔진 호출
├─ Naver Clova: POST /custom/v1/general
│ body: { images: [{ format: "png", data: base64 }] }
├─ Google Vision: POST /v1/images:annotate
│ body: { requests: [{ image: { content: base64 }, features: [{ type: "TEXT_DETECTION" }] }] }
[4] 텍스트 유사도 계산
├─ 정규화: 공백 제거, 특수문자 제거
├─ Levenshtein Distance (편집 거리)
├─ Jaro-Winkler Similarity (부분 일치 가중)
├─ 최종 일치율 = (Levenshtein 50% + Jaro-Winkler 50%)
[5] 판정
├─ 80% 이상: ✅ 통과 → 다음 단계
├─ 60~79%: ⚠️ 재시도 권유 (힌트 제공)
└─ 60% 미만: ❌ 재작성 필요
```
---
## 4. 데이터 모델
### 4.1 신규 테이블: `esign_handwriting_verifications`
```sql
CREATE TABLE esign_handwriting_verifications (
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
tenant_id BIGINT UNSIGNED NOT NULL,
contract_id BIGINT UNSIGNED NOT NULL,
signer_id BIGINT UNSIGNED NOT NULL,
-- 확인 항목 정보
step_order TINYINT UNSIGNED NOT NULL DEFAULT 1,
prompt_text VARCHAR(200) NOT NULL, -- 요구 문구
-- 인식 결과
recognized_text VARCHAR(500) NULL, -- HWR 인식 결과
similarity_score DECIMAL(5,2) NULL, -- 일치율 (0.00~100.00)
is_passed BOOLEAN NOT NULL DEFAULT FALSE, -- 통과 여부
-- 이미지 저장
handwriting_image VARCHAR(500) NULL, -- 자필 이미지 경로
-- 인식 메타데이터
hwr_engine VARCHAR(50) NULL, -- 사용 엔진 (clova/google/tesseract)
hwr_confidence DECIMAL(5,2) NULL, -- 엔진 자체 신뢰도
hwr_raw_response JSON NULL, -- 엔진 원본 응답
-- 시도 정보
attempt_number TINYINT UNSIGNED NOT NULL DEFAULT 1,
-- 기록
verified_at TIMESTAMP NULL, -- 통과 시각
ip_address VARCHAR(45) NULL,
user_agent VARCHAR(500) NULL,
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
INDEX idx_contract_signer (contract_id, signer_id),
INDEX idx_tenant (tenant_id),
FOREIGN KEY (contract_id) REFERENCES esign_contracts(id) ON DELETE CASCADE,
FOREIGN KEY (signer_id) REFERENCES esign_signers(id) ON DELETE CASCADE
);
```
### 4.2 신규 테이블: `esign_verification_templates`
관리자가 계약 유형별로 확인 문구를 설정하는 템플릿이다.
```sql
CREATE TABLE esign_verification_templates (
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
tenant_id BIGINT UNSIGNED NOT NULL,
name VARCHAR(100) NOT NULL, -- 템플릿명 (예: "표준 확인 문구")
category VARCHAR(50) NULL, -- 카테고리 (근로계약, 영업계약 등)
-- 확인 단계 (JSON 배열)
steps JSON NOT NULL,
-- [
-- { "order": 1, "text": "본인은 위 내용을 확인하였습니다", "threshold": 80 },
-- { "order": 2, "text": "이름: {signer_name}", "threshold": 85 }
-- ]
pass_threshold DECIMAL(5,2) NOT NULL DEFAULT 80.00, -- 기본 통과 임계값
max_attempts TINYINT UNSIGNED NOT NULL DEFAULT 5, -- 최대 시도 횟수
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_by BIGINT UNSIGNED NULL,
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
INDEX idx_tenant_active (tenant_id, is_active)
);
```
### 4.3 기존 테이블 변경
#### `esign_contracts` 추가 컬럼
```sql
ALTER TABLE esign_contracts
ADD COLUMN verification_template_id BIGINT UNSIGNED NULL AFTER completion_template_name,
ADD COLUMN verification_required BOOLEAN NOT NULL DEFAULT FALSE AFTER verification_template_id;
```
#### `esign_signers` 추가 컬럼
```sql
ALTER TABLE esign_signers
ADD COLUMN verification_status ENUM('pending', 'passed', 'failed', 'skipped')
NOT NULL DEFAULT 'pending' AFTER status,
ADD COLUMN verification_passed_at TIMESTAMP NULL AFTER consent_agreed_at;
```
### 4.4 ERD 관계
```
esign_contracts (1) ──── (*) esign_signers
│ │
│ │
├── verification_ ├── (*) esign_handwriting_verifications
│ template_id ──→ │ (시도별 1건, 통과까지 반복)
│ │
│ └── verification_status
│ verification_passed_at
└──→ esign_verification_templates (1)
└── steps: JSON [{ order, text, threshold }]
```
---
## 5. API 설계
### 5.1 관리자 API (인증 필요)
#### 확인 템플릿 관리
| HTTP | URI | 설명 |
|------|-----|------|
| GET | `/esign/verification-templates` | 템플릿 목록 |
| POST | `/esign/verification-templates` | 템플릿 생성 |
| PUT | `/esign/verification-templates/{id}` | 템플릿 수정 |
| DELETE | `/esign/verification-templates/{id}` | 템플릿 삭제 |
#### 계약 생성 시 (기존 확장)
```json
// POST /esign/contracts/store (기존 파라미터 + 추가)
{
"title": "근로계약서",
"verification_required": true,
"verification_template_id": 1
}
```
### 5.2 서명자 API (토큰 기반, 비인증)
| HTTP | URI | 설명 |
|------|-----|------|
| GET | `/esign/sign/{token}/api/verification` | 확인 단계 정보 조회 |
| POST | `/esign/sign/{token}/api/verification/submit` | 필기 인식 제출 |
| GET | `/esign/sign/{token}/api/verification/status` | 진행 상태 확인 |
#### 확인 단계 조회 응답
```json
// GET /esign/sign/{token}/api/verification
{
"verification_required": true,
"steps": [
{
"order": 1,
"prompt_text": "본인은 위 내용을 확인하였습니다",
"status": "pending",
"attempts_remaining": 5
},
{
"order": 2,
"prompt_text": "홍길동",
"status": "pending",
"attempts_remaining": 5
}
],
"current_step": 1,
"all_passed": false
}
```
#### 필기 제출 요청/응답
```json
// POST /esign/sign/{token}/api/verification/submit
// Request
{
"step_order": 1,
"handwriting_image": "data:image/png;base64,iVBOR..."
}
// Response (성공)
{
"recognized_text": "본인은 위 내용을 확인하였습니다",
"similarity_score": 92.5,
"is_passed": true,
"next_step": 2,
"message": "확인이 완료되었습니다."
}
// Response (실패)
{
"recognized_text": "본인은 위 내을 확합니다",
"similarity_score": 54.3,
"is_passed": false,
"attempts_remaining": 4,
"hints": [
"글자 사이 간격을 조금 넓혀 보세요.",
"또박또박 쓰면 인식률이 높아집니다."
]
}
```
---
## 6. 서비스 계층 설계
### 6.1 신규 서비스
```
app/Services/ESign/
├── HandwritingVerificationService.php ← 메인 서비스
├── HwrAdapterInterface.php ← 인식 엔진 인터페이스
├── Adapters/
│ ├── NaverClovaAdapter.php ← Naver Clova OCR
│ ├── GoogleVisionAdapter.php ← Google Cloud Vision
│ └── TesseractAdapter.php ← 무료 폴백
└── TextSimilarityService.php ← 문자열 유사도 계산
```
### 6.2 HandwritingVerificationService 주요 메서드
```php
class HandwritingVerificationService
{
// 확인 단계 정보 조회
public function getVerificationSteps(EsignSigner $signer): array
// 필기 이미지 제출 → 인식 → 유사도 검증
public function submitHandwriting(
EsignSigner $signer,
int $stepOrder,
string $base64Image
): VerificationResult
// 모든 단계 통과 여부 확인
public function isAllStepsPassed(EsignSigner $signer): bool
// 이미지 전처리 (리사이즈, 이진화, 노이즈 제거)
private function preprocessImage(string $base64Image): string
// HWR 엔진 호출 (전략 패턴, 폴백 포함)
private function recognizeText(string $processedImage): RecognitionResult
// 유사도 검증
private function verifySimilarity(
string $recognized,
string $expected,
float $threshold
): bool
}
```
### 6.3 HwrAdapterInterface
```php
interface HwrAdapterInterface
{
/**
* 필기 이미지에서 텍스트를 인식한다.
*
* @param string $base64Image base64 인코딩 이미지
* @return RecognitionResult { text, confidence, rawResponse }
*/
public function recognize(string $base64Image): RecognitionResult;
/**
* 엔진 사용 가능 여부 확인
*/
public function isAvailable(): bool;
}
```
### 6.4 TextSimilarityService
```php
class TextSimilarityService
{
/**
* 두 문자열의 유사도를 계산한다 (0.0 ~ 100.0)
*
* 알고리즘:
* 1. 정규화 (공백 제거, 특수문자 제거)
* 2. Levenshtein Distance → 유사도 (50% 가중)
* 3. Jaro-Winkler Similarity (50% 가중)
* 4. 최종 점수 반환
*/
public function calculate(string $recognized, string $expected): float
/**
* 정규화: 공백/특수문자 제거, 유니코드 정규화
*/
private function normalize(string $text): string
}
```
---
## 7. 프론트엔드 설계
### 7.1 신규 뷰 파일
```
resources/views/esign/sign/
├── auth.blade.php (기존)
├── sign.blade.php (기존)
├── verification.blade.php (NEW — 필기 확인 페이지)
└── done.blade.php (기존)
```
### 7.2 필기 캔버스 구현
기존 `signature_pad` 대신 **필기 전용 캔버스**를 구현한다. 차이점:
| 항목 | 서명 캔버스 (v1) | 필기 캔버스 (고도화) |
|------|----------------|-------------------|
| 입력 영역 | 작은 박스 (서명용) | 넓은 영역 (문장 필기용) |
| 가이드 | 없음 | 연한 가이드 문구 표시 |
| 줄 표시 | 없음 | 필기 줄(lined) 가이드 |
| 결과 | 이미지 저장만 | 인식 → 검증 → 결과 표시 |
| 라이브러리 | signature_pad | Canvas API 직접 구현 |
### 7.3 JavaScript 모듈
```javascript
// HandwritingCanvas — 필기 전용 캔버스 클래스
class HandwritingCanvas {
constructor(canvasElement, options = {}) {
this.canvas = canvasElement;
this.ctx = canvasElement.getContext('2d');
this.strokes = []; // 스트로크 데이터
this.isDrawing = false;
this.lineWidth = options.lineWidth || 3;
this.guideText = options.guideText || '';
}
// 가이드 문구를 연한 색으로 표시
drawGuide() {}
// 터치/마우스 이벤트 바인딩
bindEvents() {}
// 캔버스 → base64 PNG
toDataURL() {}
// 스트로크 데이터 (인식 보조용)
getStrokeData() {}
// 초기화
clear() {}
// 빈 캔버스 여부
isEmpty() {}
}
```
---
## 8. 비즈니스 규칙
### 8.1 기본 확인 문구 (Default Prompts)
| 순번 | 문구 | 용도 | 임계값 |
|------|------|------|:------:|
| 1 | "본인은 위 내용을 확인하였습니다" | 범용 (모든 계약) | 80% |
| 2 | "{서명자 이름}" | 본인 확인 강화 | 85% |
| 3 | "위 계약 내용에 동의합니다" | 동의 확인 | 80% |
### 8.2 계약 유형별 기본 템플릿
| 계약 유형 | 확인 단계 | 문구 |
|----------|:--------:|------|
| 영업파트너 계약서 | 2단계 | ①내용 확인 + ②본인 이름 |
| 근로계약서 | 2단계 | ①내용 확인 + ②본인 이름 |
| 비밀유지 서약서 | 3단계 | ①내용 확인 + ②기밀 유지 확인 + ③본인 이름 |
| 일반 계약서 | 1단계 | ①내용 확인 |
### 8.3 검증 규칙
| 규칙 | 값 | 비고 |
|------|-----|------|
| 기본 통과 임계값 | 80% | 관리자 조정 가능 (60~95%) |
| 최대 시도 횟수 | 5회/단계 | 초과 시 관리자 확인 필요 |
| 캔버스 최소 입력 | 5 스트로크 | 의미 있는 필기 보장 |
| 이미지 최소 크기 | 200×50px | 인식 품질 보장 |
| 인식 제한 시간 | 5초 | 타임아웃 시 재시도 |
| 필기 확인 선택 여부 | 계약별 on/off | 기존 계약은 영향 없음 |
### 8.4 감사 로그 추가 액션
```
verification_started — 필기 확인 단계 진입
verification_attempted — 필기 제출 (결과 포함)
verification_passed — 단계 통과
verification_failed — 시도 횟수 초과
verification_completed — 모든 단계 통과
```
---
## 9. 보안 고려사항
| 위협 | 대응 |
|------|------|
| 이미지 조작 (외부 텍스트 이미지 촬영) | 스트로크 데이터 + 이미지 동시 검증, 스트로크 없는 이미지 거부 |
| 동일 이미지 재사용 | 이미지 해시 중복 검사, 스트로크 타임스탬프 검증 |
| 봇/자동화 공격 | 시도 횟수 제한, reCAPTCHA (선택), 필기 속도 패턴 분석 |
| HWR API 장애 | 다중 엔진 폴백, 장애 시 수동 검증 모드 전환 |
| 개인정보 | 자필 이미지 암호화 저장, 보관 기간 후 자동 삭제 |
---
## 10. 설정 (config)
```php
// config/esign.php (추가)
'handwriting_verification' => [
'enabled' => env('ESIGN_HWR_ENABLED', false),
// HWR 엔진 우선순위
'engine' => env('ESIGN_HWR_ENGINE', 'clova'), // clova, google, tesseract
'fallback_engine' => env('ESIGN_HWR_FALLBACK', 'google'),
// Naver Clova OCR
'clova' => [
'api_url' => env('CLOVA_OCR_API_URL'),
'secret_key' => env('CLOVA_OCR_SECRET_KEY'),
],
// Google Cloud Vision
'google' => [
'credentials_path' => env('GOOGLE_VISION_CREDENTIALS'),
],
// 검증 설정
'default_threshold' => 80.0, // 기본 통과 임계값 (%)
'max_attempts' => 5, // 최대 시도 횟수
'min_strokes' => 5, // 최소 스트로크 수
'recognition_timeout' => 5, // 인식 타임아웃 (초)
// 이미지 전처리
'image_max_width' => 1200,
'image_format' => 'png',
],
```
---
## 11. 개발 로드맵
### Phase 1: 핵심 엔진 (2~3주)
| 작업 | 담당 | 설명 |
|------|------|------|
| DB 마이그레이션 | MNG | 신규 2테이블 + 기존 2테이블 변경 |
| HWR 어댑터 구현 | MNG | Naver Clova + Google Vision 어댑터 |
| TextSimilarityService | MNG | Levenshtein + Jaro-Winkler 유사도 |
| HandwritingVerificationService | MNG | 메인 비즈니스 로직 |
| 이미지 전처리 | MNG | GD/Imagick 기반 전처리 파이프라인 |
### Phase 2: 프론트엔드 (1~2주)
| 작업 | 담당 | 설명 |
|------|------|------|
| HandwritingCanvas 클래스 | MNG | 필기 전용 캔버스 (터치/마우스) |
| verification.blade.php | MNG | 필기 확인 페이지 UI |
| 디바이스 최적화 | MNG | 모바일/태블릿/웹 반응형 |
| 인식 결과 UI | MNG | 일치율 표시, 재시도 안내 |
### Phase 3: 관리자 기능 (1주)
| 작업 | 담당 | 설명 |
|------|------|------|
| 확인 템플릿 관리 UI | MNG | 문구 설정, 임계값 조정 |
| 계약 생성 마법사 연동 | MNG | 필기 확인 on/off 토글 |
| 감사 로그 연동 | MNG | 시도 이력 조회 |
### Phase 4: 검증 및 안정화 (1주)
| 작업 | 담당 | 설명 |
|------|------|------|
| 인식률 테스트 | R&D | 다양한 필체로 인식률 검증 |
| 임계값 튜닝 | R&D | 실 데이터 기반 최적 임계값 |
| 폴백 시나리오 테스트 | MNG | API 장애, 타임아웃 처리 |
| 보안 테스트 | MNG | 이미지 조작, 재사용 방지 |
### 총 예상 기간: 5~7주
---
## 12. 비용 추정
### HWR API 비용 (월간)
| 엔진 | 무료 한도 | 초과 단가 | 예상 월 사용량 | 예상 비용 |
|------|----------|----------|:------------:|----------|
| Naver Clova | 300건/월 | ~$0.3/건 | 100~200건 | **무료** |
| Google Vision | 1,000건/월 | $1.5/1,000건 | 폴백 전용 | **무료~$1.5** |
> **참고**: 전자계약 월 50건 × 평균 2단계 × 평균 2회 시도 = ~200건/월 → 무료 범위 내
---
## 13. 기존 v1과의 공존 전략
| 항목 | 정책 |
|------|------|
| 기존 계약 | 영향 없음 (verification_required = false) |
| 새 계약 | 관리자가 선택 (필기 확인 on/off) |
| 서명 단계 | 필기 확인 통과 후 기존 서명/도장 단계로 진입 |
| 라우팅 | `/esign/sign/{token}` → auth → **verification** (NEW) → sign → done |
| 코드 구조 | 기존 EsignPublicController에 verification 메서드 추가 (별도 서비스 분리) |
| 뷰 파일 | 신규 verification.blade.php 추가 (기존 파일 수정 최소화) |
---
## 관련 문서
- [전자서명 기능 개요](README.md)
- [전자서명 기술 설계 (v1)](../../projects/e-sign/technical-design.md)
- [DB 스키마 — 문서/전자서명](../../system/database/documents.md)
- [알림톡 연동 가이드](../barobill-kakaotalk/esign-notification-guide.md)
---
**최종 업데이트**: 2026-03-22