You've already forked RekomenciBackend
fix(): adaptix is a common dependency due to it being used in domain
This commit is contained in:
+1
-1
@@ -10,12 +10,12 @@ dependencies = [
|
||||
"dishka==1.7.2",
|
||||
"pydantic[email]>=2.12.4",
|
||||
"levenshtein>=0.27.3",
|
||||
"adaptix==3.0.0b11",
|
||||
"markupsafe",
|
||||
]
|
||||
|
||||
[dependency-groups]
|
||||
backend = [
|
||||
"adaptix==3.0.0b11",
|
||||
"sqlalchemy==2.0.44",
|
||||
"argon2_cffi==23.1.0",
|
||||
"cryptography==46.0.3",
|
||||
|
||||
@@ -14,7 +14,7 @@ from fastapi.middleware.cors import CORSMiddleware
|
||||
|
||||
from template_project.ml.configuration import load_configuration
|
||||
from template_project.ml.ioc.make import make_ioc
|
||||
from template_project.ml.routes import embedding, healthcheck
|
||||
from template_project.ml.routes import embedding, healthcheck, predict
|
||||
|
||||
LOG_CONFIG: Final = {
|
||||
"version": 1,
|
||||
@@ -56,6 +56,7 @@ def make_asgi_application(
|
||||
)
|
||||
app.include_router(healthcheck.router)
|
||||
app.include_router(embedding.router)
|
||||
app.include_router(predict.router)
|
||||
|
||||
setup_dishka(container=ioc, app=app)
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
from decimal import Decimal
|
||||
|
||||
from template_project.application.common.data_structure import to_data_structure
|
||||
from template_project.application.common.interactor import to_interactor
|
||||
from template_project.application.resume.entity import ResumeId
|
||||
|
||||
|
||||
@to_data_structure
|
||||
class VacancyInput:
|
||||
vacancy_id: str
|
||||
from_salary: Decimal
|
||||
to_salary: Decimal
|
||||
key_skills: list[str]
|
||||
resume_similarity: float
|
||||
|
||||
|
||||
@to_data_structure
|
||||
class PredictSalaryRequest:
|
||||
resume_id: ResumeId
|
||||
key_skills: list[str]
|
||||
vacancies: list[VacancyInput]
|
||||
|
||||
|
||||
@to_data_structure
|
||||
class PredictSalaryResponse:
|
||||
salary_from: Decimal
|
||||
salary_to: Decimal
|
||||
recommended_skills: list[str]
|
||||
|
||||
|
||||
@to_interactor
|
||||
class PredictSalaryInteractor:
|
||||
async def execute(self, request: PredictSalaryRequest) -> PredictSalaryResponse:
|
||||
return PredictSalaryResponse(
|
||||
salary_from=Decimal("50000"),
|
||||
salary_to=Decimal("80000"),
|
||||
recommended_skills=["python", "django", "postgresql"],
|
||||
)
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
from dishka import BaseScope, Provider, Scope, provide_all
|
||||
|
||||
from template_project.ml.interactors.predict_salary import PredictSalaryInteractor
|
||||
|
||||
|
||||
class InteractorProvider(Provider):
|
||||
scope: BaseScope | None = Scope.REQUEST
|
||||
|
||||
interactors = provide_all(
|
||||
PredictSalaryInteractor,
|
||||
)
|
||||
|
||||
@@ -3,6 +3,7 @@ from dishka.integrations.fastapi import FastapiProvider
|
||||
|
||||
from template_project.ml.configuration import Configuration, ServerConfiguration
|
||||
from template_project.ml.ioc.embedding import EmbeddingProvider
|
||||
from template_project.ml.ioc.interactor import InteractorProvider
|
||||
from template_project.ml.ioc.model import ModelProvider
|
||||
|
||||
|
||||
@@ -10,6 +11,7 @@ def make_ioc(configuration: Configuration) -> AsyncContainer:
|
||||
return make_async_container(
|
||||
ModelProvider(),
|
||||
EmbeddingProvider(),
|
||||
InteractorProvider(),
|
||||
FastapiProvider(),
|
||||
validation_settings=STRICT_VALIDATION,
|
||||
context={
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
from decimal import Decimal
|
||||
|
||||
from dishka import FromDishka
|
||||
from dishka.integrations.fastapi import DishkaRoute
|
||||
from fastapi import APIRouter, status
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from template_project.application.resume.entity import ResumeId
|
||||
from template_project.ml.interactors.predict_salary import (
|
||||
PredictSalaryInteractor,
|
||||
PredictSalaryRequest,
|
||||
PredictSalaryResponse,
|
||||
VacancyInput,
|
||||
)
|
||||
|
||||
router = APIRouter(route_class=DishkaRoute, tags=["Prediction"])
|
||||
|
||||
|
||||
class VacancyInputModel(BaseModel):
|
||||
vacancy_id: str = Field(description="Vacancy ID", examples=["vacancy_123"])
|
||||
from_salary: Decimal = Field(description="Minimum salary", examples=[Decimal(100000)])
|
||||
to_salary: Decimal = Field(description="Maximum salary", examples=[Decimal(150000)])
|
||||
key_skills: list[str] = Field(description="List of key skills", examples=[["Python", "FastAPI", "PostgreSQL"]])
|
||||
resume_similarity: float = Field(
|
||||
ge=0.0, le=1.0, description="Resume similarity score (0.0 to 1.0)", examples=[0.85]
|
||||
)
|
||||
|
||||
model_config = {
|
||||
"json_schema_extra": {
|
||||
"example": {
|
||||
"vacancy_id": "vacancy_123",
|
||||
"from_salary": "100000",
|
||||
"to_salary": "150000",
|
||||
"key_skills": ["Python", "FastAPI", "PostgreSQL"],
|
||||
"resume_similarity": 0.85,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class PredictSalaryRequestModel(BaseModel):
|
||||
resume_id: ResumeId = Field(description="Resume ID", examples=["01234567-89ab-cdef-0123-456789abcdef"])
|
||||
key_skills: list[str] = Field(
|
||||
min_length=1, description="List of key skills from resume", examples=[["Python", "FastAPI", "PostgreSQL"]]
|
||||
)
|
||||
vacancies: list[VacancyInputModel] = Field(
|
||||
min_length=1, description="List of relevant vacancies", examples=[[]]
|
||||
)
|
||||
|
||||
model_config = {
|
||||
"json_schema_extra": {
|
||||
"example": {
|
||||
"resume_id": "01234567-89ab-cdef-0123-456789abcdef",
|
||||
"key_skills": ["Python", "FastAPI", "PostgreSQL"],
|
||||
"vacancies": [
|
||||
{
|
||||
"vacancy_id": "vacancy_123",
|
||||
"from_salary": "100000",
|
||||
"to_salary": "150000",
|
||||
"key_skills": ["Python", "FastAPI", "PostgreSQL", "Docker"],
|
||||
"resume_similarity": 0.85,
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class PredictSalaryResponseModel(BaseModel):
|
||||
salary_from: Decimal = Field(description="Minimum predicted salary", examples=[Decimal(100000)])
|
||||
salary_to: Decimal = Field(description="Maximum predicted salary", examples=[Decimal(150000)])
|
||||
recommended_skills: list[str] = Field(
|
||||
description="Top 3 recommended skills", examples=[["Kubernetes", "Redis", "Docker"]]
|
||||
)
|
||||
|
||||
model_config = {
|
||||
"json_schema_extra": {
|
||||
"example": {
|
||||
"salary_from": "100000",
|
||||
"salary_to": "150000",
|
||||
"recommended_skills": ["Kubernetes", "Redis", "Docker"],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@router.post(
|
||||
"/predict_salary",
|
||||
summary="Predict salary",
|
||||
description="Predict salary range and recommend skills based on resume and relevant vacancies",
|
||||
responses={
|
||||
200: {"description": "Salary prediction generated successfully", "model": PredictSalaryResponseModel},
|
||||
},
|
||||
)
|
||||
async def predict_salary(
|
||||
request: PredictSalaryRequestModel,
|
||||
interactor: FromDishka[PredictSalaryInteractor],
|
||||
) -> PredictSalaryResponseModel:
|
||||
vacancy_inputs = [
|
||||
VacancyInput(
|
||||
vacancy_id=vacancy.vacancy_id,
|
||||
from_salary=vacancy.from_salary,
|
||||
to_salary=vacancy.to_salary,
|
||||
key_skills=vacancy.key_skills,
|
||||
resume_similarity=vacancy.resume_similarity,
|
||||
)
|
||||
for vacancy in request.vacancies
|
||||
]
|
||||
|
||||
predict_request = PredictSalaryRequest(
|
||||
resume_id=request.resume_id,
|
||||
key_skills=request.key_skills,
|
||||
vacancies=vacancy_inputs,
|
||||
)
|
||||
|
||||
response = await interactor.execute(predict_request)
|
||||
|
||||
return PredictSalaryResponseModel(
|
||||
salary_from=response.salary_from,
|
||||
salary_to=response.salary_to,
|
||||
recommended_skills=response.recommended_skills,
|
||||
)
|
||||
|
||||
@@ -2416,6 +2416,7 @@ name = "template-project"
|
||||
version = "1.0.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "adaptix" },
|
||||
{ name = "dishka" },
|
||||
{ name = "fastapi" },
|
||||
{ name = "levenshtein" },
|
||||
@@ -2427,7 +2428,6 @@ dependencies = [
|
||||
|
||||
[package.dev-dependencies]
|
||||
backend = [
|
||||
{ name = "adaptix" },
|
||||
{ name = "aioboto3" },
|
||||
{ name = "argon2-cffi" },
|
||||
{ name = "cryptography" },
|
||||
@@ -2478,6 +2478,7 @@ types = [
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "adaptix", specifier = "==3.0.0b11" },
|
||||
{ name = "dishka", specifier = "==1.7.2" },
|
||||
{ name = "fastapi", specifier = "==0.119.0" },
|
||||
{ name = "levenshtein", specifier = ">=0.27.3" },
|
||||
@@ -2489,7 +2490,6 @@ requires-dist = [
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
backend = [
|
||||
{ name = "adaptix", specifier = "==3.0.0b11" },
|
||||
{ name = "aioboto3", specifier = "==15.5.0" },
|
||||
{ name = "argon2-cffi", specifier = "==23.1.0" },
|
||||
{ name = "cryptography", specifier = "==46.0.3" },
|
||||
|
||||
Reference in New Issue
Block a user