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: 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
          completion_params:
            temperature: 0.2
        query:
        - start
        - query
        parameters:
        - name: baseline_inventory
          type: string
          description: '基準在庫データ（CSV形式: SKU,数量 を改行区切りで記載）'
          required: true
        - name: actual_inventory
          type: string
          description: '棚卸実績データ（CSV形式: SKU,数量 を改行区切りで記載）'
          required: true
        - name: report_period
          type: string
          description: '対象期間（例: 2026-04-01～2026-04-30）'
          required: true
        - name: variance_threshold
          type: number
          description: 差分を注視する閾値（数量、任意）
          required: false
        - name: top_k
          type: number
          description: 上位何件を重点的に分析するか（整数）
          required: true
        reasoning_mode: prompt
        instruction: 'ユーザーの入力から上記パラメータを抽出してください。inventoryフィールドはCSV形式（SKU,数量）で入力される想定です。top_kは整数。variance_thresholdは存在しない場合は空にしてください。

          '
      height: 90
      id: extractor1
      position:
        x: 380
        y: 282
      type: custom
      width: 244
    - data:
        title: 差分計算
        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    # 入力を文字列として取得\n    baseline = inputs.get(\"\
          baseline_inventory\", \"\")\n    actual = inputs.get(\"actual_inventory\"\
          , \"\")\n    top_k = inputs.get(\"top_k\")\n    threshold = inputs.get(\"\
          variance_threshold\")\n    # CSV形式をパースする簡易関数（SKU,数量 の改行区切り）\n    def parse_csv(s):\n\
          \        res = dict()\n        lines = str(s).strip().splitlines()\n   \
          \     for line in lines:\n            if not line:\n                continue\n\
          \            parts = [p.strip() for p in line.split(\",\")]\n          \
          \  if len(parts) < 2:\n                continue\n            sku = parts[0]\n\
          \            qty = 0.0\n            try:\n                qty = float(parts[1])\n\
          \            except:\n                try:\n                    qty = float(parts[1].replace(\"\
          \ \", \"\"))\n                except:\n                    qty = 0.0\n \
          \           res[sku] = qty\n        return res\n    b = parse_csv(baseline)\n\
          \    a = parse_csv(actual)\n    # 差分リストを作成\n    diffs = []\n    keys = set(list(b.keys())\
          \ + list(a.keys()))\n    for sku in keys:\n        bv = b.get(sku, 0.0)\n\
          \        av = a.get(sku, 0.0)\n        diff = av - bv\n        abs_diff\
          \ = abs(diff)\n        diffs.append(dict(sku=sku, baseline=bv, actual=av,\
          \ diff=diff, abs_diff=abs_diff))\n    # 絶対差でソート（降順）\n    diffs.sort(key=lambda\
          \ x: x[\"abs_diff\"], reverse=True)\n    # top_k の解釈\n    try:\n       \
          \ if top_k is None or str(top_k).strip() == \"\":\n            k = len(diffs)\n\
          \        else:\n            k = int(float(top_k))\n    except:\n       \
          \ k = len(diffs)\n    top_list = diffs[:k]\n    # 閾値未指定の場合は None を示す\n \
          \   thr_val = None\n    try:\n        if threshold is not None and str(threshold).strip()\
          \ != \"\":\n            thr_val = float(threshold)\n    except:\n      \
          \  thr_val = None\n    import json\n    result = dict(top=top_list, all=diffs,\
          \ threshold=thr_val)\n    # JSON文字列で返す（日本語対応）\n    return dict(diff=json.dumps(result,\
          \ ensure_ascii=False))\n"
        variables:
        - value_selector:
          - extractor1
          - baseline_inventory
          variable: baseline_inventory
        - value_selector:
          - extractor1
          - actual_inventory
          variable: actual_inventory
        - value_selector:
          - extractor1
          - report_period
          variable: report_period
        - value_selector:
          - extractor1
          - variance_threshold
          variable: variance_threshold
        - value_selector:
          - extractor1
          - top_k
          variable: top_k
        outputs:
          diff:
            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.6
        prompt_template:
        - role: system
          text: 'あなたは小売・流通業の在庫管理エキスパートです。以下の差分データをもとに、差分原因を分類（例: ロス、発注ミス、入出庫記録ミス、棚卸カウントミス、返品処理漏れなど）、各カテゴリごとの根拠、優先順位付けされた対応策、現場ですぐ実行できる短期対策と中期対策、そして発注案（数量提案、優先SKU、リードタイム考慮）を作成してください。出力は日本語で、以下の最終フォーマットに従ってください。


            最終フォーマット（必須）:

            1) 要約（3行程度）

            2) 主要な差分項目（上位K）と差分数値（表形式風）

            3) 各差分の原因分類と根拠（箇条書き）

            4) 優先対応（短期・中期）と担当候補、期待効果

            5) 発注案（SKUごとの推奨数量、理由、リードタイム考慮）

            6) 次のアクション（チェックリスト）


            出力は読みやすいビジネス文書（Markdownスタイル可）で、表や箇条書きを用いて整理してください。

            '
        - role: user
          text: 'ユーザー入力:

            {{#start.query#}}


            差分計算結果（JSON文字列）:

            {{#code1.diff#}}


            注意:

            - 差分JSONには top（上位項目） と all（全項目） が含まれます。top を優先分析対象として扱ってください。

            - report_period や variance_threshold が指定されている場合は考慮して分析してください。

            - 発注案は現場で実行可能な具体的数量を提示してください（存在する在庫と差分を踏まえた推奨）。

            '
        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
