pull/29/head
parent
ddfbb00edf
commit
6f52436165
|
|
@ -25,10 +25,15 @@ class FormDataField(str, Enum):
|
|||
ENTRY_TIME = "entry_time"
|
||||
SCREENED_BY = "screened_by"
|
||||
NAME = "name"
|
||||
GENDER = "gender"
|
||||
DATE_OF_BIRTH = "date_of_birth"
|
||||
CNIC = "cnic"
|
||||
CGPA = "cgpa"
|
||||
HR_COMMENTS = "hr_comments"
|
||||
CANDIDATE_NUMBER = "candidate_number"
|
||||
CANDIDATE_EMAIL = "candidate_email"
|
||||
PROFILE_LINK = "profile_link"
|
||||
RESUME_LINK = "resume_link"
|
||||
AREA_OF_EXPERTISE = "area_of_expertise"
|
||||
REQUISITION_NUMBER = "requisition_number"
|
||||
POSITION_SUITABLE_FOR = "position_suitable_for"
|
||||
|
|
@ -37,9 +42,12 @@ class FormDataField(str, Enum):
|
|||
MARITAL_STATUS = "marital_status"
|
||||
DEGREE = "degree"
|
||||
UNIVERSITY = "university"
|
||||
UNIVERSITY_OTHER = "university_other"
|
||||
EXPERIENCE = "experience"
|
||||
EXPERIENCE_DETAILS = "experience_details"
|
||||
AREA_OF_RESIDENCE = "area_of_residence"
|
||||
RESIDING_CITY = "residing_city"
|
||||
RESIDING_COUNTRY = "residing_country"
|
||||
COMMUNICATION_SKILLS = "communication_skills"
|
||||
PREFERRED_TIMINGS = "preferred_timings"
|
||||
HO_AVAILABILITY = "ho_availability"
|
||||
|
|
@ -48,6 +56,7 @@ class FormDataField(str, Enum):
|
|||
NOTICE_PERIOD = "notice_period"
|
||||
CURRENT_SALARY = "current_salary"
|
||||
EXPECTED_SALARY = "expected_salary"
|
||||
DIRECTOR_POC_CATEGORY = "director_poc_category"
|
||||
PROS = "pros"
|
||||
CONS = "cons"
|
||||
|
||||
|
|
@ -66,14 +75,25 @@ HEADER_ALIASES: dict[FormDataField, tuple[str, ...]] = {
|
|||
FormDataField.NAME: (
|
||||
"candidate name", "full name", "name", "names", "candidate",
|
||||
),
|
||||
FormDataField.GENDER: ("gender", "sex"),
|
||||
FormDataField.DATE_OF_BIRTH: (
|
||||
"date of birth", "dob", "birth date", "birthday",
|
||||
),
|
||||
FormDataField.CNIC: (
|
||||
"national identification no", "national identification number",
|
||||
"cnic", "nic", "national id", "cnic no", "cnic number",
|
||||
),
|
||||
FormDataField.CGPA: ("cgpa", "gpa", "grade point average"),
|
||||
FormDataField.HR_COMMENTS: ("hr comments", "hr comment", "comments", "remarks"),
|
||||
FormDataField.CANDIDATE_NUMBER: (
|
||||
"candidate number", "contact number", "phone number", "phone", "mobile", "contact",
|
||||
),
|
||||
FormDataField.CANDIDATE_EMAIL: ("candidate email", "email", "email address"),
|
||||
FormDataField.PROFILE_LINK: (
|
||||
"profile link", "linkedin profile link", "cv link", "resume link",
|
||||
"drop your updated resume", "profile",
|
||||
"profile link", "linkedin profile link", "linkedin", "profile",
|
||||
),
|
||||
FormDataField.RESUME_LINK: (
|
||||
"drop your updated resume", "resume link", "cv link", "resume", "cv",
|
||||
),
|
||||
FormDataField.AREA_OF_EXPERTISE: (
|
||||
"area of expertise", "area of interest", "expertise",
|
||||
|
|
@ -93,12 +113,15 @@ HEADER_ALIASES: dict[FormDataField, tuple[str, ...]] = {
|
|||
"education", "educational degree", "degree", "qualification",
|
||||
),
|
||||
FormDataField.UNIVERSITY: ("university of graduation", "university", "institute", "college"),
|
||||
FormDataField.UNIVERSITY_OTHER: (
|
||||
"if your university is not listed above, please specify its name",
|
||||
"university other", "other university", "specify university",
|
||||
),
|
||||
FormDataField.EXPERIENCE: ("experience", "total experience", "years of experience", "exp"),
|
||||
FormDataField.EXPERIENCE_DETAILS: ("experience details", "experience detail"),
|
||||
FormDataField.AREA_OF_RESIDENCE: (
|
||||
"area of residence", "residing city", "residing country",
|
||||
"residence", "location", "address",
|
||||
),
|
||||
FormDataField.AREA_OF_RESIDENCE: ("area of residence", "residence", "location", "address"),
|
||||
FormDataField.RESIDING_CITY: ("residing city", "city"),
|
||||
FormDataField.RESIDING_COUNTRY: ("residing country", "country"),
|
||||
FormDataField.COMMUNICATION_SKILLS: ("communication skills", "communication"),
|
||||
FormDataField.PREFERRED_TIMINGS: ("preferred timings", "preferred timing", "shift"),
|
||||
FormDataField.HO_AVAILABILITY: (
|
||||
|
|
@ -113,6 +136,10 @@ HEADER_ALIASES: dict[FormDataField, tuple[str, ...]] = {
|
|||
),
|
||||
FormDataField.CURRENT_SALARY: ("current salary", "present salary", "salary"),
|
||||
FormDataField.EXPECTED_SALARY: ("expected salary", "salary expectation", "expected"),
|
||||
FormDataField.DIRECTOR_POC_CATEGORY: (
|
||||
"director / poc / category", "director poc category",
|
||||
"director / poc", "poc / category",
|
||||
),
|
||||
FormDataField.PROS: ("pros", "strengths"),
|
||||
FormDataField.CONS: ("cons", "weaknesses"),
|
||||
}
|
||||
|
|
@ -152,10 +179,15 @@ class FormDataColumn(str, Enum):
|
|||
ENTRY_TIME = "entry_time"
|
||||
SCREENED_BY = "screened_by"
|
||||
NAME = "name"
|
||||
GENDER = "gender"
|
||||
DATE_OF_BIRTH = "date_of_birth"
|
||||
CNIC = "cnic"
|
||||
CGPA = "cgpa"
|
||||
HR_COMMENTS = "hr_comments"
|
||||
CANDIDATE_NUMBER = "candidate_number"
|
||||
CANDIDATE_EMAIL = "candidate_email"
|
||||
PROFILE_LINK = "profile_link"
|
||||
RESUME_LINK = "resume_link"
|
||||
AREA_OF_EXPERTISE = "area_of_expertise"
|
||||
REQUISITION_NUMBER = "requisition_number"
|
||||
POSITION_SUITABLE_FOR = "position_suitable_for"
|
||||
|
|
@ -165,9 +197,12 @@ class FormDataColumn(str, Enum):
|
|||
MARITAL_STATUS = "marital_status"
|
||||
DEGREE = "degree"
|
||||
UNIVERSITY = "university"
|
||||
UNIVERSITY_OTHER = "university_other"
|
||||
EXPERIENCE = "experience"
|
||||
EXPERIENCE_DETAILS = "experience_details"
|
||||
AREA_OF_RESIDENCE = "area_of_residence"
|
||||
RESIDING_CITY = "residing_city"
|
||||
RESIDING_COUNTRY = "residing_country"
|
||||
COMMUNICATION_SKILLS = "communication_skills"
|
||||
PREFERRED_TIMINGS = "preferred_timings"
|
||||
HO_AVAILABILITY = "ho_availability"
|
||||
|
|
@ -178,6 +213,7 @@ class FormDataColumn(str, Enum):
|
|||
CURRENT_SALARY_VALUE = "current_salary_value"
|
||||
EXPECTED_SALARY = "expected_salary"
|
||||
EXPECTED_SALARY_VALUE = "expected_salary_value"
|
||||
DIRECTOR_POC_CATEGORY = "director_poc_category"
|
||||
PROS = "pros"
|
||||
CONS = "cons"
|
||||
RAW_RECORD = "raw_record"
|
||||
|
|
|
|||
|
|
@ -39,10 +39,15 @@ class FormData(SQLModel, table=True):
|
|||
entry_time: str | None = Field(default=None)
|
||||
screened_by: str | None = Field(default=None, index=True)
|
||||
name: str | None = Field(default=None, index=True)
|
||||
gender: str | None = Field(default=None)
|
||||
date_of_birth: datetime | None = Field(default=None, sa_type=DateTime(timezone=True))
|
||||
cnic: str | None = Field(default=None, index=True)
|
||||
cgpa: str | None = Field(default=None)
|
||||
hr_comments: str | None = Field(default=None)
|
||||
candidate_number: str | None = Field(default=None)
|
||||
candidate_email: str | None = Field(default=None, index=True)
|
||||
profile_link: str | None = Field(default=None)
|
||||
resume_link: str | None = Field(default=None)
|
||||
area_of_expertise: str | None = Field(default=None)
|
||||
requisition_number: str | None = Field(default=None, index=True)
|
||||
position_suitable_for: str | None = Field(default=None)
|
||||
|
|
@ -52,9 +57,12 @@ class FormData(SQLModel, table=True):
|
|||
marital_status: str | None = Field(default=None)
|
||||
degree: str | None = Field(default=None)
|
||||
university: str | None = Field(default=None)
|
||||
university_other: str | None = Field(default=None)
|
||||
experience: str | None = Field(default=None)
|
||||
experience_details: str | None = Field(default=None)
|
||||
area_of_residence: str | None = Field(default=None)
|
||||
residing_city: str | None = Field(default=None)
|
||||
residing_country: str | None = Field(default=None)
|
||||
communication_skills: int | None = Field(default=None)
|
||||
preferred_timings: str | None = Field(default=None)
|
||||
ho_availability: str | None = Field(default=None)
|
||||
|
|
@ -65,6 +73,7 @@ class FormData(SQLModel, table=True):
|
|||
current_salary_value: int | None = Field(default=None)
|
||||
expected_salary: str | None = Field(default=None)
|
||||
expected_salary_value: int | None = Field(default=None)
|
||||
director_poc_category: str | None = Field(default=None)
|
||||
pros: str | None = Field(default=None)
|
||||
cons: str | None = Field(default=None)
|
||||
|
||||
|
|
@ -96,6 +105,8 @@ class FormData(SQLModel, table=True):
|
|||
cls.position_suitable_for.ilike(pattern),
|
||||
cls.area_of_expertise.ilike(pattern),
|
||||
cls.source_of_application.ilike(pattern),
|
||||
cls.cnic.ilike(pattern),
|
||||
cls.residing_city.ilike(pattern),
|
||||
))
|
||||
return filters
|
||||
|
||||
|
|
|
|||
|
|
@ -256,7 +256,7 @@ _CURRENCY_STRIP_RE=re.compile(r"(?:rs\.?|pkr|inr|usd|\$|€|£)",re.I)
|
|||
|
||||
# Every typed column key the mapper must emit (uniform dicts for bulk insert).
|
||||
_FORM_DATA_COLUMN_KEYS=tuple(field.value for field in FormDataField)+(
|
||||
"age_raw","current_salary_value","expected_salary_value",
|
||||
"age_raw","current_salary_value","expected_salary_value","job_post_id",
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -453,6 +453,8 @@ def map_record_to_form_data(sheet,record,headers,row_number):
|
|||
mapped["entry_date"]=dt
|
||||
if tm and not mapped.get("entry_time"):
|
||||
mapped["entry_time"]=tm
|
||||
elif field==FormDataField.DATE_OF_BIRTH:
|
||||
mapped["date_of_birth"]=parse_date(value)
|
||||
elif field==FormDataField.COMMUNICATION_SKILLS:
|
||||
mapped["communication_skills"]=parse_score(value)
|
||||
elif field==FormDataField.CURRENT_SALARY:
|
||||
|
|
|
|||
|
|
@ -220,8 +220,11 @@ FORM_RESPONSE_HEADERS = [
|
|||
"Email",
|
||||
"Full Name",
|
||||
"Gender",
|
||||
"Date of Birth",
|
||||
"Marital Status",
|
||||
"CGPA",
|
||||
"University",
|
||||
"If your university is not listed above, please specify its name.",
|
||||
"Educational Degree",
|
||||
"Year of Graduation",
|
||||
"Position Applied For",
|
||||
|
|
@ -234,6 +237,8 @@ FORM_RESPONSE_HEADERS = [
|
|||
"Residing Country",
|
||||
"Area of Interest",
|
||||
"Recruiter",
|
||||
"Director / POC / Category",
|
||||
"National Identification No. (42000-XXXXXXX-X)",
|
||||
"Where did you hear about the position you're applying for?",
|
||||
]
|
||||
|
||||
|
|
@ -242,8 +247,11 @@ FORM_RESPONSE_RECORD = {
|
|||
"Email": "nusratazra@gmail.com",
|
||||
"Full Name": "Nusrat Azra",
|
||||
"Gender": "Female",
|
||||
"Date of Birth": "7/24/1984",
|
||||
"Marital Status": "Single",
|
||||
"CGPA": "3.5",
|
||||
"University": "Karachi University",
|
||||
"If your university is not listed above, please specify its name.": "",
|
||||
"Educational Degree": "Masters",
|
||||
"Year of Graduation": "12/2/2007",
|
||||
"Position Applied For": "Executive Secretary",
|
||||
|
|
@ -256,11 +264,14 @@ FORM_RESPONSE_RECORD = {
|
|||
"Residing Country": "Pakistan",
|
||||
"Area of Interest": "",
|
||||
"Recruiter": "",
|
||||
"Director / POC / Category": "Operations",
|
||||
"National Identification No. (42000-XXXXXXX-X)": "4250105627772",
|
||||
"Where did you hear about the position you're applying for?": "Indeed",
|
||||
}
|
||||
|
||||
|
||||
def test_form_response_headers_map_to_typed_columns():
|
||||
assert plugins.collect_unmapped_headers(FORM_RESPONSE_HEADERS) == []
|
||||
mapped = plugins.map_record_to_form_data(
|
||||
"Form Responses - Candidate Database Sheet 2026",
|
||||
FORM_RESPONSE_RECORD,
|
||||
|
|
@ -270,6 +281,10 @@ def test_form_response_headers_map_to_typed_columns():
|
|||
assert mapped["name"] == "Nusrat Azra"
|
||||
assert mapped["candidate_email"] == "nusratazra@gmail.com"
|
||||
assert mapped["candidate_number"] == "03312464228"
|
||||
assert mapped["gender"] == "Female"
|
||||
assert mapped["date_of_birth"] == datetime(1984, 7, 24, tzinfo=timezone.utc)
|
||||
assert mapped["cnic"] == "4250105627772"
|
||||
assert mapped["cgpa"] == "3.5"
|
||||
assert mapped["degree"] == "Masters"
|
||||
assert mapped["university"] == "Karachi University"
|
||||
assert mapped["position_suitable_for"] == "Executive Secretary"
|
||||
|
|
@ -277,13 +292,16 @@ def test_form_response_headers_map_to_typed_columns():
|
|||
assert mapped["source_of_application"] == "Indeed"
|
||||
assert mapped["ho_availability"] == "Yes"
|
||||
assert mapped["marital_status"] == "Single"
|
||||
assert mapped["area_of_residence"] == "Karachi" # first matching residence header wins
|
||||
assert mapped["residing_city"] == "Karachi"
|
||||
assert mapped["residing_country"] == "Pakistan"
|
||||
assert mapped["director_poc_category"] == "Operations"
|
||||
assert mapped["profile_link"] == (
|
||||
"https://www.linkedin.com/in/nusrat-azra-executive-manager-to-c-suite-3bb86719/"
|
||||
)
|
||||
assert mapped["resume_link"] == "https://drive.google.com/open?id=1l5HOY5R6KiL6270A_sV56FkdX6EIGfI4"
|
||||
assert mapped["entry_year"] == "12/2/2007"
|
||||
assert mapped["entry_date"] is not None
|
||||
assert mapped["entry_time"] == "17:50:19"
|
||||
assert mapped["entry_time"] == "17:50"
|
||||
# Timestamp must never be used as the candidate name.
|
||||
assert mapped["name"] != "7/2/2026 17:50:19"
|
||||
assert mapped["job_post_id"] is None
|
||||
|
|
|
|||
Loading…
Reference in New Issue