From f92e3d337263063e752ae2bdd5e02d0b2b710222 Mon Sep 17 00:00:00 2001 From: gitgernit Date: Sun, 23 Nov 2025 04:25:59 +0300 Subject: [PATCH] fix(): update schemas --- .../application/resume/interactors/get.py | 136 +++++++++++++++--- .../resume/interactors/prediction_pipeline.py | 24 ++-- src/template_project/web_api/routes/resume.py | 131 +++++++++++++++++ 3 files changed, 258 insertions(+), 33 deletions(-) diff --git a/src/template_project/application/resume/interactors/get.py b/src/template_project/application/resume/interactors/get.py index 70ba5de..c925a2d 100644 --- a/src/template_project/application/resume/interactors/get.py +++ b/src/template_project/application/resume/interactors/get.py @@ -136,35 +136,89 @@ class ResumeListItemResponse: about_me: str key_skills: list[str] experience_type: ExperienceType + experience: list[ExperienceItemResponse] + education: list[EducationItemResponse] + projects: list[ProjectItemResponse] + prediction: ResumePredictionResponse | None @to_interactor class GetResumeListInteractor: identity_provider: IdentityProvider resume_data_gateway: ResumeDataGateway + resume_prediction_data_gateway: ResumePredictionDataGateway + resume_experience_data_gateway: ResumeExperienceDataGateway + resume_education_data_gateway: ResumeEducationDataGateway + resume_project_data_gateway: ResumeProjectDataGateway async def execute(self, limit: int, offset: int) -> list[ResumeListItemResponse]: user = await self.identity_provider.get_current_user() resumes = await self.resume_data_gateway.list_latest_by_user_id(user.id, limit=limit, offset=offset) - return [ - ResumeListItemResponse( - id=r.id, - position=r.position, - location=r.location, - about_me=r.about_me, - key_skills=r.key_skills, - experience_type=r.experience_type, + result = [] + for r in resumes: + resume_prediction = await self.resume_prediction_data_gateway.load_by_resume_id(r.id) + if resume_prediction is not None: + prediction = ResumePredictionResponse( + from_salary=resume_prediction.from_salary, + to_salary=resume_prediction.to_salary, + recommended_skills=resume_prediction.recommended_skills, + ) + else: + prediction = None + + experiences = await self.resume_experience_data_gateway.load_by_resume_id(r.id) + educations = await self.resume_education_data_gateway.load_by_resume_id(r.id) + projects = await self.resume_project_data_gateway.load_by_resume_id(r.id) + + result.append( + ResumeListItemResponse( + id=r.id, + position=r.position, + location=r.location, + about_me=r.about_me, + key_skills=r.key_skills, + experience_type=r.experience_type, + experience=[ + ExperienceItemResponse( + place=exp.place, + description=exp.description, + months_duration=exp.months_duration, + ) + for exp in experiences + ], + education=[ + EducationItemResponse( + place=edu.place, + grade=edu.grade, + specialization=edu.specialization, + description=edu.description, + ) + for edu in educations + ], + projects=[ + ProjectItemResponse( + name=proj.name, + description=proj.description, + ) + for proj in projects + ], + prediction=prediction, + ) ) - for r in resumes - ] + + return result @to_interactor class GetResumeHistoryInteractor: identity_provider: IdentityProvider resume_data_gateway: ResumeDataGateway + resume_prediction_data_gateway: ResumePredictionDataGateway + resume_experience_data_gateway: ResumeExperienceDataGateway + resume_education_data_gateway: ResumeEducationDataGateway + resume_project_data_gateway: ResumeProjectDataGateway async def execute(self, resume_id: ResumeId) -> list[ResumeListItemResponse]: user = await self.identity_provider.get_current_user() @@ -177,14 +231,56 @@ class GetResumeHistoryInteractor: history = await self.resume_data_gateway.get_history(resume_id) - return [ - ResumeListItemResponse( - id=r.id, - position=r.position, - location=r.location, - about_me=r.about_me, - key_skills=r.key_skills, - experience_type=r.experience_type, + result = [] + for r in history: + resume_prediction = await self.resume_prediction_data_gateway.load_by_resume_id(r.id) + if resume_prediction is not None: + prediction = ResumePredictionResponse( + from_salary=resume_prediction.from_salary, + to_salary=resume_prediction.to_salary, + recommended_skills=resume_prediction.recommended_skills, + ) + else: + prediction = None + + experiences = await self.resume_experience_data_gateway.load_by_resume_id(r.id) + educations = await self.resume_education_data_gateway.load_by_resume_id(r.id) + projects = await self.resume_project_data_gateway.load_by_resume_id(r.id) + + result.append( + ResumeListItemResponse( + id=r.id, + position=r.position, + location=r.location, + about_me=r.about_me, + key_skills=r.key_skills, + experience_type=r.experience_type, + experience=[ + ExperienceItemResponse( + place=exp.place, + description=exp.description, + months_duration=exp.months_duration, + ) + for exp in experiences + ], + education=[ + EducationItemResponse( + place=edu.place, + grade=edu.grade, + specialization=edu.specialization, + description=edu.description, + ) + for edu in educations + ], + projects=[ + ProjectItemResponse( + name=proj.name, + description=proj.description, + ) + for proj in projects + ], + prediction=prediction, + ) ) - for r in history - ] + + return result diff --git a/src/template_project/application/resume/interactors/prediction_pipeline.py b/src/template_project/application/resume/interactors/prediction_pipeline.py index 3a2a152..ebb65e5 100644 --- a/src/template_project/application/resume/interactors/prediction_pipeline.py +++ b/src/template_project/application/resume/interactors/prediction_pipeline.py @@ -41,23 +41,21 @@ def _filter_and_sort_vacancies( suitable_vacancies: list[SuitableVacancy], limit: int = 50, ) -> list[SuitableVacancy]: - def is_suitable(vacancy: SuitableVacancy) -> bool: - experience_match = resume.experience_type == vacancy.vacancy.experience_type - skills_matching = _calculate_skills_matching(resume.key_skills, vacancy.vacancy.key_skills) - skills_match = skills_matching >= 0.5 - return experience_match and skills_match + def calculate_priority(vacancy: SuitableVacancy) -> float: + priority = vacancy.resume_similarity - filtered = [v for v in suitable_vacancies if is_suitable(v)] + if resume.experience_type == vacancy.vacancy.experience_type: + priority += 0.1 - if len(filtered) >= limit: - filtered.sort(key=lambda v: v.resume_similarity, reverse=True) - return filtered[:limit] + if resume.key_skills: + skills_matching = _calculate_skills_matching(resume.key_skills, vacancy.vacancy.key_skills) + priority += skills_matching * 0.2 - remaining = [v for v in suitable_vacancies if v not in filtered] - remaining.sort(key=lambda v: v.resume_similarity, reverse=True) + return priority - total_needed = limit - len(filtered) - return filtered + remaining[:total_needed] + sorted_vacancies = sorted(suitable_vacancies, key=calculate_priority, reverse=True) + + return sorted_vacancies[:limit] @to_data_structure diff --git a/src/template_project/web_api/routes/resume.py b/src/template_project/web_api/routes/resume.py index ad35673..f33a76a 100644 --- a/src/template_project/web_api/routes/resume.py +++ b/src/template_project/web_api/routes/resume.py @@ -227,6 +227,10 @@ class ResumeListItem(BaseModel): about_me: str = Field(description="About me section") key_skills: list[str] = Field(description="List of key skills") experience_type: ExperienceType = Field(description="Experience type") + experience: list[ExperienceItem] = Field(description="Work experience list") + education: list[EducationItem] = Field(description="Education list") + projects: list[ProjectItem] = Field(description="Projects list") + prediction: SalaryPrediction | None = Field(None, description="Salary prediction (can be null)") model_config = { "json_schema_extra": { @@ -236,6 +240,27 @@ class ResumeListItem(BaseModel): "about_me": "Experienced Python developer with 5 years of experience", "key_skills": ["Python", "FastAPI", "PostgreSQL"], "experience_type": "between3And6", + "experience": [ + { + "place": "T-bank", + "description": "some description lorem ipsum", + "months_duration": 12, + } + ], + "education": [ + { + "place": "Central university", + "grade": "bachelor", + "specialization": "IT guy", + "description": "optional field, if user want add something", + } + ], + "projects": [ + { + "name": "Rekomenci fluon", + "description": "fucking shit", + } + ], } } } @@ -255,9 +280,31 @@ class GetResumeListResponse(BaseModel): "resumes": [ { "position": "Python Developer", + "location": "Moscow", "about_me": "Experienced Python developer", "key_skills": ["Python", "FastAPI"], "experience_type": "between3And6", + "experience": [ + { + "place": "T-bank", + "description": "some description lorem ipsum", + "months_duration": 12, + } + ], + "education": [ + { + "place": "Central university", + "grade": "bachelor", + "specialization": "IT guy", + "description": "optional field, if user want add something", + } + ], + "projects": [ + { + "name": "Rekomenci fluon", + "description": "fucking shit", + } + ], } ] } @@ -279,9 +326,31 @@ class GetResumeHistoryResponse(BaseModel): "resumes": [ { "position": "Python Developer", + "location": "Moscow", "about_me": "Experienced Python developer", "key_skills": ["Python", "FastAPI"], "experience_type": "between3And6", + "experience": [ + { + "place": "T-bank", + "description": "some description lorem ipsum", + "months_duration": 12, + } + ], + "education": [ + { + "place": "Central university", + "grade": "bachelor", + "specialization": "IT guy", + "description": "optional field, if user want add something", + } + ], + "projects": [ + { + "name": "Rekomenci fluon", + "description": "fucking shit", + } + ], } ] } @@ -379,6 +448,37 @@ async def get_resume_list( about_me=r.about_me, key_skills=r.key_skills, experience_type=r.experience_type, + experience=[ + ExperienceItem( + place=exp.place, + description=exp.description, + months_duration=exp.months_duration, + ) + for exp in r.experience + ], + education=[ + EducationItem( + place=edu.place, + grade=edu.grade, + specialization=edu.specialization, + description=edu.description, + ) + for edu in r.education + ], + projects=[ + ProjectItem( + name=proj.name, + description=proj.description, + ) + for proj in r.projects + ], + prediction=SalaryPrediction( + from_salary=r.prediction.from_salary, + to_salary=r.prediction.to_salary, + recommended_skills=r.prediction.recommended_skills, + ) + if r.prediction is not None + else None, ) for r in interactor_response ] @@ -588,6 +688,37 @@ async def get_resume_history( about_me=r.about_me, key_skills=r.key_skills, experience_type=r.experience_type, + experience=[ + ExperienceItem( + place=exp.place, + description=exp.description, + months_duration=exp.months_duration, + ) + for exp in r.experience + ], + education=[ + EducationItem( + place=edu.place, + grade=edu.grade, + specialization=edu.specialization, + description=edu.description, + ) + for edu in r.education + ], + projects=[ + ProjectItem( + name=proj.name, + description=proj.description, + ) + for proj in r.projects + ], + prediction=SalaryPrediction( + from_salary=r.prediction.from_salary, + to_salary=r.prediction.to_salary, + recommended_skills=r.prediction.recommended_skills, + ) + if r.prediction is not None + else None, ) for r in interactor_response ]