{
  "name": "Write a job description",
  "nodes": [
    {
      "parameters": {
        "content": "## Write a job description\nTurn your notes on a role into a job description where every requirement ties to a duty, must-haves and nice-to-haves are kept apart, and wording that could put off qualified people is flagged and fixed.\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 Duty (quote) column appears in the notes on the role, with shortened fragments in the same order. If it fails: try once more, then flag.\n- Check 2 (a plain rule, run automatically with no AI): Each quote in the Requirement (quote) column appears in the what you want in a person, with shortened fragments in the same order. If it fails: try once more, then flag.\n- Check 3 (a plain rule, run automatically with no AI): Every number in the answer also appears somewhere in at least one of \"notes on the role\" and \"what you want in a person\"; this does not check what each number means. If it fails: try once more, then flag.\n- Check 4 (a plain rule, run automatically with no AI): Every row of the table has one of \"age\", \"gender\", \"family\", \"health or disability\", \"origin\", \"stricter than the duty needs\", \"other\" or \"none\" in the Concern column. If it fails: try once more, then flag.\n- Check 5 (a plain rule, run automatically with no AI): The answer contains the text fragment \"Check this draft with your HR or legal team before you post it.\" (including inside a longer word). If it fails: try once more, then flag.\n- Check 6 (a second AI prompt answers pass or fail): Look at every row marked must-have in the answer. Does its Duty # name a real duty in the list below, and does the Reason show that the duty cannot be done without the requirement? Answer FAIL and quote each must-have that is not tied, or whose reason is only that the job is better with it. If it fails: try once more, then flag.\n- Check 7 (a plain rule, run automatically with no AI): Every row of the table has one of \"must-have\", \"nice-to-have\" or \"not tied to a duty\" in the Level column. If it fails: try once more, then flag.\n- Check 8 (a second AI prompt answers pass or fail): Read the table below and list every requirement marked not tied to a duty. Does any of them appear in the final job description, in the same words or in other words? Answer FAIL and quote each place where one appears. If it fails: try once more, then flag.\n- Check 9 (a second AI prompt answers pass or fail): Compare the final job description with the table below. Is every must-have under \"What you need\" and every nice-to-have under \"Nice to have\", with none in the wrong list and none missing? Answer FAIL and quote each requirement that is in the wrong list or missing. 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 the Replacement is not keep, does the phrase in the Phrase column still appear in the final job description? Also read the final text for any other wording that points to age, gender, family, health, nationality or background. Answer FAIL and quote each place where one is left. If it fails: try once more, then flag.\n- Check 11 (you check it by hand): Read the job description as someone who has never worked here. Ask a person who does or knows the job whether each must-have is needed on day one. Check the pay, the hours and any statement about accommodations against your own policy and local rules, and have your HR or legal team look at it before it goes out. Only a person can say what the law and your own policy require. If it fails: flag it and carry on.\n\nFrom Logic Lab (logiclabhq.com). Free to use and adapt, with credit.",
        "height": 1080,
        "width": 760,
        "color": 5
      },
      "id": "8fedda5c-da5d-4a67-a9c9-e3166598b601",
      "name": "About this workflow",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -40,
        -1040
      ]
    },
    {
      "parameters": {},
      "id": "2ecf9708-ebd0-4d97-afeb-db1ac6772c89",
      "name": "Start",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        0,
        320
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "bc80e717-ec3d-49e6-a288-c72db3603c0c",
              "name": "role_notes",
              "value": "Sample: Role: Office and events coordinator, small community arts center, reports to the center director. Part time, 24 hours a week, mostly weekdays, some Saturday events. Pay 21 to 24 dollars an hour.\nDuties: 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; help the director with the volunteer schedule.",
              "type": "string"
            },
            {
              "id": "e6875701-f01f-49f4-afee-c77f0fe6dd22",
              "name": "requirements",
              "value": "Sample (it includes wording the template will flag): Young, energetic person with 5 years of office experience. Must be good with spreadsheets. A degree would be nice. Friendly phone manner. Must be able to lift 30 pounds for event set-up. Must have their own car. Good with social media would be a bonus. Native English speaker.",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "3e5da1a1-d75b-4160-ac6d-bbcfef553cbe",
      "name": "Inputs",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        220,
        320
      ],
      "notes": "Paste your own material here: role_notes = Notes on the role; requirements = What you want in a person.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "promptType": "define",
        "hasOutputParser": false,
        "text": "=You are an experienced HR generalist who writes job descriptions from rough notes.\n\nYour task is to list every duty in the notes below.\n\nThe notes:\n{{ $(\"Inputs\").first().json[\"role_notes\"] }}\n\nRules:\n- A duty is something the person in the role will do. Do not list the pay, the hours, the team or the place as duties.\n- Quote each duty from the notes word for word, as short as you can. Do not correct it and do not reword it.\n- One row for each separate duty, in the order the notes give them. Do not merge two duties and do not add one the notes do not have.\n- Do not add duties that jobs like this one usually have.\n\nReturn one markdown table and nothing else:\n| # | Duty (quote) |{{ $(\"Check 1: Every duty is quoted word for word from the notes\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Every duty is quoted word for word from the notes. What was wrong: \" + $(\"Check 1: Every duty is quoted word for word from the notes\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}"
      },
      "id": "0615d0b2-c112-43e7-a542-59ec311654b1",
      "name": "1. List the duties",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        480,
        320
      ],
      "notes": "Pick out each duty from the notes, quoted word for word, so the rest of the work has a fixed list to test against. Returns: A numbered table of the duties, each quoted from the notes.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// Every duty is quoted word for word from the notes\n// Rule: Each quote in the Duty (quote) column appears in the notes on the role, 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 the duties\").first().json.text ?? '');\nconst sources = {\n  \"role_notes\": String($(\"Inputs\").first().json[\"role_notes\"] ?? ''),\n};\nconst result = runRule(\"column_quotes_source\", {\"column\":\"Duty (quote)\",\"input\":\"role_notes\"}, work, sources);\nreturn [{ json: result }];"
      },
      "id": "e30c78c5-a458-4378-a9b4-1daf7bdd34da",
      "name": "Check 1: Every duty is quoted word for word from the notes",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        740,
        320
      ],
      "notes": "Each quote in the Duty (quote) column appears in the notes on the role, with shortened fragments in the same order.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "d792073b-0632-448a-a13b-57098c03b5c0",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "cf7267e8-cea8-425f-a6b1-e362f40005c1",
      "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": "63bf06e8-e792-43a5-ad85-0a26ccb17ecb",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "649572b0-4c61-4a55-a113-45eaa8464def",
      "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 the duties\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "83f354a9-d2ae-4fd0-a041-61bbab3db8ca",
              "name": "flag",
              "value": "=Every duty is quoted word for word from the notes: did not pass. {{ $(\"Check 1: Every duty is quoted word for word from the notes\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "d685633d-c58e-47c4-a359-db3fdc824216",
      "name": "Flag check 1",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1130,
        700
      ],
      "notes": "Notes the problem and carries on. The note shows in Result.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "promptType": "define",
        "hasOutputParser": false,
        "text": "=You are a careful HR reviewer who asks, for each requirement, \"Does the job need this?\"\n\nYour task is to test each requirement below against the list of duties.\n\nThe requirements:\n{{ $(\"Inputs\").first().json[\"requirements\"] }}\n\nThe duties:\n{{ $(\"1. List the duties\").first().json.text }}\n\nRules:\n- One row for each separate requirement, in the order they are written. Split a line that holds two requirements into two rows. Do not add a requirement that is not in the text.\n- Quote each requirement word for word, as short as you can.\n- In the Duty # column, give the number of the duty that needs it. If no duty needs it, write none.\n- Level must be exactly one of: must-have, nice-to-have, not tied to a duty. Use must-have only when a duty cannot be done without it, and the person could not learn it in the first weeks. Use nice-to-have when it helps with a duty but is not needed. Use not tied to a duty when the Duty # is none.\n- In the Reason column, write one short sentence that names the duty and says why it needs the requirement. For a row that is not tied to a duty, say what is missing.\n- Do not judge the person. Judge only whether the duties need the requirement.\n\nReturn one markdown table and nothing else:\n| # | Requirement (quote) | Duty # | Level | Reason |{{ $(\"Check 2: Every requirement is quoted word for word from your list\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Every requirement is quoted word for word from your list. What was wrong: \" + $(\"Check 2: Every requirement is quoted word for word from your list\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}{{ $(\"Check 6: Each must-have is tied to a duty\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Each must-have is tied to a duty. What was wrong: \" + $(\"Check 6: Each must-have is tied to a duty\").first().json.text + \"\\nFix that and give your full answer again.\" : \"\" }}{{ $(\"Check 7: Every level is one of the three allowed words\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Every level is one of the three allowed words. What was wrong: \" + $(\"Check 7: Every level is one of the three allowed words\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}"
      },
      "id": "98119552-3eb5-4c49-a270-6cf82ca37d4f",
      "name": "2. Tie each requirement to a duty",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        1260,
        320
      ],
      "notes": "Test every requirement against the duties and mark it must-have, nice-to-have or not tied to a duty. Returns: A table that ties each requirement to a duty, or marks it as not tied.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// Every requirement is quoted word for word from your list\n// Rule: Each quote in the Requirement (quote) column appears in the what you want in a person, with shortened fragments in the same order.\n// Runs with no AI. Returns pass (true or false) and a reason.\nconst runRule = function runRule(rule, value, work, sources) {\n    const norm = (s) => String(s ?? '')\n        .toLowerCase()\n        .replace(/[\\u2018\\u2019\\u201c\\u201d\"'`]/g, '')\n        .replace(/\\s+/g, ' ')\n        .trim();\n    const readTable = (text) => {\n        const lines = String(text ?? '')\n            .split(/\\r?\\n/)\n            .map((l) => l.trim());\n        const tables = [];\n        let cur = null;\n        const cells = (l) => l\n            .replace(/^\\|/, '')\n            .replace(/\\|$/, '')\n            .split('|')\n            .map((c) => c.trim());\n        for (let i = 0; i < lines.length; i++) {\n            const l = lines[i] ?? '';\n            if (!l.startsWith('|')) {\n                cur = null;\n                continue;\n            }\n            if (!cur) {\n                const next = lines[i + 1] ?? '';\n                if (/^\\|?\\s*:?-{2,}/.test(next)) {\n                    cur = { header: cells(l), rows: [] };\n                    tables.push(cur);\n                    i++;\n                }\n                continue;\n            }\n            cur.rows.push(cells(l));\n        }\n        return tables;\n    };\n    // Every table that has this column, with \"None found\" rows left out.\n    // Returns that column's cells from every such table, or null if no table has it.\n    const findColumn = (name) => {\n        let seen = false;\n        const cells = [];\n        for (const t of readTable(work)) {\n            const i = t.header.findIndex((h) => norm(h) === norm(name));\n            if (i < 0)\n                continue;\n            seen = true;\n            for (const r of t.rows) {\n                if (r.some((c) => norm(c) === 'none found'))\n                    continue;\n                cells.push(r[i] ?? '');\n            }\n        }\n        return seen ? cells : null;\n    };\n    const empty = (c) => !c || /^[-\\u2013\\u2014\\s]*$/.test(c);\n    switch (rule) {\n        case 'has_text':\n            return norm(work).includes(norm(value))\n                ? { pass: true, reason: 'Found.' }\n                : { pass: false, reason: `The answer does not include \"${value}\".` };\n        case 'lacks_text':\n            return norm(work).includes(norm(value))\n                ? { pass: false, reason: `The answer includes \"${value}\", which it should not.` }\n                : { pass: true, reason: 'Not found, as it should be.' };\n        case 'max_words': {\n            const n = String(work ?? '')\n                .split(/\\s+/)\n                .filter(Boolean).length;\n            return n <= Number(value)\n                ? { pass: true, reason: `${n} words.` }\n                : { pass: false, reason: `The answer has ${n} words; the limit is ${value}.` };\n        }\n        case 'table_cells_filled': {\n            const problems = [];\n            for (const col of value) {\n                const f = findColumn(col);\n                if (!f) {\n                    problems.push(`No table with a \"${col}\" column was found.`);\n                    continue;\n                }\n                if (f.length === 0) {\n                    problems.push(`The table has no data rows to check in \"${col}\".`);\n                    continue;\n                }\n                f.forEach((c, i) => {\n                    if (empty(c))\n                        problems.push(`Row ${i + 1} has nothing in \"${col}\".`);\n                });\n            }\n            return problems.length\n                ? { pass: false, reason: problems.join(' ') }\n                : { pass: true, reason: 'Every row is filled in.' };\n        }\n        case 'column_values_in': {\n            const f = findColumn(value.column);\n            if (!f)\n                return { pass: false, reason: `No table with a \"${value.column}\" column was found.` };\n            if (f.length === 0)\n                return { pass: false, reason: `The table has no data rows to check in \"${value.column}\".` };\n            const allowed = value.allowed.map(norm);\n            const bad = [];\n            f.forEach((c, i) => {\n                if (!allowed.includes(norm(c)))\n                    bad.push(`Row ${i + 1} says \"${c}\".`);\n            });\n            return bad.length\n                ? {\n                    pass: false,\n                    reason: `The \"${value.column}\" column must say ${value.allowed.join(' or ')}. ${bad.join(' ')}`,\n                }\n                : { pass: true, reason: 'Every row uses an allowed word.' };\n        }\n        case 'column_quotes_source': {\n            const f = findColumn(value.column);\n            if (!f)\n                return { pass: false, reason: `No table with a \"${value.column}\" column was found.` };\n            if (f.length === 0)\n                return { pass: false, reason: `The table has no data rows to check in \"${value.column}\".` };\n            const src = norm(sources[value.input] ?? '');\n            const bad = [];\n            f.forEach((c, i) => {\n                // A quote may be shortened with \"...\"; each piece must be in the source.\n                const pieces = c\n                    .split(/\\.\\.\\.|\\u2026/)\n                    .map(norm)\n                    .filter(Boolean);\n                let cursor = 0;\n                const ordered = pieces.every((p) => {\n                    const found = src.indexOf(p, cursor);\n                    if (found < 0)\n                        return false;\n                    cursor = found + p.length;\n                    return true;\n                });\n                if (!pieces.length || !ordered)\n                    bad.push(`Row ${i + 1}: \"${c}\" is not in the source in that order.`);\n            });\n            return bad.length\n                ? { pass: false, reason: bad.join(' ') }\n                : { pass: true, reason: 'Every quote was found in the source.' };\n        }\n        case 'numbers_in_source': {\n            const nums = (s) => (String(s ?? '').match(/\\d[\\d,]*(?:\\.\\d+)?/g) ?? []).map((n) => n.replace(/,/g, ''));\n            const ids = Array.isArray(value.inputs) ? value.inputs : [value.input];\n            const src = new Set(ids.flatMap((id) => nums(sources[id] ?? '')));\n            // With a column named, only the numbers in that column of a table are compared (a rank column is not a claim).\n            let target = work;\n            if (value.column) {\n                const cells = findColumn(String(value.column));\n                if (cells === null)\n                    return { pass: false, reason: `The answer has no table with a ${value.column} column.` };\n                target = cells.join(' ');\n            }\n            const missing = [...new Set(nums(target))].filter((n) => !src.has(n));\n            return missing.length\n                ? {\n                    pass: false,\n                    reason: `These numbers are not in the source: ${missing.join(', ')}.`,\n                }\n                : { pass: true, reason: 'Every number was found in the source.' };\n        }\n        default:\n            return { pass: false, reason: `Unknown rule \"${rule}\".` };\n    }\n};\n\nconst work = String($(\"2. Tie each requirement to a duty\").first().json.text ?? '');\nconst sources = {\n  \"requirements\": String($(\"Inputs\").first().json[\"requirements\"] ?? ''),\n};\nconst result = runRule(\"column_quotes_source\", {\"column\":\"Requirement (quote)\",\"input\":\"requirements\"}, work, sources);\nreturn [{ json: result }];"
      },
      "id": "f9afc80a-e9ce-43e5-af27-56f8afc834e3",
      "name": "Check 2: Every requirement is quoted word for word from your list",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1520,
        320
      ],
      "notes": "Each quote in the Requirement (quote) column appears in the what you want in a person, with shortened fragments in the same order.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "d69205a8-0732-461d-a03b-55768d03b753",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "cf74a67f-cea5-43c8-a6aa-5e1d74078b06",
      "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": "64bf087b-e692-4212-ae85-0bb9cbb17d38",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "448e848b-245e-4cc6-a915-aa49284dd334",
      "name": "Try again? (check 2)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1780,
        520
      ],
      "notes": "True = first failure: send back to \"2. Tie each requirement to a duty\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "82f35316-d3ae-4163-af41-6028ac3dba5d",
              "name": "flag",
              "value": "=Every requirement is quoted word for word from your list: did not pass. {{ $(\"Check 2: Every requirement is quoted word for word from your list\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "d3855e84-c88e-4c7d-a459-dcd2db824083",
      "name": "Flag check 2",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1910,
        700
      ],
      "notes": "Notes the problem and carries on. The note shows in Result.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "promptType": "define",
        "hasOutputParser": false,
        "text": "=You are an experienced HR writer who produces plain, welcoming job descriptions.\n\nYour task is to write a job description from the notes, the duties and the tied requirements below.\n\nThe notes on the role:\n{{ $(\"Inputs\").first().json[\"role_notes\"] }}\n\nThe duties:\n{{ $(\"1. List the duties\").first().json.text }}\n\nThe requirements and their levels:\n{{ $(\"2. Tie each requirement to a duty\").first().json.text }}\n\nRules:\n- Use these headings in this order: About the role, What you will do, What you need, Nice to have, Pay and hours.\n- Under What you will do, write the duties in plain words, one bullet each. Do not add a duty.\n- Under What you need, list only the requirements marked must-have. Under Nice to have, list only those marked nice-to-have. Leave out every requirement marked not tied to a duty.\n- Under Pay and hours, use only the figures in the notes. If the notes give no pay, write: [Pay range to be added before posting]. Do not invent a figure.\n- Write about the work, not about the kind of person. Use \"you\" and active verbs. Do not describe age, gender, family, health, nationality or background, and do not use words that suggest them, such as \"young\", \"energetic\", \"mature\", \"native speaker\" or \"digital native\".\n- Word a physical must-have as the task, not the body, such as \"lift and carry items of up to 30 pounds during event set-up\", and add \"with or without a reasonable accommodation\".\n- Do not add a company claim, a benefit or a number that the notes do not give.\n- End with exactly this line: Check this draft with your HR or legal team before you post it.\n\nReturn the job description in markdown and nothing else.{{ $(\"Check 3: No number appears that your notes and list do not have\").isExecuted ? \"\\n\\nYour last answer did not pass this check: No number appears that your notes and list do not have. What was wrong: \" + $(\"Check 3: No number appears that your notes and list do not have\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}"
      },
      "id": "4c634448-c697-4e69-a3af-fe1e6c048f17",
      "name": "3. Draft the job description",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        2040,
        320
      ],
      "notes": "Write the posting from the duties and the tied requirements only, with must-haves and nice-to-haves in separate lists. Returns: A job description with duties, must-haves, nice-to-haves and pay, ending with a reminder to check it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// No number appears that your notes and list do not have\n// Rule: Every number in the answer also appears somewhere in at least one of \"notes on the role\" and \"what you want in a person\"; this does not check what each number means.\n// Runs with no AI. Returns pass (true or false) and a reason.\nconst runRule = function runRule(rule, value, work, sources) {\n    const norm = (s) => String(s ?? '')\n        .toLowerCase()\n        .replace(/[\\u2018\\u2019\\u201c\\u201d\"'`]/g, '')\n        .replace(/\\s+/g, ' ')\n        .trim();\n    const readTable = (text) => {\n        const lines = String(text ?? '')\n            .split(/\\r?\\n/)\n            .map((l) => l.trim());\n        const tables = [];\n        let cur = null;\n        const cells = (l) => l\n            .replace(/^\\|/, '')\n            .replace(/\\|$/, '')\n            .split('|')\n            .map((c) => c.trim());\n        for (let i = 0; i < lines.length; i++) {\n            const l = lines[i] ?? '';\n            if (!l.startsWith('|')) {\n                cur = null;\n                continue;\n            }\n            if (!cur) {\n                const next = lines[i + 1] ?? '';\n                if (/^\\|?\\s*:?-{2,}/.test(next)) {\n                    cur = { header: cells(l), rows: [] };\n                    tables.push(cur);\n                    i++;\n                }\n                continue;\n            }\n            cur.rows.push(cells(l));\n        }\n        return tables;\n    };\n    // Every table that has this column, with \"None found\" rows left out.\n    // Returns that column's cells from every such table, or null if no table has it.\n    const findColumn = (name) => {\n        let seen = false;\n        const cells = [];\n        for (const t of readTable(work)) {\n            const i = t.header.findIndex((h) => norm(h) === norm(name));\n            if (i < 0)\n                continue;\n            seen = true;\n            for (const r of t.rows) {\n                if (r.some((c) => norm(c) === 'none found'))\n                    continue;\n                cells.push(r[i] ?? '');\n            }\n        }\n        return seen ? cells : null;\n    };\n    const empty = (c) => !c || /^[-\\u2013\\u2014\\s]*$/.test(c);\n    switch (rule) {\n        case 'has_text':\n            return norm(work).includes(norm(value))\n                ? { pass: true, reason: 'Found.' }\n                : { pass: false, reason: `The answer does not include \"${value}\".` };\n        case 'lacks_text':\n            return norm(work).includes(norm(value))\n                ? { pass: false, reason: `The answer includes \"${value}\", which it should not.` }\n                : { pass: true, reason: 'Not found, as it should be.' };\n        case 'max_words': {\n            const n = String(work ?? '')\n                .split(/\\s+/)\n                .filter(Boolean).length;\n            return n <= Number(value)\n                ? { pass: true, reason: `${n} words.` }\n                : { pass: false, reason: `The answer has ${n} words; the limit is ${value}.` };\n        }\n        case 'table_cells_filled': {\n            const problems = [];\n            for (const col of value) {\n                const f = findColumn(col);\n                if (!f) {\n                    problems.push(`No table with a \"${col}\" column was found.`);\n                    continue;\n                }\n                if (f.length === 0) {\n                    problems.push(`The table has no data rows to check in \"${col}\".`);\n                    continue;\n                }\n                f.forEach((c, i) => {\n                    if (empty(c))\n                        problems.push(`Row ${i + 1} has nothing in \"${col}\".`);\n                });\n            }\n            return problems.length\n                ? { pass: false, reason: problems.join(' ') }\n                : { pass: true, reason: 'Every row is filled in.' };\n        }\n        case 'column_values_in': {\n            const f = findColumn(value.column);\n            if (!f)\n                return { pass: false, reason: `No table with a \"${value.column}\" column was found.` };\n            if (f.length === 0)\n                return { pass: false, reason: `The table has no data rows to check in \"${value.column}\".` };\n            const allowed = value.allowed.map(norm);\n            const bad = [];\n            f.forEach((c, i) => {\n                if (!allowed.includes(norm(c)))\n                    bad.push(`Row ${i + 1} says \"${c}\".`);\n            });\n            return bad.length\n                ? {\n                    pass: false,\n                    reason: `The \"${value.column}\" column must say ${value.allowed.join(' or ')}. ${bad.join(' ')}`,\n                }\n                : { pass: true, reason: 'Every row uses an allowed word.' };\n        }\n        case 'column_quotes_source': {\n            const f = findColumn(value.column);\n            if (!f)\n                return { pass: false, reason: `No table with a \"${value.column}\" column was found.` };\n            if (f.length === 0)\n                return { pass: false, reason: `The table has no data rows to check in \"${value.column}\".` };\n            const src = norm(sources[value.input] ?? '');\n            const bad = [];\n            f.forEach((c, i) => {\n                // A quote may be shortened with \"...\"; each piece must be in the source.\n                const pieces = c\n                    .split(/\\.\\.\\.|\\u2026/)\n                    .map(norm)\n                    .filter(Boolean);\n                let cursor = 0;\n                const ordered = pieces.every((p) => {\n                    const found = src.indexOf(p, cursor);\n                    if (found < 0)\n                        return false;\n                    cursor = found + p.length;\n                    return true;\n                });\n                if (!pieces.length || !ordered)\n                    bad.push(`Row ${i + 1}: \"${c}\" is not in the source in that order.`);\n            });\n            return bad.length\n                ? { pass: false, reason: bad.join(' ') }\n                : { pass: true, reason: 'Every quote was found in the source.' };\n        }\n        case 'numbers_in_source': {\n            const nums = (s) => (String(s ?? '').match(/\\d[\\d,]*(?:\\.\\d+)?/g) ?? []).map((n) => n.replace(/,/g, ''));\n            const ids = Array.isArray(value.inputs) ? value.inputs : [value.input];\n            const src = new Set(ids.flatMap((id) => nums(sources[id] ?? '')));\n            // With a column named, only the numbers in that column of a table are compared (a rank column is not a claim).\n            let target = work;\n            if (value.column) {\n                const cells = findColumn(String(value.column));\n                if (cells === null)\n                    return { pass: false, reason: `The answer has no table with a ${value.column} column.` };\n                target = cells.join(' ');\n            }\n            const missing = [...new Set(nums(target))].filter((n) => !src.has(n));\n            return missing.length\n                ? {\n                    pass: false,\n                    reason: `These numbers are not in the source: ${missing.join(', ')}.`,\n                }\n                : { pass: true, reason: 'Every number was found in the source.' };\n        }\n        default:\n            return { pass: false, reason: `Unknown rule \"${rule}\".` };\n    }\n};\n\nconst work = String($(\"3. Draft the job description\").first().json.text ?? '');\nconst sources = {\n\n};\nconst result = runRule(\"numbers_in_source\", {\"inputs\":[\"role_notes\",\"requirements\"]}, work, sources);\nreturn [{ json: result }];"
      },
      "id": "a2dcfabf-de15-4260-a097-47d5c62e7ad6",
      "name": "Check 3: No number appears that your notes and list do not have",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2300,
        320
      ],
      "notes": "Every number in the answer also appears somewhere in at least one of \"notes on the role\" and \"what you want in a person\"; this does not check what each number means.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "d9920a61-0432-4164-af3b-53e38e03b8e6",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "cf76e516-cea3-4531-a6ad-663474054c6f",
      "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": "65bf0a0e-e592-407f-ab85-0700ceb181f1",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "6890fbce-c05b-40c3-ad17-bccc044b5bf1",
      "name": "Try again? (check 3)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2560,
        520
      ],
      "notes": "True = first failure: send back to \"3. Draft the job description\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "81f35183-d4ae-42f6-a241-64e1a93db5a4",
              "name": "flag",
              "value": "=No number appears that your notes and list do not have: did not pass. {{ $(\"Check 3: No number appears that your notes and list do not have\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "d4856017-c78e-4aea-a559-de65da823ef0",
      "name": "Flag check 3",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        2690,
        700
      ],
      "notes": "Notes the problem and carries on. The note shows in Result.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "promptType": "define",
        "hasOutputParser": false,
        "text": "=You are a fair-hiring reviewer who reads job postings for wording that shuts people out without meaning to.\n\nYour task is to find every phrase in the draft below that could exclude or discourage qualified people.\n\nThe draft:\n{{ $(\"3. Draft the job description\").first().json.text }}\n\nRules:\n- Look for wording that points to age, gender, family status, disability or health, nationality or origin, or a background the duties do not need. Examples are \"recent graduate\", \"he\", \"salesman\", \"must be able to stand all day\" when the duty does not need it, \"native speaker\" and \"culture fit\".\n- Also look for a requirement worded more strictly than the duty needs, such as a number of years, a degree or a car, when a skill or a result would say the same thing.\n- Quote each phrase from the draft word for word, as short as you can. One row for each phrase. If you find none, write one row with none in the Phrase column.\n- Concern must be exactly one of: age, gender, family, health or disability, origin, stricter than the duty needs, other, none.\n- In the Replacement column, give plainer wording that keeps the real need. Do not leave it empty. Write keep for a row where the phrase is a true need of a duty, and say why in the same cell.\n- Do not judge the people who wrote the notes.\n\nReturn one markdown table and nothing else:\n| # | Phrase (quote) | Concern | Replacement |{{ $(\"Check 4: Each concern is one of the eight allowed words\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Each concern is one of the eight allowed words. What was wrong: \" + $(\"Check 4: Each concern is one of the eight allowed words\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}"
      },
      "id": "69254518-71c6-405b-ac35-5f8270f0a70d",
      "name": "4. Find wording that could put people off",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        2820,
        320
      ],
      "notes": "List each phrase in the draft that could exclude or discourage qualified people, with the kind of concern and a plainer replacement. Returns: A table of flagged phrases, each with a concern and a replacement.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// Each concern is one of the eight allowed words\n// Rule: Every row of the table has one of \"age\", \"gender\", \"family\", \"health or disability\", \"origin\", \"stricter than the duty needs\", \"other\" or \"none\" in the Concern column.\n// Runs with no AI. Returns pass (true or false) and a reason.\nconst runRule = function runRule(rule, value, work, sources) {\n    const norm = (s) => String(s ?? '')\n        .toLowerCase()\n        .replace(/[\\u2018\\u2019\\u201c\\u201d\"'`]/g, '')\n        .replace(/\\s+/g, ' ')\n        .trim();\n    const readTable = (text) => {\n        const lines = String(text ?? '')\n            .split(/\\r?\\n/)\n            .map((l) => l.trim());\n        const tables = [];\n        let cur = null;\n        const cells = (l) => l\n            .replace(/^\\|/, '')\n            .replace(/\\|$/, '')\n            .split('|')\n            .map((c) => c.trim());\n        for (let i = 0; i < lines.length; i++) {\n            const l = lines[i] ?? '';\n            if (!l.startsWith('|')) {\n                cur = null;\n                continue;\n            }\n            if (!cur) {\n                const next = lines[i + 1] ?? '';\n                if (/^\\|?\\s*:?-{2,}/.test(next)) {\n                    cur = { header: cells(l), rows: [] };\n                    tables.push(cur);\n                    i++;\n                }\n                continue;\n            }\n            cur.rows.push(cells(l));\n        }\n        return tables;\n    };\n    // Every table that has this column, with \"None found\" rows left out.\n    // Returns that column's cells from every such table, or null if no table has it.\n    const findColumn = (name) => {\n        let seen = false;\n        const cells = [];\n        for (const t of readTable(work)) {\n            const i = t.header.findIndex((h) => norm(h) === norm(name));\n            if (i < 0)\n                continue;\n            seen = true;\n            for (const r of t.rows) {\n                if (r.some((c) => norm(c) === 'none found'))\n                    continue;\n                cells.push(r[i] ?? '');\n            }\n        }\n        return seen ? cells : null;\n    };\n    const empty = (c) => !c || /^[-\\u2013\\u2014\\s]*$/.test(c);\n    switch (rule) {\n        case 'has_text':\n            return norm(work).includes(norm(value))\n                ? { pass: true, reason: 'Found.' }\n                : { pass: false, reason: `The answer does not include \"${value}\".` };\n        case 'lacks_text':\n            return norm(work).includes(norm(value))\n                ? { pass: false, reason: `The answer includes \"${value}\", which it should not.` }\n                : { pass: true, reason: 'Not found, as it should be.' };\n        case 'max_words': {\n            const n = String(work ?? '')\n                .split(/\\s+/)\n                .filter(Boolean).length;\n            return n <= Number(value)\n                ? { pass: true, reason: `${n} words.` }\n                : { pass: false, reason: `The answer has ${n} words; the limit is ${value}.` };\n        }\n        case 'table_cells_filled': {\n            const problems = [];\n            for (const col of value) {\n                const f = findColumn(col);\n                if (!f) {\n                    problems.push(`No table with a \"${col}\" column was found.`);\n                    continue;\n                }\n                if (f.length === 0) {\n                    problems.push(`The table has no data rows to check in \"${col}\".`);\n                    continue;\n                }\n                f.forEach((c, i) => {\n                    if (empty(c))\n                        problems.push(`Row ${i + 1} has nothing in \"${col}\".`);\n                });\n            }\n            return problems.length\n                ? { pass: false, reason: problems.join(' ') }\n                : { pass: true, reason: 'Every row is filled in.' };\n        }\n        case 'column_values_in': {\n            const f = findColumn(value.column);\n            if (!f)\n                return { pass: false, reason: `No table with a \"${value.column}\" column was found.` };\n            if (f.length === 0)\n                return { pass: false, reason: `The table has no data rows to check in \"${value.column}\".` };\n            const allowed = value.allowed.map(norm);\n            const bad = [];\n            f.forEach((c, i) => {\n                if (!allowed.includes(norm(c)))\n                    bad.push(`Row ${i + 1} says \"${c}\".`);\n            });\n            return bad.length\n                ? {\n                    pass: false,\n                    reason: `The \"${value.column}\" column must say ${value.allowed.join(' or ')}. ${bad.join(' ')}`,\n                }\n                : { pass: true, reason: 'Every row uses an allowed word.' };\n        }\n        case 'column_quotes_source': {\n            const f = findColumn(value.column);\n            if (!f)\n                return { pass: false, reason: `No table with a \"${value.column}\" column was found.` };\n            if (f.length === 0)\n                return { pass: false, reason: `The table has no data rows to check in \"${value.column}\".` };\n            const src = norm(sources[value.input] ?? '');\n            const bad = [];\n            f.forEach((c, i) => {\n                // A quote may be shortened with \"...\"; each piece must be in the source.\n                const pieces = c\n                    .split(/\\.\\.\\.|\\u2026/)\n                    .map(norm)\n                    .filter(Boolean);\n                let cursor = 0;\n                const ordered = pieces.every((p) => {\n                    const found = src.indexOf(p, cursor);\n                    if (found < 0)\n                        return false;\n                    cursor = found + p.length;\n                    return true;\n                });\n                if (!pieces.length || !ordered)\n                    bad.push(`Row ${i + 1}: \"${c}\" is not in the source in that order.`);\n            });\n            return bad.length\n                ? { pass: false, reason: bad.join(' ') }\n                : { pass: true, reason: 'Every quote was found in the source.' };\n        }\n        case 'numbers_in_source': {\n            const nums = (s) => (String(s ?? '').match(/\\d[\\d,]*(?:\\.\\d+)?/g) ?? []).map((n) => n.replace(/,/g, ''));\n            const ids = Array.isArray(value.inputs) ? value.inputs : [value.input];\n            const src = new Set(ids.flatMap((id) => nums(sources[id] ?? '')));\n            // With a column named, only the numbers in that column of a table are compared (a rank column is not a claim).\n            let target = work;\n            if (value.column) {\n                const cells = findColumn(String(value.column));\n                if (cells === null)\n                    return { pass: false, reason: `The answer has no table with a ${value.column} column.` };\n                target = cells.join(' ');\n            }\n            const missing = [...new Set(nums(target))].filter((n) => !src.has(n));\n            return missing.length\n                ? {\n                    pass: false,\n                    reason: `These numbers are not in the source: ${missing.join(', ')}.`,\n                }\n                : { pass: true, reason: 'Every number was found in the source.' };\n        }\n        default:\n            return { pass: false, reason: `Unknown rule \"${rule}\".` };\n    }\n};\n\nconst work = String($(\"4. Find wording that could put people off\").first().json.text ?? '');\nconst sources = {\n\n};\nconst result = runRule(\"column_values_in\", {\"column\":\"Concern\",\"allowed\":[\"age\",\"gender\",\"family\",\"health or disability\",\"origin\",\"stricter than the duty needs\",\"other\",\"none\"]}, work, sources);\nreturn [{ json: result }];"
      },
      "id": "fa75e687-0d58-4224-af6b-74cd05fe0af2",
      "name": "Check 4: Each concern is one of the eight allowed words",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3080,
        320
      ],
      "notes": "Every row of the table has one of \"age\", \"gender\", \"family\", \"health or disability\", \"origin\", \"stricter than the duty needs\", \"other\" or \"none\" in the Concern column.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "d89208ce-0532-42f7-ae3b-52508f03ba79",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "cf659bf5-cea1-469a-a6b9-68a773f8807c",
      "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": "66bf0ba1-e492-4eec-ac85-0893cdb1805e",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "e89cfe41-c86d-467c-a506-6713043e8ffe",
      "name": "Try again? (check 4)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        3340,
        520
      ],
      "notes": "True = first failure: send back to \"4. Find wording that could put people off\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "80f34ff0-d5ae-4489-a141-634eaa3db737",
              "name": "flag",
              "value": "=Each concern is one of the eight allowed words: did not pass. {{ $(\"Check 4: Each concern is one of the eight allowed words\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "d1855b5e-c28e-430b-ae59-d360e18249f5",
      "name": "Flag check 4",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        3470,
        700
      ],
      "notes": "Notes the problem and carries on. The note shows in Result.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "promptType": "define",
        "hasOutputParser": false,
        "text": "=You are a copy editor who swaps listed phrases and changes nothing else.\n\nYour task is to write the final job description from the draft, using the replacements in the table.\n\nThe draft:\n{{ $(\"3. Draft the job description\").first().json.text }}\n\nThe flagged phrases and their replacements:\n{{ $(\"4. Find wording that could put people off\").first().json.text }}\n\nRules:\n- Replace each flagged phrase with its replacement. For a row marked keep, leave the phrase as it is.\n- Change nothing else. Keep every other word, number and heading as written. Do not add a duty, a requirement or a figure.\n- After the job description, add a heading \"For the hiring team\" with two to four bullets. Each bullet is a question the hiring team must still answer, such as whether a must-have is truly needed on day one, whether a stated requirement could be met in another way, or whether the pay range is confirmed, since many states and cities require one in the posting.\n- One bullet must ask which duties are essential to the role and which are occasional or could be shared.\n- Start the answer with one line that begins \"Changes:\" and says how many phrases were replaced and how many were kept.\n- Keep the closing line \"Check this draft with your HR or legal team before you post it.\" as the very last line.\n\nReturn markdown in this form and nothing else:\nChanges: (the counts)\n(the job description)\n## For the hiring team\n- (a question)\nCheck this draft with your HR or legal team before you post it.{{ $(\"Check 5: The reminder to check it 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 it with your HR or legal team is in the final text. What was wrong: \" + $(\"Check 5: The reminder to check it 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 requirement that is not tied to a duty appears in the final text\").isExecuted ? \"\\n\\nYour last answer did not pass this check: No requirement that is not tied to a duty appears in the final text. What was wrong: \" + $(\"Check 8: No requirement that is not tied to a duty appears in the final text\").first().json.text + \"\\nFix that and give your full answer again.\" : \"\" }}{{ $(\"Check 9: Must-haves and nice-to-haves are in separate lists\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Must-haves and nice-to-haves are in separate lists. What was wrong: \" + $(\"Check 9: Must-haves and nice-to-haves are in separate lists\").first().json.text + \"\\nFix that and give your full answer again.\" : \"\" }}{{ $(\"Check 10: No flagged wording is left in the final text\").isExecuted ? \"\\n\\nYour last answer did not pass this check: No flagged wording is left in the final text. What was wrong: \" + $(\"Check 10: No flagged wording is left in the final text\").first().json.text + \"\\nFix that and give your full answer again.\" : \"\" }}"
      },
      "id": "8400d7be-5336-473b-a410-2138dc6f56cd",
      "name": "5. Write the final job description",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        3600,
        320
      ],
      "notes": "Apply the replacements to the draft, change nothing else, and add a short list of what the hiring team still needs to decide. Returns: The final job description, a count of the wording changes, and questions for the hiring team.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// The reminder to check it with your HR or legal team is in the final text\n// Rule: The answer contains the text fragment \"Check this draft with your HR or legal team before you post it.\" (including inside a longer word).\n// Runs with no AI. Returns pass (true or false) and a reason.\nconst runRule = function runRule(rule, value, work, sources) {\n    const norm = (s) => String(s ?? '')\n        .toLowerCase()\n        .replace(/[\\u2018\\u2019\\u201c\\u201d\"'`]/g, '')\n        .replace(/\\s+/g, ' ')\n        .trim();\n    const readTable = (text) => {\n        const lines = String(text ?? '')\n            .split(/\\r?\\n/)\n            .map((l) => l.trim());\n        const tables = [];\n        let cur = null;\n        const cells = (l) => l\n            .replace(/^\\|/, '')\n            .replace(/\\|$/, '')\n            .split('|')\n            .map((c) => c.trim());\n        for (let i = 0; i < lines.length; i++) {\n            const l = lines[i] ?? '';\n            if (!l.startsWith('|')) {\n                cur = null;\n                continue;\n            }\n            if (!cur) {\n                const next = lines[i + 1] ?? '';\n                if (/^\\|?\\s*:?-{2,}/.test(next)) {\n                    cur = { header: cells(l), rows: [] };\n                    tables.push(cur);\n                    i++;\n                }\n                continue;\n            }\n            cur.rows.push(cells(l));\n        }\n        return tables;\n    };\n    // Every table that has this column, with \"None found\" rows left out.\n    // Returns that column's cells from every such table, or null if no table has it.\n    const findColumn = (name) => {\n        let seen = false;\n        const cells = [];\n        for (const t of readTable(work)) {\n            const i = t.header.findIndex((h) => norm(h) === norm(name));\n            if (i < 0)\n                continue;\n            seen = true;\n            for (const r of t.rows) {\n                if (r.some((c) => norm(c) === 'none found'))\n                    continue;\n                cells.push(r[i] ?? '');\n            }\n        }\n        return seen ? cells : null;\n    };\n    const empty = (c) => !c || /^[-\\u2013\\u2014\\s]*$/.test(c);\n    switch (rule) {\n        case 'has_text':\n            return norm(work).includes(norm(value))\n                ? { pass: true, reason: 'Found.' }\n                : { pass: false, reason: `The answer does not include \"${value}\".` };\n        case 'lacks_text':\n            return norm(work).includes(norm(value))\n                ? { pass: false, reason: `The answer includes \"${value}\", which it should not.` }\n                : { pass: true, reason: 'Not found, as it should be.' };\n        case 'max_words': {\n            const n = String(work ?? '')\n                .split(/\\s+/)\n                .filter(Boolean).length;\n            return n <= Number(value)\n                ? { pass: true, reason: `${n} words.` }\n                : { pass: false, reason: `The answer has ${n} words; the limit is ${value}.` };\n        }\n        case 'table_cells_filled': {\n            const problems = [];\n            for (const col of value) {\n                const f = findColumn(col);\n                if (!f) {\n                    problems.push(`No table with a \"${col}\" column was found.`);\n                    continue;\n                }\n                if (f.length === 0) {\n                    problems.push(`The table has no data rows to check in \"${col}\".`);\n                    continue;\n                }\n                f.forEach((c, i) => {\n                    if (empty(c))\n                        problems.push(`Row ${i + 1} has nothing in \"${col}\".`);\n                });\n            }\n            return problems.length\n                ? { pass: false, reason: problems.join(' ') }\n                : { pass: true, reason: 'Every row is filled in.' };\n        }\n        case 'column_values_in': {\n            const f = findColumn(value.column);\n            if (!f)\n                return { pass: false, reason: `No table with a \"${value.column}\" column was found.` };\n            if (f.length === 0)\n                return { pass: false, reason: `The table has no data rows to check in \"${value.column}\".` };\n            const allowed = value.allowed.map(norm);\n            const bad = [];\n            f.forEach((c, i) => {\n                if (!allowed.includes(norm(c)))\n                    bad.push(`Row ${i + 1} says \"${c}\".`);\n            });\n            return bad.length\n                ? {\n                    pass: false,\n                    reason: `The \"${value.column}\" column must say ${value.allowed.join(' or ')}. ${bad.join(' ')}`,\n                }\n                : { pass: true, reason: 'Every row uses an allowed word.' };\n        }\n        case 'column_quotes_source': {\n            const f = findColumn(value.column);\n            if (!f)\n                return { pass: false, reason: `No table with a \"${value.column}\" column was found.` };\n            if (f.length === 0)\n                return { pass: false, reason: `The table has no data rows to check in \"${value.column}\".` };\n            const src = norm(sources[value.input] ?? '');\n            const bad = [];\n            f.forEach((c, i) => {\n                // A quote may be shortened with \"...\"; each piece must be in the source.\n                const pieces = c\n                    .split(/\\.\\.\\.|\\u2026/)\n                    .map(norm)\n                    .filter(Boolean);\n                let cursor = 0;\n                const ordered = pieces.every((p) => {\n                    const found = src.indexOf(p, cursor);\n                    if (found < 0)\n                        return false;\n                    cursor = found + p.length;\n                    return true;\n                });\n                if (!pieces.length || !ordered)\n                    bad.push(`Row ${i + 1}: \"${c}\" is not in the source in that order.`);\n            });\n            return bad.length\n                ? { pass: false, reason: bad.join(' ') }\n                : { pass: true, reason: 'Every quote was found in the source.' };\n        }\n        case 'numbers_in_source': {\n            const nums = (s) => (String(s ?? '').match(/\\d[\\d,]*(?:\\.\\d+)?/g) ?? []).map((n) => n.replace(/,/g, ''));\n            const ids = Array.isArray(value.inputs) ? value.inputs : [value.input];\n            const src = new Set(ids.flatMap((id) => nums(sources[id] ?? '')));\n            // With a column named, only the numbers in that column of a table are compared (a rank column is not a claim).\n            let target = work;\n            if (value.column) {\n                const cells = findColumn(String(value.column));\n                if (cells === null)\n                    return { pass: false, reason: `The answer has no table with a ${value.column} column.` };\n                target = cells.join(' ');\n            }\n            const missing = [...new Set(nums(target))].filter((n) => !src.has(n));\n            return missing.length\n                ? {\n                    pass: false,\n                    reason: `These numbers are not in the source: ${missing.join(', ')}.`,\n                }\n                : { pass: true, reason: 'Every number was found in the source.' };\n        }\n        default:\n            return { pass: false, reason: `Unknown rule \"${rule}\".` };\n    }\n};\n\nconst work = String($(\"5. Write the final job description\").first().json.text ?? '');\nconst sources = {\n\n};\nconst result = runRule(\"has_text\", \"Check this draft with your HR or legal team before you post it.\", work, sources);\nreturn [{ json: result }];"
      },
      "id": "1d85bb99-82fe-48d2-a4dc-01539bb12b34",
      "name": "Check 5: The reminder to check it with your HR or legal team is in the final text",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3860,
        320
      ],
      "notes": "The answer contains the text fragment \"Check this draft with your HR or legal team before you post it.\" (including inside a longer word).",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "db920d87-0232-4e3e-a53b-5d559003bc0c",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "cf67da8c-4e9e-4e83-a6bb-a73e73f641e5",
      "name": "Passed check 5?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        4120,
        320
      ],
      "notes": "True = passed. False = did not pass.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "67bf0d34-e392-4d59-a185-1072d0b18517",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "4c9fda44-a46a-4f39-a908-de56203c7d7b",
      "name": "Try again? (check 5)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        4120,
        520
      ],
      "notes": "True = first failure: send back to \"5. Write the final job description\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "87f35af5-d6ae-461c-a441-6807a73db27e",
              "name": "flag",
              "value": "=The reminder to check it with your HR or legal team is in the final text: did not pass. {{ $(\"Check 5: The reminder to check it with your HR or legal team is in the final text\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "d2855cf1-c18e-4178-af59-d4f3e0824862",
      "name": "Flag check 5",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        4250,
        700
      ],
      "notes": "Notes the problem and carries on. The note shows in Result.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "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: Each must-have is tied to a duty\nLook at every row marked must-have in the answer. Does its Duty # name a real duty in the list below, and does the Reason show that the duty cannot be done without the requirement? Answer FAIL and quote each must-have that is not tied, or whose reason is only that the job is better with it.\n\nThe duties:\n{{ $(\"1. List the duties\").first().json.text }}\n\nThe work to check (the answer from Step 2 (Tie each requirement to a duty)):\n{{ $(\"2. Tie each requirement to a duty\").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": "df20a877-0b84-4b54-adb3-5609d8a3eb3e",
      "name": "Check 6: Each must-have is tied to a duty",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        4380,
        320
      ],
      "notes": "Answers PASS or FAIL. Look at every row marked must-have in the answer. Does its Duty # name a real duty in the list below, and does the Reason show that the duty cannot be done without the requirement? Answer FAIL and quote each must-have that is not tied, or whose reason is only that the job is better with it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "da920bf4-0332-4fd1-a43b-5bc29103bd9f",
              "leftValue": "={{ String($json.text ?? '').trim().toUpperCase().startsWith('PASS') }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "4f6ae2a3-4e9c-4fec-a6b4-21f9f3fdc72a",
      "name": "Passed check 6?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        4640,
        320
      ],
      "notes": "True = passed. False = did not pass.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "68bf0ec7-e292-4bc6-a285-1205cfb18384",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "cc9854ff-cc68-4f9a-a10b-c0a58043d060",
      "name": "Try again? (check 6)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        4640,
        520
      ],
      "notes": "True = first failure: send back to \"2. Tie each requirement to a duty\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "86f35962-d7ae-47af-a341-6674a83db411",
              "name": "flag",
              "value": "=Each must-have is tied to a duty: did not pass. {{ $(\"Check 6: Each must-have is tied to a duty\").first().json.text }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "cf855838-c48e-4631-a059-d686df8246cf",
      "name": "Flag check 6",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        4770,
        700
      ],
      "notes": "Notes the problem and carries on. The note shows in Result.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// Every level is one of the three allowed words\n// Rule: Every row of the table has one of \"must-have\", \"nice-to-have\" or \"not tied to a duty\" in the Level column.\n// Runs with no AI. Returns pass (true or false) and a reason.\nconst runRule = function runRule(rule, value, work, sources) {\n    const norm = (s) => String(s ?? '')\n        .toLowerCase()\n        .replace(/[\\u2018\\u2019\\u201c\\u201d\"'`]/g, '')\n        .replace(/\\s+/g, ' ')\n        .trim();\n    const readTable = (text) => {\n        const lines = String(text ?? '')\n            .split(/\\r?\\n/)\n            .map((l) => l.trim());\n        const tables = [];\n        let cur = null;\n        const cells = (l) => l\n            .replace(/^\\|/, '')\n            .replace(/\\|$/, '')\n            .split('|')\n            .map((c) => c.trim());\n        for (let i = 0; i < lines.length; i++) {\n            const l = lines[i] ?? '';\n            if (!l.startsWith('|')) {\n                cur = null;\n                continue;\n            }\n            if (!cur) {\n                const next = lines[i + 1] ?? '';\n                if (/^\\|?\\s*:?-{2,}/.test(next)) {\n                    cur = { header: cells(l), rows: [] };\n                    tables.push(cur);\n                    i++;\n                }\n                continue;\n            }\n            cur.rows.push(cells(l));\n        }\n        return tables;\n    };\n    // Every table that has this column, with \"None found\" rows left out.\n    // Returns that column's cells from every such table, or null if no table has it.\n    const findColumn = (name) => {\n        let seen = false;\n        const cells = [];\n        for (const t of readTable(work)) {\n            const i = t.header.findIndex((h) => norm(h) === norm(name));\n            if (i < 0)\n                continue;\n            seen = true;\n            for (const r of t.rows) {\n                if (r.some((c) => norm(c) === 'none found'))\n                    continue;\n                cells.push(r[i] ?? '');\n            }\n        }\n        return seen ? cells : null;\n    };\n    const empty = (c) => !c || /^[-\\u2013\\u2014\\s]*$/.test(c);\n    switch (rule) {\n        case 'has_text':\n            return norm(work).includes(norm(value))\n                ? { pass: true, reason: 'Found.' }\n                : { pass: false, reason: `The answer does not include \"${value}\".` };\n        case 'lacks_text':\n            return norm(work).includes(norm(value))\n                ? { pass: false, reason: `The answer includes \"${value}\", which it should not.` }\n                : { pass: true, reason: 'Not found, as it should be.' };\n        case 'max_words': {\n            const n = String(work ?? '')\n                .split(/\\s+/)\n                .filter(Boolean).length;\n            return n <= Number(value)\n                ? { pass: true, reason: `${n} words.` }\n                : { pass: false, reason: `The answer has ${n} words; the limit is ${value}.` };\n        }\n        case 'table_cells_filled': {\n            const problems = [];\n            for (const col of value) {\n                const f = findColumn(col);\n                if (!f) {\n                    problems.push(`No table with a \"${col}\" column was found.`);\n                    continue;\n                }\n                if (f.length === 0) {\n                    problems.push(`The table has no data rows to check in \"${col}\".`);\n                    continue;\n                }\n                f.forEach((c, i) => {\n                    if (empty(c))\n                        problems.push(`Row ${i + 1} has nothing in \"${col}\".`);\n                });\n            }\n            return problems.length\n                ? { pass: false, reason: problems.join(' ') }\n                : { pass: true, reason: 'Every row is filled in.' };\n        }\n        case 'column_values_in': {\n            const f = findColumn(value.column);\n            if (!f)\n                return { pass: false, reason: `No table with a \"${value.column}\" column was found.` };\n            if (f.length === 0)\n                return { pass: false, reason: `The table has no data rows to check in \"${value.column}\".` };\n            const allowed = value.allowed.map(norm);\n            const bad = [];\n            f.forEach((c, i) => {\n                if (!allowed.includes(norm(c)))\n                    bad.push(`Row ${i + 1} says \"${c}\".`);\n            });\n            return bad.length\n                ? {\n                    pass: false,\n                    reason: `The \"${value.column}\" column must say ${value.allowed.join(' or ')}. ${bad.join(' ')}`,\n                }\n                : { pass: true, reason: 'Every row uses an allowed word.' };\n        }\n        case 'column_quotes_source': {\n            const f = findColumn(value.column);\n            if (!f)\n                return { pass: false, reason: `No table with a \"${value.column}\" column was found.` };\n            if (f.length === 0)\n                return { pass: false, reason: `The table has no data rows to check in \"${value.column}\".` };\n            const src = norm(sources[value.input] ?? '');\n            const bad = [];\n            f.forEach((c, i) => {\n                // A quote may be shortened with \"...\"; each piece must be in the source.\n                const pieces = c\n                    .split(/\\.\\.\\.|\\u2026/)\n                    .map(norm)\n                    .filter(Boolean);\n                let cursor = 0;\n                const ordered = pieces.every((p) => {\n                    const found = src.indexOf(p, cursor);\n                    if (found < 0)\n                        return false;\n                    cursor = found + p.length;\n                    return true;\n                });\n                if (!pieces.length || !ordered)\n                    bad.push(`Row ${i + 1}: \"${c}\" is not in the source in that order.`);\n            });\n            return bad.length\n                ? { pass: false, reason: bad.join(' ') }\n                : { pass: true, reason: 'Every quote was found in the source.' };\n        }\n        case 'numbers_in_source': {\n            const nums = (s) => (String(s ?? '').match(/\\d[\\d,]*(?:\\.\\d+)?/g) ?? []).map((n) => n.replace(/,/g, ''));\n            const ids = Array.isArray(value.inputs) ? value.inputs : [value.input];\n            const src = new Set(ids.flatMap((id) => nums(sources[id] ?? '')));\n            // With a column named, only the numbers in that column of a table are compared (a rank column is not a claim).\n            let target = work;\n            if (value.column) {\n                const cells = findColumn(String(value.column));\n                if (cells === null)\n                    return { pass: false, reason: `The answer has no table with a ${value.column} column.` };\n                target = cells.join(' ');\n            }\n            const missing = [...new Set(nums(target))].filter((n) => !src.has(n));\n            return missing.length\n                ? {\n                    pass: false,\n                    reason: `These numbers are not in the source: ${missing.join(', ')}.`,\n                }\n                : { pass: true, reason: 'Every number was found in the source.' };\n        }\n        default:\n            return { pass: false, reason: `Unknown rule \"${rule}\".` };\n    }\n};\n\nconst work = String($(\"2. Tie each requirement to a duty\").first().json.text ?? '');\nconst sources = {\n\n};\nconst result = runRule(\"column_values_in\", {\"column\":\"Level\",\"allowed\":[\"must-have\",\"nice-to-have\",\"not tied to a duty\"]}, work, sources);\nreturn [{ json: result }];"
      },
      "id": "c7eab4f7-bbe8-49ca-a06e-cc3517c2b030",
      "name": "Check 7: Every level is one of the three allowed words",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        4900,
        320
      ],
      "notes": "Every row of the table has one of \"must-have\", \"nice-to-have\" or \"not tied to a duty\" in the Level column.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "dd9210ad-0032-4b18-a33b-5a2f9203bf32",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "4f6d213a-4e99-4155-a6b7-2a1073fabf13",
      "name": "Passed check 7?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        5160,
        320
      ],
      "notes": "True = passed. False = did not pass.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "69bf105a-e192-4a33-af85-0d4cd2b1883d",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "f09acc42-a866-4857-a50d-ec582c410d8d",
      "name": "Try again? (check 7)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        5160,
        520
      ],
      "notes": "True = first failure: send back to \"2. Tie each requirement to a duty\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "85f357cf-d8ae-4942-a641-6b2da53daf58",
              "name": "flag",
              "value": "=Every level is one of the three allowed words: did not pass. {{ $(\"Check 7: Every level is one of the three allowed words\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "d08559cb-c38e-449e-a159-d819de82453c",
      "name": "Flag check 7",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        5290,
        700
      ],
      "notes": "Notes the problem and carries on. The note shows in Result.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "promptType": "define",
        "hasOutputParser": false,
        "text": "=You are a careful reviewer. You do not fix or rewrite the work; you only decide whether it passes one check.\n\nThe check: No requirement that is not tied to a duty appears in the final text\nRead the table below and list every requirement marked not tied to a duty. Does any of them appear in the final job description, in the same words or in other words? Answer FAIL and quote each place where one appears.\n\nThe requirements and their levels:\n{{ $(\"2. Tie each requirement to a duty\").first().json.text }}\n\nThe work to check (the answer from Step 5 (Write the final job description)):\n{{ $(\"5. Write the final job description\").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": "92a67fd5-d3bc-4300-aa32-5a430277de86",
      "name": "Check 8: No requirement that is not tied to a duty appears in the final text",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        5420,
        320
      ],
      "notes": "Answers PASS or FAIL. Read the table below and list every requirement marked not tied to a duty. Does any of them appear in the final job description, in the same words or in other words? Answer FAIL and quote each place where one appears.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "dc920f1a-0132-4cab-a23b-589c9303c0c5",
              "leftValue": "={{ String($json.text ?? '').trim().toUpperCase().startsWith('PASS') }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "4f5bd819-ce96-493e-a6c3-2c83f3eebca0",
      "name": "Passed check 8?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        5680,
        320
      ],
      "notes": "True = passed. False = did not pass.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "6abf11ed-e092-48a0-a085-0edfd1b186aa",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "70a6ceb5-404f-4e50-acfc-e22f0c5cb1aa",
      "name": "Try again? (check 8)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        5680,
        520
      ],
      "notes": "True = first failure: send back to \"5. Write the final job description\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "84f3563c-d9ae-4ad5-a541-699aa63db0eb",
              "name": "flag",
              "value": "=No requirement that is not tied to a duty appears in the final text: did not pass. {{ $(\"Check 8: No requirement that is not tied to a duty appears in the final text\").first().json.text }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "cd855512-be8e-4cbf-aa59-e644e5825041",
      "name": "Flag check 8",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        5810,
        700
      ],
      "notes": "Notes the problem and carries on. The note shows in Result.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "promptType": "define",
        "hasOutputParser": false,
        "text": "=You are a careful reviewer. You do not fix or rewrite the work; you only decide whether it passes one check.\n\nThe check: Must-haves and nice-to-haves are in separate lists\nCompare the final job description with the table below. Is every must-have under \"What you need\" and every nice-to-have under \"Nice to have\", with none in the wrong list and none missing? Answer FAIL and quote each requirement that is in the wrong list or missing.\n\nThe requirements and their levels:\n{{ $(\"2. Tie each requirement to a duty\").first().json.text }}\n\nThe work to check (the answer from Step 5 (Write the final job description)):\n{{ $(\"5. Write the final job description\").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": "dcac541f-86fa-4738-aaf2-cd5dcb1439ee",
      "name": "Check 9: Must-haves and nice-to-haves are in separate lists",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        5940,
        320
      ],
      "notes": "Answers PASS or FAIL. Compare the final job description with the table below. Is every must-have under \"What you need\" and every nice-to-have under \"Nice to have\", with none in the wrong list and none missing? Answer FAIL and quote each requirement that is in the wrong list or missing.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "cf91faa3-fe32-47f2-a93b-63a19403c258",
              "leftValue": "={{ String($json.text ?? '').trim().toUpperCase().startsWith('PASS') }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "4f5e16b0-ce94-4aa7-a6c6-349af3ec7e09",
      "name": "Passed check 9?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        6200,
        320
      ],
      "notes": "True = passed. False = did not pass.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "5bbefa50-ef92-403d-a585-16bec4b17233",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "e4a9c3e8-2c4d-403d-a0fe-f4b2a859d5a7",
      "name": "Try again? (check 9)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        6200,
        520
      ],
      "notes": "True = first failure: send back to \"5. Write the final job description\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "8bf36141-daae-4c68-a841-5523a33dac32",
              "name": "flag",
              "value": "=Must-haves and nice-to-haves are in separate lists: did not pass. {{ $(\"Check 9: Must-haves and nice-to-haves are in separate lists\").first().json.text }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "ce8556a5-bd8e-4b2c-ab59-e7d7e4824eae",
      "name": "Flag check 9",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        6330,
        700
      ],
      "notes": "Notes the problem and carries on. The note shows in Result.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "promptType": "define",
        "hasOutputParser": false,
        "text": "=You are a careful reviewer. You do not fix or rewrite the work; you only decide whether it passes one check.\n\nThe check: No flagged wording is left in the final text\nLook at the table below. For each row where the Replacement is not keep, does the phrase in the Phrase column still appear in the final job description? Also read the final text for any other wording that points to age, gender, family, health, nationality or background. Answer FAIL and quote each place where one is left.\n\nThe flagged phrases:\n{{ $(\"4. Find wording that could put people off\").first().json.text }}\n\nThe work to check (the answer from Step 5 (Write the final job description)):\n{{ $(\"5. Write the final job description\").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": "0cc9ae74-669e-4cf5-a632-99ae0afd3e87",
      "name": "Check 10: No flagged wording is left in the final text",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        6460,
        320
      ],
      "notes": "Answers PASS or FAIL. Look at the table below. For each row where the Replacement is not keep, does the phrase in the Phrase column still appear in the final job description? Also read the final text for any other wording that points to age, gender, family, health, nationality or background. Answer FAIL and quote each place where one is left.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "ce91f910-ff32-4985-a83b-620e9503c3eb",
              "leftValue": "={{ String($json.text ?? '').trim().toUpperCase().startsWith('PASS') }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "681738de-32ba-4f93-a710-70ac99f75c39",
      "name": "Passed check 10?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        6720,
        320
      ],
      "notes": "True = passed. False = did not pass.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "5cbefbe3-ee92-4eaa-a685-1851c3b170a0",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "d431f1a6-14df-48b9-a86d-9a74a896a217",
      "name": "Try again? (check 10)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        6720,
        520
      ],
      "notes": "True = first failure: send back to \"5. Write the final job description\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "8af35fae-dbae-4dfb-a741-5390a43dadc5",
              "name": "flag",
              "value": "=No flagged wording is left in the final text: did not pass. {{ $(\"Check 10: No flagged wording is left in the final text\").first().json.text }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "c0faed77-f3f7-451c-a573-d89d470e21d2",
      "name": "Flag check 10",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        6850,
        700
      ],
      "notes": "Notes the problem and carries on. The note shows in Result.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "content": "**Check 11: check by hand**\nRead the job description as someone who has never worked here. Ask a person who does or knows the job whether each must-have is needed on day one. Check the pay, the hours and any statement about accommodations against your own policy and local rules, and have your HR or legal team look at it before it goes out. Only a person can say what the law and your own policy require.\n\nIf not: flag it and carry on. This check is also listed in Result.",
        "height": 200,
        "width": 240,
        "color": 3
      },
      "id": "381dd45f-c2ed-4bae-a959-e4890c5026e0",
      "name": "Check 11 (by hand)",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        6700,
        70
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "bcc6ec10-55b0-49ab-ac86-47fe5fc23351",
              "name": "result",
              "value": "={{ $(\"5. Write the final job description\").first().json.text }}",
              "type": "string"
            },
            {
              "id": "d64f80b1-dde8-4c40-a0fb-fc8ff340308e",
              "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": "1553b6b7-d753-439e-a61b-f825dec52f5c",
              "name": "check_by_hand",
              "value": "- Read the job description as someone who has never worked here. Ask a person who does or knows the job whether each must-have is needed on day one. Check the pay, the hours and any statement about accommodations against your own policy and local rules, and have your HR or legal team look at it before it goes out. Only a person can say what the law and your own policy require.",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "376810fd-078e-407c-abfb-5bfb54201dc2",
      "name": "Result",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        6980,
        320
      ],
      "notes": "The final answer, anything flagged, and the checks left for you.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "options": {}
      },
      "id": "e0257193-5eed-40ce-af5a-70a12e462c6c",
      "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 the duties",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "1. List the duties": {
      "main": [
        [
          {
            "node": "Check 1: Every duty is quoted word for word from the notes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 1: Every duty is quoted word for word from the notes": {
      "main": [
        [
          {
            "node": "Passed check 1?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 1?": {
      "main": [
        [
          {
            "node": "2. Tie each requirement to a duty",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 1)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 1)": {
      "main": [
        [
          {
            "node": "1. List the duties",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 1": {
      "main": [
        [
          {
            "node": "2. Tie each requirement to a duty",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "2. Tie each requirement to a duty": {
      "main": [
        [
          {
            "node": "Check 2: Every requirement is quoted word for word from your list",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 2: Every requirement is quoted word for word from your list": {
      "main": [
        [
          {
            "node": "Passed check 2?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 2?": {
      "main": [
        [
          {
            "node": "3. Draft the job description",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 2)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 2)": {
      "main": [
        [
          {
            "node": "2. Tie each requirement to a duty",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 2": {
      "main": [
        [
          {
            "node": "3. Draft the job description",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "3. Draft the job description": {
      "main": [
        [
          {
            "node": "Check 3: No number appears that your notes and list do not have",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 3: No number appears that your notes and list do not have": {
      "main": [
        [
          {
            "node": "Passed check 3?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 3?": {
      "main": [
        [
          {
            "node": "4. Find wording that could put people off",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 3)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 3)": {
      "main": [
        [
          {
            "node": "3. Draft the job description",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 3": {
      "main": [
        [
          {
            "node": "4. Find wording that could put people off",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "4. Find wording that could put people off": {
      "main": [
        [
          {
            "node": "Check 4: Each concern is one of the eight allowed words",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 4: Each concern is one of the eight allowed words": {
      "main": [
        [
          {
            "node": "Passed check 4?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 4?": {
      "main": [
        [
          {
            "node": "5. Write the final job description",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 4)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 4)": {
      "main": [
        [
          {
            "node": "4. Find wording that could put people off",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 4",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 4": {
      "main": [
        [
          {
            "node": "5. Write the final job description",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "5. Write the final job description": {
      "main": [
        [
          {
            "node": "Check 5: The reminder to check it with your HR or legal team is in the final text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 5: The reminder to check it with your HR or legal team is in the final text": {
      "main": [
        [
          {
            "node": "Passed check 5?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 5?": {
      "main": [
        [
          {
            "node": "Check 6: Each must-have is tied to a duty",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 5)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 5)": {
      "main": [
        [
          {
            "node": "5. Write the final job description",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 5",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 5": {
      "main": [
        [
          {
            "node": "Check 6: Each must-have is tied to a duty",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 6: Each must-have is tied to a duty": {
      "main": [
        [
          {
            "node": "Passed check 6?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 6?": {
      "main": [
        [
          {
            "node": "Check 7: Every level is one of the three allowed words",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 6)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 6)": {
      "main": [
        [
          {
            "node": "2. Tie each requirement to a duty",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 6",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 6": {
      "main": [
        [
          {
            "node": "Check 7: Every level is one of the three allowed words",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 7: Every level is one of the three allowed words": {
      "main": [
        [
          {
            "node": "Passed check 7?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 7?": {
      "main": [
        [
          {
            "node": "Check 8: No requirement that is not tied to a duty appears in the final text",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 7)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 7)": {
      "main": [
        [
          {
            "node": "2. Tie each requirement to a duty",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 7",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 7": {
      "main": [
        [
          {
            "node": "Check 8: No requirement that is not tied to a duty appears in the final text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 8: No requirement that is not tied to a duty appears in the final text": {
      "main": [
        [
          {
            "node": "Passed check 8?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 8?": {
      "main": [
        [
          {
            "node": "Check 9: Must-haves and nice-to-haves are in separate lists",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 8)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 8)": {
      "main": [
        [
          {
            "node": "5. Write the final job description",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 8",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 8": {
      "main": [
        [
          {
            "node": "Check 9: Must-haves and nice-to-haves are in separate lists",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 9: Must-haves and nice-to-haves are in separate lists": {
      "main": [
        [
          {
            "node": "Passed check 9?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 9?": {
      "main": [
        [
          {
            "node": "Check 10: No flagged wording is left in the final text",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 9)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 9)": {
      "main": [
        [
          {
            "node": "5. Write the final job description",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 9",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 9": {
      "main": [
        [
          {
            "node": "Check 10: No flagged wording is left in the final text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 10: No flagged wording is left in the final text": {
      "main": [
        [
          {
            "node": "Passed check 10?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 10?": {
      "main": [
        [
          {
            "node": "Result",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 10)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 10)": {
      "main": [
        [
          {
            "node": "5. Write the final job description",
            "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 the duties",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "2. Tie each requirement to a duty",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "3. Draft the job description",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "4. Find wording that could put people off",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "5. Write the final job description",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "Check 6: Each must-have is tied to a duty",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "Check 8: No requirement that is not tied to a duty appears in the final text",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "Check 9: Must-haves and nice-to-haves are in separate lists",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "Check 10: No flagged wording is left in the final text",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "meta": {
    "template": "write-a-job-description"
  }
}
