--- license: mit tags: - text-classification - task-duration-estimation - logistic-regression - scikit-learn - fastapi - jira pipeline_tag: text-classification --- # Jira Task Duration Classifier This repository packages a machine learning API that predicts whether a Jira-style software issue is likely to be **Short**, **Standard**, or **Long-running** based on the issue text and metadata. The repository includes the trained model, a reproducible training script, cleaned sample data, evaluation artifacts, notebooks, and a FastAPI inference service. ## What It Predicts The model predicts an ordinal duration class, not an exact completion date. | Class | Duration rule used during labeling | | --- | --- | | `Short` | 3 days or less | | `Standard` | More than 3 days and up to 15 days | | `Long-running` | More than 15 days | The prediction is useful for early planning, triage, backlog sizing, and identifying tasks that may need more discovery before assignment. ## Project Highlights - **Model:** scikit-learn `Pipeline` with TF-IDF text features, one-hot categorical features, scaled numeric features, and Logistic Regression. - **API:** FastAPI service with `/health`, `/predict`, `/docs`, and `/openapi.json`. - **Inputs:** issue summary, description, priority, issue type, project metadata, date metadata, labels, assignee flag, votes, and watches. - **Output:** predicted class plus class probabilities. - **Evaluation:** held-out stratified test set with 20,315 examples and 0.80 accuracy. - **Data source:** Apache Jira issues dataset from Kaggle. ## Repository Contents ```text task-duration-classifier-hf/ |-- api/ | `-- main.py |-- data/ | `-- processed/ | |-- final_cleaned_sample.csv | `-- jira_issues_cleaned_sample.csv |-- models/ | `-- duration_logistic_regression_classifier.joblib |-- model_tests/ | |-- classification_report.txt | |-- confusion_matrix.png | `-- metric_charts/ |-- notebooks/ | |-- 01-exploratory-data-analysis.ipynb | |-- 02-data-cleaning.ipynb | |-- 03-feature-engineering.ipynb | `-- rebuild_data_notebooks.py `-- training/ |-- evaluate_data_filters.py `-- model_training.py ``` ## How It Works The model is trained from completed Jira issues. For each issue, the pipeline combines: - `total_text`: summary plus description, vectorized with TF-IDF unigrams and bigrams. - Categorical fields: priority, issue type, project key, project category, created year, and created month. - Numeric fields: text lengths, word counts, description presence, label count, assignee presence, votes, and watches. The training script builds a single scikit-learn pipeline: 1. `TfidfVectorizer(max_features=10000, stop_words="english", ngram_range=(1, 2), min_df=5, max_df=0.9, sublinear_tf=True)` 2. `OneHotEncoder(handle_unknown="ignore")` for categorical metadata 3. `SimpleImputer(strategy="median")` and `StandardScaler()` for numeric metadata 4. `LogisticRegression(solver="saga", penalty="l2", max_iter=1200, random_state=42)` The saved artifact is: ```text models/duration_logistic_regression_classifier.joblib ``` ## Data Source And Preparation The original data source is the Kaggle **Apache Jira Issues** dataset: ```text https://www.kaggle.com/datasets/tedlozzo/apaches-jira-issues ``` The notebooks document the data workflow: | Notebook | Purpose | | --- | --- | | `01-exploratory-data-analysis.ipynb` | Inspects raw fields, missingness, timestamps, text lengths, category distributions, and duration spread. | | `02-data-cleaning.ipynb` | Keeps completed issues, validates created/resolution timestamps, normalizes text and category fields, and creates count/flag features. | | `03-feature-engineering.ipynb` | Creates `duration_days`, assigns duration labels, filters noisy class boundaries, controls project dominance, and balances classes. | Dataset flow from the source project: | Stage | Rows | | --- | ---: | | Raw rows loaded in cleaning notebook | 1,149,323 | | Completed issues after timestamp filtering | 945,103 | | After duplicate removal | 937,203 | | After consistency filtering | 258,722 | | After project/class cap | 218,629 | | Final balanced modeling rows | 101,571 | | Rows per final duration class | 33,857 | | Held-out test rows | 20,315 | This Hugging Face repository includes sample processed CSVs for inspection. The full raw dataset is not included because it is large and should be downloaded from Kaggle. Sample class distribution in `data/processed/final_cleaned_sample.csv`: | Class | Sample rows | | --- | ---: | | `Short` | 27 | | `Standard` | 37 | | `Long-running` | 36 | Example processed row: | Field | Value | | --- | --- | | `summary` | `MailConsumer: Move mail after processing` | | `issuetype_name` | `New Feature` | | `priority_name` | `Major` | | `project_key` | `CAMEL` | | `has_description` | `1` | | `duration_category` | `Short` | ## Evaluation Metrics The model was evaluated on a stratified held-out test split of 20,315 examples. | Class | Precision | Recall | F1-score | Support | | --- | ---: | ---: | ---: | ---: | | `Long-running` | 0.79 | 0.71 | 0.74 | 6,771 | | `Short` | 0.85 | 0.87 | 0.86 | 6,772 | | `Standard` | 0.76 | 0.81 | 0.79 | 6,772 | | **Accuracy** | | | **0.80** | **20,315** | | **Macro avg** | **0.80** | **0.80** | **0.80** | **20,315** | | **Weighted avg** | **0.80** | **0.80** | **0.80** | **20,315** | ### Confusion Matrix ![Confusion matrix](model_tests/confusion_matrix.png) ### Per-Class Metrics ![Classification metrics by class](model_tests/metric_charts/classification_metrics_by_class.png) ![F1 score by class](model_tests/metric_charts/f1_score_by_class.png) The strongest class is `Short` with an F1-score of 0.86. `Long-running` is the hardest class, mainly because it has lower recall: some long tasks are predicted as shorter categories. ## API Usage Run the API locally from the repository root: ```bash python -m pip install fastapi uvicorn pandas scikit-learn joblib pydantic python -m uvicorn api.main:app --host 0.0.0.0 --port 8000 ``` Open the interactive API docs: ```text http://localhost:8000/docs ``` ### Endpoints | Method | Endpoint | Description | | --- | --- | --- | | `GET` | `/` | API metadata | | `GET` | `/health` | Service status and model metadata | | `POST` | `/predict` | Predicts duration class and probabilities | | `GET` | `/docs` | Swagger UI | | `GET` | `/openapi.json` | OpenAPI schema | ### Example Request ```bash curl -X POST "http://localhost:8000/predict" \ -H "Content-Type: application/json" \ -H "Origin: http://localhost:8000" \ -d '{ "summary": "Fix CORS error on user endpoint", "description": "Frontend requests are failing in production because the API response is missing an Access-Control-Allow-Origin header.", "issuetype_name": "Bug", "priority_name": "High", "project_key": "WEB", "project_category_name": "Application", "created_year": 2026, "created_month": 7, "labels_count": 2, "has_assignee": 1, "votes_votes": 1, "watches_watch_count": 4 }' ``` Example response shape: ```json { "duration_category": "Standard", "probabilities": { "Long-running": 0.18, "Short": 0.27, "Standard": 0.55 } } ``` ## Reproducing Training The training script expects the full processed dataset at: ```text data/processed/final_cleaned.csv ``` Then run: ```bash python -m pip install pandas scikit-learn joblib matplotlib python training/model_training.py ``` Training writes: ```text models/duration_logistic_regression_classifier.joblib model_tests/classification_report.txt model_tests/confusion_matrix.png ``` The included `final_cleaned_sample.csv` is intended for inspection and API/schema understanding, not for full model reproduction. ## Limitations - The model learns from historical Apache Jira patterns, so predictions may not transfer cleanly to private teams, different workflows, or non-software project management data. - Duration is affected by team capacity, priority changes, dependencies, release schedules, and human process factors that are not fully represented in the input features. - The labels are coarse buckets. The model does not estimate exact completion time. - Some boundary cases are inherently ambiguous, especially tasks near the 3-day and 15-day thresholds. - The included sample CSVs are for transparency and demonstration; full training requires rebuilding or downloading the larger processed dataset.