{
  "id": "kvNGz3KpHI7Par64pZIdM",
  "name": "Smart examination marking with plagiarism detection and moderation",
  "tags": [],
  "nodes": [
    {
      "id": "f534983f-265a-4f9f-bb89-ab2756191105",
      "name": "Manual Trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        -1312,
        1248
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "1b240cf7-c1c3-49d1-8a19-a7ff7bef4c6d",
      "name": "Workflow Configuration",
      "type": "n8n-nodes-base.set",
      "position": [
        -1120,
        1248
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "id-1",
              "name": "apiEndpoint",
              "type": "string",
              "value": "<__PLACEHOLDER_VALUE__API endpoint URL to retrieve student answer and rubric__>"
            },
            {
              "id": "id-2",
              "name": "totalMarks",
              "type": "number",
              "value": 100
            },
            {
              "id": "id-3",
              "name": "passingGrade",
              "type": "number",
              "value": 50
            },
            {
              "id": "id-4",
              "name": "slackChannel",
              "type": "string",
              "value": "<__PLACEHOLDER_VALUE__Slack channel ID for escalation alerts__>"
            },
            {
              "id": "id-5",
              "name": "googleSheetId",
              "type": "string",
              "value": "<__PLACEHOLDER_VALUE__Google Sheets ID for logging results__>"
            },
            {
              "id": "id-6",
              "name": "plagiarismThreshold",
              "type": "number",
              "value": 0.7
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "81142903-b550-4145-adb6-e0404f9203dd",
      "name": "Retrieve Student Answer and Rubric",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -928,
        1256
      ],
      "parameters": {
        "url": "={{ $('Workflow Configuration').first().json.apiEndpoint }}",
        "options": {}
      },
      "typeVersion": 4.3
    },
    {
      "id": "095df195-f3bd-4d71-9942-f7d50eebac0a",
      "name": "Rubric Interpreter Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        -704,
        1256
      ],
      "parameters": {
        "text": "=Student Answer: {{ $json.studentAnswer }}\n\nRubric: {{ $json.rubric }}",
        "options": {
          "systemMessage": "You are a Rubric Interpreter Agent specialized in analyzing examination marking criteria.\n\nYour task is to:\n1. Parse the provided rubric and extract all marking criteria\n2. Identify the weighting/points allocation for each criterion\n3. Structure the criteria into clear, measurable components\n4. Define what constitutes different grade levels for each criterion (excellent, good, satisfactory, poor)\n5. Extract any specific requirements or constraints mentioned in the rubric\n6. Identify key terms and concepts that should be present in high-quality answers\n7. Flag any ambiguities or unclear criteria that may affect marking consistency\n\nReturn a structured JSON output with:\n- Total marks available\n- List of criteria with weightings\n- Grade descriptors for each criterion\n- Key concepts and requirements\n- Any integrity or fairness considerations"
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 3.1
    },
    {
      "id": "4fd4d8d2-7a19-4c26-8602-1a99b81e8813",
      "name": "Primary Marker Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        -352,
        1256
      ],
      "parameters": {
        "text": "=Student Answer: {{ $('Retrieve Student Answer and Rubric').first().json.studentAnswer }}\n\nStructured Rubric: {{ $json.structuredRubric }}\n\nCriteria: {{ $json.criteria }}",
        "options": {
          "systemMessage": "You are a Primary Marker Agent responsible for grading student examination answers with precision and fairness.\n\nYour task is to:\n1. Carefully read and analyze the student answer against the structured rubric criteria\n2. Evaluate each criterion independently and assign marks based on the grade descriptors\n3. Provide detailed justification for each mark awarded, citing specific evidence from the student answer\n4. Calculate subtotal marks for each criterion and overall total\n5. Identify strengths and weaknesses in the answer\n6. Flag potential integrity issues (plagiarism indicators, suspicious patterns, irrelevant content)\n7. Note any borderline decisions that may benefit from moderation\n8. Maintain objectivity and consistency in marking standards\n\nReturn structured JSON output with:\n- Marks breakdown by criterion with justifications\n- Total marks awarded\n- Strengths identified\n- Weaknesses identified\n- Integrity flags (if any)\n- Borderline decisions requiring review\n- Confidence level in marking (high/medium/low)"
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 3.1
    },
    {
      "id": "e7ed2b7b-4933-46ec-8d3d-2782611485ef",
      "name": "Quality Moderator Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        800,
        848
      ],
      "parameters": {
        "text": "=Student Answer: {{ $('Retrieve Student Answer and Rubric').first().json.studentAnswer }}\n\nStructured Rubric: {{ $('Rubric Interpreter Agent').first().json.structuredRubric }}\n\nPrimary Marking: {{ $json }}",
        "options": {
          "systemMessage": "You are a Quality Moderator Agent responsible for reviewing marking decisions to ensure fairness, consistency, and accuracy.\n\nYour task is to:\n1. Review the primary marker's assessment against the rubric criteria\n2. Verify that marks awarded align with the grade descriptors and evidence provided\n3. Check for marking consistency across different criteria\n4. Identify any potential bias, over-marking, or under-marking\n5. Assess whether borderline decisions were handled appropriately\n6. Review integrity flags and determine if they are justified\n7. Adjust marks if necessary with clear reasoning for any changes\n8. Validate that the total marks calculation is correct\n9. Provide moderation notes explaining any adjustments made\n\nReturn structured JSON output with:\n- Moderation decision (APPROVED / ADJUSTED / ESCALATE)\n- Adjusted marks breakdown (if changes made)\n- Final total marks\n- Moderation notes explaining review findings\n- Changes made with justifications\n- Fairness assessment\n- Recommendation for any follow-up actions"
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 3.1
    },
    {
      "id": "f3414890-10be-4703-95df-d08e9bf3686e",
      "name": "Feedback Generator Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        1152,
        848
      ],
      "parameters": {
        "text": "=Student Answer: {{ $('Retrieve Student Answer and Rubric').first().json.studentAnswer }}\n\nStructured Rubric: {{ $('Rubric Interpreter Agent').first().json.structuredRubric }}\n\nFinal Marking: {{ $json }}",
        "options": {
          "systemMessage": "You are a Feedback Generator Agent specialized in creating constructive, actionable feedback for students.\n\nYour task is to:\n1. Synthesize the marking and moderation results into clear, student-friendly feedback\n2. Highlight specific strengths in the student's answer with examples\n3. Identify areas for improvement with concrete suggestions\n4. Explain why marks were awarded or deducted for each criterion\n5. Provide actionable recommendations for future improvement\n6. Use encouraging and supportive language while being honest about weaknesses\n7. Structure feedback in a logical, easy-to-understand format\n8. Include specific examples from the student's answer to illustrate points\n9. Suggest resources or study areas for improvement where relevant\n\nReturn structured JSON output with:\n- Overall summary of performance\n- Detailed feedback by criterion\n- Key strengths (with examples)\n- Areas for improvement (with specific suggestions)\n- Actionable recommendations\n- Encouragement and next steps"
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 3.1
    },
    {
      "id": "68034cc6-5449-4086-b1fa-c55ebec3b848",
      "name": "Final Output Compilation",
      "type": "n8n-nodes-base.set",
      "position": [
        2480,
        864
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "id-1",
              "name": "studentId",
              "type": "string",
              "value": "={{ $('Retrieve Student Answer and Rubric').first().json.studentId || 'N/A' }}"
            },
            {
              "id": "id-2",
              "name": "examTitle",
              "type": "string",
              "value": "={{ $('Retrieve Student Answer and Rubric').first().json.examTitle || 'N/A' }}"
            },
            {
              "id": "id-3",
              "name": "totalMarksAvailable",
              "type": "number",
              "value": "={{ $('Workflow Configuration').first().json.totalMarks }}"
            },
            {
              "id": "id-4",
              "name": "marksAwarded",
              "type": "number",
              "value": "={{ $('Quality Moderator Agent').first().json.finalTotalMarks }}"
            },
            {
              "id": "id-5",
              "name": "percentage",
              "type": "number",
              "value": "={{ Math.round(($('Quality Moderator Agent').first().json.finalTotalMarks / $('Workflow Configuration').first().json.totalMarks) * 100) }}"
            },
            {
              "id": "id-6",
              "name": "grade",
              "type": "string",
              "value": "={{ $('Quality Moderator Agent').first().json.finalTotalMarks >= $('Workflow Configuration').first().json.passingGrade ? 'PASS' : 'FAIL' }}"
            },
            {
              "id": "id-7",
              "name": "marksBreakdown",
              "type": "object",
              "value": "={{ $('Quality Moderator Agent').first().json.adjustedMarksBreakdown || $('Primary Marker Agent').first().json.marksBreakdown }}"
            },
            {
              "id": "id-8",
              "name": "moderationNotes",
              "type": "string",
              "value": "={{ $('Quality Moderator Agent').first().json.moderationNotes }}"
            },
            {
              "id": "id-9",
              "name": "moderationDecision",
              "type": "string",
              "value": "={{ $('Quality Moderator Agent').first().json.moderationDecision }}"
            },
            {
              "id": "id-10",
              "name": "integrityFlags",
              "type": "array",
              "value": "={{ $('Primary Marker Agent').first().json.integrityFlags || [] }}"
            },
            {
              "id": "id-11",
              "name": "feedback",
              "type": "object",
              "value": "={{ $('Feedback Generator Agent').first().json }}"
            },
            {
              "id": "id-12",
              "name": "markedAt",
              "type": "string",
              "value": "={{ new Date().toISOString() }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "51cf7982-60fa-4187-b999-fe65bb383ba7",
      "name": "OpenAI Chat Model - Rubric",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        -800,
        1488
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1-mini"
        },
        "options": {},
        "builtInTools": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "8362c4a1-aec6-46c0-8802-e1a80b7d5f6a",
      "name": "OpenAI Chat Model - Marker",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        -344,
        1480
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1-mini"
        },
        "options": {},
        "builtInTools": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "63bae304-c5e1-4df4-aaf5-10a6786f830b",
      "name": "OpenAI Chat Model - Moderator",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        808,
        1072
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1-mini"
        },
        "options": {},
        "builtInTools": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "ef4bfc22-98c3-41c7-a4fd-7246ff4407d9",
      "name": "OpenAI Chat Model - Feedback",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        1160,
        1072
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1-mini"
        },
        "options": {},
        "builtInTools": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "ad9351ba-3fe8-49c7-8c60-4df456475fc4",
      "name": "Structured Output Parser - Rubric",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        -568,
        1480
      ],
      "parameters": {
        "schemaType": "manual",
        "inputSchema": "{\"type\": \"object\", \"properties\": {\"totalMarksAvailable\": {\"type\": \"number\"}, \"criteria\": {\"type\": \"array\", \"items\": {\"type\": \"object\", \"properties\": {\"name\": {\"type\": \"string\"}, \"weighting\": {\"type\": \"number\"}, \"gradeDescriptors\": {\"type\": \"object\", \"properties\": {\"excellent\": {\"type\": \"string\"}, \"good\": {\"type\": \"string\"}, \"satisfactory\": {\"type\": \"string\"}, \"poor\": {\"type\": \"string\"}}}}}}, \"keyConcepts\": {\"type\": \"array\", \"items\": {\"type\": \"string\"}}, \"requirements\": {\"type\": \"array\", \"items\": {\"type\": \"string\"}}, \"ambiguities\": {\"type\": \"array\", \"items\": {\"type\": \"string\"}}, \"structuredRubric\": {\"type\": \"string\"}}, \"required\": [\"totalMarksAvailable\", \"criteria\", \"keyConcepts\", \"requirements\", \"structuredRubric\"]}"
      },
      "typeVersion": 1.3
    },
    {
      "id": "bdadd2d2-e661-4c08-87cc-fe7fdf65ab1d",
      "name": "Structured Output Parser - Marker",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        -176,
        1472
      ],
      "parameters": {
        "schemaType": "manual",
        "inputSchema": "{\"type\": \"object\", \"properties\": {\"marksBreakdown\": {\"type\": \"array\", \"items\": {\"type\": \"object\", \"properties\": {\"criterionName\": {\"type\": \"string\"}, \"marksAwarded\": {\"type\": \"number\"}, \"marksAvailable\": {\"type\": \"number\"}, \"justification\": {\"type\": \"string\"}, \"evidenceCited\": {\"type\": \"string\"}}}}, \"totalMarksAwarded\": {\"type\": \"number\"}, \"strengths\": {\"type\": \"array\", \"items\": {\"type\": \"string\"}}, \"weaknesses\": {\"type\": \"array\", \"items\": {\"type\": \"string\"}}, \"integrityFlags\": {\"type\": \"array\", \"items\": {\"type\": \"string\"}}, \"borderlineDecisions\": {\"type\": \"array\", \"items\": {\"type\": \"string\"}}, \"confidenceLevel\": {\"type\": \"string\", \"enum\": [\"high\", \"medium\", \"low\"]}}, \"required\": [\"marksBreakdown\", \"totalMarksAwarded\", \"strengths\", \"weaknesses\", \"integrityFlags\", \"borderlineDecisions\", \"confidenceLevel\"]}"
      },
      "typeVersion": 1.3
    },
    {
      "id": "da616f20-2853-4d0b-8026-adfe3e7acb36",
      "name": "Structured Output Parser - Moderator",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        960,
        1072
      ],
      "parameters": {
        "schemaType": "manual",
        "inputSchema": "{\"type\": \"object\", \"properties\": {\"moderationDecision\": {\"type\": \"string\", \"enum\": [\"APPROVED\", \"ADJUSTED\", \"ESCALATE\"]}, \"adjustedMarksBreakdown\": {\"type\": \"array\", \"items\": {\"type\": \"object\", \"properties\": {\"criterionName\": {\"type\": \"string\"}, \"marksAwarded\": {\"type\": \"number\"}, \"marksAvailable\": {\"type\": \"number\"}, \"justification\": {\"type\": \"string\"}}}}, \"finalTotalMarks\": {\"type\": \"number\"}, \"moderationNotes\": {\"type\": \"string\"}, \"changesMade\": {\"type\": \"array\", \"items\": {\"type\": \"object\", \"properties\": {\"criterion\": {\"type\": \"string\"}, \"originalMarks\": {\"type\": \"number\"}, \"adjustedMarks\": {\"type\": \"number\"}, \"reason\": {\"type\": \"string\"}}}}, \"fairnessAssessment\": {\"type\": \"string\"}, \"recommendations\": {\"type\": \"array\", \"items\": {\"type\": \"string\"}}}, \"required\": [\"moderationDecision\", \"finalTotalMarks\", \"moderationNotes\", \"changesMade\", \"fairnessAssessment\", \"recommendations\"]}"
      },
      "typeVersion": 1.3
    },
    {
      "id": "289604d8-44f4-4190-a30d-407308569888",
      "name": "Structured Output Parser - Feedback",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        1328,
        1072
      ],
      "parameters": {
        "schemaType": "manual",
        "inputSchema": "{\"type\": \"object\", \"properties\": {\"overallSummary\": {\"type\": \"string\"}, \"feedbackByCriterion\": {\"type\": \"array\", \"items\": {\"type\": \"object\", \"properties\": {\"criterionName\": {\"type\": \"string\"}, \"feedback\": {\"type\": \"string\"}, \"marksAwarded\": {\"type\": \"number\"}, \"marksAvailable\": {\"type\": \"number\"}}}}, \"keyStrengths\": {\"type\": \"array\", \"items\": {\"type\": \"string\"}}, \"areasForImprovement\": {\"type\": \"array\", \"items\": {\"type\": \"object\", \"properties\": {\"area\": {\"type\": \"string\"}, \"suggestion\": {\"type\": \"string\"}}}}, \"actionableRecommendations\": {\"type\": \"array\", \"items\": {\"type\": \"string\"}}, \"encouragement\": {\"type\": \"string\"}}, \"required\": [\"overallSummary\", \"feedbackByCriterion\", \"keyStrengths\", \"areasForImprovement\", \"actionableRecommendations\", \"encouragement\"]}"
      },
      "typeVersion": 1.3
    },
    {
      "id": "24080315-fe89-465f-a948-1a51116376d4",
      "name": "Route by Moderation Decision",
      "type": "n8n-nodes-base.switch",
      "position": [
        1504,
        816
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "ADJUSTED",
              "conditions": {
                "options": {
                  "leftValue": "",
                  "caseSensitive": false,
                  "typeValidation": "loose"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.moderationDecision }}",
                    "rightValue": "ADJUSTED"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "ESCALATE",
              "conditions": {
                "options": {
                  "leftValue": "",
                  "caseSensitive": false,
                  "typeValidation": "loose"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.moderationDecision }}",
                    "rightValue": "ESCALATE"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "APPROVED",
              "conditions": {
                "options": {
                  "leftValue": "",
                  "caseSensitive": false,
                  "typeValidation": "loose"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.moderationDecision }}",
                    "rightValue": "APPROVED"
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {
          "ignoreCase": true,
          "fallbackOutput": "extra"
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "1b2e86cf-03f2-4ff9-89db-f48152311445",
      "name": "Check Integrity Flags",
      "type": "n8n-nodes-base.if",
      "position": [
        0,
        1500
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": false,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "id-1",
              "operator": {
                "type": "array",
                "operation": "lengthGt"
              },
              "leftValue": "={{ $('Primary Marker Agent').item.json.integrityFlags }}",
              "rightValue": "0"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "938754da-07b0-427f-8b07-1300c454938c",
      "name": "Calculate Statistics",
      "type": "n8n-nodes-base.code",
      "position": [
        2080,
        1040
      ],
      "parameters": {
        "jsCode": "// Calculate marking statistics\nconst items = $input.all();\n\nif (items.length === 0) {\n  return [{ json: { error: 'No items to process' } }];\n}\n\n// Extract marks data\nconst marksData = items.map(item => {\n  const marksAwarded = item.json.marksAwarded || 0;\n  const totalMarksAvailable = item.json.totalMarksAvailable || 100;\n  const percentage = item.json.percentage || 0;\n  const grade = item.json.grade || 'N/A';\n  \n  return {\n    marksAwarded,\n    totalMarksAvailable,\n    percentage,\n    grade\n  };\n});\n\n// Calculate average marks\nconst totalMarks = marksData.reduce((sum, item) => sum + item.marksAwarded, 0);\nconst averageMarks = totalMarks / marksData.length;\n\n// Calculate average percentage\nconst totalPercentage = marksData.reduce((sum, item) => sum + item.percentage, 0);\nconst averagePercentage = totalPercentage / marksData.length;\n\n// Calculate standard deviation\nconst squaredDifferences = marksData.map(item => Math.pow(item.marksAwarded - averageMarks, 2));\nconst variance = squaredDifferences.reduce((sum, val) => sum + val, 0) / marksData.length;\nconst standardDeviation = Math.sqrt(variance);\n\n// Calculate grade distribution\nconst gradeDistribution = marksData.reduce((acc, item) => {\n  const grade = item.grade;\n  acc[grade] = (acc[grade] || 0) + 1;\n  return acc;\n}, {});\n\n// Calculate pass/fail counts\nconst passCount = marksData.filter(item => item.grade === 'PASS').length;\nconst failCount = marksData.filter(item => item.grade === 'FAIL').length;\nconst passRate = (passCount / marksData.length) * 100;\n\n// Find min and max marks\nconst allMarks = marksData.map(item => item.marksAwarded);\nconst minMarks = Math.min(...allMarks);\nconst maxMarks = Math.max(...allMarks);\n\n// Calculate median\nconst sortedMarks = [...allMarks].sort((a, b) => a - b);\nconst median = sortedMarks.length % 2 === 0\n  ? (sortedMarks[sortedMarks.length / 2 - 1] + sortedMarks[sortedMarks.length / 2]) / 2\n  : sortedMarks[Math.floor(sortedMarks.length / 2)];\n\n// Return statistics\nreturn [{\n  json: {\n    totalStudents: marksData.length,\n    averageMarks: Math.round(averageMarks * 100) / 100,\n    averagePercentage: Math.round(averagePercentage * 100) / 100,\n    standardDeviation: Math.round(standardDeviation * 100) / 100,\n    median: Math.round(median * 100) / 100,\n    minMarks,\n    maxMarks,\n    passCount,\n    failCount,\n    passRate: Math.round(passRate * 100) / 100,\n    gradeDistribution,\n    calculatedAt: new Date().toISOString()\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "184ceb6b-edee-4a52-8ca1-cb973f7c5cdc",
      "name": "Secondary Marker Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        1728,
        448
      ],
      "parameters": {
        "text": "=Student Answer: {{ $('Retrieve Student Answer and Rubric').first().json.studentAnswer }}\n\nStructured Rubric: {{ $('Rubric Interpreter Agent').first().json.structuredRubric }}\n\nPrimary Marking: {{ $('Primary Marker Agent').first().json }}",
        "options": {
          "systemMessage": "You are a Secondary Marker Agent providing independent assessment for adjusted cases.\n\nYour task is to:\n1. Independently mark the student answer without bias from primary marking\n2. Apply the same rubric criteria and grade descriptors\n3. Provide your own justifications and evidence citations\n4. Calculate marks independently\n5. Note any significant differences from primary marking\n\nReturn structured JSON output matching the primary marker format for comparison."
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 3.1
    },
    {
      "id": "fc286b43-a963-40a0-81fd-400d4e7cf615",
      "name": "Plagiarism Analyzer Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        224,
        1624
      ],
      "parameters": {
        "text": "=Student Answer: {{ $('Retrieve Student Answer and Rubric').first().json.studentAnswer }}\n\nIntegrity Flags: {{ $('Primary Marker Agent').first().json.integrityFlags }}",
        "options": {
          "systemMessage": "You are a Plagiarism Analyzer Agent specialized in detecting academic integrity violations.\n\nYour task is to:\n1. Analyze the student answer for plagiarism indicators and integrity violations\n2. Assess text similarity patterns, unusual phrasing, and inconsistent writing styles\n3. Identify potential sources of copied content\n4. Calculate plagiarism confidence score (0-1)\n5. Determine severity level (LOW / MEDIUM / HIGH / CRITICAL)\n6. Provide specific evidence of suspected plagiarism\n7. Recommend actions (NONE / REVIEW / INVESTIGATE / REJECT)\n\nReturn structured JSON output with:\n- Plagiarism detected (boolean)\n- Confidence score\n- Severity level\n- Evidence list\n- Suspected sources\n- Recommended action\n- Detailed analysis"
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 3.1
    },
    {
      "id": "8e696d2e-a391-4672-a88c-7b4ac402940f",
      "name": "OpenAI Chat Model - Secondary",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        1736,
        672
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1-mini"
        },
        "options": {},
        "builtInTools": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "d5bb10b6-6f60-4615-868a-19d44662ab14",
      "name": "OpenAI Chat Model - Plagiarism",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        232,
        1848
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1-mini"
        },
        "options": {},
        "builtInTools": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "df0df50f-261f-4c50-9bff-3fa0d90bd37d",
      "name": "Structured Output Parser - Secondary",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        1904,
        672
      ],
      "parameters": {
        "schemaType": "manual",
        "inputSchema": "{\"type\": \"object\", \"properties\": {\"marksBreakdown\": {\"type\": \"array\", \"items\": {\"type\": \"object\", \"properties\": {\"criterionName\": {\"type\": \"string\"}, \"marksAwarded\": {\"type\": \"number\"}, \"marksAvailable\": {\"type\": \"number\"}, \"justification\": {\"type\": \"string\"}, \"evidenceCited\": {\"type\": \"string\"}}}}, \"totalMarksAwarded\": {\"type\": \"number\"}, \"strengths\": {\"type\": \"array\", \"items\": {\"type\": \"string\"}}, \"weaknesses\": {\"type\": \"array\", \"items\": {\"type\": \"string\"}}, \"integrityFlags\": {\"type\": \"array\", \"items\": {\"type\": \"string\"}}, \"borderlineDecisions\": {\"type\": \"array\", \"items\": {\"type\": \"string\"}}, \"confidenceLevel\": {\"type\": \"string\", \"enum\": [\"high\", \"medium\", \"low\"]}}, \"required\": [\"marksBreakdown\", \"totalMarksAwarded\", \"strengths\", \"weaknesses\", \"integrityFlags\", \"borderlineDecisions\", \"confidenceLevel\"]}"
      },
      "typeVersion": 1.3
    },
    {
      "id": "e4201a43-a66c-489f-a8b3-6cd8cf9f208e",
      "name": "Structured Output Parser - Plagiarism",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        432,
        1856
      ],
      "parameters": {
        "schemaType": "manual",
        "inputSchema": "{\"type\": \"object\", \"properties\": {\"plagiarismDetected\": {\"type\": \"boolean\"}, \"confidenceScore\": {\"type\": \"number\", \"minimum\": 0, \"maximum\": 1}, \"severity\": {\"type\": \"string\", \"enum\": [\"LOW\", \"MEDIUM\", \"HIGH\", \"CRITICAL\"]}, \"evidence\": {\"type\": \"array\", \"items\": {\"type\": \"string\"}}, \"suspectedSources\": {\"type\": \"array\", \"items\": {\"type\": \"string\"}}, \"recommendedAction\": {\"type\": \"string\", \"enum\": [\"NONE\", \"REVIEW\", \"INVESTIGATE\", \"REJECT\"]}, \"detailedAnalysis\": {\"type\": \"string\"}}, \"required\": [\"plagiarismDetected\", \"confidenceScore\", \"severity\", \"evidence\", \"suspectedSources\", \"recommendedAction\", \"detailedAnalysis\"]}"
      },
      "typeVersion": 1.3
    },
    {
      "id": "1f9abded-4139-47b7-97b7-fb6b08f52874",
      "name": "Merge Marking Results",
      "type": "n8n-nodes-base.merge",
      "position": [
        576,
        1088
      ],
      "parameters": {},
      "typeVersion": 3.2
    },
    {
      "id": "a930e99c-d26a-4595-bb6f-63f5fd17a26c",
      "name": "Log to Google Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2704,
        880
      ],
      "parameters": {
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Workflow Configuration').first().json.googleSheetId }}"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "59c32d26-1a33-4cc3-bd84-1a1da7486272",
      "name": "Send Escalation Alert",
      "type": "n8n-nodes-base.slack",
      "position": [
        2080,
        848
      ],
      "parameters": {
        "text": "=\ud83d\udea8 *EXAMINATION MARKING ESCALATION*\n\n*Student ID:* {{ $json.studentId }}\n*Exam:* {{ $json.examTitle }}\n*Decision:* {{ $json.moderationDecision }}\n\n*Escalation Reason:*\n{{ $json.escalationReason }}\n\n*Timestamp:* {{ $json.timestamp }}\n\n_Requires immediate review by senior examiner_",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Workflow Configuration').first().json.slackChannel }}"
        },
        "otherOptions": {},
        "authentication": "oAuth2"
      },
      "credentials": {
        "slackOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.4
    },
    {
      "id": "db3b9c30-e433-4dcf-b96a-b6b199e9a858",
      "name": "Prepare Escalation Data",
      "type": "n8n-nodes-base.set",
      "position": [
        1792,
        848
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "id-1",
              "name": "alertType",
              "type": "string",
              "value": "ESCALATION"
            },
            {
              "id": "id-2",
              "name": "studentId",
              "type": "string",
              "value": "={{ $('Retrieve Student Answer and Rubric').first().json.studentId }}"
            },
            {
              "id": "id-3",
              "name": "examTitle",
              "type": "string",
              "value": "={{ $('Retrieve Student Answer and Rubric').first().json.examTitle }}"
            },
            {
              "id": "id-4",
              "name": "moderationDecision",
              "type": "string",
              "value": "={{ $('Quality Moderator Agent').first().json.moderationDecision }}"
            },
            {
              "id": "id-5",
              "name": "escalationReason",
              "type": "string",
              "value": "={{ $('Quality Moderator Agent').first().json.moderationNotes }}"
            },
            {
              "id": "id-6",
              "name": "timestamp",
              "type": "string",
              "value": "={{ new Date().toISOString() }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "20061d0e-8da8-4191-bd0f-c910b71cd57c",
      "name": "Prepare Integrity Report",
      "type": "n8n-nodes-base.set",
      "position": [
        576,
        1624
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "id-1",
              "name": "integrityViolation",
              "type": "boolean",
              "value": true
            },
            {
              "id": "id-2",
              "name": "plagiarismAnalysis",
              "type": "object",
              "value": "={{ $json }}"
            },
            {
              "id": "id-3",
              "name": "requiresInvestigation",
              "type": "boolean",
              "value": "={{ $json.severity === 'HIGH' || $json.severity === 'CRITICAL' }}"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "e41ba4e5-5b18-4ff0-a12d-de9fc0d77a02",
      "name": "Merge All Paths",
      "type": "n8n-nodes-base.merge",
      "position": [
        2304,
        868
      ],
      "parameters": {},
      "typeVersion": 3.2
    },
    {
      "id": "70e5dbde-9b66-4942-95b5-cb002b463b6e",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -96,
        592
      ],
      "parameters": {
        "color": 6,
        "width": 400,
        "height": 384,
        "content": "## Prerequisites\n- Google Sheets with service account credentials\n- Student answer and rubric data source (API or spreadsheet)\n## Use Cases\n- Automated essay and short-answer marking for university assessments\n## Customization\n- Replace OpenAI with Anthropic Claude for marking and moderation agents\n## Benefits\n- Automates end-to-end marking with built-in plagiarism and moderation checks"
      },
      "typeVersion": 1
    },
    {
      "id": "6f935ec4-48a1-47cb-8a8f-2bb165914f79",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -576,
        688
      ],
      "parameters": {
        "width": 384,
        "height": 272,
        "content": "## Setup Steps\n1. Configure manual trigger and connect student answer and rubric data sources.\n2. Add OpenAI API credentials to all OpenAI Chat Model nodes.\n3. Define moderation thresholds in the Route by Moderation Decision rules node.\n4. Configure Slack credentials and set escalation alert channel.\n5. Set plagiarism sensitivity thresholds in the Plagiarism Analyser Agent node."
      },
      "typeVersion": 1
    },
    {
      "id": "54860cb7-23d6-4e9e-9597-a1822a42877c",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1296,
        688
      ],
      "parameters": {
        "width": 672,
        "height": 272,
        "content": "## How It Works\n\nThis workflow streamlines academic assessment through a multi-agent AI system that interprets rubrics, grades submissions, checks for plagiarism, performs quality moderation, generates feedback, and escalates borderline cases. Designed for educators and assessment administrators, it reduces inconsistencies in manual marking while embedding integrity checks into every evaluation cycle. A manual trigger retrieves student answers and rubrics, which are first structured before being sent to a Primary Marker Agent. If integrity concerns arise, a Plagiarism Analysis Agent runs in parallel. Results are consolidated and reviewed by a Quality Moderator Agent, followed by a Feedback Generator. Borderline cases are routed to a Secondary Marker Agent, while approved outcomes proceed to escalation preparation, Slack notifications, statistics computation, final consolidation, and logging in Google Sheets.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "e31fafed-bae9-42d5-8bd8-88181e79c41a",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        176,
        1600
      ],
      "parameters": {
        "color": 7,
        "width": 704,
        "height": 544,
        "content": "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n## Plagiarism Analyser Agent\n**What** \u2013 Checks submissions for integrity flags in parallel with marking.\n**Why** \u2013 Embeds academic integrity verification without slowing the marking pipeline."
      },
      "typeVersion": 1
    },
    {
      "id": "f409a244-2e3e-4c34-a8d3-8b1819c04197",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -400,
        1040
      ],
      "parameters": {
        "color": 7,
        "width": 544,
        "height": 768,
        "content": "## Primary Marker Agent\n**What** \u2013 Marks student answers against interpreted rubric criteria.\n**Why** \u2013 Delivers objective, criteria-aligned scoring at scale."
      },
      "typeVersion": 1
    },
    {
      "id": "420f3507-dadb-4d5c-a6a5-10350b12b102",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1312,
        1056
      ],
      "parameters": {
        "color": 7,
        "width": 896,
        "height": 848,
        "content": "## Trigger, Data Retrieval & Rubric Interpretation\n**What** \u2013 On manual trigger, fetches student answers and rubrics \n**Why** \u2013 Ensures marking begins with complete, validated inputs and applies consistent "
      },
      "typeVersion": 1
    },
    {
      "id": "137c7cca-ccd0-4dc8-b812-8c8ffdd3586a",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        544,
        688
      ],
      "parameters": {
        "color": 7,
        "width": 576,
        "height": 752,
        "content": "\n## Quality Moderator Agent\n**What** \u2013 Reviews marking quality and routes borderline cases for secondary marking.\n**Why** \u2013 Maintains assessment reliability and flags inconsistent or marginal scores."
      },
      "typeVersion": 1
    },
    {
      "id": "db18a757-5be1-414f-a9bf-02e6ee04f7c1",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1136,
        352
      ],
      "parameters": {
        "color": 7,
        "width": 1824,
        "height": 1008,
        "content": "## Feedback Generator & Escalation\n**What** \u2013 Generates student feedback, sends Slack alerts, and logs to Google Sheets.\n**Why** \u2013 Closes the assessment loop with actionable feedback and full audit records."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "a8906d68-92a6-43fa-93d9-e823dcecf12f",
  "connections": {
    "Manual Trigger": {
      "main": [
        [
          {
            "node": "Workflow Configuration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge All Paths": {
      "main": [
        [
          {
            "node": "Final Output Compilation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate Statistics": {
      "main": [
        [
          {
            "node": "Merge All Paths",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Primary Marker Agent": {
      "main": [
        [
          {
            "node": "Quality Moderator Agent",
            "type": "main",
            "index": 0
          },
          {
            "node": "Check Integrity Flags",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge Marking Results",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Check Integrity Flags": {
      "main": [
        [
          {
            "node": "Plagiarism Analyzer Agent",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Quality Moderator Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Marking Results": {
      "main": [
        [
          {
            "node": "Quality Moderator Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Escalation Alert": {
      "main": [
        [
          {
            "node": "Merge All Paths",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Secondary Marker Agent": {
      "main": [
        [
          {
            "node": "Merge Marking Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Workflow Configuration": {
      "main": [
        [
          {
            "node": "Retrieve Student Answer and Rubric",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Escalation Data": {
      "main": [
        [
          {
            "node": "Send Escalation Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Quality Moderator Agent": {
      "main": [
        [
          {
            "node": "Feedback Generator Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Feedback Generator Agent": {
      "main": [
        [
          {
            "node": "Route by Moderation Decision",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Final Output Compilation": {
      "main": [
        [
          {
            "node": "Log to Google Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Integrity Report": {
      "main": [
        [
          {
            "node": "Quality Moderator Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Rubric Interpreter Agent": {
      "main": [
        [
          {
            "node": "Primary Marker Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Plagiarism Analyzer Agent": {
      "main": [
        [
          {
            "node": "Prepare Integrity Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model - Marker": {
      "ai_languageModel": [
        [
          {
            "node": "Primary Marker Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model - Rubric": {
      "ai_languageModel": [
        [
          {
            "node": "Rubric Interpreter Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model - Feedback": {
      "ai_languageModel": [
        [
          {
            "node": "Feedback Generator Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Route by Moderation Decision": {
      "main": [
        [
          {
            "node": "Secondary Marker Agent",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Prepare Escalation Data",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Calculate Statistics",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model - Moderator": {
      "ai_languageModel": [
        [
          {
            "node": "Quality Moderator Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model - Secondary": {
      "ai_languageModel": [
        [
          {
            "node": "Secondary Marker Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model - Plagiarism": {
      "ai_languageModel": [
        [
          {
            "node": "Plagiarism Analyzer Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Structured Output Parser - Marker": {
      "ai_outputParser": [
        [
          {
            "node": "Primary Marker Agent",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Structured Output Parser - Rubric": {
      "ai_outputParser": [
        [
          {
            "node": "Rubric Interpreter Agent",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Retrieve Student Answer and Rubric": {
      "main": [
        [
          {
            "node": "Rubric Interpreter Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Structured Output Parser - Feedback": {
      "ai_outputParser": [
        [
          {
            "node": "Feedback Generator Agent",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Structured Output Parser - Moderator": {
      "ai_outputParser": [
        [
          {
            "node": "Quality Moderator Agent",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Structured Output Parser - Secondary": {
      "ai_outputParser": [
        [
          {
            "node": "Secondary Marker Agent",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Structured Output Parser - Plagiarism": {
      "ai_outputParser": [
        [
          {
            "node": "Plagiarism Analyzer Agent",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    }
  }
}