HR-ATS-Portal/backend/job/candidate/serializers.py

223 lines
9.6 KiB
Python

from inbox.models import Inbox
from typing import Any,List,Dict
from job.candidate.plugins import documents_from_message, source_from_message_to
def _first_file_path(value):
if not value:
return None
return str(value).split(",")[0].strip() or None
from job.interviews.serializers import serialize_interview
from job.activity.serializers import serialize_activity
from job.feedback.serializers import serialize_feedback
from job.job_post.serializers import serialize_job_post
def serialize_candidate(row) -> dict:
return {
"id": str(row.id),
"job_id": str(row.job_id),
"source": row.source,
"filename": row.filename,
"file_path": row.file_path,
"content_sha256": row.content_sha256,
"candidate_email": row.candidate_email,
"candidate_name": row.candidate_name,
"job_title": row.job_title,
"current_company": row.current_company,
"years_experience": row.years_experience,
"match_score": row.match_score,
"matched_keywords": list(row.matched_keywords or []),
"missing_keywords": list(row.missing_keywords or []),
"summary_critique": row.summary_critique,
"linkedin_url": row.linkedin_url or None,
"status": row.status,
"error_code": row.error_code,
"error_message": row.error_message,
"model": row.model,
"created_by": str(row.created_by),
"created_at": row.created_at.isoformat() if row.created_at else None,
"updated_at": row.updated_at.isoformat() if row.updated_at else None,
}
def serialize_matching_candidate(row, job_post=None) -> Dict[str,Any]:
"""CV-bank origin row for Job Matching. assigned_job_post_id is job_posts.id."""
name=(row.candidate_name or "").strip() or (row.candidate_email or "").strip() or (row.file_name or "").strip() or "Unknown"
job_payload=serialize_job_post(job_post) if job_post else None
return {
"id":str(row.id),
"name":name,
"email":(row.candidate_email or "").strip() or None,
"file_name":(row.file_name or "").strip() or None,
"file_path":(row.file_path or "").strip() or None,
"resume_text":row.full_text or None,
"linkedin_url":row.linkedin_url or None,
"apply_via":row.apply_via,
"status":row.status or None,
"user_id":str(row.user_id) if row.user_id else None,
"assigned_job_post_id":str(row.job_post_id) if row.job_post_id else None,
"assigned_job_post":job_payload,
"created_at":row.created_at.isoformat() if row.created_at else None,
"updated_at":row.updated_at.isoformat() if row.updated_at else None,
}
def serialize_manual_upload_candidate(row) -> Dict[str,Any]:
return {
"id":str(row.id) if row.id else None,
"candidate_email":row.candidate_email,
"candidate_name":row.candidate_name,
"candidate_phone":row.candidate_phone,
"job_post_id":str(row.job_post_id) if row.job_post_id else None,
"full_text":row.full_text,
"linkedin_url":row.linkedin_url or None,
"current_company":row.current_company,
"current_position":row.current_position,
"apply_via":row.apply_via,
"user_id":str(row.user_id) if row.user_id else None,
"platform":row.platform,
"created_by":str(row.created_by) if row.created_by else None,
"experience":row.experience,
"status":row.status,
"referral_by":row.referral_by,
"file_name":row.file_name,
"file_path":row.file_path,
"created_at":row.created_at.isoformat() if row.created_at else None,
"updated_at":row.updated_at.isoformat() if row.updated_at else None,
}
def serialize_candidate_profile(
link:Inbox|List[Inbox]|Dict[str,Any]|List[Dict[str,Any]],
*,
detail:bool=False,
) -> Dict[str,Any]|List[Dict[str,Any]]:
if isinstance(link,list):
return [serialize_candidate_profile(item,detail=detail) for item in link]
if isinstance(link,dict):
return link
user = link.user
message = link.messages
payload = {
"inbox_id": link.id,
"manual_upload_candidate_id": None,
"user_id": str(link.user_id) if link.user_id else None,
"candidate_id": None,
"name": user.name if user else None,
"email": user.email if user else None,
"linkedin_url": (user.linkedin_url if user else None) or None,
"is_active": user.is_active if user else None,
"message_id": str(link.message_id) if link.message_id else None,
"created_at": link.created_at.isoformat() if link.created_at else None,
"application_status": message.application_status if message else None,
"experience": message.experience if message else None,
"current_employment": message.current_employment if message else None,
"current_title": message.current_title if message else None,
"resume_text": message.resume_text if message else None,
"suggested_job_post_ids": list(message.suggested_job_post_ids or []) if message else [],
"assigned_job_post_id": str(message.assigned_job_post_id) if message and message.assigned_job_post_id else None,
"match_summary": message.match_summary if message else None,
"match_reasoning": message.match_reasoning if message else None,
"match_status": message.match_status if message else None,
"match_error": message.match_error if message else None,
"matched_at": message.matched_at.isoformat() if message and message.matched_at else None,
"file_path": _first_file_path(message.file_path if message else None),
"job_posts": [],
}
if not detail:
return payload
payload.update({
"favorite": link.favorite,
"rating": link.rating,
"phone": message.candidate_phone_number if message else None,
"education": message.candidate_education if message else None,
"currentCompany": message.current_employment if message else None,
"current_title": message.current_title if message else None,
"stage": message.application_status if message else None,
"source": source_from_message_to(message.message_to if message else None),
"applied": message.message_received_time if message else None,
"documents": documents_from_message(
message.file_name if message else None,
message.file_path if message else None,
),
"recruiter": None,
"recruiter_id": None,
"job_title": None,
"ai_score": None,
"recommendation": None,
"sub_scores": None,
"interviews": [serialize_interview(r) for r in (link.interviews or [])],
"activity": [serialize_activity(r) for r in (link.activity or [])],
"feedback": [serialize_feedback(r) for r in (link.feedback or [])],
"notes": [],
})
return payload
def serialize_manual_candidate_profile(row, user, job_post) -> Dict[str, Any]:
"""Same key vocabulary as serialize_candidate_profile(detail=True).
Manual uploads never go through inbox, so current_position maps onto
current_title here and the agent/ATS fields stay empty.
"""
job_payload = serialize_job_post(job_post) if job_post else None
created = row.created_at.isoformat() if row.created_at else None
file_name = (row.file_name or "").strip() or None
file_path = (row.file_path or "").strip() or None
documents = [{"name": file_name or "", "path": file_path or ""}] if (file_name or file_path) else []
company = (row.current_company or "").strip() or None
position = (row.current_position or "").strip() or None
return {
"inbox_id": None,
"manual_upload_candidate_id": str(row.id),
"user_id": str(user.id) if user else (str(row.user_id) if row.user_id else None),
"candidate_id": None,
"name": (user.name if user else None) or row.candidate_name or None,
"email": (user.email if user else None) or row.candidate_email or None,
"linkedin_url": (user.linkedin_url if user else None) or row.linkedin_url or None,
"is_active": user.is_active if user else None,
"message_id": None,
"created_at": created,
"application_status": row.status or None,
"experience": (row.experience or "").strip() or None,
"current_employment": company,
"current_title": position,
"resume_text": row.full_text or None,
"suggested_job_post_ids": [],
"assigned_job_post_id": str(row.job_post_id) if row.job_post_id else None,
"match_summary": None,
"match_reasoning": None,
"match_status": None,
"match_error": None,
"matched_at": None,
"job_posts": [job_payload] if job_payload else [],
"favorite": None,
"rating": None,
"phone": (row.candidate_phone or "").strip() or None,
"education": None,
"currentCompany": company,
"stage": row.status or None,
"source": (row.platform or "").strip() or None,
"applied": created,
"file_path": file_path,
"documents": documents,
"recruiter": job_payload.get("created_by_name") if job_payload else None,
"recruiter_id": job_payload.get("created_by") if job_payload else None,
"job_title": job_payload.get("title") if job_payload else None,
"ai_score": None,
"recommendation": None,
"sub_scores": None,
"interviews": [],
"activity": [],
"feedback": [],
"notes": [],
"assigned_job_post": job_payload,
"matched_keywords": [],
"missing_keywords": [],
"summary_critique": None,
"scored_at": None,
}