add pipline

This commit is contained in:
ivankirpichnikov
2025-11-23 01:42:46 +03:00
parent 7f91b412b8
commit 96e792b122
20 changed files with 219 additions and 43 deletions
@@ -1,7 +1,7 @@
from collections.abc import Hashable
from dataclasses import dataclass, replace
from dataclasses import dataclass
from datetime import datetime
from typing import Self, cast, dataclass_transform, override
from typing import cast, dataclass_transform, override
from uuid import UUID
from template_project.application.common.errors import EntityAlreadyDeletedError
@@ -22,9 +22,6 @@ class Entity[EntityId: UUID](Hashable):
if self.deleted_at is not None:
raise EntityAlreadyDeletedError(entity_name=self.__class__.__name__)
def __copy__(self) -> Self:
return replace(self)
@override
def __eq__(self, other: object) -> bool:
if isinstance(other, Entity):
@@ -80,6 +80,23 @@ class ResumePrediction(Entity[ResumePredictionId]):
to_salary: Decimal
recommended_skills: list[str]
@classmethod
def factory(
cls,
resume_id: ResumeId,
from_salary: Decimal,
to_salary: Decimal,
recommended_skills: list[str],
) -> Self:
return cls(
id=ResumePredictionId(uuid7()),
created_at=datetime.now(tz=UTC),
resume_id=resume_id,
from_salary=from_salary,
to_salary=to_salary,
recommended_skills=recommended_skills,
)
@to_entity
class ResumeExperience(Entity[ResumeExperienceId]):
@@ -3,20 +3,21 @@ from collections.abc import Callable
from Levenshtein import ratio
from template_project.application.common.unit_of_work import UnitOfWork
from template_project.application.resume.entity import Resume, ResumeEmbedding, ResumePrediction
from template_project.application.resume.entity import Resume, ResumeEmbedding
from template_project.application.resume.resume_prediction_generator import ResumePredictionGenerator
from template_project.application.resume.vector_generator import ResumeEmbeddingVectorGenerator
from template_project.application.vacancy.data_gateway import VacancyDataGateway
from template_project.application.vacancy.entity import Vacancy
from template_project.application.vacancy.data_structure import SuitableVacancy
def suitable_vacancies_key(
resume: Resume,
) -> Callable[[Vacancy], bool]:
def wrapper(vacancy: Vacancy) -> bool:
) -> Callable[[SuitableVacancy], tuple[bool, bool]]:
def wrapper(suitable_vacancy: SuitableVacancy) -> tuple[bool, bool]:
count_skills = 0
ratio_skill_sum = 0.0
for resum_key_skill in resume.key_skills:
for suitable_resume_key_skill in vacancy.key_skills:
for suitable_resume_key_skill in suitable_vacancy.vacancy.key_skills:
ratio_skill = ratio(resum_key_skill, suitable_resume_key_skill)
if ratio_skill != 0:
count_skills += 1
@@ -27,26 +28,28 @@ def suitable_vacancies_key(
except ZeroDivisionError:
matching_skills = 0
return resume.experience_type == vacancy.experience_type and matching_skills >= 50
return resume.experience_type == suitable_vacancy.vacancy.experience_type, matching_skills >= 50
return wrapper
class ResumeEmbeddingPipeline:
class ResumeEmbeddingInteractor:
def __init__(
self,
unit_of_work: UnitOfWork,
vacancy_data_gateway: VacancyDataGateway,
vector_generator: ResumeEmbeddingVectorGenerator,
resume_prediction_generator: ResumePredictionGenerator,
) -> None:
self.unit_of_work = unit_of_work
self.vacancy_data_gateway = vacancy_data_gateway
self.vector_generator = vector_generator
self.vacancy_data_gateway = vacancy_data_gateway
self.resume_prediction_generator = resume_prediction_generator
async def run(
self,
resume: Resume,
) -> ResumePrediction:
) -> None:
vector = await self.vector_generator.generate(
position=resume.position,
about_me=resume.about_me,
@@ -62,13 +65,12 @@ class ResumeEmbeddingPipeline:
suitable_vacancies_filtered = sorted(
suitable_vacancies,
key=suitable_vacancies_key(resume),
)[:50]
resume_prediction = await self.resume_prediction_generator.generate(
resume=resume,
suituble_vacancies=suitable_vacancies_filtered,
)
suitable_vacancies = suitable_vacancies_filtered[:50]
# TODO: тут надо сделать отправку в ИИ
await self.unit_of_work.add(resume_embedding)
await self.unit_of_work.add(resume_embedding, resume_prediction)
await self.unit_of_work.commit()
raise NotImplementedError
@@ -1 +0,0 @@
# class ResumePredicition
@@ -0,0 +1,16 @@
from abc import abstractmethod
from collections.abc import Sequence
from typing import Protocol
from template_project.application.resume.entity import Resume, ResumePrediction
from template_project.application.vacancy.data_structure import SuitableVacancy
class ResumePredictionGenerator(Protocol):
@abstractmethod
async def generate(
self,
resume: Resume,
suituble_vacancies: Sequence[SuitableVacancy],
) -> ResumePrediction:
raise NotImplementedError
@@ -2,10 +2,10 @@ from abc import abstractmethod
from collections.abc import Sequence
from typing import Protocol
from template_project.application.vacancy.entity import Vacancy
from template_project.application.vacancy.data_structure import SuitableVacancy
class VacancyDataGateway(Protocol):
@abstractmethod
async def get_suitable(self, vector: list[float]) -> Sequence[Vacancy]:
async def get_suitable(self, vector: list[float]) -> Sequence[SuitableVacancy]:
raise NotImplementedError
@@ -0,0 +1,8 @@
from template_project.application.common.data_structure import to_data_structure
from template_project.application.vacancy.entity import Vacancy
@to_data_structure
class SuitableVacancy:
vacancy: Vacancy
resume_similarity: float