HR-ATS-Portal/backend/inbox/views.py

162 lines
6.8 KiB
Python

import logging
import httpx,os
from fastapi import HTTPException
from inbox.models import Inbox_Messages
from inbox.file_decoder import decode_attachment, AttachmentDecodeError
from inbox.serializers import serialize_message
from inbox.plugins import load_message_files, extract_resume_text
from dotenv import load_dotenv
load_dotenv()
from sqlalchemy.ext.asyncio import AsyncSession
from pydantic import BaseModel
from db_setup import session_scope
from job.job_post.models import JobPosts
from job.job_post.serializers import serialize_job_post
logger = logging.getLogger("inbox.match")
class Email:
def __init__(self,session:AsyncSession,token=None):
self.session=session
self.get_url=os.getenv("EMAIL_URL")
self.token=token
self.pending_match_ids: list[str] = []
@staticmethod
async def run_inbox_matching(inbox_ids: list[str], *, force: bool = False) -> None:
"""Background worker: extract CV text, run the matching agent, persist results.
Uses its own session_scope per message - never the request session.
"""
if not inbox_ids:
return
# Local import so inbox routes still load when langgraph/openai are missing.
from agent.execute_agent import run_agent
cached_posts = None
for record_id in inbox_ids:
try:
async with session_scope() as session:
row = await Inbox_Messages.get_inbox_message_by_id(session, record_id)
if not row:
continue
if not force and row.match_status is not None:
continue
paths = [p.strip() for p in (row.file_path or "").split(",") if p.strip()]
text, extract_err = await extract_resume_text(paths)
if not text:
await Inbox_Messages.set_match_result(
session,
record_id,
status="no_text",
error=extract_err or "no text extracted",
)
continue
if cached_posts is None:
posts = await JobPosts.get_active_job_posts(session)
cached_posts = [serialize_job_post(p) for p in posts]
result = await run_agent(
subject=row.message_subject or "",
resume_text=text,
job_posts=cached_posts,
)
await Inbox_Messages.set_match_result(
session,
record_id,
resume_text=text,
suggested_job_post_ids=result.get("suggested_job_post_ids") or [],
summary=result.get("summary") or "",
reasoning=result.get("reasoning") or "",
status=result.get("status") or "failed",
error=result.get("error") or "",
)
logger.info(
"matched inbox %s status=%s ids=%s",
record_id,
result.get("status"),
result.get("suggested_job_post_ids"),
)
except Exception as exc:
logger.exception("inbox matching failed for %s", record_id)
try:
async with session_scope() as session:
await Inbox_Messages.set_match_result(
session, record_id, status="failed", error=str(exc)
)
except Exception:
logger.exception("failed to persist match failure for %s", record_id)
async def service_email(self,top,skip):
async with httpx.AsyncClient() as client:
try:
response=await client.get(f"{self.get_url}/emails",
params={"skip":skip,"top":top},
headers={"Authorization":f"Bearer {self.token}"}
)
if response.status_code==200:
return response.json()
else:
raise HTTPException(status_code=response.status_code,detail=response.text)
except Exception as e:
raise HTTPException(status_code=500,detail=str(e))
async def get_email_by_id(self,message_id):
async with httpx.AsyncClient() as client:
try:
response=await client.get(f"{self.get_url}/emails/{message_id}",
headers={"Authorization":f"Bearer {self.token}"}
)
if response.status_code==200:
data=response.json()
re_create_file=await decode_attachment(data.get("attachments"))
insert_func=await Inbox_Messages.insert_email(session=self.session,email_data=data,file_path=re_create_file)
if (
insert_func.attachment
and insert_func.file_path
and insert_func.match_status is None
):
self.pending_match_ids.append(str(insert_func.id))
return response.json()
else:
raise HTTPException(status_code=response.status_code,detail=response.text)
except Exception as e:
raise HTTPException(status_code=500,detail=str(e))
async def get_inbox_messages(self,top,skip,search=None):
messages=await Inbox_Messages.get_inbox_messages(self.session,top,skip,search)
items=[]
for m in messages:
item=serialize_message(m)
files=load_message_files(m)
if files:
item["files"]=files
items.append(item)
return items
async def get_inbox_message_by_id(self,record_id):
message=await Inbox_Messages.get_inbox_message_by_id(self.session,record_id)
if not message:
raise HTTPException(status_code=404,detail="Message not found")
item=serialize_message(message)
files=load_message_files(message)
if files:
item["files"]=files
return item
async def queue_rematch(self,record_id):
message=await Inbox_Messages.get_inbox_message_by_id(self.session,record_id)
if not message:
raise HTTPException(status_code=404,detail="Message not found")
if not message.attachment or not message.file_path:
raise HTTPException(status_code=400,detail="Message has no attachment to match")
return str(message.id)
async def count_inbox_messages(self,search=None):
return await Inbox_Messages.count_inbox_messages(self.session,search)