feat(harness): Upstage OCR 연동 하네스 — Document AI + 7개 워크플로우
에이전트: - upstage-ocr-dev: Document Parse/Information Extraction/QA API 엔진 - ocr-workflow-dev: 7개 워크플로우 (계약서/납품서/청구서/감사/장애/회의록/브랜드계약) 오케스트레이터: upstage-ocr-orchestrator - Upstage API Base URL 연동 - 현대백화점 등 브랜드 계약서 스키마 포함 - 7종 내장 추출 템플릿 - 민감 정보 자동 마스킹 - multimodal.py (온프레미스) 와 보완 관계 API 가이드: references/upstage-api-guide.md - Document Parse/Extract/QA 요청·응답 구조 - 6개 시나리오별 추출 스키마 (계약서/납품서/청구서/보고서/회의록) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b8faec44e0
commit
d76caea5dd
200
.claude/agents/ocr-workflow-dev.md
Normal file
200
.claude/agents/ocr-workflow-dev.md
Normal file
@ -0,0 +1,200 @@
|
||||
# ocr-workflow-dev
|
||||
|
||||
## 핵심 역할
|
||||
Upstage OCR 결과를 **GUARDiA ITSM 워크플로우에 자동 연동**한다.
|
||||
6개 시나리오 + 기업 계약서 처리를 구현하여
|
||||
문서 → 자동 등록/분류/SR 생성 전 과정을 자동화한다.
|
||||
|
||||
## 구현 범위
|
||||
|
||||
### 신규 라우터: `doc_workflow.py`
|
||||
|
||||
```
|
||||
엔드포인트:
|
||||
POST /api/docflow/contract — 계약서 OCR → 조달 자동 등록
|
||||
POST /api/docflow/server-spec — 서버납품서 OCR → CMDB 자동 등록
|
||||
POST /api/docflow/invoice — 청구서/세금계산서 → 과금 연동
|
||||
POST /api/docflow/audit-report — 감사보고서 → CSAP 자동 업데이트
|
||||
POST /api/docflow/incident-report — 장애보고서 이미지 → SR 자동 생성
|
||||
POST /api/docflow/meeting-minutes — 회의록 → SR/작업 자동 생성
|
||||
POST /api/docflow/brand-contract — 브랜드 계약서 (현대백화점 등) 처리
|
||||
GET /api/docflow/jobs — 워크플로우 작업 목록
|
||||
GET /api/docflow/jobs/{id} — 작업 상세·결과
|
||||
```
|
||||
|
||||
### 신규 라우터: `doc_template.py`
|
||||
|
||||
```
|
||||
엔드포인트:
|
||||
GET /api/doctemplate/ — 추출 템플릿 목록
|
||||
POST /api/doctemplate/ — 템플릿 생성
|
||||
PUT /api/doctemplate/{id} — 템플릿 수정
|
||||
DELETE /api/doctemplate/{id} — 템플릿 삭제
|
||||
GET /api/doctemplate/builtin — 내장 템플릿 목록
|
||||
POST /api/doctemplate/apply-builtin — 내장 템플릿 적용
|
||||
```
|
||||
|
||||
### 시나리오 구현
|
||||
|
||||
#### 시나리오 1: 나라장터 계약서 자동 등록
|
||||
```python
|
||||
async def process_contract(file_bytes, filename, api_key, tenant_id, db):
|
||||
# 1. Upstage Information Extraction
|
||||
schema = {
|
||||
"contract_no": "계약번호",
|
||||
"contract_name": "계약품명",
|
||||
"supplier": "공급사명",
|
||||
"supplier_biz_no": "공급사사업자번호",
|
||||
"amount": "계약금액",
|
||||
"start_date": "계약시작일",
|
||||
"end_date": "계약종료일",
|
||||
"institution": "발주기관명",
|
||||
}
|
||||
extracted = await extract_information(api_key, file_bytes, filename, schema)
|
||||
|
||||
# 2. e_procurement.py 자동 등록
|
||||
if extracted.get("contract_no"):
|
||||
await db.execute(insert(ProcurementRecord).values(
|
||||
tenant_id=tenant_id,
|
||||
contract_no=extracted["contract_no"],
|
||||
contract_name=extracted["contract_name"],
|
||||
supplier=extracted["supplier"],
|
||||
amount=parse_amount(extracted["amount"]),
|
||||
start_date=parse_date(extracted["start_date"]),
|
||||
end_date=parse_date(extracted["end_date"]),
|
||||
))
|
||||
return extracted
|
||||
```
|
||||
|
||||
#### 시나리오 2: 서버 납품서 → CMDB 자동 등록
|
||||
```python
|
||||
# 납품서에서 서버 사양 추출 → tb_server_info 자동 등록
|
||||
schema = {
|
||||
"hostname": "호스트명",
|
||||
"model": "서버모델",
|
||||
"cpu": "CPU 사양",
|
||||
"memory_gb": "메모리(GB)",
|
||||
"disk_tb": "스토리지(TB)",
|
||||
"ip_addr": "IP주소",
|
||||
"serial_no": "시리얼번호",
|
||||
"warranty_until": "보증기간",
|
||||
}
|
||||
```
|
||||
|
||||
#### 시나리오 3: 브랜드 계약서 (현대백화점 등)
|
||||
```python
|
||||
# 일반 기업 계약서 템플릿
|
||||
schema = {
|
||||
"contract_title": "계약서명",
|
||||
"party_a": "갑(발주사)",
|
||||
"party_b": "을(수주사)",
|
||||
"contract_amount": "계약금액",
|
||||
"contract_period": "계약기간",
|
||||
"payment_terms": "지급조건",
|
||||
"effective_date": "계약일",
|
||||
"contract_items": "계약품목",
|
||||
"special_conditions": "특수조건",
|
||||
"penalty_clause": "위약금 조항",
|
||||
"contact_a": "갑 담당자",
|
||||
"contact_b": "을 담당자",
|
||||
}
|
||||
# 추출 후 → ProcurementRecord + SR 연계 또는 별도 BrandContract 테이블
|
||||
```
|
||||
|
||||
#### 시나리오 4: 장애보고서 이미지 → SR 자동 생성
|
||||
```python
|
||||
# Document Parse로 에러 내용 추출 → SRRequest 자동 생성
|
||||
extracted_text = await parse_document(api_key, file_bytes, filename)
|
||||
error_summary = extract_error_from_text(extracted_text["text"])
|
||||
sr = SRRequest(
|
||||
title=f"[장애보고서] {error_summary[:80]}",
|
||||
description=extracted_text["text"][:1000],
|
||||
category="INCIDENT", priority="HIGH",
|
||||
status=SRStatus.OPEN,
|
||||
)
|
||||
```
|
||||
|
||||
### 내장 추출 템플릿
|
||||
|
||||
```python
|
||||
BUILTIN_TEMPLATES = {
|
||||
"narasajang_contract": {
|
||||
"name": "나라장터 계약서",
|
||||
"schema": {"contract_no": "계약번호", "amount": "계약금액", ...},
|
||||
"workflow": "e_procurement",
|
||||
},
|
||||
"server_delivery": {
|
||||
"name": "서버 납품 명세서",
|
||||
"schema": {"hostname": "호스트명", "cpu": "CPU", "memory_gb": "메모리(GB)", ...},
|
||||
"workflow": "cmdb_register",
|
||||
},
|
||||
"brand_contract": {
|
||||
"name": "일반 기업 계약서 (현대백화점 등)",
|
||||
"schema": {"party_a": "갑", "party_b": "을", "contract_amount": "계약금액", ...},
|
||||
"workflow": "procurement_record",
|
||||
},
|
||||
"invoice": {
|
||||
"name": "세금계산서/청구서",
|
||||
"schema": {"invoice_no": "세금계산서번호", "supplier": "공급자", "amount": "공급가액", ...},
|
||||
"workflow": "billing",
|
||||
},
|
||||
"incident_report": {
|
||||
"name": "장애 보고서",
|
||||
"schema": {"error_type": "오류유형", "affected_system": "영향 시스템", ...},
|
||||
"workflow": "sr_create",
|
||||
},
|
||||
"csap_report": {
|
||||
"name": "CSAP 점검 보고서",
|
||||
"schema": {"check_item": "점검항목", "result": "점검결과", "score": "점수", ...},
|
||||
"workflow": "compliance_update",
|
||||
},
|
||||
"meeting_minutes": {
|
||||
"name": "회의록",
|
||||
"schema": {"date": "회의일", "participants": "참석자", "decisions": "결정사항", "actions": "액션아이템"},
|
||||
"workflow": "sr_create",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## DB 모델 추가
|
||||
```python
|
||||
class DocWorkflowJob(Base):
|
||||
__tablename__ = "tb_doc_workflow_job"
|
||||
id = Column(Integer, primary_key=True)
|
||||
tenant_id = Column(Integer, nullable=False, index=True)
|
||||
workflow_type = Column(String(50)) # contract|server_spec|invoice|...
|
||||
filename = Column(String(300))
|
||||
template_id = Column(Integer, nullable=True)
|
||||
status = Column(String(20), default="PROCESSING")
|
||||
extracted_data = Column(JSON, nullable=True)
|
||||
linked_record_id = Column(Integer, nullable=True)
|
||||
linked_table = Column(String(50), nullable=True)
|
||||
error_message = Column(Text, nullable=True)
|
||||
created_by = Column(Integer, ForeignKey("tb_user.id"))
|
||||
created_at = Column(DateTime, default=func.now())
|
||||
completed_at = Column(DateTime, nullable=True)
|
||||
|
||||
class DocTemplate(Base):
|
||||
__tablename__ = "tb_doc_template"
|
||||
id = Column(Integer, primary_key=True)
|
||||
tenant_id = Column(Integer, nullable=False, index=True)
|
||||
name = Column(String(200), nullable=False)
|
||||
description = Column(Text, nullable=True)
|
||||
schema_json = Column(Text, nullable=False) # 추출 스키마
|
||||
workflow = Column(String(50), nullable=True) # 연동 워크플로우
|
||||
is_builtin = Column(Boolean, default=False)
|
||||
is_active = Column(Boolean, default=True)
|
||||
created_at = Column(DateTime, default=func.now())
|
||||
```
|
||||
|
||||
## 작업 원칙
|
||||
1. 민감 정보(주민번호·계좌번호) 자동 마스킹 후 저장
|
||||
2. 추출 실패 시 수동 검토 큐에 등록 (SR 생성)
|
||||
3. 기업 계약서는 brand_contract 템플릿으로 표준화
|
||||
4. 워크플로우 결과는 반드시 원본 파일과 연계 추적
|
||||
|
||||
## 팀 통신 프로토콜
|
||||
- **수신**: orchestrator로부터 "워크플로우 구현 시작"; upstage-ocr-dev에서 API 스펙 수신
|
||||
- **발신**: `_workspace/workflow_spec.md`
|
||||
- **협업**: upstage-ocr-dev의 parse/extract 함수 재사용
|
||||
- **보고**: 6개 시나리오 + 브랜드 계약 워크플로우 완료 보고
|
||||
108
.claude/agents/upstage-ocr-dev.md
Normal file
108
.claude/agents/upstage-ocr-dev.md
Normal file
@ -0,0 +1,108 @@
|
||||
# upstage-ocr-dev
|
||||
|
||||
## 핵심 역할
|
||||
GUARDiA ITSM에 **Upstage Document AI OCR 엔진**을 구현한다.
|
||||
`workspace/guardia-itsm/routers/upstage_ocr.py`에
|
||||
Upstage API 연동 코어(Document Parse, Information Extraction, Document QA)를 구현한다.
|
||||
|
||||
## 구현 범위
|
||||
|
||||
### 신규 라우터: `upstage_ocr.py`
|
||||
|
||||
```
|
||||
엔드포인트:
|
||||
POST /api/ocr/config — Upstage API Key 설정 (AES-256-GCM 암호화)
|
||||
GET /api/ocr/config — 설정 조회 (마스킹)
|
||||
POST /api/ocr/parse — 문서 파싱 (PDF/PNG/JPG → 구조화 JSON)
|
||||
POST /api/ocr/extract — 정보 추출 (Key-Value, 스키마 기반)
|
||||
POST /api/ocr/qa — 문서 QA (문서 + 질문 → 답변)
|
||||
POST /api/ocr/batch — 배치 처리 (다중 파일)
|
||||
GET /api/ocr/history — OCR 처리 이력
|
||||
GET /api/ocr/usage — API 사용량 현황
|
||||
```
|
||||
|
||||
### Upstage API 연동
|
||||
|
||||
```python
|
||||
UPSTAGE_BASE = "https://api.upstage.ai/v1/document-ai"
|
||||
|
||||
async def parse_document(api_key: str, file_bytes: bytes,
|
||||
filename: str, model: str = "document-parse") -> dict:
|
||||
"""
|
||||
Upstage Document Parse API 호출.
|
||||
반환: {pages, elements, tables, text, html, ...}
|
||||
"""
|
||||
async with httpx.AsyncClient(timeout=60) as client:
|
||||
files = {"document": (filename, file_bytes, _mime_type(filename))}
|
||||
headers = {"Authorization": f"Bearer {api_key}"}
|
||||
r = await client.post(
|
||||
f"{UPSTAGE_BASE}/document-digitization",
|
||||
files=files, headers=headers,
|
||||
data={"model": model, "ocr": "auto", "output_formats": ["text", "html", "markdown"]}
|
||||
)
|
||||
return r.json() if r.status_code == 200 else {"error": r.text[:200]}
|
||||
|
||||
async def extract_information(api_key: str, file_bytes: bytes,
|
||||
filename: str, schema: dict) -> dict:
|
||||
"""
|
||||
Upstage Information Extraction API.
|
||||
schema 예시: {"contract_no": "계약번호", "amount": "계약금액", "supplier": "공급사명"}
|
||||
"""
|
||||
async with httpx.AsyncClient(timeout=60) as client:
|
||||
files = {"document": (filename, file_bytes, _mime_type(filename))}
|
||||
headers = {"Authorization": f"Bearer {api_key}"}
|
||||
r = await client.post(
|
||||
f"{UPSTAGE_BASE}/information-extraction",
|
||||
files=files, headers=headers,
|
||||
data={"schema": json.dumps(schema, ensure_ascii=False)}
|
||||
)
|
||||
return r.json() if r.status_code == 200 else {"error": r.text[:200]}
|
||||
```
|
||||
|
||||
### DB 모델
|
||||
|
||||
```python
|
||||
class UpstageOCRConfig(Base):
|
||||
__tablename__ = "tb_upstage_ocr_config"
|
||||
tenant_id = Column(Integer, primary_key=True)
|
||||
api_key_enc = Column(Text, nullable=False) # AES-256-GCM 암호화
|
||||
model = Column(String(50), default="document-parse")
|
||||
is_active = Column(Boolean, default=True)
|
||||
created_at = Column(DateTime, default=func.now())
|
||||
|
||||
class OCRHistory(Base):
|
||||
__tablename__ = "tb_ocr_history"
|
||||
id = Column(Integer, primary_key=True)
|
||||
tenant_id = Column(Integer, nullable=False, index=True)
|
||||
filename = Column(String(300), nullable=False)
|
||||
file_size = Column(Integer, default=0)
|
||||
ocr_type = Column(String(30)) # PARSE | EXTRACT | QA
|
||||
schema_used = Column(Text, nullable=True) # 추출 스키마 JSON
|
||||
result_json = Column(Text, nullable=True) # 결과 요약 (전체 아님)
|
||||
linked_to = Column(String(50), nullable=True) # sr | contract | cmdb
|
||||
linked_id = Column(Integer, nullable=True)
|
||||
pages = Column(Integer, default=1)
|
||||
tokens_used = Column(Integer, default=0)
|
||||
status = Column(String(20), default="SUCCESS")
|
||||
created_by = Column(Integer, ForeignKey("tb_user.id"))
|
||||
created_at = Column(DateTime, default=func.now())
|
||||
```
|
||||
|
||||
### 파일 지원 형식
|
||||
- PDF (텍스트 레이어 있음/없음 모두)
|
||||
- PNG, JPG, JPEG, TIFF, BMP
|
||||
- 최대 파일 크기: 20MB
|
||||
- 최대 페이지: 100페이지
|
||||
|
||||
### 보안 원칙
|
||||
1. Upstage API Key는 AES-256-GCM 암호화 저장
|
||||
2. 테넌트별 독립 API Key 관리
|
||||
3. 민감 문서 (기밀, 개인정보): 온프레미스 `multimodal.py` 사용 권고
|
||||
4. OCR 결과에서 주민번호, 계좌번호 자동 마스킹
|
||||
5. API 사용량 추적 및 일일 한도 관리
|
||||
|
||||
## 팀 통신 프로토콜
|
||||
- **수신**: orchestrator로부터 "OCR 엔진 구현 시작"
|
||||
- **발신**: `_workspace/ocr_api_spec.md` (API 스펙)
|
||||
- **협업**: ocr-workflow-dev에게 parse/extract 함수 인터페이스 제공
|
||||
- **보고**: 완료 후 지원 문서 형식 + API 응답 구조 문서화
|
||||
245
.claude/skills/upstage-ocr-orchestrator/SKILL.md
Normal file
245
.claude/skills/upstage-ocr-orchestrator/SKILL.md
Normal file
@ -0,0 +1,245 @@
|
||||
---
|
||||
name: upstage-ocr-orchestrator
|
||||
description: >
|
||||
Upstage Document AI OCR 연동 및 GUARDiA ITSM 워크플로우 자동화 오케스트레이터.
|
||||
Upstage API(Document Parse, Information Extraction, Document QA)를 연동하여
|
||||
계약서·납품서·청구서·장애보고서·감사보고서 등을 자동 처리한다.
|
||||
현대백화점 같은 기업 브랜드 계약서를 포함한 일반 계약서도 처리한다.
|
||||
처리 결과를 ITSM 기능(조달관리·CMDB·과금·CSAP·SR)에 자동 연동한다.
|
||||
다음 상황에서 반드시 사용:
|
||||
(1) 'OCR', 'Upstage', '문서 파싱', '문서 자동 처리', '계약서 OCR';
|
||||
(2) '납품서 CMDB 등록', '청구서 자동 파싱', '계약서 자동 등록';
|
||||
(3) '현대백화점 계약', '브랜드 계약서', '일반 계약서 처리';
|
||||
(4) '장애보고서 이미지 → SR', '회의록 → 액션아이템';
|
||||
(5) 'CSAP 보고서 자동 업데이트', '감사 문서 처리';
|
||||
(6) 다시 실행, 업데이트, 수정, 보완.
|
||||
---
|
||||
|
||||
# Upstage OCR 연동 오케스트레이터
|
||||
|
||||
**실행 모드:** 에이전트 팀
|
||||
- 팀 구성: upstage-ocr-dev (OCR 엔진) + ocr-workflow-dev (ITSM 연동)
|
||||
- Phase 1: OCR 엔진 구현
|
||||
- Phase 2: 워크플로우 구현 (병렬)
|
||||
- Phase 3: DB 모델 + main.py 등록
|
||||
|
||||
---
|
||||
|
||||
## 시스템 개요
|
||||
|
||||
```
|
||||
사용자/시스템
|
||||
↓ 문서 업로드 (PDF/PNG/JPG)
|
||||
upstage_ocr.py
|
||||
↓ Upstage API 호출
|
||||
├── Document Parse → 구조화 JSON (레이아웃/텍스트/테이블)
|
||||
├── Information Extraction → Key-Value (스키마 기반)
|
||||
└── Document QA → 자연어 답변
|
||||
doc_workflow.py
|
||||
↓ 결과 → ITSM 자동 연동
|
||||
├── 계약서 → e_procurement.py (ProcurementRecord)
|
||||
├── 납품서 → cmdb.py (Server 등록)
|
||||
├── 청구서 → billing.py (Invoice)
|
||||
├── 감사보고서 → compliance.py (CSAP 업데이트)
|
||||
├── 장애보고서 → tasks.py (SR 생성)
|
||||
├── 회의록 → tasks.py (SR + 작업)
|
||||
└── 브랜드 계약서 → ProcurementRecord
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 0: 컨텍스트 확인
|
||||
|
||||
```
|
||||
_workspace/ 없음 → 초기 구현
|
||||
있음 + OCR 엔진만 → upstage-ocr-dev 재실행
|
||||
있음 + 워크플로우만 → ocr-workflow-dev 재실행
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: OCR 엔진 구현 (upstage-ocr-dev)
|
||||
|
||||
### 구현 파일: `workspace/guardia-itsm/routers/upstage_ocr.py`
|
||||
|
||||
**핵심 구현:**
|
||||
|
||||
```python
|
||||
UPSTAGE_BASE = "https://api.upstage.ai/v1/document-ai"
|
||||
|
||||
# 지원 형식
|
||||
SUPPORTED_MIME = {
|
||||
".pdf": "application/pdf",
|
||||
".png": "image/png",
|
||||
".jpg": "image/jpeg",
|
||||
".jpeg": "image/jpeg",
|
||||
".tiff": "image/tiff",
|
||||
".bmp": "image/bmp",
|
||||
".heic": "image/heic",
|
||||
}
|
||||
|
||||
# 민감 정보 마스킹 패턴
|
||||
SENSITIVE_PATTERNS = [
|
||||
(r'\d{6}-[1-4]\d{6}', '######-#######'), # 주민번호
|
||||
(r'\d{3}-\d{2}-\d{5}', '###-##-#####'), # 사업자번호 (보존)
|
||||
(r'\d{3,4}-\d{4}-\d{4}', '****-****-****'), # 전화번호 뒷자리
|
||||
]
|
||||
```
|
||||
|
||||
**엔드포인트:**
|
||||
|
||||
| 엔드포인트 | 설명 |
|
||||
|-----------|------|
|
||||
| `POST /api/ocr/config` | Upstage API Key 저장 (AES-256-GCM) |
|
||||
| `GET /api/ocr/config` | 설정 조회 (키 마스킹) |
|
||||
| `POST /api/ocr/parse` | 문서 파싱 → 구조화 JSON |
|
||||
| `POST /api/ocr/extract` | 정보 추출 → Key-Value |
|
||||
| `POST /api/ocr/qa` | 문서 QA → 자연어 답변 |
|
||||
| `POST /api/ocr/batch` | 다중 파일 배치 처리 |
|
||||
| `GET /api/ocr/history` | 처리 이력 |
|
||||
| `GET /api/ocr/usage` | API 사용량 |
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: 워크플로우 구현 (ocr-workflow-dev)
|
||||
|
||||
### 구현 파일 1: `workspace/guardia-itsm/routers/doc_workflow.py`
|
||||
|
||||
| 엔드포인트 | 워크플로우 | 연동 |
|
||||
|-----------|---------|------|
|
||||
| `POST /api/docflow/contract` | 나라장터 계약서 | e_procurement.py |
|
||||
| `POST /api/docflow/server-spec` | 서버납품서 | cmdb.py |
|
||||
| `POST /api/docflow/invoice` | 청구서/세금계산서 | billing.py |
|
||||
| `POST /api/docflow/audit-report` | 감사·CSAP 보고서 | compliance.py |
|
||||
| `POST /api/docflow/incident-report` | 장애보고서 이미지 | tasks.py (SR) |
|
||||
| `POST /api/docflow/meeting-minutes` | 회의록 | tasks.py (SR+작업) |
|
||||
| `POST /api/docflow/brand-contract` | **기업 브랜드 계약서** | ProcurementRecord |
|
||||
| `GET /api/docflow/jobs` | 작업 목록 | — |
|
||||
| `GET /api/docflow/jobs/{id}` | 작업 상세 | — |
|
||||
|
||||
#### 브랜드 계약서 처리 (현대백화점 등)
|
||||
|
||||
```python
|
||||
BRAND_CONTRACT_SCHEMA = {
|
||||
"contract_title": "계약서 제목",
|
||||
"party_a": "갑(발주사/브랜드사)",
|
||||
"party_b": "을(수주사/공급사)",
|
||||
"contract_amount": "계약금액",
|
||||
"currency": "통화",
|
||||
"contract_period": "계약기간",
|
||||
"effective_date": "계약일",
|
||||
"expiry_date": "만료일",
|
||||
"payment_terms": "지급조건",
|
||||
"contract_items": "계약품목/서비스",
|
||||
"royalty_rate": "수수료율/로열티",
|
||||
"territory": "적용지역",
|
||||
"exclusive": "독점여부",
|
||||
"renewal_clause": "갱신조항",
|
||||
"termination": "해지조건",
|
||||
"penalty_clause": "위약금",
|
||||
"contact_a": "갑 담당자",
|
||||
"contact_b": "을 담당자",
|
||||
"special_terms": "특약사항",
|
||||
}
|
||||
```
|
||||
|
||||
### 구현 파일 2: `workspace/guardia-itsm/routers/doc_template.py`
|
||||
|
||||
내장 템플릿 + 커스텀 템플릿 관리
|
||||
|
||||
**내장 템플릿 7종:**
|
||||
1. `narasajang_contract` — 나라장터 계약서
|
||||
2. `server_delivery` — 서버 납품 명세서
|
||||
3. `brand_contract` — 기업 브랜드 계약서 (현대백화점 등)
|
||||
4. `invoice` — 세금계산서/청구서
|
||||
5. `incident_report` — 장애 보고서
|
||||
6. `csap_report` — CSAP 점검 보고서
|
||||
7. `meeting_minutes` — 회의록
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: DB 모델 + main.py 등록
|
||||
|
||||
```python
|
||||
# 신규 모델
|
||||
class UpstageOCRConfig(Base): ... # API Key 설정
|
||||
class OCRHistory(Base): ... # 처리 이력
|
||||
class DocWorkflowJob(Base): ... # 워크플로우 작업
|
||||
class DocTemplate(Base): ... # 추출 템플릿
|
||||
|
||||
# main.py 등록
|
||||
from routers import upstage_ocr, doc_workflow, doc_template
|
||||
app.include_router(upstage_ocr.router)
|
||||
app.include_router(doc_workflow.router)
|
||||
app.include_router(doc_template.router)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ITSM 사이드바 메뉴 추가
|
||||
|
||||
```javascript
|
||||
// index.html에 추가
|
||||
{ label: '문서 AI (OCR)', icon: '📄', children: [
|
||||
{ nav: 'ocr_parse', label: 'Upstage OCR 파싱' },
|
||||
{ nav: 'ocr_contract', label: '계약서 자동 처리' },
|
||||
{ nav: 'ocr_invoice', label: '청구서 처리' },
|
||||
{ nav: 'ocr_history', label: 'OCR 처리 이력' },
|
||||
{ nav: 'doc_templates', label: '추출 템플릿 관리' },
|
||||
]}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 데이터 흐름
|
||||
|
||||
```
|
||||
_workspace/
|
||||
├── ocr_api_spec.md ← upstage-ocr-dev
|
||||
└── workflow_spec.md ← ocr-workflow-dev
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 에러 핸들링
|
||||
|
||||
| 에러 | 처리 |
|
||||
|------|------|
|
||||
| Upstage API Key 없음 | 설정 페이지 안내 |
|
||||
| API 한도 초과 | 큐잉 후 재시도 |
|
||||
| 지원 안 되는 파일 형식 | 400 오류 + 지원 형식 안내 |
|
||||
| 추출 실패 (낮은 신뢰도) | 수동 검토 큐 등록 + SR 생성 |
|
||||
| 민감 정보 감지 | 자동 마스킹 후 처리 |
|
||||
| 파일 크기 초과 | 20MB 한도 안내 |
|
||||
|
||||
---
|
||||
|
||||
## 테스트 시나리오
|
||||
|
||||
**정상 흐름 (브랜드 계약서):**
|
||||
1. POST /api/docflow/brand-contract + 현대백화점_계약서.pdf
|
||||
2. Upstage Extract API 호출 → 계약번호, 금액, 기간 추출
|
||||
3. ProcurementRecord 자동 생성
|
||||
4. 응답: {"ok": true, "contract_no": "HDC-2026-001", "amount": 50000000, ...}
|
||||
|
||||
**정상 흐름 (장애보고서 이미지):**
|
||||
1. POST /api/docflow/incident-report + 장애화면.png
|
||||
2. Upstage Document Parse → 에러 텍스트 추출
|
||||
3. SR 자동 생성 (category=INCIDENT, priority=HIGH)
|
||||
4. 응답: {"ok": true, "sr_id": 1234}
|
||||
|
||||
---
|
||||
|
||||
## should-trigger
|
||||
|
||||
- "OCR 연동", "Upstage 설정", "문서 파싱"
|
||||
- "계약서 자동 등록", "현대백화점 계약", "브랜드 계약서"
|
||||
- "납품서 CMDB 등록", "청구서 자동 처리"
|
||||
- "장애보고서 이미지 SR", "회의록 액션아이템"
|
||||
- "다시 실행", "수정", "보완"
|
||||
|
||||
## should-NOT-trigger
|
||||
|
||||
- "multimodal 이미지 분석" → multimodal.py (온프레미스)
|
||||
- "SR 처리" → guardia-orchestrator
|
||||
- "CMDB 관리" → cmdb.py 직접
|
||||
@ -0,0 +1,305 @@
|
||||
# Upstage Document AI API 가이드
|
||||
|
||||
## 기본 정보
|
||||
|
||||
| 항목 | 값 |
|
||||
|------|-----|
|
||||
| Base URL | `https://api.upstage.ai/v1/document-ai` |
|
||||
| 인증 | Bearer Token (API Key) |
|
||||
| 최대 파일 크기 | 20MB |
|
||||
| 지원 형식 | PDF, PNG, JPG, JPEG, TIFF, BMP, HEIC |
|
||||
| 최대 페이지 | 100페이지/요청 |
|
||||
|
||||
---
|
||||
|
||||
## 1. Document Parse (문서 파싱)
|
||||
|
||||
### 요청
|
||||
```http
|
||||
POST https://api.upstage.ai/v1/document-ai/document-digitization
|
||||
Authorization: Bearer {API_KEY}
|
||||
Content-Type: multipart/form-data
|
||||
|
||||
document: (파일 바이너리)
|
||||
model: document-parse # 텍스트 레이어 있는 PDF
|
||||
document-parse-ocr # 이미지/스캔 PDF
|
||||
ocr: auto # auto: 자동 판단
|
||||
output_formats: ["text", "html", "markdown"]
|
||||
```
|
||||
|
||||
### 응답 구조
|
||||
```json
|
||||
{
|
||||
"api": "2.0",
|
||||
"model": "document-parse",
|
||||
"usage": {"pages": 3, "tokens": 1200},
|
||||
"content": {
|
||||
"markdown": "# 계약서\n\n...",
|
||||
"html": "<html>...</html>",
|
||||
"text": "계약서\n\n당사는 ..."
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"category": "paragraph", // paragraph | table | figure | header | footer
|
||||
"content": {"markdown": "...", "html": "...", "text": "..."},
|
||||
"coordinates": [{"x": 0.1, "y": 0.1, "w": 0.8, "h": 0.05}],
|
||||
"page": 1
|
||||
},
|
||||
{
|
||||
"category": "table",
|
||||
"content": {"markdown": "| 항목 | 금액 |\n|---|---|\n...", "html": "<table>...</table>"},
|
||||
"page": 2
|
||||
}
|
||||
],
|
||||
"pages": [{"page": 1, "width": 595, "height": 842}]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Information Extraction (정보 추출)
|
||||
|
||||
### 요청
|
||||
```http
|
||||
POST https://api.upstage.ai/v1/document-ai/information-extraction
|
||||
Authorization: Bearer {API_KEY}
|
||||
Content-Type: multipart/form-data
|
||||
|
||||
document: (파일 바이너리)
|
||||
schema: {
|
||||
"contract_no": "계약번호",
|
||||
"amount": "계약금액",
|
||||
"supplier": "공급사명"
|
||||
}
|
||||
```
|
||||
|
||||
### 응답 구조
|
||||
```json
|
||||
{
|
||||
"api": "2.0",
|
||||
"usage": {"pages": 1, "tokens": 800},
|
||||
"result": {
|
||||
"contract_no": {
|
||||
"value": "2026-IT-0042",
|
||||
"confidence": 0.97,
|
||||
"coordinates": {"x": 0.2, "y": 0.15, "page": 1}
|
||||
},
|
||||
"amount": {
|
||||
"value": "₩50,000,000",
|
||||
"confidence": 0.95,
|
||||
"normalized": 50000000
|
||||
},
|
||||
"supplier": {
|
||||
"value": "(주)지오정보기술",
|
||||
"confidence": 0.98
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Document QA (문서 질의응답)
|
||||
|
||||
### 요청
|
||||
```http
|
||||
POST https://api.upstage.ai/v1/document-ai/document-qa
|
||||
Authorization: Bearer {API_KEY}
|
||||
Content-Type: multipart/form-data
|
||||
|
||||
document: (파일 바이너리)
|
||||
question: "이 계약서의 계약 기간은 언제까지인가요?"
|
||||
```
|
||||
|
||||
### 응답
|
||||
```json
|
||||
{
|
||||
"answer": "본 계약의 계약기간은 2026년 6월 1일부터 2027년 5월 31일까지입니다.",
|
||||
"confidence": 0.93,
|
||||
"source": {"page": 2, "text": "제3조 (계약기간) 본 계약의 기간은..."}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 활용 시나리오별 스키마
|
||||
|
||||
### 나라장터 계약서
|
||||
```json
|
||||
{
|
||||
"contract_no": "계약번호",
|
||||
"contract_name": "계약품명",
|
||||
"supplier": "공급사명",
|
||||
"supplier_biz_no":"사업자등록번호",
|
||||
"amount": "계약금액(원)",
|
||||
"vat": "부가세",
|
||||
"start_date": "계약시작일(YYYY-MM-DD)",
|
||||
"end_date": "계약종료일(YYYY-MM-DD)",
|
||||
"institution": "발주기관명",
|
||||
"manager": "담당자",
|
||||
"payment_terms": "납부조건"
|
||||
}
|
||||
```
|
||||
|
||||
### 서버 납품 명세서
|
||||
```json
|
||||
{
|
||||
"hostname": "호스트명/서버명",
|
||||
"manufacturer": "제조사",
|
||||
"model_no": "모델번호",
|
||||
"serial_no": "시리얼번호",
|
||||
"cpu_model": "CPU 모델",
|
||||
"cpu_cores": "CPU 코어 수",
|
||||
"memory_gb": "메모리 용량(GB)",
|
||||
"disk_config": "스토리지 구성",
|
||||
"os": "운영체제",
|
||||
"ip_addr": "IP주소",
|
||||
"rack_location": "랙 위치",
|
||||
"warranty_until": "보증기간 만료일",
|
||||
"delivery_date": "납품일"
|
||||
}
|
||||
```
|
||||
|
||||
### 기업 브랜드 계약서 (현대백화점 등)
|
||||
```json
|
||||
{
|
||||
"contract_title": "계약서 제목",
|
||||
"party_a": "갑(브랜드사/발주사)",
|
||||
"party_a_biz_no": "갑 사업자번호",
|
||||
"party_b": "을(공급사/입점사)",
|
||||
"party_b_biz_no": "을 사업자번호",
|
||||
"contract_amount": "계약금액",
|
||||
"currency": "통화(KRW/USD)",
|
||||
"effective_date": "계약일",
|
||||
"expiry_date": "만료일",
|
||||
"auto_renewal": "자동갱신여부",
|
||||
"payment_terms": "지급조건",
|
||||
"contract_items": "계약품목/서비스",
|
||||
"royalty_rate": "수수료율",
|
||||
"territory": "적용지역/매장",
|
||||
"exclusive": "독점여부",
|
||||
"termination": "해지조건",
|
||||
"penalty_clause": "위약금",
|
||||
"special_terms": "특약사항",
|
||||
"contact_a": "갑 담당자",
|
||||
"contact_b": "을 담당자"
|
||||
}
|
||||
```
|
||||
|
||||
### 세금계산서/청구서
|
||||
```json
|
||||
{
|
||||
"invoice_no": "세금계산서번호/청구번호",
|
||||
"issue_date": "발행일",
|
||||
"supplier_name": "공급자 상호",
|
||||
"supplier_biz_no": "공급자 사업자번호",
|
||||
"buyer_name": "공급받는자 상호",
|
||||
"buyer_biz_no": "공급받는자 사업자번호",
|
||||
"supply_amount": "공급가액",
|
||||
"vat_amount": "세액",
|
||||
"total_amount": "합계금액",
|
||||
"items": "품목/내역",
|
||||
"payment_due": "결제기한",
|
||||
"bank_account": "입금계좌(마스킹)"
|
||||
}
|
||||
```
|
||||
|
||||
### 장애 보고서
|
||||
```json
|
||||
{
|
||||
"incident_date": "발생일시",
|
||||
"incident_type": "장애유형",
|
||||
"affected_system": "영향 시스템",
|
||||
"error_message": "오류 메시지",
|
||||
"root_cause": "근본원인",
|
||||
"impact_scope": "영향 범위",
|
||||
"resolution": "조치사항",
|
||||
"downtime_minutes": "다운타임(분)",
|
||||
"reporter": "보고자"
|
||||
}
|
||||
```
|
||||
|
||||
### CSAP 점검 보고서
|
||||
```json
|
||||
{
|
||||
"institution": "기관명",
|
||||
"check_date": "점검일",
|
||||
"total_items": "총 점검항목 수",
|
||||
"passed_items": "적합 항목 수",
|
||||
"failed_items": "부적합 항목 수",
|
||||
"compliance_rate": "준수율(%)",
|
||||
"major_findings": "주요 발견사항",
|
||||
"recommendations": "권고사항"
|
||||
}
|
||||
```
|
||||
|
||||
### 회의록
|
||||
```json
|
||||
{
|
||||
"meeting_date": "회의일시",
|
||||
"meeting_place": "장소",
|
||||
"participants": "참석자",
|
||||
"agenda": "안건",
|
||||
"decisions": "결정사항",
|
||||
"action_items": "액션아이템(담당자/기한)",
|
||||
"next_meeting": "차기 회의일"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 비용 가이드 (참고)
|
||||
|
||||
| API | 페이지당 비용 |
|
||||
|-----|------------|
|
||||
| Document Parse | ~$0.01/페이지 |
|
||||
| Information Extraction | ~$0.02/페이지 |
|
||||
| Document QA | ~$0.02/요청 |
|
||||
|
||||
> 최신 요금은 https://www.upstage.ai/pricing 참조
|
||||
|
||||
---
|
||||
|
||||
## Python 구현 예시
|
||||
|
||||
```python
|
||||
import httpx, base64, json
|
||||
from pathlib import Path
|
||||
|
||||
async def upstage_parse(api_key: str, file_path: str) -> dict:
|
||||
with open(file_path, "rb") as f:
|
||||
file_bytes = f.read()
|
||||
|
||||
filename = Path(file_path).name
|
||||
ext = Path(file_path).suffix.lower()
|
||||
mime = {"pdf": "application/pdf", "png": "image/png", "jpg": "image/jpeg"}.get(ext[1:], "application/octet-stream")
|
||||
|
||||
async with httpx.AsyncClient(timeout=120) as client:
|
||||
response = await client.post(
|
||||
"https://api.upstage.ai/v1/document-ai/document-digitization",
|
||||
headers={"Authorization": f"Bearer {api_key}"},
|
||||
files={"document": (filename, file_bytes, mime)},
|
||||
data={
|
||||
"model": "document-parse-ocr",
|
||||
"ocr": "auto",
|
||||
"output_formats": '["text", "html", "markdown"]'
|
||||
}
|
||||
)
|
||||
return response.json()
|
||||
|
||||
|
||||
async def upstage_extract(api_key: str, file_path: str, schema: dict) -> dict:
|
||||
with open(file_path, "rb") as f:
|
||||
file_bytes = f.read()
|
||||
|
||||
filename = Path(file_path).name
|
||||
|
||||
async with httpx.AsyncClient(timeout=120) as client:
|
||||
response = await client.post(
|
||||
"https://api.upstage.ai/v1/document-ai/information-extraction",
|
||||
headers={"Authorization": f"Bearer {api_key}"},
|
||||
files={"document": (filename, file_bytes, "application/pdf")},
|
||||
data={"schema": json.dumps(schema, ensure_ascii=False)}
|
||||
)
|
||||
return response.json()
|
||||
```
|
||||
28
CLAUDE.md
28
CLAUDE.md
@ -279,6 +279,34 @@ GUARDiA ITSM (허브, :9001/:8443)
|
||||
|
||||
---
|
||||
|
||||
## 하네스: Upstage OCR 연동
|
||||
|
||||
**목표:** Upstage Document AI API(Document Parse·Information Extraction·Document QA)를 연동하여 계약서·납품서·청구서·장애보고서·감사보고서·회의록을 자동 처리하고 ITSM 기능에 연동. 현대백화점 등 기업 브랜드 계약서 포함.
|
||||
|
||||
**트리거:** OCR, Upstage, 문서 파싱, 계약서 자동 처리, 현대백화점 계약, 브랜드 계약서, 납품서 CMDB 등록, 청구서 자동 처리, 장애보고서 이미지→SR 요청 시 `upstage-ocr-orchestrator` 스킬을 사용하라.
|
||||
|
||||
**에이전트:** upstage-ocr-dev (Document AI API 엔진) · ocr-workflow-dev (6개 워크플로우 + 브랜드 계약 연동)
|
||||
|
||||
**신규 라우터:** upstage_ocr.py · doc_workflow.py · doc_template.py
|
||||
|
||||
**지원 시나리오:**
|
||||
1. 나라장터 계약서 → ProcurementRecord 자동 등록
|
||||
2. 서버납품서 → CMDB 자동 등록
|
||||
3. 브랜드 계약서 (현대백화점 등) → 계약 이력 관리
|
||||
4. 세금계산서 → 과금 연동
|
||||
5. 장애보고서 이미지 → SR 자동 생성
|
||||
6. 회의록 → 액션아이템 SR 생성
|
||||
7. CSAP 보고서 → 준수율 자동 업데이트
|
||||
|
||||
**API 가이드:** `.claude/skills/upstage-ocr-orchestrator/references/upstage-api-guide.md`
|
||||
|
||||
**변경 이력:**
|
||||
| 날짜 | 변경 내용 | 대상 | 사유 |
|
||||
|------|----------|------|------|
|
||||
| 2026-06-02 | 초기 하네스 구성 | 전체 | Upstage OCR 연동 + 기업 계약서 처리 |
|
||||
|
||||
---
|
||||
|
||||
## 하네스: GUARDiA 고급 확장
|
||||
|
||||
**목표:** GUARDiA ITSM 현재 104개 라우터(667 엔드포인트)에서 20개 신규 라우터 추가. CMDB 자동 발견·Text-to-SQL·구성 드리프트·멀티클라우드·공공기관 특화 5개 영역 확장. 목표: 124개 라우터, ~764개 엔드포인트.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user