ÖSU
Платформа Career Quest для сотрудника и HR: сравнение навыков с требованиями роли и грейда, до трёх объяснимых рекомендаций активностей, пересчёт…
О проекте
Команда: NeonCode
Кейс HackAlem.ai: Менеджмент — Halyk Bank · кейс 1
Стадия: Рабочий MVP
Задача кейса: «Career Quest»: AI-навигатор, связывающий навыки сотрудника с шагами развития
Суть
Платформа Career Quest для сотрудника и HR: сравнение навыков с требованиями роли и грейда, до трёх объяснимых рекомендаций активностей, пересчёт прогресса после завершения и HR-сводка дефицитов команды.
Как устроено
Backend на FastAPI загружает датасет (сотрудники, навыки, требования грейдов, активности, история) в SQLite, считает дефицит до следующего грейда и отбирает активности с фильтрами по аудитории и prerequisites. Скоринг учитывает долю закрываемого дефицита, критичность навыков и пропуски/отказы. Опциональный LLM-этап выбирает 1–3 из до 8 рассчитанных кандидатов, ответ проверяется по схеме и допустимым ID, при ошибке или тайм-ауте 8 с возвращается fallback. Рост навыков считается по формуле с потолком max_level, повторное завершение с тем же completion_id идемпотентно.
Ключевые решения
- rule-based scoring
- skill gap analysis
- идемпотентное завершение по completion_id
- валидация ответа LLM по схеме
- fallback без LLM
Особенности
LLM жёстко ограничена: только выбирает среди рассчитанных кандидатов, ответ сверяется с допустимыми ID и фактами, есть пометка source: llm/fallback. Интерфейс на трёх языках (ru/en/kk), README честно указывает, что правила frontend и backend пока не унифицированы.
Стек
Python · FastAPI · Pydantic v2 · SQLAlchemy · SQLite · Next.js 16 · React 19 · TypeScript · Tailwind CSS 4 · Playwright · pytest · Docker Compose · uv
Модели
OpenAI-совместимый /chat/completions API
Проект создан за 5 часов на финале хакатона HackAlem.ai (23 сентября 2026, Астана). Код — BAITC-Hacks/hack-f485f4b0-neoncode.
README
README.mdÖSU
Платформа развития сотрудников для трека Halyk Bank.
ÖSU связывает обучение с карьерной целью: показывает, каких навыков не хватает, предлагает до трёх полезных активностей и объясняет ожидаемый результат. После завершения активности прогресс пересчитывается, а HR получает общую картину развития команды.
Основной сценарий: профиль → цель → дефицит навыков → рекомендация → действие → обновлённый прогресс.
Возможности
Для сотрудника: профиль, выбор и изменение карьерной цели, сравнение навыков с требованиями роли, объяснимые рекомендации, каталог активностей, запись и демо-завершение, история участия.
Для HR: дефициты компетенций команды, показатели участия, импорт проверочных профилей и истории. В интерфейсе также доступны готовность к выбранным целям и отсутствие завершений за последние 90 дней. Публичного рейтинга сотрудников нет.
Интерфейс: русский, английский и казахский языки, мобильная адаптация, сохранение состояния в браузере, зелёная палитра и короткое полноэкранное вступление. Анимацию можно пропустить; системная настройка уменьшения движения учитывается.
Стек
| Часть | Технологии |
|---|---|
| Frontend | Next.js 16, React 19, TypeScript, CSS / Tailwind CSS 4, SVG |
| Backend | Python 3.12+, FastAPI, Pydantic v2 |
| Хранение | SQLAlchemy 2.x, SQLite |
| LLM | HTTPX, совместимый /chat/completions API |
| Проверки | Playwright, pytest, ESLint, Prettier, Ruff |
| Запуск | npm, uv, Docker Compose для backend |
Архитектура
flowchart LR
User[Сотрудник / HR] --> UI[Next.js]
UI <--> Local[Датасет и localStorage]
UI -. Планируемая интеграция .-> API[FastAPI]
Client[Swagger / API-клиент] --> API
API --> Logic[Прогресс · рекомендации · импорт · HR]
Logic <--> DB[(SQLite)]
Logic -. Опционально .-> LLM[Языковая модель]
Frontend разделён на экраны в src/features/, общие компоненты и доменные расчёты в src/lib/. Цель выбирает сотрудник; без цели интерфейс показывает развитие в текущей роли и грейде. Локальные изменения и язык сохраняются в localStorage.
Backend находится в backend/app/. Он загружает и проверяет данные, рассчитывает дефициты и рекомендации, формирует HR-сводки и сохраняет изменения в SQLite. Импорт и завершение выполняются транзакционно. Повтор завершения с тем же completion_id возвращает сохранённый ответ без повторного начисления навыков.
Данные и модели
Все демонстрационные данные синтетические. Frontend использует исходный набор: 200 сотрудников, 40 активностей, 60 навыков, 32 профиля ролей и грейдов, 2 743 записи истории. Дата среза — 1 октября 2026 года.
Backend по умолчанию запускается на небольшом наборе из backend/data/synthetic/: 10 сотрудников, 8 активностей, 15 навыков, 8 профилей ролей и грейдов, 12 записей истории. Его идентификаторы и результаты отличаются от браузерного демо.
| Модель | Назначение |
|---|---|
| Сотрудник | Роль, грейд, стаж, навыки, карьерная цель и дата оценки |
| Навык | Справочник hard / soft skills; уровень от 0 до 5 |
| Требования роли | Нужные уровни и критические навыки для конкретного грейда |
| Активность | Аудитория, формат, условия участия и прирост навыков |
| История | Участие, даты и статусы: завершение, пропуск, отказ и другие |
| Завершение | Серверный ключ действия и ответ для защиты от повторного начисления |
Типы UI описаны в domain.ts, контракты API — в schemas.py, таблицы базы — в models.py. В SQLite используются employees, employee_skills, skills, grade_requirements, events, activity_history и completions.
Рекомендации, прогресс и AI
Логика рекомендаций
Backend определяет следующий грейд текущей роли, рассчитывает разницу между нужными и текущими навыками и отбирает доступные активности. Обязательные, завершённые, неподходящие по аудитории или prerequisites мероприятия исключаются.
Оценка учитывает долю закрываемого дефицита, критичность навыков, адресность аудитории и пропуски / отказы по похожим активностям. В ответе доступны числовые факторы и объяснение. Если полезного шага нет или требования уже выполнены, сервер возвращает соответствующий статус вместо случайной рекомендации.
Frontend использует отдельный демо-алгоритм: ориентируется на выбранную цель, дополнительно учитывает длительность и будущие сессии. Правила двух контуров предстоит унифицировать. Полная серверная формула описана в документации рекомендаций.
Рост навыков
новый уровень = min(5, max(текущий, min(текущий + gain, max_level)))
Активность не снижает уже достигнутый уровень и не даёт прирост выше своего предела. Например, при уровне 2, gain=2 и max_level=3 результат равен 3.
Готовность в UI — доля набранных уровней относительно требований цели, с ограничением вклада каждого навыка его требованием. Backend возвращает дефициты следующего грейда и сумму недостающих уровней. Эти показатели не означают автоматического повышения.
Frontend учитывает исторические завершения после последней оценки навыков. Backend считает импортированные навыки текущим снимком и не начисляет историю повторно.
Языковая модель
Собственная нейросеть не обучается. В backend реализован опциональный LLM-этап: модель получает до восьми рассчитанных кандидатов и выбирает от одного до трёх. Ответ проверяется по схеме, допустимым ID и точному содержанию предоставленных фактов; модель не может изменить навыки, баллы или каталог.
Без ключа, при ошибке или превышении тайм-аута в 8 секунд возвращается локальный результат с source: fallback. Проверенный ответ модели помечается source: llm. Во frontend LLM пока не подключена; автоматические тесты используют подмену HTTP-ответов, а не живого провайдера.
Запуск
Команды выполняются из корня репозитория, если не указано иное. Для базовой демонстрации LLM-ключ не нужен.
Frontend
Требуются Node.js 20.9+ и npm.
npm --prefix frontend ci
npm --prefix frontend run dev
Интерфейс dev-сервера: http://localhost:5173. Для production-сборки:
npm --prefix frontend run build
npm --prefix frontend start -- --port 3000
Основные API-экраны:
/me— профиль, требования к следующему грейду, рекомендации и история;/hr— сводка дефицитов, сотрудников без шага и участия в активностях;/hr/employees— поиск сотрудников по ID и фильтр по роли;/hr/employees/:id— профиль сотрудника для HR;/hr/import— импорт JSON или файлов профилей и истории;/demo— прежнее автономное демо на локальных данных.
Роль «Сотрудник» или «HR» выбирается в шапке. В режиме HR откройте список сотрудников и выберите профиль; затем выбранный ID можно показать в режиме сотрудника. Это демонстрационный переключатель, доступ окончательно проверяет backend. Под ролью employee HR-пункты скрыты, а прямой переход показывает сообщение о недостаточном доступе.
VITE_API_URL задаёт базовый URL API вместе с /api; значение по умолчанию —
/api. Dev-сервер проксирует его на http://localhost:8000, production nginx —
на сервис backend в Compose. Секреты и LLM_API_KEY во frontend не передаются.
Сценарий демо на 5 минут
- Выберите HR и на
/hrпокажите диаграмму проседающих навыков, две группы сотрудников без шага и участие в активностях. - Откройте
/hr/employees, найдите сотрудника по ID, отфильтруйте роль и перейдите в профиль. - Покажите роль, грейд, стаж, требования, дефициты и объяснимую рекомендацию с бейджем AI или «Скоринг».
- Нажмите «Выполнил»: покажите навыки до/после, ограничение
max_level, оставшийся дефицит и обновлённый следующий шаг. - Откройте историю и импорт; покажите ошибку по конкретному полю, затем успешный импорт и ссылки на профили.
- Переключитесь в «Сотрудник»: HR-навигация исчезнет, а прямой
/hrпокажет сообщение о доступе.
Проверены TypeScript, ESLint, OpenAPI-типы, unit-тесты и production-сборка.
Playwright покрывает роли, маршруты, завершение, импорт и мобильный viewport с
mock API, однако полный браузерный прогон в текущем окружении заблокирован
отсутствующей системной libnspr4.so. Реальные LLM-вызовы, production-аутентификация
и развёртывание за внешним TLS не проверялись.
Backend
Требуются Python 3.12+ и uv. В отдельном терминале:
cd backend
uv sync --locked
uv run uvicorn app.main:app --host 127.0.0.1 --port 8000
Swagger: http://127.0.0.1:8000/docs. При старте создаётся SQLite-база; повторный запуск сохраняет существующие данные.
Основные настройки: DATA_DIR, DATABASE_URL, LLM_API_KEY, LLM_BASE_URL, LLM_MODEL. Для загрузки .env используйте uv run --env-file .env uvicorn app.main:app. Проверьте модель в файле: пример конфигурации переопределяет значение по умолчанию из кода. Подробности — в backend README.
Для исходного набора задания запустите из backend/ с отдельной базой:
DATA_DIR=../dataset/case_1/career_quest_dataset \
DATABASE_URL=sqlite:///./osu-organizer.db \
uv run uvicorn app.main:app --host 127.0.0.1 --port 8000
Запуск всего проекта через Docker
Потребуется запущенный Docker Engine или Docker Desktop. Локальная установка Node.js, Python и uv для этого варианта не нужна. Все команды выполняются из корня репозитория; первая сборка требует доступа к интернету.
1. Соберите два образа из корневого Dockerfile:
docker build --target backend -t osu-backend .
docker build --target frontend -t osu-frontend .
2. Запустите backend и frontend:
docker run -d --name osu-backend --restart unless-stopped \
-p 127.0.0.1:8000:8000 \
-v osu-data:/var/lib/career-quest \
osu-backend
docker run -d --name osu-frontend --restart unless-stopped \
-p 127.0.0.1:3000:3000 \
osu-frontend
3. Откройте приложение:
| Сервис | Адрес |
|---|---|
| Интерфейс ÖSU | http://localhost:3000 |
| Swagger API | http://localhost:8000/docs |
| OpenAPI | http://localhost:8000/openapi.json |
Backend при первом запуске загружает небольшой синтетический набор и сохраняет SQLite в томе osu-data. Frontend использует собственный демо-набор; запуск двух контейнеров сам по себе не подключает интерфейс к API. Без LLM-ключа сервер возвращает локальные рекомендации.
Управление контейнерами:
# Состояние и логи; Ctrl+C завершает просмотр логов, а не контейнер.
docker ps -a --filter name=osu-
docker logs -f osu-backend
docker logs -f osu-frontend
# Остановка и повторный запуск без пересоздания.
docker stop osu-frontend osu-backend
docker start osu-backend osu-frontend
# Удаление контейнеров для последующей пересборки.
docker rm -f osu-frontend osu-backend
Удаление контейнеров не удаляет том osu-data. Для обновления кода пересоберите образы и заново выполните команды docker run после удаления старых контейнеров. Если порты 3000 или 8000 заняты локальными серверами, сначала остановите их. Ошибка подключения к Docker daemon означает, что нужно запустить Docker Engine / Desktop.
Запуск frontend и backend через Compose
docker compose up --build -d
docker compose ps
docker compose logs -f frontend backend
Compose использует frontend/Dockerfile и backend/Dockerfile, открывает интерфейс
на порту 3000, API на 8000 и хранит SQLite в томе career_quest_db. Nginx
отдаёт SPA с fallback на index.html и проксирует /api в сервис backend.
Для остановки выполните docker compose down; без -v база сохраняется.
Compose передаёт LLM_API_KEY из окружения или корневого .env; остальные настройки контейнеров заданы в docker-compose.yml.
API и импорт
Все /api-запросы требуют X-Role: employee|hr и X-Employee-Id. Сотрудник обращается только к своему профилю; HR — ко всем профилям и HR-маршрутам. Это доверенные заголовки для разработки, а не полноценная аутентификация.
| Метод | Маршрут | Назначение |
|---|---|---|
GET | /api/employees/{id} | Профиль, прогресс и история |
GET | /api/employees/{id}/recommendations | Рекомендации, факторы и дефициты |
POST | /api/employees/{id}/complete | Завершение и обновление навыков |
GET | /api/employees | HR-список сотрудников |
GET | /api/hr/summary | Командные дефициты и участие |
POST | /api/import | Атомарный импорт профилей и истории |
Пример для стандартного серверного набора:
curl http://127.0.0.1:8000/api/employees/SYN_E001/recommendations \
-H 'X-Role: employee' -H 'X-Employee-Id: SYN_E001'
Импорт принимает JSON с массивами employees и history либо multipart-файлы employees_file и history_file (CSV / JSON для истории). Проверяются схемы, ссылки и дубликаты; ошибка откатывает весь пакет. Профиль с существующим ID заменяется целиком. Идентификаторы навыков и мероприятий должны соответствовать загруженному каталогу.
Готовые материалы: три проверочных профиля, правила импорта, примеры API, OpenAPI.
Демонстрация для жюри
- Выберите профиль, откройте траекторию и задайте карьерную цель.
- Покажите дефицит навыка и объяснение подходящей рекомендации.
- Запишитесь на доступную активность, отметьте демо-завершение и сравните прогресс.
- Переключите язык и обновите страницу — состояние сохранится.
- Выберите «Демо-роль → HR», покажите аналитику и импорт проверочных данных.
- Отдельно в Swagger проверьте рекомендации и завершение. Повторите запрос с тем же
completion_id: повторного прироста не будет.
Тестирование
Frontend — из frontend/:
npm run lint
npm run format:check
npm run build
npx playwright install chromium
npm test
Backend — из backend/:
uv run pytest -q
uv run ruff check app scripts tests
uv run ruff format --check app scripts tests
В проекте 33 frontend-теста и 113 backend-тестов: расчёты, пользовательские сценарии, локализация, мобильный вид, импорт, доступ, LLM-fallback, повторные и конкурентные завершения. Есть проверка импорта трёх профилей и трёх fallback-рекомендаций менее чем за две секунды; это локальная регрессионная проверка, не нагрузочный SLA.
Структура проекта
frontend/
src/app/ # Страница, метаданные и стили
src/components/ # Оболочка, вступление и UI-примитивы
src/features/ # Сотрудник, каталог, история, HR, импорт
src/lib/ # Модели, расчёты, валидация и переводы
tests/ # Playwright
backend/
app/ # FastAPI, модели и доменная логика
data/synthetic/ # Серверный демо-набор
docs/ # Контракты и примеры
tests/ # pytest
Dockerfile
pyproject.toml
dataset/case_1/ # Исходные данные задания
docker-compose.yml # Запуск backend
Авторы коммитов
История коммитов
- 2026-09-23 17:53Merge branch 'dev'Dias Zakir
- 2026-09-23 17:52finalDias Zakir
- 2026-09-23 16:55Merge remote-tracking branch 'origin/dev' into devDaniyal
- 2026-09-23 16:50backend v1Dias Zakir
- 2026-09-23 16:44merge: integrate ÖSU branding into devDaniyal
- 2026-09-23 16:44merge: promote ÖSU branding to mainDaniyal
- 2026-09-23 16:44feat(frontend): rebrand interface to ÖSU and remove event brandingDaniyal
- 2026-09-23 16:34hr and importsDias Zakir
Все коммиты (48)
- 2026-09-23 16:22Merge pull request #4 from BAITC-Hacks/devDaniyal Adilbekov
- 2026-09-23 16:20Merge pull request #3 from BAITC-Hacks/feature/frontendDaniyal Adilbekov
- 2026-09-23 16:16merge: integrate full-screen opening into devDaniyal
- 2026-09-23 16:16merge: promote full-screen opening to mainDaniyal
- 2026-09-23 16:16feat(frontend): add full-screen editorial opening experienceDaniyal
- 2026-09-23 16:14basic openai integrationDias Zakir
- 2026-09-23 16:08merge: integrate frontend visual refresh into devDaniyal
- 2026-09-23 16:08merge: promote frontend visual refresh to mainDaniyal
- 2026-09-23 16:08feat(frontend): refresh green palette and add gentle page animationsDaniyal
- 2026-09-23 15:51Merge remote-tracking branch 'origin/main'Daniyal
- 2026-09-23 15:51Merge remote-tracking branch 'origin/dev' into devDaniyal
- 2026-09-23 15:47merge: integrate dev into mainDaniyal
- 2026-09-23 15:47merge: integrate feature/frontend into devDaniyal
- 2026-09-23 15:47docs: correct frontend branch name to feature/frontendDaniyal
- 2026-09-23 15:41merge: promote verified dev changes to mainDaniyal
- 2026-09-23 15:41merge: integrate frontend corrections into devDaniyal
- 2026-09-23 15:41fix(frontend): clarify career goals and split application into feature modulesDaniyal
- 2026-09-23 15:41chore(frontend): document feature boundaries and extend formatting checksDaniyal
- 2026-09-23 15:30Merge branch 'dev' of https://github.com/BAITC-Hacks/hack-f485f4b0-neoncode into devDias Zakir
- 2026-09-23 15:29Skills and progressDias Zakir
- 2026-09-23 15:18merge: integrate project review fixes and architecture auditDaniyal
- 2026-09-23 15:18fix(frontend): validate imported and restored career data consistentlyDaniyal
- 2026-09-23 15:18fix(backend): align CORS origins with Next.js development serverDaniyal
- 2026-09-23 15:18docs: audit project architecture and record remaining integration gapsDaniyal
- 2026-09-23 15:09Merge pull request #2 from BAITC-Hacks/devDaniyal Adilbekov
- 2026-09-23 15:03Merge remote-tracking branch 'origin/main' into devDaniyal
- 2026-09-23 14:58Merge remote-tracking branch 'origin/dev' into devDaniyal
- 2026-09-23 14:51feat(i18n): add Russian English and Kazakh dictionaries and locale stateDaniyal
- 2026-09-23 14:51feat(frontend): localize all screens and add persistent language switcherDaniyal
- 2026-09-23 14:51merge: integrate trilingual Career Quest interface into devDaniyal
- 2026-09-23 14:37Merge branch 'dev' of https://github.com/BAITC-Hacks/hack-f485f4b0-neoncode into devDias Zakir
- 2026-09-23 14:25basic applicationDias Zakir
- 2026-09-23 14:24test(frontend): verify career flows and document demo integrationDaniyal
- 2026-09-23 14:24merge: integrate Career Quest frontend into devDaniyal
- 2026-09-23 14:24feat(frontend): build calm responsive Career Quest experienceDaniyal
- 2026-09-23 14:14feat(frontend): add dataset adapter and explainable career calculationsDaniyal
- 2026-09-23 14:09Merge pull request #1 from BAITC-Hacks/feature/frontendDaniyal Adilbekov
- 2026-09-23 13:59feat: initialize frontendDaniyal
- 2026-09-23 13:54basic app configurationDias Zakir
- 2026-09-23 13:39adding datasetDias Zakir
Структура репозитория (138 файлов)
.dockerignore .env.example .gitignore Dockerfile README.md backend/.dockerignore backend/.env.example backend/.gitignore backend/.python-version backend/AGENTS.md backend/Dockerfile backend/README.md backend/app/__init__.py backend/app/config.py backend/app/data_loader.py backend/app/db.py backend/app/gaps.py backend/app/hr.py backend/app/importer.py backend/app/llm.py backend/app/main.py backend/app/models.py backend/app/progress.py backend/app/recommend.py backend/app/routers/__init__.py backend/app/routers/dependencies.py backend/app/routers/employees.py backend/app/routers/hr.py backend/app/routers/imports.py backend/app/schemas.py backend/career-quest-backend/.python-version backend/career-quest-backend/README.md backend/career-quest-backend/pyproject.toml backend/career-quest-backend/src/career_quest_backend/__init__.py backend/career-quest-backend/uv.lock backend/data/synthetic/README.md backend/data/synthetic/activity_history.csv backend/data/synthetic/employees.json backend/data/synthetic/events.json backend/data/synthetic/skills.json backend/docs/examples.md backend/docs/hr.md backend/docs/import-example.json backend/docs/openapi.json backend/docs/recommendations.md backend/pyproject.toml backend/scripts/__init__.py backend/scripts/export_openapi.py backend/tests/conftest.py backend/tests/test_contract.py backend/tests/test_data.py backend/tests/test_gaps.py backend/tests/test_hr.py backend/tests/test_llm.py backend/tests/test_progress.py backend/tests/test_recommend.py backend/uv.lock dataset/case_1/.DS_Store dataset/case_1/career_quest_dataset/README.kz.md dataset/case_1/career_quest_dataset/README.md dataset/case_1/career_quest_dataset/README.ru.md dataset/case_1/career_quest_dataset/activity_history.csv dataset/case_1/career_quest_dataset/employees.json dataset/case_1/career_quest_dataset/events.json dataset/case_1/career_quest_dataset/skills.json docker-compose.yml docs/PROJECT_REVIEW.md frontend/.dockerignore frontend/.env.example frontend/.gitignore frontend/.prettierignore frontend/AGENTS.md frontend/Dockerfile frontend/README.md frontend/docs/API_AUDIT.md frontend/eslint.config.mjs frontend/index.html frontend/next.config.ts frontend/nginx.conf frontend/package-lock.json frontend/package.json frontend/playwright.config.ts frontend/postcss.config.mjs frontend/scripts/prepare-data.mjs frontend/src/api/client.ts frontend/src/api/hooks.ts frontend/src/api/provider.tsx frontend/src/api/types.ts frontend/src/app/globals.css frontend/src/app/icon.svg frontend/src/app/layout.tsx frontend/src/app/page.tsx frontend/src/components/career-app.tsx frontend/src/components/opening-experience.tsx frontend/src/components/ui/primitives.tsx frontend/src/features/career/career-context.tsx frontend/src/features/career/use-career-state.ts frontend/src/features/catalog/catalog.tsx frontend/src/features/catalog/event-card.tsx frontend/src/features/catalog/event-dialog.tsx frontend/src/features/employee/goal-editor.tsx frontend/src/features/employee/overview.tsx frontend/src/features/employee/trajectory.tsx frontend/src/features/history/history.tsx frontend/src/features/hr/analytics.ts frontend/src/features/hr/hr.tsx frontend/src/features/import/import.tsx frontend/src/features/live/app.tsx frontend/src/features/live/employee.tsx frontend/src/features/live/hr.tsx frontend/src/features/live/import.tsx frontend/src/features/live/live.css frontend/src/features/live/session.tsx frontend/src/features/live/states.tsx frontend/src/lib/api/adapters.ts frontend/src/lib/career.ts frontend/src/lib/data.ts frontend/src/lib/dataset.json frontend/src/lib/domain.ts frontend/src/lib/i18n.ts frontend/src/lib/import-data.ts frontend/src/lib/locale-context.tsx frontend/src/lib/locales/en.json frontend/src/lib/locales/kk.json frontend/src/lib/locales/ru.json frontend/src/lib/validation.ts frontend/src/main.tsx frontend/src/vite-env.d.ts frontend/tests/api-integration.spec.ts frontend/tests/career.spec.ts frontend/tests/frontend-fixes.spec.ts frontend/tests/i18n.spec.ts frontend/tests/opening.spec.ts frontend/tests/review.spec.ts frontend/tests/unit/client.test.ts frontend/tsconfig.json frontend/vite.config.mts frontend/vitest.config.mts
Обсуждение 0 комментариев
Войдите, чтобы комментироватьПока нет комментариев — будьте первым.