213 lines
9.0 KiB
Python
213 lines
9.0 KiB
Python
from sqlalchemy.ext.asyncio import AsyncSession
|
|
import os,logging,io
|
|
from datetime import datetime,timezone
|
|
from fastapi import HTTPException
|
|
from pypdf import PdfReader
|
|
from sqlalchemy import select
|
|
from sqlalchemy.orm import selectinload
|
|
from sqlmodel import true
|
|
from job.job_post.models import JobPosts
|
|
from job.job_post.serializers import serialize_job_post
|
|
from job.candidate.serializers import serialize_candidate_profile
|
|
from job.candidate.models import Notes
|
|
from job.notes.serializers import serialize_note
|
|
from inbox.models import Inbox_Messages,Inbox
|
|
from job.candidate.plugins import normalize_spaced_text
|
|
|
|
class FileRead:
|
|
def __init__(self,session:AsyncSession,filename=None,file=None):
|
|
self.session=session
|
|
self.filename=filename
|
|
self.file=file
|
|
|
|
async def read_file(self,file=None,filename=None):
|
|
try:
|
|
reader = PdfReader(io.BytesIO(self.file))
|
|
if reader.is_encrypted:
|
|
raise HTTPException(400, "PDF is password protected")
|
|
pages = [(page.extract_text() or "") for page in reader.pages]
|
|
return {
|
|
"filename": self.filename,
|
|
"num_pages": len(reader.pages),
|
|
"text": normalize_spaced_text("\n".join(pages)),
|
|
}
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
raise HTTPException(400, str(e))
|
|
|
|
async def match_inbox_cv(self,inbox_message_id):
|
|
from inbox.plugins import resolve_attachment_path
|
|
from inbox.tasks import match_inbox_message
|
|
|
|
row=await Inbox_Messages.get_inbox_message_by_id(self.session,inbox_message_id)
|
|
if not row:
|
|
raise HTTPException(status_code=404,detail="Message not found")
|
|
if not row.attachment or not row.file_path:
|
|
raise HTTPException(status_code=400,detail="your file isnt in the system")
|
|
|
|
found=None
|
|
for path_str in (p.strip() for p in row.file_path.split(",") if p.strip()):
|
|
path=resolve_attachment_path(path_str)
|
|
if path.is_file():
|
|
found=path
|
|
break
|
|
if found is None:
|
|
raise HTTPException(status_code=400,detail="your file isnt in the system")
|
|
|
|
created_at=datetime.now(timezone.utc).isoformat()
|
|
task=await match_inbox_message.kicker().with_labels(
|
|
created_at=created_at,
|
|
correlation_id=str(row.id),
|
|
queue="inbox",
|
|
).kiq(str(row.id),force=True)
|
|
|
|
file_name=(row.file_name or "").split(",")[0].strip() or found.name
|
|
return {
|
|
"queued":True,
|
|
"inbox_message_id":str(row.id),
|
|
"file_name":file_name,
|
|
"task_id":task.task_id,
|
|
}
|
|
# async def get_intention(self,input):
|
|
# try:
|
|
# get_subject=Inbox_Messages.candidate_x_inbox(self.session,self.candidate_id)
|
|
# get_file=
|
|
|
|
class CandidateView:
|
|
def __init__(self,session:AsyncSession):
|
|
self.session=session
|
|
|
|
async def get_candidate(self,user_id=None,limit=10,offset=0,search=None):
|
|
try:
|
|
detail=bool(user_id)
|
|
# Detail mode must see every application for the candidate, not one page.
|
|
fetch_limit=1000 if detail else limit
|
|
rows=await Inbox.get_candidate_profile(session=self.session,user_id=user_id,limit=fetch_limit,offset=offset,search=search)
|
|
if detail:
|
|
return await self.attach_profile_detail(rows)
|
|
return await self.attach_job_posts(rows)
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500,detail=str(e))
|
|
|
|
async def count_candidates(self,user_id=None,search=None):
|
|
try:
|
|
return await Inbox.count_candidate_profiles(session=self.session,user_id=user_id,search=search)
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500,detail=str(e))
|
|
|
|
async def update_candidate(self,user_id,payload):
|
|
try:
|
|
if not user_id:
|
|
raise HTTPException(status_code=400,detail="user_id is required")
|
|
fields={k:v for k,v in (payload or {}).items() if k in ("favorite","rating") and v is not None}
|
|
if not fields:
|
|
raise HTTPException(status_code=400,detail="favorite or rating is required")
|
|
links=await Inbox.get_candidate_profile(session=self.session,user_id=user_id,limit=100,offset=0)
|
|
records=links if isinstance(links,list) else ([links] if links else [])
|
|
if not records:
|
|
raise HTTPException(status_code=404,detail="Candidate not found")
|
|
for link in records:
|
|
await Inbox.update_inbox(self.session,link.id,fields)
|
|
refreshed=await Inbox.get_candidate_profile(session=self.session,user_id=user_id,limit=100,offset=0)
|
|
return await self.attach_profile_detail(refreshed)
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500,detail=str(e))
|
|
|
|
async def get_job_post_by_id(self,record_id,data=None):
|
|
"""Load full job_posts row and optionally append it onto a candidate payload."""
|
|
try:
|
|
job_post_data=await JobPosts.get_job_post_by_id(session=self.session,record_id=record_id)
|
|
if not job_post_data:
|
|
return None
|
|
payload=serialize_job_post(job_post_data)
|
|
if isinstance(data,dict):
|
|
data.setdefault("job_posts",[]).append(payload)
|
|
if data.get("recruiter") is None and payload.get("created_by_name"):
|
|
data["recruiter"]=payload.get("created_by_name")
|
|
data["recruiter_id"]=payload.get("created_by")
|
|
if data.get("job_title") is None and payload.get("title"):
|
|
data["job_title"]=payload.get("title")
|
|
return payload
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500,detail=str(e))
|
|
|
|
async def attach_job_posts(self,data):
|
|
"""Normalize list/single, serialize each record, attach full job_posts rows."""
|
|
single=not isinstance(data,list)
|
|
records=[data] if single else list(data or [])
|
|
enriched=[]
|
|
for record in records:
|
|
payload=serialize_candidate_profile(record)
|
|
payload["job_posts"]=[]
|
|
for job_id in payload.get("suggested_job_post_ids") or []:
|
|
await self.get_job_post_by_id(record_id=job_id,data=payload)
|
|
enriched.append(payload)
|
|
return enriched[0] if single else enriched
|
|
|
|
async def attach_profile_detail(self,data):
|
|
"""Detail mode: flatten child collections across every Inbox row for the candidate."""
|
|
single=not isinstance(data,list)
|
|
records=[data] if single else list(data or [])
|
|
if not records:
|
|
return {} if single else []
|
|
|
|
interviews=[]
|
|
activity=[]
|
|
feedback=[]
|
|
documents=[]
|
|
job_posts=[]
|
|
base=None
|
|
user_id=None
|
|
favorite=None
|
|
rating=None
|
|
for record in records:
|
|
payload=serialize_candidate_profile(record,detail=True)
|
|
if base is None:
|
|
base=payload
|
|
user_id=payload.get("user_id")
|
|
favorite=payload.get("favorite")
|
|
rating=payload.get("rating")
|
|
interviews.extend(payload.get("interviews") or [])
|
|
activity.extend(payload.get("activity") or [])
|
|
feedback.extend(payload.get("feedback") or [])
|
|
documents.extend(payload.get("documents") or [])
|
|
for job_id in payload.get("suggested_job_post_ids") or []:
|
|
await self.get_job_post_by_id(record_id=job_id,data=payload)
|
|
for jp in payload.get("job_posts") or []:
|
|
if not any(x.get("id")==jp.get("id") for x in job_posts):
|
|
job_posts.append(jp)
|
|
if base.get("recruiter") is None and payload.get("recruiter"):
|
|
base["recruiter"]=payload.get("recruiter")
|
|
base["recruiter_id"]=payload.get("recruiter_id")
|
|
if base.get("job_title") is None and payload.get("job_title"):
|
|
base["job_title"]=payload.get("job_title")
|
|
|
|
notes=[]
|
|
uid=Notes._as_uuid(user_id) if user_id else None
|
|
if uid is not None:
|
|
result=await self.session.execute(
|
|
select(Notes)
|
|
.options(selectinload(Notes.author))
|
|
.where(Notes.user_id==uid)
|
|
.order_by(Notes.created_at.desc())
|
|
)
|
|
notes=[serialize_note(r) for r in result.scalars().all()]
|
|
|
|
activity.sort(key=lambda r:(r.get("activity_date") or ""),reverse=True)
|
|
base["favorite"]=favorite
|
|
base["rating"]=rating
|
|
base["interviews"]=interviews
|
|
base["activity"]=activity
|
|
base["feedback"]=feedback
|
|
base["documents"]=documents
|
|
base["notes"]=notes
|
|
base["job_posts"]=job_posts or base.get("job_posts") or []
|
|
return base
|