{
  "name": "Write an incident report",
  "nodes": [
    {
      "parameters": {
        "content": "## Write an incident report\nTurn your rough notes on something that went wrong into a factual, blame-free incident report, with the timeline kept apart from the likely cause, and with personal and health details flagged for removal.\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): Each quote in the Event (quote) column appears in the your notes on what happened, with shortened fragments in the same order. If it fails: try once more, then flag.\n- Check 2 (a plain rule, run automatically with no AI): Each quote in the Statement (quote) column appears in the your notes on what happened, with shortened fragments in the same order. If it fails: try once more, then flag.\n- Check 3 (a plain rule, run automatically with no AI): Every number in the answer also appears somewhere in at least one of \"your notes on what happened\"; this does not check what each number means. If it fails: try once more, then flag.\n- Check 4 (a plain rule, run automatically with no AI): Every row of the table has one of \"blame\", \"guess stated as fact\", \"name\", \"personal or health detail\" or \"none\" in the Concern column. If it fails: try once more, then flag.\n- Check 5 (a plain rule, run automatically with no AI): The answer contains the text fragment \"Check this report with the people who were there and with your own policy before you file it.\" (including inside a longer word). If it fails: try once more, then flag.\n- Check 6 (a plain rule, run automatically with no AI): Every row of the table has one of \"likely cause\", \"opinion about a person\", \"personal or health detail\", \"impact\" or \"none\" in the Type column. If it fails: try once more, then flag.\n- Check 7 (a second AI prompt answers pass or fail): Read the Timeline and Summary sections of the report. Does either one state a cause, a reason or a guess about why something happened, rather than only what happened? Answer FAIL and quote each place where it does. If it fails: try once more, then flag.\n- Check 8 (a second AI prompt answers pass or fail): Look at the table below. For each row marked leave out, or with a concern of name or personal or health detail, does that detail still appear in the final report, in the same words or in other words? Also read the final report for any first name, last name, or detail about someone's body, health or family. Also check that any injury under Impact says only that an injury was reported and where, with no diagnosis or treatment. Answer FAIL and quote each place where one is left. If it fails: try once more, then flag.\n- Check 9 (a second AI prompt answers pass or fail): Read the final report. Does any line judge a person's effort, care, speed or character, or say a person caused the incident? Answer FAIL and quote each place where one does. Wording about a process or a condition, such as a wet ramp, is fine. If it fails: try once more, then flag.\n- Check 10 (a second AI prompt answers pass or fail): Compare the Timeline in the final report with the table below. Is every event in the table in the report, in the same order, with the same time? Answer FAIL and quote each event that is missing, changed or out of order. If it fails: try once more, then flag.\n- Check 11 (you check it by hand): Ask someone who was there to read the report and say whether the timeline matches what they remember. Check that nothing blames a person and that no private detail is left. Check the report against your own policy for what must be reported, to whom and by when, and have your safety, HR or legal lead look at it if the incident involved an injury, a complaint or personal data. Only a person can say what your policy and the law require. If it fails: flag it and carry on.\n\nFrom Logic Lab (logiclabhq.com). Free to use and adapt, with credit.",
        "height": 1140,
        "width": 760,
        "color": 5
      },
      "id": "3de95395-f64e-4848-aeca-914ff801ef42",
      "name": "About this workflow",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -40,
        -1100
      ]
    },
    {
      "parameters": {},
      "id": "e6e7c51d-dd40-40b4-ae12-cf6fc9c084f6",
      "name": "Start",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        0,
        320
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "cad842fa-1bed-4299-a280-82fcd004b5ab",
              "name": "incident_notes",
              "value": "Sample: Thursday March 12, loading bay at the warehouse. Around 2:15 pm a pallet of boxes slid off the pallet jack on the ramp and hit the floor. Nobody was hit. The operator, Sam, was rushing because the truck was due at 2:30, and he has a bad back so he probably could not hold it. The pallet had not been wrapped. The ramp was wet from the morning clean. Sam stopped work and told the shift lead about 2:16. Shift lead roped off the area at 2:25. Eight boxes damaged. Bay shut for 40 minutes.",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "3c00aaf2-2ac5-443d-a23e-5e4851e1a2fb",
      "name": "Inputs",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        220,
        320
      ],
      "notes": "Paste your own material here: incident_notes = Your notes on what happened.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "promptType": "define",
        "hasOutputParser": false,
        "text": "=You are a careful incident reviewer who writes down what happened and nothing more.\n\nYour task is to list every event in the notes below, in the order it happened.\n\nThe notes:\n{{ $(\"Inputs\").first().json[\"incident_notes\"] }}\n\nRules:\n- An event is something that happened or was done, such as a pallet falling or an area being closed off. Do not list opinions, guesses or reasons as events.\n- Quote each event from the notes word for word, as short as you can. Do not correct it and do not reword it.\n- In the When column, give the time or day exactly as the notes give it. If the notes give none, write not stated. Do not work out or guess a time.\n- One row for each separate event. Do not merge two events and do not add one the notes do not have.\n\nReturn one markdown table and nothing else:\n| # | When | Event (quote) |{{ $(\"Check 1: Every event is quoted word for word from your notes\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Every event is quoted word for word from your notes. What was wrong: \" + $(\"Check 1: Every event is quoted word for word from your notes\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}"
      },
      "id": "fca8d6c6-cf87-43bd-a629-6d7447d1094b",
      "name": "1. List what happened in order",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        480,
        320
      ],
      "notes": "Pick out each event from the notes, quoted word for word, with its time where the notes give one, so the report has a fixed list of facts to build on. Returns: A numbered table of events in order, each quoted from your notes.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// Every event is quoted word for word from your notes\n// Rule: Each quote in the Event (quote) column appears in the your notes on what happened, with shortened fragments in the same order.\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                if (f.length === 0) {\n                    problems.push(`The table has no data rows to check in \"${col}\".`);\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            if (f.length === 0)\n                return { pass: false, reason: `The table has no data rows to check in \"${value.column}\".` };\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            if (f.length === 0)\n                return { pass: false, reason: `The table has no data rows to check in \"${value.column}\".` };\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                let cursor = 0;\n                const ordered = pieces.every((p) => {\n                    const found = src.indexOf(p, cursor);\n                    if (found < 0)\n                        return false;\n                    cursor = found + p.length;\n                    return true;\n                });\n                if (!pieces.length || !ordered)\n                    bad.push(`Row ${i + 1}: \"${c}\" is not in the source in that order.`);\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 ids = Array.isArray(value.inputs) ? value.inputs : [value.input];\n            const src = new Set(ids.flatMap((id) => nums(sources[id] ?? '')));\n            // With a column named, only the numbers in that column of a table are compared (a rank column is not a claim).\n            let target = work;\n            if (value.column) {\n                const cells = findColumn(String(value.column));\n                if (cells === null)\n                    return { pass: false, reason: `The answer has no table with a ${value.column} column.` };\n                target = cells.join(' ');\n            }\n            const missing = [...new Set(nums(target))].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 what happened in order\").first().json.text ?? '');\nconst sources = {\n  \"incident_notes\": String($(\"Inputs\").first().json[\"incident_notes\"] ?? ''),\n};\nconst result = runRule(\"column_quotes_source\", {\"column\":\"Event (quote)\",\"input\":\"incident_notes\"}, work, sources);\nreturn [{ json: result }];"
      },
      "id": "472cabd8-741a-4fdf-aa5c-ccfab7714de9",
      "name": "Check 1: Every event is quoted word for word from your notes",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        740,
        320
      ],
      "notes": "Each quote in the Event (quote) column appears in the your notes on what happened, with shortened fragments in the same order.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "48324fec-3853-4faf-a0c1-4c02722173fd",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "f1334c95-c039-4744-af50-ce871c39b62e",
      "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": "6d011897-6977-439c-a093-2f1d87eab1ba",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "ef1ebb83-ccf8-4f7c-a1a6-8ffd322db146",
      "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 what happened in order\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "8e1c7206-7f25-403d-a899-ccb8dc0941bf",
              "name": "flag",
              "value": "=Every event is quoted word for word from your notes: did not pass. {{ $(\"Check 1: Every event is quoted word for word from your notes\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "c06acf42-8619-4761-a939-a49c22aac913",
      "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 fair-minded reviewer who keeps what happened apart from why it happened.\n\nYour task is to sort every statement in the notes below that is not a plain event.\n\nThe notes:\n{{ $(\"Inputs\").first().json[\"incident_notes\"] }}\n\nThe events already listed:\n{{ $(\"1. List what happened in order\").first().json.text }}\n\nRules:\n- Look for statements about why something happened, guesses (\"probably\", \"must have\"), opinions about a person, and personal or health details about anyone.\n- Quote each statement from the notes word for word, as short as you can. One row for each statement. If there are none, write one row with none in the Statement column.\n- Type must be exactly one of: likely cause, opinion about a person, personal or health detail, impact, none.\n- Use likely cause for a reason about the conditions or the process. Use opinion about a person for a judgment about someone's effort, care, speed or character. Use personal or health detail for background about someone's body, health history, family or private life that is not part of what happened. An injury the incident caused is impact: record only that an injury was reported and where, and add an Open Question to take the medical detail to your separate protected record. Use impact for what was damaged, closed or lost, and for an injury the incident caused.\n- In the Use column, write keep as a likely cause, keep as impact (including an injury the incident caused), leave out, or none. An opinion about a person and a personal or health detail are always leave out.\n- Do not judge the people who wrote the notes.\n\nReturn one markdown table and nothing else:\n| # | Statement (quote) | Type | Use |{{ $(\"Check 2: Every statement is quoted word for word from your notes\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Every statement is quoted word for word from your notes. What was wrong: \" + $(\"Check 2: Every statement is quoted word for word from your notes\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}{{ $(\"Check 6: Every statement type is one of the five allowed words\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Every statement type is one of the five allowed words. What was wrong: \" + $(\"Check 6: Every statement type is one of the five allowed words\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}"
      },
      "id": "cdb59d6b-aaa1-4a72-a2fe-7e65f2e5b85c",
      "name": "2. Separate facts from guesses and details to protect",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        1260,
        320
      ],
      "notes": "Sort every statement in the notes into a fact, a likely cause, an opinion about a person, or a personal or health detail, so each one goes in the right place or is left out. Returns: A table of causes, impacts, opinions and private details, each with what to do with it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// Every statement is quoted word for word from your notes\n// Rule: Each quote in the Statement (quote) column appears in the your notes on what happened, with shortened fragments in the same order.\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                if (f.length === 0) {\n                    problems.push(`The table has no data rows to check in \"${col}\".`);\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            if (f.length === 0)\n                return { pass: false, reason: `The table has no data rows to check in \"${value.column}\".` };\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            if (f.length === 0)\n                return { pass: false, reason: `The table has no data rows to check in \"${value.column}\".` };\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                let cursor = 0;\n                const ordered = pieces.every((p) => {\n                    const found = src.indexOf(p, cursor);\n                    if (found < 0)\n                        return false;\n                    cursor = found + p.length;\n                    return true;\n                });\n                if (!pieces.length || !ordered)\n                    bad.push(`Row ${i + 1}: \"${c}\" is not in the source in that order.`);\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 ids = Array.isArray(value.inputs) ? value.inputs : [value.input];\n            const src = new Set(ids.flatMap((id) => nums(sources[id] ?? '')));\n            // With a column named, only the numbers in that column of a table are compared (a rank column is not a claim).\n            let target = work;\n            if (value.column) {\n                const cells = findColumn(String(value.column));\n                if (cells === null)\n                    return { pass: false, reason: `The answer has no table with a ${value.column} column.` };\n                target = cells.join(' ');\n            }\n            const missing = [...new Set(nums(target))].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. Separate facts from guesses and details to protect\").first().json.text ?? '');\nconst sources = {\n  \"incident_notes\": String($(\"Inputs\").first().json[\"incident_notes\"] ?? ''),\n};\nconst result = runRule(\"column_quotes_source\", {\"column\":\"Statement (quote)\",\"input\":\"incident_notes\"}, work, sources);\nreturn [{ json: result }];"
      },
      "id": "c9208d92-e9c4-4491-aec5-ecc07266fe37",
      "name": "Check 2: Every statement is quoted word for word from your notes",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1520,
        320
      ],
      "notes": "Each quote in the Statement (quote) column appears in the your notes on what happened, with shortened fragments in the same order.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "4932517f-3753-4e1c-a1c1-4d957121726a",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "713ad1da-c03b-45db-af4d-c6709c3230e9",
      "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": "6c011704-6a77-452f-af93-2d8a88eab34d",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "6f2640c8-acf1-4157-a9a3-adae5a302ed5",
      "name": "Try again? (check 2)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1780,
        520
      ],
      "notes": "True = first failure: send back to \"2. Separate facts from guesses and details to protect\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "8f1c7399-7e25-4eaa-a999-ce4bdb09402c",
              "name": "flag",
              "value": "=Every statement is quoted word for word from your notes: did not pass. {{ $(\"Check 2: Every statement is quoted word for word from your notes\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "bf6acdaf-8319-42a8-ac39-a95523aacaa6",
      "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 an experienced safety and operations writer who produces plain, factual incident reports.\n\nYour task is to write an incident report from the notes, the timeline and the sorted statements below.\n\nThe notes:\n{{ $(\"Inputs\").first().json[\"incident_notes\"] }}\n\nThe timeline:\n{{ $(\"1. List what happened in order\").first().json.text }}\n\nThe sorted statements:\n{{ $(\"2. Separate facts from guesses and details to protect\").first().json.text }}\n\nRules:\n- Use these headings in this order: Summary, Timeline, Who Was Involved, Immediate Response, Impact, Likely Cause, Follow-Up, Open Questions.\n- Summary is two or three sentences of fact. Timeline lists the events from the timeline table, one line each, with the time where one is given.\n- Who Was Involved uses roles only, such as \"the operator\" and \"the shift lead\". Do not use a first name or a last name.\n- Under Likely Cause, use only statements marked keep as likely cause, and begin with: This is a likely cause, not a confirmed one.\n- Under Impact, use only statements marked keep as impact.\n- Leave out every statement marked leave out. Do not mention health, family or private details, and do not describe anyone's effort, care or speed.\n- Under Follow-Up, write one action for each likely cause, with an owner role. For the date, use only a date in the notes. If the notes give none, write date to be set. Do not invent a date, a number or a time.\n- If an impact statement describes an injury, write only that an injury was reported and where on the body, with no diagnosis or treatment, and add under Open Questions: Record the medical details in your separate protected record, not in this report.\n- Under Open Questions, list what the notes do not say and a person should find out, such as a time marked not stated.\n- End with exactly this line: Check this report with the people who were there and with your own policy before you file it.\n\nReturn the report in markdown and nothing else.{{ $(\"Check 3: No number appears that your notes do not have\").isExecuted ? \"\\n\\nYour last answer did not pass this check: No number appears that your notes do not have. What was wrong: \" + $(\"Check 3: No number appears that your notes do not have\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}{{ $(\"Check 7: The likely cause stays out of the timeline\").isExecuted ? \"\\n\\nYour last answer did not pass this check: The likely cause stays out of the timeline. What was wrong: \" + $(\"Check 7: The likely cause stays out of the timeline\").first().json.text + \"\\nFix that and give your full answer again.\" : \"\" }}"
      },
      "id": "7d78872e-abde-4b69-a425-41c4e81fec2f",
      "name": "3. Draft the incident report",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        2040,
        320
      ],
      "notes": "Write the report from the timeline and the kept statements only, with the facts and the likely cause under separate headings. Returns: An incident report with a timeline, an impact, a separate likely cause, follow-up actions and open questions.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// No number appears that your notes do not have\n// Rule: Every number in the answer also appears somewhere in at least one of \"your notes on what happened\"; this does not check what each number means.\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                if (f.length === 0) {\n                    problems.push(`The table has no data rows to check in \"${col}\".`);\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            if (f.length === 0)\n                return { pass: false, reason: `The table has no data rows to check in \"${value.column}\".` };\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            if (f.length === 0)\n                return { pass: false, reason: `The table has no data rows to check in \"${value.column}\".` };\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                let cursor = 0;\n                const ordered = pieces.every((p) => {\n                    const found = src.indexOf(p, cursor);\n                    if (found < 0)\n                        return false;\n                    cursor = found + p.length;\n                    return true;\n                });\n                if (!pieces.length || !ordered)\n                    bad.push(`Row ${i + 1}: \"${c}\" is not in the source in that order.`);\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 ids = Array.isArray(value.inputs) ? value.inputs : [value.input];\n            const src = new Set(ids.flatMap((id) => nums(sources[id] ?? '')));\n            // With a column named, only the numbers in that column of a table are compared (a rank column is not a claim).\n            let target = work;\n            if (value.column) {\n                const cells = findColumn(String(value.column));\n                if (cells === null)\n                    return { pass: false, reason: `The answer has no table with a ${value.column} column.` };\n                target = cells.join(' ');\n            }\n            const missing = [...new Set(nums(target))].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. Draft the incident report\").first().json.text ?? '');\nconst sources = {\n\n};\nconst result = runRule(\"numbers_in_source\", {\"inputs\":[\"incident_notes\"]}, work, sources);\nreturn [{ json: result }];"
      },
      "id": "b42d754f-000b-4f1c-ae2c-6521e226623e",
      "name": "Check 3: No number appears that your notes do not have",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2300,
        320
      ],
      "notes": "Every number in the answer also appears somewhere in at least one of \"your notes on what happened\"; this does not check what each number means.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "4a325312-3a53-42d5-aec1-48dc702170d7",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "f137c9c3-c03d-4472-af4b-87d99c346f80",
      "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": "6f011bbd-6b77-46c2-ae93-2bf785eaae94",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "7b241515-d0f4-489a-a5a1-366b4e325a88",
      "name": "Try again? (check 3)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2560,
        520
      ],
      "notes": "True = first failure: send back to \"3. Draft the incident report\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "8c1c6ee0-7d25-4d17-aa99-cfdede0944e5",
              "name": "flag",
              "value": "=No number appears that your notes do not have: did not pass. {{ $(\"Check 3: No number appears that your notes do not have\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "be6acc1c-8419-443b-ab39-a7c224aacc39",
      "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": {
        "promptType": "define",
        "hasOutputParser": false,
        "text": "=You are a reviewer who reads incident reports for blame, guesses and private details.\n\nYour task is to find every phrase in the draft below that could blame a person, state a guess as a fact, name a person, or show a personal or health detail.\n\nThe draft:\n{{ $(\"3. Draft the incident report\").first().json.text }}\n\nRules:\n- Look for words that judge a person (\"careless\", \"rushed\", \"failed to\"), a cause stated as certain when the notes only guess it, a first or last name, and any health, family or private detail.\n- Quote each phrase from the draft word for word, as short as you can. One row for each phrase. If you find none, write one row with none in the Phrase column.\n- Concern must be exactly one of: blame, guess stated as fact, name, personal or health detail, none.\n- In the Replacement column, give plainer wording that says only what happened or names a role. Do not leave it empty. For a personal or health detail, write remove.\n- Do not judge the people who wrote the notes.\n\nReturn one markdown table and nothing else:\n| # | Phrase (quote) | Concern | Replacement |{{ $(\"Check 4: Each concern is one of the five allowed words\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Each concern is one of the five allowed words. What was wrong: \" + $(\"Check 4: Each concern is one of the five allowed words\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}"
      },
      "id": "a80f059e-2e3b-438d-aaa5-b228af9bd1f7",
      "name": "4. Find blame and private details in the draft",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        2820,
        320
      ],
      "notes": "List each phrase in the draft that puts the fault on a person, states a guess as a fact, names someone, or shows a personal or health detail, with a plainer replacement. Returns: A table of phrases that blame, guess, name or reveal, each with a replacement.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// Each concern is one of the five allowed words\n// Rule: Every row of the table has one of \"blame\", \"guess stated as fact\", \"name\", \"personal or health detail\" or \"none\" in the Concern 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                if (f.length === 0) {\n                    problems.push(`The table has no data rows to check in \"${col}\".`);\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            if (f.length === 0)\n                return { pass: false, reason: `The table has no data rows to check in \"${value.column}\".` };\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            if (f.length === 0)\n                return { pass: false, reason: `The table has no data rows to check in \"${value.column}\".` };\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                let cursor = 0;\n                const ordered = pieces.every((p) => {\n                    const found = src.indexOf(p, cursor);\n                    if (found < 0)\n                        return false;\n                    cursor = found + p.length;\n                    return true;\n                });\n                if (!pieces.length || !ordered)\n                    bad.push(`Row ${i + 1}: \"${c}\" is not in the source in that order.`);\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 ids = Array.isArray(value.inputs) ? value.inputs : [value.input];\n            const src = new Set(ids.flatMap((id) => nums(sources[id] ?? '')));\n            // With a column named, only the numbers in that column of a table are compared (a rank column is not a claim).\n            let target = work;\n            if (value.column) {\n                const cells = findColumn(String(value.column));\n                if (cells === null)\n                    return { pass: false, reason: `The answer has no table with a ${value.column} column.` };\n                target = cells.join(' ');\n            }\n            const missing = [...new Set(nums(target))].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($(\"4. Find blame and private details in the draft\").first().json.text ?? '');\nconst sources = {\n\n};\nconst result = runRule(\"column_values_in\", {\"column\":\"Concern\",\"allowed\":[\"blame\",\"guess stated as fact\",\"name\",\"personal or health detail\",\"none\"]}, work, sources);\nreturn [{ json: result }];"
      },
      "id": "5fe56265-f7f1-4de6-a1c6-37e380f5218c",
      "name": "Check 4: Each concern is one of the five allowed words",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3080,
        320
      ],
      "notes": "Every row of the table has one of \"blame\", \"guess stated as fact\", \"name\", \"personal or health detail\" or \"none\" in the Concern column.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "4b3254a5-3953-4142-afc1-4a6f6f216f44",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "713f4f08-4040-4c89-af49-49429c2db3bb",
      "name": "Passed check 4?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        3340,
        320
      ],
      "notes": "True = passed. False = did not pass.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "6e011a2a-6c77-4855-ad93-2a6486eab027",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "fb2b9a5a-50ec-4355-adb2-8c24b6353cd7",
      "name": "Try again? (check 4)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        3340,
        520
      ],
      "notes": "True = first failure: send back to \"4. Find blame and private details in the draft\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "8d1c7073-7c25-4b84-ab99-d171dd094352",
              "name": "flag",
              "value": "=Each concern is one of the five allowed words: did not pass. {{ $(\"Check 4: Each concern is one of the five allowed words\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "bd6aca89-8919-4c1a-a639-9fe325aacdcc",
      "name": "Flag check 4",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        3470,
        700
      ],
      "notes": "Notes the problem and carries on. The note shows in Result.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "promptType": "define",
        "hasOutputParser": false,
        "text": "=You are a copy editor who swaps listed phrases and changes nothing else.\n\nYour task is to write the final incident report from the draft, using the replacements in the table.\n\nThe draft:\n{{ $(\"3. Draft the incident report\").first().json.text }}\n\nThe flagged phrases and their replacements:\n{{ $(\"4. Find blame and private details in the draft\").first().json.text }}\n\nRules:\n- Replace each flagged phrase with its replacement. For a replacement of remove, delete the phrase and any words that only make sense with it.\n- Change nothing else. Keep every other word, number and heading as written. Do not add an event, a cause, a number or a date.\n- Start the answer with one line that begins \"Changes:\" and says how many phrases were replaced or removed.\n- Keep the closing line \"Check this report with the people who were there and with your own policy before you file it.\" as the very last line.\n\nReturn markdown in this form and nothing else:\nChanges: (the count)\n(the incident report)\nCheck this report with the people who were there and with your own policy before you file it.{{ $(\"Check 5: The reminder to check it with the people who were there is in the final text\").isExecuted ? \"\\n\\nYour last answer did not pass this check: The reminder to check it with the people who were there is in the final text. What was wrong: \" + $(\"Check 5: The reminder to check it with the people who were there is in the final text\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}{{ $(\"Check 8: No name, health detail or private detail is in the final text\").isExecuted ? \"\\n\\nYour last answer did not pass this check: No name, health detail or private detail is in the final text. What was wrong: \" + $(\"Check 8: No name, health detail or private detail is in the final text\").first().json.text + \"\\nFix that and give your full answer again.\" : \"\" }}{{ $(\"Check 9: No wording left that blames a person\").isExecuted ? \"\\n\\nYour last answer did not pass this check: No wording left that blames a person. What was wrong: \" + $(\"Check 9: No wording left that blames a person\").first().json.text + \"\\nFix that and give your full answer again.\" : \"\" }}{{ $(\"Check 10: Every event in the timeline table is in the final report\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Every event in the timeline table is in the final report. What was wrong: \" + $(\"Check 10: Every event in the timeline table is in the final report\").first().json.text + \"\\nFix that and give your full answer again.\" : \"\" }}"
      },
      "id": "32b1ccdc-dfed-488f-ac7b-22be77618d29",
      "name": "5. Write the final incident report",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        3600,
        320
      ],
      "notes": "Apply the replacements to the draft, change nothing else, and add a count of the changes. Returns: The final incident report and a count of the wording changes.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// The reminder to check it with the people who were there is in the final text\n// Rule: The answer contains the text fragment \"Check this report with the people who were there and with your own policy before you file it.\" (including inside a longer word).\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                if (f.length === 0) {\n                    problems.push(`The table has no data rows to check in \"${col}\".`);\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            if (f.length === 0)\n                return { pass: false, reason: `The table has no data rows to check in \"${value.column}\".` };\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            if (f.length === 0)\n                return { pass: false, reason: `The table has no data rows to check in \"${value.column}\".` };\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                let cursor = 0;\n                const ordered = pieces.every((p) => {\n                    const found = src.indexOf(p, cursor);\n                    if (found < 0)\n                        return false;\n                    cursor = found + p.length;\n                    return true;\n                });\n                if (!pieces.length || !ordered)\n                    bad.push(`Row ${i + 1}: \"${c}\" is not in the source in that order.`);\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 ids = Array.isArray(value.inputs) ? value.inputs : [value.input];\n            const src = new Set(ids.flatMap((id) => nums(sources[id] ?? '')));\n            // With a column named, only the numbers in that column of a table are compared (a rank column is not a claim).\n            let target = work;\n            if (value.column) {\n                const cells = findColumn(String(value.column));\n                if (cells === null)\n                    return { pass: false, reason: `The answer has no table with a ${value.column} column.` };\n                target = cells.join(' ');\n            }\n            const missing = [...new Set(nums(target))].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($(\"5. Write the final incident report\").first().json.text ?? '');\nconst sources = {\n\n};\nconst result = runRule(\"has_text\", \"Check this report with the people who were there and with your own policy before you file it.\", work, sources);\nreturn [{ json: result }];"
      },
      "id": "6fab93bd-2934-4efc-a285-d39f2158a6fe",
      "name": "Check 5: The reminder to check it with the people who were there is in the final text",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3860,
        320
      ],
      "notes": "The answer contains the text fragment \"Check this report with the people who were there and with your own policy before you file it.\" (including inside a longer word).",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "443249a0-3453-4963-acc1-45b66e216db1",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "713d1071-4043-4b20-af46-412b9c2ff252",
      "name": "Passed check 5?",
      "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": "6901124b-6577-4d50-ac93-28d183eaab6e",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "d7292317-44ee-4f08-a9b0-79a1da37b41a",
      "name": "Try again? (check 5)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        4120,
        520
      ],
      "notes": "True = first failure: send back to \"5. Write the final incident report\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "921c7852-7b25-49f1-ac99-d304d8093b73",
              "name": "flag",
              "value": "=The reminder to check it with the people who were there is in the final text: did not pass. {{ $(\"Check 5: The reminder to check it with the people who were there is in the final text\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "bc6ac8f6-8a19-4dad-a539-9e5026aacf5f",
      "name": "Flag check 5",
      "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": {
        "jsCode": "// Every statement type is one of the five allowed words\n// Rule: Every row of the table has one of \"likely cause\", \"opinion about a person\", \"personal or health detail\", \"impact\" or \"none\" in the Type 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                if (f.length === 0) {\n                    problems.push(`The table has no data rows to check in \"${col}\".`);\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            if (f.length === 0)\n                return { pass: false, reason: `The table has no data rows to check in \"${value.column}\".` };\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            if (f.length === 0)\n                return { pass: false, reason: `The table has no data rows to check in \"${value.column}\".` };\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                let cursor = 0;\n                const ordered = pieces.every((p) => {\n                    const found = src.indexOf(p, cursor);\n                    if (found < 0)\n                        return false;\n                    cursor = found + p.length;\n                    return true;\n                });\n                if (!pieces.length || !ordered)\n                    bad.push(`Row ${i + 1}: \"${c}\" is not in the source in that order.`);\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 ids = Array.isArray(value.inputs) ? value.inputs : [value.input];\n            const src = new Set(ids.flatMap((id) => nums(sources[id] ?? '')));\n            // With a column named, only the numbers in that column of a table are compared (a rank column is not a claim).\n            let target = work;\n            if (value.column) {\n                const cells = findColumn(String(value.column));\n                if (cells === null)\n                    return { pass: false, reason: `The answer has no table with a ${value.column} column.` };\n                target = cells.join(' ');\n            }\n            const missing = [...new Set(nums(target))].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. Separate facts from guesses and details to protect\").first().json.text ?? '');\nconst sources = {\n\n};\nconst result = runRule(\"column_values_in\", {\"column\":\"Type\",\"allowed\":[\"likely cause\",\"opinion about a person\",\"personal or health detail\",\"impact\",\"none\"]}, work, sources);\nreturn [{ json: result }];"
      },
      "id": "4fa0de62-b99a-4ec5-a06d-7b58e2cf8a83",
      "name": "Check 6: Every statement type is one of the five allowed words",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        4380,
        320
      ],
      "notes": "Every row of the table has one of \"likely cause\", \"opinion about a person\", \"personal or health detail\", \"impact\" or \"none\" in the Type column.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "45324b33-3353-47d0-adc1-47496d216c1e",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "f14495b6-4045-49b7-af44-02941c286d0d",
      "name": "Passed check 6?",
      "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": "680110b8-6677-4ee3-ab93-273e84eaad01",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "f730113c-c4e7-49c3-a1ad-7e22b239b3b9",
      "name": "Try again? (check 6)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        4640,
        520
      ],
      "notes": "True = first failure: send back to \"2. Separate facts from guesses and details to protect\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "931c79e5-7a25-485e-ad99-d497d70939e0",
              "name": "flag",
              "value": "=Every statement type is one of the five allowed words: did not pass. {{ $(\"Check 6: Every statement type is one of the five allowed words\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "bb6ac763-8719-48f4-a839-a30927aad0f2",
      "name": "Flag check 6",
      "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": {
        "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: The likely cause stays out of the timeline\nRead the Timeline and Summary sections of the report. Does either one state a cause, a reason or a guess about why something happened, rather than only what happened? Answer FAIL and quote each place where it does.\n\nThe work to check (the answer from Step 3 (Draft the incident report)):\n{{ $(\"3. Draft the incident report\").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": "c729861f-9a86-4646-a876-e39116738cf8",
      "name": "Check 7: The likely cause stays out of the timeline",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        4900,
        320
      ],
      "notes": "Answers PASS or FAIL. Read the Timeline and Summary sections of the report. Does either one state a cause, a reason or a guess about why something happened, rather than only what happened? Answer FAIL and quote each place where it does.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "46324cc6-3653-4c89-aac1-42906c216a8b",
              "leftValue": "={{ String($json.text ?? '').trim().toUpperCase().startsWith('PASS') }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "71418d9f-c048-41ce-af41-c3fd1c2aaba4",
      "name": "Passed check 7?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        5160,
        320
      ],
      "notes": "True = passed. False = did not pass.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "6b011571-6777-4076-aa93-25ab81eaa848",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "d32d99f9-28ea-45c6-adab-06dfd63c2afc",
      "name": "Try again? (check 7)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        5160,
        520
      ],
      "notes": "True = first failure: send back to \"3. Draft the incident report\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "901c752c-7925-46cb-ae99-d62ada093e99",
              "name": "flag",
              "value": "=The likely cause stays out of the timeline: did not pass. {{ $(\"Check 7: The likely cause stays out of the timeline\").first().json.text }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "ba6ac5d0-8819-4a87-a739-a17628aad285",
      "name": "Flag check 7",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        5290,
        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: No name, health detail or private detail is in the final text\nLook at the table below. For each row marked leave out, or with a concern of name or personal or health detail, does that detail still appear in the final report, in the same words or in other words? Also read the final report for any first name, last name, or detail about someone's body, health or family. Also check that any injury under Impact says only that an injury was reported and where, with no diagnosis or treatment. Answer FAIL and quote each place where one is left.\n\nThe sorted statements:\n{{ $(\"2. Separate facts from guesses and details to protect\").first().json.text }}\n\nThe work to check (the answer from Step 5 (Write the final incident report)):\n{{ $(\"5. Write the final incident report\").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": "2477412c-07be-4d97-af9c-dd763643d0a1",
      "name": "Check 8: No name, health detail or private detail is in the final text",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        5420,
        320
      ],
      "notes": "Answers PASS or FAIL. Look at the table below. For each row marked leave out, or with a concern of name or personal or health detail, does that detail still appear in the final report, in the same words or in other words? Also read the final report for any first name, last name, or detail about someone's body, health or family. Also check that any injury under Impact says only that an injury was reported and where, with no diagnosis or treatment. Answer FAIL and quote each place where one is left.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "47324e59-3553-4af6-abc1-44236b2168f8",
              "leftValue": "={{ String($json.text ?? '').trim().toUpperCase().startsWith('PASS') }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "f1220374-c04a-4065-af66-94d61c4aff4f",
      "name": "Passed check 8?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        5680,
        320
      ],
      "notes": "True = passed. False = did not pass.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "6a0113de-6877-4209-a993-241882eaa9db",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "f30d78ae-c8e2-42e1-a5bc-75c8ae171b2b",
      "name": "Try again? (check 8)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        5680,
        520
      ],
      "notes": "True = first failure: send back to \"5. Write the final incident report\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "911c76bf-7825-4538-af99-d7bdd9093d06",
              "name": "flag",
              "value": "=No name, health detail or private detail is in the final text: did not pass. {{ $(\"Check 8: No name, health detail or private detail is in the final text\").first().json.text }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "c96add6d-7d19-4936-a239-b2c729aad418",
      "name": "Flag check 8",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        5810,
        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: No wording left that blames a person\nRead the final report. Does any line judge a person's effort, care, speed or character, or say a person caused the incident? Answer FAIL and quote each place where one does. Wording about a process or a condition, such as a wet ramp, is fine.\n\nThe flagged phrases:\n{{ $(\"4. Find blame and private details in the draft\").first().json.text }}\n\nThe work to check (the answer from Step 5 (Write the final incident report)):\n{{ $(\"5. Write the final incident report\").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": "e15f8d65-f35d-4340-af67-f12f4fdee30a",
      "name": "Check 9: No wording left that blames a person",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        5940,
        320
      ],
      "notes": "Answers PASS or FAIL. Read the final report. Does any line judge a person's effort, care, speed or character, or say a person caused the incident? Answer FAIL and quote each place where one does. Wording about a process or a condition, such as a wet ramp, is fine.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "50325c84-4053-4c47-a8c1-589a6a216765",
              "leftValue": "={{ String($json.text ?? '').trim().toUpperCase().startsWith('PASS') }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "711efb5d-c04c-4efc-af64-563f1c4d3de6",
      "name": "Passed check 9?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        6200,
        320
      ],
      "notes": "True = passed. False = did not pass.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "65010bff-7177-4034-a893-22857feaa522",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "cf0b016b-ace5-4564-a1ba-4a15d219926e",
      "name": "Try again? (check 9)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        6200,
        520
      ],
      "notes": "True = first failure: send back to \"5. Write the final incident report\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "961c7e9e-7725-43a5-a099-c020e4094e57",
              "name": "flag",
              "value": "=No wording left that blames a person: did not pass. {{ $(\"Check 9: No wording left that blames a person\").first().json.text }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "c86adbda-7e19-4ac9-a139-b1342aaad5ab",
      "name": "Flag check 9",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        6330,
        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 event in the timeline table is in the final report\nCompare the Timeline in the final report with the table below. Is every event in the table in the report, in the same order, with the same time? Answer FAIL and quote each event that is missing, changed or out of order.\n\nThe timeline:\n{{ $(\"1. List what happened in order\").first().json.text }}\n\nThe work to check (the answer from Step 5 (Write the final incident report)):\n{{ $(\"5. Write the final incident report\").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": "7b403cc6-2d9e-4a45-a123-23ace49eaf33",
      "name": "Check 10: Every event in the timeline table is in the final report",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        6460,
        320
      ],
      "notes": "Answers PASS or FAIL. Compare the Timeline in the final report with the table below. Is every event in the table in the report, in the same order, with the same time? Answer FAIL and quote each event that is missing, changed or out of order.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "51325e17-3f53-4ab4-a9c1-5a2d692165d2",
              "leftValue": "={{ String($json.text ?? '').trim().toUpperCase().startsWith('PASS') }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "5dc3ee35-9514-46d2-a419-952bffcd9a50",
      "name": "Passed check 10?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        6720,
        320
      ],
      "notes": "True = passed. False = did not pass.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "64010a6c-7277-41c7-a793-20f280eaa6b5",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "b34b08e3-0483-4ffa-a946-44b1e12bde18",
      "name": "Try again? (check 10)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        6720,
        520
      ],
      "notes": "True = first failure: send back to \"5. Write the final incident report\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "971c8031-7625-4212-a199-c1b3e3094cc4",
              "name": "flag",
              "value": "=Every event in the timeline table is in the final report: did not pass. {{ $(\"Check 10: Every event in the timeline table is in the final report\").first().json.text }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "5a249076-6b7b-4483-a1be-3ac4b5daa219",
      "name": "Flag check 10",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        6850,
        700
      ],
      "notes": "Notes the problem and carries on. The note shows in Result.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "content": "**Check 11: check by hand**\nAsk someone who was there to read the report and say whether the timeline matches what they remember. Check that nothing blames a person and that no private detail is left. Check the report against your own policy for what must be reported, to whom and by when, and have your safety, HR or legal lead look at it if the incident involved an injury, a complaint or personal data. Only a person can say what your policy and the law require.\n\nIf not: flag it and carry on. This check is also listed in Result.",
        "height": 200,
        "width": 240,
        "color": 3
      },
      "id": "8cf9bb34-820a-4ccb-aaa7-b046f5c31e95",
      "name": "Check 11 (by hand)",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        6700,
        70
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "cedc7915-9096-403c-a8a7-e6334506b07a",
              "name": "result",
              "value": "={{ $(\"5. Write the final incident report\").first().json.text }}",
              "type": "string"
            },
            {
              "id": "ade4fb8a-d99d-4755-adbb-7878a4ded2c3",
              "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 : \"\", $(\"Flag check 8\").isExecuted ? $(\"Flag check 8\").first().json.flag : \"\", $(\"Flag check 9\").isExecuted ? $(\"Flag check 9\").first().json.flag : \"\", $(\"Flag check 10\").isExecuted ? $(\"Flag check 10\").first().json.flag : \"\"].filter(x => x).join(\"\\n\") || \"None\" }}",
              "type": "string"
            },
            {
              "id": "2bacd278-f349-42eb-aa3d-5d76382afff9",
              "name": "check_by_hand",
              "value": "- Ask someone who was there to read the report and say whether the timeline matches what they remember. Check that nothing blames a person and that no private detail is left. Check the report against your own policy for what must be reported, to whom and by when, and have your safety, HR or legal lead look at it if the incident involved an injury, a complaint or personal data. Only a person can say what your policy and the law require.",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "1614620e-eeae-4731-a552-94148389475f",
      "name": "Result",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        6980,
        320
      ],
      "notes": "The final answer, anything flagged, and the checks left for you.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "options": {}
      },
      "id": "d6aa25c8-2fc2-4567-abb9-fbf6afe78325",
      "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 what happened in order",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "1. List what happened in order": {
      "main": [
        [
          {
            "node": "Check 1: Every event is quoted word for word from your notes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 1: Every event is quoted word for word from your notes": {
      "main": [
        [
          {
            "node": "Passed check 1?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 1?": {
      "main": [
        [
          {
            "node": "2. Separate facts from guesses and details to protect",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 1)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 1)": {
      "main": [
        [
          {
            "node": "1. List what happened in order",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 1": {
      "main": [
        [
          {
            "node": "2. Separate facts from guesses and details to protect",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "2. Separate facts from guesses and details to protect": {
      "main": [
        [
          {
            "node": "Check 2: Every statement is quoted word for word from your notes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 2: Every statement is quoted word for word from your notes": {
      "main": [
        [
          {
            "node": "Passed check 2?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 2?": {
      "main": [
        [
          {
            "node": "3. Draft the incident report",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 2)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 2)": {
      "main": [
        [
          {
            "node": "2. Separate facts from guesses and details to protect",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 2": {
      "main": [
        [
          {
            "node": "3. Draft the incident report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "3. Draft the incident report": {
      "main": [
        [
          {
            "node": "Check 3: No number appears that your notes do not have",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 3: No number appears that your notes do not have": {
      "main": [
        [
          {
            "node": "Passed check 3?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 3?": {
      "main": [
        [
          {
            "node": "4. Find blame and private details in the draft",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 3)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 3)": {
      "main": [
        [
          {
            "node": "3. Draft the incident report",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 3": {
      "main": [
        [
          {
            "node": "4. Find blame and private details in the draft",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "4. Find blame and private details in the draft": {
      "main": [
        [
          {
            "node": "Check 4: Each concern is one of the five allowed words",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 4: Each concern is one of the five allowed words": {
      "main": [
        [
          {
            "node": "Passed check 4?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 4?": {
      "main": [
        [
          {
            "node": "5. Write the final incident report",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 4)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 4)": {
      "main": [
        [
          {
            "node": "4. Find blame and private details in the draft",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 4",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 4": {
      "main": [
        [
          {
            "node": "5. Write the final incident report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "5. Write the final incident report": {
      "main": [
        [
          {
            "node": "Check 5: The reminder to check it with the people who were there is in the final text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 5: The reminder to check it with the people who were there is in the final text": {
      "main": [
        [
          {
            "node": "Passed check 5?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 5?": {
      "main": [
        [
          {
            "node": "Check 6: Every statement type is one of the five allowed words",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 5)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 5)": {
      "main": [
        [
          {
            "node": "5. Write the final incident report",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 5",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 5": {
      "main": [
        [
          {
            "node": "Check 6: Every statement type is one of the five allowed words",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 6: Every statement type is one of the five allowed words": {
      "main": [
        [
          {
            "node": "Passed check 6?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 6?": {
      "main": [
        [
          {
            "node": "Check 7: The likely cause stays out of the timeline",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 6)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 6)": {
      "main": [
        [
          {
            "node": "2. Separate facts from guesses and details to protect",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 6",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 6": {
      "main": [
        [
          {
            "node": "Check 7: The likely cause stays out of the timeline",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 7: The likely cause stays out of the timeline": {
      "main": [
        [
          {
            "node": "Passed check 7?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 7?": {
      "main": [
        [
          {
            "node": "Check 8: No name, health detail or private detail is in the final text",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 7)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 7)": {
      "main": [
        [
          {
            "node": "3. Draft the incident report",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 7",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 7": {
      "main": [
        [
          {
            "node": "Check 8: No name, health detail or private detail is in the final text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 8: No name, health detail or private detail is in the final text": {
      "main": [
        [
          {
            "node": "Passed check 8?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 8?": {
      "main": [
        [
          {
            "node": "Check 9: No wording left that blames a person",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 8)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 8)": {
      "main": [
        [
          {
            "node": "5. Write the final incident report",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 8",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 8": {
      "main": [
        [
          {
            "node": "Check 9: No wording left that blames a person",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 9: No wording left that blames a person": {
      "main": [
        [
          {
            "node": "Passed check 9?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 9?": {
      "main": [
        [
          {
            "node": "Check 10: Every event in the timeline table is in the final report",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 9)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 9)": {
      "main": [
        [
          {
            "node": "5. Write the final incident report",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 9",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 9": {
      "main": [
        [
          {
            "node": "Check 10: Every event in the timeline table is in the final report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 10: Every event in the timeline table is in the final report": {
      "main": [
        [
          {
            "node": "Passed check 10?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 10?": {
      "main": [
        [
          {
            "node": "Result",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 10)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 10)": {
      "main": [
        [
          {
            "node": "5. Write the final incident report",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 10",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 10": {
      "main": [
        [
          {
            "node": "Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Chat model": {
      "ai_languageModel": [
        [
          {
            "node": "1. List what happened in order",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "2. Separate facts from guesses and details to protect",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "3. Draft the incident report",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "4. Find blame and private details in the draft",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "5. Write the final incident report",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "Check 7: The likely cause stays out of the timeline",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "Check 8: No name, health detail or private detail is in the final text",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "Check 9: No wording left that blames a person",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "Check 10: Every event in the timeline table is in the final report",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "meta": {
    "template": "write-an-incident-report"
  }
}
