forms done

Edition_Fomrs
ahmed.mujtaba 2026-09-11 18:37:16 +05:00
parent 80a3b290b9
commit a021d60fc3
5 changed files with 55 additions and 12 deletions

View File

@ -16,10 +16,14 @@ class Position(BaseModel):
date_needed:Optional[date] date_needed:Optional[date]
type:Optional[EmploymentType] type:Optional[EmploymentType]
job_description:Optional[str] job_description:Optional[str]
period_from:Optional[date]=None
period_to:Optional[date]=None
jd_available:Optional[bool]=None
class InternalRecommendate(BaseModel): class InternalRecommendate(BaseModel):
employee_name:Optional[str]=None employee_name:Optional[str]=None
employee_department:Optional[str]=None employee_department:Optional[str]=None
entity:Optional[str]=None
class ReplacementFor(BaseModel): class ReplacementFor(BaseModel):
to_replace:Optional[str] to_replace:Optional[str]

View File

@ -34,9 +34,13 @@ class Requisition(SQLModel, table=True):
), ),
) )
job_description: Optional[str] = None job_description: Optional[str] = None
period_from: Optional[Date] = None
period_to: Optional[Date] = None
jd_available: Optional[bool] = None
employee_name: Optional[str] = None employee_name: Optional[str] = None
employee_department: Optional[str] = None employee_department: Optional[str] = None
entity: Optional[str] = None
to_replace: Optional[str] = None to_replace: Optional[str] = None
grade: Optional[str] = None grade: Optional[str] = None
@ -139,8 +143,12 @@ class Requisition(SQLModel, table=True):
date_needed=position.get("date_needed") if position.get("date_needed") else None, date_needed=position.get("date_needed") if position.get("date_needed") else None,
employment_type=EmploymentType(position.get("type")) if position.get("type") else None, employment_type=EmploymentType(position.get("type")) if position.get("type") else None,
job_description=position.get("job_description") if position.get("job_description") else None, job_description=position.get("job_description") if position.get("job_description") else None,
period_from=position.get("period_from") if position.get("period_from") else None,
period_to=position.get("period_to") if position.get("period_to") else None,
jd_available=position.get("jd_available") if position.get("jd_available") is not None else None,
employee_name=referral.get("employee_name") if referral.get("employee_name") else None, employee_name=referral.get("employee_name") if referral.get("employee_name") else None,
employee_department=referral.get("employee_department") if referral.get("employee_department") else None, employee_department=referral.get("employee_department") if referral.get("employee_department") else None,
entity=referral.get("entity") if referral.get("entity") else None,
to_replace=replacement.get("to_replace") if replacement.get("to_replace") else None, to_replace=replacement.get("to_replace") if replacement.get("to_replace") else None,
grade=replacement.get("grade") if replacement.get("grade") else None, grade=replacement.get("grade") if replacement.get("grade") else None,
recruitment_title=replacement.get("title") if replacement.get("title") else None, recruitment_title=replacement.get("title") if replacement.get("title") else None,
@ -183,6 +191,12 @@ class Requisition(SQLModel, table=True):
row.employment_type = EmploymentType(position.get("type")) if position.get("type") else None row.employment_type = EmploymentType(position.get("type")) if position.get("type") else None
if "job_description" in position: if "job_description" in position:
row.job_description = position.get("job_description") if position.get("job_description") else None row.job_description = position.get("job_description") if position.get("job_description") else None
if "period_from" in position:
row.period_from = position.get("period_from") if position.get("period_from") else None
if "period_to" in position:
row.period_to = position.get("period_to") if position.get("period_to") else None
if "jd_available" in position:
row.jd_available = position.get("jd_available") if position.get("jd_available") is not None else None
if "replacement_for" in fields: if "replacement_for" in fields:
replacement = fields.get("replacement_for") if fields.get("replacement_for") else {} replacement = fields.get("replacement_for") if fields.get("replacement_for") else {}
if "to_replace" in replacement: if "to_replace" in replacement:
@ -205,6 +219,8 @@ class Requisition(SQLModel, table=True):
row.employee_name = referral.get("employee_name") if referral.get("employee_name") else None row.employee_name = referral.get("employee_name") if referral.get("employee_name") else None
if "employee_department" in referral: if "employee_department" in referral:
row.employee_department = referral.get("employee_department") if referral.get("employee_department") else None row.employee_department = referral.get("employee_department") if referral.get("employee_department") else None
if "entity" in referral:
row.entity = referral.get("entity") if referral.get("entity") else None
if "initiated_by" in fields: if "initiated_by" in fields:
row.initiated_by = fields.get("initiated_by") if fields.get("initiated_by") else None row.initiated_by = fields.get("initiated_by") if fields.get("initiated_by") else None
if "initiated_date" in fields: if "initiated_date" in fields:

