LESSON 30分

ストーリー

田中VPoE
Document AIの基礎を学んだ。次は最も需要の高い「請求書処理」と「契約書処理」の具体的な実装を見ていこう
あなた
経理部が月3,000件の請求書を手作業で処理しているやつですね
田中VPoE
そうだ。請求書は定型・準定型・非定型の3パターンがある。それぞれに対して最適な処理戦略を使い分けることで、高い精度と効率を両立できる
あなた
パターンごとに戦略が違うんですね
田中VPoE
加えて契約書処理は、条項の解釈やリスク分析までAIで支援できる。法務部の業務効率化にも直結する重要なテーマだ

請求書処理の戦略

文書パターン別の処理戦略

パターン割合特徴処理戦略
定型60%主要取引先30社の固定フォーマットテンプレートマッチング + ルールベース
準定型30%フォーマットは異なるが項目は共通VLM + 構造化プロンプト
非定型10%海外取引先、特殊フォーマットVLM + 人的確認

定型請求書のテンプレートマッチング

from dataclasses import dataclass

@dataclass
class InvoiceTemplate:
    vendor_name: str
    regions: dict  # フィールド名 → 座標領域

# テンプレートDB
INVOICE_TEMPLATES = {
    "株式会社ABC": InvoiceTemplate(
        vendor_name="株式会社ABC",
        regions={
            "invoice_number": {"x": 400, "y": 80, "w": 200, "h": 30},
            "invoice_date": {"x": 400, "y": 120, "w": 200, "h": 30},
            "total_amount": {"x": 350, "y": 600, "w": 250, "h": 40},
        }
    ),
}

def process_templated_invoice(image_path: str, vendor_name: str) -> dict:
    """テンプレートマッチングで定型請求書を処理"""
    template = INVOICE_TEMPLATES.get(vendor_name)
    if not template:
        return None

    from PIL import Image
    img = Image.open(image_path)

    result = {}
    for field_name, region in template.regions.items():
        # 領域をクロップしてOCR
        cropped = img.crop((
            region["x"], region["y"],
            region["x"] + region["w"],
            region["y"] + region["h"]
        ))
        text = ocr_region(cropped)  # OCR関数(省略)
        result[field_name] = text

    return result

VLMベースの請求書処理

def process_invoice_with_vlm(image_path: str) -> dict:
    """VLMで請求書を処理(準定型・非定型対応)"""
    client = anthropic.Anthropic()

    with open(image_path, "rb") as f:
        image_data = base64.b64encode(f.read()).decode()

    prompt = """この請求書から以下の情報を正確に抽出し、JSON形式で返してください。

注意事項:
- 金額は数値のみ(カンマ、円マーク、$マークは除去)
- 日付はYYYY-MM-DD形式
- 読み取れない/存在しない項目はnull
- 各フィールドの信頼度(0.0〜1.0)も付与してください

{
  "vendor": {
    "name": {"value": "", "confidence": 0.0},
    "address": {"value": "", "confidence": 0.0},
    "registration_number": {"value": "", "confidence": 0.0}
  },
  "invoice_number": {"value": "", "confidence": 0.0},
  "invoice_date": {"value": "", "confidence": 0.0},
  "due_date": {"value": "", "confidence": 0.0},
  "line_items": [
    {
      "description": {"value": "", "confidence": 0.0},
      "quantity": {"value": 0, "confidence": 0.0},
      "unit_price": {"value": 0, "confidence": 0.0},
      "amount": {"value": 0, "confidence": 0.0}
    }
  ],
  "subtotal": {"value": 0, "confidence": 0.0},
  "tax": {"value": 0, "confidence": 0.0},
  "total": {"value": 0, "confidence": 0.0}
}"""

    response = client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=4096,
        messages=[{
            "role": "user",
            "content": [
                {"type": "image", "source": {"type": "base64", "media_type": "image/jpeg", "data": image_data}},
                {"type": "text", "text": prompt}
            ]
        }]
    )

    return json.loads(response.content[0].text)

バリデーションとクロスチェック

数値の整合性検証

