{
  "name": "Check a report before it goes to the board",
  "nodes": [
    {
      "parameters": {
        "content": "## Check a report before it goes to the board\nCheck a report for wrong or unsupported figures, a figure given two ways and a missing ask, and get a ready, ready after fixes or not ready verdict with a fix list that you decide on.\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 Quote column appears in the report, 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): Every row of the table has one of \"matches\", \"differs\" or \"no source\" in the Verdict column. If it fails: try once more, then flag.\n- Check 3 (a plain rule, run automatically with no AI): Every row of the table has something written in \"Appearances\", \"Agrees\" and \"Sum Check\" (a blank or a dash does not count). 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 something written in \"Quote\" and \"Matches Ask\" (a blank or a dash does not count). If it fails: try once more, then flag.\n- Check 5 (a plain rule, run automatically with no AI): Every number in the answer also appears somewhere in at least one of \"the report\", \"source figures\" and \"what the board is being asked to decide\"; this does not check what each number means. 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 \"number\", \"date\", \"percentage\", \"name\" or \"change\" in the Type column. If it fails: try once more, then flag.\n- Check 7 (a second AI prompt answers pass or fail): Look at the table. Count the percentages, totals and changes in the Quote column, and look in the Sum column for each. Does every one have a worked sum, and is each sum right when you work it again from the source figures below? Answer FAIL and quote each row whose sum is missing or wrong. If it fails: try once more, then flag.\n- Check 8 (a second AI prompt answers pass or fail): For each row of the table whose Source Passage is not No source, is that passage in the source figures below, word for word? Answer FAIL and quote each passage that is not. If it fails: try once more, then flag.\n- Check 9 (a plain rule, run automatically with no AI): Every row of the table has one of \"yes\" or \"no\" in the Agrees column. If it fails: try once more, then flag.\n- Check 10 (a second AI prompt answers pass or fail): Read the report below. Find every list of parts that has a stated total, such as sources of funding or a budget. Does the table show a sum for each one in the Sum Check column, and is each sum right? Answer FAIL and quote each total with no sum or a wrong sum. If it fails: try once more, then flag.\n- Check 11 (a second AI prompt answers pass or fail): Count the words in the first paragraph of the report below. Is it 120 words or fewer? Answer FAIL, give the count, and quote the first sentence that goes past the limit. If it fails: flag it and carry on.\n- Check 12 (a plain rule, run automatically with no AI): The answer contains the text fragment \"This verdict comes from the checks above. An AI tool can miss things, so read the flagged parts yourself.\" (including inside a longer word). If it fails: try once more, then flag.\n- Check 13 (a second AI prompt answers pass or fail): Count the problems in the three tables below: each verdict of differs, each Agrees of no, each part marked Missing, each Matches Ask of no, an opening paragraph over 120 words, and each abbreviation listed. Count a problem raised in two tables once. Does the fix table have one row for each? Show both numbers. Answer FAIL and name each problem with no row. If it fails: try once more, then flag.\n- Check 14 (a second AI prompt answers pass or fail): The rule is: Not ready if any figure differs or the Ask is Missing. Ready after fixes if any other problem was raised. Ready by these checks if none was. Use the two tables below. Does the verdict follow the rule? Answer FAIL and say which line of the rule it breaks. If it fails: try once more, then flag.\n- Check 15 (you check it by hand): For each figure that differs, look at both numbers and decide which one is right, then decide whether a part marked Missing was left out on purpose. Read the fix list against the verdict yourself. The checks find what is wrong or missing; only a person can decide whether the report goes, waits or changes. If it fails: flag it and carry on.\n\nFrom Logic Lab (logiclabhq.com). Free to use and adapt, with credit.",
        "height": 1280,
        "width": 760,
        "color": 5
      },
      "id": "02025ee6-11e7-4b85-ae85-66fc80701c9b",
      "name": "About this workflow",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -40,
        -1240
      ]
    },
    {
      "parameters": {},
      "id": "0307c60a-0a34-4f2d-aa0e-56a85c92648b",
      "name": "Start",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        0,
        320
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "6ce96d65-b3ad-4acc-acc9-6e8b21c0da3a",
              "name": "report",
              "value": "Sample: Riverside Tool Library. This report asks the board to approve funding for a second delivery van at its March meeting. It covers the year to December.\n\nMembership grew 48 percent to 340 households. Loans reached 1,900, nearly double last year. Volunteers gave 2,400 hours.\n\nThe van would cost 18,000 dollars, paid from three sources: 9,000 dollars from the reserve fund, 6,000 dollars from the city grant and 2,000 dollars from member donations. A second van would let us deliver on two days a week instead of one.\n\nWe recommend the board approve the purchase so the 18,500 dollar van can be ordered in April.",
              "type": "string"
            },
            {
              "id": "3d1472f7-9478-4a80-a543-d951e889ae02",
              "name": "sources",
              "value": "Sample: Households: 210 at January, 310 at December. Loans: 1,040 last year, 1,860 this year. Volunteer hours: 2,150 this year. Supplier quote for the van: 18,000 dollars.",
              "type": "string"
            },
            {
              "id": "07265e64-99b4-4403-a996-6f32fb33c3b1",
              "name": "ask",
              "value": "Sample: Approve funding for a second delivery van",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "3ca1fc5f-3c21-46a2-a224-e5c1ea1b27b4",
      "name": "Inputs",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        220,
        320
      ],
      "notes": "Paste your own material here: report = The Report; sources = Source Figures (optional); ask = What The Board Is Being Asked To Decide.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "promptType": "define",
        "hasOutputParser": false,
        "text": "=You are a careful reviewer who checks a report before it goes to a board.\n\nYour task is to list every figure and claim in the report below and judge nothing yet.\n\nThe report:\n{{ $(\"Inputs\").first().json[\"report\"] }}\n\nRules:\n- Quote every number, date, percentage, name and word of change (such as \"up\", \"grew\" or \"doubled\") from the report, word for word, in the order they appear.\n- Quote each one as short as you can. Do not correct it and do not reword it.\n- In the Type column, write exactly one of: number, date, percentage, name, change.\n- One row for each quote. Do not add a quote the report does not have.\n\nReturn one markdown table and nothing else:\n| # | Quote | Type |{{ $(\"Check 1: Every Row Quotes The Report\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Every Row Quotes The Report. What was wrong: \" + $(\"Check 1: Every Row Quotes The Report\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}{{ $(\"Check 6: Every Type Is One Of The Five Allowed Words\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Every Type Is One Of The Five Allowed Words. What was wrong: \" + $(\"Check 6: Every Type Is One Of The Five Allowed Words\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}"
      },
      "id": "2719f0f1-cdf1-41c0-ad4f-8577ead489f6",
      "name": "1. List Every Figure And Claim",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        480,
        320
      ],
      "notes": "Quote each number, date, percentage, name and word of change in the report, word for word, so the checks that follow have a fixed list to work from. Returns: A numbered table of every figure and claim, each quoted from the report.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// Every Row Quotes The Report\n// Rule: Each quote in the Quote column appears in the report, 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 Every Figure And Claim\").first().json.text ?? '');\nconst sources = {\n  \"report\": String($(\"Inputs\").first().json[\"report\"] ?? ''),\n};\nconst result = runRule(\"column_quotes_source\", {\"column\":\"Quote\",\"input\":\"report\"}, work, sources);\nreturn [{ json: result }];"
      },
      "id": "650b530a-1dc2-46e7-a193-c82ca77b1c79",
      "name": "Check 1: Every Row Quotes The Report",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        740,
        320
      ],
      "notes": "Each quote in the Quote column appears in the report, with shortened fragments in the same order.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "50778b39-b90c-4b7c-a576-371b9f17a586",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "a5529672-e894-49c5-a57a-5b08bc0e6333",
      "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": "d6c4d156-d69a-4ba7-a585-9f4883af6561",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "a6607a1a-ceb4-4f93-ae45-bad0eb4f27e9",
      "name": "Try again? (check 1)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1000,
        520
      ],
      "notes": "True = first failure: send back to \"1. List Every Figure And Claim\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "6fcee72b-d245-4296-a764-1409a5f1968c",
              "name": "flag",
              "value": "=Every Row Quotes The Report: did not pass. {{ $(\"Check 1: Every Row Quotes The Report\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "3f36a7ef-92e7-4a52-a1b7-fbdd14ef9690",
      "name": "Flag check 1",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1130,
        700
      ],
      "notes": "Notes the problem and carries on. The note shows in Result.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "promptType": "define",
        "hasOutputParser": false,
        "text": "=You are a careful reviewer who matches a report's figures to the records behind them.\n\nYour task is to tie each figure in the table below to the source figures.\n\nThe figures:\n{{ $(\"1. List Every Figure And Claim\").first().json.text }}\n\nThe source figures:\n{{ $(\"Inputs\").first().json[\"sources\"] }}\n\nRules:\n- One row for each figure in the table. Keep its number and its quote.\n- In the Source Passage column, quote the matching passage from the source figures word for word, as short as you can. If no passage speaks to the figure, write exactly No source.\n- In the Verdict column, write exactly one of: matches, differs, no source. A figure that is only a name, a date or a word of change is no source unless a passage speaks to it.\n- For every percentage, total or change, recompute it from the source figures and show the sum in the Sum column, such as 310 minus 210 is 100, and 100 divided by 210 is 48 percent. For any other row, write Not worked out.\n- If the source figures are empty or say none given, write no source in every Verdict cell and say so in one line below the table.\n- Do not choose which figure is right when they differ. Show both.\n\nReturn one markdown table and nothing else, apart from the one line if the source figures are empty:\n| # | Quote | Source Passage | Verdict | Sum |{{ $(\"Check 2: Every Verdict Is One Of The Three Allowed Words\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Every Verdict Is One Of The Three Allowed Words. What was wrong: \" + $(\"Check 2: Every Verdict Is One Of The Three Allowed Words\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}{{ $(\"Check 7: Every Worked-Out Figure Was Recomputed\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Every Worked-Out Figure Was Recomputed. What was wrong: \" + $(\"Check 7: Every Worked-Out Figure Was Recomputed\").first().json.text + \"\\nFix that and give your full answer again.\" : \"\" }}{{ $(\"Check 8: Every Source Passage Is In The Source Figures\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Every Source Passage Is In The Source Figures. What was wrong: \" + $(\"Check 8: Every Source Passage Is In The Source Figures\").first().json.text + \"\\nFix that and give your full answer again.\" : \"\" }}"
      },
      "id": "a78c8a63-1b1f-43ce-ad8f-49d5a08c8a00",
      "name": "2. Tie Each Figure To Its Source",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        1260,
        320
      ],
      "notes": "Match each figure to a passage in the source figures, say whether they agree, and recompute every percentage, total and change. Returns: A table that sets each figure beside its source passage, with a verdict and the working for every worked-out figure.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// Every Verdict Is One Of The Three Allowed Words\n// Rule: Every row of the table has one of \"matches\", \"differs\" or \"no source\" in the Verdict 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. Tie Each Figure To Its Source\").first().json.text ?? '');\nconst sources = {\n\n};\nconst result = runRule(\"column_values_in\", {\"column\":\"Verdict\",\"allowed\":[\"matches\",\"differs\",\"no source\"]}, work, sources);\nreturn [{ json: result }];"
      },
      "id": "533134f7-0872-44a6-af0c-65811e1b5d78",
      "name": "Check 2: Every Verdict Is One Of The Three Allowed Words",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1520,
        320
      ],
      "notes": "Every row of the table has one of \"matches\", \"differs\" or \"no source\" in the Verdict column.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "4f7789a6-ba0c-4d0f-a476-3588a017a719",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "a54bdaad-689c-4f0a-a57c-999fbc0c249c",
      "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": "d7c4d2e9-d59a-4a14-a685-a0db82af63ce",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "7e6279b9-4ebb-44d8-ae3e-032bd34cc38a",
      "name": "Try again? (check 2)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1780,
        520
      ],
      "notes": "True = first failure: send back to \"2. Tie Each Figure To Its Source\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "6ecee598-d345-4429-a664-1276a6f1981f",
              "name": "flag",
              "value": "=Every Verdict Is One Of The Three Allowed Words: did not pass. {{ $(\"Check 2: Every Verdict Is One Of The Three Allowed Words\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "4036a982-91e7-48bf-aeb7-f72417ef9b49",
      "name": "Flag check 2",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1910,
        700
      ],
      "notes": "Notes the problem and carries on. The note shows in Result.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "promptType": "define",
        "hasOutputParser": false,
        "text": "=You are a careful reviewer who reads a whole report for statements that disagree with each other.\n\nYour task is to check the report below against itself.\n\nThe report:\n{{ $(\"Inputs\").first().json[\"report\"] }}\n\nRules:\n- Number the paragraphs of the report from the top: paragraph 1, paragraph 2, and so on.\n- List every quantity, name and period that appears more than once, and every list of parts that has a stated total. One row for each.\n- In the Appearances column, quote each place it appears, word for word, with its paragraph number.\n- In the Agrees column, write exactly one of: yes, no. Write no if the versions differ, if the parts do not add up to the stated total, or if the period a figure covers changes (for example calendar year to financial year).\n- In the Sum Check column, show the sum of the parts against the stated total for a list of parts. For any other row, write Not a total.\n- If nothing appears more than once and there is no total, write one row with None found in every cell.\n- Do not decide which version is right.\n\nReturn one markdown table and nothing else:\n| # | Item | Appearances | Agrees | Sum Check |{{ $(\"Check 3: Every Row Is Filled In\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Every Row Is Filled In. What was wrong: \" + $(\"Check 3: Every Row Is Filled In\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}{{ $(\"Check 9: Every Agrees Cell Is Yes Or No\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Every Agrees Cell Is Yes Or No. What was wrong: \" + $(\"Check 9: Every Agrees Cell Is Yes Or No\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}{{ $(\"Check 10: Every Stated Total Was Added Up\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Every Stated Total Was Added Up. What was wrong: \" + $(\"Check 10: Every Stated Total Was Added Up\").first().json.text + \"\\nFix that and give your full answer again.\" : \"\" }}"
      },
      "id": "be4ffc48-e6fc-40ad-a502-5402cf4a1b77",
      "name": "3. Check The Report Agrees With Itself",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        2040,
        320
      ],
      "notes": "Find a figure given two ways, parts that do not add up to their total, and a name or a period that changes. Returns: A table of repeated items and totals, each with its places in the report and whether they agree.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// Every Row Is Filled In\n// Rule: Every row of the table has something written in \"Appearances\", \"Agrees\" and \"Sum Check\" (a blank or a dash does not count).\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. Check The Report Agrees With Itself\").first().json.text ?? '');\nconst sources = {\n\n};\nconst result = runRule(\"table_cells_filled\", [\"Appearances\",\"Agrees\",\"Sum Check\"], work, sources);\nreturn [{ json: result }];"
      },
      "id": "73543f7f-561c-4860-a8ea-edd9ff80ee32",
      "name": "Check 3: Every Row Is Filled In",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2300,
        320
      ],
      "notes": "Every row of the table has something written in \"Appearances\", \"Agrees\" and \"Sum Check\" (a blank or a dash does not count).",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "4e778813-bb0c-4ea2-a776-3a419d17a260",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "a54e1944-689a-4073-a57f-a1b63c091c85",
      "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": "d4c4ce30-d89a-4ecd-a785-a26e81af623b",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "a264f0fc-5ab9-4925-a240-7a6eef4ab107",
      "name": "Try again? (check 3)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2560,
        520
      ],
      "notes": "True = first failure: send back to \"3. Check The Report Agrees With Itself\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "71ceea51-d045-4f70-a564-10e3a7f199b2",
              "name": "flag",
              "value": "=Every Row Is Filled In: did not pass. {{ $(\"Check 3: Every Row Is Filled In\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "4136ab15-90e7-472c-afb7-f8b716ef99b6",
      "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 board member who reads the first page of a paper to see what is wanted from them.\n\nYour task is to check that the report below gives the board what it needs.\n\nThe report:\n{{ $(\"Inputs\").first().json[\"report\"] }}\n\nWhat the board is being asked to decide:\n{{ $(\"Inputs\").first().json[\"ask\"] }}\n\nRules:\n- Quote the sentence in the first paragraph that states what the board is asked to decide. If the first paragraph does not state it, write Missing.\n- Then quote where the report gives its recommendation, its main risk, its cost or effort, and what happens if the board says no. Write Missing for any part the report does not give. Do not write a part for the report.\n- In the Matches Ask column, for the first row only, write yes if the quote says the same as the decision above, no if it differs, or Not applicable if the row is Missing. For the other rows, write Not applicable.\n- After the table, add one line starting with \"Abbreviations:\" that lists any abbreviation not explained where it first appears, or says None.\n\nReturn the table, then the one line, and nothing else:\n| Part | Quote | Matches Ask |\nUse these five rows in this order: Ask, Recommendation, Main Risk, Cost Or Effort, If The Board Says No.{{ $(\"Check 4: Every Part Has A Quote Or The Word Missing\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Every Part Has A Quote Or The Word Missing. What was wrong: \" + $(\"Check 4: Every Part Has A Quote Or The Word Missing\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}"
      },
      "id": "424a2f64-f359-4529-abd7-61721a284c87",
      "name": "4. Read It As The Board Will",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        2820,
        320
      ],
      "notes": "Check that the ask, the recommendation, the main risk, the cost or effort and the result of saying no are all there, each with a quote or the word Missing. Returns: A table of the five things a board looks for, each with a quote or Missing, and a line of unexplained abbreviations.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// Every Part Has A Quote Or The Word Missing\n// Rule: Every row of the table has something written in \"Quote\" and \"Matches Ask\" (a blank or a dash does not count).\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. Read It As The Board Will\").first().json.text ?? '');\nconst sources = {\n\n};\nconst result = runRule(\"table_cells_filled\", [\"Quote\",\"Matches Ask\"], work, sources);\nreturn [{ json: result }];"
      },
      "id": "95e2f2dd-dea8-4206-aaf7-4c733f9e2b4c",
      "name": "Check 4: Every Part Has A Quote Or The Word Missing",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3080,
        320
      ],
      "notes": "Every row of the table has something written in \"Quote\" and \"Matches Ask\" (a blank or a dash does not count).",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "4d778680-bc0c-4035-a676-38ae9e17a3f3",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "255a1bb7-e8a1-45b8-a56e-58953c1a65a6",
      "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": "d5c4cfc3-d79a-4d3a-a885-a40180af60a8",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "9a539b43-dac0-4e6a-a24d-4661c7483378",
      "name": "Try again? (check 4)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        3340,
        520
      ],
      "notes": "True = first failure: send back to \"4. Read It As The Board Will\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "70cee8be-d145-4103-a464-0f50a8f19b45",
              "name": "flag",
              "value": "=Every Part Has A Quote Or The Word Missing: did not pass. {{ $(\"Check 4: Every Part Has A Quote Or The Word Missing\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "3a36a010-8fe7-4599-acb7-f3fe19ef9e6f",
      "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 careful reviewer who gives a clear verdict and a short fix list and leaves the decision to the person.\n\nYour task is to give the verdict and the fix list for the report below.\n\nThe report:\n{{ $(\"Inputs\").first().json[\"report\"] }}\n\nThe source figures:\n{{ $(\"Inputs\").first().json[\"sources\"] }}\n\nThe figures tied to their sources:\n{{ $(\"2. Tie Each Figure To Its Source\").first().json.text }}\n\nThe report checked against itself:\n{{ $(\"3. Check The Report Agrees With Itself\").first().json.text }}\n\nThe board read:\n{{ $(\"4. Read It As The Board Will\").first().json.text }}\n\nRules:\n- Make one row for each problem: a figure that differs, an item that does not agree with itself, a part marked Missing, a Matches Ask of no, an opening paragraph over 120 words, or an abbreviation not explained. A problem raised in two tables gets one row. A figure marked no source because no source figures were given is not a problem.\n- The row has four cells. Where quotes the words in the report, or says The whole report for a part marked Missing. Problem says in plain words what is wrong. Evidence quotes the source figure or the other version. Suggested Wording uses only facts and numbers from the report and the source figures.\n- If no safe wording exists, write exactly Cut it until a source exists. For a part marked Missing, write exactly Add it yourself, because the report gives nothing to draw on. For parts that do not add up to their total, write exactly Check the parts and the total yourself.\n- Do not write a number of your own in the table, and do not write a step number. Do not work out a new total.\n- Verdict by this rule only: Not ready if any figure differs or the Ask is Missing. Ready after fixes if any other problem was raised. Ready by these checks if none was.\n- If there are no problems, write one row with None found in every cell.\n- Write the verdict line first, in words only, as \"Verdict:\" then the verdict then one deciding reason. On the next line write exactly: This verdict comes from the checks above. An AI tool can miss things, so read the flagged parts yourself.\n- Do not rewrite the report, and do not choose to send, hold or fix. The person decides.\n\nReturn the verdict line, the next line, and one markdown table, and nothing else:\n| Where | Problem | Evidence | Suggested Wording |{{ $(\"Check 5: No Number Appears That The Report Or The Sources Do Not Have\").isExecuted ? \"\\n\\nYour last answer did not pass this check: No Number Appears That The Report Or The Sources Do Not Have. What was wrong: \" + $(\"Check 5: No Number Appears That The Report Or The Sources Do Not Have\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}{{ $(\"Check 12: The Line About Reading The Flagged Parts Is There\").isExecuted ? \"\\n\\nYour last answer did not pass this check: The Line About Reading The Flagged Parts Is There. What was wrong: \" + $(\"Check 12: The Line About Reading The Flagged Parts Is There\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}{{ $(\"Check 13: Every Problem Has A Row\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Every Problem Has A Row. What was wrong: \" + $(\"Check 13: Every Problem Has A Row\").first().json.text + \"\\nFix that and give your full answer again.\" : \"\" }}{{ $(\"Check 14: The Verdict Follows The Written Rule\").isExecuted ? \"\\n\\nYour last answer did not pass this check: The Verdict Follows The Written Rule. What was wrong: \" + $(\"Check 14: The Verdict Follows The Written Rule\").first().json.text + \"\\nFix that and give your full answer again.\" : \"\" }}"
      },
      "id": "b1f94287-f612-44da-ab73-fce5ba8c29f8",
      "name": "5. Give The Verdict And The Fix List",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        3600,
        320
      ],
      "notes": "Turn every problem into one fix row, then give one verdict by a fixed rule and stop. Returns: A verdict, a fix table with a row for every problem, and the line that says a person must read the flagged parts.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// No Number Appears That The Report Or The Sources Do Not Have\n// Rule: Every number in the answer also appears somewhere in at least one of \"the report\", \"source figures\" and \"what the board is being asked to decide\"; 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($(\"5. Give The Verdict And The Fix List\").first().json.text ?? '');\nconst sources = {\n\n};\nconst result = runRule(\"numbers_in_source\", {\"inputs\":[\"report\",\"sources\",\"ask\"]}, work, sources);\nreturn [{ json: result }];"
      },
      "id": "1efaeeb7-dab1-4e14-a724-de29296c4e76",
      "name": "Check 5: No Number Appears That The Report Or The Sources Do Not Have",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3860,
        320
      ],
      "notes": "Every number in the answer also appears somewhere in at least one of \"the report\", \"source figures\" and \"what the board is being asked to decide\"; this does not check what each number means.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "54779185-b50c-4530-a976-3d67a317abd2",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "a55d23ce-e89f-4721-a570-972c3c18270f",
      "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": "dac4d7a2-d29a-455b-a985-a59487af6bad",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "fe567746-76be-4267-a64f-58e453453e45",
      "name": "Try again? (check 5)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        4120,
        520
      ],
      "notes": "True = first failure: send back to \"5. Give The Verdict And The Fix List\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "73ceed77-d645-48e2-ab64-1a55a1f19040",
              "name": "flag",
              "value": "=No Number Appears That The Report Or The Sources Do Not Have: did not pass. {{ $(\"Check 5: No Number Appears That The Report Or The Sources Do Not Have\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "3b36a1a3-8ee7-4406-adb7-f59118ef9cdc",
      "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 Type Is One Of The Five Allowed Words\n// Rule: Every row of the table has one of \"number\", \"date\", \"percentage\", \"name\" or \"change\" 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($(\"1. List Every Figure And Claim\").first().json.text ?? '');\nconst sources = {\n\n};\nconst result = runRule(\"column_values_in\", {\"column\":\"Type\",\"allowed\":[\"number\",\"date\",\"percentage\",\"name\",\"change\"]}, work, sources);\nreturn [{ json: result }];"
      },
      "id": "9ffd9982-249f-4923-a4cb-a94c9b30a3bd",
      "name": "Check 6: Every 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 \"number\", \"date\", \"percentage\", \"name\" or \"change\" in the Type column.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "53778ff2-b60c-46c3-a876-3bd4a417ad65",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "25559e89-e8a6-42e6-a572-d5c33c15e878",
      "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": "dbc4d935-d19a-43c8-aa85-a72786af6a1a",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "2658f4d5-d6c5-454c-a647-d39f6b432576",
      "name": "Try again? (check 6)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        4640,
        520
      ],
      "notes": "True = first failure: send back to \"1. List Every Figure And Claim\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "72ceebe4-d745-4a75-aa64-18c2a2f191d3",
              "name": "flag",
              "value": "=Every Type Is One Of The Five Allowed Words: did not pass. {{ $(\"Check 6: Every Type Is One Of The Five Allowed Words\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "3c36a336-8de7-4273-aab7-f0d81befa195",
      "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: Every Worked-Out Figure Was Recomputed\nLook at the table. Count the percentages, totals and changes in the Quote column, and look in the Sum column for each. Does every one have a worked sum, and is each sum right when you work it again from the source figures below? Answer FAIL and quote each row whose sum is missing or wrong.\n\nThe source figures:\n{{ $(\"Inputs\").first().json[\"sources\"] }}\n\nThe work to check (the answer from Step 2 (Tie Each Figure To Its Source)):\n{{ $(\"2. Tie Each Figure To Its Source\").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": "eb3e9760-c749-420b-a80c-9f7ecaeb0529",
      "name": "Check 7: Every Worked-Out Figure Was Recomputed",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        4900,
        320
      ],
      "notes": "Answers PASS or FAIL. Look at the table. Count the percentages, totals and changes in the Quote column, and look in the Sum column for each. Does every one have a worked sum, and is each sum right when you work it again from the source figures below? Answer FAIL and quote each row whose sum is missing or wrong.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "52778e5f-b70c-4856-ab76-408da117a8ac",
              "leftValue": "={{ String($json.text ?? '').trim().toUpperCase().startsWith('PASS') }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "2557dd20-e8a3-444f-a575-ddda3c13a9e1",
      "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": "d8c4d47c-d49a-4881-ab85-a8ba85af6887",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "1a5b2088-f2c3-42c9-aa4a-4ae24740ae33",
      "name": "Try again? (check 7)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        5160,
        520
      ],
      "notes": "True = first failure: send back to \"2. Tie Each Figure To Its Source\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "75cef09d-d445-45bc-a964-172fa3f19366",
              "name": "flag",
              "value": "=Every Worked-Out Figure Was Recomputed: did not pass. {{ $(\"Check 7: Every Worked-Out Figure Was Recomputed\").first().json.text }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "3d36a4c9-8ce7-40e0-abb7-f26b1aefa002",
      "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: Every Source Passage Is In The Source Figures\nFor each row of the table whose Source Passage is not No source, is that passage in the source figures below, word for word? Answer FAIL and quote each passage that is not.\n\nThe source figures:\n{{ $(\"Inputs\").first().json[\"sources\"] }}\n\nThe work to check (the answer from Step 2 (Tie Each Figure To Its Source)):\n{{ $(\"2. Tie Each Figure To Its Source\").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": "8601d714-6101-4afd-a466-7b5af0b2145b",
      "name": "Check 8: Every Source Passage Is In The Source Figures",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        5420,
        320
      ],
      "notes": "Answers PASS or FAIL. For each row of the table whose Source Passage is not No source, is that passage in the source figures below, word for word? Answer FAIL and quote each passage that is not.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "51778ccc-b80c-49e9-aa76-3efaa217aa3f",
              "leftValue": "={{ String($json.text ?? '').trim().toUpperCase().startsWith('PASS') }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "a563df93-e883-40a4-a563-cb39bbfd1a12",
      "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": "d9c4d60f-d39a-46ee-ac85-aa4d84af66f4",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "0249b19f-d2a2-4cbe-aa57-16d5ef3de514",
      "name": "Try again? (check 8)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        5680,
        520
      ],
      "notes": "True = first failure: send back to \"2. Tie Each Figure To Its Source\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "74ceef0a-d545-474f-a864-159ca4f194f9",
              "name": "flag",
              "value": "=Every Source Passage Is In The Source Figures: did not pass. {{ $(\"Check 8: Every Source Passage Is In The Source Figures\").first().json.text }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "4636b2f4-9be7-487d-a8b7-edb21defa4bb",
      "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": {
        "jsCode": "// Every Agrees Cell Is Yes Or No\n// Rule: Every row of the table has one of \"yes\" or \"no\" in the Agrees 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($(\"3. Check The Report Agrees With Itself\").first().json.text ?? '');\nconst sources = {\n\n};\nconst result = runRule(\"column_values_in\", {\"column\":\"Agrees\",\"allowed\":[\"yes\",\"no\"]}, work, sources);\nreturn [{ json: result }];"
      },
      "id": "32ac4d14-d470-4b2b-abe8-30ba730929a1",
      "name": "Check 9: Every Agrees Cell Is Yes Or No",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        5940,
        320
      ],
      "notes": "Every row of the table has one of \"yes\" or \"no\" in the Agrees column.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "587797d1-c10c-4814-ad76-2a83a717b21e",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "2566e7aa-e881-420d-a566-d350bbfadb7b",
      "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": "dec4ddee-ce9a-4f0f-ad85-92b07baf58c9",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "264c28e2-eea0-4a3b-ae59-4288cb3b6dd1",
      "name": "Try again? (check 9)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        6200,
        520
      ],
      "notes": "True = first failure: send back to \"3. Check The Report Agrees With Itself\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "67ceda93-da45-4f2e-af64-20a1adf1a324",
              "name": "flag",
              "value": "=Every Agrees Cell Is Yes Or No: did not pass. {{ $(\"Check 9: Every Agrees Cell Is Yes Or No\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "4736b487-9ae7-46ea-a9b7-ef451cefa328",
      "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 Stated Total Was Added Up\nRead the report below. Find every list of parts that has a stated total, such as sources of funding or a budget. Does the table show a sum for each one in the Sum Check column, and is each sum right? Answer FAIL and quote each total with no sum or a wrong sum.\n\nThe report:\n{{ $(\"Inputs\").first().json[\"report\"] }}\n\nThe work to check (the answer from Step 3 (Check The Report Agrees With Itself)):\n{{ $(\"3. Check The Report Agrees With Itself\").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": "53edc93b-e427-4c9c-ad23-fa41b2713092",
      "name": "Check 10: Every Stated Total Was Added Up",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        6460,
        320
      ],
      "notes": "Answers PASS or FAIL. Read the report below. Find every list of parts that has a stated total, such as sources of funding or a budget. Does the table show a sum for each one in the Sum Check column, and is each sum right? Answer FAIL and quote each total with no sum or a wrong sum.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "5777963e-c20c-49a7-ac76-28f0a817b3b1",
              "leftValue": "={{ String($json.text ?? '').trim().toUpperCase().startsWith('PASS') }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "0e0a207c-1c23-4325-a69a-8f7e16c63f8f",
      "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": "dfc4df81-cd9a-4d7c-ae85-94437aaf5736",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "80a71024-7bc6-4b33-a4b3-e0c6d98548b5",
      "name": "Try again? (check 10)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        6720,
        520
      ],
      "notes": "True = first failure: send back to \"3. Check The Report Agrees With Itself\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "66ced900-db45-40c1-ae64-1f0eaef1a4b7",
              "name": "flag",
              "value": "=Every Stated Total Was Added Up: did not pass. {{ $(\"Check 10: Every Stated Total Was Added Up\").first().json.text }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "620a440d-a47e-4846-a1a1-9617952a1de0",
      "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": {
        "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 Opening Paragraph Is Short Enough\nCount the words in the first paragraph of the report below. Is it 120 words or fewer? Answer FAIL, give the count, and quote the first sentence that goes past the limit.\n\nThe report:\n{{ $(\"Inputs\").first().json[\"report\"] }}\n\nThe work to check (the answer from Step 4 (Read It As The Board Will)):\n{{ $(\"4. Read It As The Board Will\").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": "e4b8ce9d-e778-4292-a18a-9c23b9a930b8",
      "name": "Check 11: The Opening Paragraph Is Short Enough",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        6980,
        320
      ],
      "notes": "Answers PASS or FAIL. Count the words in the first paragraph of the report below. Is it 120 words or fewer? Answer FAIL, give the count, and quote the first sentence that goes past the limit.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "af2d9722-20db-4a2d-ae16-90a82e3c258b",
              "leftValue": "={{ String($json.text ?? '').trim().toUpperCase().startsWith('PASS') }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "0e07e1e5-1c25-41bc-a698-50e716c87e26",
      "name": "Passed check 11?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        7240,
        320
      ],
      "notes": "True = passed. False = did not pass.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "17b38778-af0a-435b-a588-c432fd5291fd",
              "name": "flag",
              "value": "=The Opening Paragraph Is Short Enough: did not pass. {{ $(\"Check 11: The Opening Paragraph Is Short Enough\").first().json.text }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "610a427a-a57e-49d9-a0a1-9484962a1f73",
      "name": "Flag check 11",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        7370,
        700
      ],
      "notes": "Notes the problem and carries on. The note shows in Result.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// The Line About Reading The Flagged Parts Is There\n// Rule: The answer contains the text fragment \"This verdict comes from the checks above. An AI tool can miss things, so read the flagged parts yourself.\" (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. Give The Verdict And The Fix List\").first().json.text ?? '');\nconst sources = {\n\n};\nconst result = runRule(\"has_text\", \"This verdict comes from the checks above. An AI tool can miss things, so read the flagged parts yourself.\", work, sources);\nreturn [{ json: result }];"
      },
      "id": "e5b25660-19be-49cb-a22a-37a6572ec239",
      "name": "Check 12: The Line About Reading The Flagged Parts Is There",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        7500,
        320
      ],
      "notes": "The answer contains the text fragment \"This verdict comes from the checks above. An AI tool can miss things, so read the flagged parts yourself.\" (including inside a longer word).",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "b02d98b5-1fdb-489a-af16-923b2d3c23f8",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "8e0f672a-1c27-4053-a696-125016c1c261",
      "name": "Passed check 12?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        7760,
        320
      ],
      "notes": "True = passed. False = did not pass.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "dbc717cc-d198-4531-aa87-e5befeac556b",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "24a20222-87cb-4b45-a0b8-8a08cd80b8a3",
      "name": "Try again? (check 12)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        7760,
        520
      ],
      "notes": "True = first failure: send back to \"5. Give The Verdict And The Fix List\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "18b3890b-ae0a-41c8-a688-c5c5fc52906a",
              "name": "flag",
              "value": "=The Line About Reading The Flagged Parts Is There: did not pass. {{ $(\"Check 12: The Line About Reading The Flagged Parts Is There\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "600a40e7-a27e-4520-a3a1-993d972a2106",
      "name": "Flag check 12",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        7890,
        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 Problem Has A Row\nCount the problems in the three tables below: each verdict of differs, each Agrees of no, each part marked Missing, each Matches Ask of no, an opening paragraph over 120 words, and each abbreviation listed. Count a problem raised in two tables once. Does the fix table have one row for each? Show both numbers. Answer FAIL and name each problem with no row.\n\nThe figures tied to their sources:\n{{ $(\"2. Tie Each Figure To Its Source\").first().json.text }}\n\nThe report checked against itself:\n{{ $(\"3. Check The Report Agrees With Itself\").first().json.text }}\n\nThe board read:\n{{ $(\"4. Read It As The Board Will\").first().json.text }}\n\nThe work to check (the answer from Step 5 (Give The Verdict And The Fix List)):\n{{ $(\"5. Give The Verdict And The Fix List\").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": "c45b362f-f17e-4b60-a2ca-b35dc1093bbe",
      "name": "Check 13: Every Problem Has A Row",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        8020,
        320
      ],
      "notes": "Answers PASS or FAIL. Count the problems in the three tables below: each verdict of differs, each Agrees of no, each part marked Missing, each Matches Ask of no, an opening paragraph over 120 words, and each abbreviation listed. Count a problem raised in two tables once. Does the fix table have one row for each? Show both numbers. Answer FAIL and name each problem with no row.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "ad2d93fc-1edb-4707-a016-93ce303c28b1",
              "leftValue": "={{ String($json.text ?? '').trim().toUpperCase().startsWith('PASS') }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "0e0c5f13-9c2a-486a-a693-0a3916c400f8",
      "name": "Passed check 13?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        8280,
        320
      ],
      "notes": "True = passed. False = did not pass.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "dec71c85-ce98-4078-a987-e42bffac56fe",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "009f8adf-fbce-4078-acb6-5e55b182cb26",
      "name": "Try again? (check 13)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        8280,
        520
      ],
      "notes": "True = first failure: send back to \"5. Give The Verdict And The Fix List\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "19b38a9e-b10a-4681-a388-c10cfb528ed7",
              "name": "flag",
              "value": "=Every Problem Has A Row: did not pass. {{ $(\"Check 13: Every Problem Has A Row\").first().json.text }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "5f0a3f54-a37e-46b3-a2a1-97aa982a2299",
      "name": "Flag check 13",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        8410,
        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 Verdict Follows The Written Rule\nThe rule is: Not ready if any figure differs or the Ask is Missing. Ready after fixes if any other problem was raised. Ready by these checks if none was. Use the two tables below. Does the verdict follow the rule? Answer FAIL and say which line of the rule it breaks.\n\nThe figures tied to their sources:\n{{ $(\"2. Tie Each Figure To Its Source\").first().json.text }}\n\nThe board read:\n{{ $(\"4. Read It As The Board Will\").first().json.text }}\n\nThe work to check (the answer from Step 5 (Give The Verdict And The Fix List)):\n{{ $(\"5. Give The Verdict And The Fix List\").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": "f4f9232f-6dea-4b06-a20b-ace96f18cab8",
      "name": "Check 14: The Verdict Follows The Written Rule",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        8540,
        320
      ],
      "notes": "Answers PASS or FAIL. The rule is: Not ready if any figure differs or the Ask is Missing. Ready after fixes if any other problem was raised. Ready by these checks if none was. Use the two tables below. Does the verdict follow the rule? Answer FAIL and say which line of the rule it breaks.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "ae2d958f-1ddb-4574-a116-95612f3c271e",
              "leftValue": "={{ String($json.text ?? '').trim().toUpperCase().startsWith('PASS') }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "8e13e458-9c2c-4701-a690-cba296bc7bb3",
      "name": "Passed check 14?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        8800,
        320
      ],
      "notes": "True = passed. False = did not pass.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "ddc71af2-cf98-420b-a887-e29800ac5891",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "189d7210-23d0-4e07-acbd-e39ab18f9719",
      "name": "Try again? (check 14)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        8800,
        520
      ],
      "notes": "True = first failure: send back to \"5. Give The Verdict And The Fix List\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "1ab38c31-b00a-44ee-a488-c29ffa528d44",
              "name": "flag",
              "value": "=The Verdict Follows The Written Rule: did not pass. {{ $(\"Check 14: The Verdict Follows The Written Rule\").first().json.text }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "5e0a3dc1-a87e-4e92-ada1-8fcb992a242c",
      "name": "Flag check 14",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        8930,
        700
      ],
      "notes": "Notes the problem and carries on. The note shows in Result.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "content": "**Check 15: check by hand**\nFor each figure that differs, look at both numbers and decide which one is right, then decide whether a part marked Missing was left out on purpose. Read the fix list against the verdict yourself. The checks find what is wrong or missing; only a person can decide whether the report goes, waits or changes.\n\nIf not: flag it and carry on. This check is also listed in Result.",
        "height": 200,
        "width": 240,
        "color": 3
      },
      "id": "7d885bed-74d7-4068-aef3-b523ed18c48e",
      "name": "Check 15 (by hand)",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        8780,
        70
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "b154d38e-9ce5-4705-af00-3ff003bc401f",
              "name": "result",
              "value": "={{ $(\"5. Give The Verdict And The Fix List\").first().json.text }}",
              "type": "string"
            },
            {
              "id": "5e273b3f-9faf-411a-a5ce-5c114ac2c06c",
              "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 : \"\", $(\"Flag check 11\").isExecuted ? $(\"Flag check 11\").first().json.flag : \"\", $(\"Flag check 12\").isExecuted ? $(\"Flag check 12\").first().json.flag : \"\", $(\"Flag check 13\").isExecuted ? $(\"Flag check 13\").first().json.flag : \"\", $(\"Flag check 14\").isExecuted ? $(\"Flag check 14\").first().json.flag : \"\"].filter(x => x).join(\"\\n\") || \"None\" }}",
              "type": "string"
            },
            {
              "id": "754eba95-bda3-4688-a4d2-92174be66eba",
              "name": "check_by_hand",
              "value": "- For each figure that differs, look at both numbers and decide which one is right, then decide whether a part marked Missing was left out on purpose. Read the fix list against the verdict yourself. The checks find what is wrong or missing; only a person can decide whether the report goes, waits or changes.",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "526ceacb-bcde-42be-ada9-be1dfc890008",
      "name": "Result",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        9060,
        320
      ],
      "notes": "The final answer, anything flagged, and the checks left for you.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "options": {}
      },
      "id": "7b129711-7a14-4498-a944-ab3348209f4a",
      "name": "Chat model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "typeVersion": 1.2,
      "position": [
        480,
        840
      ],
      "notes": "Attach your credential here, or swap in any chat model node.",
      "notesInFlow": true
    }
  ],
  "connections": {
    "Start": {
      "main": [
        [
          {
            "node": "Inputs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Inputs": {
      "main": [
        [
          {
            "node": "1. List Every Figure And Claim",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "1. List Every Figure And Claim": {
      "main": [
        [
          {
            "node": "Check 1: Every Row Quotes The Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 1: Every Row Quotes The Report": {
      "main": [
        [
          {
            "node": "Passed check 1?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 1?": {
      "main": [
        [
          {
            "node": "2. Tie Each Figure To Its Source",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 1)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 1)": {
      "main": [
        [
          {
            "node": "1. List Every Figure And Claim",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 1": {
      "main": [
        [
          {
            "node": "2. Tie Each Figure To Its Source",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "2. Tie Each Figure To Its Source": {
      "main": [
        [
          {
            "node": "Check 2: Every Verdict Is One Of The Three Allowed Words",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 2: Every Verdict Is One Of The Three Allowed Words": {
      "main": [
        [
          {
            "node": "Passed check 2?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 2?": {
      "main": [
        [
          {
            "node": "3. Check The Report Agrees With Itself",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 2)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 2)": {
      "main": [
        [
          {
            "node": "2. Tie Each Figure To Its Source",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 2": {
      "main": [
        [
          {
            "node": "3. Check The Report Agrees With Itself",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "3. Check The Report Agrees With Itself": {
      "main": [
        [
          {
            "node": "Check 3: Every Row Is Filled In",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 3: Every Row Is Filled In": {
      "main": [
        [
          {
            "node": "Passed check 3?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 3?": {
      "main": [
        [
          {
            "node": "4. Read It As The Board Will",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 3)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 3)": {
      "main": [
        [
          {
            "node": "3. Check The Report Agrees With Itself",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 3": {
      "main": [
        [
          {
            "node": "4. Read It As The Board Will",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "4. Read It As The Board Will": {
      "main": [
        [
          {
            "node": "Check 4: Every Part Has A Quote Or The Word Missing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 4: Every Part Has A Quote Or The Word Missing": {
      "main": [
        [
          {
            "node": "Passed check 4?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 4?": {
      "main": [
        [
          {
            "node": "5. Give The Verdict And The Fix List",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 4)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 4)": {
      "main": [
        [
          {
            "node": "4. Read It As The Board Will",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 4",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 4": {
      "main": [
        [
          {
            "node": "5. Give The Verdict And The Fix List",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "5. Give The Verdict And The Fix List": {
      "main": [
        [
          {
            "node": "Check 5: No Number Appears That The Report Or The Sources Do Not Have",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 5: No Number Appears That The Report Or The Sources Do Not Have": {
      "main": [
        [
          {
            "node": "Passed check 5?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 5?": {
      "main": [
        [
          {
            "node": "Check 6: Every 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. Give The Verdict And The Fix List",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 5",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 5": {
      "main": [
        [
          {
            "node": "Check 6: Every Type Is One Of The Five Allowed Words",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 6: Every Type Is One Of The Five Allowed Words": {
      "main": [
        [
          {
            "node": "Passed check 6?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 6?": {
      "main": [
        [
          {
            "node": "Check 7: Every Worked-Out Figure Was Recomputed",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 6)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 6)": {
      "main": [
        [
          {
            "node": "1. List Every Figure And Claim",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 6",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 6": {
      "main": [
        [
          {
            "node": "Check 7: Every Worked-Out Figure Was Recomputed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 7: Every Worked-Out Figure Was Recomputed": {
      "main": [
        [
          {
            "node": "Passed check 7?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 7?": {
      "main": [
        [
          {
            "node": "Check 8: Every Source Passage Is In The Source Figures",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 7)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 7)": {
      "main": [
        [
          {
            "node": "2. Tie Each Figure To Its Source",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 7",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 7": {
      "main": [
        [
          {
            "node": "Check 8: Every Source Passage Is In The Source Figures",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 8: Every Source Passage Is In The Source Figures": {
      "main": [
        [
          {
            "node": "Passed check 8?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 8?": {
      "main": [
        [
          {
            "node": "Check 9: Every Agrees Cell Is Yes Or No",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 8)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 8)": {
      "main": [
        [
          {
            "node": "2. Tie Each Figure To Its Source",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 8",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 8": {
      "main": [
        [
          {
            "node": "Check 9: Every Agrees Cell Is Yes Or No",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 9: Every Agrees Cell Is Yes Or No": {
      "main": [
        [
          {
            "node": "Passed check 9?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 9?": {
      "main": [
        [
          {
            "node": "Check 10: Every Stated Total Was Added Up",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 9)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 9)": {
      "main": [
        [
          {
            "node": "3. Check The Report Agrees With Itself",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 9",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 9": {
      "main": [
        [
          {
            "node": "Check 10: Every Stated Total Was Added Up",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 10: Every Stated Total Was Added Up": {
      "main": [
        [
          {
            "node": "Passed check 10?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 10?": {
      "main": [
        [
          {
            "node": "Check 11: The Opening Paragraph Is Short Enough",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 10)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 10)": {
      "main": [
        [
          {
            "node": "3. Check The Report Agrees With Itself",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 10",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 10": {
      "main": [
        [
          {
            "node": "Check 11: The Opening Paragraph Is Short Enough",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 11: The Opening Paragraph Is Short Enough": {
      "main": [
        [
          {
            "node": "Passed check 11?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 11?": {
      "main": [
        [
          {
            "node": "Check 12: The Line About Reading The Flagged Parts Is There",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 11",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 11": {
      "main": [
        [
          {
            "node": "Check 12: The Line About Reading The Flagged Parts Is There",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 12: The Line About Reading The Flagged Parts Is There": {
      "main": [
        [
          {
            "node": "Passed check 12?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 12?": {
      "main": [
        [
          {
            "node": "Check 13: Every Problem Has A Row",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 12)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 12)": {
      "main": [
        [
          {
            "node": "5. Give The Verdict And The Fix List",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 12",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 12": {
      "main": [
        [
          {
            "node": "Check 13: Every Problem Has A Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 13: Every Problem Has A Row": {
      "main": [
        [
          {
            "node": "Passed check 13?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 13?": {
      "main": [
        [
          {
            "node": "Check 14: The Verdict Follows The Written Rule",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 13)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 13)": {
      "main": [
        [
          {
            "node": "5. Give The Verdict And The Fix List",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 13",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 13": {
      "main": [
        [
          {
            "node": "Check 14: The Verdict Follows The Written Rule",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 14: The Verdict Follows The Written Rule": {
      "main": [
        [
          {
            "node": "Passed check 14?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 14?": {
      "main": [
        [
          {
            "node": "Result",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 14)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 14)": {
      "main": [
        [
          {
            "node": "5. Give The Verdict And The Fix List",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 14",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 14": {
      "main": [
        [
          {
            "node": "Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Chat model": {
      "ai_languageModel": [
        [
          {
            "node": "1. List Every Figure And Claim",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "2. Tie Each Figure To Its Source",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "3. Check The Report Agrees With Itself",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "4. Read It As The Board Will",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "5. Give The Verdict And The Fix List",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "Check 7: Every Worked-Out Figure Was Recomputed",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "Check 8: Every Source Passage Is In The Source Figures",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "Check 10: Every Stated Total Was Added Up",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "Check 11: The Opening Paragraph Is Short Enough",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "Check 13: Every Problem Has A Row",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "Check 14: The Verdict Follows The Written Rule",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "meta": {
    "template": "check-report-before-board"
  }
}
