Files
sam-docs/features/esign/handwriting-verification.md

859 lines
33 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 전자계약 eSign 고도화 — 필기 문구 확인 (Handwriting Verification)
> **작성일**: 2026-03-22
> **상태**: Phase 1~3 구현 완료 (관리자 기능 + 데모)
> **담당**: R&D실
> **메뉴**: 연구개발 > 전자서명 고도화 (`/esign-verification`)
> **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: 핵심 엔진 — ✅ 구현 완료 (2026-03-22)
| 작업 | 상태 | 파일 |
|------|:----:|------|
| DB 마이그레이션 | ✅ | `mng/database/migrations/2026_03_22_1*` (2개) |
| 모델 | ✅ | `EsignVerificationTemplate`, `EsignHandwritingVerification` |
| HWR 어댑터 | ✅ | `NaverClovaAdapter`, `GoogleVisionAdapter`, `TesseractAdapter` |
| TextSimilarityService | ✅ | Levenshtein + 글자유사도 (한글 mb_str_split 안전) |
| HandwritingVerificationService | ✅ | 메인 비즈니스 로직 (제출/검증/폴백/힌트) |
| config/esign.php | ✅ | HWR 엔진/임계값/시도횟수 설정 |
### Phase 2: 프론트엔드 — ✅ 구현 완료 (2026-03-22)
| 작업 | 상태 | 파일 |
|------|:----:|------|
| HandwritingCanvas (React) | ✅ | Canvas API 직접 구현 (터치/마우스, 가이드 문구) |
| 인식 데모 페이지 | ✅ | `views/esign/verification/demo.blade.php` |
| 인식 결과 UI | ✅ | 일치율 프로그레스바, 힌트, 테스트 이력 테이블 |
### Phase 3: 관리자 기능 — ✅ 구현 완료 (2026-03-22)
| 작업 | 상태 | 파일 |
|------|:----:|------|
| 확인 템플릿 CRUD | ✅ | `views/esign/verification/templates.blade.php` (React) |
| 대시보드 | ✅ | `views/esign/verification/dashboard.blade.php` (통계/흐름도) |
| 컨트롤러 | ✅ | `EsignVerificationController` (11개 API) |
| 라우트 | ✅ | `/esign-verification/*` (11개 엔드포인트) |
| 메뉴 | ✅ | 연구개발 > 전자서명 고도화 (ID: 15621) |
### Phase 4: 검증 및 안정화 — ✅ 기본 검증 완료 (2026-03-22)
| 작업 | 상태 | 결과 |
|------|:----:|------|
| TextSimilarity 유사도 테스트 | ✅ | 완전일치 100%, 오타1개 92.9%, 오타2개 85.7%, 부분누락 64.3%, 완전다름 25% |
| 서비스 DI 검증 | ✅ | Laravel 컨테이너 자동 주입 정상 |
| 라우트 등록 검증 | ✅ | 11개 라우트 정상 |
| HWR 폴백 검증 | ✅ | API 키 없을 시 빈 결과 + 적절한 힌트 반환 |
### 남은 작업 (추후)
| 작업 | 우선순위 | 설명 |
|------|:-------:|------|
| HWR API 키 설정 | 높음 | `.env`에 Naver Clova 또는 Google Vision API 키 추가 |
| 실제 전자계약 연동 | 높음 | 기존 서명 플로우에 필기 확인 단계 삽입 (verification.blade.php) |
| `esign_contracts` 컬럼 추가 | 중간 | `verification_required`, `verification_template_id` |
| `esign_signers` 컬럼 추가 | 중간 | `verification_status`, `verification_passed_at` |
| 인식률 실 데이터 튜닝 | 중간 | 다양한 필체 테스트 후 임계값 조정 |
| 감사 로그 연동 | 낮음 | `esign_audit_logs`에 verification 액션 기록 |
---
## 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 추가 (기존 파일 수정 최소화) |
---
## 14. 구현 결과 (2026-03-22)
### 14.1 생성된 파일 목록
```
mng/
├── config/esign.php ← HWR 설정
├── database/migrations/
│ ├── 2026_03_22_100000_create_esign_verification_templates_table.php
│ └── 2026_03_22_110000_create_esign_handwriting_verifications_table.php
├── app/Models/ESign/
│ ├── EsignVerificationTemplate.php
│ └── EsignHandwritingVerification.php
├── app/Services/ESign/
│ ├── TextSimilarityService.php ← 유사도 계산
│ ├── HwrAdapterInterface.php ← 엔진 인터페이스
│ ├── HandwritingVerificationService.php ← 메인 서비스
│ └── Adapters/
│ ├── NaverClovaAdapter.php ← Naver Clova OCR
│ ├── GoogleVisionAdapter.php ← Google Vision
│ └── TesseractAdapter.php ← 무료 폴백
├── app/Http/Controllers/ESign/
│ └── EsignVerificationController.php ← 관리 컨트롤러
└── resources/views/esign/verification/
├── dashboard.blade.php ← 대시보드
├── templates.blade.php ← 템플릿 CRUD
└── demo.blade.php ← 필기 인식 테스트
```
### 14.2 엔드포인트 (11개)
| Method | URI | 설명 |
|--------|-----|------|
| GET | `/esign-verification` | 대시보드 |
| GET | `/esign-verification/templates` | 템플릿 관리 |
| GET | `/esign-verification/demo` | 인식 테스트 |
| GET | `/esign-verification/api/stats` | 통계 |
| GET | `/esign-verification/api/templates` | 템플릿 목록 |
| POST | `/esign-verification/api/templates` | 템플릿 생성 |
| GET | `/esign-verification/api/templates/{id}` | 템플릿 상세 |
| PUT | `/esign-verification/api/templates/{id}` | 템플릿 수정 |
| DELETE | `/esign-verification/api/templates/{id}` | 템플릿 삭제 |
| POST | `/esign-verification/api/demo` | 인식 테스트 API |
| GET | `/esign-verification/api/history` | 검증 이력 |
### 14.3 HWR 활성화 (.env)
```bash
# Naver Clova OCR (1순위 권장)
CLOVA_OCR_API_URL=https://xxxxxx.apigw.ntruss.com/custom/v1/xxxxx/general
CLOVA_OCR_SECRET_KEY=your_secret_key
# Google Vision (폴백)
GOOGLE_VISION_API_KEY=your_api_key
```
### 14.4 기존 서비스 영향
```
✅ 기존 eSign 파일 수정 0건
✅ 별도 prefix (/esign-verification) — 기존 /esign/* 라우트와 완전 독립
✅ 별도 컨트롤러 (EsignVerificationController) — 기존 컨트롤러 미수정
✅ 별도 테이블 (MNG 마이그레이션) — 기존 esign_* 테이블 미수정
```
---
## 관련 문서
- [전자서명 기능 개요](README.md)
- [전자서명 기술 설계 (v1)](../../projects/e-sign/technical-design.md)
- [DB 스키마 — 문서/전자서명](../../system/database/documents.md)
- [알림톡 연동 가이드](../barobill-kakaotalk/esign-notification-guide.md)
---
**최종 업데이트**: 2026-03-22