app:
  description: '総務向け: 複数部門から提出されたベンダ・業者リストを統合し、重複検出、マスタ整合性チェック、支払条件のばらつき検出、与信リスク候補抽出を行うワークフロー'
  icon: 🏢
  icon_background: '#FFEAD5'
  mode: workflow
  name: ベンダ一覧 統合・重複チェック
  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: code
      id: start-to-code1
      source: start
      target: code1
    - data:
        sourceType: code
        targetType: code
      id: code1-to-code2
      source: code1
      target: code2
    - data:
        sourceType: code
        targetType: llm
      id: code2-to-llm1
      source: code2
      target: llm1
    - data:
        sourceType: llm
        targetType: template-transform
      id: llm1-to-template1
      source: llm1
      target: template1
    - data:
        sourceType: template-transform
        targetType: end
      id: template1-to-end
      source: template1
      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: code
        code_language: python3
        code: "import json\ndef normalize_header(h):\n    return h.strip()\ndef parse_rows(text):\n\
          \    lines = [l.strip() for l in text.splitlines() if l.strip()]\n    if\
          \ not lines:\n        return []\n    # detect delimiter by header\n    header_line\
          \ = lines[0]\n    delim = \",\"\n    if \"\\t\" in header_line and header_line.count(\"\
          \\t\") > header_line.count(\",\"):\n        delim = \"\\t\"\n    headers\
          \ = [normalize_header(p) for p in header_line.split(delim)]\n    rows =\
          \ []\n    for line in lines[1:]:\n        parts = line.split(delim)\n  \
          \      record = dict()\n        for i, h in enumerate(headers):\n      \
          \      val = parts[i].strip() if i < len(parts) else \"\"\n            record[h]\
          \ = val\n        rows.append(record)\n    return rows\ndef _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    text = inputs.get(\"text\", \"\")\n    merged_rows = parse_rows(text)\n\
          \    # Output as JSON string\n    out = json.dumps(dict(rows=merged_rows),\
          \ ensure_ascii=False)\n    return dict(output=out)\n"
        variables:
        - value_selector:
          - start
          - query
          variable: text
        outputs:
          output:
            type: string
            children: null
      height: 90
      id: code1
      position:
        x: 380
        y: 282
      type: custom
      width: 244
    - data:
        title: 重複検出・整合性チェック
        type: code
        code_language: python3
        code: "import json\ndef normalize_name(n):\n    return n.strip().lower()\n\
          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    raw = inputs.get(\"text\", \"\")\n\
          \    try:\n        parsed = json.loads(raw)\n        rows = parsed.get(\"\
          rows\", [])\n    except Exception:\n        rows = []\n    unique = dict()\n\
          \    duplicates = []\n    mismatched_payment = []\n    credit_candidates\
          \ = []\n    for r in rows:\n        # try common name fields\n        name_val\
          \ = r.get(\"name\", \"\") or r.get(\"vendor\", \"\") or r.get(\"取引先\") or\
          \ r.get(\"取引先名\") or \"\"\n        norm = normalize_name(name_val)\n   \
          \     if not norm:\n            # skip empty names but mark as potential\
          \ issue\n            credit_candidates.append(name_val or \"（無名レコード）\")\n\
          \            continue\n        if norm in unique:\n            duplicates.append(name_val)\n\
          \            existing = unique.get(norm)\n            p_old = existing.get(\"\
          payment_terms\", \"\") or existing.get(\"支払条件\", \"\")\n            p_new\
          \ = r.get(\"payment_terms\", \"\") or r.get(\"支払条件\", \"\")\n          \
          \  if p_old != p_new and p_new:\n                mismatched_payment.append(name_val\
          \ + \" (既存:\" + str(p_old) + \" 新:\" + str(p_new) + \")\")\n        else:\n\
          \            unique[norm] = r\n        status = r.get(\"credit_status\"\
          , \"\") or r.get(\"与信\", \"\") or r.get(\"status\", \"\")\n        if not\
          \ status or str(status).strip().lower() in [\"unknown\", \"n/a\", \"na\"\
          , \"unverified\", \"未確認\"]:\n            credit_candidates.append(name_val)\n\
          \    summary = \"total_records: \" + str(len(rows)) + \"; unique_vendors:\
          \ \" + str(len(unique)) + \"; duplicates_found: \" + str(len(duplicates))\n\
          \    details = \"duplicates_examples: \" + \", \".join(duplicates[:10])\
          \ + \"; mismatched_payment_terms_examples: \" + \", \".join(mismatched_payment[:10])\
          \ + \"; potential_credit_risk_candidates: \" + \", \".join(list(dict.fromkeys(credit_candidates))[:20])\n\
          \    result = summary + \"\\n\" + details\n    return dict(output=result)\n"
        variables:
        - value_selector:
          - code1
          - output
          variable: text
        outputs:
          output:
            type: string
            children: null
      height: 90
      id: code2
      position:
        x: 680
        y: 282
      type: custom
      width: 244
    - data:
        title: AI解析・方針提案
        type: llm
        model:
          provider: openai
          name: gpt-5-mini
          mode: chat
          completion_params:
            temperature: 0.2
        prompt_template:
        - role: system
          text: 'あなたは総務部のデータクレンジングとベンダーマスター整備の専門コンサルタントです。出力は「要約」「検出結果の解説」「推奨する修正手順」「優先対応候補（優先度）」「データ品質チェックリスト」の5つのセクションを含めてください。最後にCSV整備のための推奨正規化ルール例を短く示してください。


            ---


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

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

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

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

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

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

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


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

            に変換してください。)'
        - role: user
          text: '入力データの要旨と検出サマリを以下に示します。これを基に上記5セクションで構造化された日本語のレポートを作成してください。


            元データ（生データ）:

            {{#start.query#}}


            前処理・重複・整合性のサマリ:

            {{#code2.output#}}


            出力は読みやすい日本語で、箇条書きと短い理由付けを含めてください。'
        vision:
          enabled: false
        memory: null
        context:
          enabled: false
      height: 90
      id: llm1
      position:
        x: 980
        y: 282
      type: custom
      width: 244
    - data:
        title: レポート整形
        type: template-transform
        template: '分析レポート

          入力サマリー:

          {{ data }}


          AIによる解析・推奨:

          {{ analysis }}


          次のステップ(例):

          - ベンダーマスター作成: 正規化ルールを適用して重複統合を実施

          - 支払条件の標準化: 条件差異がある業者を優先リスト化

          - 与信候補: 速やかに与信確認またはモニタリング対象に追加

          '
        variables:
        - value_selector:
          - code2
          - output
          variable: data
        - value_selector:
          - llm1
          - text
          variable: analysis
      height: 90
      id: template1
      position:
        x: 1280
        y: 282
      type: custom
      width: 244
    - data:
        title: 終了
        type: end
        outputs:
        - value_selector:
          - template1
          - output
          variable: result
      height: 90
      id: end
      position:
        x: 1580
        y: 282
      type: custom
      width: 244
  hash: null
