AutomationFlowsWeb Scraping › Scheduled Marche AI Pipeline with HTTP Requests

Scheduled Marche AI Pipeline with HTTP Requests

Original n8n title: Marche AI - Pipeline

Marche AI - Pipeline. Uses httpRequest, emailSend. Scheduled trigger; 20 nodes.

Cron / scheduled trigger★★★★☆ complexity20 nodesHTTP RequestEmail Send
Web Scraping Trigger: Cron / scheduled Nodes: 20 Complexity: ★★★★☆ Added:

This workflow follows the Emailsend → HTTP Request recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "name": "Marche AI - Pipeline",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 9 * * 1-5"
            }
          ]
        }
      },
      "id": "node-cron",
      "name": "Cron 9h",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        0,
        300
      ]
    },
    {
      "parameters": {
        "url": "={{ ($env.API_BASE_URL || 'http://127.0.0.1:8011') + '/health' }}",
        "options": {
          "timeout": 10000
        }
      },
      "id": "node-health",
      "name": "Health",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        240,
        380
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 5000
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "cond-health",
              "leftValue": "={{ $json.status }}",
              "rightValue": "ok",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        }
      },
      "id": "node-check-health",
      "name": "API OK ?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        460,
        380
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ ($env.API_BASE_URL || 'http://127.0.0.1:8011') + '/scrape' }}",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"fast_mode\": true,\n  \"concurrency\": 5,\n  \"webhook_url\": \"{{ $execution.resumeUrl }}\"\n}",
        "options": {
          "timeout": 10000
        }
      },
      "id": "node-scrape",
      "name": "Scrape",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        680,
        320
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ ($env.API_BASE_URL || 'http://127.0.0.1:8011') + '/pipeline/filter-it' }}",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ { \"csv_path\": ($json.last_csv || \"\") } }}",
        "options": {
          "timeout": 60000
        }
      },
      "id": "node-filter-it",
      "name": "Filtrer IT",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1700,
        420
      ]
    },
    {
      "parameters": {
        "url": "={{ ($env.API_BASE_URL || 'http://127.0.0.1:8011') + '/scrape/status' }}",
        "options": {
          "timeout": 10000
        }
      },
      "id": "node-scrape-status",
      "name": "Scrape Status",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1500,
        420
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 2000
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "cond-scrape-not-running",
              "leftValue": "={{ $json.running }}",
              "rightValue": false,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            },
            {
              "id": "cond-has-last-csv",
              "leftValue": "={{ $json.last_csv }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "notEmpty"
              }
            }
          ],
          "combinator": "and"
        }
      },
      "id": "node-scrape-done",
      "name": "Scrape done ?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1600,
        420
      ]
    },
    {
      "parameters": {
        "resumeOption": "afterTimeInterval",
        "amount": 10,
        "unit": "seconds"
      },
      "id": "node-wait-10s",
      "name": "Wait 10s",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        1600,
        540
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "cond-relevant",
              "leftValue": "={{ $json.total_it }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "gt"
              }
            }
          ],
          "combinator": "and"
        }
      },
      "id": "node-has-results",
      "name": "IT trouvees ?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1920,
        420
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ ($env.API_BASE_URL || 'http://127.0.0.1:8011') + '/pipeline/run-lite-sync' }}",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "{\n  \"generate_dossiers\": false,\n  \"use_rag\": false,\n  \"enrich_cps\": false,\n  \"all_priorities\": true\n}",
        "options": {
          "timeout": 300000
        }
      },
      "id": "node-pipeline",
      "name": "Pipeline",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2140,
        360
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ ($env.API_BASE_URL || 'http://127.0.0.1:8011') + '/pipeline/generate-dossiers-async' }}",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "{\n  \"use_rag\": false,\n  \"convert_pdf\": true,\n  \"rate_limit\": 3.0,\n  \"min_priority\": \"WARM\",\n  \"include_liked\": true,\n  \"only_active\": true,\n  \"max_consultations\": 120,\n  \"skip_existing\": true\n}",
        "options": {
          "timeout": 60000
        }
      },
      "id": "node-gen-dossiers",
      "name": "Dossiers RAG",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2360,
        360
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "set-summary",
              "name": "summary",
              "value": "=Pipeline termine\n\nScraping\n  Consultations brutes : {{ $(\"Pipeline\").item.json.summary.total }}\n  Consultations qualifiees (HOT/WARM/COLD) : {{ $(\"Pipeline\").item.json.summary.relevant }}\n\nRepartition par domaine\n  AI             : {{ $(\"Filtrer IT\").item.json.par_domaine.AI ?? 0 }}\n  Data           : {{ $(\"Filtrer IT\").item.json.par_domaine.Data ?? 0 }}\n  BI             : {{ $(\"Filtrer IT\").item.json.par_domaine.BI ?? 0 }}\n  Dev            : {{ $(\"Filtrer IT\").item.json.par_domaine.Dev ?? 0 }}\n  Cloud          : {{ $(\"Filtrer IT\").item.json.par_domaine.Cloud ?? 0 }}\n  Cybersecurity  : {{ $(\"Filtrer IT\").item.json.par_domaine.Cybersecurity ?? 0 }}\n\nDossiers\n  Generation : lancee en arriere-plan (async)\n\nDate : {{ $now.setZone('Africa/Casablanca').toFormat('dd/LL/yyyy HH:mm') }}",
              "type": "string"
            },
            {
              "id": "set-summary-html",
              "name": "summary_html",
              "value": "=('\\n' +'<div style=\"font-family:Arial,Helvetica,sans-serif;line-height:1.4;color:#0b1f3b;\">' +'<strong>AI Procurement Intelligence Report</strong><br/>' +'Run: ' + ($now.setZone ? $now.setZone('Africa/Casablanca').toFormat('dd/LL/yyyy HH:mm') : '') +'</div>')",
              "type": "string"
            },
            {
              "id": "set-total-it",
              "name": "total_it",
              "value": "={{ $(\"Pipeline\").item.json.summary.relevant }}",
              "type": "number"
            },
            {
              "id": "set-total-brutes",
              "name": "total_brutes",
              "value": "={{ $(\"Pipeline\").item.json.summary.total }}",
              "type": "number"
            },
            {
              "id": "set-email-html",
              "name": "email_html",
              "value": "=(() => {\n  const esc = (s) => String(s ?? '')\n    .replace(/&/g, '&amp;')\n    .replace(/</g, '&lt;')\n    .replace(/>/g, '&gt;');\n  const escAttr = (s) => esc(s).replace(/\"/g, '&quot;');\n\n  const fmtMoney = (n) => {\n    const v = Number(n);\n    if (!Number.isFinite(v) || v <= 0) return 'N/A';\n    const s = v.toFixed(2);\n    const parts = s.split('.');\n    const t = parts[0];\n    let out = '';\n    for (let i = t.length; i > 0; i -= 3) {\n      const start = Math.max(0, i - 3);\n      out = t.slice(start, i) + (out ? ' ' + out : '');\n    }\n    return out + ',' + parts[1] + ' DH';\n  };\n\n  const fmtDate = (d) => {\n    if (!d) return 'N/A';\n    const dt = new Date(d);\n    if (isNaN(dt)) return esc(d);\n    const dd = String(dt.getDate()).padStart(2, '0');\n    const mm = String(dt.getMonth() + 1).padStart(2, '0');\n    return dd + '/' + mm + '/' + dt.getFullYear();\n  };\n\n  const badgeHtml = (lvl) => {\n    const L = String(lvl || '').toUpperCase();\n    const label = (L === 'HOT' || L === 'WARM' || L === 'COLD') ? L : 'COLD';\n    const bg = (label === 'HOT') ? '#b42318' : (label === 'WARM') ? '#f97316' : '#6b7280';\n    return '<span style=\"display:inline-block;padding:3px 10px;border-radius:999px;font-weight:700;font-size:11px;line-height:14px;color:#fff;background:' + bg + ';\">' + esc(label) + '</span>';\n  };\n\n  // Luxon DateTime in n8n: $now\n  let runStr = '';\n  try {\n    runStr = $now.setZone('Africa/Casablanca').toFormat('dd/LL/yyyy HH:mm');\n  } catch (e) {\n    runStr = (new Date()).toLocaleString();\n  }\n\n  const raw = $(\"Top Opportunities\").item.json.relevant_consultations || [];\n  const today = new Date();\n  today.setHours(0, 0, 0, 0);\n\n  const active = raw.filter((o) => {\n    if (!o || !o.deadline) return true;\n    const d = new Date(o.deadline);\n    if (isNaN(d)) return true;\n    d.setHours(0, 0, 0, 0);\n    return d >= today;\n  });\n\n  const totalQualified = active.length;\n  const hotCount = active.filter((o) => String((o && o.priority) || '').toUpperCase() === 'HOT').length;\n\n  const deadlines = active\n    .map((o) => o && o.deadline)\n    .filter(Boolean)\n    .map((d) => new Date(d))\n    .filter((d) => !isNaN(d));\n\n  let nextDeadline = 'N/A';\n  if (deadlines.length) {\n    const min = new Date(Math.min.apply(null, deadlines));\n    nextDeadline = fmtDate(min.toISOString());\n  }\n\n  const urgent = active.filter((o) => {\n    if (!o || !o.deadline) return false;\n    const d = new Date(o.deadline);\n    if (isNaN(d)) return false;\n    d.setHours(0, 0, 0, 0);\n    const diffDays = Math.round((d.getTime() - today.getTime()) / 86400000);\n    return diffDays >= 0 && diffDays < 7;\n  }).slice(0, 8);\n\n  const top = active.slice(0, 10);\n\n  const dashUrl = ($env && $env.DASHBOARD_URL) ? String($env.DASHBOARD_URL) : 'http://localhost:5173';\n\n  let rows = '';\n  if (!top.length) {\n    rows = '<tr><td colspan=\"6\" style=\"padding:12px 10px;border-top:1px solid #eef2f7;font-family:Arial,Helvetica,sans-serif;font-size:12px;color:#5a6b82;\">Aucune opportunite qualifiee active.</td></tr>';\n  } else {\n    for (let i = 0; i < top.length; i++) {\n      const o = top[i] || {};\n      const bg = (i % 2 === 0) ? '#ffffff' : '#fbfcfe';\n      const ref = esc(o.reference || o.id || 'N/A');\n      const org = esc(o.acheteur || o.organization || 'N/A');\n      const budget = esc(fmtMoney(o.budget));\n      const deadline = esc(fmtDate(o.deadline));\n      const level = badgeHtml(o.priority);\n      const url = String(o.url || '').trim();\n      const urlHtml = url\n        ? ('<a href=\"' + escAttr(url) + '\" target=\"_blank\" style=\"color:#1e3a8a;text-decoration:none;font-weight:700;\">Voir</a>')\n        : '<span style=\"color:#9aa6b2;\">N/A</span>';\n\n      rows += ''\n        + '<tr style=\"background:' + bg + ';\">'\n        + '<td style=\"padding:10px;border-top:1px solid #eef2f7;font-family:Arial,Helvetica,sans-serif;font-size:12px;color:#0b1f3b;\">' + ref + '</td>'\n        + '<td style=\"padding:10px;border-top:1px solid #eef2f7;font-family:Arial,Helvetica,sans-serif;font-size:12px;color:#0b1f3b;\">' + org + '</td>'\n        + '<td style=\"padding:10px;border-top:1px solid #eef2f7;font-family:Arial,Helvetica,sans-serif;font-size:12px;color:#0b1f3b;white-space:nowrap;\">' + budget + '</td>'\n        + '<td style=\"padding:10px;border-top:1px solid #eef2f7;font-family:Arial,Helvetica,sans-serif;font-size:12px;color:#0b1f3b;white-space:nowrap;\">' + deadline + '</td>'\n        + '<td style=\"padding:10px;border-top:1px solid #eef2f7;font-family:Arial,Helvetica,sans-serif;font-size:12px;color:#0b1f3b;\">' + level + '</td>'\n        + '<td style=\"padding:10px;border-top:1px solid #eef2f7;font-family:Arial,Helvetica,sans-serif;font-size:12px;\">' + urlHtml + '</td>'\n        + '</tr>';\n    }\n  }\n\n  let urgentBlock = '';\n  if (urgent.length) {\n    let items = '';\n    for (let i = 0; i < urgent.length; i++) {\n      const o = urgent[i] || {};\n      items += '<div style=\"margin:0 0 6px 0;\">&#8226; <strong>'\n        + esc(o.reference || o.id || 'N/A')\n        + '</strong> &#8212; '\n        + esc(o.acheteur || o.organization || 'N/A')\n        + ' <span style=\"color:#9a3a3a;\">(' + esc(fmtDate(o.deadline)) + ')</span></div>';\n    }\n\n    urgentBlock = ''\n      + '<div style=\"margin-top:18px;border:1px solid #ffe1e1;background:#fff5f5;border-radius:12px;padding:12px;\">'\n      + '<div style=\"font-family:Arial,Helvetica,sans-serif;font-weight:800;color:#8b1d1d;font-size:13px;\">Deadlines urgentes (&lt; 7 jours)</div>'\n      + '<div style=\"font-family:Arial,Helvetica,sans-serif;color:#6b2b2b;font-size:12px;margin-top:8px;\">'\n      + items\n      + '</div>'\n      + '</div>';\n  }\n\n  const html = ''\n    + '<!doctype html>'\n    + '<html lang=\"fr\" xmlns=\"http://www.w3.org/1999/xhtml\">'\n    + '<head>'\n    + '  <meta charset=\"utf-8\" />'\n    + '  <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\" />'\n    + '  <meta http-equiv=\"x-ua-compatible\" content=\"ie=edge\" />'\n    + '  <title>AI Procurement Intelligence</title>'\n    + '</head>'\n    + '<body style=\"margin:0;padding:0;background-color:#f3f5f8;\">'\n    + '  <table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"100%\" style=\"background-color:#f3f5f8;\">'\n    + '    <tr>'\n    + '      <td align=\"center\" style=\"padding:24px 12px;\">'\n    + '        <table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"600\" style=\"width:600px;max-width:600px;background:#ffffff;border:1px solid #e6eaf0;border-radius:16px;overflow:hidden;box-shadow:0 10px 30px rgba(15,23,42,0.08);\">'\n\n    + '          <tr>'\n    + '            <td style=\"background:#0b1f3b;background-image:linear-gradient(135deg,#0b1f3b 0%,#1e3a8a 100%);padding:22px;\">'\n    + '              <table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\">'\n    + '                <tr>'\n    + '                  <td>'\n    + '                    <div style=\"font-family:Arial,Helvetica,sans-serif;color:#ffffff;font-size:18px;font-weight:800;letter-spacing:.2px;\">AI Procurement Intelligence</div>'\n    + '                    <div style=\"font-family:Arial,Helvetica,sans-serif;color:#c9d4e5;font-size:13px;margin-top:6px;\">Automated IT Tender Monitoring</div>'\n    + '                    <div style=\"font-family:Arial,Helvetica,sans-serif;color:#c9d4e5;font-size:12px;margin-top:10px;\">Run: <strong>' + esc(runStr) + '</strong></div>'\n    + '                  </td>'\n    + '                  <td align=\"right\" valign=\"top\">'\n    + '                    <span style=\"display:inline-block;background:rgba(255,255,255,.12);border:1px solid rgba(255,255,255,.18);color:#ffffff;font-family:Arial,Helvetica,sans-serif;font-size:11px;font-weight:800;letter-spacing:.6px;padding:6px 10px;border-radius:999px;\">AUTO REPORT</span>'\n    + '                  </td>'\n    + '                </tr>'\n    + '              </table>'\n    + '            </td>'\n    + '          </tr>'\n\n    + '          <tr>'\n    + '            <td style=\"padding:18px;\">'\n\n    + '              <table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\">'\n    + '                <tr>'\n    + '                  <td style=\"padding:6px;\">'\n    + '                    <div style=\"border:1px solid #e7ecf3;border-radius:12px;background:#f8fafc;padding:14px;font-family:Arial,Helvetica,sans-serif;\">'\n    + '                      <div style=\"font-size:11px;color:#5a6b82;text-transform:uppercase;letter-spacing:.5px;\">Total Opportunities (qualifiees)</div>'\n    + '                      <div style=\"font-size:22px;color:#0b1f3b;font-weight:800;margin-top:6px;\">' + esc(totalQualified) + '</div>'\n    + '                    </div>'\n    + '                  </td>'\n    + '                  <td style=\"padding:6px;\">'\n    + '                    <div style=\"border:1px solid #e7ecf3;border-radius:12px;background:#f8fafc;padding:14px;font-family:Arial,Helvetica,sans-serif;\">'\n    + '                      <div style=\"font-size:11px;color:#5a6b82;text-transform:uppercase;letter-spacing:.5px;\">Opportunites IT</div>'\n    + '                      <div style=\"font-size:22px;color:#0b1f3b;font-weight:800;margin-top:6px;\">' + esc(totalQualified) + '</div>'\n    + '                    </div>'\n    + '                  </td>'\n    + '                  <td style=\"padding:6px;\">'\n    + '                    <div style=\"border:1px solid #ffe1e1;border-radius:12px;background:#fff5f5;padding:14px;font-family:Arial,Helvetica,sans-serif;\">'\n    + '                      <div style=\"font-size:11px;color:#8b4b4b;text-transform:uppercase;letter-spacing:.5px;\">HOT</div>'\n    + '                      <div style=\"font-size:22px;color:#b42318;font-weight:800;margin-top:6px;\">' + esc(hotCount) + '</div>'\n    + '                    </div>'\n    + '                  </td>'\n    + '                </tr>'\n    + '              </table>'\n\n    + '              <table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"margin-top:8px;\">'\n    + '                <tr>'\n    + '                  <td colspan=\"2\" style=\"padding:6px;\">'\n    + '                    <div style=\"border:1px solid #ffe8d2;border-radius:12px;background:#fff7ed;padding:14px;font-family:Arial,Helvetica,sans-serif;\">'\n    + '                      <div style=\"font-size:11px;color:#7a532e;text-transform:uppercase;letter-spacing:.5px;\">Next Deadline</div>'\n    + '                      <div style=\"font-size:16px;color:#0b1f3b;font-weight:800;margin-top:6px;\">' + esc(nextDeadline) + '</div>'\n    + '                    </div>'\n    + '                  </td>'\n    + '                </tr>'\n    + '              </table>'\n\n    + '              <div style=\"margin-top:18px;font-family:Arial,Helvetica,sans-serif;font-size:14px;font-weight:800;color:#0b1f3b;\">Top Opportunities</div>'\n    + '              <div style=\"font-family:Arial,Helvetica,sans-serif;font-size:12px;color:#5a6b82;margin-top:4px;\">Top 10 opportunites qualifiees (deadline en cours).</div>'\n\n    + '              <table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"margin-top:10px;border:1px solid #e6eaf0;border-radius:12px;overflow:hidden;border-collapse:separate;\">'\n    + '                <tr>'\n    + '                  <td style=\"background:#0f2a52;color:#fff;font-family:Arial,Helvetica,sans-serif;font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.4px;padding:10px;\">Reference</td>'\n    + '                  <td style=\"background:#0f2a52;color:#fff;font-family:Arial,Helvetica,sans-serif;font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.4px;padding:10px;\">Organisme</td>'\n    + '                  <td style=\"background:#0f2a52;color:#fff;font-family:Arial,Helvetica,sans-serif;font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.4px;padding:10px;\">Budget</td>'\n    + '                  <td style=\"background:#0f2a52;color:#fff;font-family:Arial,Helvetica,sans-serif;font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.4px;padding:10px;\">Deadline</td>'\n    + '                  <td style=\"background:#0f2a52;color:#fff;font-family:Arial,Helvetica,sans-serif;font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.4px;padding:10px;\">Niveau</td>'\n    + '                  <td style=\"background:#0f2a52;color:#fff;font-family:Arial,Helvetica,sans-serif;font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.4px;padding:10px;\">URL</td>'\n    + '                </tr>'\n    + rows\n    + '              </table>'\n\n    + urgentBlock\n\n    + '              <div style=\"margin-top:18px;text-align:center;\">'\n    + '                <a href=\"' + escAttr(dashUrl) + '\" target=\"_blank\" style=\"display:inline-block;background:#f97316;color:#ffffff;text-decoration:none;font-family:Arial,Helvetica,sans-serif;font-size:14px;line-height:46px;font-weight:800;padding:0 22px;border-radius:12px;border:1px solid #f97316;\">Voir le Dashboard</a>'\n    + '              </div>'\n\n    + '              <div style=\"margin-top:14px;font-family:Arial,Helvetica,sans-serif;font-size:11px;line-height:16px;color:#7a8aa2;text-align:center;\">'\n    + '                Rapport genere automatiquement par AI Monitoring Pipeline'\n    + '              </div>'\n\n    + '            </td>'\n    + '          </tr>'\n\n    + '        </table>'\n    + '      </td>'\n    + '    </tr>'\n    + '  </table>'\n    + '</body>'\n    + '</html>';\n\n  return html;\n})()",
              "type": "string"
            }
          ]
        }
      },
      "id": "node-summary",
      "name": "Resume",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        2580,
        360
      ]
    },
    {
      "parameters": {
        "fromEmail": "Marche AI - Veille IT <dohasafri7@gmail.com>",
        "toEmail": "doha.safri@emsi-edu.ma",
        "subject": "=Rapport Veille IT - {{ $json.total_qualified_active ?? 0 }} opportunites ({{ $now.setZone('Africa/Casablanca').toFormat('dd/LL/yyyy HH:mm') }})",
        "emailType": "html",
        "html": "={{ $json.email_html }}",
        "text": "={{ $json.summary || \"AI Procurement Intelligence Report\" }}",
        "message": "={{ $json.email_html }}"
      },
      "id": "node-email",
      "name": "Rapport",
      "type": "n8n-nodes-base.emailSend",
      "typeVersion": 2.1,
      "position": [
        2800,
        360
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "set-no-results",
              "name": "message",
              "value": "=0 IT.\\n\\nFiltrer IT.total_it={{ $(\"Filtrer IT\").item.json.total_it }}\\nFiltrer IT.message={{ $(\"Filtrer IT\").item.json.message ?? '' }}\\nFiltrer IT.csv_source={{ $(\"Filtrer IT\").item.json.csv_source ?? '' }}\\nScrape Status.last_csv={{ $(\"Scrape Status\").item.json.last_csv ?? '' }}\\nScrape Status.running={{ $(\"Scrape Status\").item.json.running ?? '' }}",
              "type": "string"
            }
          ]
        }
      },
      "id": "node-no-results",
      "name": "0 IT",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        2140,
        520
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "set-err-msg",
              "name": "error_message",
              "value": "=Erreur scraping : {{ $(\"Wait Webhook\").item.json.body.scraping.error }}\\nVerifier les logs API.",
              "type": "string"
            }
          ]
        }
      },
      "id": "node-scrape-fail",
      "name": "Log Erreur",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1700,
        540
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "set-api-down",
              "name": "error_message",
              "value": "API non disponible. Lancer : python -m api.main",
              "type": "string"
            }
          ]
        }
      },
      "id": "node-api-down",
      "name": "API Down",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        680,
        480
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ ($env.API_BASE_URL || 'http://127.0.0.1:8011') + '/pipeline/score' }}",
        "responseFormat": "json",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ { \"csv_path\": ($json.last_csv || \"\"), \"require_valid_deadline\": false } }}",
        "options": {
          "timeout": 180000,
          "fullResponse": false
        }
      },
      "id": "node-top-opportunities",
      "name": "Top Opportunities",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2470,
        360
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Build a premium HTML email from the latest scored opportunities.\n// Runs once per incoming item (expects /pipeline/score output shape).\n\nconst raw = Array.isArray($json.relevant_consultations) ? $json.relevant_consultations : [];\n\nconst dedupeByKey = (arr) => {\n  const byId = new Map();\n  for (const o of (Array.isArray(arr) ? arr : [])) {\n    const key = String((o && (o.reference || o.id)) || \"\").trim();\n    if (!key) continue;\n    if (!byId.has(key)) byId.set(key, o);\n  }\n  return Array.from(byId.values());\n};\nconst items = dedupeByKey(raw);\n\nconst esc = (s) => String(s ?? '')\n  .replace(/&/g, '&amp;')\n  .replace(/</g, '&lt;')\n  .replace(/>/g, '&gt;');\nconst escAttr = (s) => esc(s).replace(/\\\"/g, '&quot;');\n\nconst fmtMoney = (n) => {\n  const v = Number(n);\n  if (!Number.isFinite(v) || v <= 0) return 'N/A';\n  const s = v.toFixed(2);\n  const parts = s.split('.');\n  const t = parts[0];\n  let out = '';\n  for (let i = t.length; i > 0; i -= 3) {\n    const start = Math.max(0, i - 3);\n    out = t.slice(start, i) + (out ? ' ' + out : '');\n  }\n  return out + ',' + parts[1] + ' DH';\n};\n\nconst parseDeadlineDate = (value) => {\n  if (!value) return null;\n  if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value;\n  const v = String(value).trim();\n  if (!v) return null;\n  const fr = v.match(/^(\\d{2})\\/(\\d{2})\\/(\\d{4})(?:\\s+(\\d{2}):(\\d{2}))?$/);\n  if (fr) {\n    const [, dd, mm, yyyy, hh = \"00\", min = \"00\"] = fr;\n    const d = new Date(Number(yyyy), Number(mm) - 1, Number(dd), Number(hh), Number(min));\n    return Number.isNaN(d.getTime()) ? null : d;\n  }\n  if (/^\\d{4}-\\d{2}-\\d{2}/.test(v)) {\n    const d = new Date(v);\n    return Number.isNaN(d.getTime()) ? null : d;\n  }\n  const d = new Date(v);\n  return Number.isNaN(d.getTime()) ? null : d;\n};\n\nconst toDeadlineComparableDate = (value) => {\n  const v = value == null ? \"\" : String(value).trim();\n  const d = parseDeadlineDate(value);\n  if (!d) return null;\n  const isDateOnly =\n    /^(\\d{2})\\/(\\d{2})\\/(\\d{4})$/.test(v) ||\n    /^\\d{4}-\\d{2}-\\d{2}$/.test(v) ||\n    /^\\d{4}-\\d{2}-\\d{2}T00:00(?::00(?:\\.\\d{1,3})?)?(?:Z)?$/.test(v);\n  if (!isDateOnly) return d;\n  return new Date(d.getFullYear(), d.getMonth(), d.getDate(), 23, 59, 59, 999);\n};\n\nconst fmtDate = (d) => {\n  const dt = (d instanceof Date) ? d : parseDeadlineDate(d);\n  if (!dt || Number.isNaN(dt.getTime())) return \"N/A\";\n  const dd = String(dt.getDate()).padStart(2, \"0\");\n  const mm = String(dt.getMonth() + 1).padStart(2, \"0\");\n  return dd + \"/\" + mm + \"/\" + dt.getFullYear();\n};\n\nconst badgeHtml = (lvl) => {\n  const L = String(lvl || '').toUpperCase();\n  const label = (L === 'HOT' || L === 'WARM' || L === 'COLD') ? L : 'COLD';\n  const bg = (label === 'HOT') ? '#b42318' : (label === 'WARM') ? '#f97316' : '#6b7280';\n  return '<span style=\"display:inline-block;padding:3px 10px;border-radius:999px;font-weight:700;font-size:11px;line-height:14px;color:#fff;background:' + bg + ';\">' + esc(label) + '</span>';\n};\n\n// Casablanca time string (works even if container is in UTC)\nlet runStr;\ntry {\n  runStr = new Date().toLocaleString('fr-FR', { timeZone: 'Africa/Casablanca' });\n} catch (e) {\n  runStr = new Date().toLocaleString();\n}\n\nconst today = new Date();\ntoday.setHours(0, 0, 0, 0);\n\n// Same rules as Dashboard.jsx: require a parseable deadline; compare using toDeadlineComparableDate.\nconst active = items.filter((o) => {\n  const dt = toDeadlineComparableDate(o && o.deadline);\n  if (!dt) return false;\n  return dt >= today;\n});\n\nconst totalQualifiedActive = active.length;\nconst hotCount = active.filter((o) => String((o && o.priority) || '').toUpperCase() === 'HOT').length;\nconst deadlines = active.map((o) => toDeadlineComparableDate(o && o.deadline)).filter(Boolean);\nlet nextDeadline = 'N/A';\nif (deadlines.length) {\n  deadlines.sort((a, b) => a.getTime() - b.getTime());\n  nextDeadline = fmtDate(deadlines[0]);\n}\n\nconst urgent = active\n  .filter((o) => {\n    const cmp = toDeadlineComparableDate(o && o.deadline);\n    if (!cmp) return false;\n    const deadlineDay = new Date(cmp.getFullYear(), cmp.getMonth(), cmp.getDate());\n    const diffDays = Math.round((deadlineDay.getTime() - today.getTime()) / 86400000);\n    return diffDays >= 0 && diffDays < 7;\n  })\n  .slice(0, 8);\n\nconst top = active.slice(0, 10);\nconst dashUrl = (process.env.DASHBOARD_URL && String(process.env.DASHBOARD_URL)) || 'http://localhost:5173';\n\nlet rows = '';\nif (!top.length) {\n  rows = '<tr><td colspan=\"6\" style=\"padding:12px 10px;border-top:1px solid #eef2f7;font-family:Arial,Helvetica,sans-serif;font-size:12px;color:#5a6b82;\">Aucune opportunite qualifiee active.</td></tr>';\n} else {\n  for (let i = 0; i < top.length; i++) {\n    const o = top[i] || {};\n    const bg = (i % 2 === 0) ? '#ffffff' : '#fbfcfe';\n    const ref = esc(o.reference || o.id || 'N/A');\n    const org = esc(o.acheteur || o.organization || 'N/A');\n    const budget = esc(fmtMoney(o.budget));\n    const deadline = esc(fmtDate(o.deadline));\n    const level = badgeHtml(o.priority);\n    const url = String(o.url || '').trim();\n    const urlHtml = url\n      ? ('<a href=\"' + escAttr(url) + '\" target=\"_blank\" style=\"color:#1e3a8a;text-decoration:none;font-weight:700;\">Voir</a>')\n      : '<span style=\"color:#9aa6b2;\">N/A</span>';\n\n    rows += ''\n      + '<tr style=\"background:' + bg + ';\">'\n      + '<td style=\"padding:10px;border-top:1px solid #eef2f7;font-family:Arial,Helvetica,sans-serif;font-size:12px;color:#0b1f3b;\">' + ref + '</td>'\n      + '<td style=\"padding:10px;border-top:1px solid #eef2f7;font-family:Arial,Helvetica,sans-serif;font-size:12px;color:#0b1f3b;\">' + org + '</td>'\n      + '<td style=\"padding:10px;border-top:1px solid #eef2f7;font-family:Arial,Helvetica,sans-serif;font-size:12px;color:#0b1f3b;white-space:nowrap;\">' + budget + '</td>'\n      + '<td style=\"padding:10px;border-top:1px solid #eef2f7;font-family:Arial,Helvetica,sans-serif;font-size:12px;color:#0b1f3b;white-space:nowrap;\">' + deadline + '</td>'\n      + '<td style=\"padding:10px;border-top:1px solid #eef2f7;font-family:Arial,Helvetica,sans-serif;font-size:12px;color:#0b1f3b;\">' + level + '</td>'\n      + '<td style=\"padding:10px;border-top:1px solid #eef2f7;font-family:Arial,Helvetica,sans-serif;font-size:12px;\">' + urlHtml + '</td>'\n      + '</tr>';\n  }\n}\n\nlet urgentBlock = '';\nif (urgent.length) {\n  let items = '';\n  for (let i = 0; i < urgent.length; i++) {\n    const o = urgent[i] || {};\n    items += '<div style=\"margin:0 0 6px 0;\">&#8226; <strong>'\n      + esc(o.reference || o.id || 'N/A')\n      + '</strong> &#8212; '\n      + esc(o.acheteur || o.organization || 'N/A')\n      + ' <span style=\"color:#9a3a3a;\">(' + esc(fmtDate(o.deadline)) + ')</span></div>';\n  }\n  urgentBlock = ''\n    + '<div style=\"margin-top:18px;border:1px solid #ffe1e1;background:#fff5f5;border-radius:12px;padding:12px;\">'\n    + '<div style=\"font-family:Arial,Helvetica,sans-serif;font-weight:800;color:#8b1d1d;font-size:13px;\">Deadlines urgentes (&lt; 7 jours)</div>'\n    + '<div style=\"font-family:Arial,Helvetica,sans-serif;color:#6b2b2b;font-size:12px;margin-top:8px;\">'\n    + items\n    + '</div>'\n    + '</div>';\n}\n\nconst html = ''\n  + '<!doctype html>'\n  + '<html lang=\"fr\" xmlns=\"http://www.w3.org/1999/xhtml\">'\n  + '<head><meta charset=\"utf-8\" /><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\" />'\n  + '<meta http-equiv=\"x-ua-compatible\" content=\"ie=edge\" />'\n  + '<title>AI Procurement Intelligence</title></head>'\n  + '<body style=\"margin:0;padding:0;background-color:#f3f5f8;\">'\n  + '<table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"100%\" style=\"background-color:#f3f5f8;\">'\n  + '<tr><td align=\"center\" style=\"padding:24px 12px;\">'\n  + '<table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"600\" style=\"width:600px;max-width:600px;background:#ffffff;border:1px solid #e6eaf0;border-radius:16px;overflow:hidden;box-shadow:0 10px 30px rgba(15,23,42,0.08);\">'\n\n  + '<tr><td style=\"background:#0b1f3b;background-image:linear-gradient(135deg,#0b1f3b 0%,#1e3a8a 100%);padding:22px;\">'\n  + '<table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\"><tr>'\n  + '<td>'\n  + '<div style=\"font-family:Arial,Helvetica,sans-serif;color:#ffffff;font-size:18px;font-weight:800;letter-spacing:.2px;\">AI Procurement Intelligence</div>'\n  + '<div style=\"font-family:Arial,Helvetica,sans-serif;color:#c9d4e5;font-size:13px;margin-top:6px;\">Automated IT Tender Monitoring</div>'\n  + '<div style=\"font-family:Arial,Helvetica,sans-serif;color:#c9d4e5;font-size:12px;margin-top:10px;\">Run: <strong>' + esc(runStr) + '</strong></div>'\n  + '</td>'\n  + '<td align=\"right\" valign=\"top\"><span style=\"display:inline-block;background:rgba(255,255,255,.12);border:1px solid rgba(255,255,255,.18);color:#ffffff;font-family:Arial,Helvetica,sans-serif;font-size:11px;font-weight:800;letter-spacing:.6px;padding:6px 10px;border-radius:999px;\">AUTO REPORT</span></td>'\n  + '</tr></table>'\n  + '</td></tr>'\n\n  + '<tr><td style=\"padding:18px;\">'\n\n  + '<table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\"><tr>'\n  + '<td style=\"padding:6px;\"><div style=\"border:1px solid #e7ecf3;border-radius:12px;background:#f8fafc;padding:14px;font-family:Arial,Helvetica,sans-serif;\">'\n  + '<div style=\"font-size:11px;color:#5a6b82;text-transform:uppercase;letter-spacing:.5px;\">Total Opportunities (qualifiees)</div>'\n  + '<div style=\"font-size:22px;color:#0b1f3b;font-weight:800;margin-top:6px;\">' + esc(totalQualifiedActive) + '</div></div></td>'\n  + '<td style=\"padding:6px;\"><div style=\"border:1px solid #e7ecf3;border-radius:12px;background:#f8fafc;padding:14px;font-family:Arial,Helvetica,sans-serif;\">'\n  + '<div style=\"font-size:11px;color:#5a6b82;text-transform:uppercase;letter-spacing:.5px;\">Opportunites IT</div>'\n  + '<div style=\"font-size:22px;color:#0b1f3b;font-weight:800;margin-top:6px;\">' + esc(totalQualifiedActive) + '</div></div></td>'\n  + '<td style=\"padding:6px;\"><div style=\"border:1px solid #ffe1e1;border-radius:12px;background:#fff5f5;padding:14px;font-family:Arial,Helvetica,sans-serif;\">'\n  + '<div style=\"font-size:11px;color:#8b4b4b;text-transform:uppercase;letter-spacing:.5px;\">HOT</div>'\n  + '<div style=\"font-size:22px;color:#b42318;font-weight:800;margin-top:6px;\">' + esc(hotCount) + '</div></div></td>'\n  + '</tr></table>'\n\n  + '<table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"margin-top:8px;\"><tr>'\n  + '<td colspan=\"2\" style=\"padding:6px;\"><div style=\"border:1px solid #ffe8d2;border-radius:12px;background:#fff7ed;padding:14px;font-family:Arial,Helvetica,sans-serif;\">'\n  + '<div style=\"font-size:11px;color:#7a532e;text-transform:uppercase;letter-spacing:.5px;\">Next Deadline</div>'\n  + '<div style=\"font-size:16px;color:#0b1f3b;font-weight:800;margin-top:6px;\">' + esc(nextDeadline) + '</div></div></td>'\n  + '</tr></table>'\n\n  + '<div style=\"margin-top:18px;font-family:Arial,Helvetica,sans-serif;font-size:14px;font-weight:800;color:#0b1f3b;\">Top Opportunities</div>'\n  + '<div style=\"font-family:Arial,Helvetica,sans-serif;font-size:12px;color:#5a6b82;margin-top:4px;\">Top 10 opportunites qualifiees (deadline en cours).</div>'\n\n  + '<table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"margin-top:10px;border:1px solid #e6eaf0;border-radius:12px;overflow:hidden;border-collapse:separate;\">'\n  + '<tr>'\n  + '<td style=\"background:#0f2a52;color:#fff;font-family:Arial,Helvetica,sans-serif;font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.4px;padding:10px;\">Reference</td>'\n  + '<td style=\"background:#0f2a52;color:#fff;font-family:Arial,Helvetica,sans-serif;font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.4px;padding:10px;\">Organisme</td>'\n  + '<td style=\"background:#0f2a52;color:#fff;font-family:Arial,Helvetica,sans-serif;font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.4px;padding:10px;\">Budget</td>'\n  + '<td style=\"background:#0f2a52;color:#fff;font-family:Arial,Helvetica,sans-serif;font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.4px;padding:10px;\">Deadline</td>'\n  + '<td style=\"background:#0f2a52;color:#fff;font-family:Arial,Helvetica,sans-serif;font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.4px;padding:10px;\">Niveau</td>'\n  + '<td style=\"background:#0f2a52;color:#fff;font-family:Arial,Helvetica,sans-serif;font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.4px;padding:10px;\">URL</td>'\n  + '</tr>'\n  + rows\n  + '</table>'\n\n  + urgentBlock\n\n  + '<div style=\"margin-top:18px;text-align:center;\">'\n  + '<a href=\"' + escAttr(dashUrl) + '\" target=\"_blank\" style=\"display:inline-block;background:#f97316;color:#ffffff;text-decoration:none;font-family:Arial,Helvetica,sans-serif;font-size:14px;line-height:46px;font-weight:800;padding:0 22px;border-radius:12px;border:1px solid #f97316;\">Voir le Dashboard</a>'\n  + '</div>'\n\n  + '<div style=\"margin-top:14px;font-family:Arial,Helvetica,sans-serif;font-size:11px;line-height:16px;color:#7a8aa2;text-align:center;\">Rapport genere automatiquement par AI Monitoring Pipeline</div>'\n  + '</td></tr>'\n\n  + '</table>'\n  + '</td></tr></table>'\n  + '</body></html>';\n\nreturn {\n  json: {\n    ...$json,\n    total_qualified_active: totalQualifiedActive,\n    hot_active: hotCount,\n    next_deadline: nextDeadline,\n    email_html: html,\n  }\n};\n"
      },
      "id": "node-build-email",
      "name": "Build Email",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2690,
        360
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "cond-sync-error",
              "leftValue": "={{ $json.body.scraping.error }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "notEmpty"
              }
            }
          ],
          "combinator": "and"
        }
      },
      "id": "node-scrape-sync-error",
      "name": "Check Erreur Webhook",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        900,
        320
      ]
    },
    {
      "parameters": {
        "resumeOption": "webhook",
        "options": {}
      },
      "id": "node-wait-webhook-real",
      "name": "Wait Webhook",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        780,
        320
      ]
    }
  ],
  "connections": {
    "Cron 9h": {
      "main": [
        [
          {
            "node": "Health",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Health": {
      "main": [
        [
          {
            "node": "API OK ?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "API OK ?": {
      "main": [
        [
          {
            "node": "Scrape",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "API Down",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Scrape": {
      "main": [
        [
          {
            "node": "Wait Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filtrer IT": {
      "main": [
        [
          {
            "node": "IT trouvees ?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IT trouvees ?": {
      "main": [
        [
          {
            "node": "Pipeline",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "0 IT",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Pipeline": {
      "main": [
        [
          {
            "node": "Top Opportunities",
            "type": "main",
            "index": 0
          },
          {
            "node": "Dossiers RAG",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Dossiers RAG": {
      "main": [
        []
      ]
    },
    "Resume": {
      "main": [
        []
      ]
    },
    "Top Opportunities": {
      "main": [
        [
          {
            "node": "Build Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Email": {
      "main": [
        [
          {
            "node": "Rapport",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait Webhook": {
      "main": [
        [
          {
            "node": "Check Erreur Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Erreur Webhook": {
      "main": [
        [
          {
            "node": "Log Erreur",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Scrape Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Scrape Status": {
      "main": [
        [
          {
            "node": "Scrape done ?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Scrape done ?": {
      "main": [
        [
          {
            "node": "Filtrer IT",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Wait 10s",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait 10s": {
      "main": [
        [
          {
            "node": "Scrape Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "saveManualExecutions": true,
    "callerPolicy": "workflowsFromSameOwner",
    "errorWorkflow": ""
  },
  "staticData": null,
  "tags": [
    {
      "name": "marches-publics"
    },
    {
      "name": "alexsys"
    },
    {
      "name": "pipeline-v2"
    }
  ],
  "versionId": "3"
}
Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Marche AI - Pipeline. Uses httpRequest, emailSend. Scheduled trigger; 20 nodes.

Source: https://github.com/dohasafri03/PFE/blob/b4c52bb26dbd1f309dce010f6dd9ea18c4e10546/n8n/workflow_pipeline.json — original creator credit. Request a take-down →

More Web Scraping workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Web Scraping

This workflow is an improvement of this workflow by Greg Brzezinka.

HTTP Request, Email Send, XML +1
Web Scraping

N8N-Self-Updater. Uses ssh, emailSend, httpRequest. Scheduled trigger; 27 nodes.

Ssh, Email Send, HTTP Request
Web Scraping

&gt; An automated n8n workflow originally built for DigitalOcean-based n8n deployments, but fully compatible with any VPS or cloud hosting (e.g., AWS, Google Cloud, Hetzner, Linode, etc.) where n8n ru

Ssh, Email Send, HTTP Request
Web Scraping

What if you could spot a major sales problem—or a winning campaign—the very next morning, instead of weeks later? Imagine receiving a beautiful, data-rich alert directly in your inbox the moment your

QuickBooks, HTTP Request, Email Send
Web Scraping

Track Changes Of Product Prices. Uses htmlExtract, functionItem, httpRequest, writeBinaryFile. Scheduled trigger; 25 nodes.

Html Extract, Function Item, HTTP Request +5