В ходе выполнения выпускной квалификационной работы была разработана и внедрена информационная система «Подбор персонала» для ООО «ШСУ», предназначенная для автоматизации процесса рекрутинга и управления кадровыми данными.
На этапе анализа предметной области был изучен текущий порядок подбора сотрудников, выявлены основные проблемы — высокая трудоёмкость обработки заявок, дублирование информации, недостаточная прозрачность этапов подбора. На основании выявленных недостатков сформулированы функциональные и нефункциональные требования к системе, а также определены ключевые роли пользователей.
В процессе проектирования была разработана архитектура системы, включающая backend-часть на основе FastAPI, frontend-интерфейс с использованием библиотеки Streamlit, а также централизованную базу данных SQLite. Были реализованы модули для работы с вакансиями, резюме, заявками кандидатов, проведения собеседований, хранения документов и управления пользователями. Система поддерживает разграничение прав доступа в зависимости от роли и обеспечивает безопасность авторизации и хранения данных.
Реализация проекта сопровождалась тестированием, включающим как функциональные проверки отдельных модулей, так и интеграционные тесты для оценки совместной работы компонентов. По итогам тестирования были подтверждены корректность функционирования системы, стабильность её работы и удобство интерфейса.
Экономическая оценка проекта показала высокую эффективность внедрения. Использование системы позволяет сократить время закрытия вакансий, снизить затраты на обработку и хранение информации, а также повысить прозрачность процессов. Ожидаемая окупаемость проекта составляет менее одного года, что делает его целесообразным для внедрения в производственную деятельность предприятия.
Поставленные в работе цели и задачи полностью достигнуты. Разработанная информационная система готова к эксплуатации и способна существенно повысить эффективность работы отдела кадров ООО «ШСУ», обеспечив автоматизацию ключевых процессов и повышение качества управления подбором персонала. В приложениях указаны основные модули для работы сервиса, полный проект предоставлен в отдельном виде.
Список используемой литературы
Аршинский Л. В., Жукова М. С. Интеллектуальные информационные системы и технологии. — Иркутск: ИрГУПС, 2023. — 128 с.
Магомедов М. Н. Информационные системы и технологии. — СПбГИКиТ, 2020. — 89 с.
ГОСТ 34.601-90. Автоматизированные системы. Стадии создания. — М.: Госстандарт, 1990. https://www.prj-exp.ru/gost/gost_34-601-90.php
Бутырский Е. Ю., Цехановский В. В., Жукова Н. А. и др. Машинное обучение: учебник. — Москва: Директ-Медиа, 2023. — 368 с.
Huntflow. Официальный сайт. https://huntflow.ru
CleverStaff. Обзор возможностей. https://cleverstaff.net/ru/
Федеральный закон от 27.07.2006 № 152-ФЗ «О персональных данных». https://www.consultant.ru/document/cons_doc_LAW_61801/
ПОСЛЕДНИЙ ЛИСТ ВЫПУСКНОЙ КВАЛИФИКАЦИОННОЙ РАБОТЫ
Выпускная квалификационная работа выполнена мной совершенно самостоятельно. Все использованные в работе материалы и концепции из опубликованной научной литературы и других источников имеют ссылки на них.
«13» августа 2025 г.
________________________/ Попрукайло Роман Борисович
(подпись) (Ф.И.О.)
Приложение А. Конфигурация и подключение к БД
from pathlib import Path
from typing import ClassVar
from pydantic_settings import BaseSettings, SettingsConfigDict
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, DeclarativeBase
class Settings(BaseSettings):
BASE_DIR: ClassVar[Path] = Path(__file__).resolve().parent.parent
DB_PATH: ClassVar[Path] = BASE_DIR / "ir.db"
DATABASE_URL: str = f"sqlite:///{DB_PATH}"
SECRET_KEY: str = "dev_secret"
ACCESS_TOKEN_EXPIRE_MINUTES: int = 60
ALGORITHM: str = "HS256"
FILES_DIR: str = str(BASE_DIR / "files")
model_config = SettingsConfigDict(
env_file=str(BASE_DIR / ".env"),
env_file_encoding="utf-8"
)
settings = Settings()
print("DB фактический путь:", settings.DATABASE_URL)
engine = create_engine(settings.DATABASE_URL, pool_pre_ping=True, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
class Base(DeclarativeBase):
pass
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
Приложение Б. Модели данных
from sqlalchemy import (
String, Text, Integer, Date, DateTime, Enum, ForeignKey, Boolean
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
from datetime import datetime
import enum
from .database import Base
class RoleEnum(str, enum.Enum):
admin = "admin"
recruiter = "recruiter"
manager = "manager"
hr = "hr"
viewer = "viewer"
class VacancyStatusEnum(str, enum.Enum):
open = "open"
in_progress = "in_progress"
closed = "closed"
class ApplicationStatusEnum(str, enum.Enum):
applied = "applied"
screening = "screening"
interview = "interview"
offer = "offer"
hired = "hired"
rejected = "rejected"
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
full_name: Mapped[str] = mapped_column(String(150))
email: Mapped[str] = mapped_column(String(255), unique=True, index=True)
password_hash: Mapped[str] = mapped_column(String(255))
role: Mapped[RoleEnum] = mapped_column(Enum(RoleEnum), default=RoleEnum.recruiter)
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
created_vacancies = relationship("Vacancy", back_populates="created_by")
class Department(Base):
__tablename__ = "departments"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(100), unique=True)
vacancies = relationship("Vacancy", back_populates="department")
class Vacancy(Base):
__tablename__ = "vacancies"
id: Mapped[int] = mapped_column(primary_key=True)
title: Mapped[str] = mapped_column(String(200))
description: Mapped[str | None] = mapped_column(Text())
status: Mapped[VacancyStatusEnum] = mapped_column(
Enum(VacancyStatusEnum), default=VacancyStatusEnum.open
)
department_id: Mapped[int | None] = mapped_column(ForeignKey("departments.id"))
created_by_id: Mapped[int | None] = mapped_column(ForeignKey("users.id"))
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
created_by = relationship("User", back_populates="created_vacancies")
department = relationship("Department", back_populates="vacancies")
applications = relationship("CandidateApplication", back_populates="vacancy")
class Request(Base):
__tablename__ = "requests"
id: Mapped[int] = mapped_column(primary_key=True)
user_id: Mapped[int] = mapped_column(ForeignKey("users.id"))
position: Mapped[str] = mapped_column(String(200))
description: Mapped[str | None] = mapped_column(Text())
status: Mapped[str] = mapped_column(String(50), default="new")
submitted_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
user = relationship("User")
class Resume(Base):
__tablename__ = "resumes"
id: Mapped[int] = mapped_column(primary_key=True)
full_name: Mapped[str] = mapped_column(String(150))
birth_date: Mapped[datetime | None] = mapped_column(Date)
education: Mapped[str | None] = mapped_column(Text())
experience: Mapped[str | None] = mapped_column(Text())
contact_email: Mapped[str | None] = mapped_column(String(255))
contact_phone: Mapped[str | None] = mapped_column(String(50))
applications = relationship("CandidateApplication", back_populates="resume")
class CandidateApplication(Base):
__tablename__ = "candidate_applications"
id: Mapped[int] = mapped_column(primary_key=True)
resume_id: Mapped[int] = mapped_column(ForeignKey("resumes.id"))
vacancy_id: Mapped[int] = mapped_column(ForeignKey("vacancies.id"))
status: Mapped[ApplicationStatusEnum] = mapped_column(
Enum(ApplicationStatusEnum), default=ApplicationStatusEnum.applied
)
applied_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
resume = relationship("Resume", back_populates="applications")
vacancy = relationship("Vacancy", back_populates="applications")
interviews = relationship("Interview", back_populates="application")
class Interview(Base):
__tablename__ = "interviews"
id: Mapped[int] = mapped_column(primary_key=True)
candidate_application_id: Mapped[int] = mapped_column(ForeignKey("candidate_applications.id"))
interview_date: Mapped[datetime] = mapped_column(DateTime)
interviewer_id: Mapped[int] = mapped_column(ForeignKey("users.id"))
result: Mapped[str | None] = mapped_column(Text())
application = relationship("CandidateApplication", back_populates="interviews")
interviewer = relationship("User")
class Document(Base):
__tablename__ = "documents"
id: Mapped[int] = mapped_column(primary_key=True)
file_name: Mapped[str] = mapped_column(String(255))
file_type: Mapped[str | None] = mapped_column(String(50))
related_resume_id: Mapped[int | None] = mapped_column(ForeignKey("resumes.id"))
related_vacancy_id: Mapped[int | None] = mapped_column(ForeignKey("vacancies.id"))
uploaded_by: Mapped[int | None] = mapped_column(ForeignKey("users.id"))
uploaded_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
Приложение В. Схемы валидации (Pydantic) — выдержки
from pydantic import BaseModel, EmailStr, Field
from datetime import datetime, date
from typing import Optional, List
from .models import RoleEnum, VacancyStatusEnum, ApplicationStatusEnum
class Token(BaseModel):
access_token: str
token_type: str = "bearer"
class UserBase(BaseModel):
full_name: str
email: EmailStr
role: RoleEnum
is_active: bool = True
class UserCreate(BaseModel):
full_name: str
email: EmailStr
password: str = Field(min_length=6)
role: RoleEnum = RoleEnum.recruiter
class UserOut(UserBase):
id: int
created_at: datetime
class Config:
from_attributes = True
class LoginIn(BaseModel):
email: EmailStr
password: str
class DepartmentOut(BaseModel):
id: int
name: str
class Config:
from_attributes = True
class VacancyIn(BaseModel):
title: str
description: Optional[str] = None
status: VacancyStatusEnum = VacancyStatusEnum.open
department_id: Optional[int] = None
class VacancyOut(VacancyIn):
id: int
created_by_id: Optional[int]
created_at: datetime
class Config:
from_attributes = True
class ResumeIn(BaseModel):
full_name: str
birth_date: Optional[date] = None
education: Optional[str] = None
experience: Optional[str] = None
contact_email: Optional[EmailStr] = None
contact_phone: Optional[str] = None
class ResumeOut(ResumeIn):
id: int
class Config:
from_attributes = True
class ApplicationIn(BaseModel):
resume_id: int
vacancy_id: int
status: ApplicationStatusEnum = ApplicationStatusEnum.applied
class ApplicationOut(ApplicationIn):
id: int
applied_at: datetime
class Config:
from_attributes = True
class InterviewIn(BaseModel):
candidate_application_id: int
interview_date: datetime
interviewer_id: int
result: Optional[str] = None
class InterviewOut(InterviewIn):
id: int
class Config:
from_attributes = True
class RequestIn(BaseModel):
position: str
description: Optional[str] = None
class RequestOut(RequestIn):
id: int
user_id: int
status: str
submitted_at: datetime
class Config:
from_attributes = True
Приложение Г. Безопасность и доступ
from datetime import datetime, timedelta, timezone
from jose import jwt
from passlib.context import CryptContext
from .database import settings
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def hash_password(password: str) -> str:
return pwd_context.hash(password)
def verify_password(plain: str, hashed: str) -> bool:
return pwd_context.verify(plain, hashed)
def create_access_token(data: dict, expires_minutes: int | None = None) -> str:
to_encode = data.copy()
if "sub" in to_encode:
to_encode["sub"] = str(to_encode["sub"])
expire = datetime.now(timezone.utc) + timedelta(
minutes=expires_minutes or settings.ACCESS_TOKEN_EXPIRE_MINUTES
)
to_encode.update({"exp": expire})
return jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)