Career Quest
Карьерный навигатор на Spring Boot. Сравнивает навыки сотрудника с целевой ролью, выдаёт до 3 объяснимых рекомендаций мероприятий, после завершения…
О проекте
Команда: AceForce
Кейс HackAlem.ai: Менеджмент — Halyk Bank · кейс 1
Стадия: Рабочий MVP
Задача кейса: «Career Quest»: AI-навигатор, связывающий навыки сотрудника с шагами развития
Суть
Карьерный навигатор на Spring Boot. Сравнивает навыки сотрудника с целевой ролью, выдаёт до 3 объяснимых рекомендаций мероприятий, после завершения пересчитывает прогресс, начисляет XP и бейджи. Для HR есть аналитика.
Как устроено
DatasetImportService при первом запуске загружает в реляционную БД (MySQL, для демо H2) данные заказчика: skills.json, employees.json, events.json и activity_history.csv на 2743 записи. Разрыв считается как max(required − effective, 0), от него зависит процент готовности. WeightedScoringPolicy ранжирует мероприятия с учётом prerequisite, расписания, обязательности и уже пройденного. Симуляция завершения обновляет навыки, XP и рекомендации. LLM не используется.
Ключевые решения
- взвешенный rule-based скоринг рекомендаций
- skill gap analysis
- геймификация (XP, уровни, бейджи)
- HR-аналитика
- RBAC (Spring Security)
Особенности
Один из немногих проектов на Java/Spring с классической слоистой архитектурой (dao/service/controller) и плотным покрытием тестами: unit, H2-интеграционные, MockMvc. Интерфейс на RU/KK/EN. В README прямо сказано, что LLM не подключены и что рекомендации детерминированные.
Стек
Java 17 · Spring Boot 3.5 · Spring MVC · Spring Security · JdbcTemplate · Thymeleaf · MySQL · H2 · JUnit 5 · Mockito · Maven · Docker Compose
Проект создан за 5 часов на финале хакатона HackAlem.ai (23 сентября 2026, Астана). Код — BAITC-Hacks/hack-62902277-aceforce.
README
README.mdCareer Quest
Карьерный навигатор для сотрудников: роль → цель → дефицит навыков → объяснимая рекомендация → активность → обновлённый прогресс.
Проект хакатона Hackalem, трек «Управление». Приложение связывает разрозненные HR-мероприятия с карьерной целью сотрудника, а HR показывает общую картину развития команды.
Что работает в коде
- Spring Boot MVC-приложение с многослойной архитектурой config/controller/dao/model/service.
- Импорт настоящего синтетического датасета заказчика в реляционную БД при первом запуске.
- Профиль сотрудника и изменение карьерной цели, включая переход между ролями.
- Учёт завершений после последней оценки навыков; критические навыки и процент готовности.
- До трёх рекомендаций с объяснением дефицита, соответствия роли/грейду и истории формата.
- Проверка prerequisite, расписания, обязательности и уже пройденных активностей.
- Симуляция завершения, сохранение истории, повторный расчёт навыков и рекомендаций.
- XP, уровень и три вычисляемых бейджа.
- HR-аналитика: частые gaps, средняя готовность, отсутствие цели/следующего шага, статусы.
- Form login, BCrypt, права HR/EMPLOYEE, CSRF и ограничение доступа к чужому профилю.
- Переключение русского, казахского и английского интерфейса.
- История с серверной пагинацией, централизованные ошибки, Log4j2.
- Unit-тесты с Mockito, H2-интеграционные тесты DAO, MockMvc-тесты страниц и безопасности.
Названия курсов, ролей и навыков сохранены на языке исходного датасета. UI-подписи и объясняющие шаблоны переведены.
Технологии
Java 17; Spring Boot 3.5.6; Spring MVC; Spring Security; Spring JDBC/JdbcTemplate; Thymeleaf; MySQL; H2 для demo/tests; Jackson; Apache Commons CSV; Log4j2; JUnit 5; Mockito; MockMvc; JaCoCo; Maven.
Внешние OpenAI/NVIDIA API и LLM сейчас не подключены. Recommendation Engine — детерминированные правила и scoring. Ключи API для запуска не требуются. Созданный ранее описательный README не являлся доказательством реализации; этот README относится к исходникам в данном архиве.
Быстрый запуск без MySQL
Нужны Java 17+ и Maven 3.6.3+. В комплекте Maven Wrapper 3.9.9: если mvn не установлен, замените mvn на ./mvnw (Windows: mvnw.cmd). Первый запуск требует интернет для зависимостей.
Запускать команды нужно из корневой папки проекта, где лежит pom.xml.
Пример для macOS, если проект лежит в IntelliJ IDEA Projects:
cd /Users/yeldarskiy/IdeaProjects/hack-62902277-aceforce
ls -la
В списке должны быть:
pom.xml
src
mvnw
.mvn
README.md
Если проект распакован как career-quest, команда будет другой:
cd /Users/yeldarskiy/IdeaProjects/career-quest
После этого можно запускать тесты и сервер:
mvn clean test
mvn spring-boot:run -Dspring-boot.run.profiles=demo
Если Maven не установлен, используйте Maven Wrapper:
chmod +x mvnw
./mvnw clean test
./mvnw spring-boot:run -Dspring-boot.run.profiles=demo
Если ./mvnw выдаёт ошибку cannot read distributionUrl property in ./.mvn/wrapper/maven-wrapper.properties, значит при копировании проекта потерялась скрытая папка .mvn. В этом случае проще запустить через установленный Maven:
brew install maven
mvn clean test
mvn spring-boot:run -Dspring-boot.run.profiles=demo
Открыть http://localhost:8080.
Только для локального demo-профиля:
| Логин | Пароль | Доступ |
|---|---|---|
| hr | CareerDemo123! | Все сотрудники и HR dashboard |
| employee | CareerDemo123! | Только E0001 |
H2 demo хранит данные в памяти: после остановки они сбрасываются. Демо-аккаунты и пароль нельзя использовать при публичном размещении.
Данные для входа и проверки
Основной адрес приложения после запуска:
http://localhost:8080
Страница входа:
http://localhost:8080/login
Демо-профиль запуска:
mvn spring-boot:run -Dspring-boot.run.profiles=demo
или:
./mvnw spring-boot:run -Dspring-boot.run.profiles=demo
Учётные записи для локальной демонстрации:
| Роль | Логин | Пароль | Что можно делать |
|---|---|---|---|
| HR | hr | CareerDemo123! | Смотреть всех сотрудников, HR-аналитику, менять выбранного сотрудника на dashboard |
| Employee | employee | CareerDemo123! | Смотреть только профиль сотрудника E0001 и выполнять его рекомендации |
Основные страницы:
| URL | Назначение | Доступ |
|---|---|---|
/ | Главный dashboard сотрудника | HR, Employee |
/login | Страница авторизации | Все |
/activities | История активностей сотрудника | HR, Employee |
/achievements | XP, уровень и бейджи | HR, Employee |
/hr | HR dashboard и аналитика | Только HR |
/api/data/summary | Проверка количества импортированных данных | Только HR |
/api/hr/analytics | JSON с HR-аналитикой | Только HR |
Быстрая проверка данных после входа как HR:
http://localhost:8080/api/data/summary
Ожидаемый смысл ответа:
employees = 200
skills = 60
profiles = 32
events = 40
historyRecords = 2743
Проверочный сотрудник для демонстрации:
E0001 - Marat Yessenov
Current role: Backend Engineer Junior
Default target: Backend Engineer Middle
Что показать жюри в первую очередь:
- Войти как
hr. - Открыть
/и выбрать сотрудникаE0001. - Показать текущую роль, целевую роль, процент готовности и skill gaps.
- Показать рекомендованное мероприятие и объяснение, почему оно выбрано.
- Нажать завершение рекомендации.
- Показать, что XP вырос, история пополнилась, прогресс и рекомендации пересчитались.
- Открыть
/achievementsи показать уровень/бейджи. - Открыть
/activitiesи показать историю с пагинацией. - Открыть
/hrи показать HR-аналитику. - Переключить язык RU / KK / EN.
- Выйти и войти как
employee; показать, что HR-раздел недоступен.
Данные авторизации актуальны только для demo-профиля. При запуске с MySQL demo-пользователи создаются только если включить переменные DEMO_USERS=true и DEMO_PASSWORD.
Запуск с MySQL
MySQL — профиль по умолчанию. Можно использовать собственный MySQL 8 или приложенный compose.yaml.
- Задайте локальные пароли (не добавляйте их в Git):
export DB_PASSWORD='replace-with-your-own-password'
export MYSQL_ROOT_PASSWORD='replace-with-another-password'
docker compose up -d
docker compose ps
Дождитесь healthy. Compose публикует MySQL только на 127.0.0.1.
- Для локальной демонстрации создайте тестовые учётные записи с собственным паролем:
export DEMO_USERS=true
export DEMO_PASSWORD='replace-with-at-least-12-characters'
export DB_USERNAME=career
mvn spring-boot:run -Dspring-boot.run.profiles=mysql
Логины будут hr и employee; пароль — ваш DEMO_PASSWORD. Пароли записываются в app_user только в виде BCrypt-хешей. Если учётные записи уже существуют, повторный запуск их пароль не меняет.
- Внешняя база: задайте DB_URL, DB_USERNAME, DB_PASSWORD. База career_quest должна существовать, пользователь должен иметь права на создание таблиц и работу с данными. Приложение создаёт таблицы, но не удаляет существующие данные.
export DB_URL='jdbc:mysql://localhost:3306/career_quest?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC'
- При DEMO_USERS=false автоматически учётные записи не создаются. Для эксплуатации их нужно отдельно безопасно подготовить в app_user; UI управления пользователями в этой версии нет.
Сборка JAR
mvn clean package
java -jar target/career-quest-1.0.0.jar --spring.profiles.active=demo
Архитектура и файлы
career-quest/
pom.xml
README.md
index.html # автономный макет дизайна, не рабочее приложение
compose.yaml
.gitignore
docs/
ER-diagram.md
ARCHITECTURE.md
logs/ # log4j2 создаёт журнал; содержимое игнорируется
src/main/java/kz/hackalem/careerquest/
CareerQuestApplication.java
config/
controller/support/
dao/impl/
exception/
interceptor/
model/
service/
util/
src/main/resources/
application.properties
application-demo.properties
application-mysql.properties
log4j2.xml
db/schema.sql
db/schema-h2.sql
db/data.sql
data/ # JSON/CSV заказчика
i18n/messages.properties
i18n/messages_ru.properties
i18n/messages_kk.properties
i18n/messages_en.properties
templates/
auth/ dashboard/ activities/ achievements/ hr/ error/ fragments/
static/css/app.css
static/js/app.js
src/test/java/ # unit + DAO/MockMvc integration tests
src/test/resources/application.properties
target/ # генерируется Maven; не входит в исходники
ER-диаграмма · Архитектура и соответствие капстоуну.
Для исполняемого Boot JAR web.xml и WEB-INF не нужны: DispatcherServlet настраивается автоматически. Точка входа UI — MVC dashboard/index.html. Корневой index.html — только предварительный просмотр дизайна.
Данные и импорт
| Файл | Содержание |
|---|---|
| skills.json | 60 навыков, 32 профиля (8 ролей × 4 грейда) |
| employees.json | 200 сотрудников |
| events.json | 40 мероприятий |
| activity_history.csv | 2 743 записи |
Все данные синтетические. Расчётная дата — 2026-10-01, не дата компьютера. Исходные уровни employee_skill — оценка на last_review_date.
Импорт транзакционный, один раз на пустую базу. При повторном запуске marker dataset_import предотвращает дубли. Для нового набора укажите DATASET_LOCATION=file:/absolute/path/to/dataset/ и используйте отдельную пустую БД (или перезапустите demo). Формат должен соответствовать исходному README заказчика. Не надо редактировать идентификаторы в Java. Неправильные внешние ключи приводят к откату импорта.
schema.sql — MySQL-совместимая схема. schema-h2.sql — схема тестовой БД в режиме MySQL. data.sql не содержит тысяч строк и открытых паролей: бизнес-данные импортирует DatasetImportService из исходных файлов.
Расчёт готовности
gap(skill) = max(required - effective, 0)
progress = 100 × sum(min(effective, required)) / sum(required)
Отсутствующий навык равен 0. Завершения после last_review_date применяются в хронологическом порядке. Прирост ограничен gain/max_level/шкалой 0–5 и не снижает текущий уровень.
Если career_goal отсутствует, выбирается следующий грейд той же роли. Для Lead используется текущий Lead-профиль. Для межролевой цели сравнение проводится с требованиями целевой роли.
Процент — индикатор развития, не автоматическое решение о повышении. Критические дефициты выделяются отдельно.
Рекомендации
Фильтры: mandatory=false; релевантная текущая или целевая роль и грейд; выполненные prerequisites; реальное сокращение gap; актуальная сессия либо self_paced/уже начатая активность; отсутствие завершения (кроме EV_036).
score = 4 × impact + 3 × criticalImpact + 2
+ min(2, completedSameFormat × 0.25)
- min(2, droppedOrMissedSameFormat × 0.5)
- min(2, durationHours / 20)
+ (alreadyStarted ? 1 : 0)
impact — фактически сокращаемое число уровней навыков с учётом max_level и требований цели. При равном score сортировка по event_id обеспечивает воспроизводимость. Выбор политики — Strategy (RecommendationScoringPolicy). В UI показаны факторы объяснения, а не выдуманная «вероятность повышения».
В исходном CSV нет отдельной completion_date: поле date используется как временной ориентир завершения. В демо при нажатии «Завершить» используется дата среза.
Безопасность и сохранение
- Все пользовательские SQL-параметры передаются через JdbcTemplate bindings.
- Строка сотрудника блокируется на время завершения и смены цели.
- Повторный requestId не начисляет XP дважды.
- За каждое completed — 100 XP; level = 1 + completed / 3 (целочисленно).
- Бейджи: 1, 5, 10 завершений. XP не участвует в формуле readiness.
- История и цели сохраняются в MySQL и переживают перезапуск.
- CSRF нельзя отключать для обхода ошибок API. POST требует токен из пользовательской сессии.
- Запросы логируются без тела, query string, паролей и API-ключей.
Как проверить
- Запустить demo и войти как hr.
- Проверить /api/data/summary: 200 / 60 / 32 / 40 / 2743.
- На dashboard выбрать E0001 — Marat Yessenov; текущая роль Backend Engineer Junior, цель Middle.
- Посмотреть gaps, критические навыки и объяснение первой рекомендации.
- Завершить рекомендованную активность. Прогресс растёт, XP добавляется, рекомендации пересчитываются.
- Обновить страницу: результат остаётся. В MySQL сохраняется и после перезапуска приложения.
- Сменить цель или выбрать другого сотрудника, включая цель в другой роли.
- Переключить RU / KK / EN.
- Проверить историю и pagination, достижения, HR analytics.
- Войти как employee: HR недоступен, чужой employeeId возвращает 403.
Тесты
mvn clean test
Результаты JUnit: target/surefire-reports/. Отчёт покрытия JaCoCo создаётся уже на фазе test: target/site/jacoco/index.html. Тесты по умолчанию используют H2, настоящая MySQL для mvn clean test не требуется.
Проверено при подготовке архива: 60 тестов, 0 failures, 0 errors, 0 skipped. Покрытие строк: общее 98,7%, service 99,4%, dao/impl 100%. Это метрика выполненных строк, не гарантия отсутствия ошибок.
После mvn package можно дополнительно запустить node scripts/smoke.cjs (Node.js 18+): скрипт поднимает JAR на порту 18080, проверяет login, датасет и рабочие страницы, затем останавливает приложение. Подробности и границы проверки — docs/VERIFICATION.md.
Есть отдельные тестовые классы для конкретных production-классов; DAO проверяются интеграционно на SQL, бизнес-сервисы — изолированно с Mockito, конфигурация и контроллеры — через Spring/MockMvc. Для интерфейса без реализации unit-тест не создаётся искусственно.
КАК ЗАПУСТИТЬ ПРОЕКТ ЛОКАЛЬНО?
cd /Users/yeldarskiy/IdeaProjects/hack-62902277-aceforce mvn spring-boot:run -Dspring-boot.run.profiles=demo
hr CareerDemo123! employee CareerDemo123!
Авторы коммитов
История коммитов
- 2026-09-23 17:57Updated a READme filemqyerkhan
- 2026-09-23 17:36full version of projectybazarba
- 2026-09-23 15:51Добавлен README файлmqyerkhan
Структура репозитория (111 файлов)
README.md compose.yaml index.html logs/.gitkeep logs/career-quest.log mvnw mvnw.cmd pom.xml scripts/smoke.cjs src/main/java/kz/hackalem/careerquest/CareerQuestApplication.java src/main/java/kz/hackalem/careerquest/config/DataInitializer.java src/main/java/kz/hackalem/careerquest/config/SecurityConfig.java src/main/java/kz/hackalem/careerquest/config/WebConfig.java src/main/java/kz/hackalem/careerquest/controller/ApiController.java src/main/java/kz/hackalem/careerquest/controller/DashboardController.java src/main/java/kz/hackalem/careerquest/controller/HrController.java src/main/java/kz/hackalem/careerquest/controller/support/EmployeeAccess.java src/main/java/kz/hackalem/careerquest/controller/support/GlobalExceptionHandler.java src/main/java/kz/hackalem/careerquest/dao/ActivityDao.java src/main/java/kz/hackalem/careerquest/dao/CatalogDao.java src/main/java/kz/hackalem/careerquest/dao/EmployeeDao.java src/main/java/kz/hackalem/careerquest/dao/EventDao.java src/main/java/kz/hackalem/careerquest/dao/impl/JdbcActivityDao.java src/main/java/kz/hackalem/careerquest/dao/impl/JdbcCatalogDao.java src/main/java/kz/hackalem/careerquest/dao/impl/JdbcEmployeeDao.java src/main/java/kz/hackalem/careerquest/dao/impl/JdbcEventDao.java src/main/java/kz/hackalem/careerquest/exception/BusinessException.java src/main/java/kz/hackalem/careerquest/exception/NotFoundException.java src/main/java/kz/hackalem/careerquest/interceptor/RequestTimingInterceptor.java src/main/java/kz/hackalem/careerquest/model/ActivityRecord.java src/main/java/kz/hackalem/careerquest/model/CareerEvent.java src/main/java/kz/hackalem/careerquest/model/CareerGoal.java src/main/java/kz/hackalem/careerquest/model/CareerProgress.java src/main/java/kz/hackalem/careerquest/model/DevelopedSkill.java src/main/java/kz/hackalem/careerquest/model/Employee.java src/main/java/kz/hackalem/careerquest/model/GameProgress.java src/main/java/kz/hackalem/careerquest/model/Recommendation.java src/main/java/kz/hackalem/careerquest/model/RoleProfile.java src/main/java/kz/hackalem/careerquest/model/Skill.java src/main/java/kz/hackalem/careerquest/service/CareerService.java src/main/java/kz/hackalem/careerquest/service/CompletionService.java src/main/java/kz/hackalem/careerquest/service/DatasetImportService.java src/main/java/kz/hackalem/careerquest/service/GamificationService.java src/main/java/kz/hackalem/careerquest/service/HrAnalyticsService.java src/main/java/kz/hackalem/careerquest/service/RecommendationScoringPolicy.java src/main/java/kz/hackalem/careerquest/service/RecommendationService.java src/main/java/kz/hackalem/careerquest/service/WeightedScoringPolicy.java src/main/java/kz/hackalem/careerquest/util/SkillMath.java src/main/resources/application-demo.properties src/main/resources/application-mysql.properties src/main/resources/application.properties src/main/resources/data/README.ru.md src/main/resources/data/activity_history.csv src/main/resources/data/employees.json src/main/resources/data/events.json src/main/resources/data/skills.json src/main/resources/db/data.sql src/main/resources/db/schema-h2.sql src/main/resources/db/schema.sql src/main/resources/i18n/messages.properties src/main/resources/i18n/messages_en.properties src/main/resources/i18n/messages_kk.properties src/main/resources/i18n/messages_ru.properties src/main/resources/log4j2.xml src/main/resources/static/css/app.css src/main/resources/static/js/app.js src/main/resources/templates/achievements/index.html src/main/resources/templates/activities/index.html src/main/resources/templates/auth/login.html src/main/resources/templates/dashboard/index.html src/main/resources/templates/error/error.html src/main/resources/templates/fragments/layout.html src/main/resources/templates/hr/index.html src/test/java/kz/hackalem/careerquest/CareerQuestApplicationTest.java src/test/java/kz/hackalem/careerquest/IntegrationTestBase.java src/test/java/kz/hackalem/careerquest/TestFixtures.java src/test/java/kz/hackalem/careerquest/config/DataInitializerTest.java src/test/java/kz/hackalem/careerquest/config/SecurityConfigTest.java src/test/java/kz/hackalem/careerquest/config/WebConfigTest.java src/test/java/kz/hackalem/careerquest/controller/ApiControllerTest.java src/test/java/kz/hackalem/careerquest/controller/DashboardControllerTest.java src/test/java/kz/hackalem/careerquest/controller/HrControllerTest.java src/test/java/kz/hackalem/careerquest/controller/support/EmployeeAccessTest.java src/test/java/kz/hackalem/careerquest/controller/support/GlobalExceptionHandlerTest.java src/test/java/kz/hackalem/careerquest/dao/impl/JdbcActivityDaoTest.java src/test/java/kz/hackalem/careerquest/dao/impl/JdbcCatalogDaoTest.java src/test/java/kz/hackalem/careerquest/dao/impl/JdbcEmployeeDaoTest.java src/test/java/kz/hackalem/careerquest/dao/impl/JdbcEventDaoTest.java src/test/java/kz/hackalem/careerquest/exception/BusinessExceptionTest.java src/test/java/kz/hackalem/careerquest/exception/NotFoundExceptionTest.java src/test/java/kz/hackalem/careerquest/interceptor/RequestTimingInterceptorTest.java src/test/java/kz/hackalem/careerquest/model/ActivityRecordTest.java src/test/java/kz/hackalem/careerquest/model/CareerEventTest.java src/test/java/kz/hackalem/careerquest/model/CareerGoalTest.java src/test/java/kz/hackalem/careerquest/model/CareerProgressTest.java src/test/java/kz/hackalem/careerquest/model/DevelopedSkillTest.java src/test/java/kz/hackalem/careerquest/model/EmployeeTest.java src/test/java/kz/hackalem/careerquest/model/GameProgressTest.java src/test/java/kz/hackalem/careerquest/model/RecommendationTest.java src/test/java/kz/hackalem/careerquest/model/RoleProfileTest.java src/test/java/kz/hackalem/careerquest/model/SkillTest.java src/test/java/kz/hackalem/careerquest/service/CareerServiceTest.java src/test/java/kz/hackalem/careerquest/service/CompletionServiceTest.java src/test/java/kz/hackalem/careerquest/service/DatasetImportServiceTest.java src/test/java/kz/hackalem/careerquest/service/GamificationServiceTest.java src/test/java/kz/hackalem/careerquest/service/HrAnalyticsServiceTest.java src/test/java/kz/hackalem/careerquest/service/RecommendationServiceTest.java src/test/java/kz/hackalem/careerquest/service/WeightedScoringPolicyTest.java src/test/java/kz/hackalem/careerquest/util/SkillMathTest.java src/test/resources/application.properties src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
Обсуждение 0 комментариев
Войдите, чтобы комментироватьПока нет комментариев — будьте первым.