def validate_invoice(data: dict) -> dict:
    """請求書データの整合性を検証"""
    errors = []
    warnings = []

    # 1. 明細合計と小計の一致確認
    line_total = sum(
        item["amount"]["value"]
        for item in data.get("line_items", [])
        if item["amount"]["value"] is not None
    )
    subtotal = data.get("subtotal", {}).get("value", 0)

    if subtotal and abs(line_total - subtotal) > 1:
        errors.append(f"明細合計({line_total})と小計({subtotal})が不一致")

    # 2. 小計 + 税 = 合計の確認
    tax = data.get("tax", {}).get("value", 0) or 0
    total = data.get("total", {}).get("value", 0) or 0

    if subtotal and total and abs((subtotal + tax) - total) > 1:
        errors.append(f"小計({subtotal})+税({tax})と合計({total})が不一致")

    # 3. 税率の妥当性チェック(日本の場合)
    if subtotal and tax:
        tax_rate = tax / subtotal
        if not (0.08 <= tax_rate <= 0.10 + 0.01):  # 8%〜10%(誤差1%許容)
            warnings.append(f"税率が異常: {tax_rate:.2%}")

    # 4. 日付の妥当性チェック
    from datetime import datetime, timedelta
    invoice_date = data.get("invoice_date", {}).get("value")
    if invoice_date:
        try:
            dt = datetime.strptime(invoice_date, "%Y-%m-%d")
            if dt > datetime.now():
                warnings.append("請求日が未来日付")
            if dt < datetime.now() - timedelta(days=365):
                warnings.append("請求日が1年以上前")
        except ValueError:
            errors.append(f"日付形式が不正: {invoice_date}")

    # 5. 信頼度チェック
    low_confidence_fields = []
    for field_name in ["invoice_number", "total", "vendor"]:
        field = data.get(field_name, {})
        if isinstance(field, dict) and field.get("confidence", 1.0) < 0.9:
            low_confidence_fields.append(field_name)

    if low_confidence_fields:
        warnings.append(f"信頼度が低いフィールド: {', '.join(low_confidence_fields)}")

    return {
        "valid": len(errors) == 0,
        "errors": errors,
        "warnings": warnings,
        "needs_human_review": len(errors) > 0 or len(low_confidence_fields) > 0
    }

契約書処理

契約書分析のアプローチ

def analyze_contract(document_path: str) -> dict:
    """契約書を分析"""
    client = anthropic.Anthropic()

    with open(document_path, "rb") as f:
        doc_data = base64.b64encode(f.read()).decode()

    prompt = """この契約書を分析し、以下の情報をJSON形式で出力してください。

{
  "basic_info": {
    "contract_type": "契約種別",
    "parties": [{"name": "当事者名", "role": "甲/乙"}],
    "effective_date": "YYYY-MM-DD",
    "expiration_date": "YYYY-MM-DD",
    "auto_renewal": true/false
  },
  "key_clauses": [
    {
      "clause_number": "条項番号",
      "title": "条項タイトル",
      "summary": "条項の要約",
      "risk_level": "HIGH/MEDIUM/LOW",
      "risk_reason": "リスクの理由(該当する場合)"
    }
  ],
  "financial_terms": {
    "contract_value": 0,
    "payment_terms": "支払条件",
    "penalties": "違約金条項"
  },
  "termination": {
    "notice_period": "解約通知期間",
    "termination_conditions": ["解約条件"]
  },
  "risk_summary": {
    "overall_risk": "HIGH/MEDIUM/LOW",
    "key_risks": ["主要リスクのリスト"],
    "recommendations": ["推奨事項"]
  }
}"""

    response = client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=4096,
        messages=[{
            "role": "user",
            "content": [
                {"type": "image", "source": {"type": "base64", "media_type": "image/png", "data": doc_data}},
                {"type": "text", "text": prompt}
            ]
        }]
    )

    return json.loads(response.content[0].text)

リスク分析のチェックポイント

チェック項目リスクレベル判定基準
自動更新条項解約通知期間が3ヶ月以上 → HIGH
損害賠償上限上限なし → HIGH
知的財産権成果物の権利帰属が不明確 → HIGH
準拠法海外法が適用 → MEDIUM
競業避止期間2年以上 → MEDIUM
解約条件理由なし解約不可 → MEDIUM

処理パイプラインの統合

文書入力


[文書分類] → 請求書/契約書/その他

    ├── 請求書 → [取引先識別] → 定型/準定型/非定型
    │   ├── 定型 → テンプレートマッチング
    │   ├── 準定型 → VLM抽出
    │   └── 非定型 → VLM抽出 + 人的確認
    │   │
    │   ▼
    │   [バリデーション] → [会計システム登録]

    └── 契約書 → [VLM分析] → [リスク評価]


        [法務担当者レビュー] → [契約管理DB登録]

まとめ

項目内容
請求書処理定型→テンプレート、準定型→VLM、非定型→VLM+人的確認
バリデーション数値整合性、税率、日付、信頼度のクロスチェック
契約書分析条項抽出 + リスクレベル判定 + 推奨事項生成
統合パイプライン分類 → 抽出 → 検証 → 登録

チェックリスト

  • 請求書の3パターン(定型・準定型・非定型)の処理戦略を理解した
  • VLMベースの請求書データ抽出(信頼度付き)を実装できる
  • バリデーションロジック(数値整合性、税率チェック等)を設計できる
  • 契約書のリスク分析アプローチを把握した

推定所要時間: 30分