{
  "nodes": [
    {
      "id": "48a01936-9cff-4d1c-b81e-5f08da188ce1",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1456,
        -288
      ],
      "parameters": {
        "width": 480,
        "height": 896,
        "content": "## Generate and email daily AI-powered HR changes digest from SAP SuccessFactors EC\n\n### How it works\n\n1. Triggers daily or manually for execution.\n2. Configures base URL and email settings for SAP SF.\n3. Fetches HR change data from SuccessFactors.\n4. Aggregates data and checks for errors or changes.\n5. Processes results with AI and sends digest or error emails.\n\n### Setup steps\n\n- [ ] Ensure SAP SuccessFactors API credentials are set for data fetching.\n- [ ] Configure email settings including recipient for digest and error reporting.\n- [ ] Schedule the n8n workflow at an appropriate time, typically early morning.\n\n### Customization\n\nAdjust the schedule time and email recipient as per organizational needs.\n\nChange from Basic Auth to OAuth 2.0 SAML Bearer Token by combining other templates from me."
      },
      "typeVersion": 1
    },
    {
      "id": "c8fd1e51-977c-44b0-b019-77591e9d420a",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -896,
        0
      ],
      "parameters": {
        "color": 7,
        "height": 528,
        "content": "## Execution trigger\n\nTriggers the workflow execution either daily or manually."
      },
      "typeVersion": 1
    },
    {
      "id": "61e3d4b1-262f-4bca-a8f5-44b80f33e7ae",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -624,
        144
      ],
      "parameters": {
        "color": 7,
        "width": 448,
        "height": 304,
        "content": "## Setup configuration\n\nSets up configuration parameters like base URL and email."
      },
      "typeVersion": 1
    },
    {
      "id": "d1a41ff3-2e5c-4dd1-b7e1-b9ffb4be55e2",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -112,
        160
      ],
      "parameters": {
        "color": 7,
        "width": 976,
        "height": 272,
        "content": "## Fetch HR data\n\nFetches hire, termination, move, and address data from SuccessFactors."
      },
      "typeVersion": 1
    },
    {
      "id": "0f969662-0265-4314-bad3-3d5ab81db7f7",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        928,
        160
      ],
      "parameters": {
        "color": 7,
        "width": 464,
        "height": 272,
        "content": "## Data aggregation\n\nAggregates fetched data for consistency check."
      },
      "typeVersion": 1
    },
    {
      "id": "28aa36e8-4800-4324-b348-29d6766d393e",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1456,
        16
      ],
      "parameters": {
        "color": 7,
        "width": 448,
        "height": 432,
        "content": "## Change detection\n\nDetects changes in aggregated data and branches logic."
      },
      "typeVersion": 1
    },
    {
      "id": "2c11617e-aff7-4070-b01e-b7cafc684aa7",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1712,
        -288
      ],
      "parameters": {
        "color": 7,
        "width": 1232,
        "height": 272,
        "content": "## AI processing and digest email\n\nProcesses data with AI and prepares email if changes detected."
      },
      "typeVersion": 1
    },
    {
      "id": "98952fc7-c75f-47c4-a24f-8673a45c017a",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1456,
        480
      ],
      "parameters": {
        "color": 7,
        "width": 448,
        "height": 304,
        "content": "## Error handling\n\nFormats and sends an error report if a critical failure occurs."
      },
      "typeVersion": 1
    },
    {
      "id": "node-schedule",
      "name": "When 06:30AM Daily",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -848,
        160
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "30 6 * * *"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "node-manual",
      "name": "When Triggered Manually",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        -848,
        368
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "node-config",
      "name": "Set Config Variables",
      "type": "n8n-nodes-base.set",
      "position": [
        -576,
        272
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "cfg-url",
              "name": "SF_BASE_URL",
              "type": "string",
              "value": "https://apiXX.successfactors.com/odata/v2"
            },
            {
              "id": "cfg-to",
              "name": "EMAIL_TO",
              "type": "string",
              "value": "user@example.com"
            },
            {
              "id": "cfg-lang",
              "name": "DIGEST_LANGUAGE",
              "type": "string",
              "value": "German"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "node-daterange",
      "name": "Calculate Date Range",
      "type": "n8n-nodes-base.code",
      "position": [
        -320,
        272
      ],
      "parameters": {
        "jsCode": "// Reads SF_BASE_URL from the Configuration node.\n// Builds OData filter URLs for the last 24 hours.\n\nconst config = $('Set Config Variables').first().json;\nconst SF_BASE_URL = config.SF_BASE_URL;\n\nconst now       = new Date();\nconst yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000);\n\n// SF OData V2 expects datetime'YYYY-MM-DDTHH:mm:ss' (no ms, no Z)\nconst fmt = (d) => d.toISOString().slice(0, 19);\nconst fromDate = fmt(yesterday);\nconst toDate   = fmt(now);\n\nconst displayDate = now.toLocaleDateString('de-DE', {\n  day: '2-digit', month: '2-digit', year: 'numeric'\n});\n\n// ---- Hires: EmpJob startDate in time window ----\nconst hiresUrl =\n  SF_BASE_URL + '/EmpJob' +\n  \"?$filter=startDate ge datetime'\" + fromDate + \"' and startDate lt datetime'\" + toDate + \"'\" +\n  '&$select=userId,startDate,position,department,location,managerId' +\n  '&$expand=userNav' +\n  '&$top=200&$format=json';\n\n// ---- Terminations: EmpEmployment endDate in time window ----\nconst terminationsUrl =\n  SF_BASE_URL + '/EmpEmployment' +\n  \"?$filter=endDate ge datetime'\" + fromDate + \"' and endDate lt datetime'\" + toDate + \"'\" +\n  '&$select=userId,startDate,endDate' +\n  '&$expand=userNav' +\n  '&$top=200&$format=json';\n\n// ---- Internal moves: EmpJob lastModifiedDateTime in window, not a new hire ----\nconst movesUrl =\n  SF_BASE_URL + '/EmpJob' +\n  \"?$filter=lastModifiedDateTime ge datetime'\" + fromDate + \"' and startDate lt datetime'\" + fromDate + \"'\" +\n  '&$select=userId,startDate,position,department,location,managerId,lastModifiedDateTime' +\n  '&$expand=userNav' +\n  '&$top=200&$format=json';\n\n// ---- Address changes: PerAddressDEFLT ----\n// Uses personIdExternal (not userId) + personNav for name resolution\nconst addressesUrl =\n  SF_BASE_URL + '/PerAddressDEFLT' +\n  \"?$filter=lastModifiedDateTime ge datetime'\" + fromDate + \"'\" +\n  '&$select=personIdExternal,city,country,lastModifiedDateTime' +\n  '&$expand=personNav' +\n  '&$top=200&$format=json';\n\nreturn [{\n  json: {\n    fromDate,\n    toDate,\n    displayDate,\n    SF_BASE_URL,\n    hiresUrl,\n    terminationsUrl,\n    movesUrl,\n    addressesUrl\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "node-hires",
      "name": "Fetch New Hires",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -64,
        272
      ],
      "parameters": {
        "url": "={{ $('Calculate Date Range').first().json.hiresUrl }}",
        "options": {},
        "sendHeaders": true,
        "authentication": "genericCredentialType",
        "genericAuthType": "httpBasicAuth",
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "application/json"
            }
          ]
        }
      },
      "credentials": {},
      "typeVersion": 4.3
    },
    {
      "id": "node-terms",
      "name": "Fetch Terminated Employees",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        192,
        272
      ],
      "parameters": {
        "url": "={{ $('Calculate Date Range').first().json.terminationsUrl }}",
        "options": {},
        "sendHeaders": true,
        "authentication": "genericCredentialType",
        "genericAuthType": "httpBasicAuth",
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "application/json"
            }
          ]
        }
      },
      "credentials": {},
      "typeVersion": 4.3
    },
    {
      "id": "node-moves",
      "name": "Fetch Internal Transfers",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        448,
        272
      ],
      "parameters": {
        "url": "={{ $('Calculate Date Range').first().json.movesUrl }}",
        "options": {},
        "sendHeaders": true,
        "authentication": "genericCredentialType",
        "genericAuthType": "httpBasicAuth",
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "application/json"
            }
          ]
        }
      },
      "credentials": {},
      "typeVersion": 4.3
    },
    {
      "id": "node-addr",
      "name": "Fetch Address Changes",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        720,
        272
      ],
      "parameters": {
        "url": "={{ $('Calculate Date Range').first().json.addressesUrl }}",
        "options": {},
        "sendHeaders": true,
        "authentication": "genericCredentialType",
        "genericAuthType": "httpBasicAuth",
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "application/json"
            }
          ]
        }
      },
      "credentials": {},
      "typeVersion": 4.3
    },
    {
      "id": "node-aggregate",
      "name": "Combine Data from Sources",
      "type": "n8n-nodes-base.code",
      "position": [
        976,
        272
      ],
      "parameters": {
        "jsCode": "// Aggregates results from all 4 SF queries.\n// Note: PerAddressDEFLT uses personIdExternal + personNav\n//       (not userId/userNav like EmpJob/EmpEmployment).\n\nconst dateInfo = $('Calculate Date Range').first().json;\n\nfunction safeResults(nodeName) {\n  try {\n    const data = $(nodeName).first().json;\n    if (data && data.error) {\n      const msg = (data.error.message || data.error.description || JSON.stringify(data.error));\n      return { data: [], error: nodeName + ': ' + msg };\n    }\n    return { data: (data.d && data.d.results) || [], error: null };\n  } catch (e) {\n    return { data: [], error: nodeName + ': ' + e.message };\n  }\n}\n\nconst hiresResult = safeResults('Fetch Hires');\nconst termsResult = safeResults('Fetch Terminations');\nconst movesResult = safeResults('Fetch Moves');\nconst addrResult  = safeResults('Fetch Addresses');\n\nfunction nameFromUserNav(r) {\n  if (r && r.userNav) {\n    return ((r.userNav.firstName || '') + ' ' + (r.userNav.lastName || '')).trim() || r.userId;\n  }\n  return r && r.userId ? r.userId : '(unknown)';\n}\n\nfunction nameFromPersonNav(r) {\n  if (r && r.personNav) {\n    const nav = r.personNav;\n    if (nav.firstName || nav.lastName) {\n      return ((nav.firstName || '') + ' ' + (nav.lastName || '')).trim();\n    }\n  }\n  return r && r.personIdExternal ? r.personIdExternal : '(unknown)';\n}\n\nconst hires = hiresResult.data.map(function(r) {\n  return {\n    name:       nameFromUserNav(r),\n    empId:      (r.userNav && r.userNav.empId) || r.userId || '\u2014',\n    startDate:  r.startDate  || '\u2014',\n    position:   r.position   || '\u2014',\n    department: r.department || '\u2014',\n    location:   r.location   || '\u2014'\n  };\n});\n\nconst terminations = termsResult.data.map(function(r) {\n  return {\n    name:            nameFromUserNav(r),\n    empId:           (r.userNav && r.userNav.empId) || r.userId || '\u2014',\n    terminationDate: r.endDate   || '\u2014',\n    startDate:       r.startDate || '\u2014'\n  };\n});\n\nconst hireUserIds = new Set(hiresResult.data.map(function(r) { return r.userId; }));\nconst internalMoves = movesResult.data\n  .filter(function(r) { return !hireUserIds.has(r.userId); })\n  .map(function(r) {\n    return {\n      name:          nameFromUserNav(r),\n      empId:         (r.userNav && r.userNav.empId) || r.userId || '\u2014',\n      newPosition:   r.position   || '\u2014',\n      newDepartment: r.department || '\u2014',\n      newLocation:   r.location   || '\u2014',\n      newManager:    r.managerId  || '\u2014',\n      changeDate:    r.lastModifiedDateTime || '\u2014'\n    };\n  });\n\nconst addressChanges = addrResult.data.map(function(r) {\n  return {\n    name:       nameFromPersonNav(r),\n    empId:      r.personIdExternal || '\u2014',\n    city:       r.city    || '\u2014',\n    country:    r.country || '\u2014',\n    changeDate: r.lastModifiedDateTime || '\u2014'\n  };\n});\n\nconst errors = [\n  hiresResult.error,\n  termsResult.error,\n  movesResult.error,\n  addrResult.error\n].filter(Boolean);\n\nconst criticalError = (errors.length === 4);\nconst totalChanges  = hires.length + terminations.length + internalMoves.length + addressChanges.length;\n\nreturn [{\n  json: {\n    displayDate:   dateInfo.displayDate,\n    fromDate:      dateInfo.fromDate,\n    toDate:        dateInfo.toDate,\n    criticalError,\n    errors,\n    counts: {\n      hires:          hires.length,\n      terminations:   terminations.length,\n      internalMoves:  internalMoves.length,\n      addressChanges: addressChanges.length\n    },\n    totalChanges,\n    hires,\n    terminations,\n    internalMoves,\n    addressChanges\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "node-criticalerror",
      "name": "Check for Critical Errors",
      "type": "n8n-nodes-base.if",
      "position": [
        1248,
        272
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "pcd-cond-01",
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ $json.errors.length }}",
              "rightValue": 3
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "node-haschanges",
      "name": "Check for HR Changes",
      "type": "n8n-nodes-base.if",
      "position": [
        1504,
        144
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "pcd-cond-02",
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ $json.totalChanges }}",
              "rightValue": 0
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "node-aiprompt",
      "name": "Create AI Prompt",
      "type": "n8n-nodes-base.code",
      "position": [
        1760,
        -176
      ],
      "parameters": {
        "jsCode": "// Builds system and user prompts for the OpenAI call.\n// DIGEST_LANGUAGE controls the response language (e.g. German, English, French).\n\nconst d    = $input.first().json;\nconst lang = $('Set Config Variables').first().json.DIGEST_LANGUAGE || 'German';\n\nconst systemPrompt =\n  'You are an HR assistant summarizing daily people changes in neutral, professional HR language. ' +\n  'Respond in ' + lang + '. Maximum 400 words. Use clear bullet points for each person.';\n\nconst errorNote = (d.errors && d.errors.length > 0)\n  ? ('\\n\\nNOTE: Data could not be retrieved for the following categories (please verify manually):\\n- ' + d.errors.join('\\n- '))\n  : '';\n\nconst changeData = JSON.stringify({\n  hires:          d.hires,\n  terminations:   d.terminations,\n  internalMoves:  d.internalMoves,\n  addressChanges: d.addressChanges\n}, null, 2);\n\nconst userPrompt =\n  'Create a Daily People-Changes Digest for ' + d.displayDate + '.\\n\\n' +\n  'Use exactly this structure:\\n' +\n  '# Daily People-Changes Digest \u2013 ' + d.displayDate + '\\n\\n' +\n  '## Hires (' + d.counts.hires + ')\\n' +\n  '## Terminations (' + d.counts.terminations + ')\\n' +\n  '## Internal Moves (' + d.counts.internalMoves + ')\\n' +\n  '## Address Changes (' + d.counts.addressChanges + ')\\n\\n' +\n  'Rules:\\n' +\n  '- Empty categories: write \"No changes.\"\\n' +\n  '- Last line: \"Total: ' + d.totalChanges + ' change(s) today.\"\\n' +\n  '- Show employee ID (empId) in parentheses after each name.\\n\\n' +\n  'Data:\\n' + changeData + errorNote;\n\nreturn [{ json: { ...d, systemPrompt, userPrompt } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "node-openai",
      "name": "Request AI Completion",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2016,
        -176
      ],
      "parameters": {
        "url": "https://api.openai.com/v1/chat/completions",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ JSON.stringify({ model: 'gpt-4o-mini', messages: [ { role: 'system', content: $('Create AI Prompt').first().json.systemPrompt }, { role: 'user', content: $('Create AI Prompt').first().json.userPrompt } ], max_tokens: 1200, temperature: 0.3 }) }}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "credentials": {},
      "typeVersion": 4.3
    },
    {
      "id": "node-prepareemail",
      "name": "Draft Digest Email",
      "type": "n8n-nodes-base.code",
      "position": [
        2288,
        -176
      ],
      "parameters": {
        "jsCode": "// Reads EMAIL_TO from the Configuration node.\n\nconst EMAIL_TO  = $('Set Config Variables').first().json.EMAIL_TO;\nconst aiJson    = $input.first().json;\nconst promptCtx = $('Create AI Prompt').first().json;\n\nconst digestText =\n  (aiJson.choices && aiJson.choices[0] && aiJson.choices[0].message && aiJson.choices[0].message.content)\n  || '[ERROR: No AI response received]';\n\nreturn [{\n  json: {\n    to:          EMAIL_TO,\n    subject:     'Daily People-Changes Digest - ' + promptCtx.displayDate,\n    text:        digestText,\n    displayDate: promptCtx.displayDate,\n    counts:      promptCtx.counts,\n    totalChanges: promptCtx.totalChanges\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "node-digestemail",
      "name": "Email Digest via Gmail",
      "type": "n8n-nodes-base.gmail",
      "position": [
        2544,
        -176
      ],
      "parameters": {
        "sendTo": "={{ $json.to }}",
        "message": "={{ $json.text }}",
        "options": {},
        "subject": "={{ $json.subject }}",
        "emailType": "text"
      },
      "credentials": {},
      "typeVersion": 2.1
    },
    {
      "id": "node-logsummary",
      "name": "Log Digest Summary",
      "type": "n8n-nodes-base.code",
      "position": [
        2800,
        -176
      ],
      "parameters": {
        "jsCode": "const d = $('Draft Digest Email').first().json;\nconst c = d.counts || {};\nconst msg =\n  '[SFEC Digest ' + d.displayDate + '] ' +\n  'Hires: '   + (c.hires          || 0) + ' | ' +\n  'Terms: '   + (c.terminations   || 0) + ' | ' +\n  'Moves: '   + (c.internalMoves  || 0) + ' | ' +\n  'Addr: '    + (c.addressChanges || 0) + ' | ' +\n  'Total: '   + (d.totalChanges   || 0);\nconsole.log(msg);\nreturn [{ json: { status: 'digest_sent', date: d.displayDate, counts: c, totalChanges: d.totalChanges, log: msg } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "node-nochanges",
      "name": "Log Unchanged Status",
      "type": "n8n-nodes-base.code",
      "position": [
        1760,
        272
      ],
      "parameters": {
        "jsCode": "// No changes detected \u2014 no digest sent.\n// To send a \"no changes\" notification, replace this node with an email node.\nconst d = $input.first().json;\nconst msg = '[SFEC Digest ' + d.displayDate + '] No people changes today \u2014 digest skipped.';\nconsole.log(msg);\nreturn [{ json: { status: 'no_changes', date: d.displayDate, log: msg } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "node-errorreport",
      "name": "Create Error Email Content",
      "type": "n8n-nodes-base.code",
      "position": [
        1504,
        608
      ],
      "parameters": {
        "jsCode": "// Reads EMAIL_TO from the Configuration node.\n// Triggered when all 4 SF API calls fail simultaneously.\n\nconst EMAIL_TO = $('Set Config Variables').first().json.EMAIL_TO;\nconst d        = $input.first().json;\nconst errorList = (d.errors || ['Unknown error']).join('\\n- ');\n\nconst text =\n  'The SFEC Daily People-Changes Digest could NOT be created.\\n\\n' +\n  'Date:      ' + d.displayDate + '\\n' +\n  'Timeframe: ' + d.fromDate + '  \u2192  ' + d.toDate + '\\n\\n' +\n  'Errors:\\n- ' + errorList + '\\n\\n' +\n  'Please check the SuccessFactors connection and credentials.\\n\\n' +\n  '\u2014 n8n SFEC Digest Automation';\n\nreturn [{\n  json: {\n    to:      EMAIL_TO,\n    subject: '[ERROR] SFEC Daily Digest - ' + d.displayDate,\n    text:    text\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "node-erroremail",
      "name": "Email Error Report via Gmail",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1760,
        608
      ],
      "parameters": {
        "sendTo": "={{ $json.to }}",
        "message": "={{ $json.text }}",
        "options": {},
        "subject": "={{ $json.subject }}",
        "emailType": "text"
      },
      "credentials": {},
      "typeVersion": 2.1
    }
  ],
  "connections": {
    "Fetch New Hires": {
      "main": [
        [
          {
            "node": "Fetch Terminated Employees",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create AI Prompt": {
      "main": [
        [
          {
            "node": "Request AI Completion",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Draft Digest Email": {
      "main": [
        [
          {
            "node": "Email Digest via Gmail",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When 06:30AM Daily": {
      "main": [
        [
          {
            "node": "Set Config Variables",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate Date Range": {
      "main": [
        [
          {
            "node": "Fetch New Hires",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check for HR Changes": {
      "main": [
        [
          {
            "node": "Create AI Prompt",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Log Unchanged Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Config Variables": {
      "main": [
        [
          {
            "node": "Calculate Date Range",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Address Changes": {
      "main": [
        [
          {
            "node": "Combine Data from Sources",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Request AI Completion": {
      "main": [
        [
          {
            "node": "Draft Digest Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Email Digest via Gmail": {
      "main": [
        [
          {
            "node": "Log Digest Summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When Triggered Manually": {
      "main": [
        [
          {
            "node": "Set Config Variables",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Internal Transfers": {
      "main": [
        [
          {
            "node": "Fetch Address Changes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check for Critical Errors": {
      "main": [
        [
          {
            "node": "Create Error Email Content",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Check for HR Changes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Combine Data from Sources": {
      "main": [
        [
          {
            "node": "Check for Critical Errors",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Error Email Content": {
      "main": [
        [
          {
            "node": "Email Error Report via Gmail",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Terminated Employees": {
      "main": [
        [
          {
            "node": "Fetch Internal Transfers",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}