View File

@ -80,7 +80,8 @@ FORM_DEFINITIONS = {
"criteria": [ "criteria": [
{"key": "core_job_knowledge", "label": "Core Job Knowledge & Domain Expertise"}, {"key": "core_job_knowledge", "label": "Core Job Knowledge & Domain Expertise"},
{"key": "relevant_experience", "label": "Depth of Relevant Experience"}, {"key": "relevant_experience", "label": "Depth of Relevant Experience"},
{"key": "problem_solving", "label": "Problem Solving & Analytical Reasoning"}, {"key": "problem_solving", "label": "Problem Solving"},
{"key": "analytical_reasoning", "label": "Analytical Reasoning"},
{"key": "tools_proficiency", "label": "Technical Tools & Systems Proficiency"}, {"key": "tools_proficiency", "label": "Technical Tools & Systems Proficiency"},
{"key": "quality_of_work", "label": "Quality of Work & Attention to Detail"}, {"key": "quality_of_work", "label": "Quality of Work & Attention to Detail"},
], ],
@ -109,27 +110,41 @@ FORM_DEFINITIONS = {
), ),
"has_recommendation": True, "has_recommendation": True,
}, },
# form_type/section/field keys below stay "cultural_fit"/"cultural"/"cultural_note" —
# renamed labels only. Titles and criterion labels are denormalized into every
# saved row at write time (see module docstring), so historical rows keep the
# "Cultural Fit" wording they were saved under while new rows pick up the fuller
# revision 2 "HR Evaluation" section below; the key stays stable so old rows keep
# validating and combined_summary()'s "cultural" lookup keeps matching both.
"cultural_fit": { "cultural_fit": {
"title": "Cultural Fit", "title": "HR Evaluation",
"source": "Annexure E - Interview Evaluation Form", "source": "Annexure E - Interview Evaluation Form",
"scale_note": RATING_SCALE_NOTE, "scale_note": RATING_SCALE_NOTE,
"sections": [ "sections": [
{ {
"key": "cultural", "key": "cultural",
"title": "CULTURAL FIT", "title": "HR EVALUATION",
"average_label": "CULTURAL FIT SECTION", "average_label": "HR EVALUATION SECTION",
"criteria": [ "criteria": [
{"key": "company_values", "label": "Alignment with Company Values"}, {"key": "basic_jd_requirement", "label": "Basic JD requirement"},
{"key": "company_values", "label": "Alignment with Company Culture"},
{"key": "professionalism", "label": "Professionalism & Integrity"}, {"key": "professionalism", "label": "Professionalism & Integrity"},
{"key": "collaboration", "label": "Collaboration & Team Orientation"}, {"key": "collaboration", "label": "Collaboration & Team Orientation"},
{"key": "adaptability", "label": "Adaptability to Change"}, {"key": "adaptability", "label": "Adaptability"},
{"key": "work_ethic", "label": "Work Ethic & Reliability"}, {"key": "agility", "label": "Agility"},
{"key": "work_ethic", "label": "Work Ethics"},
{"key": "communication_articulation", "label": "Communication & Articulation"},
{"key": "problem_solving_orientation", "label": "Problem Solving & Solution Orientation"},
{"key": "critical_thinking", "label": "Critical Thinking & Analytical Capability"},
{"key": "initiative", "label": "Initiative & Proactiveness"},
{"key": "decision_making", "label": "Decision Making"},
{"key": "leadership", "label": "Leadership"},
], ],
}, },
], ],
"fields": ( "fields": (
_EVALUATION_HEADER_FIELDS _EVALUATION_HEADER_FIELDS
+ [{"key": "cultural_note", "label": "Cultural Fit — Notes", "kind": "text"}] + [{"key": "cultural_note", "label": "HR Evaluation — Notes", "kind": "text"}]
+ _EVALUATION_FOOTER_FIELDS + _EVALUATION_FOOTER_FIELDS
), ),
"has_recommendation": True, "has_recommendation": True,
@ -174,6 +189,7 @@ FORM_DEFINITIONS = {
{"key": "internal_recommendation", "label": "INCASE OF INTERNAL RECOMMENDATE", "kind": "bool"}, {"key": "internal_recommendation", "label": "INCASE OF INTERNAL RECOMMENDATE", "kind": "bool"},
{"key": "recommended_employee_name", "label": "EMPLOYEE NAME", "kind": "text"}, {"key": "recommended_employee_name", "label": "EMPLOYEE NAME", "kind": "text"},
{"key": "recommended_employee_department", "label": "EMPLOYEE DEPARTMENT", "kind": "text"}, {"key": "recommended_employee_department", "label": "EMPLOYEE DEPARTMENT", "kind": "text"},
{"key": "entity", "label": "Entity", "kind": "text"},
{"key": "initiated_by", "label": "Initiated By — Name", "kind": "text"}, {"key": "initiated_by", "label": "Initiated By — Name", "kind": "text"},
{"key": "initiated_date", "label": "Initiated By — Date", "kind": "date"}, {"key": "initiated_date", "label": "Initiated By — Date", "kind": "date"},
{"key": "recommended_by", "label": "Recommended By — Name (Director)", "kind": "text"}, {"key": "recommended_by", "label": "Recommended By — Name (Director)", "kind": "text"},
@ -354,7 +370,10 @@ def combined_summary(rows):
`rows` are candidate_forms records (attribute access: form_type, created_at, `rows` are candidate_forms records (attribute access: form_type, created_at,
sections). The latest interview_analysis row supplies the technical and sections). The latest interview_analysis row supplies the technical and
behavioral averages, the latest cultural_fit row the cultural average. behavioral averages, the latest cultural_fit row the "cultural" section
average cultural_fit's own section carries the fuller HR Evaluation
criteria as of revision 2, but the key stays "cultural" so this lookup
(and the `cultural_avg` key below) don't need to change with it.
The combined overall (mean of the three section averages, 2 dp, already The combined overall (mean of the three section averages, 2 dp, already
ranged onto 0100) appears only once all three exist. Returns None when ranged onto 0100) appears only once all three exist. Returns None when
neither evaluation exists. Legacy 14 section averages are converted neither evaluation exists. Legacy 14 section averages are converted

View File

@ -85,6 +85,9 @@ def serialize_requisition(row) -> dict:
"date_needed": _date(row.date_needed), "date_needed": _date(row.date_needed),
"type": _enum(row.employment_type), "type": _enum(row.employment_type),
"job_description": row.job_description, "job_description": row.job_description,
"period_from": _date(row.period_from),
"period_to": _date(row.period_to),
"jd_available": row.jd_available,
}, },
"replacement_for": { "replacement_for": {
"to_replace": row.to_replace, "to_replace": row.to_replace,
@ -98,6 +101,7 @@ def serialize_requisition(row) -> dict:
"refferal_by": { "refferal_by": {
"employee_name": row.employee_name, "employee_name": row.employee_name,
"employee_department": row.employee_department, "employee_department": row.employee_department,
"entity": row.entity,
}, },
"initiated_by": row.initiated_by, "initiated_by": row.initiated_by,
"initiated_date": _date(row.initiated_date), "initiated_date": _date(row.initiated_date),

View File

@ -52,7 +52,7 @@ class TestNormalizeSections:
normalized, overall = normalize_sections("interview_analysis", []) normalized, overall = normalize_sections("interview_analysis", [])
assert [s["key"] for s in normalized] == ["technical", "behavioral"] assert [s["key"] for s in normalized] == ["technical", "behavioral"]
technical = normalized[0] technical = normalized[0]
assert len(technical["criteria"]) == 5 assert len(technical["criteria"]) == 6
assert technical["criteria"][0]["label"] == "Core Job Knowledge & Domain Expertise" assert technical["criteria"][0]["label"] == "Core Job Knowledge & Domain Expertise"
assert technical["average"] is None assert technical["average"] is None
assert overall is None assert overall is None
@ -149,8 +149,8 @@ class TestDefinitions:
def test_paper_parity_criterion_counts(self): def test_paper_parity_criterion_counts(self):
ia = FORM_DEFINITIONS["interview_analysis"] ia = FORM_DEFINITIONS["interview_analysis"]
cf = FORM_DEFINITIONS["cultural_fit"] cf = FORM_DEFINITIONS["cultural_fit"]
assert [len(s["criteria"]) for s in ia["sections"]] == [5, 5] assert [len(s["criteria"]) for s in ia["sections"]] == [6, 5]
assert [len(s["criteria"]) for s in cf["sections"]] == [5] assert [len(s["criteria"]) for s in cf["sections"]] == [13]
def test_stage_gate_vocabulary(self): def test_stage_gate_vocabulary(self):
assert set(FORM_READY_STATUSES) == {"INTERVIEW", "OFFER", "HIRED", "APPROVED"} assert set(FORM_READY_STATUSES) == {"INTERVIEW", "OFFER", "HIRED", "APPROVED"}