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
        query:
        - start
        - query
        parameters:
        - name: team_members
          type: string
          description: チームメンバー名（カンマ区切り）
          required: true
        - name: rotation_length_weeks
          type: number
          description: ローテーションの全期間（週数）
          required: true
        - name: shift_length_days
          type: number
          description: 1シフトの長さ（日数）
          required: true
        - name: min_rest_days
          type: number
          description: 同一メンバーの最低休息日（日数）
          required: false
        - name: max_shifts_per_member
          type: number
          description: 期間中の1メンバー最大シフト数（目安）
          required: false
        - name: unavailable
          type: string
          description: '利用不可日や固定NG（例: 名前:YYYY-MM-DD; 別行で複数）'
          required: false
        - name: fairness_criteria
          type: string
          description: '公平性基準（例: シフト回数均等、平日優先等）'
          required: false
        reasoning_mode: prompt
        instruction: 以下の自由文から、上記のパラメータを抽出してください。メンバーはカンマ区切り、日付指定はName:YYYY-MM-DD形式などで抽出可能ならそのまま返してください。不明な項目は空で返して構いません。
      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    # 入力を安全に取得\n    members_raw = inputs.get(\"\
          team_members\", \"\")\n    members = [m.strip() for m in str(members_raw).split(\"\
          ,\") if m and m.strip()]\n    try:\n        rotation_weeks = int(inputs.get(\"\
          rotation_length_weeks\") or 0)\n    except:\n        rotation_weeks = 0\n\
          \    try:\n        shift_length_days = int(inputs.get(\"shift_length_days\"\
          ) or 1)\n    except:\n        shift_length_days = 1\n    # 基本チェック\n    total_days\
          \ = rotation_weeks * 7\n    if not members or rotation_weeks <= 0 or shift_length_days\
          \ <= 0:\n        return dict(rotation=\"エラー: メンバー、ローテーション期間、シフト長を正しく指定してください。\"\
          )\n    num_shifts = total_days // shift_length_days\n    if num_shifts <=\
          \ 0:\n        return dict(rotation=\"エラー: ローテーション期間に対してシフト長が大きすぎます。\")\n\
          \    # 単純な公平割当: シフト数をメンバー間で均等に割り当てる（余りは順に配分）\n    counts = [0] * len(members)\n\
          \    schedule_lines = []\n    idx = 0\n    for i in range(num_shifts):\n\
          \        # 最小割当のメンバーを順次選択して公平性を担保\n        min_count = min(counts)\n   \
          \     found = None\n        for j in range(len(members)):\n            k\
          \ = (idx + j) % len(members)\n            if counts[k] == min_count:\n \
          \               found = k\n                break\n        if found is None:\n\
          \            found = idx % len(members)\n        counts[found] = counts[found]\
          \ + 1\n        schedule_lines.append(\"Shift \" + str(i + 1) + \": \" +\
          \ members[found])\n        idx = (found + 1) % len(members)\n    # summary\n\
          \    summary_lines = []\n    summary_lines.append(\"期間: \" + str(rotation_weeks)\
          \ + \" 週, シフト長: \" + str(shift_length_days) + \" 日, 合計シフト数: \" + str(num_shifts))\n\
          \    summary_lines.append(\"\")\n    summary_lines.append(\"割当一覧:\")\n \
          \   summary_lines.extend(schedule_lines)\n    summary_lines.append(\"\"\
          )\n    summary_lines.append(\"メンバー別割当数:\")\n    for m, c in zip(members,\
          \ counts):\n        summary_lines.append(m + \": \" + str(c))\n    # 注意:\
          \ unavailable や min_rest_days, max_shifts_per_member の複雑な制約はここでは簡易扱いとして省略し、最終出力で注意事項として示す\n\
          \    result_text = \"\\n\".join(summary_lines)\n    return dict(rotation=result_text)\n"
        variables:
        - value_selector:
          - extract1
          - team_members
          variable: team_members
        - value_selector:
          - extract1
          - rotation_length_weeks
          variable: rotation_length_weeks
        - value_selector:
          - extract1
          - shift_length_days
          variable: shift_length_days
        - value_selector:
          - extract1
          - min_rest_days
          variable: min_rest_days
        - value_selector:
          - extract1
          - max_shifts_per_member
          variable: max_shifts_per_member
        - value_selector:
          - extract1
          - unavailable
          variable: unavailable
        - value_selector:
          - extract1
          - fairness_criteria
          variable: fairness_criteria
        outputs:
          rotation:
            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.5
        prompt_template:
        - role: system
          text: 'あなたはオンコールスケジュール設計の専門家です。出力は読みやすく、実行可能な形式で提供してください。出力項目: 1) 提案されたオンコールローテーション（表形式または箇条書き）
            2) 割当の公平性に関する説明 3) 制約や追加考慮事項（利用不可日、最小休息日、最大シフト数等がある場合の注意点） 4) 次のアクション（例:
            日付確定、直接調整が必要なメンバーの通知）'
        - role: user
          text: 'ユーザー入力:

            {{#start.query#}}


            計算結果（ローテーションの下書き）:

            {{#code1.rotation#}}


            上記を基に、実運用で使える最終的なオンコールローテーション案を作成してください。必要に応じて公平化の説明や制約による注意点を明記してください。'
        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
