zioinfo-mail/.claude/agents/ocr-workflow-dev.md
DESKTOP-TKLFCPR\ython d76caea5dd 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>
2026-06-02 18:24:26 +09:00

7.6 KiB

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: 나라장터 계약서 자동 등록

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 자동 등록

# 납품서에서 서버 사양 추출 → tb_server_info 자동 등록
schema = {
    "hostname": "호스트명",
    "model": "서버모델",
    "cpu": "CPU 사양",
    "memory_gb": "메모리(GB)",
    "disk_tb": "스토리지(TB)",
    "ip_addr": "IP주소",
    "serial_no": "시리얼번호",
    "warranty_until": "보증기간",
}

시나리오 3: 브랜드 계약서 (현대백화점 등)

# 일반 기업 계약서 템플릿
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 자동 생성

# 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,
)

내장 추출 템플릿

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 모델 추가

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개 시나리오 + 브랜드 계약 워크플로우 완료 보고