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-extract1
      source: start
      target: extract1
    - data:
        sourceType: parameter-extractor
        targetType: code
      id: extract1-to-code1
      source: extract1
      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: product_name
          type: string
          description: 商品名または商品識別子（可能であれば）
          required: true
        - name: avg_daily_sales
          type: number
          description: '平均日次売上（単位: 個/日）'
          required: true
        - name: current_stock
          type: number
          description: 現在庫（個数）
          required: true
        - name: safety_stock
          type: number
          description: 安全在庫（個数、未指定なら0）
          required: false
        - name: lead_time_days
          type: number
          description: リードタイム（日数）
          required: true
        - name: min_order_qty
          type: number
          description: 最少発注ロット（未指定なら1）
          required: false
        - name: shelf_capacity_units
          type: number
          description: 棚の収容可能数または目安（個）
          required: false
        - name: display_rules
          type: string
          description: '陳列ルール（例: フェイス数、カテゴリ優先、季節商品など）'
          required: false
        - name: recent_sales_trend
          type: string
          description: 直近の売上トレンド（増加/減少/横ばい 等）
          required: false
        reasoning_mode: prompt
        instruction: ユーザーの入力（売上メモ・在庫メモ・陳列ルール）から上記パラメータを抽出してください。数値は可能な限り数値型で返し、不明な項目は空欄にしてください。
      height: 90
      id: extract1
      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    import math\n    # ローカル設定（必要に応じて調整）\n\
          \    default_min_order = 1\n    # 安全な取得とキャスト\n    def to_float(val, default=0.0):\n\
          \        try:\n            return float(val)\n        except Exception:\n\
          \            try:\n                return float(str(val).strip())\n    \
          \        except Exception:\n                return default\n    product_name\
          \ = inputs.get(\"product_name\", \"\") or \"\"\n    avg_daily_sales = to_float(inputs.get(\"\
          avg_daily_sales\", 0.0), 0.0)\n    current_stock = to_float(inputs.get(\"\
          current_stock\", 0.0), 0.0)\n    safety_stock = to_float(inputs.get(\"safety_stock\"\
          , 0.0), 0.0)\n    lead_time_days = to_float(inputs.get(\"lead_time_days\"\
          , 0.0), 0.0)\n    min_order_qty = to_float(inputs.get(\"min_order_qty\"\
          , default_min_order), default_min_order)\n    shelf_capacity_units = to_float(inputs.get(\"\
          shelf_capacity_units\", 0.0), 0.0)\n    display_rules = inputs.get(\"display_rules\"\
          , \"\") or \"\"\n    recent_sales_trend = inputs.get(\"recent_sales_trend\"\
          , \"\") or \"\"\n    # 計算: 発注点と推奨発注量\n    reorder_point = avg_daily_sales\
          \ * lead_time_days + safety_stock\n    needed = reorder_point - current_stock\n\
          \    if needed < 0:\n        needed = 0.0\n    # 発注量は最少発注ロットで丸め、整数化\n  \
          \  try:\n        lot = max(int(math.ceil(min_order_qty)), 1)\n    except\
          \ Exception:\n        lot = 1\n    suggested_order_qty = int(math.ceil(needed\
          \ / lot) * lot) if needed > 0 else 0\n    # 棚割案の簡易生成\n    shelf_plan = \"\
          \"\n    details = dict()\n    details[\"product_name\"] = product_name\n\
          \    details[\"avg_daily_sales\"] = avg_daily_sales\n    details[\"current_stock\"\
          ] = current_stock\n    details[\"reorder_point\"] = reorder_point\n    details[\"\
          suggested_order_qty\"] = suggested_order_qty\n    details[\"min_order_qty\"\
          ] = lot\n    # 簡易棚割ロジック\n    if shelf_capacity_units and shelf_capacity_units\
          \ > 0:\n        # 表示件数（フェイス数）を概算: 棚容量に応じた推奨配置\n        try:\n          \
          \  faces = int(max(1, math.floor(shelf_capacity_units / max(1, avg_daily_sales))))\
          \ if avg_daily_sales > 0 else int(max(1, math.floor(shelf_capacity_units)))\n\
          \        except Exception:\n            faces = 1\n        shelf_plan =\
          \ \"推奨フェイス数: \" + str(faces) + \"（棚容量 \" + str(int(shelf_capacity_units))\
          \ + \"）\"\n        details[\"faces\"] = faces\n    else:\n        shelf_plan\
          \ = \"棚容量情報が不足しています。陳列ルールに従い柔軟に配置してください。\"\n    if display_rules:\n    \
          \    shelf_plan = shelf_plan + \" | 陳列ルール: \" + display_rules\n        details[\"\
          display_rules\"] = display_rules\n    if recent_sales_trend:\n        details[\"\
          recent_sales_trend\"] = recent_sales_trend\n    result = dict(order_qty=int(suggested_order_qty),\
          \ shelf_plan=str(shelf_plan), details=details)\n    return dict(result=result)\n"
        variables:
        - value_selector:
          - extract1
          - product_name
          variable: product_name
        - value_selector:
          - extract1
          - avg_daily_sales
          variable: avg_daily_sales
        - value_selector:
          - extract1
          - current_stock
          variable: current_stock
        - value_selector:
          - extract1
          - safety_stock
          variable: safety_stock
        - value_selector:
          - extract1
          - lead_time_days
          variable: lead_time_days
        - value_selector:
          - extract1
          - min_order_qty
          variable: min_order_qty
        - value_selector:
          - extract1
          - shelf_capacity_units
          variable: shelf_capacity_units
        - value_selector:
          - extract1
          - display_rules
          variable: display_rules
        - value_selector:
          - extract1
          - recent_sales_trend
          variable: recent_sales_trend
        outputs:
          result:
            type: object
            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: 'あなたは小売・流通向けの発注・陳列提案アシスタントです。出力は日本語で分かりやすく行ってください。回答は以下の構成に従ってください:

            1) 提案数量（明確な数値）

            2) 理由（箇条書きで根拠を簡潔に）

            3) 棚割案（可能であれば表形式で、フェイス数・配置優先度・陳列ルールへの対応を含める）

            不要な長文は避け、店舗担当者が実行できる具体的な指示を含めてください。'
        - role: user
          text: 'ユーザー入力:

            {{#start.query#}}


            計算結果（機械出力）:

            {{#code1.result#}}


            上記を参照して、発注数量と棚割案を提案してください。数値と理由、簡潔な棚割手順を含めてください。'
        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
