{
  "name": "Draft interview questions",
  "nodes": [
    {
      "parameters": {
        "content": "## Draft interview questions\nTurn a job description into one set of job-related interview questions that every candidate gets, each tied to a duty, with what a good answer includes and a check that no question touches a protected topic.\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 What the job needs (quote) column appears in the job description, 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 \"past experience\", \"work sample\" or \"situation\" in the Type 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 one of \"age\", \"family or marital status\", \"pregnancy\", \"religion\", \"race or ethnicity\", \"nationality or origin\", \"disability or health\", \"gender or identity\", \"other protected topic\" or \"none\" in the Touches column. If it fails: try once more, then flag.\n- Check 4 (a plain rule, run automatically with no AI): The answer contains the text fragment \"Check this guide with your HR or legal team before you use it.\" (including inside a longer word). If it fails: try once more, then flag.\n- Check 5 (a plain rule, run automatically with no AI): Every row of the table has something written in \"Question\" and \"Good answer includes\" (a blank or a dash does not count). If it fails: try once more, then flag.\n- Check 6 (a plain rule, run automatically with no AI): Each quote in the Duty it tests (quote) column appears in the job description, with shortened fragments in the same order. If it fails: try once more, then flag.\n- Check 7 (a second AI prompt answers pass or fail): Count the rows of the table in the answer. The number asked for was below; if it was empty or outside 4 to 10, the right number is 6, but fewer is fine when the list of items is shorter. Does the count match? Answer FAIL and give the count and the number asked for. If it fails: try once more, then flag.\n- Check 8 (a second AI prompt answers pass or fail): Read every question in the guide. Does any of them ask, or invite the candidate to talk, about age, family or children, marital status, pregnancy, religion, race or ethnicity, nationality or origin, accent, disability or health, gender or identity, sexual orientation, citizenship, injuries or sick leave, family medical history, union membership, arrests or convictions, or current or past pay? Do any depend on a particular kind of school, employer or life? Answer FAIL and quote each such question or phrase. If it fails: try once more, then flag.\n- Check 9 (a second AI prompt answers pass or fail): Read the guide. Does it contain anything that changes the questions for a particular candidate, such as \"if the candidate is a graduate, ask\", a follow-up that depends on a person's background, or a question that is missing a Tests or Good answer includes line? Answer FAIL and quote each place. If it fails: try once more, then flag.\n- Check 10 (a second AI prompt answers pass or fail): Look at the table below. For each row where Replacement is not keep, is the original question gone from the guide and the replacement in its place? Answer FAIL and quote each original that is still there. If it fails: try once more, then flag.\n- Check 11 (you check it by hand): Read each question as a candidate from a very different background. Could they answer from their own life, or does it assume one kind of past? Ask a person who does or knows the job whether the good-answer points are the right ones. Make sure the whole panel uses the guide the same way, and have your HR or legal team check it against your own policy and local rules. Only a person can say what is fair in your setting. If it fails: flag it and carry on.\n\nFrom Logic Lab (logiclabhq.com). Free to use and adapt, with credit.",
        "height": 1160,
        "width": 760,
        "color": 5
      },
      "id": "fcda9011-9573-4082-a351-a05b185a6f54",
      "name": "About this workflow",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -40,
        -1120
      ]
    },
    {
      "parameters": {},
      "id": "22c22039-f29b-4336-a470-544b75a331d0",
      "name": "Start",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        0,
        320
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "87e69913-7578-4420-aaec-794d20715f62",
              "name": "role_info",
              "value": "Sample: Office and events coordinator, part time. What you will do: answer the front desk phone and email; book rooms for classes and rentals and keep the booking calendar current; set up and close down for about 6 events a month; order supplies and keep a simple spending sheet; send the monthly newsletter to the mailing list. What you need: friendly phone manner; accurate with a booking calendar and a spreadsheet; able to lift 30 pounds for event set-up. Nice to have: social media experience.",
              "type": "string"
            },
            {
              "id": "7ff8c5f9-847f-4e66-a6d0-2d4f0af1700c",
              "name": "count",
              "value": "Sample: 6",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "f117face-aaf0-473b-af6d-257488ee5279",
      "name": "Inputs",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        220,
        320
      ],
      "notes": "Paste your own material here: role_info = The job description; count = How many questions (optional).",
      "notesInFlow": true
    },
    {
      "parameters": {
        "promptType": "define",
        "hasOutputParser": false,
        "text": "=You are an experienced HR interviewer who plans questions from the job itself.\n\nYour task is to list what this job needs, using the job description below.\n\nThe job description:\n{{ $(\"Inputs\").first().json[\"role_info\"] }}\n\nRules:\n- List each duty and each must-have requirement. Leave out nice-to-have items.\n- Quote each one from the text word for word, as short as you can. Do not correct it and do not reword it.\n- One row for each, in the order the text gives them. Do not merge two items and do not add one the text does not have.\n- Do not add qualities that jobs like this one usually ask for, such as \"team player\" or \"fast learner\", unless the text says them.\n\nReturn one markdown table and nothing else:\n| # | What the job needs (quote) |{{ $(\"Check 1: Every item is quoted word for word from the job description\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Every item is quoted word for word from the job description. What was wrong: \" + $(\"Check 1: Every item is quoted word for word from the job description\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}"
      },
      "id": "e9666543-1e71-4ba6-a813-cdb1de4f42d4",
      "name": "1. List what the job needs",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        480,
        320
      ],
      "notes": "Pick out each duty and must-have from the job description, quoted word for word, as the list the questions will cover. Returns: A numbered table of the duties and must-haves, each quoted from the job description.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// Every item is quoted word for word from the job description\n// Rule: Each quote in the What the job needs (quote) column appears in the job description, with shortened fragments in the same order.\n// Runs with no AI. Returns pass (true or false) and a reason.\nconst runRule = function runRule(rule, value, work, sources) {\n    const norm = (s) => String(s ?? '')\n        .toLowerCase()\n        .replace(/[\\u2018\\u2019\\u201c\\u201d\"'`]/g, '')\n        .replace(/\\s+/g, ' ')\n        .trim();\n    const readTable = (text) => {\n        const lines = String(text ?? '')\n            .split(/\\r?\\n/)\n            .map((l) => l.trim());\n        const tables = [];\n        let cur = null;\n        const cells = (l) => l\n            .replace(/^\\|/, '')\n            .replace(/\\|$/, '')\n            .split('|')\n            .map((c) => c.trim());\n        for (let i = 0; i < lines.length; i++) {\n            const l = lines[i] ?? '';\n            if (!l.startsWith('|')) {\n                cur = null;\n                continue;\n            }\n            if (!cur) {\n                const next = lines[i + 1] ?? '';\n                if (/^\\|?\\s*:?-{2,}/.test(next)) {\n                    cur = { header: cells(l), rows: [] };\n                    tables.push(cur);\n                    i++;\n                }\n                continue;\n            }\n            cur.rows.push(cells(l));\n        }\n        return tables;\n    };\n    // Every table that has this column, with \"None found\" rows left out.\n    // Returns that column's cells from every such table, or null if no table has it.\n    const findColumn = (name) => {\n        let seen = false;\n        const cells = [];\n        for (const t of readTable(work)) {\n            const i = t.header.findIndex((h) => norm(h) === norm(name));\n            if (i < 0)\n                continue;\n            seen = true;\n            for (const r of t.rows) {\n                if (r.some((c) => norm(c) === 'none found'))\n                    continue;\n                cells.push(r[i] ?? '');\n            }\n        }\n        return seen ? cells : null;\n    };\n    const empty = (c) => !c || /^[-\\u2013\\u2014\\s]*$/.test(c);\n    switch (rule) {\n        case 'has_text':\n            return norm(work).includes(norm(value))\n                ? { pass: true, reason: 'Found.' }\n                : { pass: false, reason: `The answer does not include \"${value}\".` };\n        case 'lacks_text':\n            return norm(work).includes(norm(value))\n                ? { pass: false, reason: `The answer includes \"${value}\", which it should not.` }\n                : { pass: true, reason: 'Not found, as it should be.' };\n        case 'max_words': {\n            const n = String(work ?? '')\n                .split(/\\s+/)\n                .filter(Boolean).length;\n            return n <= Number(value)\n                ? { pass: true, reason: `${n} words.` }\n                : { pass: false, reason: `The answer has ${n} words; the limit is ${value}.` };\n        }\n        case 'table_cells_filled': {\n            const problems = [];\n            for (const col of value) {\n                const f = findColumn(col);\n                if (!f) {\n                    problems.push(`No table with a \"${col}\" column was found.`);\n                    continue;\n                }\n                if (f.length === 0) {\n                    problems.push(`The table has no data rows to check in \"${col}\".`);\n                    continue;\n                }\n                f.forEach((c, i) => {\n                    if (empty(c))\n                        problems.push(`Row ${i + 1} has nothing in \"${col}\".`);\n                });\n            }\n            return problems.length\n                ? { pass: false, reason: problems.join(' ') }\n                : { pass: true, reason: 'Every row is filled in.' };\n        }\n        case 'column_values_in': {\n            const f = findColumn(value.column);\n            if (!f)\n                return { pass: false, reason: `No table with a \"${value.column}\" column was found.` };\n            if (f.length === 0)\n                return { pass: false, reason: `The table has no data rows to check in \"${value.column}\".` };\n            const allowed = value.allowed.map(norm);\n            const bad = [];\n            f.forEach((c, i) => {\n                if (!allowed.includes(norm(c)))\n                    bad.push(`Row ${i + 1} says \"${c}\".`);\n            });\n            return bad.length\n                ? {\n                    pass: false,\n                    reason: `The \"${value.column}\" column must say ${value.allowed.join(' or ')}. ${bad.join(' ')}`,\n                }\n                : { pass: true, reason: 'Every row uses an allowed word.' };\n        }\n        case 'column_quotes_source': {\n            const f = findColumn(value.column);\n            if (!f)\n                return { pass: false, reason: `No table with a \"${value.column}\" column was found.` };\n            if (f.length === 0)\n                return { pass: false, reason: `The table has no data rows to check in \"${value.column}\".` };\n            const src = norm(sources[value.input] ?? '');\n            const bad = [];\n            f.forEach((c, i) => {\n                // A quote may be shortened with \"...\"; each piece must be in the source.\n                const pieces = c\n                    .split(/\\.\\.\\.|\\u2026/)\n                    .map(norm)\n                    .filter(Boolean);\n                let cursor = 0;\n                const ordered = pieces.every((p) => {\n                    const found = src.indexOf(p, cursor);\n                    if (found < 0)\n                        return false;\n                    cursor = found + p.length;\n                    return true;\n                });\n                if (!pieces.length || !ordered)\n                    bad.push(`Row ${i + 1}: \"${c}\" is not in the source in that order.`);\n            });\n            return bad.length\n                ? { pass: false, reason: bad.join(' ') }\n                : { pass: true, reason: 'Every quote was found in the source.' };\n        }\n        case 'numbers_in_source': {\n            const nums = (s) => (String(s ?? '').match(/\\d[\\d,]*(?:\\.\\d+)?/g) ?? []).map((n) => n.replace(/,/g, ''));\n            const ids = Array.isArray(value.inputs) ? value.inputs : [value.input];\n            const src = new Set(ids.flatMap((id) => nums(sources[id] ?? '')));\n            // With a column named, only the numbers in that column of a table are compared (a rank column is not a claim).\n            let target = work;\n            if (value.column) {\n                const cells = findColumn(String(value.column));\n                if (cells === null)\n                    return { pass: false, reason: `The answer has no table with a ${value.column} column.` };\n                target = cells.join(' ');\n            }\n            const missing = [...new Set(nums(target))].filter((n) => !src.has(n));\n            return missing.length\n                ? {\n                    pass: false,\n                    reason: `These numbers are not in the source: ${missing.join(', ')}.`,\n                }\n                : { pass: true, reason: 'Every number was found in the source.' };\n        }\n        default:\n            return { pass: false, reason: `Unknown rule \"${rule}\".` };\n    }\n};\n\nconst work = String($(\"1. List what the job needs\").first().json.text ?? '');\nconst sources = {\n  \"role_info\": String($(\"Inputs\").first().json[\"role_info\"] ?? ''),\n};\nconst result = runRule(\"column_quotes_source\", {\"column\":\"What the job needs (quote)\",\"input\":\"role_info\"}, work, sources);\nreturn [{ json: result }];"
      },
      "id": "7eec6113-339c-4432-aa6f-a26140ee7b90",
      "name": "Check 1: Every item is quoted word for word from the job description",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        740,
        320
      ],
      "notes": "Each quote in the What the job needs (quote) column appears in the job description, with shortened fragments in the same order.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "a5499d30-0468-473d-aa74-fb46e01492a3",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "3ef6c0d1-f2f4-446e-a750-19f3dfd17220",
      "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": "c9d84b3b-e517-46fa-a355-11211ed8acf0",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "2374d05f-ad79-4d06-a2fe-61298a175368",
      "name": "Try again? (check 1)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1000,
        520
      ],
      "notes": "True = first failure: send back to \"1. List what the job needs\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "960f5eba-9a8f-45ff-a50f-db4c88ea87c1",
              "name": "flag",
              "value": "=Every item is quoted word for word from the job description: did not pass. {{ $(\"Check 1: Every item is quoted word for word from the job description\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "0e4b99c6-b34b-4fd3-a6a6-d5d07be6fd65",
      "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 an interview designer who writes fair, job-related questions.\n\nYour task is to draft interview questions from the list of what the job needs.\n\nThe job description:\n{{ $(\"Inputs\").first().json[\"role_info\"] }}\n\nWhat the job needs:\n{{ $(\"1. List what the job needs\").first().json.text }}\n\nHow many questions (if this is empty, write 6):\n{{ $(\"Inputs\").first().json[\"count\"] }}\n\nRules:\n- Write the number of questions asked for, from 4 to 10. If the number is outside that range, write 6. If the list has fewer items than the number, write fewer questions. Do not write two questions for one item unless the number asks for more than the list has.\n- Each question tests one item on the list. Give the item's number in the Item # column.\n- Type must be exactly one of: past experience, work sample, situation. Past experience asks what the person has done. Work sample asks the person to do or explain a small piece of the job now. Situation asks what the person would do in a case the job could bring.\n- Write each question so that any candidate can answer from their own work, study, volunteering or daily life. Do not ask about a past employer, a school or a tool by name unless the job description names it.\n- In the Good answer includes column, write two or three short points, drawn only from the job description, that a strong answer would show. Separate the points with semicolons.\n- Do not ask about age, family, children, plans for children, pregnancy, marriage, sex, sexual orientation, gender identity, health, disability, injuries, past sick leave or workers' compensation claims, family medical history, religion, race, ethnic background, nationality, citizenship, accent, where the person grew up, union membership, arrests or convictions, current or past pay, or any other personal trait. You may ask whether the person can do a listed task, with or without a reasonable accommodation, and whether they are allowed to work in the US. Do not ask a question only some candidates could answer.\n\nReturn one markdown table and nothing else:\n| # | Item # | Question | Type | Good answer includes |{{ $(\"Check 2: Each type is one of the three allowed words\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Each type is one of the three allowed words. What was wrong: \" + $(\"Check 2: Each type is one of the three allowed words\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}{{ $(\"Check 5: Every question has its good answer points filled in\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Every question has its good answer points filled in. What was wrong: \" + $(\"Check 5: Every question has its good answer points filled in\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}{{ $(\"Check 7: The number of questions matches what was asked for\").isExecuted ? \"\\n\\nYour last answer did not pass this check: The number of questions matches what was asked for. What was wrong: \" + $(\"Check 7: The number of questions matches what was asked for\").first().json.text + \"\\nFix that and give your full answer again.\" : \"\" }}"
      },
      "id": "3869bd45-44b3-4e66-a873-f3ffdd17f938",
      "name": "2. Draft one question for each area",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        1260,
        320
      ],
      "notes": "Write job-related questions, each tied to one item on the list, with what a good answer includes. Returns: A table of questions, each tied to an item, with a type and what a good answer includes.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// Each type is one of the three allowed words\n// Rule: Every row of the table has one of \"past experience\", \"work sample\" or \"situation\" in the Type column.\n// Runs with no AI. Returns pass (true or false) and a reason.\nconst runRule = function runRule(rule, value, work, sources) {\n    const norm = (s) => String(s ?? '')\n        .toLowerCase()\n        .replace(/[\\u2018\\u2019\\u201c\\u201d\"'`]/g, '')\n        .replace(/\\s+/g, ' ')\n        .trim();\n    const readTable = (text) => {\n        const lines = String(text ?? '')\n            .split(/\\r?\\n/)\n            .map((l) => l.trim());\n        const tables = [];\n        let cur = null;\n        const cells = (l) => l\n            .replace(/^\\|/, '')\n            .replace(/\\|$/, '')\n            .split('|')\n            .map((c) => c.trim());\n        for (let i = 0; i < lines.length; i++) {\n            const l = lines[i] ?? '';\n            if (!l.startsWith('|')) {\n                cur = null;\n                continue;\n            }\n            if (!cur) {\n                const next = lines[i + 1] ?? '';\n                if (/^\\|?\\s*:?-{2,}/.test(next)) {\n                    cur = { header: cells(l), rows: [] };\n                    tables.push(cur);\n                    i++;\n                }\n                continue;\n            }\n            cur.rows.push(cells(l));\n        }\n        return tables;\n    };\n    // Every table that has this column, with \"None found\" rows left out.\n    // Returns that column's cells from every such table, or null if no table has it.\n    const findColumn = (name) => {\n        let seen = false;\n        const cells = [];\n        for (const t of readTable(work)) {\n            const i = t.header.findIndex((h) => norm(h) === norm(name));\n            if (i < 0)\n                continue;\n            seen = true;\n            for (const r of t.rows) {\n                if (r.some((c) => norm(c) === 'none found'))\n                    continue;\n                cells.push(r[i] ?? '');\n            }\n        }\n        return seen ? cells : null;\n    };\n    const empty = (c) => !c || /^[-\\u2013\\u2014\\s]*$/.test(c);\n    switch (rule) {\n        case 'has_text':\n            return norm(work).includes(norm(value))\n                ? { pass: true, reason: 'Found.' }\n                : { pass: false, reason: `The answer does not include \"${value}\".` };\n        case 'lacks_text':\n            return norm(work).includes(norm(value))\n                ? { pass: false, reason: `The answer includes \"${value}\", which it should not.` }\n                : { pass: true, reason: 'Not found, as it should be.' };\n        case 'max_words': {\n            const n = String(work ?? '')\n                .split(/\\s+/)\n                .filter(Boolean).length;\n            return n <= Number(value)\n                ? { pass: true, reason: `${n} words.` }\n                : { pass: false, reason: `The answer has ${n} words; the limit is ${value}.` };\n        }\n        case 'table_cells_filled': {\n            const problems = [];\n            for (const col of value) {\n                const f = findColumn(col);\n                if (!f) {\n                    problems.push(`No table with a \"${col}\" column was found.`);\n                    continue;\n                }\n                if (f.length === 0) {\n                    problems.push(`The table has no data rows to check in \"${col}\".`);\n                    continue;\n                }\n                f.forEach((c, i) => {\n                    if (empty(c))\n                        problems.push(`Row ${i + 1} has nothing in \"${col}\".`);\n                });\n            }\n            return problems.length\n                ? { pass: false, reason: problems.join(' ') }\n                : { pass: true, reason: 'Every row is filled in.' };\n        }\n        case 'column_values_in': {\n            const f = findColumn(value.column);\n            if (!f)\n                return { pass: false, reason: `No table with a \"${value.column}\" column was found.` };\n            if (f.length === 0)\n                return { pass: false, reason: `The table has no data rows to check in \"${value.column}\".` };\n            const allowed = value.allowed.map(norm);\n            const bad = [];\n            f.forEach((c, i) => {\n                if (!allowed.includes(norm(c)))\n                    bad.push(`Row ${i + 1} says \"${c}\".`);\n            });\n            return bad.length\n                ? {\n                    pass: false,\n                    reason: `The \"${value.column}\" column must say ${value.allowed.join(' or ')}. ${bad.join(' ')}`,\n                }\n                : { pass: true, reason: 'Every row uses an allowed word.' };\n        }\n        case 'column_quotes_source': {\n            const f = findColumn(value.column);\n            if (!f)\n                return { pass: false, reason: `No table with a \"${value.column}\" column was found.` };\n            if (f.length === 0)\n                return { pass: false, reason: `The table has no data rows to check in \"${value.column}\".` };\n            const src = norm(sources[value.input] ?? '');\n            const bad = [];\n            f.forEach((c, i) => {\n                // A quote may be shortened with \"...\"; each piece must be in the source.\n                const pieces = c\n                    .split(/\\.\\.\\.|\\u2026/)\n                    .map(norm)\n                    .filter(Boolean);\n                let cursor = 0;\n                const ordered = pieces.every((p) => {\n                    const found = src.indexOf(p, cursor);\n                    if (found < 0)\n                        return false;\n                    cursor = found + p.length;\n                    return true;\n                });\n                if (!pieces.length || !ordered)\n                    bad.push(`Row ${i + 1}: \"${c}\" is not in the source in that order.`);\n            });\n            return bad.length\n                ? { pass: false, reason: bad.join(' ') }\n                : { pass: true, reason: 'Every quote was found in the source.' };\n        }\n        case 'numbers_in_source': {\n            const nums = (s) => (String(s ?? '').match(/\\d[\\d,]*(?:\\.\\d+)?/g) ?? []).map((n) => n.replace(/,/g, ''));\n            const ids = Array.isArray(value.inputs) ? value.inputs : [value.input];\n            const src = new Set(ids.flatMap((id) => nums(sources[id] ?? '')));\n            // With a column named, only the numbers in that column of a table are compared (a rank column is not a claim).\n            let target = work;\n            if (value.column) {\n                const cells = findColumn(String(value.column));\n                if (cells === null)\n                    return { pass: false, reason: `The answer has no table with a ${value.column} column.` };\n                target = cells.join(' ');\n            }\n            const missing = [...new Set(nums(target))].filter((n) => !src.has(n));\n            return missing.length\n                ? {\n                    pass: false,\n                    reason: `These numbers are not in the source: ${missing.join(', ')}.`,\n                }\n                : { pass: true, reason: 'Every number was found in the source.' };\n        }\n        default:\n            return { pass: false, reason: `Unknown rule \"${rule}\".` };\n    }\n};\n\nconst work = String($(\"2. Draft one question for each area\").first().json.text ?? '');\nconst sources = {\n\n};\nconst result = runRule(\"column_values_in\", {\"column\":\"Type\",\"allowed\":[\"past experience\",\"work sample\",\"situation\"]}, work, sources);\nreturn [{ json: result }];"
      },
      "id": "2cb36ab7-0cd3-4a8a-a2e6-a45d55c3b978",
      "name": "Check 2: Each type 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 \"past experience\", \"work sample\" or \"situation\" in the Type column.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "a6499ec3-0368-45aa-ab74-fcd9df149110",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "befe4616-72ec-4f29-a74d-db5cdfd3b0b7",
      "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": "c8d849a8-e617-488d-a255-0f8e1fd8ae83",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "a37c55a4-d57b-4a95-aafb-fcca0a0fce23",
      "name": "Try again? (check 2)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1780,
        520
      ],
      "notes": "True = first failure: send back to \"2. Draft one question for each area\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "970f604d-998f-446c-a60f-dcdf87ea862e",
              "name": "flag",
              "value": "=Each type is one of the three allowed words: did not pass. {{ $(\"Check 2: Each type is one of the three allowed words\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "0d4b9833-b44b-4166-a9a6-da8978e6f8ac",
      "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 fair-hiring reviewer who reads interview questions the way a candidate and a regulator would.\n\nYour task is to screen each question below for a protected topic and for a link to the job.\n\nThe job description:\n{{ $(\"Inputs\").first().json[\"role_info\"] }}\n\nThe questions:\n{{ $(\"2. Draft one question for each area\").first().json.text }}\n\nRules:\n- One row for each question, in the same order. Do not skip a question and do not add one.\n- In the Question # column, give the number from the questions table.\n- In the Touches column, write exactly one of: age, family or marital status, pregnancy, religion, race or ethnicity, nationality or origin, disability or health, gender or identity, other protected topic, none. Write none only if the question asks about nothing but the work.\n- In the Duty it tests column, quote the words from the job description that the question tests, word for word, as short as you can. If no words in the job description are tested, write the single word none.\n- In the Replacement column, write a safe question that tests the same duty when Touches is not none or the duty is none. Otherwise write keep.\n- Do not accept a question because it sounds friendly. Judge what it asks, not how it sounds.\n- A question about whether the person can do a listed task, with or without a reasonable accommodation, is about the work: write none.\n\nReturn one markdown table and nothing else:\n| # | Question # | Touches | Duty it tests (quote) | Replacement |{{ $(\"Check 3: Each topic is one of the allowed words\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Each topic is one of the allowed words. What was wrong: \" + $(\"Check 3: Each topic is one of the allowed words\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}{{ $(\"Check 6: Every question is tied to words in the job description\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Every question is tied to words in the job description. What was wrong: \" + $(\"Check 6: Every question is tied to words in the job description\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}"
      },
      "id": "929eae5e-8e6a-4879-af34-4fa0e103fef3",
      "name": "3. Screen every question for protected topics",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        2040,
        320
      ],
      "notes": "Test each question for a protected topic and for a link to the job, and give a safe replacement for any that fail. Returns: A table that screens each question, with the duty it tests and a replacement where needed.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// Each topic is one of the allowed words\n// Rule: Every row of the table has one of \"age\", \"family or marital status\", \"pregnancy\", \"religion\", \"race or ethnicity\", \"nationality or origin\", \"disability or health\", \"gender or identity\", \"other protected topic\" or \"none\" in the Touches 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. Screen every question for protected topics\").first().json.text ?? '');\nconst sources = {\n\n};\nconst result = runRule(\"column_values_in\", {\"column\":\"Touches\",\"allowed\":[\"age\",\"family or marital status\",\"pregnancy\",\"religion\",\"race or ethnicity\",\"nationality or origin\",\"disability or health\",\"gender or identity\",\"other protected topic\",\"none\"]}, work, sources);\nreturn [{ json: result }];"
      },
      "id": "001b16ab-bb9d-45c0-aa5b-73e948a2822e",
      "name": "Check 3: Each topic is one of the allowed words",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2300,
        320
      ],
      "notes": "Every row of the table has one of \"age\", \"family or marital status\", \"pregnancy\", \"religion\", \"race or ethnicity\", \"nationality or origin\", \"disability or health\", \"gender or identity\", \"other protected topic\" or \"none\" in the Touches column.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "a749a056-0268-4417-a874-f820e21495c9",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "befc077f-72ee-4dc0-a74a-d3455fd6b8ce",
      "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": "cbd84e61-e317-43d4-a155-0dfb20d8b016",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "bf7a4321-c97e-4648-a6f9-ea47ee11e0a6",
      "name": "Try again? (check 3)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2560,
        520
      ],
      "notes": "True = first failure: send back to \"3. Screen every question for protected topics\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "940f5b94-9c8f-4925-a70f-de7286ea849b",
              "name": "flag",
              "value": "=Each topic is one of the allowed words: did not pass. {{ $(\"Check 3: Each topic is one of the allowed words\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "0c4b96a0-b54b-42f9-a8a6-d8f679e6fa3f",
      "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 an HR professional who prepares a structured interview guide for a panel.\n\nYour task is to write the interview guide from the questions and the screen.\n\nThe questions:\n{{ $(\"2. Draft one question for each area\").first().json.text }}\n\nThe screen of each question:\n{{ $(\"3. Screen every question for protected topics\").first().json.text }}\n\nRules:\n- Use each question as written, except where the Replacement column holds a question. Then use the replacement and drop the original.\n- Number the questions in the order of the questions table. Under each, write Tests: with the duty quote from the screen, Good answer includes: with the points from the questions table, and Notes: with a blank line for the interviewer to write what the candidate actually said.\n- Open with a short section called How to use this guide. It must say: ask every candidate the same questions in the same order; use the same follow-ups for every candidate, such as \"Can you say more about what you did?\"; write down what the candidate said, not an impression; compare answers only to the points listed, not to other candidates.\n- Do not add a question, a follow-up that depends on the candidate's background, or a point the questions table does not have.\n- Do not use the word \"fit\" or \"culture\" as something to judge.\n- End with exactly this line: Check this guide with your HR or legal team before you use it.\n\nReturn the guide in markdown and nothing else.{{ $(\"Check 4: The reminder to check the guide with your HR or legal team is in the final text\").isExecuted ? \"\\n\\nYour last answer did not pass this check: The reminder to check the guide with your HR or legal team is in the final text. What was wrong: \" + $(\"Check 4: The reminder to check the guide with your HR or legal team is in the final text\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}{{ $(\"Check 8: No question in the guide touches a protected topic\").isExecuted ? \"\\n\\nYour last answer did not pass this check: No question in the guide touches a protected topic. What was wrong: \" + $(\"Check 8: No question in the guide touches a protected topic\").first().json.text + \"\\nFix that and give your full answer again.\" : \"\" }}{{ $(\"Check 9: The guide asks every candidate the same questions in the same way\").isExecuted ? \"\\n\\nYour last answer did not pass this check: The guide asks every candidate the same questions in the same way. What was wrong: \" + $(\"Check 9: The guide asks every candidate the same questions in the same way\").first().json.text + \"\\nFix that and give your full answer again.\" : \"\" }}{{ $(\"Check 10: Every flagged question was replaced\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Every flagged question was replaced. What was wrong: \" + $(\"Check 10: Every flagged question was replaced\").first().json.text + \"\\nFix that and give your full answer again.\" : \"\" }}"
      },
      "id": "96b61842-c079-4b43-a13a-6db8f0c1f041",
      "name": "4. Write the interview guide",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        2820,
        320
      ],
      "notes": "Put the cleared questions into one guide that every candidate gets, with fixed follow-ups and a place to note evidence. Returns: A structured interview guide that every candidate is asked in the same way.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// The reminder to check the guide with your HR or legal team is in the final text\n// Rule: The answer contains the text fragment \"Check this guide with your HR or legal team before you use it.\" (including inside a longer word).\n// Runs with no AI. Returns pass (true or false) and a reason.\nconst runRule = function runRule(rule, value, work, sources) {\n    const norm = (s) => String(s ?? '')\n        .toLowerCase()\n        .replace(/[\\u2018\\u2019\\u201c\\u201d\"'`]/g, '')\n        .replace(/\\s+/g, ' ')\n        .trim();\n    const readTable = (text) => {\n        const lines = String(text ?? '')\n            .split(/\\r?\\n/)\n            .map((l) => l.trim());\n        const tables = [];\n        let cur = null;\n        const cells = (l) => l\n            .replace(/^\\|/, '')\n            .replace(/\\|$/, '')\n            .split('|')\n            .map((c) => c.trim());\n        for (let i = 0; i < lines.length; i++) {\n            const l = lines[i] ?? '';\n            if (!l.startsWith('|')) {\n                cur = null;\n                continue;\n            }\n            if (!cur) {\n                const next = lines[i + 1] ?? '';\n                if (/^\\|?\\s*:?-{2,}/.test(next)) {\n                    cur = { header: cells(l), rows: [] };\n                    tables.push(cur);\n                    i++;\n                }\n                continue;\n            }\n            cur.rows.push(cells(l));\n        }\n        return tables;\n    };\n    // Every table that has this column, with \"None found\" rows left out.\n    // Returns that column's cells from every such table, or null if no table has it.\n    const findColumn = (name) => {\n        let seen = false;\n        const cells = [];\n        for (const t of readTable(work)) {\n            const i = t.header.findIndex((h) => norm(h) === norm(name));\n            if (i < 0)\n                continue;\n            seen = true;\n            for (const r of t.rows) {\n                if (r.some((c) => norm(c) === 'none found'))\n                    continue;\n                cells.push(r[i] ?? '');\n            }\n        }\n        return seen ? cells : null;\n    };\n    const empty = (c) => !c || /^[-\\u2013\\u2014\\s]*$/.test(c);\n    switch (rule) {\n        case 'has_text':\n            return norm(work).includes(norm(value))\n                ? { pass: true, reason: 'Found.' }\n                : { pass: false, reason: `The answer does not include \"${value}\".` };\n        case 'lacks_text':\n            return norm(work).includes(norm(value))\n                ? { pass: false, reason: `The answer includes \"${value}\", which it should not.` }\n                : { pass: true, reason: 'Not found, as it should be.' };\n        case 'max_words': {\n            const n = String(work ?? '')\n                .split(/\\s+/)\n                .filter(Boolean).length;\n            return n <= Number(value)\n                ? { pass: true, reason: `${n} words.` }\n                : { pass: false, reason: `The answer has ${n} words; the limit is ${value}.` };\n        }\n        case 'table_cells_filled': {\n            const problems = [];\n            for (const col of value) {\n                const f = findColumn(col);\n                if (!f) {\n                    problems.push(`No table with a \"${col}\" column was found.`);\n                    continue;\n                }\n                if (f.length === 0) {\n                    problems.push(`The table has no data rows to check in \"${col}\".`);\n                    continue;\n                }\n                f.forEach((c, i) => {\n                    if (empty(c))\n                        problems.push(`Row ${i + 1} has nothing in \"${col}\".`);\n                });\n            }\n            return problems.length\n                ? { pass: false, reason: problems.join(' ') }\n                : { pass: true, reason: 'Every row is filled in.' };\n        }\n        case 'column_values_in': {\n            const f = findColumn(value.column);\n            if (!f)\n                return { pass: false, reason: `No table with a \"${value.column}\" column was found.` };\n            if (f.length === 0)\n                return { pass: false, reason: `The table has no data rows to check in \"${value.column}\".` };\n            const allowed = value.allowed.map(norm);\n            const bad = [];\n            f.forEach((c, i) => {\n                if (!allowed.includes(norm(c)))\n                    bad.push(`Row ${i + 1} says \"${c}\".`);\n            });\n            return bad.length\n                ? {\n                    pass: false,\n                    reason: `The \"${value.column}\" column must say ${value.allowed.join(' or ')}. ${bad.join(' ')}`,\n                }\n                : { pass: true, reason: 'Every row uses an allowed word.' };\n        }\n        case 'column_quotes_source': {\n            const f = findColumn(value.column);\n            if (!f)\n                return { pass: false, reason: `No table with a \"${value.column}\" column was found.` };\n            if (f.length === 0)\n                return { pass: false, reason: `The table has no data rows to check in \"${value.column}\".` };\n            const src = norm(sources[value.input] ?? '');\n            const bad = [];\n            f.forEach((c, i) => {\n                // A quote may be shortened with \"...\"; each piece must be in the source.\n                const pieces = c\n                    .split(/\\.\\.\\.|\\u2026/)\n                    .map(norm)\n                    .filter(Boolean);\n                let cursor = 0;\n                const ordered = pieces.every((p) => {\n                    const found = src.indexOf(p, cursor);\n                    if (found < 0)\n                        return false;\n                    cursor = found + p.length;\n                    return true;\n                });\n                if (!pieces.length || !ordered)\n                    bad.push(`Row ${i + 1}: \"${c}\" is not in the source in that order.`);\n            });\n            return bad.length\n                ? { pass: false, reason: bad.join(' ') }\n                : { pass: true, reason: 'Every quote was found in the source.' };\n        }\n        case 'numbers_in_source': {\n            const nums = (s) => (String(s ?? '').match(/\\d[\\d,]*(?:\\.\\d+)?/g) ?? []).map((n) => n.replace(/,/g, ''));\n            const ids = Array.isArray(value.inputs) ? value.inputs : [value.input];\n            const src = new Set(ids.flatMap((id) => nums(sources[id] ?? '')));\n            // With a column named, only the numbers in that column of a table are compared (a rank column is not a claim).\n            let target = work;\n            if (value.column) {\n                const cells = findColumn(String(value.column));\n                if (cells === null)\n                    return { pass: false, reason: `The answer has no table with a ${value.column} column.` };\n                target = cells.join(' ');\n            }\n            const missing = [...new Set(nums(target))].filter((n) => !src.has(n));\n            return missing.length\n                ? {\n                    pass: false,\n                    reason: `These numbers are not in the source: ${missing.join(', ')}.`,\n                }\n                : { pass: true, reason: 'Every number was found in the source.' };\n        }\n        default:\n            return { pass: false, reason: `Unknown rule \"${rule}\".` };\n    }\n};\n\nconst work = String($(\"4. Write the interview guide\").first().json.text ?? '');\nconst sources = {\n\n};\nconst result = runRule(\"has_text\", \"Check this guide with your HR or legal team before you use it.\", work, sources);\nreturn [{ json: result }];"
      },
      "id": "b989e21f-b9a9-4e1e-a80d-36d13efacd68",
      "name": "Check 4: The reminder to check the guide with your HR or legal team is in the final text",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3080,
        320
      ],
      "notes": "The answer contains the text fragment \"Check this guide with your HR or legal team before you use it.\" (including inside a longer word).",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "a849a1e9-0168-4284-a974-f9b3e1149436",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "beef3b8c-72e8-41fb-a75c-1c665fc56fad",
      "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": "cad84cce-e417-4567-a055-0c6821d8b1a9",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "bf6d772e-3180-4897-aef7-6cb8ee1eac99",
      "name": "Try again? (check 4)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        3340,
        520
      ],
      "notes": "True = first failure: send back to \"4. Write the interview guide\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "950f5d27-9b8f-4792-a80f-e00585ea8308",
              "name": "flag",
              "value": "=The reminder to check the guide with your HR or legal team is in the final text: did not pass. {{ $(\"Check 4: The reminder to check the guide with your HR or legal team is in the final text\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "134ba1a5-b64b-448c-aba6-ddaf76e6f586",
      "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": {
        "jsCode": "// Every question has its good answer points filled in\n// Rule: Every row of the table has something written in \"Question\" and \"Good answer includes\" (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($(\"2. Draft one question for each area\").first().json.text ?? '');\nconst sources = {\n\n};\nconst result = runRule(\"table_cells_filled\", [\"Question\",\"Good answer includes\"], work, sources);\nreturn [{ json: result }];"
      },
      "id": "4e91936e-71c2-4f2f-aea7-8bc805096ec9",
      "name": "Check 5: Every question has its good answer points filled in",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3600,
        320
      ],
      "notes": "Every row of the table has something written in \"Question\" and \"Good answer includes\" (a blank or a dash does not count).",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "a949a37c-0068-40f1-ae75-0192e41498ef",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "beecfcf5-72ea-4092-a759-ddcf5fc7ae44",
      "name": "Passed check 5?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        3860,
        320
      ],
      "notes": "True = passed. False = did not pass.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "cdd85187-e117-40ae-a755-176d22d8b33c",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "9b6affeb-5583-4fda-aaf4-7785122123dc",
      "name": "Try again? (check 5)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        3860,
        520
      ],
      "notes": "True = first failure: send back to \"2. Draft one question for each area\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "920f586e-968f-4fb3-a10f-d5008cea8e0d",
              "name": "flag",
              "value": "=Every question has its good answer points filled in: did not pass. {{ $(\"Check 5: Every question has its good answer points filled in\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "124ba012-b74b-461f-aaa6-dc1c77e6f719",
      "name": "Flag check 5",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        3990,
        700
      ],
      "notes": "Notes the problem and carries on. The note shows in Result.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// Every question is tied to words in the job description\n// Rule: Each quote in the Duty it tests (quote) column appears in the job description, 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($(\"3. Screen every question for protected topics\").first().json.text ?? '');\nconst sources = {\n  \"role_info\": String($(\"Inputs\").first().json[\"role_info\"] ?? ''),\n};\nconst result = runRule(\"column_quotes_source\", {\"column\":\"Duty it tests (quote)\",\"input\":\"role_info\"}, work, sources);\nreturn [{ json: result }];"
      },
      "id": "b2a0e607-711f-422c-a64d-45ad350eb46a",
      "name": "Check 6: Every question is tied to words in the job description",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        4120,
        320
      ],
      "notes": "Each quote in the Duty it tests (quote) column appears in the job description, with shortened fragments in the same order.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "aa49a50f-ff68-4f5e-af75-0325e314975c",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "3ef4823a-f2e2-4b4d-a757-9f385fc9ecdb",
      "name": "Passed check 6?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        4380,
        320
      ],
      "notes": "True = passed. False = did not pass.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "ccd84ff4-e217-4241-a655-15da23d8b4cf",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "3b72b790-2d85-4f79-a2f2-5eb672196c37",
      "name": "Try again? (check 6)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        4380,
        520
      ],
      "notes": "True = first failure: send back to \"3. Screen every question for protected topics\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "930f5a01-958f-4e20-a20f-d6938bea8c7a",
              "name": "flag",
              "value": "=Every question is tied to words in the job description: did not pass. {{ $(\"Check 6: Every question is tied to words in the job description\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "114b9e7f-b84b-47b2-ada6-e0d574e6f260",
      "name": "Flag check 6",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        4510,
        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 number of questions matches what was asked for\nCount the rows of the table in the answer. The number asked for was below; if it was empty or outside 4 to 10, the right number is 6, but fewer is fine when the list of items is shorter. Does the count match? Answer FAIL and give the count and the number asked for.\n\nThe number asked for:\n{{ $(\"Inputs\").first().json[\"count\"] }}\n\nThe work to check (the answer from Step 2 (Draft one question for each area)):\n{{ $(\"2. Draft one question for each area\").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": "60e049c5-0ea6-48be-a343-b8b7f759ca18",
      "name": "Check 7: The number of questions matches what was asked for",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        4640,
        320
      ],
      "notes": "Answers PASS or FAIL. Count the rows of the table in the answer. The number asked for was below; if it was empty or outside 4 to 10, the right number is 6, but fewer is fine when the list of items is shorter. Does the count match? Answer FAIL and give the count and the number asked for.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "ab49a6a2-fe68-4dcb-ac74-fe6ce6149c15",
              "leftValue": "={{ String($json.text ?? '').trim().toUpperCase().startsWith('PASS') }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "3ef243a3-f2e5-49e4-a755-60a15fcc2b72",
      "name": "Passed check 7?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        4900,
        320
      ],
      "notes": "True = passed. False = did not pass.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "cfd854ad-df17-4d88-a555-144724d8b662",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "2770597d-5187-46bc-aeef-e773961be37a",
      "name": "Try again? (check 7)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        4900,
        520
      ],
      "notes": "True = first failure: send back to \"2. Draft one question for each area\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "900f5548-988f-42d9-a30f-d8268aea8ae7",
              "name": "flag",
              "value": "=The number of questions matches what was asked for: did not pass. {{ $(\"Check 7: The number of questions matches what was asked for\").first().json.text }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "104b9cec-b94b-4945-aca6-df4275e6f3f3",
      "name": "Flag check 7",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        5030,
        700
      ],
      "notes": "Notes the problem and carries on. The note shows in Result.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "promptType": "define",
        "hasOutputParser": false,
        "text": "=You are a careful reviewer. You do not fix or rewrite the work; you only decide whether it passes one check.\n\nThe check: No question in the guide touches a protected topic\nRead every question in the guide. Does any of them ask, or invite the candidate to talk, about age, family or children, marital status, pregnancy, religion, race or ethnicity, nationality or origin, accent, disability or health, gender or identity, sexual orientation, citizenship, injuries or sick leave, family medical history, union membership, arrests or convictions, or current or past pay? Do any depend on a particular kind of school, employer or life? Answer FAIL and quote each such question or phrase.\n\nThe work to check (the answer from Step 4 (Write the interview guide)):\n{{ $(\"4. Write the interview guide\").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": "2de1ac5d-ed6a-4c1a-a3dc-2353aa5cd518",
      "name": "Check 8: No question in the guide touches a protected topic",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        5160,
        320
      ],
      "notes": "Answers PASS or FAIL. Read every question in the guide. Does any of them ask, or invite the candidate to talk, about age, family or children, marital status, pregnancy, religion, race or ethnicity, nationality or origin, accent, disability or health, gender or identity, sexual orientation, citizenship, injuries or sick leave, family medical history, union membership, arrests or convictions, or current or past pay? Do any depend on a particular kind of school, employer or life? Answer FAIL and quote each such question or phrase.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "ac49a835-fd68-4c38-ad74-ffffe5149a82",
              "leftValue": "={{ String($json.text ?? '').trim().toUpperCase().startsWith('PASS') }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "3ee577b0-f305-4d8f-a73e-d0d2dfe2bb41",
      "name": "Passed check 8?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        5420,
        320
      ],
      "notes": "True = passed. False = did not pass.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "ced8531a-e017-4f1b-a455-12b425d8b7f5",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "c78b98da-2962-46eb-a6ed-1e54f600a41d",
      "name": "Try again? (check 8)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        5420,
        520
      ],
      "notes": "True = first failure: send back to \"4. Write the interview guide\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "910f56db-978f-4146-a40f-d9b989ea8954",
              "name": "flag",
              "value": "=No question in the guide touches a protected topic: did not pass. {{ $(\"Check 8: No question in the guide touches a protected topic\").first().json.text }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "174ba7f1-ba4b-4ad8-afa6-e3fb82e7086a",
      "name": "Flag check 8",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        5550,
        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 guide asks every candidate the same questions in the same way\nRead the guide. Does it contain anything that changes the questions for a particular candidate, such as \"if the candidate is a graduate, ask\", a follow-up that depends on a person's background, or a question that is missing a Tests or Good answer includes line? Answer FAIL and quote each place.\n\nThe work to check (the answer from Step 4 (Write the interview guide)):\n{{ $(\"4. Write the interview guide\").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": "a5f205a0-2430-4865-a8e8-dc8e71f439c3",
      "name": "Check 9: The guide asks every candidate the same questions in the same way",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        5680,
        320
      ],
      "notes": "Answers PASS or FAIL. Read the guide. Does it contain anything that changes the questions for a particular candidate, such as \"if the candidate is a graduate, ask\", a follow-up that depends on a person's background, or a question that is missing a Tests or Good answer includes line? Answer FAIL and quote each place.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "ad49a9c8-fc68-4aa5-a275-07dee8149f3b",
              "leftValue": "={{ String($json.text ?? '').trim().toUpperCase().startsWith('PASS') }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "3ee33919-f307-4c26-a73c-923bdfe4f9d8",
      "name": "Passed check 9?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        5940,
        320
      ],
      "notes": "True = passed. False = did not pass.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "c1d83ea3-dd17-4a62-ab55-048926d8b988",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "a3892197-4d65-4e2e-a2ea-a7110a030230",
      "name": "Try again? (check 9)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        5940,
        520
      ],
      "notes": "True = first failure: send back to \"4. Write the interview guide\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "8e0f5222-a28f-4297-ad0f-e7e480ea7b29",
              "name": "flag",
              "value": "=The guide asks every candidate the same questions in the same way: did not pass. {{ $(\"Check 9: The guide asks every candidate the same questions in the same way\").first().json.text }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "164ba65e-bb4b-4c6b-aea6-e26883e709fd",
      "name": "Flag check 9",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        6070,
        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 flagged question was replaced\nLook at the table below. For each row where Replacement is not keep, is the original question gone from the guide and the replacement in its place? Answer FAIL and quote each original that is still there.\n\nThe screen of each question:\n{{ $(\"3. Screen every question for protected topics\").first().json.text }}\n\nThe work to check (the answer from Step 4 (Write the interview guide)):\n{{ $(\"4. Write the interview guide\").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": "f1c9503a-7dae-4675-ac45-6d0c93125027",
      "name": "Check 10: Every flagged question was replaced",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        6200,
        320
      ],
      "notes": "Answers PASS or FAIL. Look at the table below. For each row where Replacement is not keep, is the original question gone from the guide and the replacement in its place? Answer FAIL and quote each original that is still there.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "ae49ab5b-fb68-4912-a375-0971e7149da8",
              "leftValue": "={{ String($json.text ?? '').trim().toUpperCase().startsWith('PASS') }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "0c606d89-4762-4910-af38-efcf75db77a6",
      "name": "Passed check 10?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        6460,
        320
      ],
      "notes": "True = passed. False = did not pass.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "c0d83d10-de17-4bf5-aa55-02f627d8bb1b",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "c31d4587-b964-4d58-a25c-6875477f25ee",
      "name": "Try again? (check 10)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        6460,
        520
      ],
      "notes": "True = first failure: send back to \"4. Write the interview guide\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "8f0f53b5-a18f-4104-ae0f-e9777fea7996",
              "name": "flag",
              "value": "=Every flagged question was replaced: did not pass. {{ $(\"Check 10: Every flagged question was replaced\").first().json.text }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "77035e42-225c-4259-a8a2-afa061a0cccf",
      "name": "Flag check 10",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        6590,
        700
      ],
      "notes": "Notes the problem and carries on. The note shows in Result.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "content": "**Check 11: check by hand**\nRead each question as a candidate from a very different background. Could they answer from their own life, or does it assume one kind of past? Ask a person who does or knows the job whether the good-answer points are the right ones. Make sure the whole panel uses the guide the same way, and have your HR or legal team check it against your own policy and local rules. Only a person can say what is fair in your setting.\n\nIf not: flag it and carry on. This check is also listed in Result.",
        "height": 200,
        "width": 240,
        "color": 3
      },
      "id": "da048f90-bd33-4f55-ad55-dc92fc489947",
      "name": "Check 11 (by hand)",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        6440,
        70
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "22d5a8e1-2414-433a-acd4-2d5fbaacf268",
              "name": "result",
              "value": "={{ $(\"4. Write the interview guide\").first().json.text }}",
              "type": "string"
            },
            {
              "id": "d7f09cbe-8988-4c83-ae7d-48ac09242d19",
              "name": "flagged",
              "value": "={{ [$(\"Flag check 1\").isExecuted ? $(\"Flag check 1\").first().json.flag : \"\", $(\"Flag check 2\").isExecuted ? $(\"Flag check 2\").first().json.flag : \"\", $(\"Flag check 3\").isExecuted ? $(\"Flag check 3\").first().json.flag : \"\", $(\"Flag check 4\").isExecuted ? $(\"Flag check 4\").first().json.flag : \"\", $(\"Flag check 5\").isExecuted ? $(\"Flag check 5\").first().json.flag : \"\", $(\"Flag check 6\").isExecuted ? $(\"Flag check 6\").first().json.flag : \"\", $(\"Flag check 7\").isExecuted ? $(\"Flag check 7\").first().json.flag : \"\", $(\"Flag check 8\").isExecuted ? $(\"Flag check 8\").first().json.flag : \"\", $(\"Flag check 9\").isExecuted ? $(\"Flag check 9\").first().json.flag : \"\", $(\"Flag check 10\").isExecuted ? $(\"Flag check 10\").first().json.flag : \"\"].filter(x => x).join(\"\\n\") || \"None\" }}",
              "type": "string"
            },
            {
              "id": "77dace0c-64d9-45b9-a363-78fadc202adf",
              "name": "check_by_hand",
              "value": "- Read each question as a candidate from a very different background. Could they answer from their own life, or does it assume one kind of past? Ask a person who does or knows the job whether the good-answer points are the right ones. Make sure the whole panel uses the guide the same way, and have your HR or legal team check it against your own policy and local rules. Only a person can say what is fair in your setting.",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "15d70152-c0d2-409f-a59b-ecc8777d1765",
      "name": "Result",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        6720,
        320
      ],
      "notes": "The final answer, anything flagged, and the checks left for you.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "options": {}
      },
      "id": "0c390d1c-6f3e-44e5-af28-d98ae299042b",
      "name": "Chat model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "typeVersion": 1.2,
      "position": [
        480,
        840
      ],
      "notes": "Attach your credential here, or swap in any chat model node.",
      "notesInFlow": true
    }
  ],
  "connections": {
    "Start": {
      "main": [
        [
          {
            "node": "Inputs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Inputs": {
      "main": [
        [
          {
            "node": "1. List what the job needs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "1. List what the job needs": {
      "main": [
        [
          {
            "node": "Check 1: Every item is quoted word for word from the job description",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 1: Every item is quoted word for word from the job description": {
      "main": [
        [
          {
            "node": "Passed check 1?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 1?": {
      "main": [
        [
          {
            "node": "2. Draft one question for each area",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 1)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 1)": {
      "main": [
        [
          {
            "node": "1. List what the job needs",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 1": {
      "main": [
        [
          {
            "node": "2. Draft one question for each area",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "2. Draft one question for each area": {
      "main": [
        [
          {
            "node": "Check 2: Each type is one of the three allowed words",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 2: Each type is one of the three allowed words": {
      "main": [
        [
          {
            "node": "Passed check 2?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 2?": {
      "main": [
        [
          {
            "node": "3. Screen every question for protected topics",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 2)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 2)": {
      "main": [
        [
          {
            "node": "2. Draft one question for each area",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 2": {
      "main": [
        [
          {
            "node": "3. Screen every question for protected topics",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "3. Screen every question for protected topics": {
      "main": [
        [
          {
            "node": "Check 3: Each topic is one of the allowed words",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 3: Each topic is one of the allowed words": {
      "main": [
        [
          {
            "node": "Passed check 3?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 3?": {
      "main": [
        [
          {
            "node": "4. Write the interview guide",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 3)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 3)": {
      "main": [
        [
          {
            "node": "3. Screen every question for protected topics",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 3": {
      "main": [
        [
          {
            "node": "4. Write the interview guide",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "4. Write the interview guide": {
      "main": [
        [
          {
            "node": "Check 4: The reminder to check the guide with your HR or legal team is in the final text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 4: The reminder to check the guide with your HR or legal team is in the final text": {
      "main": [
        [
          {
            "node": "Passed check 4?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 4?": {
      "main": [
        [
          {
            "node": "Check 5: Every question has its good answer points filled in",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 4)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 4)": {
      "main": [
        [
          {
            "node": "4. Write the interview guide",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 4",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 4": {
      "main": [
        [
          {
            "node": "Check 5: Every question has its good answer points filled in",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 5: Every question has its good answer points filled in": {
      "main": [
        [
          {
            "node": "Passed check 5?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 5?": {
      "main": [
        [
          {
            "node": "Check 6: Every question is tied to words in the job description",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 5)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 5)": {
      "main": [
        [
          {
            "node": "2. Draft one question for each area",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 5",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 5": {
      "main": [
        [
          {
            "node": "Check 6: Every question is tied to words in the job description",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 6: Every question is tied to words in the job description": {
      "main": [
        [
          {
            "node": "Passed check 6?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 6?": {
      "main": [
        [
          {
            "node": "Check 7: The number of questions matches what was asked for",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 6)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 6)": {
      "main": [
        [
          {
            "node": "3. Screen every question for protected topics",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 6",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 6": {
      "main": [
        [
          {
            "node": "Check 7: The number of questions matches what was asked for",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 7: The number of questions matches what was asked for": {
      "main": [
        [
          {
            "node": "Passed check 7?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 7?": {
      "main": [
        [
          {
            "node": "Check 8: No question in the guide touches a protected topic",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 7)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 7)": {
      "main": [
        [
          {
            "node": "2. Draft one question for each area",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 7",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 7": {
      "main": [
        [
          {
            "node": "Check 8: No question in the guide touches a protected topic",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 8: No question in the guide touches a protected topic": {
      "main": [
        [
          {
            "node": "Passed check 8?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 8?": {
      "main": [
        [
          {
            "node": "Check 9: The guide asks every candidate the same questions in the same way",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 8)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 8)": {
      "main": [
        [
          {
            "node": "4. Write the interview guide",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 8",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 8": {
      "main": [
        [
          {
            "node": "Check 9: The guide asks every candidate the same questions in the same way",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 9: The guide asks every candidate the same questions in the same way": {
      "main": [
        [
          {
            "node": "Passed check 9?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 9?": {
      "main": [
        [
          {
            "node": "Check 10: Every flagged question was replaced",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 9)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 9)": {
      "main": [
        [
          {
            "node": "4. Write the interview guide",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 9",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 9": {
      "main": [
        [
          {
            "node": "Check 10: Every flagged question was replaced",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 10: Every flagged question was replaced": {
      "main": [
        [
          {
            "node": "Passed check 10?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 10?": {
      "main": [
        [
          {
            "node": "Result",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 10)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 10)": {
      "main": [
        [
          {
            "node": "4. Write the interview guide",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 10",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 10": {
      "main": [
        [
          {
            "node": "Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Chat model": {
      "ai_languageModel": [
        [
          {
            "node": "1. List what the job needs",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "2. Draft one question for each area",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "3. Screen every question for protected topics",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "4. Write the interview guide",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "Check 7: The number of questions matches what was asked for",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "Check 8: No question in the guide touches a protected topic",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "Check 9: The guide asks every candidate the same questions in the same way",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "Check 10: Every flagged question was replaced",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "meta": {
    "template": "draft-interview-questions"
  }
}
