Skip to main content

process_job_questions

Processes job applicant question answers after a Job Applicant document is inserted.

Function Signature

@frappe.whitelist()
def process_job_questions(doc, method)

Parameters

doc
Document
The Job Applicant document instance
method
string
The document event method name (“after_insert”)

Behavior

  1. Validates Input: Checks if custom_job_question_answers exists on the document
  2. Parses Answers: Converts JSON string of answers to Python dictionary
  3. Retrieves Question Set: Fetches the Job Question Set linked to the job opening
  4. Stores Individual Answers: Creates child table records for each question/answer pair
  5. Calculates Score: Computes applicant score based on “Yes” answers (0-10 scale)
  6. Saves Document: Updates the Job Applicant with answers and score

Scoring Algorithm

score_multiplier = 10 / len(qset.questions)
doc.custom_score = score_multiplier * total_answered
  • Each “Yes” answer contributes to the total score
  • Final score is normalized to a 0-10 scale
  • Example: If 3 out of 5 questions answered “Yes”, score = (10/5) * 3 = 6.0

Implementation

handlers.py
import json
import frappe

@frappe.whitelist()
def process_job_questions(doc, method):
    if not doc.custom_job_question_answers:
        return

    answers = json.loads(doc.custom_job_question_answers)

    job_opening = doc.job_title
    qset_name = frappe.db.get_value(
        "Job Opening",
        job_opening,
        "custom_job_question_set"
    )

    if not qset_name:
        return

    qset = frappe.get_doc("Job Question Set", qset_name)

    total_answered = 0

    for q in qset.questions:
        doc.append("custom_question_answers", {
            "question": q.question,
            "fieldname": q.fieldname,
            "answer": answers.get(q.fieldname),
            "job_opening": job_opening
        })

        if answers.get(q.fieldname) == "Yes":
            total_answered += 1


    score_multiplier = 10 / len(qset.questions)
    doc.custom_score = score_multiplier * total_answered

    doc.save(ignore_permissions=True)

validate

Validates that all required questions have been answered before saving a Job Applicant.

Function Signature

def validate(doc, method)

Parameters

doc
Document
The Job Applicant document being validated
method
string
The document event method name (“validate”)

Behavior

  1. Checks for Answers: Returns early if no answers provided
  2. Parses Answer Data: Converts JSON to dictionary
  3. Retrieves Question Set: Gets questions from the job opening
  4. Validates Required Fields: Ensures all required questions are answered
  5. Throws Error: Raises validation error with specific missing question

Validation Logic

  • Iterates through all questions in the Question Set
  • For each required question, checks if answer exists in submitted data
  • Throws user-friendly error message if validation fails

Implementation

handlers.py
def validate(doc, method):
    if not doc.custom_job_question_answers:
        return

    answers = json.loads(doc.custom_job_question_answers)

    qset_name = frappe.db.get_value(
        "Job Opening",
        doc.job_opening,
        "custom_job_question_set"
    )

    qset = frappe.get_doc("Job Question Set", qset_name)

    for q in qset.questions:
        if q.required and q.fieldname not in answers:
            frappe.throw(
                f"Missing answer for required question: {q.question}"
            )

Error Example

frappe.throw("Missing answer for required question: Do you have 5+ years experience?")

Usage in Hooks

These handlers are registered in hooks.py:
doc_events = {
  "Job Applicant": {
    "after_insert": "ion_career.handlers.process_job_questions"
  }
}
The validate function is not currently hooked but can be added to doc_events if needed:
"validate": "ion_career.handlers.validate"

Build docs developers (and LLMs) love