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: 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: 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    # 入力: inputs[\"text\"] に生データ（CSV, JSON\
          \ Lines, またはタブ/カンマ区切りの行）が入る想定\n    text = inputs.get(\"text\", \"\")\n \
          \   import csv\n    import io\n    import json\n    import re\n    rows_list\
          \ = []\n    # 1) CSVとして試行\n    try:\n        f = io.StringIO(text)\n   \
          \     reader = csv.DictReader(f)\n        if reader.fieldnames:\n      \
          \      for r in reader:\n                # dict() を用いて辞書を生成\n          \
          \      rows_list.append(dict((k, v) for k, v in r.items()))\n    except\
          \ Exception:\n        pass\n    # 2) JSON Linesとして試行\n    if not rows_list:\n\
          \        try:\n            for ln in text.splitlines():\n              \
          \  ln = ln.strip()\n                if not ln:\n                    continue\n\
          \                try:\n                    obj = json.loads(ln)\n      \
          \              rows_list.append(obj)\n                except Exception:\n\
          \                    continue\n        except Exception:\n            pass\n\
          \    # 3) 行ごとにスコア, コメント形式を推定して取り出す（タブ or カンマ）\n    if not rows_list:\n \
          \       for ln in text.splitlines():\n            ln = ln.strip()\n    \
          \        if not ln:\n                continue\n            parts = ln.split(\"\
          \\t\")\n            if len(parts) < 2:\n                parts = ln.split(\"\
          ,\", 1)\n            if len(parts) >= 2:\n                rows_list.append(dict(score=parts[0].strip(),\
          \ comment=parts[1].strip()))\n    # 正規化: キーを小文字化して扱う\n    norm_rows = []\n\
          \    for r in rows_list:\n        nr = dict()\n        try:\n          \
          \  for k, v in r.items():\n                nk = str(k).strip().lower()\n\
          \                nv = v\n                nr[nk] = nv\n        except Exception:\n\
          \            continue\n        norm_rows.append(nr)\n    # 評価軸の候補キー（日本語・英語を想定）\n\
          \    axes_keys = dict(\n        instructor=[\"instructor\", \"講師\", \"講師評価\"\
          , \"講師スコア\"],\n        content=[\"content\", \"コンテンツ\", \"内容\", \"教材\"],\n\
          \        facilitation=[\"facilitation\", \"progress\", \"進行\", \"運営\", \"\
          進行管理\"],\n        venue=[\"venue\", \"会場\", \"会場環境\", \"設備\"]\n    )\n \
          \   comments_keys = [\"comment\", \"feedback\", \"free_text\", \"自由記述\"\
          , \"意見\"]\n    # 集計の初期化\n    total_rows = len(norm_rows)\n    axis_stats\
          \ = dict()\n    for ax in axes_keys.keys():\n        axis_stats[ax] = dict(sum=0.0,\
          \ count=0)\n    comments_list = []\n    overall_scores = []\n    # 各行を走査してスコアとコメントを抽出\n\
          \    for r in norm_rows:\n        # コメント収集\n        for ck in comments_keys:\n\
          \            if ck in r and r.get(ck):\n                try:\n         \
          \           cval = str(r.get(ck))\n                    if cval.strip():\n\
          \                        comments_list.append(cval.strip())\n          \
          \      except Exception:\n                    pass\n        # 軸別スコア抽出\n\
          \        for ax, keys in axes_keys.items():\n            found_val = None\n\
          \            for k in keys:\n                if k in r and r.get(k) is not\
          \ None:\n                    found_val = r.get(k)\n                    break\n\
          \            if found_val is not None:\n                try:\n         \
          \           num = float(str(found_val))\n                    axis_stats[ax][\"\
          sum\"] = axis_stats[ax][\"sum\"] + num\n                    axis_stats[ax][\"\
          count\"] = axis_stats[ax][\"count\"] + 1\n                except Exception:\n\
          \                    # 値が数値でない場合はスキップ\n                    pass\n      \
          \  # 全体スコアの候補列名\n        for cand in [\"score\", \"rating\", \"満足度\", \"\
          評価\"]:\n            if cand in r and r.get(cand) is not None:\n        \
          \        try:\n                    n = float(str(r.get(cand)))\n       \
          \             overall_scores.append(n)\n                    break\n    \
          \            except Exception:\n                    pass\n    # 軸ごとの平均を計算\n\
          \    axis_summary = dict()\n    for ax in axis_stats.keys():\n        cnt\
          \ = axis_stats[ax][\"count\"]\n        if cnt and cnt > 0:\n           \
          \ avg = axis_stats[ax][\"sum\"] / cnt\n        else:\n            avg =\
          \ None\n        axis_summary[ax] = dict(average=avg, count=cnt)\n    # 全体平均\n\
          \    overall_average = None\n    if overall_scores:\n        overall_average\
          \ = sum(overall_scores) / len(overall_scores)\n    # 出力結果を構築（機械処理しやすいJSON文字列）\n\
          \    result = dict(\n        total_responses=total_rows,\n        overall_average=overall_average,\n\
          \        axis_summary=axis_summary,\n        sample_comments=comments_list[:50]\n\
          \    )\n    return dict(output=json.dumps(result, ensure_ascii=False))\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: llm
        model:
          provider: openai
          name: gpt-5-mini
          mode: chat
          completion_params:
            temperature: 0.6
        prompt_template:
        - role: system
          text: 'あなたは人事（研修担当）向けのデータ分析と改善提案の専門家です。提供される集計データと抜粋コメントをもとに、以下を日本語で作成してください。1)
            要約（2-3文） 2) 4軸（講師, コンテンツ, 進行, 会場）それぞれの評価（点数感と解説） 3) 優先度付きトップ3の具体的な改善提案（実行ステップ、想定責任部署/担当、概算工数・コスト感）
            4) 次回に向けたチェックリスト（実務で使える項目） 5) 最後に機械可読なJSONをコードブロックで出力（キー: summary, axis_evaluation,
            recommendations, suggestions）。簡潔かつ実行可能な形で記載してください。


            ---


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

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

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

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

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

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

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


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

            に変換してください。)'
        - role: user
          text: '元データ（生入力）:

            {{#start.query#}}


            前処理済み集計（JSON文字列）:

            {{#code1.output#}}


            指示: 上記を踏まえて、人事（研修担当）がすぐに運用に移せるように、改善案を優先順位付きで、実行手順・責任者・コスト感付きで作成してください。数値は可能な限り根拠を示してください。'
        vision:
          enabled: false
        memory: null
        context:
          enabled: false
      height: 90
      id: llm1
      position:
        x: 680
        y: 282
      type: custom
      width: 244
    - data:
        title: 終了
        type: end
        outputs:
        - value_selector:
          - llm1
          - text
          variable: result
      height: 90
      id: end
      position:
        x: 980
        y: 282
      type: custom
      width: 244
  hash: null
