app:
  description: 指定商品の価格改定案を作成し、売上・利益への影響を試算して、社内説明用ドラフト（スライド/説明資料）を生成します。ユーザーは1つのテキスト欄に商品・現行価格・改定案（割合または新価格）・想定出荷量や原価・対象期間等を記載してください。
  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-llm2
      source: code1
      target: llm2
    - data:
        sourceType: llm
        targetType: end
      id: llm2-to-end
      source: llm2
      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: product_names
          type: string
          description: 対象商品名（複数はカンマ区切り）
          required: true
        - name: current_price
          type: number
          description: 現行の単価（通貨は数値で）
          required: false
        - name: proposed_price
          type: number
          description: 改定後の単価（指定がある場合）
          required: false
        - name: price_change_percent
          type: number
          description: 価格改定率（%。正の数は値上げ、負の数は値下げ）
          required: false
        - name: expected_volume
          type: number
          description: '対象期間内の想定販売量（単位: 台/個 等）'
          required: false
        - name: cost_per_unit
          type: number
          description: 単位あたり原価（可能なら）
          required: false
        - name: period_months
          type: number
          description: 想定期間（月数。指定がなければ年次想定とする）
          required: false
        reasoning_mode: prompt
        instruction: '以下の自由入力テキストから、必要な項目を抽出してください。出力は上記パラメータ名に対応する値で返してください。


          - product_names: 商品名は複数ある場合はカンマ区切りで記述すること。

          - current_price / proposed_price: 通貨表記は数値に正規化する（例: "¥12,000" -> 12000）。

          - price_change_percent: "%" で指定があれば数値に変換（例: "10%" -> 10）。単価差と優先順は proposed_price
          > price_change_percent。両方ある場合は proposed_price を優先。

          - expected_volume: 期間内の販売数量（数値）。単位が示されていれば注記するが、数値のみ返す。

          - cost_per_unit: 単位当たり原価（数値）。

          - period_months: 指定が無ければ空欄とする（空欄なら expected_volume を年次換算と扱う）。


          不明点は空欄にして構いません。数値が推定の場合は必ず推定である旨をユーザーに明記してください。

          '
      id: extractor1
      height: 120
      position:
        x: 380
        y: 282
      type: custom
      width: 300
    - 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  \
          \  def to_float(v):\n        try:\n            if v is None:\n         \
          \       return None\n            if isinstance(v, (int, float)):\n     \
          \           return float(v)\n            s = str(v).strip()\n          \
          \  if s == \"\":\n                return None\n            # 削除: 通貨記号とカンマ\n\
          \            s = s.replace(\"¥\", \"\").replace(\"$\", \"\").replace(\"\
          ,\", \"\").replace(\"，\", \"\")\n            # パーセント表記\n            if s.endswith(\"\
          %\"):\n                return float(s[:-1])\n            return float(s)\n\
          \        except:\n            return None\n    # 取得\n    product_names =\
          \ inputs.get(\"product_names\")\n    current_price = to_float(inputs.get(\"\
          current_price\"))\n    proposed_price = to_float(inputs.get(\"proposed_price\"\
          ))\n    price_change_percent = to_float(inputs.get(\"price_change_percent\"\
          ))\n    expected_volume = to_float(inputs.get(\"expected_volume\"))\n  \
          \  cost_per_unit = to_float(inputs.get(\"cost_per_unit\"))\n    period_months\
          \ = to_float(inputs.get(\"period_months\"))\n    notes_list = []\n    #\
          \ 優先ルール: proposed_price があればそれを使う。なければ price_change_percent と current_price\
          \ から計算\n    if proposed_price is None and price_change_percent is not None\
          \ and current_price is not None:\n        proposed_price = current_price\
          \ * (1.0 + (price_change_percent / 100.0))\n        notes_list.append(\"\
          proposed_price を price_change_percent と current_price から計算しました。\")\n   \
          \ # ボリュームの年次換算\n    annual_volume = None\n    if expected_volume is not\
          \ None:\n        if period_months is not None and period_months > 0:\n \
          \           annual_volume = expected_volume * (12.0 / float(period_months))\n\
          \            notes_list.append(\"expected_volume を period_months に基づき年換算しました。\"\
          )\n        else:\n            annual_volume = expected_volume\n    # 計算\n\
          \    def safe_round(v):\n        try:\n            if v is None:\n     \
          \           return None\n            return round(float(v), 2)\n       \
          \ except:\n            return None\n    revenue_current = None\n    revenue_proposed\
          \ = None\n    revenue_change_amount = None\n    revenue_change_percent_calc\
          \ = None\n    gross_profit_current = None\n    gross_profit_proposed = None\n\
          \    gross_profit_change = None\n    if current_price is not None and annual_volume\
          \ is not None:\n        revenue_current = current_price * annual_volume\n\
          \    if proposed_price is not None and annual_volume is not None:\n    \
          \    revenue_proposed = proposed_price * annual_volume\n    if revenue_current\
          \ is not None and revenue_proposed is not None:\n        revenue_change_amount\
          \ = revenue_proposed - revenue_current\n        try:\n            if revenue_current\
          \ != 0:\n                revenue_change_percent_calc = (revenue_change_amount\
          \ / revenue_current) * 100.0\n        except:\n            revenue_change_percent_calc\
          \ = None\n    if cost_per_unit is not None and annual_volume is not None:\n\
          \        gross_profit_current = None\n        gross_profit_proposed = None\n\
          \        if current_price is not None:\n            gross_profit_current\
          \ = (current_price - cost_per_unit) * annual_volume\n        if proposed_price\
          \ is not None:\n            gross_profit_proposed = (proposed_price - cost_per_unit)\
          \ * annual_volume\n        if gross_profit_current is not None and gross_profit_proposed\
          \ is not None:\n            gross_profit_change = gross_profit_proposed\
          \ - gross_profit_current\n    # 不足データの指摘\n    if product_names is None or\
          \ str(product_names).strip() == \"\":\n        notes_list.append(\"商品名が未指定です。\"\
          )\n    if current_price is None:\n        notes_list.append(\"現行単価が未指定のため、試算が限定的です。\"\
          )\n    if proposed_price is None and price_change_percent is None:\n   \
          \     notes_list.append(\"改定後価格または改定率が未指定です。\")\n    if annual_volume is\
          \ None:\n        notes_list.append(\"想定販売量（expected_volume）が未指定です。\")\n\
          \    # 結果まとめ\n    result = dict(\n        product_names = str(product_names)\
          \ if product_names is not None else None,\n        current_price = safe_round(current_price),\n\
          \        proposed_price = safe_round(proposed_price),\n        price_change_percent_input\
          \ = safe_round(price_change_percent),\n        annual_volume = safe_round(annual_volume),\n\
          \        period_months = safe_round(period_months),\n        cost_per_unit\
          \ = safe_round(cost_per_unit),\n        revenue_current = safe_round(revenue_current),\n\
          \        revenue_proposed = safe_round(revenue_proposed),\n        revenue_change_amount\
          \ = safe_round(revenue_change_amount),\n        revenue_change_percent =\
          \ safe_round(revenue_change_percent_calc),\n        gross_profit_current\
          \ = safe_round(gross_profit_current),\n        gross_profit_proposed = safe_round(gross_profit_proposed),\n\
          \        gross_profit_change = safe_round(gross_profit_change),\n      \
          \  notes = \"; \".join(notes_list) if notes_list else None\n    )\n    return\
          \ dict(output = result)\n"
        variables:
        - value_selector:
          - extractor1
          - product_names
          variable: product_names
        - value_selector:
          - extractor1
          - current_price
          variable: current_price
        - value_selector:
          - extractor1
          - proposed_price
          variable: proposed_price
        - value_selector:
          - extractor1
          - price_change_percent
          variable: price_change_percent
        - value_selector:
          - extractor1
          - expected_volume
          variable: expected_volume
        - value_selector:
          - extractor1
          - cost_per_unit
          variable: cost_per_unit
        - value_selector:
          - extractor1
          - period_months
          variable: period_months
        outputs:
          output:
            type: object
            children: null
      height: 120
      id: code1
      position:
        x: 680
        y: 282
      type: custom
      width: 300
    - data:
        title: 説明ドラフト生成
        type: llm
        model:
          provider: openai
          name: gpt-5-mini
          mode: chat
          completion_params:
            temperature: 0.6
        prompt_template:
        - role: system
          text: 'あなたは商社の事業・財務分析担当者向けに「価格改定の影響試算」と「社内説明用ドラフト（スライド/説明資料）」を作成するエキスパートです。以下の要件に従い、日本語で出力してください。


            出力要件（必須）:

            1) 最初に短い要約（Executive Summary）: 目的、結論（売上・利益が増減するか、推奨案）を1段落で。

            2) 前提・仮定: 抽出済みの前提（単価、改定率、期間、出荷量、原価）を箇条書きで明示。

            3) 数値サマリ（表形式で）: 現行売上、改定後売上、増減額、増減率、現行粗利、改定後粗利、粗利増減を示す（通貨は可能な限り¥で）。

            4) 感度分析（簡易）: 改定率が±2段階（例: -2pt, +2pt）変動した場合の売上・粗利の影響（概算）を箇条書きで示す。

            5) 推奨アクション: 販売政策、在庫・プロモーション、さらなる確認事項を3点以内で提案。

            6) 不足情報: 必要な追加データを箇条書きで列挙。

            7) 出力は社内説明に使える体裁（見出し、箇条書き、簡潔な表）で整形すること。スライド想定の箇条書きスタイルで。

            8) 数値が推定に基づく場合はその旨を明確に記載する。

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

            {{#start.query#}}


            試算結果（構造化）:

            {{#code1.output#}}


            指示:

            - 上記の試算結果を元に、社内説明用のドラフトを日本語で作成してください。

            - 表の数値はできるだけ ¥ 表記で、桁区切り（,）を入れてください。数値が None の場合は「未指定」と表示してください。

            - 感度分析は、price_change_percent がある場合はそれを基準に ±2%point（例: 基準が10%なら 8% と 12%）で概算してください。proposed_price
            のみがある場合は、proposed_price を基準に ±2%の差で計算してください。

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