{
  "name": "Write up a decision in one minute",
  "nodes": [
    {
      "parameters": {
        "content": "## Write up a decision in one minute\nTurn your notes from a discussion into a short decision record a person can read in about a minute, with what was decided, why, what was ruled out and why, who decided, and when to look at it again.\n\n**How to use**\n1. Open **Chat model** and pick your own credential. To use another AI service, delete that node, add the service's chat model node, and connect it to the Model plug under every step and AI check.\n2. Open **Inputs** and paste your material into each field. Examples are filled in so you can try it first.\n3. Click **Execute workflow**.\n4. Open **Result** to read the answer, anything flagged, and the checks left for you.\n\n**Checks**\n- Check 1 (a plain rule, run automatically with no AI): Every row of the table has one of \"chosen\", \"ruled out\" or \"not clear\" in the Outcome column. If it fails: try once more, then flag.\n- Check 2 (a plain rule, run automatically with no AI): Every quote in the Quote column appears word for word in the notes from the discussion. If it fails: try once more, then flag.\n- Check 3 (a plain rule, run automatically with no AI): The answer is no longer than 170 words. If it fails: try once more, then flag.\n- Check 4 (a plain rule, run automatically with no AI): Every number in the answer also appears in the notes from the discussion. If it fails: try once more, then flag.\n- Check 5 (a plain rule, run automatically with no AI): The answer includes the words \"Ask the person who decided to confirm this before you file it.\". If it fails: try once more, then flag.\n- Check 6 (a second AI prompt answers pass or fail): Compare the record with the options table below. Is every option marked \"ruled out\" named in the record, each with the reason the table gives, and is every option marked \"not clear\" listed under \"Not in the notes\"? Answer FAIL and name each option that is missing or has a different reason. If it fails: try once more, then flag.\n- Check 7 (a second AI prompt answers pass or fail): Read the \"Why\" and \"Ruled out\" sections. Does any line give a reason the notes do not give, or name who decided, or give a date, when the notes do not? Answer FAIL and quote each line that does. If it fails: try once more, then flag.\n- Check 8 (you check it by hand): Show the record to the person who made the decision. Ask whether it says what they decided and why, and whether any ruled-out option is shown unfairly. Only they know what they meant. If it fails: flag it and carry on.\n\nFrom Logic Lab (logiclabhq.com). Free to use and adapt, with credit.",
        "height": 760,
        "width": 760,
        "color": 5
      },
      "id": "ff29ddba-5703-4b7d-ae3d-2e0c62f900ef",
      "name": "About this workflow",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -40,
        -720
      ]
    },
    {
      "parameters": {},
      "id": "282122be-acf7-4335-a5a3-8358ca35278f",
      "name": "Start",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        0,
        320
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "bd741a13-621e-4776-a21a-ac95ac724c10",
              "name": "question",
              "value": "Sample: Which printer to use for the annual report",
              "type": "string"
            },
            {
              "id": "d7488bec-025c-4ec3-a990-283a092437b1",
              "name": "notes",
              "value": "Sample: Printing the annual report. Meeting on October 2 with Ana (director), Tomás (finance) and Keiko (communications). Three options came up. Brightline Print quoted 1,240 dollars for 500 copies, delivered in 6 working days. Corner Copy quoted 980 dollars for 500 copies but could not promise delivery before October 16, and the report launch is on October 15. Printing in-house would cost about 400 dollars in paper and toner, but it would tie up the office printer for three days and the copies would be stapled, not bound. Tomás said the printing line in the budget is 1,300 dollars. Keiko said the launch date cannot move because the board chair is coming. Ana said Brightline Print is the only option that fits both the budget and the launch date, and decided to go with it. She asked Keiko to place the order by October 5. Tomás suggested looking at Corner Copy again next year if the launch is later in the year.",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "574dd593-ee04-491a-a62d-90111c5046e8",
      "name": "Inputs",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        220,
        320
      ],
      "notes": "Paste your own material here: question = The question that was settled; notes = Notes from the discussion.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "promptType": "define",
        "hasOutputParser": false,
        "text": "=You are a careful note-taker who records what was said and does not weigh it.\n\nYour task is to list every option the group considered and how each came out. This step does one job: it lists options. It does not write the record.\n\nThe question:\n{{ $(\"Inputs\").first().json[\"question\"] }}\n\nThe notes:\n{{ $(\"Inputs\").first().json[\"notes\"] }}\n\nRules:\n- Write one row for each option the notes mention, including one that is only mentioned in passing.\n- In \"Outcome\", use exactly one of these: chosen, ruled out, not clear. Use not clear when the notes do not say what happened to the option.\n- In \"Reason\", give the reason in the notes' own words, as short as you can. If the notes give no reason, write exactly Not in the notes.\n- Do not add an option the notes do not mention. Do not give a reason of your own.\n\nReturn one markdown table and nothing else:\n| Option | Outcome | Reason |\nThe Outcome column must say exactly chosen, ruled out or not clear.{{ $(\"Check 1: Every outcome is chosen, ruled out or not clear\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Every outcome is chosen, ruled out or not clear. What was wrong: \" + $(\"Check 1: Every outcome is chosen, ruled out or not clear\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}"
      },
      "id": "53b91ce7-40cd-428a-adc4-4c85dfdcce98",
      "name": "1. List every option and how it came out",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        480,
        320
      ],
      "notes": "Write down each option the notes mention, whether it was chosen or ruled out, and the reason the notes give. Returns: A table of every option, with an outcome and the reason the notes give.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// Every outcome is chosen, ruled out or not clear\n// Rule: Every row of the table has one of \"chosen\", \"ruled out\" or \"not clear\" in the Outcome column.\n// Runs with no AI. Returns pass (true or false) and a reason.\nconst runRule = function runRule(rule, value, work, sources) {\n    const norm = (s) => String(s ?? '')\n        .toLowerCase()\n        .replace(/[\\u2018\\u2019\\u201c\\u201d\"'`]/g, '')\n        .replace(/\\s+/g, ' ')\n        .trim();\n    const readTable = (text) => {\n        const lines = String(text ?? '')\n            .split(/\\r?\\n/)\n            .map((l) => l.trim());\n        const tables = [];\n        let cur = null;\n        const cells = (l) => l\n            .replace(/^\\|/, '')\n            .replace(/\\|$/, '')\n            .split('|')\n            .map((c) => c.trim());\n        for (let i = 0; i < lines.length; i++) {\n            const l = lines[i] ?? '';\n            if (!l.startsWith('|')) {\n                cur = null;\n                continue;\n            }\n            if (!cur) {\n                const next = lines[i + 1] ?? '';\n                if (/^\\|?\\s*:?-{2,}/.test(next)) {\n                    cur = { header: cells(l), rows: [] };\n                    tables.push(cur);\n                    i++;\n                }\n                continue;\n            }\n            cur.rows.push(cells(l));\n        }\n        return tables;\n    };\n    // Every table that has this column, with \"None found\" rows left out.\n    // Returns that column's cells from every such table, or null if no table has it.\n    const findColumn = (name) => {\n        let seen = false;\n        const cells = [];\n        for (const t of readTable(work)) {\n            const i = t.header.findIndex((h) => norm(h) === norm(name));\n            if (i < 0)\n                continue;\n            seen = true;\n            for (const r of t.rows) {\n                if (r.some((c) => norm(c) === 'none found'))\n                    continue;\n                cells.push(r[i] ?? '');\n            }\n        }\n        return seen ? cells : null;\n    };\n    const empty = (c) => !c || /^[-\\u2013\\u2014\\s]*$/.test(c);\n    switch (rule) {\n        case 'has_text':\n            return norm(work).includes(norm(value))\n                ? { pass: true, reason: 'Found.' }\n                : { pass: false, reason: `The answer does not include \"${value}\".` };\n        case 'lacks_text':\n            return norm(work).includes(norm(value))\n                ? { pass: false, reason: `The answer includes \"${value}\", which it should not.` }\n                : { pass: true, reason: 'Not found, as it should be.' };\n        case 'max_words': {\n            const n = String(work ?? '')\n                .split(/\\s+/)\n                .filter(Boolean).length;\n            return n <= Number(value)\n                ? { pass: true, reason: `${n} words.` }\n                : { pass: false, reason: `The answer has ${n} words; the limit is ${value}.` };\n        }\n        case 'table_cells_filled': {\n            const problems = [];\n            for (const col of value) {\n                const f = findColumn(col);\n                if (!f) {\n                    problems.push(`No table with a \"${col}\" column was found.`);\n                    continue;\n                }\n                f.forEach((c, i) => {\n                    if (empty(c))\n                        problems.push(`Row ${i + 1} has nothing in \"${col}\".`);\n                });\n            }\n            return problems.length\n                ? { pass: false, reason: problems.join(' ') }\n                : { pass: true, reason: 'Every row is filled in.' };\n        }\n        case 'column_values_in': {\n            const f = findColumn(value.column);\n            if (!f)\n                return { pass: false, reason: `No table with a \"${value.column}\" column was found.` };\n            const allowed = value.allowed.map(norm);\n            const bad = [];\n            f.forEach((c, i) => {\n                if (!allowed.includes(norm(c)))\n                    bad.push(`Row ${i + 1} says \"${c}\".`);\n            });\n            return bad.length\n                ? {\n                    pass: false,\n                    reason: `The \"${value.column}\" column must say ${value.allowed.join(' or ')}. ${bad.join(' ')}`,\n                }\n                : { pass: true, reason: 'Every row uses an allowed word.' };\n        }\n        case 'column_quotes_source': {\n            const f = findColumn(value.column);\n            if (!f)\n                return { pass: false, reason: `No table with a \"${value.column}\" column was found.` };\n            const src = norm(sources[value.input] ?? '');\n            const bad = [];\n            f.forEach((c, i) => {\n                // A quote may be shortened with \"...\"; each piece must be in the source.\n                const pieces = c\n                    .split(/\\.\\.\\.|\\u2026/)\n                    .map(norm)\n                    .filter(Boolean);\n                if (!pieces.length || !pieces.every((p) => src.includes(p)))\n                    bad.push(`Row ${i + 1}: \"${c}\" is not in the source.`);\n            });\n            return bad.length\n                ? { pass: false, reason: bad.join(' ') }\n                : { pass: true, reason: 'Every quote was found in the source.' };\n        }\n        case 'numbers_in_source': {\n            const nums = (s) => (String(s ?? '').match(/\\d[\\d,]*(?:\\.\\d+)?/g) ?? []).map((n) => n.replace(/,/g, ''));\n            const src = new Set(nums(sources[value.input] ?? ''));\n            const missing = [...new Set(nums(work))].filter((n) => !src.has(n));\n            return missing.length\n                ? {\n                    pass: false,\n                    reason: `These numbers are not in the source: ${missing.join(', ')}.`,\n                }\n                : { pass: true, reason: 'Every number was found in the source.' };\n        }\n        default:\n            return { pass: false, reason: `Unknown rule \"${rule}\".` };\n    }\n};\n\nconst work = String($(\"1. List every option and how it came out\").first().json.text ?? '');\nconst sources = {\n\n};\nconst result = runRule(\"column_values_in\", {\"column\":\"Outcome\",\"allowed\":[\"chosen\",\"ruled out\",\"not clear\"]}, work, sources);\nreturn [{ json: result }];"
      },
      "id": "2f18d45f-e833-46be-a3b2-6625bb9de6ec",
      "name": "Check 1: Every outcome is chosen, ruled out or not clear",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        740,
        320
      ],
      "notes": "Every row of the table has one of \"chosen\", \"ruled out\" or \"not clear\" in the Outcome column.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "8b2f6c25-7363-4304-a5fb-bf8b5270b2e2",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "b98936a6-71c8-435d-a82c-2258bf75ef27",
      "name": "Passed check 1?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1000,
        320
      ],
      "notes": "True = passed. False = did not pass.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "57883c02-3ca4-454f-af42-4038a5aceb7d",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "89a8915e-6788-41eb-a13e-a180a3d3e91d",
      "name": "Try again? (check 1)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1000,
        520
      ],
      "notes": "True = first failure: send back to \"1. List every option and how it came out\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "a3d8e097-169a-4aae-a556-94b9d43ea518",
              "name": "flag",
              "value": "=Every outcome is chosen, ruled out or not clear: did not pass. {{ $(\"Check 1: Every outcome is chosen, ruled out or not clear\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "df0d7b9b-7231-45da-acfc-dbcd8308b3bc",
      "name": "Flag check 1",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1130,
        700
      ],
      "notes": "Notes the problem and carries on. The note shows in Result.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "promptType": "define",
        "hasOutputParser": false,
        "text": "=You are a careful note-taker who finds facts in notes and never fills a gap.\n\nYour task is to find four facts in the notes: who decided, the date it was decided, the main reason, and when to look at it again. This step does one job: it finds facts.\n\nThe question:\n{{ $(\"Inputs\").first().json[\"question\"] }}\n\nThe notes:\n{{ $(\"Inputs\").first().json[\"notes\"] }}\n\nRules:\n- Write exactly four rows, one for each fact, in this order: Decided by, Date decided, Main reason, Look again when.\n- In \"Value\", write the fact as the notes give it. If the notes do not say, write exactly Not stated.\n- In \"Quote\", copy the words from the notes that show it, word for word, as short as you can. If the value is Not stated, write exactly None found.\n- If the notes do not say clearly who decided, write Not stated and do not name the person who spoke last. If the notes give no date for the decision, write Not stated. Do not work out a date from another date.\n- Do not guess a reason or a date to look again.\n\nReturn one markdown table and nothing else:\n| Fact | Value | Quote |{{ $(\"Check 2: Every quote is word for word from the notes\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Every quote is word for word from the notes. What was wrong: \" + $(\"Check 2: Every quote is word for word from the notes\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}"
      },
      "id": "3db8b258-2fe1-44bb-aadc-09c229e45cb5",
      "name": "2. Pull out who decided, when, why and when to look again",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        1260,
        320
      ],
      "notes": "Find the four facts a record needs, each quoted from the notes, with a gap marked as a gap. Returns: A four-row table of the facts a decision record needs, each quoted or marked Not stated.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// Every quote is word for word from the notes\n// Rule: Every quote in the Quote column appears word for word in the notes from the discussion.\n// Runs with no AI. Returns pass (true or false) and a reason.\nconst runRule = function runRule(rule, value, work, sources) {\n    const norm = (s) => String(s ?? '')\n        .toLowerCase()\n        .replace(/[\\u2018\\u2019\\u201c\\u201d\"'`]/g, '')\n        .replace(/\\s+/g, ' ')\n        .trim();\n    const readTable = (text) => {\n        const lines = String(text ?? '')\n            .split(/\\r?\\n/)\n            .map((l) => l.trim());\n        const tables = [];\n        let cur = null;\n        const cells = (l) => l\n            .replace(/^\\|/, '')\n            .replace(/\\|$/, '')\n            .split('|')\n            .map((c) => c.trim());\n        for (let i = 0; i < lines.length; i++) {\n            const l = lines[i] ?? '';\n            if (!l.startsWith('|')) {\n                cur = null;\n                continue;\n            }\n            if (!cur) {\n                const next = lines[i + 1] ?? '';\n                if (/^\\|?\\s*:?-{2,}/.test(next)) {\n                    cur = { header: cells(l), rows: [] };\n                    tables.push(cur);\n                    i++;\n                }\n                continue;\n            }\n            cur.rows.push(cells(l));\n        }\n        return tables;\n    };\n    // Every table that has this column, with \"None found\" rows left out.\n    // Returns that column's cells from every such table, or null if no table has it.\n    const findColumn = (name) => {\n        let seen = false;\n        const cells = [];\n        for (const t of readTable(work)) {\n            const i = t.header.findIndex((h) => norm(h) === norm(name));\n            if (i < 0)\n                continue;\n            seen = true;\n            for (const r of t.rows) {\n                if (r.some((c) => norm(c) === 'none found'))\n                    continue;\n                cells.push(r[i] ?? '');\n            }\n        }\n        return seen ? cells : null;\n    };\n    const empty = (c) => !c || /^[-\\u2013\\u2014\\s]*$/.test(c);\n    switch (rule) {\n        case 'has_text':\n            return norm(work).includes(norm(value))\n                ? { pass: true, reason: 'Found.' }\n                : { pass: false, reason: `The answer does not include \"${value}\".` };\n        case 'lacks_text':\n            return norm(work).includes(norm(value))\n                ? { pass: false, reason: `The answer includes \"${value}\", which it should not.` }\n                : { pass: true, reason: 'Not found, as it should be.' };\n        case 'max_words': {\n            const n = String(work ?? '')\n                .split(/\\s+/)\n                .filter(Boolean).length;\n            return n <= Number(value)\n                ? { pass: true, reason: `${n} words.` }\n                : { pass: false, reason: `The answer has ${n} words; the limit is ${value}.` };\n        }\n        case 'table_cells_filled': {\n            const problems = [];\n            for (const col of value) {\n                const f = findColumn(col);\n                if (!f) {\n                    problems.push(`No table with a \"${col}\" column was found.`);\n                    continue;\n                }\n                f.forEach((c, i) => {\n                    if (empty(c))\n                        problems.push(`Row ${i + 1} has nothing in \"${col}\".`);\n                });\n            }\n            return problems.length\n                ? { pass: false, reason: problems.join(' ') }\n                : { pass: true, reason: 'Every row is filled in.' };\n        }\n        case 'column_values_in': {\n            const f = findColumn(value.column);\n            if (!f)\n                return { pass: false, reason: `No table with a \"${value.column}\" column was found.` };\n            const allowed = value.allowed.map(norm);\n            const bad = [];\n            f.forEach((c, i) => {\n                if (!allowed.includes(norm(c)))\n                    bad.push(`Row ${i + 1} says \"${c}\".`);\n            });\n            return bad.length\n                ? {\n                    pass: false,\n                    reason: `The \"${value.column}\" column must say ${value.allowed.join(' or ')}. ${bad.join(' ')}`,\n                }\n                : { pass: true, reason: 'Every row uses an allowed word.' };\n        }\n        case 'column_quotes_source': {\n            const f = findColumn(value.column);\n            if (!f)\n                return { pass: false, reason: `No table with a \"${value.column}\" column was found.` };\n            const src = norm(sources[value.input] ?? '');\n            const bad = [];\n            f.forEach((c, i) => {\n                // A quote may be shortened with \"...\"; each piece must be in the source.\n                const pieces = c\n                    .split(/\\.\\.\\.|\\u2026/)\n                    .map(norm)\n                    .filter(Boolean);\n                if (!pieces.length || !pieces.every((p) => src.includes(p)))\n                    bad.push(`Row ${i + 1}: \"${c}\" is not in the source.`);\n            });\n            return bad.length\n                ? { pass: false, reason: bad.join(' ') }\n                : { pass: true, reason: 'Every quote was found in the source.' };\n        }\n        case 'numbers_in_source': {\n            const nums = (s) => (String(s ?? '').match(/\\d[\\d,]*(?:\\.\\d+)?/g) ?? []).map((n) => n.replace(/,/g, ''));\n            const src = new Set(nums(sources[value.input] ?? ''));\n            const missing = [...new Set(nums(work))].filter((n) => !src.has(n));\n            return missing.length\n                ? {\n                    pass: false,\n                    reason: `These numbers are not in the source: ${missing.join(', ')}.`,\n                }\n                : { pass: true, reason: 'Every number was found in the source.' };\n        }\n        default:\n            return { pass: false, reason: `Unknown rule \"${rule}\".` };\n    }\n};\n\nconst work = String($(\"2. Pull out who decided, when, why and when to look again\").first().json.text ?? '');\nconst sources = {\n  \"notes\": String($(\"Inputs\").first().json[\"notes\"] ?? ''),\n};\nconst result = runRule(\"column_quotes_source\", {\"column\":\"Quote\",\"input\":\"notes\"}, work, sources);\nreturn [{ json: result }];"
      },
      "id": "e3504628-95ed-4699-ae19-4416bd1490e7",
      "name": "Check 2: Every quote is word for word from the notes",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1520,
        320
      ],
      "notes": "Every quote in the Quote column appears word for word in the notes from the discussion.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "8a2f6a92-7463-4497-a4fb-bdf85370b475",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "b9827ae1-f1d0-48a2-a82f-2a6fbf73b090",
      "name": "Passed check 2?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1780,
        320
      ],
      "notes": "True = passed. False = did not pass.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "58883d95-3ba4-43bc-a042-41cba4ace9ea",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "b1ab0eed-078f-4990-a137-4e9bbbd1d04e",
      "name": "Try again? (check 2)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1780,
        520
      ],
      "notes": "True = first failure: send back to \"2. Pull out who decided, when, why and when to look again\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "a2d8df04-179a-4c41-a456-9326d53ea6ab",
              "name": "flag",
              "value": "=Every quote is word for word from the notes: did not pass. {{ $(\"Check 2: Every quote is word for word from the notes\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "e00d7d2e-7131-4447-a9fc-d7148608b875",
      "name": "Flag check 2",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1910,
        700
      ],
      "notes": "Notes the problem and carries on. The note shows in Result.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "promptType": "define",
        "hasOutputParser": false,
        "text": "=You are a clear writer who keeps records that people can read in a minute.\n\nYour task is to write the decision record from the material below. This step does one job: it writes up what the earlier steps found.\n\nThe question:\n{{ $(\"Inputs\").first().json[\"question\"] }}\n\nThe options:\n{{ $(\"1. List every option and how it came out\").first().json.text }}\n\nThe facts:\n{{ $(\"2. Pull out who decided, when, why and when to look again\").first().json.text }}\n\nRules:\n- Keep the whole record to 170 words or fewer, so it reads in about a minute.\n- Under \"Decision\", write one sentence that says what was decided, then a line that begins \"Decided by:\" with the person and the date. If either is Not stated, write \"Not stated in the notes\" in its place.\n- Under \"Why\", write one or two sentences using only the reasons the notes give.\n- Under \"Ruled out\", write one bullet for each option marked ruled out, with the option and its reason. If an option is marked not clear, put it under \"Not in the notes\" instead.\n- Under \"Look again when\", write one line from the facts. If it is Not stated, write \"Not stated in the notes.\"\n- Under \"Not in the notes\", write one bullet for each thing the notes leave open, such as an unclear decider or an option with no outcome. If there is nothing, write \"- None found.\"\n- Write a number only as the notes write it. Do not add a total, a date or a reason that the notes do not give.\n- End with exactly this line: Ask the person who decided to confirm this before you file it.\n\nReturn markdown in this form and nothing else:\n## Decision\n(one sentence)\nDecided by: (person and date)\n## Why\n(one or two sentences)\n## Ruled out\n(one bullet for each)\n## Look again when\n(one line)\n## Not in the notes\n(one bullet for each)\nAsk the person who decided to confirm this before you file it.{{ $(\"Check 3: The record is 170 words or fewer\").isExecuted ? \"\\n\\nYour last answer did not pass this check: The record is 170 words or fewer. What was wrong: \" + $(\"Check 3: The record is 170 words or fewer\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}{{ $(\"Check 4: Every number in the record is in the notes\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Every number in the record is in the notes. What was wrong: \" + $(\"Check 4: Every number in the record is in the notes\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}{{ $(\"Check 5: The record ends with the reminder to confirm it\").isExecuted ? \"\\n\\nYour last answer did not pass this check: The record ends with the reminder to confirm it. What was wrong: \" + $(\"Check 5: The record ends with the reminder to confirm it\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}{{ $(\"Check 6: Every option that was ruled out is in the record with its reason\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Every option that was ruled out is in the record with its reason. What was wrong: \" + $(\"Check 6: Every option that was ruled out is in the record with its reason\").first().json.text + \"\\nFix that and give your full answer again.\" : \"\" }}{{ $(\"Check 7: Every reason is one the notes give\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Every reason is one the notes give. What was wrong: \" + $(\"Check 7: Every reason is one the notes give\").first().json.text + \"\\nFix that and give your full answer again.\" : \"\" }}"
      },
      "id": "e4ae238f-567d-476e-a032-d4091f9b1788",
      "name": "3. Write the one-minute record",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        2040,
        320
      ],
      "notes": "Put the decision, the reason, what was ruled out, when to look again and the gaps into a record short enough to read in a minute. Returns: A decision record of 170 words or fewer that reads in about a minute.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// The record is 170 words or fewer\n// Rule: The answer is no longer than 170 words.\n// Runs with no AI. Returns pass (true or false) and a reason.\nconst runRule = function runRule(rule, value, work, sources) {\n    const norm = (s) => String(s ?? '')\n        .toLowerCase()\n        .replace(/[\\u2018\\u2019\\u201c\\u201d\"'`]/g, '')\n        .replace(/\\s+/g, ' ')\n        .trim();\n    const readTable = (text) => {\n        const lines = String(text ?? '')\n            .split(/\\r?\\n/)\n            .map((l) => l.trim());\n        const tables = [];\n        let cur = null;\n        const cells = (l) => l\n            .replace(/^\\|/, '')\n            .replace(/\\|$/, '')\n            .split('|')\n            .map((c) => c.trim());\n        for (let i = 0; i < lines.length; i++) {\n            const l = lines[i] ?? '';\n            if (!l.startsWith('|')) {\n                cur = null;\n                continue;\n            }\n            if (!cur) {\n                const next = lines[i + 1] ?? '';\n                if (/^\\|?\\s*:?-{2,}/.test(next)) {\n                    cur = { header: cells(l), rows: [] };\n                    tables.push(cur);\n                    i++;\n                }\n                continue;\n            }\n            cur.rows.push(cells(l));\n        }\n        return tables;\n    };\n    // Every table that has this column, with \"None found\" rows left out.\n    // Returns that column's cells from every such table, or null if no table has it.\n    const findColumn = (name) => {\n        let seen = false;\n        const cells = [];\n        for (const t of readTable(work)) {\n            const i = t.header.findIndex((h) => norm(h) === norm(name));\n            if (i < 0)\n                continue;\n            seen = true;\n            for (const r of t.rows) {\n                if (r.some((c) => norm(c) === 'none found'))\n                    continue;\n                cells.push(r[i] ?? '');\n            }\n        }\n        return seen ? cells : null;\n    };\n    const empty = (c) => !c || /^[-\\u2013\\u2014\\s]*$/.test(c);\n    switch (rule) {\n        case 'has_text':\n            return norm(work).includes(norm(value))\n                ? { pass: true, reason: 'Found.' }\n                : { pass: false, reason: `The answer does not include \"${value}\".` };\n        case 'lacks_text':\n            return norm(work).includes(norm(value))\n                ? { pass: false, reason: `The answer includes \"${value}\", which it should not.` }\n                : { pass: true, reason: 'Not found, as it should be.' };\n        case 'max_words': {\n            const n = String(work ?? '')\n                .split(/\\s+/)\n                .filter(Boolean).length;\n            return n <= Number(value)\n                ? { pass: true, reason: `${n} words.` }\n                : { pass: false, reason: `The answer has ${n} words; the limit is ${value}.` };\n        }\n        case 'table_cells_filled': {\n            const problems = [];\n            for (const col of value) {\n                const f = findColumn(col);\n                if (!f) {\n                    problems.push(`No table with a \"${col}\" column was found.`);\n                    continue;\n                }\n                f.forEach((c, i) => {\n                    if (empty(c))\n                        problems.push(`Row ${i + 1} has nothing in \"${col}\".`);\n                });\n            }\n            return problems.length\n                ? { pass: false, reason: problems.join(' ') }\n                : { pass: true, reason: 'Every row is filled in.' };\n        }\n        case 'column_values_in': {\n            const f = findColumn(value.column);\n            if (!f)\n                return { pass: false, reason: `No table with a \"${value.column}\" column was found.` };\n            const allowed = value.allowed.map(norm);\n            const bad = [];\n            f.forEach((c, i) => {\n                if (!allowed.includes(norm(c)))\n                    bad.push(`Row ${i + 1} says \"${c}\".`);\n            });\n            return bad.length\n                ? {\n                    pass: false,\n                    reason: `The \"${value.column}\" column must say ${value.allowed.join(' or ')}. ${bad.join(' ')}`,\n                }\n                : { pass: true, reason: 'Every row uses an allowed word.' };\n        }\n        case 'column_quotes_source': {\n            const f = findColumn(value.column);\n            if (!f)\n                return { pass: false, reason: `No table with a \"${value.column}\" column was found.` };\n            const src = norm(sources[value.input] ?? '');\n            const bad = [];\n            f.forEach((c, i) => {\n                // A quote may be shortened with \"...\"; each piece must be in the source.\n                const pieces = c\n                    .split(/\\.\\.\\.|\\u2026/)\n                    .map(norm)\n                    .filter(Boolean);\n                if (!pieces.length || !pieces.every((p) => src.includes(p)))\n                    bad.push(`Row ${i + 1}: \"${c}\" is not in the source.`);\n            });\n            return bad.length\n                ? { pass: false, reason: bad.join(' ') }\n                : { pass: true, reason: 'Every quote was found in the source.' };\n        }\n        case 'numbers_in_source': {\n            const nums = (s) => (String(s ?? '').match(/\\d[\\d,]*(?:\\.\\d+)?/g) ?? []).map((n) => n.replace(/,/g, ''));\n            const src = new Set(nums(sources[value.input] ?? ''));\n            const missing = [...new Set(nums(work))].filter((n) => !src.has(n));\n            return missing.length\n                ? {\n                    pass: false,\n                    reason: `These numbers are not in the source: ${missing.join(', ')}.`,\n                }\n                : { pass: true, reason: 'Every number was found in the source.' };\n        }\n        default:\n            return { pass: false, reason: `Unknown rule \"${rule}\".` };\n    }\n};\n\nconst work = String($(\"3. Write the one-minute record\").first().json.text ?? '');\nconst sources = {\n\n};\nconst result = runRule(\"max_words\", 170, work, sources);\nreturn [{ json: result }];"
      },
      "id": "e4368866-afaf-4011-aaea-4d98feb99173",
      "name": "Check 3: The record is 170 words or fewer",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2300,
        320
      ],
      "notes": "The answer is no longer than 170 words.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "892f68ff-7563-462a-a7fb-c2b15070afbc",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "b984b978-f1ce-4a0b-a831-69063f70a879",
      "name": "Passed check 3?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2560,
        320
      ],
      "notes": "True = passed. False = did not pass.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "558838dc-3ea4-4875-a142-435ea3ace857",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "85ad0840-f38d-4b7d-a53a-2a9e97cf590b",
      "name": "Try again? (check 3)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2560,
        520
      ],
      "notes": "True = first failure: send back to \"3. Write the one-minute record\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "a5d8e3bd-149a-4788-a356-9193d63ea83e",
              "name": "flag",
              "value": "=The record is 170 words or fewer: did not pass. {{ $(\"Check 3: The record is 170 words or fewer\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "e10d7ec1-7031-42b4-aafc-d8a78508b6e2",
      "name": "Flag check 3",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        2690,
        700
      ],
      "notes": "Notes the problem and carries on. The note shows in Result.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// Every number in the record is in the notes\n// Rule: Every number in the answer also appears in the notes from the discussion.\n// Runs with no AI. Returns pass (true or false) and a reason.\nconst runRule = function runRule(rule, value, work, sources) {\n    const norm = (s) => String(s ?? '')\n        .toLowerCase()\n        .replace(/[\\u2018\\u2019\\u201c\\u201d\"'`]/g, '')\n        .replace(/\\s+/g, ' ')\n        .trim();\n    const readTable = (text) => {\n        const lines = String(text ?? '')\n            .split(/\\r?\\n/)\n            .map((l) => l.trim());\n        const tables = [];\n        let cur = null;\n        const cells = (l) => l\n            .replace(/^\\|/, '')\n            .replace(/\\|$/, '')\n            .split('|')\n            .map((c) => c.trim());\n        for (let i = 0; i < lines.length; i++) {\n            const l = lines[i] ?? '';\n            if (!l.startsWith('|')) {\n                cur = null;\n                continue;\n            }\n            if (!cur) {\n                const next = lines[i + 1] ?? '';\n                if (/^\\|?\\s*:?-{2,}/.test(next)) {\n                    cur = { header: cells(l), rows: [] };\n                    tables.push(cur);\n                    i++;\n                }\n                continue;\n            }\n            cur.rows.push(cells(l));\n        }\n        return tables;\n    };\n    // Every table that has this column, with \"None found\" rows left out.\n    // Returns that column's cells from every such table, or null if no table has it.\n    const findColumn = (name) => {\n        let seen = false;\n        const cells = [];\n        for (const t of readTable(work)) {\n            const i = t.header.findIndex((h) => norm(h) === norm(name));\n            if (i < 0)\n                continue;\n            seen = true;\n            for (const r of t.rows) {\n                if (r.some((c) => norm(c) === 'none found'))\n                    continue;\n                cells.push(r[i] ?? '');\n            }\n        }\n        return seen ? cells : null;\n    };\n    const empty = (c) => !c || /^[-\\u2013\\u2014\\s]*$/.test(c);\n    switch (rule) {\n        case 'has_text':\n            return norm(work).includes(norm(value))\n                ? { pass: true, reason: 'Found.' }\n                : { pass: false, reason: `The answer does not include \"${value}\".` };\n        case 'lacks_text':\n            return norm(work).includes(norm(value))\n                ? { pass: false, reason: `The answer includes \"${value}\", which it should not.` }\n                : { pass: true, reason: 'Not found, as it should be.' };\n        case 'max_words': {\n            const n = String(work ?? '')\n                .split(/\\s+/)\n                .filter(Boolean).length;\n            return n <= Number(value)\n                ? { pass: true, reason: `${n} words.` }\n                : { pass: false, reason: `The answer has ${n} words; the limit is ${value}.` };\n        }\n        case 'table_cells_filled': {\n            const problems = [];\n            for (const col of value) {\n                const f = findColumn(col);\n                if (!f) {\n                    problems.push(`No table with a \"${col}\" column was found.`);\n                    continue;\n                }\n                f.forEach((c, i) => {\n                    if (empty(c))\n                        problems.push(`Row ${i + 1} has nothing in \"${col}\".`);\n                });\n            }\n            return problems.length\n                ? { pass: false, reason: problems.join(' ') }\n                : { pass: true, reason: 'Every row is filled in.' };\n        }\n        case 'column_values_in': {\n            const f = findColumn(value.column);\n            if (!f)\n                return { pass: false, reason: `No table with a \"${value.column}\" column was found.` };\n            const allowed = value.allowed.map(norm);\n            const bad = [];\n            f.forEach((c, i) => {\n                if (!allowed.includes(norm(c)))\n                    bad.push(`Row ${i + 1} says \"${c}\".`);\n            });\n            return bad.length\n                ? {\n                    pass: false,\n                    reason: `The \"${value.column}\" column must say ${value.allowed.join(' or ')}. ${bad.join(' ')}`,\n                }\n                : { pass: true, reason: 'Every row uses an allowed word.' };\n        }\n        case 'column_quotes_source': {\n            const f = findColumn(value.column);\n            if (!f)\n                return { pass: false, reason: `No table with a \"${value.column}\" column was found.` };\n            const src = norm(sources[value.input] ?? '');\n            const bad = [];\n            f.forEach((c, i) => {\n                // A quote may be shortened with \"...\"; each piece must be in the source.\n                const pieces = c\n                    .split(/\\.\\.\\.|\\u2026/)\n                    .map(norm)\n                    .filter(Boolean);\n                if (!pieces.length || !pieces.every((p) => src.includes(p)))\n                    bad.push(`Row ${i + 1}: \"${c}\" is not in the source.`);\n            });\n            return bad.length\n                ? { pass: false, reason: bad.join(' ') }\n                : { pass: true, reason: 'Every quote was found in the source.' };\n        }\n        case 'numbers_in_source': {\n            const nums = (s) => (String(s ?? '').match(/\\d[\\d,]*(?:\\.\\d+)?/g) ?? []).map((n) => n.replace(/,/g, ''));\n            const src = new Set(nums(sources[value.input] ?? ''));\n            const missing = [...new Set(nums(work))].filter((n) => !src.has(n));\n            return missing.length\n                ? {\n                    pass: false,\n                    reason: `These numbers are not in the source: ${missing.join(', ')}.`,\n                }\n                : { pass: true, reason: 'Every number was found in the source.' };\n        }\n        default:\n            return { pass: false, reason: `Unknown rule \"${rule}\".` };\n    }\n};\n\nconst work = String($(\"3. Write the one-minute record\").first().json.text ?? '');\nconst sources = {\n  \"notes\": String($(\"Inputs\").first().json[\"notes\"] ?? ''),\n};\nconst result = runRule(\"numbers_in_source\", {\"input\":\"notes\"}, work, sources);\nreturn [{ json: result }];"
      },
      "id": "07f3d24c-9f57-42cb-ac6a-e29211ba6279",
      "name": "Check 4: Every number in the record is in the notes",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2820,
        320
      ],
      "notes": "Every number in the answer also appears in the notes from the discussion.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "882f676c-7663-47bd-a6fb-c11e5170b14f",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "397d3433-71d5-4f50-a820-1fe53f6e69e2",
      "name": "Passed check 4?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        3080,
        320
      ],
      "notes": "True = passed. False = did not pass.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "56883a6f-3da4-46e2-a242-44f1a2ace6c4",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "adaf85cf-1394-49a2-a546-2d119fe0aec4",
      "name": "Try again? (check 4)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        3080,
        520
      ],
      "notes": "True = first failure: send back to \"3. Write the one-minute record\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "a4d8e22a-159a-491b-a256-9000d73ea9d1",
              "name": "flag",
              "value": "=Every number in the record is in the notes: did not pass. {{ $(\"Check 4: Every number in the record is in the notes\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "e20d8054-6f31-4121-a7fc-d3ee8008af03",
      "name": "Flag check 4",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        3210,
        700
      ],
      "notes": "Notes the problem and carries on. The note shows in Result.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// The record ends with the reminder to confirm it\n// Rule: The answer includes the words \"Ask the person who decided to confirm this before you file it.\".\n// Runs with no AI. Returns pass (true or false) and a reason.\nconst runRule = function runRule(rule, value, work, sources) {\n    const norm = (s) => String(s ?? '')\n        .toLowerCase()\n        .replace(/[\\u2018\\u2019\\u201c\\u201d\"'`]/g, '')\n        .replace(/\\s+/g, ' ')\n        .trim();\n    const readTable = (text) => {\n        const lines = String(text ?? '')\n            .split(/\\r?\\n/)\n            .map((l) => l.trim());\n        const tables = [];\n        let cur = null;\n        const cells = (l) => l\n            .replace(/^\\|/, '')\n            .replace(/\\|$/, '')\n            .split('|')\n            .map((c) => c.trim());\n        for (let i = 0; i < lines.length; i++) {\n            const l = lines[i] ?? '';\n            if (!l.startsWith('|')) {\n                cur = null;\n                continue;\n            }\n            if (!cur) {\n                const next = lines[i + 1] ?? '';\n                if (/^\\|?\\s*:?-{2,}/.test(next)) {\n                    cur = { header: cells(l), rows: [] };\n                    tables.push(cur);\n                    i++;\n                }\n                continue;\n            }\n            cur.rows.push(cells(l));\n        }\n        return tables;\n    };\n    // Every table that has this column, with \"None found\" rows left out.\n    // Returns that column's cells from every such table, or null if no table has it.\n    const findColumn = (name) => {\n        let seen = false;\n        const cells = [];\n        for (const t of readTable(work)) {\n            const i = t.header.findIndex((h) => norm(h) === norm(name));\n            if (i < 0)\n                continue;\n            seen = true;\n            for (const r of t.rows) {\n                if (r.some((c) => norm(c) === 'none found'))\n                    continue;\n                cells.push(r[i] ?? '');\n            }\n        }\n        return seen ? cells : null;\n    };\n    const empty = (c) => !c || /^[-\\u2013\\u2014\\s]*$/.test(c);\n    switch (rule) {\n        case 'has_text':\n            return norm(work).includes(norm(value))\n                ? { pass: true, reason: 'Found.' }\n                : { pass: false, reason: `The answer does not include \"${value}\".` };\n        case 'lacks_text':\n            return norm(work).includes(norm(value))\n                ? { pass: false, reason: `The answer includes \"${value}\", which it should not.` }\n                : { pass: true, reason: 'Not found, as it should be.' };\n        case 'max_words': {\n            const n = String(work ?? '')\n                .split(/\\s+/)\n                .filter(Boolean).length;\n            return n <= Number(value)\n                ? { pass: true, reason: `${n} words.` }\n                : { pass: false, reason: `The answer has ${n} words; the limit is ${value}.` };\n        }\n        case 'table_cells_filled': {\n            const problems = [];\n            for (const col of value) {\n                const f = findColumn(col);\n                if (!f) {\n                    problems.push(`No table with a \"${col}\" column was found.`);\n                    continue;\n                }\n                f.forEach((c, i) => {\n                    if (empty(c))\n                        problems.push(`Row ${i + 1} has nothing in \"${col}\".`);\n                });\n            }\n            return problems.length\n                ? { pass: false, reason: problems.join(' ') }\n                : { pass: true, reason: 'Every row is filled in.' };\n        }\n        case 'column_values_in': {\n            const f = findColumn(value.column);\n            if (!f)\n                return { pass: false, reason: `No table with a \"${value.column}\" column was found.` };\n            const allowed = value.allowed.map(norm);\n            const bad = [];\n            f.forEach((c, i) => {\n                if (!allowed.includes(norm(c)))\n                    bad.push(`Row ${i + 1} says \"${c}\".`);\n            });\n            return bad.length\n                ? {\n                    pass: false,\n                    reason: `The \"${value.column}\" column must say ${value.allowed.join(' or ')}. ${bad.join(' ')}`,\n                }\n                : { pass: true, reason: 'Every row uses an allowed word.' };\n        }\n        case 'column_quotes_source': {\n            const f = findColumn(value.column);\n            if (!f)\n                return { pass: false, reason: `No table with a \"${value.column}\" column was found.` };\n            const src = norm(sources[value.input] ?? '');\n            const bad = [];\n            f.forEach((c, i) => {\n                // A quote may be shortened with \"...\"; each piece must be in the source.\n                const pieces = c\n                    .split(/\\.\\.\\.|\\u2026/)\n                    .map(norm)\n                    .filter(Boolean);\n                if (!pieces.length || !pieces.every((p) => src.includes(p)))\n                    bad.push(`Row ${i + 1}: \"${c}\" is not in the source.`);\n            });\n            return bad.length\n                ? { pass: false, reason: bad.join(' ') }\n                : { pass: true, reason: 'Every quote was found in the source.' };\n        }\n        case 'numbers_in_source': {\n            const nums = (s) => (String(s ?? '').match(/\\d[\\d,]*(?:\\.\\d+)?/g) ?? []).map((n) => n.replace(/,/g, ''));\n            const src = new Set(nums(sources[value.input] ?? ''));\n            const missing = [...new Set(nums(work))].filter((n) => !src.has(n));\n            return missing.length\n                ? {\n                    pass: false,\n                    reason: `These numbers are not in the source: ${missing.join(', ')}.`,\n                }\n                : { pass: true, reason: 'Every number was found in the source.' };\n        }\n        default:\n            return { pass: false, reason: `Unknown rule \"${rule}\".` };\n    }\n};\n\nconst work = String($(\"3. Write the one-minute record\").first().json.text ?? '');\nconst sources = {\n\n};\nconst result = runRule(\"has_text\", \"Ask the person who decided to confirm this before you file it.\", work, sources);\nreturn [{ json: result }];"
      },
      "id": "14e6989e-8177-45a7-ae63-f4e4884a0a25",
      "name": "Check 5: The record ends with the reminder to confirm it",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3340,
        320
      ],
      "notes": "The answer includes the words \"Ask the person who decided to confirm this before you file it.\".",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "872f65d9-6f63-4cb8-a9fb-c5d74e70ac96",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "397f72ca-f1d2-4739-a822-5e7c3f6c2b4b",
      "name": "Passed check 5?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        3600,
        320
      ],
      "notes": "True = passed. False = did not pass.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "538835b6-38a3-4f03-a342-4684a1ace531",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "11b261d2-ef91-425f-a948-a4543bddd2c1",
      "name": "Try again? (check 5)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        3600,
        520
      ],
      "notes": "True = first failure: send back to \"3. Write the one-minute record\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "9fd8da4b-1a9a-40fa-a956-9b05d83eab64",
              "name": "flag",
              "value": "=The record ends with the reminder to confirm it: did not pass. {{ $(\"Check 5: The record ends with the reminder to confirm it\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "e30d81e7-6e31-4f8e-a8fc-d5817f08ad70",
      "name": "Flag check 5",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        3730,
        700
      ],
      "notes": "Notes the problem and carries on. The note shows in Result.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "promptType": "define",
        "hasOutputParser": false,
        "text": "=You are a careful reviewer. You do not fix or rewrite the work; you only decide whether it passes one check.\n\nThe check: Every option that was ruled out is in the record with its reason\nCompare the record with the options table below. Is every option marked \"ruled out\" named in the record, each with the reason the table gives, and is every option marked \"not clear\" listed under \"Not in the notes\"? Answer FAIL and name each option that is missing or has a different reason.\n\nThe options:\n{{ $(\"1. List every option and how it came out\").first().json.text }}\n\nThe work to check (the answer from Step 3 (Write the one-minute record)):\n{{ $(\"3. Write the one-minute record\").first().json.text }}\n\nReply with exactly PASS or FAIL on the first line. On the second line, say in one sentence why. If it fails, name the exact item or line that fails. Do not add anything else."
      },
      "id": "2db1cabb-2757-4c00-a94e-cf65e882ceea",
      "name": "Check 6: Every option that was ruled out is in the record with its reason",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        3860,
        320
      ],
      "notes": "Answers PASS or FAIL. Compare the record with the options table below. Is every option marked \"ruled out\" named in the record, each with the reason the table gives, and is every option marked \"not clear\" listed under \"Not in the notes\"? Answer FAIL and name each option that is missing or has a different reason.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "862f6446-7063-4e4b-a8fb-c4444f70ae29",
              "leftValue": "={{ String($json.text ?? '').trim().toUpperCase().startsWith('PASS') }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "b977ed85-71da-4c7e-a824-9d133f69ecb4",
      "name": "Passed check 6?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        4120,
        320
      ],
      "notes": "True = passed. False = did not pass.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "54883749-37a3-4d70-a442-4817a0ace39e",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "09b493d1-6f99-47a4-a941-1f0f43dba0c2",
      "name": "Try again? (check 6)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        4120,
        520
      ],
      "notes": "True = first failure: send back to \"3. Write the one-minute record\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "9ed8d8b8-1b9a-428d-a856-9972d93eacf7",
              "name": "flag",
              "value": "=Every option that was ruled out is in the record with its reason: did not pass. {{ $(\"Check 6: Every option that was ruled out is in the record with its reason\").first().json.text }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "e40d837a-6d31-4dfb-a5fc-d0c88208b229",
      "name": "Flag check 6",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        4250,
        700
      ],
      "notes": "Notes the problem and carries on. The note shows in Result.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "promptType": "define",
        "hasOutputParser": false,
        "text": "=You are a careful reviewer. You do not fix or rewrite the work; you only decide whether it passes one check.\n\nThe check: Every reason is one the notes give\nRead the \"Why\" and \"Ruled out\" sections. Does any line give a reason the notes do not give, or name who decided, or give a date, when the notes do not? Answer FAIL and quote each line that does.\n\nThe notes:\n{{ $(\"Inputs\").first().json[\"notes\"] }}\n\nThe work to check (the answer from Step 3 (Write the one-minute record)):\n{{ $(\"3. Write the one-minute record\").first().json.text }}\n\nReply with exactly PASS or FAIL on the first line. On the second line, say in one sentence why. If it fails, name the exact item or line that fails. Do not add anything else."
      },
      "id": "ba5f03bf-58b3-4e8c-a569-aac5ca39ec4a",
      "name": "Check 7: Every reason is one the notes give",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        4380,
        320
      ],
      "notes": "Answers PASS or FAIL. Read the \"Why\" and \"Ruled out\" sections. Does any line give a reason the notes do not give, or name who decided, or give a date, when the notes do not? Answer FAIL and quote each line that does.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "852f62b3-7163-4fde-abfb-c8fd4c70a970",
              "leftValue": "={{ String($json.text ?? '').trim().toUpperCase().startsWith('PASS') }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "397af59c-71d7-4de7-a827-a52abf66e49d",
      "name": "Passed check 7?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        4640,
        320
      ],
      "notes": "True = passed. False = did not pass.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "51883290-3aa4-4229-a542-49aa9face20b",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "2db70b14-8b97-4521-ad43-fb121fd9297f",
      "name": "Try again? (check 7)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        4640,
        520
      ],
      "notes": "True = first failure: send back to \"3. Write the one-minute record\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "a1d8dd71-189a-4dd4-a756-97dfda3eae8a",
              "name": "flag",
              "value": "=Every reason is one the notes give: did not pass. {{ $(\"Check 7: Every reason is one the notes give\").first().json.text }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "e50d850d-6c31-4c68-a6fc-d25b8108b096",
      "name": "Flag check 7",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        4770,
        700
      ],
      "notes": "Notes the problem and carries on. The note shows in Result.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "content": "**Check 8: check by hand**\nShow the record to the person who made the decision. Ask whether it says what they decided and why, and whether any ruled-out option is shown unfairly. Only they know what they meant.\n\nIf not: flag it and carry on. This check is also listed in Result.",
        "height": 200,
        "width": 240,
        "color": 3
      },
      "id": "1373cc2b-c1fa-49a8-ae10-f211c740de76",
      "name": "Check 8 (by hand)",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        4620,
        70
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "0c439932-da63-4b1d-a167-ec0033cc6d93",
              "name": "result",
              "value": "={{ $(\"3. Write the one-minute record\").first().json.text }}",
              "type": "string"
            },
            {
              "id": "734ba06b-e691-4b02-ae67-dd4196023b58",
              "name": "flagged",
              "value": "={{ [$(\"Flag check 1\").isExecuted ? $(\"Flag check 1\").first().json.flag : \"\", $(\"Flag check 2\").isExecuted ? $(\"Flag check 2\").first().json.flag : \"\", $(\"Flag check 3\").isExecuted ? $(\"Flag check 3\").first().json.flag : \"\", $(\"Flag check 4\").isExecuted ? $(\"Flag check 4\").first().json.flag : \"\", $(\"Flag check 5\").isExecuted ? $(\"Flag check 5\").first().json.flag : \"\", $(\"Flag check 6\").isExecuted ? $(\"Flag check 6\").first().json.flag : \"\", $(\"Flag check 7\").isExecuted ? $(\"Flag check 7\").first().json.flag : \"\"].filter(x => x).join(\"\\n\") || \"None\" }}",
              "type": "string"
            },
            {
              "id": "3ad7ff01-96a2-42f0-aca0-95c733c91356",
              "name": "check_by_hand",
              "value": "- Show the record to the person who made the decision. Ask whether it says what they decided and why, and whether any ruled-out option is shown unfairly. Only they know what they meant.",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "7350e527-d3ac-4806-a2d3-2c6da256d8b4",
      "name": "Result",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        4900,
        320
      ],
      "notes": "The final answer, anything flagged, and the checks left for you.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "options": {}
      },
      "id": "37a2947d-969c-45d0-a214-58e3a1c36516",
      "name": "Chat model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "typeVersion": 1.2,
      "position": [
        480,
        840
      ],
      "notes": "Attach your credential here, or swap in any chat model node.",
      "notesInFlow": true
    }
  ],
  "connections": {
    "Start": {
      "main": [
        [
          {
            "node": "Inputs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Inputs": {
      "main": [
        [
          {
            "node": "1. List every option and how it came out",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "1. List every option and how it came out": {
      "main": [
        [
          {
            "node": "Check 1: Every outcome is chosen, ruled out or not clear",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 1: Every outcome is chosen, ruled out or not clear": {
      "main": [
        [
          {
            "node": "Passed check 1?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 1?": {
      "main": [
        [
          {
            "node": "2. Pull out who decided, when, why and when to look again",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 1)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 1)": {
      "main": [
        [
          {
            "node": "1. List every option and how it came out",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 1": {
      "main": [
        [
          {
            "node": "2. Pull out who decided, when, why and when to look again",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "2. Pull out who decided, when, why and when to look again": {
      "main": [
        [
          {
            "node": "Check 2: Every quote is word for word from the notes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 2: Every quote is word for word from the notes": {
      "main": [
        [
          {
            "node": "Passed check 2?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 2?": {
      "main": [
        [
          {
            "node": "3. Write the one-minute record",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 2)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 2)": {
      "main": [
        [
          {
            "node": "2. Pull out who decided, when, why and when to look again",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 2": {
      "main": [
        [
          {
            "node": "3. Write the one-minute record",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "3. Write the one-minute record": {
      "main": [
        [
          {
            "node": "Check 3: The record is 170 words or fewer",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 3: The record is 170 words or fewer": {
      "main": [
        [
          {
            "node": "Passed check 3?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 3?": {
      "main": [
        [
          {
            "node": "Check 4: Every number in the record is in the notes",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 3)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 3)": {
      "main": [
        [
          {
            "node": "3. Write the one-minute record",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 3": {
      "main": [
        [
          {
            "node": "Check 4: Every number in the record is in the notes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 4: Every number in the record is in the notes": {
      "main": [
        [
          {
            "node": "Passed check 4?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 4?": {
      "main": [
        [
          {
            "node": "Check 5: The record ends with the reminder to confirm it",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 4)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 4)": {
      "main": [
        [
          {
            "node": "3. Write the one-minute record",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 4",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 4": {
      "main": [
        [
          {
            "node": "Check 5: The record ends with the reminder to confirm it",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 5: The record ends with the reminder to confirm it": {
      "main": [
        [
          {
            "node": "Passed check 5?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 5?": {
      "main": [
        [
          {
            "node": "Check 6: Every option that was ruled out is in the record with its reason",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 5)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 5)": {
      "main": [
        [
          {
            "node": "3. Write the one-minute record",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 5",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 5": {
      "main": [
        [
          {
            "node": "Check 6: Every option that was ruled out is in the record with its reason",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 6: Every option that was ruled out is in the record with its reason": {
      "main": [
        [
          {
            "node": "Passed check 6?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 6?": {
      "main": [
        [
          {
            "node": "Check 7: Every reason is one the notes give",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 6)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 6)": {
      "main": [
        [
          {
            "node": "3. Write the one-minute record",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 6",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 6": {
      "main": [
        [
          {
            "node": "Check 7: Every reason is one the notes give",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 7: Every reason is one the notes give": {
      "main": [
        [
          {
            "node": "Passed check 7?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 7?": {
      "main": [
        [
          {
            "node": "Result",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 7)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 7)": {
      "main": [
        [
          {
            "node": "3. Write the one-minute record",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 7",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 7": {
      "main": [
        [
          {
            "node": "Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Chat model": {
      "ai_languageModel": [
        [
          {
            "node": "1. List every option and how it came out",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "2. Pull out who decided, when, why and when to look again",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "3. Write the one-minute record",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "Check 6: Every option that was ruled out is in the record with its reason",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "Check 7: Every reason is one the notes give",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "meta": {
    "template": "write-up-a-decision-in-one-minute"
  }
}
