{
  "nodes": [
    {
      "id": "6f110ebd-9f44-4f0a-9227-bae4470cd24c",
      "name": "\ud83d\udccb Template Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -8368,
        1184
      ],
      "parameters": {
        "width": 680,
        "height": 520,
        "content": "# Automated daily standup system with Slack, Notion and Redis\n\n## How it works\nThis workflow automates daily standups for your team across two sessions \u2014 morning and evening \u2014 using Slack as the interface, Notion as the data store, and Redis for real-time session state management.\n\n**Morning standup (Mon\u2013Sat, 9:40 AM IST)**\nThe bot messages each active team member on Slack asking 4 questions: how they're feeling, what they plan to work on, any blockers, and which tickets/tasks. Responses are saved to Notion's `Daily_Standups` database. At 10:10 AM a summary is posted to the admin Slack channel.\n\n**Evening standup (Mon\u2013Sat, 6:00 PM IST)**\nThe bot asks 3 questions: what was completed, whether help is needed tomorrow, and whether planned tasks were finished. Results are saved to `Daily_Standups_Afternoon`. A summary is posted at 6:30 PM.\n\n**Session management**\nRedis caches active sessions (TTL: 8h morning / 3h evening) so the bot can track where each user is in the conversation. Notion serves as the persistent fallback if Redis expires.\n\n## Setup steps\n1. Add your **Notion API** credentials (connect to `Team_Members`, `Daily_Standups`, `Daily_Standups_Afternoon`, `Conversation_State`, `Conversation_State_Afternoon`, `Blockers_Tracking` databases)\n2. Add your **Slack Bot** credentials and invite the bot to your workspace DMs\n3. Add your **Redis** credentials\n4. Ensure the `Team_Members` Notion database has `Slack_User_ID`, `Developer_ID`, `Active_Status`, and `Daily_Standups` (checkbox) fields\n5. Activate all three workflows: Morning Initiator, Evening Initiator, and the main Slack Handler"
      },
      "typeVersion": 1
    },
    {
      "id": "367c6c50-f925-4e84-92a8-de0d31e5df8d",
      "name": "\ud83c\udf19 Evening Summary Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1008,
        1376
      ],
      "parameters": {
        "color": 6,
        "width": 404,
        "height": 572,
        "content": "# \ud83c\udf19 Evening Standup Summary\n\n## How it works\n1. Triggers Mon\u2013Sat at 6:30 PM IST\n2. Fetches active members from `Team_Members` Notion DB\n3. Fetches today's evening standups from `Daily_Standups_Afternoon` DB\n4. Merges data, builds summary, sends to admin Slack channel\n\n## Setup steps\n1. Connect your Notion integration and set correct DB IDs\n2. Connect Slack bot (SlackBot Chintanbhai)\n3. Confirm Slack channel ID `C07PSV73SK0` is correct\n4. Activate the workflow"
      },
      "typeVersion": 1
    },
    {
      "id": "73ac0ac6-c917-4de0-9ac5-16ff881cabe5",
      "name": "Trigger Group Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -496,
        1488
      ],
      "parameters": {
        "color": 7,
        "width": 260,
        "height": 320,
        "content": "## \u23f0 Trigger\nFires Mon\u2013Sat at 6:30 PM IST (`30 18 * * 1-6`). Kicks off both Notion fetches in parallel."
      },
      "typeVersion": 1
    },
    {
      "id": "f4d8740c-1fd8-4e8c-9da4-8e9ab9fbc2db",
      "name": "Fetch Data Group Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -208,
        1328
      ],
      "parameters": {
        "color": 7,
        "width": 292,
        "height": 596,
        "content": "## \ud83d\udccb Fetch Data\nFetches active members (`Team_Members`) and today's responded evening standups (`Daily_Standups_Afternoon`) from Notion in parallel."
      },
      "typeVersion": 1
    },
    {
      "id": "d0c3e15f-b77c-4e65-a492-8891574f9724",
      "name": "Merge Build Group Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        144,
        1488
      ],
      "parameters": {
        "color": 7,
        "width": 424,
        "height": 344,
        "content": "## \ud83d\udd00 Merge & Build\nMerges both datasets, calculates response rate, missing members, help requests, task completion, and formats the Slack message."
      },
      "typeVersion": 1
    },
    {
      "id": "876b6513-75a0-4778-960b-a106c6665ec4",
      "name": "Send Slack Group Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        592,
        1488
      ],
      "parameters": {
        "color": 7,
        "width": 320,
        "height": 336,
        "content": "## \ud83d\udce4 Send to Slack\nPosts the formatted evening summary to the admin Slack channel via SlackBot Chintanbhai."
      },
      "typeVersion": 1
    },
    {
      "id": "71ef2aec-3f18-4c5b-8a17-e7ae01cbf425",
      "name": "Fetch Active Members",
      "type": "n8n-nodes-base.notion",
      "position": [
        -112,
        1728
      ],
      "parameters": {
        "filters": {
          "conditions": [
            {
              "key": "Active_Status|status",
              "condition": "equals",
              "statusValue": "Active"
            },
            {
              "key": "Daily_Standups|checkbox",
              "condition": "equals",
              "checkboxValue": true
            }
          ]
        },
        "options": {},
        "resource": "databasePage",
        "matchType": "allFilters",
        "operation": "getAll",
        "returnAll": true,
        "databaseId": {
          "__rl": true,
          "mode": "list",
          "value": "2d4da36d-2b3d-80ba-931c-fa5646433363",
          "cachedResultUrl": "https://www.notion.so/2d4da36d2b3d80ba931cfa5646433363",
          "cachedResultName": "Team_Members"
        },
        "filterType": "manual"
      },
      "typeVersion": 2.2
    },
    {
      "id": "598c75cc-04ad-4e38-b6ac-edafe14d0383",
      "name": "Merge Data",
      "type": "n8n-nodes-base.merge",
      "position": [
        160,
        1632
      ],
      "parameters": {},
      "typeVersion": 3
    },
    {
      "id": "06b8f6de-7298-421a-89ed-04bbbf00b967",
      "name": "Fetch Evening Standups",
      "type": "n8n-nodes-base.notion",
      "position": [
        -112,
        1536
      ],
      "parameters": {
        "filters": {
          "conditions": [
            {
              "key": "Responded|checkbox",
              "condition": "equals",
              "checkboxValue": true
            },
            {
              "key": "Date|date",
              "date": "={{ $now.setZone('Asia/Kolkata').toFormat('yyyy-MM-dd') }}",
              "condition": "equals"
            }
          ]
        },
        "options": {},
        "resource": "databasePage",
        "matchType": "allFilters",
        "operation": "getAll",
        "returnAll": true,
        "databaseId": {
          "__rl": true,
          "mode": "list",
          "value": "2d4da36d-2b3d-8089-9e59-f645221ee1b7",
          "cachedResultUrl": "https://www.notion.so/2d4da36d2b3d80899e59f645221ee1b7",
          "cachedResultName": "Daily_Standups_Afternoon"
        },
        "filterType": "manual"
      },
      "typeVersion": 2.2
    },
    {
      "id": "c41a3b62-1eea-42d8-a350-1e1cc7d7f424",
      "name": "Evening schedule trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -416,
        1632
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "30 18 * * 1-6"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "9de24fc0-0075-4746-9b2d-269adbb48fbc",
      "name": "Build evening summary message",
      "type": "n8n-nodes-base.code",
      "position": [
        416,
        1632
      ],
      "parameters": {
        "jsCode": "// ============================================\n// BUILD EVENING STANDUP SUMMARY\n// ============================================\n\nconst standups = $('Fetch Evening Standups').all();\nconst activeMembers = $('Fetch Active Members').all();\n\nconst now = $now.setZone('Asia/Kolkata');\nconst today = now.toFormat('EEEE, MMMM d, yyyy');\nconst currentTime = now.toFormat('h:mm a');\n\nconsole.log('=== EVENING SUMMARY ===');\nconsole.log('Date:', today);\nconsole.log('Time:', currentTime);\nconsole.log('Active Members:', activeMembers.length);\nconsole.log('Standups Received:', standups.length);\n\n// Calculate statistics\nconst totalActive = activeMembers.length;\nconst totalResponded = standups.length;\nconst responseRate = totalActive > 0 ? ((totalResponded / totalActive) * 100).toFixed(1) : 0;\n\n// Find missing members\nconst respondedIds = new Set(standups.map(s => {\n  const devId = s.json.property_developer_id || s.json.Developer_ID || '';\n  return devId;\n}));\n\nconst missingMembers = activeMembers\n  .filter(m => {\n    const devId = m.json.property_developer_id || m.json.Developer_ID || '';\n    return !respondedIds.has(devId);\n  })\n  .map(m => m.json.property_full_name || m.json.Full_Name || 'Unknown');\n\nconsole.log('Missing Members:', missingMembers);\n\n// Build message header\nlet message = `\ud83c\udf19 *Evening Standup Summary*\\n`;\nmessage += `\ud83d\udcc5 ${today}\\n`;\nmessage += `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\\n\\n`;\n\n// Response statistics\nmessage += `\ud83d\udcc8 *Response Statistics*\\n`;\nmessage += `\u2705 Responded: ${totalResponded}/${totalActive} (${responseRate}%)\\n`;\nif (missingMembers.length > 0) {\n  message += `\u274c Missing: ${missingMembers.length}\\n`;\n}\nmessage += `\\n`;\n\n// Missing responses\nif (missingMembers.length > 0) {\n  message += `\u26a0\ufe0f *Missing Responses:*\\n`;\n  missingMembers.forEach(name => {\n    message += `   \u2022 ${name}\\n`;\n  });\n  message += `\\n`;\n}\n\n// Help needed section\nconst needHelp = standups.filter(s => {\n  const help = s.json.property_help_needed || s.json.Help_Needed || '';\n  const noHelpPatterns = ['no', 'none', 'n/a', 'na', 'no help needed', 'not needed', 'nope'];\n  const helpLower = help.toLowerCase().trim();\n  return help.trim().length > 0 && \n         !noHelpPatterns.some(pattern => helpLower === pattern || helpLower.includes(pattern));\n});\n\nconsole.log('Help Needed Count:', needHelp.length);\n\nif (needHelp.length > 0) {\n  message += `\ud83c\udd98 *Help Requested (${needHelp.length}):*\\n`;\n  needHelp.forEach(s => {\n    const devId = s.json.property_developer_id || s.json.Developer_ID;\n    const member = activeMembers.find(m => {\n      const memberDevId = m.json.property_developer_id || m.json.Developer_ID;\n      return memberDevId === devId;\n    });\n    const name = member ? (member.json.property_full_name || member.json.Full_Name) : devId;\n    const help = s.json.property_help_needed || s.json.Help_Needed || '';\n    const helpText = help.substring(0, 100);\n    message += `\\n\ud83d\udc64 *${name}*\\n`;\n    message += `   ${helpText}${help.length > 100 ? '...' : ''}\\n`;\n  });\n  message += `\\n`;\n} else {\n  message += `\u2705 *No Help Requested*\\n\\n`;\n}\n\n// Task completion status\nconst completed = standups.filter(s => {\n  const completion = (s.json.property_task_completion_info || s.json.Task_Completion_Info || '').toLowerCase();\n  return completion.includes('yes') || completion.includes('complete') || completion.includes('done') || completion.includes('all complete');\n});\n\nconst incomplete = standups.filter(s => {\n  const completion = (s.json.property_task_completion_info || s.json.Task_Completion_Info || '').toLowerCase();\n  return completion.includes('no') || completion.includes('pending') || completion.includes('incomplete');\n});\n\nconsole.log('Completed:', completed.length);\nconsole.log('Incomplete:', incomplete.length);\n\nmessage += `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\\n`;\nmessage += `\u2705 *Task Completion Status:*\\n`;\nmessage += `   \u2713 Completed: ${completed.length}\\n`;\nmessage += `   \u23f3 Pending: ${incomplete.length}\\n`;\nmessage += `   \u2139\ufe0f Other: ${standups.length - completed.length - incomplete.length}\\n\\n`;\n\n// Today's accomplishments\nmessage += `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\\n`;\nmessage += `\ud83c\udfaf *Today's Accomplishments:*\\n\\n`;\n\nconst displayCount = Math.min(5, standups.length);\nstandups.slice(0, displayCount).forEach(s => {\n  const devId = s.json.property_developer_id || s.json.Developer_ID;\n  const member = activeMembers.find(m => {\n    const memberDevId = m.json.property_developer_id || m.json.Developer_ID;\n    return memberDevId === devId;\n  });\n  const name = member ? (member.json.property_full_name || member.json.Full_Name) : devId;\n  const completedWork = s.json.property_today_completed || s.json.Today_Completed || '';\n  \n  if (completedWork.trim()) {\n    const workText = completedWork.substring(0, 80);\n    message += `\ud83d\udc64 *${name}*\\n`;\n    message += `   ${workText}${completedWork.length > 80 ? '...' : ''}\\n\\n`;\n  }\n});\n\nif (standups.length > 5) {\n  message += `_...and ${standups.length - 5} more team members_\\n\\n`;\n}\n\n// Footer\nmessage += `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\\n`;\nmessage += `\ud83d\udd50 Generated at ${currentTime} IST`;\n\nconsole.log('=== SUMMARY COMPLETE ===');\n\nreturn {\n  message: message,\n  total_active: totalActive,\n  total_responded: totalResponded,\n  response_rate: parseFloat(responseRate),\n  missing_count: missingMembers.length,\n  help_needed_count: needHelp.length,\n  completed_count: completed.length,\n  incomplete_count: incomplete.length,\n  missing_members: missingMembers\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "c0ff3755-e5da-4608-a265-f85421f5e296",
      "name": "Send to Admin Channel Evening",
      "type": "n8n-nodes-base.slack",
      "position": [
        704,
        1632
      ],
      "parameters": {
        "text": "={{ $json.message }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $json.slackBotID }}"
        },
        "otherOptions": {
          "includeLinkToWorkflow": false
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "2020c710-a210-42df-a02e-21ade4381f71",
      "name": "\ud83c\udf05 Morning Summary Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -976,
        480
      ],
      "parameters": {
        "color": 6,
        "width": 420,
        "height": 584,
        "content": "# \ud83c\udf05 Morning Standup Summary\n\n## How it works\n1. Triggers Mon\u2013Sat at 10:10 AM IST\n2. Fetches active members from `Team_Members` Notion DB\n3. Fetches today's responded standups from `Daily_Standups` DB\n4. Merges data, builds summary, sends to admin Slack channel\n5. Output includes attendance, blockers & stats\n\n## Setup steps\n1. Connect your Notion integration and verify DB IDs\n2. Connect Slack bot (SlackBot Chintanbhai)\n3. Confirm Slack channel ID `C07PSV73SK0` is correct\n4. Activate the workflow"
      },
      "typeVersion": 1
    },
    {
      "id": "a8681070-4e2f-4901-a4b4-9b72bfb644d6",
      "name": "Send to Admin Channel",
      "type": "n8n-nodes-base.slack",
      "position": [
        864,
        784
      ],
      "parameters": {
        "text": "={{ $json.message }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $json.slackBotID }}"
        },
        "otherOptions": {
          "includeLinkToWorkflow": false
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "d1644f61-afd1-4054-9103-0a887c82aad4",
      "name": "Morning schedule trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -384,
        768
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "10 10 * * 1-6"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "985857f3-5a33-4461-9a8d-7c301e6d682d",
      "name": "Fetch team members (morning)",
      "type": "n8n-nodes-base.notion",
      "position": [
        -32,
        640
      ],
      "parameters": {
        "filters": {
          "conditions": [
            {
              "key": "Active_Status|status",
              "condition": "equals",
              "statusValue": "Active"
            },
            {
              "key": "Daily_Standups|checkbox",
              "condition": "equals",
              "checkboxValue": true
            }
          ]
        },
        "options": {},
        "resource": "databasePage",
        "matchType": "allFilters",
        "operation": "getAll",
        "returnAll": true,
        "databaseId": {
          "__rl": true,
          "mode": "list",
          "value": "2d4da36d-2b3d-80ba-931c-fa5646433363",
          "cachedResultUrl": "https://www.notion.so/2d4da36d2b3d80ba931cfa5646433363",
          "cachedResultName": "Team_Members"
        },
        "filterType": "manual"
      },
      "typeVersion": 2.2
    },
    {
      "id": "e269b8d3-f186-46e7-8db4-6779eb8d5ee6",
      "name": "Fetch today's morning standups",
      "type": "n8n-nodes-base.notion",
      "position": [
        -32,
        880
      ],
      "parameters": {
        "filters": {
          "conditions": [
            {
              "key": "Responded|checkbox",
              "condition": "equals",
              "checkboxValue": true
            },
            {
              "key": "Date|date",
              "date": "={{ $now.setZone('Asia/Kolkata').toFormat('yyyy-MM-dd') }}",
              "condition": "equals"
            }
          ]
        },
        "options": {},
        "resource": "databasePage",
        "matchType": "allFilters",
        "operation": "getAll",
        "returnAll": true,
        "databaseId": {
          "__rl": true,
          "mode": "list",
          "value": "2d4da36d-2b3d-8008-9194-d8e45419e824",
          "cachedResultUrl": "https://www.notion.so/2d4da36d2b3d80089194d8e45419e824",
          "cachedResultName": "Daily_Standups"
        },
        "filterType": "manual"
      },
      "typeVersion": 2.2
    },
    {
      "id": "484fc410-b4f5-4418-a087-948e464aa90d",
      "name": "Merge morning data",
      "type": "n8n-nodes-base.merge",
      "position": [
        288,
        784
      ],
      "parameters": {},
      "typeVersion": 3.2
    },
    {
      "id": "aa7c5fdd-41a0-46b5-ad69-7265e0f7c13b",
      "name": "Build morning summary message",
      "type": "n8n-nodes-base.code",
      "position": [
        496,
        784
      ],
      "parameters": {
        "jsCode": "// Get all merged input items\nconst allItems = $input.all();\n\nconsole.log('\ud83d\udce5 Total items received:', allItems.length);\n\n// Normalize field names function\nconst normalizeRecord = (record) => ({\n  Developer_ID: record.property_developer_id || record.Developer_ID || '',\n  Full_Name: record.property_full_name || record.Full_Name || '',\n  Active_Status: record.property_active_status || record.Active_Status || '',\n  Responded: record.property_responded || record.Responded || false,\n  Blockers: record.property_blockers || record.Blockers || '',\n  Date: record.property_date || record.Date || '',\n  Completed_Yesterday: record.property_completed_yesterday || record.Completed_Yesterday || '',\n  Goals_Today: record.property_goals_today || record.Goals_Today || '',\n  Plans_Tickets: record.property_plans_tickets || record.Plans_Tickets || '',\n  Help_Needed: record.property_help_needed || record.Help_Needed || '',\n  Slack_User_ID: record.property_slack_user_id || record.Slack_User_ID || ''\n});\n\n// Process all items and separate by Developer_ID\nconst memberMap = new Map();\nconst standupMap = new Map();\n\nallItems.forEach(item => {\n  const normalized = normalizeRecord(item.json);\n  \n  // Skip if no Developer_ID\n  if (!normalized.Developer_ID) return;\n  \n  // If this record has Active_Status, it's a member record\n  if (normalized.Active_Status === 'Active') {\n    memberMap.set(normalized.Developer_ID, normalized);\n  }\n  \n  // If this record has Date and Responded=true, it's a standup response\n  if (normalized.Date && normalized.Responded === true) {\n    standupMap.set(normalized.Developer_ID, normalized);\n  }\n});\n\nconst activeMembers = Array.from(memberMap.values());\nconst standupResponses = Array.from(standupMap.values());\n\nconsole.log('\ud83d\udc65 Active members:', activeMembers.length);\nconsole.log('\u2705 Standup responses:', standupResponses.length);\n\n// Get current India time\nconst now = new Date();\n\nconst todayFormatted = new Intl.DateTimeFormat(\"en-IN\", {\n  timeZone: \"Asia/Kolkata\",\n  weekday: \"long\",\n  year: \"numeric\",\n  month: \"long\",\n  day: \"numeric\"\n}).format(now);\n\nconst currentTime = new Intl.DateTimeFormat(\"en-IN\", {\n  timeZone: \"Asia/Kolkata\",\n  hour: \"numeric\",\n  minute: \"2-digit\",\n  hour12: true\n}).format(now);\n\n// Calculate statistics\nconst totalActive = activeMembers.length;\nconst respondedIds = new Set(standupResponses.map(s => s.Developer_ID));\nconst totalResponded = respondedIds.size;\nconst totalNotAttended = Math.max(0, totalActive - totalResponded);\nconst responseRate = totalActive > 0 ? ((totalResponded / totalActive) * 100).toFixed(1) : \"0.0\";\n\n// Separate members into attended and not attended\nconst respondedMembers = activeMembers.filter(m => respondedIds.has(m.Developer_ID));\nconst notAttendedMembers = activeMembers.filter(m => !respondedIds.has(m.Developer_ID));\n\nconsole.log('\u2705 Responded members:', respondedMembers.length);\nconsole.log('\u274c Not attended members:', notAttendedMembers.length);\n\n// Function to check if text indicates \"no blocker\"\nconst isNoBlocker = (text) => {\n  if (!text) return true;\n  \n  const cleaned = text.toString().trim().toLowerCase();\n  if (cleaned.length === 0) return true;\n  \n  const noneWords = [\n    \"no\", \"none\", \"n/a\", \"na\", \"nil\", \"nothing\", \"nope\",\n    \"no blocker\", \"no blockers\", \"no blocking\", \"no blocks\",\n    \"not applicable\", \"not available\", \"empty\", \"null\"\n  ];\n  \n  if (noneWords.includes(cleaned)) return true;\n  \n  const noBlockerPatterns = [\n    /^no[\\s\\-_]*blocker/i,\n    /^no[\\s\\-_]*block/i,\n    /^none$/i,\n    /^n\\/?a$/i,\n    /^nil$/i\n  ];\n  \n  return noBlockerPatterns.some(pattern => pattern.test(cleaned));\n};\n\n// Find members with actual blockers\nconst membersWithBlockers = standupResponses.filter(s => {\n  const blockerText = s.Blockers || s.Help_Needed || '';\n  return !isNoBlocker(blockerText);\n});\n\nconsole.log('\ud83d\udea7 Members with blockers:', membersWithBlockers.length);\n\n// Build WhatsApp formatted message\nlet message = `\ud83d\udcca *Daily Standup Summary Report*\\n`;\nmessage += `\ud83d\udcc5 Date: ${todayFormatted}\\n`;\nmessage += `\ud83d\udd50 Time: ${currentTime} IST\\n`;\nmessage += `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\\n\\n`;\n\nmessage += `\ud83d\udcc8 *Attendance Overview*\\n`;\nmessage += `\ud83d\udc65 Total Active Members: ${totalActive}\\n`;\nmessage += `\u2705 Attended (Responded): ${totalResponded}\\n`;\nmessage += `\u274c Not Attended: ${totalNotAttended}\\n`;\nmessage += `\ud83d\udcca Response Rate: ${responseRate}%\\n\\n`;\n\nif (respondedMembers.length > 0) {\n  message += `\u2705 *Members Who Attended (${respondedMembers.length}):*\\n`;\n  respondedMembers.forEach(member => {\n    const name = member.Full_Name || member.Developer_ID || 'Unknown';\n    message += `   \u2022 ${name}\\n`;\n  });\n  message += `\\n`;\n} else {\n  message += `\u2705 *No members have attended yet*\\n\\n`;\n}\n\nif (notAttendedMembers.length > 0) {\n  message += `\u274c *Members Who Did NOT Attend (${notAttendedMembers.length}):*\\n`;\n  notAttendedMembers.forEach(member => {\n    const name = member.Full_Name || member.Developer_ID || 'Unknown';\n    message += `   \u2022 ${name}\\n`;\n  });\n  message += `\\n`;\n} else {\n  message += `\ud83c\udf89 *All members have attended!*\\n\\n`;\n}\n\nmessage += `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\\n`;\n\nif (membersWithBlockers.length > 0) {\n  message += `\ud83d\udea7 *Blockers Reported (${membersWithBlockers.length}):*\\n\\n`;\n  membersWithBlockers.forEach(member => {\n    const name = member.Full_Name || member.Developer_ID || 'Unknown';\n    const blockerText = member.Blockers || member.Help_Needed || 'Not specified';\n    message += `\ud83d\udc64 *${name}*\\n`;\n    message += `   \ud83d\udeab ${blockerText}\\n\\n`;\n  });\n} else {\n  message += `\u2705 *No Blockers Reported*\\n\\n`;\n}\n\nmessage += `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\\n`;\nmessage += `\ud83d\udcca *Summary Statistics*\\n`;\nmessage += `\u2022 Team Size: ${totalActive}\\n`;\nmessage += `\u2022 Responses: ${totalResponded}\\n`;\nmessage += `\u2022 Pending: ${totalNotAttended}\\n`;\nmessage += `\u2022 Blockers: ${membersWithBlockers.length}\\n\\n`;\n\nmessage += `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\\n`;\nmessage += `\ud83e\udd16 Auto-generated Daily Standup Report\\n`;\nmessage += `\u23f0 Generated at ${currentTime} IST\\n`;\n\nreturn {\n  json: {\n    message,\n    date: todayFormatted,\n    time: currentTime,\n    timestamp: now.toISOString(),\n    total_active: totalActive,\n    total_responded: totalResponded,\n    total_not_attended: totalNotAttended,\n    response_rate: parseFloat(responseRate),\n    blockers_count: membersWithBlockers.length,\n    responded_members: respondedMembers.map(m => ({\n      id: m.Developer_ID,\n      name: m.Full_Name,\n      slack_user_id: m.Slack_User_ID\n    })),\n    not_attended_members: notAttendedMembers.map(m => ({\n      id: m.Developer_ID,\n      name: m.Full_Name,\n      slack_user_id: m.Slack_User_ID\n    })),\n    blockers: membersWithBlockers.map(m => ({\n      id: m.Developer_ID,\n      name: m.Full_Name,\n      blocker: m.Blockers || m.Help_Needed,\n      completed_yesterday: m.Completed_Yesterday,\n      goals_today: m.Goals_Today,\n      plans_tickets: m.Plans_Tickets\n    })),\n    debug: {\n      total_items: allItems.length,\n      active_members_found: activeMembers.length,\n      standup_responses_found: standupResponses.length,\n      responded_ids: Array.from(respondedIds)\n    }\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "056d01d8-6b8b-45f0-b1bc-4d8d81fa6594",
      "name": "Trigger Group Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -496,
        608
      ],
      "parameters": {
        "color": 7,
        "width": 324,
        "height": 336,
        "content": "## \u23f0 Trigger\nFires Mon\u2013Sat at 10:10 AM IST (`10 10 * * 1-6`). Kicks off both Notion fetches in parallel."
      },
      "typeVersion": 1
    },
    {
      "id": "9223fba9-3482-4478-955e-002d1545f070",
      "name": "Fetch Data Group Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -144,
        496
      ],
      "parameters": {
        "color": 7,
        "width": 320,
        "height": 572,
        "content": "## \ud83d\udccb Fetch Data\nFetches active members (`Team_Members`) and today's responded morning standups (`Daily_Standups`) from Notion in parallel."
      },
      "typeVersion": 1
    },
    {
      "id": "d25db796-f2df-43a6-8feb-9ded9861083e",
      "name": "Merge Build Group Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        208,
        640
      ],
      "parameters": {
        "color": 7,
        "width": 492,
        "height": 328,
        "content": "## \ud83d\udd00 Merge & Build\nMerges both datasets. Code node normalizes fields, calculates attendance stats, detects blockers, and formats the Slack message."
      },
      "typeVersion": 1
    },
    {
      "id": "45394719-6b96-47a5-8d46-51472c82e029",
      "name": "Send Slack Group Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        736,
        640
      ],
      "parameters": {
        "color": 7,
        "width": 352,
        "height": 336,
        "content": "## \ud83d\udce4 Send to Slack\nPosts the formatted morning standup summary to the admin Slack channel via SlackBot Chintanbhai."
      },
      "typeVersion": 1
    },
    {
      "id": "e9d5f66e-57d8-462f-9bc2-ec3a28db3d22",
      "name": "\ud83c\udf05 Morning Initiator Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -7472,
        240
      ],
      "parameters": {
        "color": 6,
        "width": 460,
        "height": 508,
        "content": "# \ud83c\udf05 Morning Standup Initiator\n\n## How it works\n1. Triggers Mon\u2013Sat at 9:40 AM IST\n2. Fetches active members from `Team_Members` Notion DB\n3. Loops through each developer one at a time\n4. Validates member data, checks Redis for existing session\n5. Creates Notion session record + saves to Redis\n6. Sends Question 1 via Slack DM, saves channel ID back to Notion\n\n## Setup steps\n1. Connect Notion integration and verify all DB IDs\n2. Connect Redis instance with correct credentials\n3. Connect Slack bot and confirm DM permissions\n4. Activate the workflow"
      },
      "typeVersion": 1
    },
    {
      "id": "84bbf89f-6820-4ae4-8aec-afadd95438ea",
      "name": "Fetch Gate Group Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -6624,
        336
      ],
      "parameters": {
        "color": 7,
        "width": 412,
        "height": 336,
        "content": "## \ud83d\udccb Fetch & Gate\nFetches active members from `Team_Members` Notion DB. IF node stops the flow if no active members are found."
      },
      "typeVersion": 1
    },
    {
      "id": "13b3c99e-e92d-432f-a687-f67c5ec512e2",
      "name": "Loop Group Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -6128,
        288
      ],
      "parameters": {
        "color": 7,
        "width": 300,
        "height": 384,
        "content": "## \ud83d\udd01 Developer Loop\n`splitInBatches` iterates one member at a time. All paths (valid, invalid, skipped) converge at `Continue to next` before looping back."
      },
      "typeVersion": 1
    },
    {
      "id": "d02ad8b0-9326-43fb-95bd-94f22b63f93b",
      "name": "Validate Group Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -5760,
        192
      ],
      "parameters": {
        "color": 7,
        "width": 448,
        "height": 336,
        "content": "## \u2705 Validate Member\nCode node normalizes Notion fields and checks required values. IF node skips invalid members without breaking the loop."
      },
      "typeVersion": 1
    },
    {
      "id": "99dc63bc-7092-4a96-a503-decf4694a4c4",
      "name": "Redis Check Group Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -5280,
        128
      ],
      "parameters": {
        "color": 7,
        "width": 468,
        "height": 408,
        "content": "## \ud83d\udd34 Redis Session Check\nGETs existing session key `morning:standup:session:{slack_user_id}`. Code node parses the response \u2014 sets `needs_new_session` true/false. IF node skips members with active sessions."
      },
      "typeVersion": 1
    },
    {
      "id": "11396eb2-cb2a-44cd-82fb-6b1afc5c5d2e",
      "name": "Create Session Group Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -4784,
        96
      ],
      "parameters": {
        "color": 7,
        "width": 768,
        "height": 420,
        "content": "## \ud83d\udcdd Create Session\nCreates a `Conversation_State` record in Notion (question 1, In Progress). Code node builds the Redis payload with a 24hr TTL."
      },
      "typeVersion": 1
    },
    {
      "id": "fefd9e1e-81b4-4a9c-a4d5-d4e3c851ea27",
      "name": "Save Send Group Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3968,
        0
      ],
      "parameters": {
        "color": 7,
        "width": 1244,
        "height": 824,
        "content": "## \ud83d\udd34 Save & Send\nIF `can_write_redis` is true: SETs session in Redis (86400s TTL), sends Question 1 Slack DM, then saves the DM channel ID back to `Team_Members` in Notion."
      },
      "typeVersion": 1
    },
    {
      "id": "900c50f1-aebc-4e22-85a6-5d7bce1cfd09",
      "name": "Morning standup initiator trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -6864,
        480
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 40 9 * * 1-6"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "828dc392-0297-4856-82a1-9dca2efc75ab",
      "name": "Get active team members",
      "type": "n8n-nodes-base.notion",
      "position": [
        -6576,
        480
      ],
      "parameters": {
        "filters": {
          "conditions": [
            {
              "key": "Daily_Standups|checkbox",
              "condition": "equals",
              "checkboxValue": true
            },
            {
              "key": "Active_Status|status",
              "condition": "equals",
              "statusValue": "Active"
            }
          ]
        },
        "options": {},
        "resource": "databasePage",
        "matchType": "allFilters",
        "operation": "getAll",
        "returnAll": true,
        "databaseId": {
          "__rl": true,
          "mode": "list",
          "value": "2d4da36d-2b3d-80ba-931c-fa5646433363",
          "cachedResultUrl": "https://www.notion.so/2d4da36d2b3d80ba931cfa5646433363",
          "cachedResultName": "Team_Members"
        },
        "filterType": "manual"
      },
      "typeVersion": 2.2
    },
    {
      "id": "6aaaacd8-76ae-4428-948b-41860e06a7e7",
      "name": "Check if members found",
      "type": "n8n-nodes-base.if",
      "position": [
        -6352,
        480
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "ef1ed4ff-f356-49a9-9b2a-907619ccbd85",
              "operator": {
                "type": "boolean",
                "operation": "false",
                "singleValue": true
              },
              "leftValue": "={{ $json.isEmpty() }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "ae99b0ff-4147-4ff9-86a2-688156f51893",
      "name": "Developer loop",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        -6048,
        464
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "86aaa4ea-6e5a-427a-8d13-46622cce5bec",
      "name": "Extract and validate member data",
      "type": "n8n-nodes-base.code",
      "position": [
        -5712,
        352
      ],
      "parameters": {
        "jsCode": "/**\n * Team Member JSON Validation & Normalization\n */\n\nconst items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n  const data = item.json || {};\n\n  const notionPageId = data.id || '';\n  const fullName = (data.property_full_name || data.name || '').trim();\n  const slackUserId = (data.property_slack_user_id || '').trim();\n  const developerId = (data.property_developer_id || '').trim();\n  const teamRole = (data.property_team_role || '').trim();\n  const activeStatus = (data.property_active_status || '').trim();\n  const communicationTool = (data.property_client_communication || '').trim();\n\n  const errors = [];\n\n  if (!notionPageId) errors.push('Missing Notion Page ID');\n  if (!fullName) errors.push('Missing Full Name');\n  if (!slackUserId) errors.push('Missing Slack User ID');\n  if (!developerId) errors.push('Missing Developer ID');\n  if (!teamRole) errors.push('Missing Team Role');\n  if (!activeStatus) errors.push('Missing Active Status');\n\n  const isValid = errors.length === 0;\n\n  results.push({\n    json: {\n      notion_page_id: notionPageId,\n      full_name: fullName,\n      slack_user_id: slackUserId,\n      developer_id: developerId,\n      team_role: teamRole,\n      active_status: activeStatus,\n      client_communication: communicationTool || null,\n      leave_management: Boolean(data.property_leave_management),\n      leave_type: data.property_leave_type || null,\n      leave_start_date: data.property_leave_start_date || null,\n      leave_end_date: data.property_leave_end_date || null,\n      daily_standups: Boolean(data.property_daily_standups),\n      blockers_tracking: Boolean(data.property_blockers_tracking),\n      is_valid: isValid,\n      validation_errors: errors.length ? errors : null,\n      source: 'notion',\n      validated_at: new Date().toISOString()\n    }\n  });\n}\n\nreturn results;\n"
      },
      "typeVersion": 2
    },
    {
      "id": "425d4c33-df67-4b4a-a4c1-21b6b3f6efcf",
      "name": "Is valid member",
      "type": "n8n-nodes-base.if",
      "position": [
        -5440,
        352
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "45141cf2-e523-4074-b833-35f4829346c8",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.is_valid }}",
              "rightValue": "true"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "d1c62232-d310-49e5-984e-f6d35d5488f0",
      "name": "Check existing Redis session",
      "type": "n8n-nodes-base.redis",
      "position": [
        -5200,
        336
      ],
      "parameters": {
        "key": "={{ \"morning:standup:session:\" + $json.slack_user_id }}",
        "options": {},
        "operation": "get"
      },
      "typeVersion": 1
    },
    {
      "id": "f4da8526-ae46-4004-ad4b-ba8fba872e5a",
      "name": "Parse session state",
      "type": "n8n-nodes-base.code",
      "position": [
        -4944,
        336
      ],
      "parameters": {
        "jsCode": "const items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n  const redisValue = item.json.value;\n  let sessionExists = false;\n  let sessionData = null;\n  \n  if (redisValue && redisValue !== 'null' && redisValue !== '') {\n    try {\n      sessionData = JSON.parse(redisValue);\n      sessionExists = true;\n    } catch (e) {\n      sessionExists = false;\n    }\n  }\n  \n  results.push({\n    json: {\n      ...item.json,\n      session_exists: sessionExists,\n      session_data: sessionData,\n      needs_new_session: !sessionExists\n    }\n  });\n}\n\nreturn results;"
      },
      "typeVersion": 2
    },
    {
      "id": "6e2c790c-e4bc-4a9c-bcad-77b4202cac15",
      "name": "Needs new session",
      "type": "n8n-nodes-base.if",
      "position": [
        -4688,
        320
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "c3b9efe6-8860-4a54-81bf-b018cdf2f759",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.needs_new_session }}",
              "rightValue": "true"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "6966a3c3-bc2b-46ad-9f31-96e776b94a53",
      "name": "Create Notion session",
      "type": "n8n-nodes-base.notion",
      "position": [
        -4400,
        256
      ],
      "parameters": {
        "options": {},
        "resource": "databasePage",
        "databaseId": {
          "__rl": true,
          "mode": "list",
          "value": "2d4da36d-2b3d-80e1-b7c8-e3a96667c823",
          "cachedResultUrl": "https://www.notion.so/2d4da36d2b3d80e1b7c8e3a96667c823",
          "cachedResultName": "Conversation_State"
        },
        "propertiesUi": {
          "propertyValues": [
            {
              "key": "User_ID|title",
              "title": "={{ $('Is valid member').item.json.slack_user_id }}"
            },
            {
              "key": "Current_Question|number",
              "numberValue": 1
            },
            {
              "key": "Date|date",
              "date": "={{ $now.setZone('Asia/Kolkata').toFormat('M/d/yyyy') }}"
            },
            {
              "key": "Status|status",
              "statusValue": "In Progress"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "a4937e19-26c6-4e8a-930d-66af41f04dba",
      "name": "Prepare session data",
      "type": "n8n-nodes-base.code",
      "position": [
        -4144,
        256
      ],
      "parameters": {
        "jsCode": "/**\n * Prepare Redis session data with readiness flag\n */\n\nconst items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n  const data = item.json || {};\n\n  const slackUserId =\n    data.property_user_id ||\n    data.name ||\n    '';\n\n  const answers = [\n    data.property_answer_1 || '',\n    data.property_answer_2 || '',\n    data.property_answer_3 || '',\n    data.property_answer_4 || ''\n  ].filter(ans => ans !== '');\n\n  const isSessionReady = Boolean(slackUserId && data.id);\n\n  const sessionData = {\n    notion_page_id: data.id,\n    slack_user_id: slackUserId,\n    current_question: data.property_current_question ?? 0,\n    answers,\n    started_at: data.property_date?.start || new Date().toISOString(),\n    last_updated: new Date().toISOString(),\n    is_session_ready: isSessionReady\n  };\n\n  const redisKey = isSessionReady\n    ? `morning:standup:session:${slackUserId}`\n    : null;\n\n  const redisValue = isSessionReady\n    ? JSON.stringify(sessionData)\n    : null;\n\n  results.push({\n    json: {\n      session_data: sessionData,\n      redis_key: redisKey,\n      redis_value: redisValue,\n      redis_ttl: 86400,\n      can_write_redis: isSessionReady\n    }\n  });\n}\n\nreturn results;\n"
      },
      "typeVersion": 2
    },
    {
      "id": "0cd88a6f-bcf5-406a-bf2e-8ab7fcafe8d5",
      "name": "Session data ready",
      "type": "n8n-nodes-base.if",
      "position": [
        -3904,
        240
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "a188bc1f-ea74-4f52-82c8-393e8414c48c",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.can_write_redis }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "2d534d2a-b921-4f26-ac21-39bb38efbe1f",
      "name": "Save session to Redis",
      "type": "n8n-nodes-base.redis",
      "position": [
        -3648,
        192
      ],
      "parameters": {
        "key": "={{ $json.redis_key }}",
        "ttl": "={{ $json.redis_ttl }}",
        "value": "={{ $json.redis_value }}",
        "expire": true,
        "operation": "set"
      },
      "typeVersion": 1
    },
    {
      "id": "976a61ec-965a-440a-82f9-0467a8088f4c",
      "name": "Send standup message",
      "type": "n8n-nodes-base.slack",
      "position": [
        -3376,
        192
      ],
      "parameters": {
        "text": "=Good morning {{ $('Is valid member').item.json.full_name }}! \ud83d\udc4b\n\n\ud83d\udccb *Daily Standup - Question 1 of 4*\n\n*How are you feeling today?*",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $json.session_data.slack_user_id }}"
        },
        "otherOptions": {
          "includeLinkToWorkflow": false
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "ca0b2a68-fd2c-4bd3-b047-c965beb6ee52",
      "name": "Update Slack channel ID in Notion",
      "type": "n8n-nodes-base.notion",
      "position": [
        -3104,
        128
      ],
      "parameters": {
        "pageId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Developer loop').item.json.id }}"
        },
        "options": {},
        "resource": "databasePage",
        "operation": "update",
        "propertiesUi": {
          "propertyValues": [
            {
              "key": "Slack_Bot_Channel_ID|rich_text",
              "textContent": "={{ $json.channel }}"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "6346f77a-1041-4bc5-88e0-ba53d7cf8bc6",
      "name": "Continue to next (morning)",
      "type": "n8n-nodes-base.noOp",
      "position": [
        -2976,
        560
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "ea8dbfff-e1e1-48b2-89aa-3ac474554cc2",
      "name": "Trigger Group Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -6976,
        336
      ],
      "parameters": {
        "color": 7,
        "width": 328,
        "height": 336,
        "content": "## \u23f0 Trigger\nFires Mon\u2013Sat at 9:40 AM IST (`0 40 9 * * 1-6`). Kicks off the standup initiation flow for all active team members."
      },
      "typeVersion": 1
    },
    {
      "id": "6645947e-7806-4d7b-86e7-aec6a332b340",
      "name": "Main Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -7456,
        1312
      ],
      "parameters": {
        "color": 6,
        "width": 464,
        "height": 560,
        "content": "# \ud83c\udf19 Evening Standup Initiator\n\n## How it works\nFires Mon\u2013Sat at 6 PM IST. Fetches all active developers from Notion, loops through each one, checks Redis for a duplicate session (skips if today's session already exists), creates a Notion session record, stores state in Redis (TTL 24h), and sends Question 1 of 3 via Slack DM. All paths \u2014 skipped, invalid, or completed \u2014 return to the loop.\n\n## Setup steps\n1. Add your **Notion** credentials and verify `Team_Members` DB ID\n2. Verify `Conversation_State_Afternoon` Notion DB ID\n3. Add your **Redis** credentials (host, port, password)\n4. Add your **Slack** credentials with `chat:write` scope\n5. Confirm cron timezone is UTC (workflow fires at 12:30 UTC = 6 PM IST)\n6. Test manually with one developer before activating"
      },
      "typeVersion": 1
    },
    {
      "id": "4a794720-ee6a-40e5-bd87-d2adbf8f1594",
      "name": "Group: Trigger Fetch Gate",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -6912,
        1440
      ],
      "parameters": {
        "color": 7,
        "width": 1056,
        "height": 372,
        "content": "## \u23f0 Trigger, Fetch & Gate\nSchedule fires Mon\u2013Sat 6 PM IST. Fetches active members from Notion (`Daily_Standups = true`, `Active_Status = Active`). If no records returned, outputs `NO_DEVELOPERS` status and stops."
      },
      "typeVersion": 1
    },
    {
      "id": "105aa8a6-8215-41df-8048-a2252287ae9c",
      "name": "Group: Loop Validate",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -5856,
        1600
      ],
      "parameters": {
        "color": 7,
        "width": 816,
        "height": 276,
        "content": "## \ud83d\udd01 Developer Loop & Validation\nIterates through each member one at a time. Validates required fields (`slack_user_id`, `developer_id`, `full_name`, `team_role`). Invalid members are silently skipped via the false branch."
      },
      "typeVersion": 1
    },
    {
      "id": "b841778d-7663-48e0-91aa-9dabda821b0d",
      "name": "Group: Redis Check",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -4976,
        1584
      ],
      "parameters": {
        "color": 7,
        "width": 848,
        "height": 260,
        "content": "## \ud83d\udd34 Redis Session Check\nGETs `standup:evening:{slack_user_id}`. If a session exists with today's date, sets `skip: true` to prevent duplicate standups. Continues safely if Redis is unreachable (`continueOnFail: true`)."
      },
      "typeVersion": 1
    },
    {
      "id": "df73094b-61e2-41d6-9182-36f0a77c1a1a",
      "name": "Group: Session Create Store",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -4112,
        1456
      ],
      "parameters": {
        "color": 7,
        "width": 928,
        "height": 324,
        "content": "## \ud83d\udcdd Session Creation & Storage\nCreates a Notion record in `Conversation_State_Afternoon` (Question 1, In Progress). Builds Redis session object with answers, timestamps, and record ID. SETs key with 24h TTL."
      },
      "typeVersion": 1
    },
    {
      "id": "e32eaf2e-c587-4d12-8055-0137fbdc57bd",
      "name": "Group: Slack Log Continue",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3152,
        1296
      ],
      "parameters": {
        "color": 7,
        "width": 1168,
        "height": 996,
        "content": "## \ud83d\udcac Send, Log & Loop Back\nSends Question 1 of 3 via Slack DM (`chat:write`). Logs final status (`SUCCESS` or `SLACK_FAILED`). All paths \u2014 success, skipped, or failed \u2014 converge at `Continue to next`, which loops back to the iterator for the next developer."
      },
      "typeVersion": 1
    },
    {
      "id": "c3966ff3-dd43-45a3-a7ad-91c2e279edb6",
      "name": "Evening standup initiator trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -6848,
        1648
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 18 * * 1-6"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "a1dcd3e0-e402-440b-8d76-059f2e295672",
      "name": "Get active team members (evening)",
      "type": "n8n-nodes-base.notion",
      "position": [
        -6576,
        1648
      ],
      "parameters": {
        "filters": {
          "conditions": [
            {
              "key": "Daily_Standups|checkbox",
              "condition": "equals",
              "checkboxValue": true
            },
            {
              "key": "Active_Status|status",
              "condition": "equals",
              "statusValue": "Active"
            }
          ]
        },
        "options": {},
        "resource": "databasePage",
        "matchType": "allFilters",
        "operation": "getAll",
        "returnAll": true,
        "databaseId": {
          "__rl": true,
          "mode": "list",
          "value": "2d4da36d-2b3d-80ba-931c-fa5646433363",
          "cachedResultUrl": "https://www.notion.so/2d4da36d2b3d80ba931cfa5646433363",
          "cachedResultName": "Team_Members"
        },
        "filterType": "manual"
      },
      "typeVersion": 2.2
    },
    {
      "id": "63623162-b826-4913-8c74-42e49f269ec9",
      "name": "Check developers found",
      "type": "n8n-nodes-base.if",
      "position": [
        -6336,
        1632
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "has-developers",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.isEmpty() }}",
              "rightValue": 0
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "d61c8546-6257-49bb-ab43-be9862570360",
      "name": "No developers found",
      "type": "n8n-nodes-base.code",
      "position": [
        -6112,
        1536
      ],
      "parameters": {
        "jsCode": "const now = new Date();\n\nreturn {\n  status: 'NO_DEVELOPERS',\n  message: 'No active developers found for daily standup',\n  timestamp: now.toISOString(),\n  date: now.toISOString().split('T')[0]\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "41d9d62d-6903-4b7e-bcfa-ea6bb4f1f2f8",
      "name": "Process each developer",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        -5792,
        1696
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "63aa7f68-312d-476f-8f59-532126317494",
      "name": "Validate and prepare member data",
      "type": "n8n-nodes-base.code",
      "position": [
        -5536,
        1712
      ],
      "parameters": {
        "jsCode": "/**\n * Team Member JSON Validation & Normalization\n */\n\nconst items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n  const data = item.json || {};\n\n  const notionPageId = data.id || '';\n  const fullName = (data.property_full_name || data.name || '').trim();\n  const slackUserId = (data.property_slack_user_id || '').trim();\n  const developerId = (data.property_developer_id || '').trim();\n  const teamRole = (data.property_team_role || '').trim();\n  const activeStatus = (data.property_active_status || '').trim();\n  const communicationTool = (data.property_client_communication || '').trim();\n\n  const errors = [];\n\n  if (!notionPageId) errors.push('Missing Notion Page ID');\n  if (!fullName) errors.push('Missing Full Name');\n  if (!slackUserId) errors.push('Missing Slack User ID');\n  if (!developerId) errors.push('Missing Developer ID');\n  if (!teamRole) errors.push('Missing Team Role');\n  if (!activeStatus) errors.push('Missing Active Status');\n\n  const isValid = errors.length === 0;\n\n  results.push({\n    json: {\n      notion_page_id: notionPageId,\n      full_name: fullName,\n      slack_user_id: slackUserId,\n      developer_id: developerId,\n      team_role: teamRole,\n      active_status: activeStatus,\n      client_communication: communicationTool || null,\n      leave_management: Boolean(data.property_leave_management),\n      leave_type: data.property_leave_type || null,\n      leave_start_date: data.property_leave_start_date || null,\n      leave_end_date: data.property_leave_end_date || null,\n      daily_standups: Boolean(data.property_daily_standups),\n      blockers_tracking: Boolean(data.property_blockers_tracking),\n      is_valid: isValid,\n      validation_errors: errors.length ? errors : null,\n      source: 'notion',\n      validated_at: new Date().toISOString()\n    }\n  });\n}\n\nreturn results;\n"
      },
      "typeVersion": 2
    },
    {
      "id": "e4f2fa1e-1569-4b15-9bd9-272c9b94cfb5",
      "name": "Check if valid member (evening)",
      "type": "n8n-nodes-base.if",
      "position": [
        -5296,
        1696
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "is-valid",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.is_valid }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "7729190a-eda6-4380-8ffe-c1d5c30698f7",
      "name": "Check evening Redis session",
      "type": "n8n-nodes-base.redis",
      "position": [
        -4912,
        1680
      ],
      "parameters": {
        "key": "=standup:evening:{{ $json.slack_user_id }}",
        "options": {},
        "operation": "get",
        "propertyName": "redis_session"
      },
      "typeVersion": 1,
      "continueOnFail": true
    },
    {
      "id": "cd413cd2-60f1-4de8-8fa8-7a40cb381b0e",
      "name": "Check evening session status",
      "type": "n8n-nodes-base.code",
      "position": [
        -4640,
        1680
      ],
      "parameters": {
        "jsCode": "const devData = $('Check if valid member (evening)').first().json;\nconst redisInputs = $input.all();\n\nlet redisResult = '';\nfor (const item of redisInputs) {\n  if (item.json.redis_session !== undefined) {\n    redisResult = item.json.redis_session;\n    break;\n  }\n}\n\nlet sessionExists = false;\nif (redisResult && redisResult !== '') {\n  try {\n    const session = typeof redisResult === 'string' ? JSON.parse(redisResult) : redisResult;\n    const sessionDate = session.date || '';\n    const today = new Date().toISOString().split('T')[0];\n    \n    if (sessionDate === today) {\n      sessionExists = true;\n    }\n  } catch (error) {\n    console.log('Redis parse error:', error.message);\n  }\n}\n\nconst now = new Date();\n\nif (sessionExists) {\n  return {\n    skip: true,\n    skip_reason: 'SESSION_EXISTS',\n    slack_user_id: devData.slack_user_id,\n    developer_id: devData.developer_id,\n    full_name: devData.full_name,\n    timestamp: now.toISOString()\n  };\n}\n\nreturn {\n  skip: false,\n  slack_user_id: devData.slack_user_id,\n  developer_id: devData.developer_id,\n  full_name: devData.full_name,\n  client_name: devData.client_name || '',\n  team_role: devData.team_role || ''\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "30d95079-1c26-4295-a02f-1cefd6609724",
      "name": "Should proceed with evening session",
      "type": "n8n-nodes-base.if",
      "position": [
        -4384,
        1680
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "should-proceed",
              "operator": {
                "type": "boolean",
                "operation": "equals"
              },
              "leftValue": "={{ $json.skip }}",
              "rightValue": false
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "1041c488-c78c-4217-9c02-ff6e77f31f2c",
      "name": "Create evening Notion session",
      "type": "n8n-nodes-base.notion",
      "position": [
        -4048,
        1616
      ],
      "parameters": {
        "options": {},
        "resource": "databasePage",
        "databaseId": {
          "__rl": true,
          "mode": "list",
          "value": "2d4da36d-2b3d-806a-a528-f463dae04e54",
          "cachedResultUrl": "https://www.notion.so/2d4da36d2b3d806aa528f463dae04e54",
          "cachedResultName": "Conversation_State_Afternoon"
        },
        "propertiesUi": {
          "propertyValues": [
            {
              "key": "User_ID|title",
              "title": "={{ $json.slack_user_id }}"
            },
            {
              "key": "Current_Question|number",
              "numberValue": 1
            },
            {
              "key": "Date|date",
              "date": "={{ $now.setZone('Asia/Kolkata').toFormat('M/d/yyyy') }}"
            },
            {
              "key": "Status|status",
              "statusValue": "In Progress"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "75246922-3404-42fb-8a46-423b972d6f3a",
      "name": "Build evening session data",
      "type": "n8n-nodes-base.code",
      "position": [
        -3760,
        1600
      ],
      "parameters": {
        "jsCode": "const devData = $('Should proceed with evening session').first().json;\nconst airtableInputs = $input.all();\n\nlet airtableResult = null;\nfor (const item of airtableInputs) {\n  if (item.json.id) {\n    airtableResult = item.json;\n    break;\n  }\n}\n\nif (!airtableResult || !airtableResult.id) {\n  return {\n    skip: true,\n    skip_reason: 'AIRTABLE_FAILED',\n    error: 'Failed to create Airtable record',\n    slack_user_id: devData.slack_user_id,\n    developer_id: devData.developer_id,\n    full_name: devData.full_name\n  };\n}\n\nconst now = new Date();\nconst today = now.toISOString().split('T')[0];\n\nconst sessionData = {\n  user_id: devData.slack_user_id,\n  developer_id: devData.developer_id,\n  full_name: devData.full_name,\n  client_name: devData.client_name || '',\n  team_role: devData.team_role || '',\n  current_question: 1,\n  status: 'Active',\n  date: today,\n  started_at: now.toISOString(),\n  airtable_record_id: airtableResult.id,\n  answers: {\n    answer_1: null,\n    answer_2: null,\n    answer_3: null,\n    answer_4: null\n  }\n};\n\nreturn {\n  skip: false,\n  redis_key: `standup:evening:${devData.slack_user_id}`,\n  redis_value: JSON.stringify(sessionData),\n  session_data: sessionData,\n  slack_user_id: devData.slack_user_id,\n  full_name: devData.full_name,\n  developer_id: devData.developer_id,\n  airtable_id: airtableResult.id\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "177f9493-44cf-4e03-927e-3b23211fa843",
      "name": "Evening session ready",
      "type": "n8n-nodes-base.if",
      "position": [
        -3440,
        1600
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "session-ready",
              "operator": {
                "type": "boolean",
                "operation": "equals"
              },
              "leftValue": "={{ $json.skip }}",
              "rightValue": false
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "00406a2d-922a-440f-b29f-d05686392b6c",
      "name": "Store evening session in Redis",
      "type": "n8n-nodes-base.redis",
      "position": [
        -3088,
        1488
      ],
      "parameters": {
        "key": "={{ $json.redis_key }}",
        "ttl": 86400,
        "value": "={{ $json.redis_value }}",
        "expire": true,
        "operation": "set"
      },
      "typeVersion": 1,
      "continueOnFail": true
    },
    {
      "id": "828b422e-c5fe-42e4-a888-79a865e1835e",
      "name": "Send evening standup message",
      "type": "n8n-nodes-base.slack",
      "position": [
        -2816,
        1472
      ],
      "parameters": {
        "text": "=Good Evening {{ $json.full_name }} \u2600\ufe0f\n\n\ud83d\udccb *Daily Standup \u2013 Question 1 of 3*\n\n*What have you completed today?*\n\nPlease share your accomplishments from today.",
        "user": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $json.session_data.user_id }}"
        },
        "select": "user",
        "otherOptions": {
          "includeLinkToWorkflow": false
        }
      },
      "typeVersion": 2.2,
      "continueOnFail": true
    },
    {
      "id": "3b67d075-85b1-4f3d-bd8a-2bf8c808d2e8",
      "name": "Log final result",
      "type": "n8n-nodes-base.code",
      "position": [
        -2576,
        1472
      ],
      "parameters": {
        "jsCode": "const sessionData = $('Store evening session in Redis').first().json;\nconst slackInputs = $input.all();\n\nlet slackResult = null;\nfor (const item of slackInputs) {\n  if (item.json.ok !== undefined || item.json.error !== undefined) {\n    slackResult = item.json;\n    break;\n  }\n}\n\nlet slackSuccess = true;\nif (slackResult !== null) {\n  if (slackResult.ok === false || String(JSON.stringify(slackResult)).toLowerCase().includes('error')) {\n    slackSuccess = false;\n  }\n}\n\nconst now = new Date();\n\nreturn {\n  status: slackSuccess ? 'SUCCESS' : 'SLACK_FAILED',\n  slack_success: slackSuccess,\n  redis_success: true,\n  slack_user_id: sessionData.slack_user_id,\n  developer_id: sessionData.developer_id,\n  full_name: sessionData.full_name,\n  airtable_id: sessionData.airtable_id,\n  timestamp: now.toISOString()\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "f3bf25e0-83c1-4c72-b24b-c5402a1954f5",
      "name": "Continue to next (evening)",
      "type": "n8n-nodes-base.noOp",
      "position": [
        -2176,
        2064
      ],
      "parameters": {},
      "typeVersion": 1
    }
  ],
  "connections": {
    "Merge Data": {
      "main": [
        [
          {
            "node": "Build evening summary message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Developer loop": {
      "main": [
        [],
        [
          {
            "node": "Extract and validate member data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is valid member": {
      "main": [
        [
          {
            "node": "Check existing Redis session",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Continue to next (morning)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log final result": {
      "main": [
        [
          {
            "node": "Continue to next (evening)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Needs new session": {
      "main": [
        [
          {
            "node": "Create Notion session",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Continue to next (morning)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge morning data": {
      "main": [
        [
          {
            "node": "Build morning summary message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Session data ready": {
      "main": [
        [
          {
            "node": "Save session to Redis",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Continue to next (morning)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse session state": {
      "main": [
        [
          {
            "node": "Needs new session",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Active Members": {
      "main": [
        [
          {
            "node": "Merge Data",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Prepare session data": {
      "main": [
        [
          {
            "node": "Session data ready",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send standup message": {
      "main": [
        [
          {
            "node": "Continue to next (morning)",
            "type": "main",
            "index": 0
          },
          {
            "node": "Update Slack channel ID in Notion",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Notion session": {
      "main": [
        [
          {
            "node": "Prepare session data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Evening session ready": {
      "main": [
        [
          {
            "node": "Store evening session in Redis",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Continue to next (evening)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save session to Redis": {
      "main": [
        [
          {
            "node": "Send standup message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check developers found": {
      "main": [
        [
          {
            "node": "No developers found",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Process each developer",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check if members found": {
      "main": [
        [
          {
            "node": "Developer loop",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Evening Standups": {
      "main": [
        [
          {
            "node": "Merge Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process each developer": {
      "main": [
        [],
        [
          {
            "node": "Validate and prepare member data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get active team members": {
      "main": [
        [
          {
            "node": "Check if members found",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Evening schedule trigger": {
      "main": [
        [
          {
            "node": "Fetch Active Members",
            "type": "main",
            "index": 0
          },
          {
            "node": "Fetch Evening Standups",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Morning schedule trigger": {
      "main": [
        [
          {
            "node": "Fetch team members (morning)",
            "type": "main",
            "index": 0
          },
          {
            "node": "Fetch today's morning standups",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build evening session data": {
      "main": [
        [
          {
            "node": "Evening session ready",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Continue to next (evening)": {
      "main": [
        [
          {
            "node": "Process each developer",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Continue to next (morning)": {
      "main": [
        [
          {
            "node": "Developer loop",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check evening Redis session": {
      "main": [
        [
          {
            "node": "Check evening session status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check evening session status": {
      "main": [
        [
          {
            "node": "Should proceed with evening session",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check existing Redis session": {
      "main": [
        [
          {
            "node": "Parse session state",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch team members (morning)": {
      "main": [
        [
          {
            "node": "Merge morning data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send evening standup message": {
      "main": [
        [
          {
            "node": "Log final result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build evening summary message": {
      "main": [
        [
          {
            "node": "Send to Admin Channel Evening",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build morning summary message": {
      "main": [
        [
          {
            "node": "Send to Admin Channel",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create evening Notion session": {
      "main": [
        [
          {
            "node": "Build evening session data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch today's morning standups": {
      "main": [
        [
          {
            "node": "Merge morning data",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Store evening session in Redis": {
      "main": [
        [
          {
            "node": "Send evening standup message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check if valid member (evening)": {
      "main": [
        [
          {
            "node": "Check evening Redis session",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Continue to next (evening)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract and validate member data": {
      "main": [
        [
          {
            "node": "Is valid member",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate and prepare member data": {
      "main": [
        [
          {
            "node": "Check if valid member (evening)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Evening standup initiator trigger": {
      "main": [
        [
          {
            "node": "Get active team members (evening)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get active team members (evening)": {
      "main": [
        [
          {
            "node": "Check developers found",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Morning standup initiator trigger": {
      "main": [
        [
          {
            "node": "Get active team members",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Should proceed with evening session": {
      "main": [
        [
          {
            "node": "Create evening Notion session",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Continue to next (evening)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}