에이전트: - 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>
201 lines
7.6 KiB
Markdown
201 lines
7.6 KiB
Markdown
# 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개 시나리오 + 브랜드 계약 워크플로우 완료 보고
|