{
  "id": "8cmZ2C8vmHtA9wLl6uaML",
  "name": "Generate student tests from PDFs using AI and Google Docs",
  "tags": [],
  "nodes": [
    {
      "id": "9ad448d2-3e46-4370-a94a-c5660c2a011c",
      "name": "Form Trigger",
      "type": "n8n-nodes-base.formTrigger",
      "position": [
        112,
        448
      ],
      "parameters": {
        "options": {
          "appendAttribution": false
        },
        "formTitle": "AI Test Generator",
        "formFields": {
          "values": [
            {
              "fieldLabel": "Name",
              "requiredField": true
            },
            {
              "fieldLabel": "Subject",
              "requiredField": true
            },
            {
              "fieldLabel": "Grade",
              "placeholder": "e.g. 10th Grade",
              "requiredField": true
            },
            {
              "fieldType": "date",
              "fieldLabel": "Date",
              "requiredField": true
            },
            {
              "fieldType": "file",
              "fieldLabel": "Upload PDF",
              "requiredField": true,
              "acceptFileTypes": ".pdf"
            },
            {
              "fieldType": "email",
              "fieldLabel": "Email",
              "requiredField": true
            }
          ]
        },
        "formDescription": "Generate test questions from PDF materials"
      },
      "typeVersion": 2.5
    },
    {
      "id": "9f09d15c-af0f-4dc4-b001-02bad462747a",
      "name": "Extract PDF",
      "type": "n8n-nodes-base.extractFromFile",
      "position": [
        320,
        448
      ],
      "parameters": {
        "options": {},
        "operation": "pdf",
        "binaryPropertyName": "Upload_PDF"
      },
      "typeVersion": 1.1
    },
    {
      "id": "6cae75f2-f3a2-46a7-ae32-9cba52b7a691",
      "name": "AI Generate",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        496,
        448
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini"
        },
        "options": {
          "maxTokens": 3000,
          "textFormat": {
            "textOptions": {
              "type": "json_schema",
              "schema": "{\"type\":\"object\",\"properties\":{\"test_title\":{\"type\":\"string\"},\"questions\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"group\":{\"type\":\"string\",\"enum\":[\"A\",\"B\"]},\"number\":{\"type\":\"integer\"},\"type\":{\"type\":\"string\",\"enum\":[\"fill-in\",\"multiple-choice\",\"problem\"]},\"text\":{\"type\":\"string\"},\"options\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"correct_answer\":{\"type\":\"string\"},\"points\":{\"type\":\"integer\"}},\"required\":[\"group\",\"number\",\"type\",\"text\",\"options\",\"correct_answer\",\"points\"],\"additionalProperties\":false}},\"additionalProperties\":false},\"required\":[\"test_title\",\"questions\"],\"additionalProperties\":false}"
            }
          }
        },
        "responses": {
          "values": [
            {
              "role": "system",
              "content": "You are a professional test generation system for educational materials.\n\nGENERAL RULES:\n\n1. Language: Write in English only, using clear and professional language.\n\n2. Accuracy:\n   - Be professionally precise\n   - Do not invent data not present in the input material\n   - Do not repeat concepts across multiple questions\n\n3. Pedagogical Quality:\n   - Avoid questions requiring mere memorization of numbers\n   - Questions must test understanding, not rote memory\n   - Distractors (wrong answers) must be realistic and represent common student errors\n   - For problem-solving tasks, always specify units of measurement\n\nTEST STRUCTURE:\n\nGenerate 10 questions total, divided into two independent groups:\n\nGROUP A \u2013 5 questions\nGROUP B \u2013 5 questions\n\nFor EACH group:\n- 2 \"fill-in\" questions (easy \u2013 basic concepts)\n- 2 \"multiple-choice\" questions with 4 options (medium \u2013 understanding)\n- 1 \"problem\" question (hard \u2013 application of knowledge)\n\nQuestions in Group A and Group B must be substantially different.\n\nOUTPUT FORMAT:\n- Return ONLY valid JSON\n- Follow the predefined schema exactly\n- Do not add explanations or text outside JSON"
            },
            {
              "content": "={{ $json.text }}"
            }
          ]
        },
        "builtInTools": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "4ab7a9bc-c1e0-4428-a0e5-c3c5a47a2be7",
      "name": "Format Test",
      "type": "n8n-nodes-base.code",
      "position": [
        768,
        448
      ],
      "parameters": {
        "jsCode": "const ai = $input.first().json;\nconst qs = ai.output[0].content[0].text.questions;\nconst form = $('Form Trigger').item.json;\n\nconst h = `SUBJECT: ${form.Subject}\\nGRADE: ${form.Grade}\\nDATE: ${form.Date}\\nSTUDENT NAME: _________________________\\n\\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\\n\\n`;\n\nfunction fmt(q) {\n  let t = `${q.number}. ${q.text}\\n\\n`;\n  if (q.type === \"multiple-choice\" && q.options?.length === 4) {\n    t += `a) ${q.options[0]}\\nb) ${q.options[1]}\\nc) ${q.options[2]}\\nd) ${q.options[3]}\\n\\n`;\n  }\n  t += \"Answer:\\n______________________________\\n\";\n  if (q.type === \"problem\") t += \"Work:\\n______________________________\\n\";\n  t += `\\nPoints: ${q.points}\\n\\n`;\n  return t;\n}\n\nconst a = qs.filter(q => q.group === \"A\");\nconst b = qs.filter(q => q.group === \"B\");\n\nlet tA = h + `=== GROUP A ===\\n\\n`;\na.forEach(q => tA += fmt(q));\n\nlet tB = h + `=== GROUP B ===\\n\\n`;\nb.forEach(q => tB += fmt(q));\n\nreturn [{json: {textA: tA, textB: tB}}];"
      },
      "typeVersion": 2
    },
    {
      "id": "137926bb-065b-4612-ac7b-a4612bae4a5a",
      "name": "Create Doc",
      "type": "n8n-nodes-base.googleDocs",
      "position": [
        992,
        448
      ],
      "parameters": {
        "title": "={{ $('Form Trigger').item.json.Subject }} - {{ $('Form Trigger').item.json.Grade }} - {{ $('Form Trigger').item.json.Date }}"
      },
      "credentials": {
        "googleDocsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "35286471-c560-4175-a6b7-e1fa74444e15",
      "name": "Share Doc",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        1136,
        448
      ],
      "parameters": {
        "fileId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $json.id }}"
        },
        "options": {},
        "operation": "share",
        "permissionsUi": {
          "permissionsValues": {
            "role": "reader",
            "type": "anyone"
          }
        }
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 3
    },
    {
      "id": "fa6d3d38-6f02-48c7-aeaa-ae800affbe22",
      "name": "Insert Group A",
      "type": "n8n-nodes-base.googleDocs",
      "position": [
        1280,
        448
      ],
      "parameters": {
        "actionsUi": {
          "actionFields": [
            {
              "text": "={{ $('Format Test').item.json.textA }}",
              "action": "insert"
            }
          ]
        },
        "operation": "update",
        "documentURL": "={{ $('Create Doc').item.json.id }}"
      },
      "credentials": {
        "googleDocsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "43e1d9a2-8969-42bc-96c6-ba20b7d07a4e",
      "name": "Page Break",
      "type": "n8n-nodes-base.googleDocs",
      "position": [
        1424,
        448
      ],
      "parameters": {
        "actionsUi": {
          "actionFields": [
            {
              "action": "insert",
              "object": "pageBreak"
            }
          ]
        },
        "operation": "update",
        "documentURL": "={{ $('Create Doc').item.json.id }}"
      },
      "credentials": {
        "googleDocsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "65835ab4-9049-47c2-bdec-aab494584899",
      "name": "Insert Group B",
      "type": "n8n-nodes-base.googleDocs",
      "position": [
        1568,
        448
      ],
      "parameters": {
        "actionsUi": {
          "actionFields": [
            {
              "text": "={{ $('Format Test').item.json.textB }}",
              "action": "insert"
            }
          ]
        },
        "operation": "update",
        "documentURL": "={{ $('Create Doc').item.json.id }}"
      },
      "credentials": {
        "googleDocsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "618a02e0-1855-42c4-a208-6fdb08732839",
      "name": "Send Email",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1808,
        448
      ],
      "parameters": {
        "sendTo": "={{ $('Form Trigger').item.json.Email }}",
        "message": "<p>Dear {{ $('Form Trigger').item.json.Name }},</p><p>Your test has been generated!</p><p><strong>\ud83d\udcc4 Link:</strong><br><a href=\"https://docs.google.com/document/d/{{ $('Create Doc').item.json.id }}\">Open Document</a></p><p><strong>Details:</strong></p><ul><li>Subject: {{ $('Form Trigger').item.json.Subject }}</li><li>Grade: {{ $('Form Trigger').item.json.Grade }}</li><li>Questions: 10 (5 per group)</li></ul><p>\u2713 Two versions (Group A & B)<br>\u2713 Ready to print</p><hr><p><small>AI Test Generator</small></p>",
        "options": {},
        "subject": "={{ $('Form Trigger').item.json.Subject }} - Test Ready"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "53a169ac-2f10-4925-bf54-f52ec3dc9716",
      "name": "\ud83d\udcdd Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        64,
        -256
      ],
      "parameters": {
        "width": 420,
        "height": 476,
        "content": "## How it works\n\n1. **User submits form** with PDF teaching materials\n2. **PDF text extraction** pulls content from document  \n3. **AI generates questions** (10 total: 5 Group A, 5 Group B)\n4. **Google Doc created** with both test versions\n5. **Email sent** with document link\n\nTotal time: ~60 seconds\n\n## Setup steps\n\n1. Add OpenAI API key (for question generation)\n2. Add Google Docs OAuth2 (for document creation)\n3. Add Google Drive OAuth2 (for sharing)\n4. Add Gmail OAuth2 (for email notifications)\n5. (Optional) Set Google Drive folder ID in \"Create Doc\" node\n6. Test with sample PDF\n\n**Cost:** ~$0.002 per test (GPT-4o-mini)"
      },
      "typeVersion": 1
    },
    {
      "id": "12dc0854-1599-4af4-9d57-1e4260210a12",
      "name": "Group: Input",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        64,
        256
      ],
      "parameters": {
        "color": 7,
        "width": 388,
        "height": 404,
        "content": "## \ud83d\udcdd Form & PDF Processing\n\nCaptures user input and extracts text from PDF document."
      },
      "typeVersion": 1
    },
    {
      "id": "22f479f2-7881-4d9a-a8f8-30bdd041ca8f",
      "name": "Group: AI",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        480,
        256
      ],
      "parameters": {
        "color": 7,
        "width": 452,
        "height": 396,
        "content": "## \ud83e\udd16 AI Question Generation\n\nOpenAI generates 10 questions (5 per group) based on PDF content.\n\n**Model:** GPT-4o-mini  \n**Cost:** ~$0.002 per test"
      },
      "typeVersion": 1
    },
    {
      "id": "7e75915b-1c49-48fb-be90-7de4c8382361",
      "name": "Group: Docs",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        960,
        256
      ],
      "parameters": {
        "color": 7,
        "width": 756,
        "height": 388,
        "content": "## \ud83d\udcc4 Google Docs Output\n\nCreates formatted document with both test versions and auto-shares with link."
      },
      "typeVersion": 1
    },
    {
      "id": "851da681-c3b3-495a-b59d-e0b1077650a3",
      "name": "Group: Email",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1744,
        256
      ],
      "parameters": {
        "color": 7,
        "width": 260,
        "height": 388,
        "content": "## \u2709\ufe0f Email Notification\n\nSends professional email with document link."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "6780eb7b-1334-4560-a97b-a37d16069e2c",
  "connections": {
    "Share Doc": {
      "main": [
        [
          {
            "node": "Insert Group A",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Doc": {
      "main": [
        [
          {
            "node": "Share Doc",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Page Break": {
      "main": [
        [
          {
            "node": "Insert Group B",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Generate": {
      "main": [
        [
          {
            "node": "Format Test",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract PDF": {
      "main": [
        [
          {
            "node": "AI Generate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Test": {
      "main": [
        [
          {
            "node": "Create Doc",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Form Trigger": {
      "main": [
        [
          {
            "node": "Extract PDF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Insert Group A": {
      "main": [
        [
          {
            "node": "Page Break",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Insert Group B": {
      "main": [
        [
          {
            "node": "Send Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}