{
  "name": "Weekly Owner Report with AI Summary (Helm Systems)",
  "nodes": [
    {
      "parameters": {
        "content": "## Weekly Owner Report with AI Summary\nUses the same workbook as the Monday Money Report (Leads, Estimates, Invoices tabs) and adds a plain-English summary on top.\n1. Pick the same Google Sheet on all three Read nodes, with the Leads, Estimates and Invoices tab on each.\n2. Fill in **Settings**. Connect Gmail, Google Sheets and the AI model credentials.\n3. Every Monday at 7am you get an email: a short summary written by the AI, then the exact numbers below it. The AI only writes the summary. The numbers come from the sheet, so it cannot change them.\nMade by Helm Systems: helmsystems.biz/field-kit",
        "width": 420,
        "height": 330,
        "color": 4
      },
      "id": "50a939f3-9afc-4891-a21a-bb93a4fede81",
      "name": "Note 1",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        0,
        -342
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "weeksInterval": 1,
              "triggerAtDay": [
                1
              ],
              "triggerAtHour": 7
            }
          ]
        }
      },
      "id": "b0bd16c3-cfe4-4db1-a481-2fb87052d4db",
      "name": "Every Monday at 7am",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        0,
        0
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "72012d99-124f-4e74-aa4b-dd7bc94fd0a0",
              "name": "business",
              "value": "Your Business Name",
              "type": "string"
            },
            {
              "id": "2b63d9f0-e923-44f7-a020-000bb955a918",
              "name": "owner",
              "value": "Your Name",
              "type": "string"
            },
            {
              "id": "c677b7b8-a494-475e-af7a-fdcb3a4b117d",
              "name": "ownerEmail",
              "value": "you@yourbusiness.com",
              "type": "string"
            },
            {
              "id": "cf6dfa94-76b6-473b-ad7c-0ed9115ac8e1",
              "name": "staleDays",
              "value": 3,
              "type": "number"
            },
            {
              "id": "66fd83e8-3241-4446-a286-1c6fedc23953",
              "name": "replyHours",
              "value": 24,
              "type": "number"
            }
          ]
        },
        "options": {}
      },
      "id": "85f66e55-3ec9-439f-a518-a1540b8017de",
      "name": "Settings",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        260,
        0
      ]
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "options": {}
      },
      "id": "6db55c0e-c452-4668-a88e-3f7bd189d600",
      "name": "Read leads",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        520,
        0
      ]
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "options": {}
      },
      "id": "cad3623e-b375-4132-ab41-9a38eb770747",
      "name": "Read estimates",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        780,
        0
      ],
      "executeOnce": true
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "options": {}
      },
      "id": "bd9f291b-bbc9-4c6d-aebf-60e714ebb482",
      "name": "Read invoices",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        1040,
        0
      ],
      "executeOnce": true
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const asDate = (v) => { if (!v) return null; const d = new Date(v); return isNaN(d) ? null : d; };\nconst startOfDay = (d) => { const x = new Date(d); x.setHours(0, 0, 0, 0); return x; };\nconst today = startOfDay(new Date());\nconst daysBetween = (a, b) => Math.round((b - a) / 86400000);\nconst s = $('Settings').first().json;\nconst money = (v) => { const n = parseFloat(String(v ?? '').replace(/[^0-9.\\-]/g, '')); return isNaN(n) ? 0 : n; };\nconst usd = (n) => '$' + Math.round(n).toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ',');\nconst closed = (v) => ['won', 'lost', 'closed', 'paid'].includes(String(v || '').trim().toLowerCase());\nconst leads = $('Read leads').all().map((i) => i.json);\nconst estimates = $('Read estimates').all().map((i) => i.json);\nconst invoices = $('Read invoices').all().map((i) => i.json);\nconst week = { leads: { now: 0, before: 0 }, sent: { now: 0, before: 0 }, cash: { now: 0, before: 0 } };\nconst bucket = (t, d, v) => { if (d >= 0 && d <= 6) t.now += v; else if (d >= 7 && d <= 13) t.before += v; };\nconst overdue = [], cold = [], missed = [];\nfor (const r of invoices) {\n  const c = String(r['Customer'] || '').trim(); if (!c) continue;\n  const amount = money(r['Amount']); const due = asDate(r['Due date']); const paid = asDate(r['Date paid']);\n  if (paid) bucket(week.cash, daysBetween(startOfDay(paid), today), amount);\n  else if (due && startOfDay(due) < today) overdue.push({ customer: c, amount, daysLate: daysBetween(startOfDay(due), today) });\n}\nfor (const r of estimates) {\n  const c = String(r['Customer'] || '').trim(); if (!c) continue;\n  const amount = money(r['Amount']); const sent = asDate(r['Date sent']);\n  if (sent) bucket(week.sent, daysBetween(startOfDay(sent), today), amount);\n  if (closed(r['Status']) || !sent) continue;\n  const quiet = daysBetween(startOfDay(asDate(r['Last follow-up (date)']) || sent), today);\n  if (quiet >= Number(s.staleDays)) cold.push({ customer: c, amount, daysQuiet: quiet });\n}\nfor (const r of leads) {\n  const n = String(r['Name'] || '').trim(); if (!n) continue;\n  const received = asDate(r['Date received']);\n  if (received) bucket(week.leads, daysBetween(startOfDay(received), today), 1);\n  if (closed(r['Status']) || !received || asDate(r['First reply (date)'])) continue;\n  if ((Date.now() - received.getTime()) / 3600000 > Number(s.replyHours)) missed.push({ name: n, daysAgo: daysBetween(startOfDay(received), today) });\n}\noverdue.sort((a, b) => b.amount - a.amount);\ncold.sort((a, b) => b.amount - a.amount);\nconst sum = (l) => l.reduce((t, x) => t + x.amount, 0);\nconst overdueTotal = sum(overdue), coldTotal = sum(cold);\nconst metrics = {\n  overdue_total: usd(overdueTotal), overdue_count: overdue.length, biggest_overdue: overdue[0] ? overdue[0].customer + ' ' + usd(overdue[0].amount) + ', ' + overdue[0].daysLate + ' days late' : 'none',\n  cold_estimates: cold.length, cold_total: usd(coldTotal), missed_leads: missed.length,\n  new_leads_this_week: week.leads.now, new_leads_last_week: week.leads.before,\n  estimates_sent_this_week: usd(week.sent.now), estimates_sent_last_week: usd(week.sent.before),\n  cash_in_this_week: usd(week.cash.now), cash_in_last_week: usd(week.cash.before),\n};\nconst list = (items, fmt) => items.slice(0, 5).map((x) => '  - ' + fmt(x)).join('\\n') || '  - none';\nconst reportText = [\n  'THE NUMBERS',\n  '',\n  'Money waiting on you: ' + usd(overdueTotal) + ' across ' + overdue.length + ' overdue invoices',\n  list(overdue, (x) => x.customer + ': ' + usd(x.amount) + ', ' + x.daysLate + ' days late'),\n  '',\n  'Estimates going cold: ' + cold.length + ' worth ' + usd(coldTotal),\n  list(cold, (x) => x.customer + ': ' + usd(x.amount) + ', quiet for ' + x.daysQuiet + ' days'),\n  '',\n  'Leads nobody replied to: ' + missed.length,\n  list(missed, (x) => x.name + ', ' + x.daysAgo + ' days ago'),\n  '',\n  'Last 7 days vs the week before',\n  '  New leads: ' + week.leads.now + ' vs ' + week.leads.before,\n  '  Estimates sent: ' + usd(week.sent.now) + ' vs ' + usd(week.sent.before),\n  '  Cash collected: ' + usd(week.cash.now) + ' vs ' + usd(week.cash.before),\n].join('\\n');\nreturn [{ json: { business: s.business, metrics: JSON.stringify(metrics, null, 1), reportText, subject: (s.business ? s.business + ': ' : '') + 'Monday report: ' + usd(overdueTotal) + ' waiting on you' } }];"
      },
      "id": "8c8654a4-97b6-4dde-aeb6-e4f2e3cd26f6",
      "name": "Compute the numbers",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1300,
        0
      ]
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=Numbers:\n{{ $json.metrics }}",
        "hasOutputParser": false,
        "messages": {
          "messageValues": [
            {
              "message": "=You write the opening of a Monday report for the owner of {{ $json.business }}.\n\nRules:\n- Use only the numbers provided. Never invent a number, a cause or a trend.\n- 4 or 5 plain sentences. Mention the biggest overdue amount, how many estimates are going cold, and how the week compares to the week before.\n- End with one line that starts \"Do this first:\" and names the single most valuable action from the data.\n- No hype, no emojis.\nReturn only the text."
            }
          ]
        }
      },
      "id": "0396d0db-00ce-4405-a902-db9391a788f8",
      "name": "Write the summary",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        1560,
        0
      ]
    },
    {
      "parameters": {
        "model": {
          "__rl": true,
          "value": "gpt-5-mini",
          "mode": "list",
          "cachedResultName": "gpt-5-mini"
        },
        "options": {}
      },
      "id": "80ed78d7-a3cc-400f-adf1-54a5d2fac0da",
      "name": "Write the summary model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "typeVersion": 1.2,
      "position": [
        1560,
        266
      ]
    },
    {
      "parameters": {
        "resource": "message",
        "operation": "send",
        "sendTo": "={{ $('Settings').first().json.ownerEmail }}",
        "subject": "={{ $('Compute the numbers').item.json.subject }}",
        "emailType": "text",
        "message": "={{ $json.text + '\\n\\n' + $('Compute the numbers').item.json.reportText }}",
        "options": {
          "appendAttribution": false
        }
      },
      "id": "e71c3995-d1bd-44f0-ab29-e7d7b539084b",
      "name": "Email the report",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        1820,
        0
      ]
    }
  ],
  "connections": {
    "Write the summary model": {
      "ai_languageModel": [
        [
          {
            "node": "Write the summary",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Every Monday at 7am": {
      "main": [
        [
          {
            "node": "Settings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Settings": {
      "main": [
        [
          {
            "node": "Read leads",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read leads": {
      "main": [
        [
          {
            "node": "Read estimates",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read estimates": {
      "main": [
        [
          {
            "node": "Read invoices",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read invoices": {
      "main": [
        [
          {
            "node": "Compute the numbers",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Compute the numbers": {
      "main": [
        [
          {
            "node": "Write the summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Write the summary": {
      "main": [
        [
          {
            "node": "Email the report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {},
  "tags": []
}
