Added reports router
This commit is contained in:
parent
c5068562c4
commit
87135f5c60
88
app/repositories/interview_reports_repository.py
Normal file
88
app/repositories/interview_reports_repository.py
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
from fastapi import Depends
|
||||||
|
from sqlalchemy import select, update
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from app.core.database import get_session
|
||||||
|
from app.models.interview_report import InterviewReport
|
||||||
|
from app.models.interview import InterviewSession
|
||||||
|
from app.models.resume import Resume
|
||||||
|
from app.models.vacancy import Vacancy
|
||||||
|
from app.repositories.base_repository import BaseRepository
|
||||||
|
|
||||||
|
|
||||||
|
class InterviewReportRepository(BaseRepository[InterviewReport]):
|
||||||
|
def __init__(self, session: Annotated[AsyncSession, Depends(get_session)]):
|
||||||
|
super().__init__(InterviewReport, session)
|
||||||
|
|
||||||
|
async def get_by_session_id(self, session_id: int) -> InterviewReport | None:
|
||||||
|
"""Получить отчёт по ID сессии интервью"""
|
||||||
|
statement = select(InterviewReport).where(
|
||||||
|
InterviewReport.interview_session_id == session_id
|
||||||
|
)
|
||||||
|
result = await self._session.execute(statement)
|
||||||
|
return result.scalar_one_or_none()
|
||||||
|
|
||||||
|
async def update_scores(
|
||||||
|
self,
|
||||||
|
report_id: int,
|
||||||
|
scores: dict,
|
||||||
|
) -> bool:
|
||||||
|
"""Обновить оценки в отчёте"""
|
||||||
|
try:
|
||||||
|
await self._session.execute(
|
||||||
|
update(InterviewReport)
|
||||||
|
.where(InterviewReport.id == report_id)
|
||||||
|
.values(
|
||||||
|
**scores,
|
||||||
|
updated_at=datetime.utcnow(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
await self._session.commit()
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
await self._session.rollback()
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def update_pdf_url(self, report_id: int, pdf_url: str) -> bool:
|
||||||
|
"""Обновить ссылку на PDF отчёта"""
|
||||||
|
try:
|
||||||
|
await self._session.execute(
|
||||||
|
update(InterviewReport)
|
||||||
|
.where(InterviewReport.id == report_id)
|
||||||
|
.values(pdf_report_url=pdf_url, updated_at=datetime.utcnow())
|
||||||
|
)
|
||||||
|
await self._session.commit()
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
await self._session.rollback()
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def get_by_vacancy_id(self, vacancy_id: int) -> list[InterviewReport]:
|
||||||
|
"""Получить все отчёты по вакансии"""
|
||||||
|
statement = (
|
||||||
|
select(InterviewReport)
|
||||||
|
.join(InterviewSession, InterviewSession.id == InterviewReport.interview_session_id)
|
||||||
|
.join(Resume, Resume.id == InterviewSession.resume_id)
|
||||||
|
.join(Vacancy, Vacancy.id == Resume.vacancy_id)
|
||||||
|
.where(Vacancy.id == vacancy_id)
|
||||||
|
.order_by(InterviewReport.overall_score.desc())
|
||||||
|
)
|
||||||
|
result = await self._session.execute(statement)
|
||||||
|
return result.scalars().all()
|
||||||
|
|
||||||
|
async def update_notes(self, report_id: int, notes: str) -> bool:
|
||||||
|
"""Обновить заметки интервьюера"""
|
||||||
|
try:
|
||||||
|
await self._session.execute(
|
||||||
|
update(InterviewReport)
|
||||||
|
.where(InterviewReport.id == report_id)
|
||||||
|
.values(interviewer_notes=notes, updated_at=datetime.utcnow())
|
||||||
|
)
|
||||||
|
await self._session.commit()
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
await self._session.rollback()
|
||||||
|
return False
|
113
app/routers/interview_reports_router.py
Normal file
113
app/routers/interview_reports_router.py
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
from fastapi import APIRouter, Depends, HTTPException, Request
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
from app.core.session_middleware import get_current_session
|
||||||
|
from app.models.session import Session
|
||||||
|
from app.models.interview_report import InterviewReport
|
||||||
|
from app.services.interview_reports_service import InterviewReportService
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/interview-reports", tags=["interview-reports"])
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/vacancy/{vacancy_id}", response_model=List[InterviewReport])
|
||||||
|
async def get_reports_by_vacancy(
|
||||||
|
vacancy_id: int,
|
||||||
|
current_session: Session = Depends(get_current_session),
|
||||||
|
report_service: InterviewReportService = Depends(InterviewReportService),
|
||||||
|
):
|
||||||
|
"""Получить все отчёты по вакансии"""
|
||||||
|
if not current_session:
|
||||||
|
raise HTTPException(status_code=401, detail="No active session")
|
||||||
|
|
||||||
|
reports = await report_service.get_reports_by_vacancy(vacancy_id)
|
||||||
|
return reports
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/session/{session_id}", response_model=InterviewReport)
|
||||||
|
async def get_report_by_session(
|
||||||
|
session_id: int,
|
||||||
|
current_session: Session = Depends(get_current_session),
|
||||||
|
report_service: InterviewReportService = Depends(InterviewReportService),
|
||||||
|
):
|
||||||
|
"""Получить отчёт по сессии интервью"""
|
||||||
|
if not current_session:
|
||||||
|
raise HTTPException(status_code=401, detail="No active session")
|
||||||
|
|
||||||
|
report = await report_service.get_report_by_session(session_id)
|
||||||
|
if not report:
|
||||||
|
raise HTTPException(status_code=404, detail="Report not found")
|
||||||
|
|
||||||
|
return report
|
||||||
|
|
||||||
|
|
||||||
|
@router.patch("/{report_id}/scores")
|
||||||
|
async def update_report_scores(
|
||||||
|
report_id: int,
|
||||||
|
scores: dict,
|
||||||
|
current_session: Session = Depends(get_current_session),
|
||||||
|
report_service: InterviewReportService = Depends(InterviewReportService),
|
||||||
|
):
|
||||||
|
"""Обновить оценки отчёта"""
|
||||||
|
if not current_session:
|
||||||
|
raise HTTPException(status_code=401, detail="No active session")
|
||||||
|
|
||||||
|
success = await report_service.update_report_scores(report_id, scores)
|
||||||
|
if not success:
|
||||||
|
raise HTTPException(status_code=500, detail="Failed to update report scores")
|
||||||
|
|
||||||
|
return {"message": "Report scores updated successfully"}
|
||||||
|
|
||||||
|
|
||||||
|
@router.patch("/{report_id}/notes")
|
||||||
|
async def update_report_notes(
|
||||||
|
report_id: int,
|
||||||
|
notes: str,
|
||||||
|
current_session: Session = Depends(get_current_session),
|
||||||
|
report_service: InterviewReportService = Depends(InterviewReportService),
|
||||||
|
):
|
||||||
|
"""Обновить заметки интервьюера"""
|
||||||
|
if not current_session:
|
||||||
|
raise HTTPException(status_code=401, detail="No active session")
|
||||||
|
|
||||||
|
success = await report_service.update_interviewer_notes(report_id, notes)
|
||||||
|
if not success:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=500, detail="Failed to update interviewer notes"
|
||||||
|
)
|
||||||
|
|
||||||
|
return {"message": "Interviewer notes updated successfully"}
|
||||||
|
|
||||||
|
|
||||||
|
@router.patch("/{report_id}/pdf")
|
||||||
|
async def update_report_pdf(
|
||||||
|
report_id: int,
|
||||||
|
pdf_url: str,
|
||||||
|
current_session: Session = Depends(get_current_session),
|
||||||
|
report_service: InterviewReportService = Depends(InterviewReportService),
|
||||||
|
):
|
||||||
|
"""Обновить PDF отчёта"""
|
||||||
|
if not current_session:
|
||||||
|
raise HTTPException(status_code=401, detail="No active session")
|
||||||
|
|
||||||
|
success = await report_service.update_pdf_url(report_id, pdf_url)
|
||||||
|
if not success:
|
||||||
|
raise HTTPException(status_code=500, detail="Failed to update PDF URL")
|
||||||
|
|
||||||
|
return {"message": "PDF URL updated successfully"}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/create")
|
||||||
|
async def create_report(
|
||||||
|
report_data: dict,
|
||||||
|
current_session: Session = Depends(get_current_session),
|
||||||
|
report_service: InterviewReportService = Depends(InterviewReportService),
|
||||||
|
):
|
||||||
|
"""Создать новый отчёт интервью"""
|
||||||
|
if not current_session:
|
||||||
|
raise HTTPException(status_code=401, detail="No active session")
|
||||||
|
|
||||||
|
report = await report_service.create_report(**report_data)
|
||||||
|
if not report:
|
||||||
|
raise HTTPException(status_code=500, detail="Failed to create report")
|
||||||
|
|
||||||
|
return report
|
87
app/services/interview_reports_service.py
Normal file
87
app/services/interview_reports_service.py
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
from fastapi import Depends
|
||||||
|
|
||||||
|
from app.models.interview_report import InterviewReport
|
||||||
|
from app.repositories.interview_reports_repository import InterviewReportRepository
|
||||||
|
|
||||||
|
|
||||||
|
class InterviewReportService:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
report_repo: Annotated[InterviewReportRepository, Depends(InterviewReportRepository)],
|
||||||
|
):
|
||||||
|
self.report_repo = report_repo
|
||||||
|
|
||||||
|
async def get_report_by_session(self, session_id: int) -> InterviewReport | None:
|
||||||
|
"""Получить отчёт по ID сессии"""
|
||||||
|
return await self.report_repo.get_by_session_id(session_id)
|
||||||
|
|
||||||
|
async def get_reports_by_vacancy(self, vacancy_id: int) -> list[InterviewReport]:
|
||||||
|
"""Получить все отчёты по вакансии"""
|
||||||
|
return await self.report_repo.get_by_vacancy_id(vacancy_id)
|
||||||
|
|
||||||
|
async def update_report_scores(
|
||||||
|
self, report_id: int, scores: dict
|
||||||
|
) -> bool:
|
||||||
|
"""
|
||||||
|
Обновить оценки отчёта.
|
||||||
|
Пример scores:
|
||||||
|
{
|
||||||
|
"technical_skills_score": 8,
|
||||||
|
"communication_score": 7,
|
||||||
|
"overall_score": 8
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
return await self.report_repo.update_scores(report_id, scores)
|
||||||
|
|
||||||
|
async def update_pdf_url(self, report_id: int, pdf_url: str) -> bool:
|
||||||
|
"""Обновить ссылку на PDF отчёта"""
|
||||||
|
return await self.report_repo.update_pdf_url(report_id, pdf_url)
|
||||||
|
|
||||||
|
async def update_interviewer_notes(self, report_id: int, notes: str) -> bool:
|
||||||
|
"""Обновить заметки интервьюера"""
|
||||||
|
return await self.report_repo.update_notes(report_id, notes)
|
||||||
|
|
||||||
|
async def create_report(
|
||||||
|
self,
|
||||||
|
interview_session_id: int,
|
||||||
|
technical_skills_score: int,
|
||||||
|
experience_relevance_score: int,
|
||||||
|
communication_score: int,
|
||||||
|
problem_solving_score: int,
|
||||||
|
cultural_fit_score: int,
|
||||||
|
overall_score: int,
|
||||||
|
recommendation: str,
|
||||||
|
strengths: dict | None = None,
|
||||||
|
weaknesses: dict | None = None,
|
||||||
|
red_flags: dict | None = None,
|
||||||
|
next_steps: str | None = None,
|
||||||
|
interviewer_notes: str | None = None,
|
||||||
|
pdf_report_url: str | None = None,
|
||||||
|
) -> InterviewReport | None:
|
||||||
|
"""Создать новый отчёт для сессии"""
|
||||||
|
try:
|
||||||
|
report_data = {
|
||||||
|
"interview_session_id": interview_session_id,
|
||||||
|
"technical_skills_score": technical_skills_score,
|
||||||
|
"experience_relevance_score": experience_relevance_score,
|
||||||
|
"communication_score": communication_score,
|
||||||
|
"problem_solving_score": problem_solving_score,
|
||||||
|
"cultural_fit_score": cultural_fit_score,
|
||||||
|
"overall_score": overall_score,
|
||||||
|
"recommendation": recommendation,
|
||||||
|
"strengths": strengths or {},
|
||||||
|
"weaknesses": weaknesses or {},
|
||||||
|
"red_flags": red_flags or {},
|
||||||
|
"next_steps": next_steps,
|
||||||
|
"interviewer_notes": interviewer_notes,
|
||||||
|
"pdf_report_url": pdf_report_url,
|
||||||
|
"created_at": datetime.utcnow(),
|
||||||
|
"updated_at": datetime.utcnow(),
|
||||||
|
}
|
||||||
|
return await self.report_repo.create(report_data)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error creating interview report: {str(e)}")
|
||||||
|
return None
|
3
main.py
3
main.py
@ -9,6 +9,7 @@ from app.routers.admin_router import router as admin_router
|
|||||||
from app.routers.analysis_router import router as analysis_router
|
from app.routers.analysis_router import router as analysis_router
|
||||||
from app.routers.interview_router import router as interview_router
|
from app.routers.interview_router import router as interview_router
|
||||||
from app.routers.session_router import router as session_router
|
from app.routers.session_router import router as session_router
|
||||||
|
from app.routers.interview_reports_router import router as interview_report_router
|
||||||
|
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
@ -56,7 +57,7 @@ app.include_router(session_router, prefix="/api/v1")
|
|||||||
app.include_router(interview_router, prefix="/api/v1")
|
app.include_router(interview_router, prefix="/api/v1")
|
||||||
app.include_router(analysis_router, prefix="/api/v1")
|
app.include_router(analysis_router, prefix="/api/v1")
|
||||||
app.include_router(admin_router, prefix="/api/v1")
|
app.include_router(admin_router, prefix="/api/v1")
|
||||||
|
app.include_router(interview_report_router, prefix="/api/v1")
|
||||||
|
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
async def root():
|
async def root():
|
||||||
|
@ -33,6 +33,7 @@ dependencies = [
|
|||||||
"yandex-speechkit>=1.5.0",
|
"yandex-speechkit>=1.5.0",
|
||||||
"pdfkit>=1.0.0",
|
"pdfkit>=1.0.0",
|
||||||
"jinja2>=3.1.6",
|
"jinja2>=3.1.6",
|
||||||
|
"greenlet>=3.2.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
|
Loading…
Reference in New Issue
Block a user