ML-powered API that classifies job postings into ML Engineer, Data Engineer, and Data Scientist roles and extracts structured skill tags from job descriptions.
Built as an end-to-end MLOps project: real data preprocessing, multi-model experiment tracking, containerized API serving, interactive demo UI, and CI/CD.
┌──────────────────┐
│ Kaggle Dataset │
│ 7.3K LinkedIn │
│ Job Postings │
└────────┬─────────┘
│
▼
┌─────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Data │────▶│ Training │────▶│ MLflow │
│ Preprocess │ │ Pipeline │ │ Tracking + │
│ (Label + │ │ 3 Models: │ │ Model Registry │
│ Merge) │ │ LR / RF / GB │ │ :5001 │
└─────────────┘ └──────────────────┘ └─────────────────┘
│
▼ (best model)
┌─────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Streamlit │────▶│ FastAPI │◀───▶│ Skill │
│ Demo UI │ │ Serving Layer │ │ Extractor │
│ :8501 │ │ /classify │ │ (Taxonomy from │
└─────────────┘ │ /extract-skills │ │ real data) │
│ /batch-classify │ └─────────────────┘
└──────────────────┘
│
┌──────────────────┐
│ Docker + │
│ docker-compose │
│ CI/CD (GH Act.) │
└──────────────────┘
Trained on 7,343 real LinkedIn job postings from the Data Science Job Postings & Skills (2024) dataset. Three classifiers compared via MLflow experiment tracking:
| Model | F1 Macro | Accuracy | CV F1 (5-fold) |
|---|---|---|---|
| Logistic Regression | 0.9495 | 0.9503 | 0.9587 |
| Random Forest | 0.9633 | 0.9632 | 0.9690 |
| Gradient Boosting | 0.9711 | 0.9721 | 0.9759 |
Best model (Gradient Boosting) per-class performance:
| Role | Precision | Recall | F1 | Support |
|---|---|---|---|---|
| Data Engineer | 0.96 | 0.99 | 0.97 | 700 |
| Data Scientist | 0.99 | 0.96 | 0.97 | 559 |
| ML Engineer | 0.99 | 0.95 | 0.97 | 210 |
Class imbalance handled via stratified splitting and class_weight="balanced" where supported.
The training pipeline logs per model:
- Parameters: classifier type, TF-IDF config, class balance, train/test sizes
- Metrics: accuracy, F1 (macro/weighted), precision, recall, CV scores
- Artifacts: trained model, classification report, confusion matrix
- Model comparison: side-by-side view of all 3 classifiers
Access the MLflow UI at http://localhost:5001 when running via docker-compose.
| Component | Technology |
|---|---|
| API Framework | FastAPI + Pydantic v2 |
| Demo UI | Streamlit |
| ML Pipeline | Scikit-learn (TF-IDF + Gradient Boosting) |
| Experiment Tracking | MLflow (tracking + model registry) |
| Containerization | Docker + docker-compose (4 services) |
| CI/CD | GitHub Actions (lint, test, build) |
| Testing | Pytest + pytest-asyncio |
| Linting | Ruff |
| Data Source | 7.3K LinkedIn job postings (Kaggle) |
# Install dependencies
pip install -r requirements.txt -r requirements-dev.txt
# Preprocess real data (requires data/raw/ CSVs from Kaggle)
make preprocess
# Train all models (Logistic Regression, Random Forest, Gradient Boosting)
make train
# Start API server
make serve
# -> http://localhost:8000/docs# Preprocess + train locally first (generates artifacts)
make preprocess
make train
# Start full stack: API + MLflow + Streamlit UI
docker compose up -d
# -> Streamlit UI: http://localhost:8501
# -> FastAPI Docs: http://localhost:8000/docs
# -> MLflow UI: http://localhost:5001
# Run training inside Docker (logs to MLflow server)
docker compose --profile train run trainer# Classify a job posting
curl -X POST http://localhost:8000/classify \
-H "Content-Type: application/json" \
-d '{
"title": "Senior Machine Learning Engineer",
"description": "Build production ML pipelines using PyTorch and Kubernetes. Experience with MLflow and distributed training required. 5+ years."
}'
# Response:
# {
# "predicted_role": "ml_engineer",
# "confidence": 0.97,
# "probabilities": {"ml_engineer": 0.97, "data_engineer": 0.02, "data_scientist": 0.01},
# "skills": [
# {"skill": "PyTorch", "confidence": 0.8, "primary_roles": ["ml_engineer"]},
# {"skill": "Machine Learning", "confidence": 0.8, "primary_roles": ["ml_engineer"]}
# ]
# }
# Extract skills only
curl -X POST http://localhost:8000/extract-skills \
-H "Content-Type: application/json" \
-d '{
"title": "Data Engineer",
"description": "Design ETL pipelines with Apache Spark, Airflow, and dbt. Experience with Snowflake and AWS required."
}'
# Batch classification (up to 100 postings)
curl -X POST http://localhost:8000/batch-classify \
-H "Content-Type: application/json" \
-d '{"postings": [{"title": "ML Engineer", "description": "..."}, {"title": "DE", "description": "..."}]}'
# Health check
curl http://localhost:8000/healthRaw data flows through three stages:
-
Raw ingestion (
data/raw/): Kaggle CSVs containing 12K+ LinkedIn job postings with titles, metadata, and extracted skills. -
Preprocessing (
src/data/preprocess.py): Assigns role labels via keyword matching on job titles (60% mapping rate), merges postings with skills, builds text features (title | skills), and generates a real-data skill taxonomy. -
Training (
src/models/train.py): Trains 3 classifier types with TF-IDF vectorization, logs all experiments to MLflow, selects the best model by F1 macro, and saves it for API serving.
job-intelligence-api/
├── src/
│ ├── api/
│ │ ├── app.py # FastAPI application (4 endpoints)
│ │ └── schemas.py # Pydantic request/response models
│ ├── data/
│ │ ├── preprocess.py # Real data preprocessing pipeline
│ │ └── generate.py # Synthetic data generator (fallback)
│ ├── models/
│ │ ├── train.py # Multi-model training + MLflow tracking
│ │ └── skill_extractor.py # Taxonomy-based skill extraction
│ └── utils/
├── tests/
│ ├── test_api.py # API endpoint tests
│ ├── test_data.py # Data generation tests
│ └── test_skill_extractor.py # Skill extraction tests
├── configs/
│ └── train_config.yaml # Training hyperparameters
├── data/
│ └── raw/ # Kaggle CSVs (not tracked in git)
├── .github/workflows/
│ └── ci.yml # CI/CD pipeline
├── Dockerfile # API container
├── Dockerfile.streamlit # UI container
├── streamlit_app.py # Demo UI
├── docker-compose.yml # 4 services: API, MLflow, UI, Trainer
├── Makefile
├── pyproject.toml
└── requirements.txt
make preprocess # Preprocess Kaggle data
make train # Train all models
make serve # Start API locally
make test # Run test suite
make lint # Check code style
make format # Auto-format code
make docker-up # Start Docker services
make docker-down # Stop Docker services
make help # Show all commandsThis project uses the Data Science Job Postings & Skills (2024) dataset by asaniczka. The raw CSVs are not included in this repository. To reproduce:
- Download the dataset from Kaggle
- Place
job_postings.csvandjob_skills.csvindata/raw/ - Run
make preprocessto generate the training data