app:
  description: 製造業の検品帳票・納品書テキストから品目・数量・日付・判定結果を抽出し、構造化データとして出力するワークフロー
  icon: 🤖
  icon_background: '#FFEAD5'
  mode: workflow
  name: 製造業 帳票OCR 検品・納品書項目抽出ワークフロー
  use_icon_as_answer_icon: false
kind: app
version: 0.6.0
workflow:
  conversation_variables: []
  environment_variables: []
  features:
    file_upload:
      image:
        enabled: false
    opening_statement: ''
    suggested_questions: []
    suggested_questions_after_answer:
      enabled: false
    text_to_speech:
      enabled: false
  graph:
    edges:
    - data:
        sourceType: start
        targetType: parameter-extractor
      id: start-to-extractor1
      source: start
      target: extractor1
    - data:
        sourceType: parameter-extractor
        targetType: code
      id: extractor1-to-code1
      source: extractor1
      target: code1
    - data:
        sourceType: code
        targetType: llm
      id: code1-to-llm1
      source: code1
      target: llm1
    - data:
        sourceType: llm
        targetType: end
      id: llm1-to-end
      source: llm1
      target: end
    nodes:
    - data:
        title: 開始
        type: start
        variables:
        - label: query
          max_length: 2048
          options: []
          required: true
          type: paragraph
          variable: query
      height: 90
      id: start
      position:
        x: 80
        y: 282
      type: custom
      width: 244
    - data:
        title: 帳票項目抽出（パラメータ抽出）
        type: parameter-extractor
        model:
          provider: openai
          name: gpt-5-mini
          mode: chat
        query:
        - start
        - query
        parameters:
        - name: items
          type: string
          description: '明細行の一覧。JSON文字列で、各行は item_name, spec, unit, quantity, accepted_quantity,
            rejected_quantity, judgment を持つオブジェクト配列として返す。例: [{"item_name":"部品A","spec":"M8×20","unit":"個","quantity":100,"accepted_quantity":98,"rejected_quantity":2,"judgment":"OK"}]'
          required: true
        - name: document_type
          type: string
          description: 帳票種別（検品記録, 検査成績書, 納品書, 出荷指示書 など）
          required: false
        - name: document_date
          type: string
          description: 帳票の日付。検品日または納品日。優先度は 検品日 > 納品日 > 発行日。形式は可能なら YYYY-MM-DD。
          required: false
        - name: delivery_note_number
          type: string
          description: 納品書番号・検査記録番号などの帳票番号。なければ空文字。
          required: false
        - name: supplier_name
          type: string
          description: 仕入先名・納入元名。
          required: false
        - name: customer_name
          type: string
          description: 納入先名・得意先名。
          required: false
        - name: overall_judgment
          type: string
          description: 帳票全体としての判定（OK, NG, 要確認 など）。本文に無い場合は、行の判定から総合的に判断してよい。
          required: false
        reasoning_mode: prompt
        instruction: "あなたは製造業の品質管理担当者です。\nOCRで読み取った「検品帳票」「検査記録」「納品書」などのテキストから、品目・数量・日付・判定結果を抽出し、構造化データに変換してください。\n\
          \n【必ず守るルール】\n- items は JSON 配列文字列として返してください。\n  - 例: [{\"item_name\":\"\
          部品A\",\"spec\":\"M8×20\",\"unit\":\"個\",\"quantity\":100,\"accepted_quantity\"\
          :98,\"rejected_quantity\":2,\"judgment\":\"OK\"}]\n  - JSON文字列の外側に余計な文字（コメント、日本語、改行を含む説明文など）を付けないこと。\n\
          - 数値項目（quantity, accepted_quantity, rejected_quantity）は、カンマや単位を取り除いた数値として返してください。\n\
          - accepted_quantity, rejected_quantity が本文に無い場合は、判定結果から推定してもよいが、その場合は logical\
          \ に矛盾しない値としてください。\n- judgment は行ごとの判定で、可能なら「OK」「NG」「要確認」など簡潔な日本語に正規化してください。\n\
          - document_date は、本文に複数日付がある場合、以下の優先度で選択してください。\n  1. 検品日・検査日\n  2. 納品日\n\
          \  3. 発行日\n- 日付の形式は可能なら YYYY-MM-DD に正規化してください。（難しい場合は原文のままでもよい）\n- overall_judgment\
          \ は、明細行の判定や本文中の記載から「OK」「NG」「要確認」などで要約してください。\n\n【抽出したい主な情報】\n- 明細行（items）\n\
          \  - item_name: 品目名（部品名、製品名など）\n  - spec: 仕様・型番・サイズ（例: \"M8×20\", \"品番 ABC-123\"\
          \ など）。無ければ空文字。\n  - unit: 単位（個、セット、箱、本 など）。無ければ空文字。\n  - quantity: 納入数量または検査対象数量\n\
          \  - accepted_quantity: 合格数量（良品数）\n  - rejected_quantity: 不合格数量（不良数）\n \
          \ - judgment: 行ごとの判定（OK, NG, 要確認 など）\n- 帳票メタ情報\n  - document_type: 検品記録,\
          \ 検査成績書, 納品書, 出荷指示書 など\n  - document_date: 検品日・納品日・発行日など代表的な日付\n  - delivery_note_number:\
          \ 納品書番号や検査記録番号など\n  - supplier_name: 仕入先名・納入元名\n  - customer_name: 納入先名・得意先名\n\
          \  - overall_judgment: 帳票全体の総合判定\n\n入力テキスト:\n{{#start.query#}}\n"
      height: 90
      id: extractor1
      position:
        x: 380
        y: 282
      type: custom
      width: 244
    - data:
        title: JSON整形・妥当性チェック
        type: code
        code_language: python3
        code: "def _safe_str(v):\n    \"\"\"Convert any value to string safely.\"\"\
          \"\n    if v is None:\n        return \"\"\n    if isinstance(v, list):\n\
          \        return \"\\n\".join(str(i) for i in v)\n    return str(v)\n\ndef\
          \ _safe_list(v):\n    \"\"\"Ensure value is a list.\"\"\"\n    if v is None:\n\
          \        return []\n    if isinstance(v, str):\n        return [v]\n   \
          \ if isinstance(v, list):\n        return v\n    return [v]\n\ndef main(**kwargs)\
          \ -> dict:\n    inputs = kwargs\n    import json\n\n    raw_items = inputs.get(\"\
          items\", \"\")\n    overall_judgment = inputs.get(\"overall_judgment\",\
          \ \"\")\n    document_type = inputs.get(\"document_type\", \"\")\n    document_date\
          \ = inputs.get(\"document_date\", \"\")\n    delivery_note_number = inputs.get(\"\
          delivery_note_number\", \"\")\n    supplier_name = inputs.get(\"supplier_name\"\
          , \"\")\n    customer_name = inputs.get(\"customer_name\", \"\")\n\n   \
          \ # デフォルト値\n    normalized_items = []\n    issues = []\n\n    # JSONパース\n\
          \    try:\n        parsed = json.loads(raw_items)\n        if isinstance(parsed,\
          \ list):\n            for idx, row in enumerate(parsed):\n             \
          \   if not isinstance(row, dict):\n                    issues.append(\"\
          items[\" + str(idx) + \"] がオブジェクトではありません。\")\n                    continue\n\
          \                item = dict()\n                item[\"item_name\"] = str(row.get(\"\
          item_name\", \"\")).strip()\n                item[\"spec\"] = str(row.get(\"\
          spec\", \"\")).strip()\n                item[\"unit\"] = str(row.get(\"\
          unit\", \"\")).strip()\n\n                # 数値項目の正規化\n                def\
          \ to_number(value):\n                    if value is None:\n           \
          \             return 0\n                    s = str(value)\n           \
          \         s = s.replace(\",\", \"\").replace(\"個\", \"\").replace(\"本\"\
          , \"\").replace(\"セット\", \"\").replace(\"箱\", \"\")\n                  \
          \  s = s.strip()\n                    if s == \"\":\n                  \
          \      return 0\n                    try:\n                        return\
          \ float(s)\n                    except Exception:\n                    \
          \    return 0\n\n                item[\"quantity\"] = to_number(row.get(\"\
          quantity\"))\n                item[\"accepted_quantity\"] = to_number(row.get(\"\
          accepted_quantity\"))\n                item[\"rejected_quantity\"] = to_number(row.get(\"\
          rejected_quantity\"))\n\n                # 判定の正規化\n                j = str(row.get(\"\
          judgment\", \"\")).strip()\n                if j == \"\":\n            \
          \        # 数量から自動推定\n                    if item[\"rejected_quantity\"]\
          \ > 0:\n                        j = \"NG\"\n                    else:\n\
          \                        j = \"OK\"\n                j_norm = j.upper()\n\
          \                if \"OK\" in j_norm or \"良\" in j:\n                  \
          \  item[\"judgment\"] = \"OK\"\n                elif \"NG\" in j_norm or\
          \ \"不\" in j or \"否\" in j:\n                    item[\"judgment\"] = \"\
          NG\"\n                elif \"要\" in j and \"確認\" in j:\n               \
          \     item[\"judgment\"] = \"要確認\"\n                else:\n            \
          \        item[\"judgment\"] = j\n\n                # 数量整合チェック\n        \
          \        if item[\"accepted_quantity\"] + item[\"rejected_quantity\"] ==\
          \ 0 and item[\"quantity\"] > 0:\n                    item[\"accepted_quantity\"\
          ] = item[\"quantity\"]\n                elif item[\"quantity\"] == 0 and\
          \ (item[\"accepted_quantity\"] + item[\"rejected_quantity\"] > 0):\n   \
          \                 item[\"quantity\"] = item[\"accepted_quantity\"] + item[\"\
          rejected_quantity\"]\n\n                normalized_items.append(item)\n\
          \        else:\n            issues.append(\"items が配列ではありません。\")\n    except\
          \ Exception as e:\n        issues.append(\"items の JSON パースに失敗しました: \" +\
          \ str(e))\n\n    # overall_judgment の補正\n    if overall_judgment:\n    \
          \    oj = overall_judgment\n    else:\n        has_ng = any(i.get(\"judgment\"\
          ) == \"NG\" for i in normalized_items)\n        if has_ng:\n           \
          \ oj = \"NG\"\n        else:\n            oj = \"OK\"\n\n    result_object\
          \ = dict(\n        document_type=document_type,\n        document_date=document_date,\n\
          \        delivery_note_number=delivery_note_number,\n        supplier_name=supplier_name,\n\
          \        customer_name=customer_name,\n        overall_judgment=oj,\n  \
          \      items=normalized_items,\n        issues=issues,\n    )\n\n    result_json\
          \ = json.dumps(result_object, ensure_ascii=False, indent=2)\n    return\
          \ dict(\n        output=result_json\n    )\n"
        variables:
        - value_selector:
          - extractor1
          - items
          variable: items
        - value_selector:
          - extractor1
          - overall_judgment
          variable: overall_judgment
        - value_selector:
          - extractor1
          - document_type
          variable: document_type
        - value_selector:
          - extractor1
          - document_date
          variable: document_date
        - value_selector:
          - extractor1
          - delivery_note_number
          variable: delivery_note_number
        - value_selector:
          - extractor1
          - supplier_name
          variable: supplier_name
        - value_selector:
          - extractor1
          - customer_name
          variable: customer_name
        outputs:
          output:
            type: string
            children: null
      height: 90
      id: code1
      position:
        x: 680
        y: 282
      type: custom
      width: 244
    - data:
        title: 要約・説明生成
        type: llm
        model:
          provider: openai
          name: gpt-5-mini
          mode: chat
          completion_params:
            temperature: 0.2
        prompt_template:
        - role: system
          text: 'あなたは製造業の品質管理・受入検査の担当者です。

            既に JSON 形式で整理された検品・納品情報を、利用者が理解しやすい形で日本語要約してください。

            ただし、JSON 自体はそのままの形で保持し、説明文では重要なポイントだけを簡潔に述べてください。



            ---


            【最終出力フォーマット (この指示が最優先)】

            最終出力は **読みやすい日本語の Markdown** で返してください。JSON

            配列・オブジェクトをそのまま出力してはいけません。

            - 見出し (## / ###) でセクションを区切る

            - リスト (- / 1.) や表 (| ... |) で構造化

            - 各項目の意図や根拠が分かるよう短い説明文を添える

            - 営業デモで顧客に直接見せられる体裁にする


            (構造化されたデータ項目を内部で扱うのは構いませんが、出力時に Markdown

            に変換してください。)'
        - role: user
          text: '以下は検品帳票／納品書から抽出・整形した結果です。


            JSON結果:

            {{#code1.output#}}


            求める出力:

            1. 概要（何の帳票か、日付、仕入先／納入先、帳票番号、全体判定）

            2. 明細のサマリ（品目数、NG・要確認があるか）

            3. 注意すべき点（issues に何かあれば、それを分かりやすく説明）


            ただし最終的なマシン連携用データは、上記 JSON のまま利用するため、JSON を改変しないでください。

            '
        vision:
          enabled: false
        memory: null
        context:
          enabled: false
      height: 90
      id: llm1
      position:
        x: 980
        y: 282
      type: custom
      width: 244
    - data:
        title: 終了
        type: end
        outputs:
        - value_selector:
          - llm1
          - text
          variable: result
      height: 90
      id: end
      position:
        x: 1280
        y: 282
      type: custom
      width: 244
  hash: null
