An ML + LLM pipeline for automated sprint retrospective analysis.
Given Jira (or Jira-compatible) sprint export data, this engine:
- Classifies custom field names to standard semantic fields automatically
- Computes baselines (median cycle times, delivery rates) per team and issue type
- Detects bottlenecks and flags delayed issues in each sprint
- Classifies root causes (assignee overload, late handoff, dependency delay, scope creep, hygiene)
- Detects cross-sprint patterns (delivery trend, recurring bottlenecks, scope creep frequency)
- Generates narrative analysis using an LLM (Claude or Mistral) explaining why patterns exist
- Produces Word reports (.docx) with charts, metrics, and recommendations
data/sprint_data.json
│
▼
data_loader.py ← load + validate, compute state durations
│
├── field_classifier_v2.py (optional: auto-map custom Jira field names)
│
▼
baseline_engine.py ← compute per-team, per-type cycle time baselines
│
▼
analysis_engine.py ← analyse all sprints, flag delayed issues
│
├── reasoning_engine.py ← ML root cause classification
│ (assignee_overloaded, late_handoff,
│ dependency_delay, hygiene_issue,
│ scope_creep)
├── severity_classifier.py ← severity scoring per issue
│
▼
pattern_detector.py ← detect cross-sprint trends
│
▼
llm_analysis_layer.py ← LLM deep "WHY" analysis for each sprint
│
▼
report_generator.js ← generate .docx Word report
For single-sprint analysis with LLM narrative:
single_sprint_report.py → single_sprint_report.js → .docx
Your sprint data should be a JSON array of issue objects:
[
{
"issue_id": "PROJ-101",
"title": "Implement OAuth2 login",
"type": "story",
"story_points": 5,
"assignee": "Alice Chen",
"sprint_id": "sprint_01",
"sprint_start": "2024-01-08",
"sprint_end": "2024-01-19",
"committed": true,
"delivered": true,
"states": [
{"state": "todo", "entered": "2024-01-08", "exited": "2024-01-09"},
{"state": "in_progress", "entered": "2024-01-09", "exited": "2024-01-13"},
{"state": "code_review", "entered": "2024-01-13", "exited": "2024-01-15"},
{"state": "qa", "entered": "2024-01-15", "exited": "2024-01-17"},
{"state": "done", "entered": "2024-01-17", "exited": null}
]
}
]If your Jira export uses custom field names (custom_120, sp, ticket_owner, etc.),
use load_unknown_schema() in data_loader.py -- the field classifier will auto-map them.
See data/sample_sprint_data.json for a complete working example.
python3 -m venv venv
source venv/bin/activate
pip install pandas numpy scikit-learn scipy
# For Word report generation
npm install
# For LLM analysis
export ANTHROPIC_API_KEY=sk-ant-your-key-here
# or for Mistral:
export LLM_BACKEND=mistral
export MISTRAL_BASE_URL=http://your-server:11434Train the ML models first (one-time setup):
python3 field_classifier_v2.py # trains field name classifier
python3 reasoning_engine.py # trains root cause classifier
python3 severity_classifier.py # trains severity scorerRun full pipeline on all datasets:
python3 run_all.pyRun single sprint analysis with LLM narrative:
python3 single_sprint_report.py --sprint sprint_01 --data data/sample_sprint_data.jsonRun on unknown schema (custom Jira field names):
from data_loader import load_unknown_schema
df = load_unknown_schema("data/your_jira_export.json")data/retrospective_team_a.docx-- Word report with charts, metrics, and recommendationsdata/single_sprint_data.json-- structured JSON for single sprint analysisdata/baselines_*.json-- computed baselines per teamdata/report_data.json-- full structured report data
| Category | Description |
|---|---|
assignee_overloaded |
Assignee had too many concurrent tickets in this stage |
late_handoff |
Ticket arrived at this stage too late in the sprint |
dependency_delay |
Ticket was blocked waiting on another ticket or external dependency |
hygiene_issue |
Ticket wasn't updated for multiple days; may not reflect real state |
scope_creep |
Ticket was added after sprint start and disrupted planned work |
| Backend | Model | How to use |
|---|---|---|
| Claude (default) | claude-sonnet-4-6 | export ANTHROPIC_API_KEY=... |
| Mistral (self-hosted) | mistral-small | export LLM_BACKEND=mistral + MISTRAL_BASE_URL |
| Fallback (no LLM) | rule-based | automatically used if no API key set |
- Python 3.9+
- pandas, numpy, scikit-learn, scipy
- Node.js 18+ (for .docx generation)
- Anthropic or Mistral API key (optional -- falls back to rule-based analysis)