create a basic PDF template for a job application - this will be generated and populated by a background process, and should be the final 'report' style output for the hiring manager and interviewer:
/************************************************
* Enhanced Applicant → Structured PDF → Email
* Added: Test Mode (last submission) & Manual Mode (all submissions)
************************************************/
const MyApp = (function () {
const CONFIG = {
FORM_ID_PROP: "FORM_ID", // Script Property key for Form ID
RECIPIENT: "hiring@quiles.studio",
OPENAI_KEY: "OPENAI_API_KEY", // Script Property key for OpenAI API key
OPENAI_URL: "https://api.openai.com/v1/chat/completions",
MODEL: "gpt-4",
SYSTEM_MSG: "You are an expert HR assistant. Summarize applicant info professionally."
};
/**
* Retrieve the Form ID from Script Properties
*/
function getFormId() {
const formId = PropertiesService
.getScriptProperties()
.getProperty(CONFIG.FORM_ID_PROP);
if (!formId) throw new Error(`Missing property: ${CONFIG.FORM_ID_PROP}`);
return formId;
}
/**
* Build a flat text block of Q/A pairs for summarization
*/
function buildRawText(map) {
return Object.entries(map)
.map(([q, a]) => `• ${q}: ${a}`)
.join("\n\n");
}
/**
* Generate a JSON-formatted summary via OpenAI
*/
function generateSummary(rawText) {
const prompt = `\n${CONFIG.SYSTEM_MSG}\n\nPlease read the applicant details below and produce a bullet-point summary under these headings:\n\nPersonal Info \nCertifications \nWork History \nBackground & Skills \nProjects & Portfolio \nOnline Presence \n\nUse 1–3 bullets per section. Output valid JSON only:\n{\n \"Personal Info\": [...],\n \"Certifications\": [...],\n \"Work History\": [...],\n \"Background & Skills\": [...],\n \"Projects & Portfolio\": [...],\n \"Online Presence\": [...]\n}\n\nApplicant details:\n${rawText}\n`;
const apiKey = PropertiesService
.getScriptProperties()
.getProperty(CONFIG.OPENAI_KEY);
if (!apiKey) throw new Error(`Missing property: ${CONFIG.OPENAI_KEY}`);
const response = UrlFetchApp.fetch(CONFIG.OPENAI_URL, {
method: "post",
contentType: "application/json",
headers: { Authorization: `Bearer ${apiKey}` },
payload: JSON.stringify({
model: CONFIG.MODEL,
messages: [
{ role: "system", content: CONFIG.SYSTEM_MSG },
{ role: "user", content: prompt }
],
temperature: 0.2
})
});
const data = JSON.parse(response.getContentText());
return data.choices?.[0]?.message?.content.trim() || "{}";
}
/**
* Transform a FormResponse into a Q->A map
*/
function buildMapping(response) {
const map = {};
response.getItemResponses().forEach(ir => {
let ans = ir.getResponse();
if (Array.isArray(ans)) ans = ans.join(", ");
map[ir.getItem().getTitle().trim()] = ans;
});
return map;
}
/**
* Create a Google Doc containing the summary and full Q/A
*/
function createDoc(name, jsonSummary, mapping) {
const doc = DocumentApp.create(`Application – ${name}`);
const body = doc.getBody();
body.appendParagraph("📝 Structured Applicant Summary")
.setHeading(DocumentApp.ParagraphHeading.HEADING1);
body.appendParagraph(jsonSummary)
.setFontFamily("Courier New")
.setSpacingAfter(20);
body.appendParagraph("📋 Full Question & Answer")
.setHeading(DocumentApp.ParagraphHeading.HEADING2);
Object.entries(mapping).forEach(([q,a]) => {
body.appendParagraph(q).setBold(true);
body.appendParagraph(a).setIndentStart(18);
body.appendParagraph("");
});
doc.saveAndClose();
return doc.getId();
}
/**
* Process a single FormResponse: summarize, PDF, and email
*/
function processResponse(response) {
const mapping = buildMapping(response);
const name = mapping["Please enter your full name "] || "Unknown";
const rawText = buildRawText(mapping);
const summary = generateSummary(rawText);
const docId = createDoc(name, summary, mapping);
// Convert to PDF and email
const pdfBlob = DriveApp.getFileById(docId)
.getAs(MimeType.PDF)
.setName(`App_${name.replace(/\s+/g, "_")}.pdf`);
const dateStr = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "MM/dd/yyyy");
GmailApp.sendEmail(
CONFIG.RECIPIENT,
`New Application – ${name} (${dateStr})`,
"Please find attached the structured summary.",
{ attachments: [pdfBlob] }
);
// Clean up
DriveApp.getFileById(docId).setTrashed(true);
}
// Expose public methods
return {
processResponse,
getFormId
};
})();
/**
* Form Submit trigger: runs on each new submission
*/
function onFormSubmit(e) {
if (e?.response) {
MyApp.processResponse(e.response);
} else {
Logger.log("onFormSubmit: no response data");
}
}
/**
* Test Mode: process only the most recent submission
*/
function testLastSubmission() {
try {
const formId = MyApp.getFormId();
const form = FormApp.openById(formId);
const responses = form.getResponses();
if (!responses.length) {
Logger.log("testLastSubmission: no form responses.");
return;
}
MyApp.processResponse(responses.pop());
} catch (err) {
Logger.log(`testLastSubmission error: ${err}`);
}
}
/**
* Manual Mode: process ALL existing submissions
*/
function runAllSubmissions() {
try {
const formId = MyApp.getFormId();
const form = FormApp.openById(formId);
const responses = form.getResponses();
if (!responses.length) {
Logger.log("runAllSubmissions: no form responses.");
return;
}
responses.forEach(r => MyApp.processResponse(r));
} catch (err) {
Logger.log(`runAllSubmissions error: ${err}`);
}
}