{
  "name": "Daily Churn Retention Actions - V9",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 2 * * *"
            }
          ]
        }
      },
      "id": "c192ba3b-7071-4d41-8ace-2c09a23329e0",
      "name": "Cron Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.1,
      "position": [
        -2320,
        864
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT *\nFROM churn_predictions\nWHERE send_action_flag = TRUE\nORDER BY churn_probability DESC\nLIMIT 10;",
        "options": {}
      },
      "id": "ed3ea43b-1303-4496-aa87-15c46c035128",
      "name": "Read Predictions",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        -2096,
        848
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "url": "http://127.0.0.1:62881/explainability/latest",
        "options": {}
      },
      "id": "675c0510-626b-40ca-8c38-83b1554d356e",
      "name": "Read SHAP Explainability",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.1,
      "position": [
        -2096,
        1024
      ]
    },
    {
      "parameters": {
        "mode": "combine",
        "fieldsToMatchString": "customer_unique_id",
        "options": {}
      },
      "id": "2b7c74f4-238e-460e-b4aa-c059d3107244",
      "name": "Merge Data",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3,
      "position": [
        -1664,
        944
      ]
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 1
                },
                "conditions": [
                  {
                    "leftValue": "={{ String($json.risk_tier || '').toUpperCase() }}",
                    "rightValue": "HIGH",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "high_risk"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 1
                },
                "conditions": [
                  {
                    "leftValue": "={{ String($json.risk_tier || '').toUpperCase() }}",
                    "rightValue": "MEDIUM",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "medium_risk"
            }
          ]
        },
        "options": {}
      },
      "id": "88f168f3-a1f6-4377-9cbc-901891761fc5",
      "name": "Risk Switch",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3,
      "notes": "Portfolio senior uplift 2026-05-31: this switch now routes on canonical risk_tier emitted by the scoring lineage instead of recomputing tiers from hardcoded churn_probability thresholds. This removes silent scoring-vs-orchestration drift in the demo baseline.",
      "position": [
        -1456,
        944
      ]
    },
    {
      "parameters": {},
      "id": "4f36637c-379e-4f72-8eb3-abbe0b54735a",
      "name": "Merge Risks",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3,
      "position": [
        -1216,
        944
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://127.0.0.1:62881/coupons/generate",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "customer_id",
              "value": "={{ $json.customer_unique_id }}"
            },
            {
              "name": "risk_level",
              "value": "={{ $json.risk_tier }}"
            },
            {
              "name": "discount_pct",
              "value": 20
            }
          ]
        },
        "options": {}
      },
      "id": "ce1ac59e-22ba-400d-968f-ad62eab957b3",
      "name": "Generate Coupon",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.1,
      "position": [
        -992,
        944
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 1
          },
          "conditions": [
            {
              "id": "check-coupon-code",
              "leftValue": "={{ $json.coupon_code }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "notEmpty"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "27b1e9ae-51d1-4e05-8fd1-3b9fbf6e85ff",
      "name": "Validate Pre-Send",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.1,
      "position": [
        -768,
        944
      ],
      "notes": "Validates that coupon_code, email, customer_id and first_name exist before firing any channel. TRUE branch \u2192 send. FALSE branch \u2192 log skipped record."
    },
    {
      "parameters": {
        "fromEmail": "architect.openclaw@gmail.com",
        "toEmail": "={{ $json.email }}",
        "subject": "Sentimos sua falta, {{ $json.first_name }}! \ud83c\udf81",
        "html": "=<p>Ol\u00e1, {{ $json.first_name }}!</p>\n<p>Sentimos a sua falta no VivaMarket. Preparamos uma oferta exclusiva para voc\u00ea:</p>\n<p style=\"font-size:1.4em; font-weight:bold;\">\ud83c\udf81 Use o cupom <span style=\"color:#E53935;\">{{ $json.coupon_code }}</span> e ganhe {{ $json.discount_pct }}% de desconto na sua pr\u00f3xima compra.</p>\n<p>O cupom \u00e9 v\u00e1lido por 7 dias. N\u00e3o perca!</p>\n<p>At\u00e9 breve,<br>Equipe VivaMarket</p>",
        "options": {}
      },
      "id": "b86cf74d-c128-4320-abc3-09f14b076012",
      "name": "Send Email",
      "type": "n8n-nodes-base.emailSend",
      "typeVersion": 2.1,
      "position": [
        -544,
        768
      ],
      "credentials": {
        "smtp": {
          "name": "<your credential>"
        }
      },
      "disabled": true
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://onesignal.com/api/v1/notifications",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Basic {{ $vars.ONE_SIGNAL_API_KEY }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"app_id\": \"b466045e-3354-4304-b99b-ada211caaee4\",\n  \"include_external_user_ids\": [\"{{ $json.customer_unique_id }}\"],\n  \"contents\": {\n    \"pt\": \"Temos uma oferta especial para voc\u00ea! Use o cupom {{ $json.coupon_code }} e ganhe {{ $json.discount_pct }}% de desconto.\",\n    \"en\": \"We have a special offer for you! Use coupon {{ $json.coupon_code }} and get {{ $json.discount_pct }}% off.\"\n  },\n  \"headings\": {\n    \"pt\": \"\ud83c\udf81 Oferta exclusiva VivaMarket\",\n    \"en\": \"\ud83c\udf81 Exclusive VivaMarket offer\"\n  }\n}",
        "options": {}
      },
      "id": "153edce9-a905-4b44-99f1-43af1d8cd8d0",
      "name": "Send Push",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.1,
      "position": [
        -544,
        944
      ],
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "notes": "FIX v6: OneSignal credentials now read from n8n Variables (ONE_SIGNAL_API_KEY, ONE_SIGNAL_APP_ID). $credentials.x syntax is not supported in HTTP Request nodes."
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "INSERT INTO retention_actions_skipped (customer_id, reason, skipped_at, churn_probability)\nVALUES ($1, 'missing_coupon_or_email', NOW(), $2);",
        "options": {
          "queryReplacement": "={{ [$json.customer_id, $json.churn_probability] }}"
        }
      },
      "id": "39392540-3d42-48dd-8953-48fd32ab601d",
      "name": "Log Skipped",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        -544,
        1120
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "onError": "continueErrorOutput",
      "notes": "FALSE branch from Validate Pre-Send. FIX v6: Added queryReplacement binding for $1=customer_id, $2=churn_probability. Success output is terminal \u2014 NOT connected to Error Handler."
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "INSERT INTO retention_actions (customer_id, risk_level, action_type, coupon_code, executed_at, churn_probability, send_action_flag)\nVALUES ($1, $2, $3, $4, NOW(), $5, TRUE);",
        "options": {
          "queryReplacement": "={{ [$json.customer_id, $json.risk_level, $json.action_type ?? 'email_push', $json.coupon_code, $json.churn_probability] }}"
        }
      },
      "id": "041b8e46-8790-4bff-b8f4-24c934e43d4c",
      "name": "Log Actions",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        -320,
        864
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "onError": "continueErrorOutput",
      "notes": "FIX v6: Added queryReplacement binding for all 5 parameters ($1=customer_id, $2=risk_level, $3=action_type, $4=coupon_code, $5=churn_probability)."
    },
    {
      "parameters": {
        "fieldToSplitOut": "records",
        "options": {}
      },
      "type": "n8n-nodes-base.splitOut",
      "typeVersion": 1,
      "position": [
        -1888,
        1024
      ],
      "id": "6f6c54a8-0b17-4d4e-bd2d-7c7e76716daf",
      "name": "Split Out"
    }
  ],
  "connections": {
    "Cron Trigger": {
      "main": [
        [
          {
            "node": "Read Predictions",
            "type": "main",
            "index": 0
          },
          {
            "node": "Read SHAP Explainability",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Predictions": {
      "main": [
        [
          {
            "node": "Merge Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read SHAP Explainability": {
      "main": [
        [
          {
            "node": "Split Out",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Data": {
      "main": [
        [
          {
            "node": "Risk Switch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Risk Switch": {
      "main": [
        [
          {
            "node": "Merge Risks",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Merge Risks",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Merge Risks": {
      "main": [
        [
          {
            "node": "Generate Coupon",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Coupon": {
      "main": [
        [
          {
            "node": "Validate Pre-Send",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Pre-Send": {
      "main": [
        [
          {
            "node": "Send Email",
            "type": "main",
            "index": 0
          },
          {
            "node": "Send Push",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Log Skipped",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Email": {
      "main": [
        [
          {
            "node": "Log Actions",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Push": {
      "main": [
        [
          {
            "node": "Log Actions",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Out": {
      "main": [
        [
          {
            "node": "Merge Data",
            "type": "main",
            "index": 1
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "binaryMode": "separate",
    "availableInMCP": false,
    "timeSavedMode": "fixed",
    "errorWorkflow": "5W6h4T8ck3na51md",
    "callerPolicy": "workflowsFromSameOwner"
  },
  "versionId": "559d07a2-3b99-4c80-a5e8-ebc32b6818a8",
  "id": "F6MT21Rkgr4BZvCT",
  "tags": []
}