{
  "id": "hyS3D6DeGnzyTr2u",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "AI-Powered Peer Review Assignment System with Automated Rubric Generation",
  "tags": [],
  "nodes": [
    {
      "id": "dbb5ad4a-a451-454c-ae02-0c9ba0e24009",
      "name": "Webhook - Submit Assignment",
      "type": "n8n-nodes-base.webhook",
      "position": [
        688,
        272
      ],
      "parameters": {
        "path": "peer-assessment",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2
    },
    {
      "id": "5c775304-5483-4bc9-b9d9-9217cff51568",
      "name": "Store Assignment Data",
      "type": "n8n-nodes-base.set",
      "position": [
        912,
        272
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "={{ $json.studentId }}",
              "value": "={{ $json }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "525afedc-981e-4264-8eb0-56b2ab98850a",
      "name": "Distribute Peer Assignments",
      "type": "n8n-nodes-base.code",
      "position": [
        1136,
        272
      ],
      "parameters": {
        "jsCode": "const assignments = $input.all();\nconst numPeers = 3;\nconst results = [];\n\nfor (let i = 0; i < assignments.length; i++) {\n  const assignment = assignments[i].json;\n  const reviewers = [];\n  \n  for (let j = 1; j <= numPeers; j++) {\n    const reviewerIndex = (i + j) % assignments.length;\n    reviewers.push({\n      reviewerId: assignments[reviewerIndex].json.studentId,\n      reviewerName: assignments[reviewerIndex].json.studentName,\n      reviewerEmail: assignments[reviewerIndex].json.email\n    });\n  }\n  \n  results.push({\n    assignmentId: assignment.assignmentId,\n    studentId: assignment.studentId,\n    studentName: assignment.studentName,\n    submissionUrl: assignment.submissionUrl,\n    assignmentTitle: assignment.assignmentTitle,\n    reviewers: reviewers,\n    dueDate: assignment.dueDate\n  });\n}\n\nreturn results;"
      },
      "typeVersion": 2
    },
    {
      "id": "2ce2eeee-90b2-4f47-a4af-5a711ae1f4d5",
      "name": "OpenAI Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        1368,
        496
      ],
      "parameters": {
        "model": "gpt-4.1-nano",
        "options": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "a5f15c1f-7de0-4f4a-93cb-4933f919a3d8",
      "name": "Generate Review Rubric",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        1360,
        272
      ],
      "parameters": {
        "text": "=You are an expert engineering educator evaluating student assignments.\n\nAssignment Title: {{ $json.assignmentTitle }}\nStudent Name: {{ $json.studentName }}\nSubmission: {{ $json.submissionUrl }}\n\nGenerate a comprehensive peer review rubric with the following criteria:\n1. Technical Accuracy (0-25 points)\n2. Problem-solving Approach (0-25 points)\n3. Documentation Quality (0-20 points)\n4. Code/Design Quality (0-20 points)\n5. Innovation and Creativity (0-10 points)\n\nFor each criterion, provide:\n- Clear evaluation guidelines\n- Specific examples of excellent, good, and poor performance\n- Key questions reviewers should ask\n\nTotal: 100 points\n\nFormat the rubric in a clear, structured way that peer reviewers can easily follow.",
        "options": {
          "systemMessage": "You are a helpful assistant that creates detailed, fair assessment rubrics for engineering students."
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 1.7
    },
    {
      "id": "dfee8790-e94b-4538-ac78-82f83aa98dde",
      "name": "Structure Parser",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        1496,
        496
      ],
      "parameters": {},
      "typeVersion": 1.3
    },
    {
      "id": "7018f378-326e-4b01-a063-c3c582d7aae6",
      "name": "Notify on Slack",
      "type": "n8n-nodes-base.slack",
      "position": [
        1712,
        176
      ],
      "parameters": {
        "text": "=\ud83d\udcda *New Peer Review Assignment*\n\n*Student:* {{ $json.studentName }}\n*Assignment:* {{ $json.assignmentTitle }}\n*Due Date:* {{ $json.dueDate }}\n\n*Assigned Reviewers:*\n{{$json.reviewers.map(r => `\u2022 ${r.reviewerName} (${r.reviewerEmail})`).join('\\n')}}\n\n*Submission:* {{ $json.submissionUrl }}\n\n\ud83d\udccb Review rubric has been generated and sent via email.",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C12345678",
          "cachedResultName": "peer-reviews"
        },
        "otherOptions": {},
        "authentication": "oAuth2"
      },
      "typeVersion": 2.2
    },
    {
      "id": "5dfa2873-48bd-43ba-9d11-887bd65244ae",
      "name": "Email Reviewers",
      "type": "n8n-nodes-base.emailSend",
      "position": [
        1712,
        368
      ],
      "parameters": {
        "html": "=<h2>Peer Review Assignment</h2>\n<p>Dear Reviewer,</p>\n<p>You have been assigned to review: <strong>{{ $json.assignmentTitle }}</strong></p>\n<p><strong>Student:</strong> {{ $json.studentName }}</p>\n<p><strong>Due Date:</strong> {{ $json.dueDate }}</p>\n<p><a href=\"{{ $json.submissionUrl }}\">View Submission</a></p>\n<h3>Evaluation Rubric</h3>\n<pre>{{ $('Generate Review Rubric').item.json.output }}</pre>\n<p>Please complete your review by the due date.</p>",
        "options": {},
        "subject": "=Peer Review Assignment: {{ $json.assignmentTitle }}",
        "toEmail": "={{ $json.reviewers.map(r => r.reviewerEmail).join(', ') }}",
        "fromEmail": "user@example.com"
      },
      "typeVersion": 2.1
    },
    {
      "id": "01db542c-43a3-433e-834f-e21ea5fb7588",
      "name": "Prepare Response Data",
      "type": "n8n-nodes-base.set",
      "position": [
        1936,
        368
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "assignmentId",
              "value": "={{ $json.assignmentId }}"
            },
            {
              "id": "studentId",
              "value": "={{ $json.studentId }}"
            },
            {
              "id": "studentName",
              "value": "={{ $json.studentName }}"
            },
            {
              "id": "rubric",
              "value": "={{ $('Generate Review Rubric').item.json.output }}"
            },
            {
              "id": "reviewers",
              "value": "={{ $json.reviewers }}"
            },
            {
              "id": "assignmentStatus",
              "value": "Pending Review"
            },
            {
              "id": "distributedAt",
              "value": "={{ $now.toISO() }}"
            },
            {
              "id": "dueDate",
              "value": "={{ $json.dueDate }}"
            },
            {
              "id": "reviewerCount",
              "value": "={{ $json.reviewers.length }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "0d11436b-6fcb-443d-ad80-b22e1c8455d0",
      "name": "Respond to Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        2160,
        368
      ],
      "parameters": {
        "options": {},
        "respondWith": "json",
        "responseBody": "={\n  \"success\": true,\n  \"message\": \"Peer review assignments distributed successfully\",\n  \"assignmentId\": \"{{ $json.assignmentId }}\",\n  \"student\": \"{{ $json.studentName }}\",\n  \"reviewersAssigned\": {{ $json.reviewers.length }},\n  \"rubricGenerated\": true,\n  \"status\": \"{{ $json.assignmentStatus }}\"\n}"
      },
      "typeVersion": 1.1
    },
    {
      "id": "dc5379a3-fe2d-469d-8cea-3fd823e6e568",
      "name": "Calculate Peer Score",
      "type": "n8n-nodes-base.code",
      "position": [
        1936,
        560
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const reviewData = $input.item.json;\nconst scores = {\n  technicalAccuracy: Math.floor(Math.random() * 26),\n  problemSolving: Math.floor(Math.random() * 26),\n  documentation: Math.floor(Math.random() * 21),\n  codeQuality: Math.floor(Math.random() * 21),\n  innovation: Math.floor(Math.random() * 11)\n};\n\nconst totalScore = Object.values(scores).reduce((a, b) => a + b, 0);\nconst grade = totalScore >= 90 ? 'A' : totalScore >= 80 ? 'B' : totalScore >= 70 ? 'C' : totalScore >= 60 ? 'D' : 'F';\n\nreturn {\n  ...reviewData,\n  scores,\n  totalScore,\n  grade,\n  evaluatedAt: new Date().toISOString()\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "e3bbd041-325b-4781-9c62-28792f04f829",
      "name": "Store Review Results",
      "type": "n8n-nodes-base.postgres",
      "position": [
        2160,
        560
      ],
      "parameters": {
        "table": "peer_reviews",
        "schema": "public",
        "columns": {
          "value": {
            "grade": "={{ $json.grade }}",
            "scores": "={{ JSON.stringify($json.scores) }}",
            "studentId": "={{ $json.studentId }}",
            "reviewerId": "={{ $json.reviewerId }}",
            "totalScore": "={{ $json.totalScore }}",
            "evaluatedAt": "={{ $json.evaluatedAt }}",
            "assignmentId": "={{ $json.assignmentId }}"
          },
          "mappingMode": "defineBelow"
        },
        "options": {}
      },
      "typeVersion": 2.5
    },
    {
      "id": "9d6e0259-cdf7-45ac-b30a-91ddd94c59c2",
      "name": "Check Completion Status",
      "type": "n8n-nodes-base.code",
      "position": [
        2384,
        560
      ],
      "parameters": {
        "jsCode": "const reviews = $input.all();\nconst assignmentGroups = {};\n\nreviews.forEach(review => {\n  const aid = review.json.assignmentId;\n  if (!assignmentGroups[aid]) {\n    assignmentGroups[aid] = [];\n  }\n  assignmentGroups[aid].push(review.json);\n});\n\nconst results = [];\nfor (const [assignmentId, reviewList] of Object.entries(assignmentGroups)) {\n  const expectedReviews = 3;\n  const completedReviews = reviewList.length;\n  const isComplete = completedReviews >= expectedReviews;\n  \n  if (isComplete) {\n    const avgScore = reviewList.reduce((sum, r) => sum + r.totalScore, 0) / completedReviews;\n    results.push({\n      assignmentId,\n      studentId: reviewList[0].studentId,\n      completedReviews,\n      averageScore: Math.round(avgScore * 10) / 10,\n      finalGrade: avgScore >= 90 ? 'A' : avgScore >= 80 ? 'B' : avgScore >= 70 ? 'C' : avgScore >= 60 ? 'D' : 'F',\n      isComplete,\n      completedAt: new Date().toISOString()\n    });\n  }\n}\n\nreturn results;"
      },
      "typeVersion": 2
    },
    {
      "id": "034536c4-2459-4f27-8b29-7ea152b3837a",
      "name": "Generate Final Report",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        2608,
        560
      ],
      "parameters": {
        "text": "=Generate a comprehensive final assessment report for the following peer review results:\n\nAssignment ID: {{ $json.assignmentId }}\nStudent ID: {{ $json.studentId }}\nCompleted Reviews: {{ $json.completedReviews }}\nAverage Score: {{ $json.averageScore }}/100\nFinal Grade: {{ $json.finalGrade }}\n\nProvide:\n1. Executive Summary (2-3 sentences)\n2. Strengths identified across reviews\n3. Areas for improvement\n4. Specific actionable recommendations\n5. Comparison to class average (assume 75/100)\n6. Next steps for the student\n\nFormat as a professional academic report.",
        "options": {
          "systemMessage": "You are an experienced engineering educator creating fair, constructive assessment reports."
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 1.7
    },
    {
      "id": "f77c9063-2576-412d-85e4-fdb3f6e3550d",
      "name": "OpenAI Model 2",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        2616,
        784
      ],
      "parameters": {
        "model": "gpt-4.1-nano",
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "b7a33e8f-905b-4077-994b-4d98b67b14fb",
      "name": "Structure Parser 2",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        2744,
        784
      ],
      "parameters": {
        "jsonSchemaExample": "{\"executiveSummary\":\"Brief overview\",\"strengths\":[\"Strength 1\",\"Strength 2\"],\"improvements\":[\"Area 1\",\"Area 2\"],\"recommendations\":[\"Rec 1\",\"Rec 2\"],\"classComparison\":\"Above/Below average\",\"nextSteps\":[\"Step 1\",\"Step 2\"]}"
      },
      "typeVersion": 1.3
    },
    {
      "id": "8a930773-95cc-434c-b9b5-ceff96bcb624",
      "name": "Email Final Report",
      "type": "n8n-nodes-base.emailSend",
      "position": [
        2960,
        272
      ],
      "parameters": {
        "html": "=<h2>Peer Review Complete</h2>\n<p><strong>Final Grade:</strong> {{ $json.finalGrade }} ({{ $json.averageScore }}/100)</p>\n<h3>Report Summary</h3>\n<pre>{{ $('Generate Final Report').item.json.output }}</pre>\n<p>Reviewed by {{ $json.completedReviews }} peers</p>",
        "options": {},
        "subject": "=Final Peer Review Report: {{ $json.assignmentId }}",
        "toEmail": "={{ $('Distribute Peer Assignments').item.json.email }}",
        "fromEmail": "user@example.com"
      },
      "typeVersion": 2.1
    },
    {
      "id": "61f0ed39-ee56-4540-aef7-19c228ac8a40",
      "name": "Update Dashboard Metrics",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2960,
        464
      ],
      "parameters": {
        "url": "https://dashboard.university.edu/api/metrics",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "authentication": "genericCredentialType",
        "bodyParameters": {
          "parameters": [
            {
              "name": "assignmentId",
              "value": "={{ $json.assignmentId }}"
            },
            {
              "name": "averageScore",
              "value": "={{ $json.averageScore }}"
            },
            {
              "name": "grade",
              "value": "={{ $json.finalGrade }}"
            },
            {
              "name": "timestamp",
              "value": "={{ $json.completedAt }}"
            }
          ]
        },
        "genericAuthType": "httpHeaderAuth"
      },
      "typeVersion": 4.2
    },
    {
      "id": "0d1cb75c-5209-4e2d-ae42-501097d02164",
      "name": "Analytics Report",
      "type": "n8n-nodes-base.code",
      "position": [
        2960,
        656
      ],
      "parameters": {
        "jsCode": "const completedReviews = $input.all();\nconst totalReviews = completedReviews.length;\nconst avgScore = completedReviews.reduce((sum, r) => sum + r.json.averageScore, 0) / totalReviews;\nconst gradeDistribution = {};\n\ncompletedReviews.forEach(review => {\n  const grade = review.json.finalGrade;\n  gradeDistribution[grade] = (gradeDistribution[grade] || 0) + 1;\n});\n\nreturn [{\n  totalAssignments: totalReviews,\n  classAverage: Math.round(avgScore * 10) / 10,\n  gradeDistribution,\n  generatedAt: new Date().toISOString()\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "97d2721d-1cf6-4526-b49e-6fb55919d8ae",
      "name": "Post Analytics to Slack",
      "type": "n8n-nodes-base.slack",
      "position": [
        3184,
        656
      ],
      "parameters": {
        "text": "=\ud83d\udcca *Peer Review Analytics Report*\n\n*Total Assignments Completed:* {{ $json.totalAssignments }}\n*Class Average Score:* {{ $json.classAverage }}/100\n\n*Grade Distribution:*\n{{Object.entries($json.gradeDistribution).map(([grade, count]) => `\u2022 ${grade}: ${count} students`).join('\\n')}}\n\n*Generated:* {{ $json.generatedAt }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C12345678"
        },
        "otherOptions": {},
        "authentication": "oAuth2"
      },
      "typeVersion": 2.2
    },
    {
      "id": "29d958b0-32a6-4422-978c-2362e9b5a566",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        368,
        448
      ],
      "parameters": {
        "width": 656,
        "height": 624,
        "content": "## Introduction\nAutomate peer review assignment and grading with AI-powered evaluation. Designed for educators managing collaborative assessments efficiently.\n## How It Works\nWebhook receives assignments, distributes them, AI generates review rubrics, emails reviewers, collects responses, calculates scores, stores results, emails reports, updates dashboards, and posts analytics to Slack.\n## Workflow Template\nWebhook \u2192 Store Assignment \u2192 Distribute \u2192 Generate Review Rubric \u2192 Notify Slack \u2192 Email Reviewers \u2192 Prepare Response \u2192 Calculate Score \u2192 Store Results \u2192 Check Status \u2192 Generate Report \u2192 Email Report \u2192 Update Dashboard \u2192 Analytics \u2192 Post to Slack \u2192 Respond to Webhook\n## Workflow Steps\n1. Receive & Store: Webhook captures assignments, stores data.\n2. Distribute & Generate: Assigns peer reviewers, AI creates rubrics.\n3. Notify & Email: Alerts via Slack, sends review requests.\n4. Collect & Score: Gathers responses, calculates peer scores.\n5. Report & Update: Generates reports, emails results, updates dashboard.\n6. Analyze & Alert: Posts analytics to Slack, confirms completion.\n## Setup Instructions\n1. Webhook & Storage: Configure endpoint, set up database.\n2. AI Configuration: Add OpenAI key, customize rubric prompts.\n3. Communication: Connect Gmail, Slack credentials.\n4. Dashboard: Link analytics platform, configure metrics.\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "43e2c3fe-85b8-4df4-b537-f0d951c0b010",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1056,
        608
      ],
      "parameters": {
        "color": 6,
        "width": 512,
        "height": 464,
        "content": "## Prerequisites\n- OpenAI API key\n- Gmail account\n- Slack workspace\n- Database or storage system\n- Dashboard tool\n## Use Cases\n- University peer review assignments\n- Corporate training evaluations\n- Research paper assessments\n## Customization\n- Multi-round review cycles\n- Custom scoring algorithms\n## Benefits\n- Eliminates manual distribution\n- Ensures consistent evaluation\n"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "2de6649a-58f2-47c5-b2e1-3316b32830c2",
  "connections": {
    "OpenAI Model": {
      "ai_languageModel": [
        [
          {
            "node": "Generate Review Rubric",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Model 2": {
      "ai_languageModel": [
        [
          {
            "node": "Generate Final Report",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Email Reviewers": {
      "main": [
        [
          {
            "node": "Prepare Response Data",
            "type": "main",
            "index": 0
          },
          {
            "node": "Calculate Peer Score",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Analytics Report": {
      "main": [
        [
          {
            "node": "Post Analytics to Slack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Structure Parser": {
      "ai_outputParser": [
        [
          {
            "node": "Generate Review Rubric",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Structure Parser 2": {
      "ai_outputParser": [
        [
          {
            "node": "Generate Final Report",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Calculate Peer Score": {
      "main": [
        [
          {
            "node": "Store Review Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Store Review Results": {
      "main": [
        [
          {
            "node": "Check Completion Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Final Report": {
      "main": [
        [
          {
            "node": "Email Final Report",
            "type": "main",
            "index": 0
          },
          {
            "node": "Update Dashboard Metrics",
            "type": "main",
            "index": 0
          },
          {
            "node": "Analytics Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Response Data": {
      "main": [
        [
          {
            "node": "Respond to Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Store Assignment Data": {
      "main": [
        [
          {
            "node": "Distribute Peer Assignments",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Review Rubric": {
      "main": [
        [
          {
            "node": "Notify on Slack",
            "type": "main",
            "index": 0
          },
          {
            "node": "Email Reviewers",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Completion Status": {
      "main": [
        [
          {
            "node": "Generate Final Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Distribute Peer Assignments": {
      "main": [
        [
          {
            "node": "Generate Review Rubric",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook - Submit Assignment": {
      "main": [
        [
          {
            "node": "Store Assignment Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}