Engineering Metrics: DORA and SPACE Frameworks
Modern software engineering organizations rely on data-driven insights to measure team performance, identify bottlenecks, and continuously improve delivery processes. Two complementary frameworks have emerged as industry standards: DORA (DevOps Research and Assessment) and SPACE. This tutorial covers both frameworks in depth, provides practical implementation examples, and explores how to integrate them into your engineering workflows.
What Is the DORA Framework?
The DORA framework was developed by the DevOps Research and Assessment team at Google Cloud after years of surveying thousands of engineering teams. It identifies four key metrics that correlate strongly with software delivery performance and organizational success:
- Deployment Frequency — How often an organization deploys code to production or releases to end users
- Lead Time for Changes — The time from code commit to code successfully running in production
- Mean Time to Restore (MTTR) — The time it takes to recover from a failure in production (incident, outage, or service degradation)
- Change Failure Rate — The percentage of deployments that cause a failure in production requiring remediation
Why DORA Matters
These four metrics provide a balanced view of both velocity (deployment frequency and lead time) and stability (MTTR and change failure rate). Research shows that teams optimizing for all four metrics simultaneously achieve higher levels of reliability, faster time-to-market, and greater job satisfaction. Elite-performing teams deploy on demand, achieve lead times under one hour, recover from incidents in under an hour, and maintain change failure rates below 5%.
Implementing DORA Metrics: Practical Code Examples
1. Collecting Deployment Frequency
Deployment frequency can be tracked by querying your CI/CD pipeline events or deployment logs. Below is a Python script that calculates deployment frequency from a simulated deployment log stored as JSON:
import json
from datetime import datetime, timedelta
from collections import defaultdict
# Sample deployment log (in practice, pull this from your CI/CD API)
deployment_log = [
{"timestamp": "2025-01-10T08:30:00Z", "environment": "production", "status": "success"},
{"timestamp": "2025-01-10T14:15:00Z", "environment": "production", "status": "success"},
{"timestamp": "2025-01-11T09:00:00Z", "environment": "production", "status": "success"},
{"timestamp": "2025-01-11T16:45:00Z", "environment": "production", "status": "failed"},
{"timestamp": "2025-01-12T10:00:00Z", "environment": "production", "status": "success"},
{"timestamp": "2025-01-13T08:30:00Z", "environment": "production", "status": "success"},
{"timestamp": "2025-01-13T14:00:00Z", "environment": "production", "status": "success"},
]
def calculate_deployment_frequency(deployments, target_env="production"):
"""Calculate daily deployment frequency for a given environment."""
daily_counts = defaultdict(int)
for deploy in deployments:
if deploy["environment"] == target_env and deploy["status"] == "success":
date = datetime.fromisoformat(deploy["timestamp"].replace("Z", "+00:00")).date()
daily_counts[date] += 1
total_days = len(daily_counts)
total_deployments = sum(daily_counts.values())
avg_frequency = total_deployments / total_days if total_days > 0 else 0
print(f"Average daily deployment frequency: {avg_frequency:.2f}")
print(f"Total successful deployments: {total_deployments} over {total_days} days")
return avg_frequency
calculate_deployment_frequency(deployment_log)
2. Calculating Lead Time for Changes
Lead time is measured from the moment a commit is made to when it reaches production. This requires correlating commit timestamps with deployment timestamps. Here is a script that computes lead time using a simulated commit-to-deploy mapping:
from datetime import datetime, timedelta
import statistics
# Sample data: each record links a commit to its production deployment
commit_to_deploy = [
{"commit_hash": "abc123", "commit_time": "2025-01-12T09:00:00Z", "deploy_time": "2025-01-12T10:30:00Z"},
{"commit_hash": "def456", "commit_time": "2025-01-12T11:00:00Z", "deploy_time": "2025-01-12T12:45:00Z"},
{"commit_hash": "ghi789", "commit_time": "2025-01-12T13:00:00Z", "deploy_time": "2025-01-12T14:10:00Z"},
{"commit_hash": "jkl012", "commit_time": "2025-01-12T15:00:00Z", "deploy_time": "2025-01-12T15:55:00Z"},
{"commit_hash": "mno345", "commit_time": "2025-01-12T16:30:00Z", "deploy_time": "2025-01-12T18:20:00Z"},
]
def calculate_lead_time(records):
"""Calculate lead time in minutes for each commit-to-deploy pair."""
lead_times = []
for record in records:
commit_dt = datetime.fromisoformat(record["commit_time"].replace("Z", "+00:00"))
deploy_dt = datetime.fromisoformat(record["deploy_time"].replace("Z", "+00:00"))
delta_minutes = (deploy_dt - commit_dt).total_seconds() / 60
lead_times.append(delta_minutes)
print(f"Commit {record['commit_hash']}: lead time = {delta_minutes:.0f} minutes")
p50 = statistics.median(lead_times)
p95 = sorted(lead_times)[int(len(lead_times) * 0.95) - 1] if len(lead_times) > 1 else lead_times[0]
print(f"\nMedian lead time (P50): {p50:.0f} minutes")
print(f"P95 lead time: {p95:.0f} minutes")
return lead_times
calculate_lead_time(commit_to_deploy)
3. Measuring Mean Time to Restore (MTTR)
MTTR tracks how quickly your team resolves incidents. The following script processes incident data to compute MTTR:
from datetime import datetime
import statistics
# Sample incident records with start and resolution times
incidents = [
{"id": "INC-001", "severity": "critical", "start_time": "2025-01-15T14:00:00Z", "resolution_time": "2025-01-15T14:42:00Z"},
{"id": "INC-002", "severity": "high", "start_time": "2025-01-15T16:00:00Z", "resolution_time": "2025-01-15T17:15:00Z"},
{"id": "INC-003", "severity": "medium", "start_time": "2025-01-16T09:30:00Z", "resolution_time": "2025-01-16T10:05:00Z"},
{"id": "INC-004", "severity": "critical", "start_time": "2025-01-16T13:00:00Z", "resolution_time": "2025-01-16T14:30:00Z"},
{"id": "INC-005", "severity": "high", "start_time": "2025-01-17T08:00:00Z", "resolution_time": "2025-01-17T08:55:00Z"},
]
def calculate_mttr(incident_list):
"""Calculate Mean Time to Restore from incident data."""
restore_times = []
for inc in incident_list:
start = datetime.fromisoformat(inc["start_time"].replace("Z", "+00:00"))
end = datetime.fromisoformat(inc["resolution_time"].replace("Z", "+00:00"))
minutes = (end - start).total_seconds() / 60
restore_times.append(minutes)
print(f"{inc['id']} ({inc['severity']}): restored in {minutes:.0f} minutes")
mttr = statistics.mean(restore_times)
median_ttr = statistics.median(restore_times)
print(f"\nMean Time to Restore (MTTR): {mttr:.0f} minutes")
print(f"Median Time to Restore: {median_ttr:.0f} minutes")
# Breakdown by severity
severity_groups = {}
for inc in incident_list:
severity_groups.setdefault(inc["severity"], []).append(
(datetime.fromisoformat(inc["resolution_time"].replace("Z", "+00:00")) -
datetime.fromisoformat(inc["start_time"].replace("Z", "+00:00"))).total_seconds() / 60
)
for severity, times in severity_groups.items():
print(f" MTTR for {severity}: {statistics.mean(times):.0f} minutes")
return mttr
calculate_mttr(incidents)
4. Computing Change Failure Rate
Change failure rate is the ratio of deployments that cause incidents to total deployments. This script demonstrates the calculation:
from datetime import datetime
# All deployments in a given period
all_deployments = [
{"id": "DEP-100", "timestamp": "2025-01-20T10:00:00Z", "caused_incident": False},
{"id": "DEP-101", "timestamp": "2025-01-20T14:00:00Z", "caused_incident": True},
{"id": "DEP-102", "timestamp": "2025-01-21T09:00:00Z", "caused_incident": False},
{"id": "DEP-103", "timestamp": "2025-01-21T11:00:00Z", "caused_incident": False},
{"id": "DEP-104", "timestamp": "2025-01-21T15:00:00Z", "caused_incident": True},
{"id": "DEP-105", "timestamp": "2025-01-22T08:30:00Z", "caused_incident": False},
{"id": "DEP-106", "timestamp": "2025-01-22T12:00:00Z", "caused_incident": False},
{"id": "DEP-107", "timestamp": "2025-01-22T16:00:00Z", "caused_incident": False},
{"id": "DEP-108", "timestamp": "2025-01-23T10:00:00Z", "caused_incident": False},
{"id": "DEP-109", "timestamp": "2025-01-23T14:00:00Z", "caused_incident": True},
]
def calculate_change_failure_rate(deployments):
"""Calculate the percentage of deployments that result in incidents."""
total = len(deployments)
failures = sum(1 for d in deployments if d["caused_incident"])
rate = (failures / total) * 100 if total > 0 else 0
print(f"Total deployments: {total}")
print(f"Failed deployments: {failures}")
print(f"Change Failure Rate: {rate:.1f}%")
# Categorize performance based on DORA benchmarks
if rate <= 5:
level = "Elite (≤5%)"
elif rate <= 10:
level = "High (≤10%)"
elif rate <= 15:
level = "Medium (≤15%)"
else:
level = "Low (>15%)"
print(f"Performance level: {level}")
return rate
calculate_change_failure_rate(all_deployments)
The SPACE Framework
While DORA focuses narrowly on delivery pipeline metrics, the SPACE framework (developed by GitHub researchers and Microsoft) provides a broader, more holistic view of developer productivity and team health. SPACE stands for:
- S — Satisfaction: How happy developers are with their tools, workflows, and team culture. Measured through surveys and sentiment analysis.
- P — Performance: The quality and impact of work produced, including code reliability, system uptime, and user satisfaction with the product.
- A — Activity: The volume of work completed, such as commits, pull requests, code reviews, and deployments.
- C — Communication: How effectively team members collaborate, including meeting load, documentation quality, and knowledge sharing.
- E — Efficiency: The resources required to achieve outcomes, including build times, CI pipeline duration, and time spent on manual tasks.
Why SPACE Matters
SPACE acknowledges that engineering productivity cannot be reduced to a single number. A team with high deployment frequency might be burning out developers or sacrificing code quality. By measuring across all five dimensions, organizations gain a nuanced understanding of team health and can spot negative trade-offs before they become systemic problems.
Implementing SPACE Metrics
S — Satisfaction: Developer Survey Pipeline
Satisfaction is best measured through regular, anonymous surveys. Below is a simple survey processing script that calculates satisfaction scores:
import json
import statistics
# Sample survey responses (1-5 Likert scale)
survey_responses = [
{"respondent_id": 1, "tool_satisfaction": 4, "workflow_satisfaction": 3, "team_collaboration": 5, "work_life_balance": 4},
{"respondent_id": 2, "tool_satisfaction": 5, "workflow_satisfaction": 4, "team_collaboration": 4, "work_life_balance": 3},
{"respondent_id": 3, "tool_satisfaction": 3, "workflow_satisfaction": 2, "team_collaboration": 4, "work_life_balance": 5},
{"respondent_id": 4, "tool_satisfaction": 4, "workflow_satisfaction": 5, "team_collaboration": 3, "work_life_balance": 4},
{"respondent_id": 5, "tool_satisfaction": 2, "workflow_satisfaction": 3, "team_collaboration": 4, "work_life_balance": 3},
]
def compute_satisfaction_scores(responses):
"""Calculate average satisfaction across dimensions."""
dimensions = ["tool_satisfaction", "workflow_satisfaction", "team_collaboration", "work_life_balance"]
for dim in dimensions:
scores = [r[dim] for r in responses]
avg = statistics.mean(scores)
print(f"{dim}: average = {avg:.2f} (min={min(scores)}, max={max(scores)})")
# Overall composite satisfaction score
all_scores = []
for r in responses:
respondent_avg = statistics.mean([r[d] for d in dimensions])
all_scores.append(respondent_avg)
overall_satisfaction = statistics.mean(all_scores)
print(f"\nOverall composite satisfaction: {overall_satisfaction:.2f}/5.00")
# Detect concerning signals
for dim in dimensions:
scores = [r[dim] for r in responses]
low_count = sum(1 for s in scores if s <= 2)
if low_count > 0:
print(f" ⚠ {dim}: {low_count} respondent(s) scored ≤ 2 — investigate")
compute_satisfaction_scores(survey_responses)
P — Performance: Quality Metrics Dashboard Query
Performance metrics include code quality, reliability, and user-facing outcomes. This example shows how to aggregate performance indicators from multiple sources:
from datetime import datetime, timedelta
import json
# Simulated performance data from various sources
performance_data = {
"code_quality": {
"unit_test_coverage_percent": 87,
"integration_test_pass_rate_percent": 96,
"static_analysis_warnings": 34,
"code_review_acceptance_rate_percent": 92
},
"reliability": {
"uptime_percent": 99.97,
"p99_latency_ms": 245,
"error_rate_percent": 0.03,
"successful_deployments_percent": 94
},
"user_satisfaction": {
"nps_score": 45,
"support_tickets_weekly": 12,
"p0_bugs_open": 3,
"p1_bugs_open": 8
}
}
def evaluate_performance_metrics(data):
"""Assess performance across quality, reliability, and user satisfaction."""
# Code quality assessment
quality = data["code_quality"]
quality_score = 0
if quality["unit_test_coverage_percent"] >= 80:
quality_score += 25
print(f"✓ Test coverage: {quality['unit_test_coverage_percent']}% (threshold met)")
else:
print(f"✗ Test coverage: {quality['unit_test_coverage_percent']}% (below 80% target)")
if quality["integration_test_pass_rate_percent"] >= 95:
quality_score += 25
print(f"✓ Integration test pass rate: {quality['integration_test_pass_rate_percent']}%")
if quality["static_analysis_warnings"] <= 50:
quality_score += 25
print(f"✓ Static analysis warnings: {quality['static_analysis_warnings']} (under 50)")
if quality["code_review_acceptance_rate_percent"] >= 90:
quality_score += 25
print(f"✓ Code review acceptance rate: {quality['code_review_acceptance_rate_percent']}%")
print(f"Code quality score: {quality_score}/100")
# Reliability assessment
reliability = data["reliability"]
print(f"\nReliability indicators:")
print(f" Uptime: {reliability['uptime_percent']}%")
print(f" P99 latency: {reliability['p99_latency_ms']}ms")
print(f" Error rate: {reliability['error_rate_percent']}%")
# User satisfaction indicators
user = data["user_satisfaction"]
print(f"\nUser satisfaction indicators:")
print(f" NPS score: {user['nps_score']}")
print(f" Open P0 bugs: {user['p0_bugs_open']}")
print(f" Open P1 bugs: {user['p1_bugs_open']}")
return quality_score
evaluate_performance_metrics(performance_data)
A — Activity: Engineering Activity Log Analyzer
Activity metrics track the volume of engineering work. This script analyzes activity data from a simulated GitHub-style event log:
from datetime import datetime, timedelta
from collections import defaultdict
# Simulated engineering activity log
activity_log = [
{"date": "2025-01-20", "user": "alice", "event": "commit", "count": 8},
{"date": "2025-01-20", "user": "bob", "event": "commit", "count": 6},
{"date": "2025-01-20", "user": "alice", "event": "pull_request_created", "count": 3},
{"date": "2025-01-20", "user": "charlie", "event": "code_review_completed", "count": 7},
{"date": "2025-01-21", "user": "alice", "event": "commit", "count": 10},
{"date": "2025-01-21", "user": "bob", "event": "commit", "count": 4},
{"date": "2025-01-21", "user": "charlie", "event": "pull_request_merged", "count": 5},
{"date": "2025-01-21", "user": "alice", "event": "code_review_completed", "count": 4},
{"date": "2025-01-22", "user": "bob", "event": "commit", "count": 7},
{"date": "2025-01-22", "user": "charlie", "event": "commit", "count": 9},
{"date": "2025-01-22", "user": "alice", "event": "pull_request_merged", "count": 6},
{"date": "2025-01-22", "user": "bob", "event": "code_review_completed", "count": 5},
]
def analyze_activity(events):
"""Summarize engineering activity by type and contributor."""
# Aggregate by event type
event_totals = defaultdict(int)
user_activity = defaultdict(lambda: defaultdict(int))
for entry in events:
event_totals[entry["event"]] += entry["count"]
user_activity[entry["user"]][entry["event"]] += entry["count"]
print("=== Activity by Event Type ===")
for event, total in sorted(event_totals.items(), key=lambda x: x[1], reverse=True):
print(f" {event}: {total}")
print("\n=== Activity by Contributor ===")
for user, activities in sorted(user_activity.items()):
total = sum(activities.values())
print(f" {user}: {total} total actions")
for event, count in activities.items():
print(f" - {event}: {count}")
# Detect potential imbalances
print("\n=== Activity Balance Check ===")
user_totals = {user: sum(acts.values()) for user, acts in user_activity.items()}
max_actions = max(user_totals.values())
min_actions = min(user_totals.values())
if max_actions > min_actions * 3:
print(" ⚠ High activity disparity detected between contributors")
print(f" Max: {max_actions}, Min: {min_actions}")
else:
print(" ✓ Activity distribution is reasonably balanced")
analyze_activity(activity_log)
C — Communication & E — Efficiency: Combined Pipeline Analyzer
Communication and Efficiency metrics can be derived from collaboration tool data and CI/CD pipeline statistics. This combined script measures both dimensions:
from datetime import datetime, timedelta
# Sample collaboration and pipeline data
collaboration_data = {
"meetings": {
"avg_weekly_meeting_hours_per_dev": 8.5,
"standup_duration_minutes": 15,
"adhoc_meetings_per_week": 4,
"focus_time_percent": 62
},
"documentation": {
"wiki_pages_updated_weekly": 12,
"design_docs_reviewed_weekly": 5,
"onboarding_docs_up_to_date": True
},
"knowledge_sharing": {
"internal_tech_talks_monthly": 3,
"mentoring_sessions_weekly": 6,
"code_review_threads_avg_depth": 4.2
}
}
pipeline_data = {
"ci_pipeline": {
"avg_build_time_minutes": 12,
"avg_test_suite_minutes": 8,
"pipeline_success_rate_percent": 88,
"flaky_test_percentage": 3.5
},
"cd_pipeline": {
"avg_deploy_time_minutes": 5,
"rollback_rate_percent": 4,
"manual_intervention_required_percent": 12
},
"developer_experience": {
"local_build_time_minutes": 3,
"hot_reload_latency_seconds": 0.8,
"time_to_first_review_hours": 6.5
}
}
def assess_communication_and_efficiency(collab, pipeline):
"""Evaluate communication health and pipeline efficiency."""
print("=== Communication Metrics ===")
# Meeting health
meeting_hours = collab["meetings"]["avg_weekly_meeting_hours_per_dev"]
focus_percent = collab["meetings"]["focus_time_percent"]
print(f"Weekly meeting hours per dev: {meeting_hours}h")
print(f"Focus time: {focus_percent}%")
if meeting_hours > 12:
print(" ⚠ High meeting load — consider async standups and no-meeting days")
elif meeting_hours < 4:
print(" ⚠ Very low meeting load — check for insufficient coordination")
else:
print(" ✓ Meeting load is within healthy range")
# Documentation health
doc_updates = collab["documentation"]["wiki_pages_updated_weekly"]
print(f"\nWiki pages updated weekly: {doc_updates}")
print(f"Onboarding docs current: {'✓' if collab['documentation']['onboarding_docs_up_to_date'] else '✗ Needs update'}")
# Knowledge sharing
print(f"\nInternal tech talks (monthly): {collab['knowledge_sharing']['internal_tech_talks_monthly']}")
print(f"Avg code review thread depth: {collab['knowledge_sharing']['code_review_threads_avg_depth']}")
print(f"Mentoring sessions (weekly): {collab['knowledge_sharing']['mentoring_sessions_weekly']}")
print("\n=== Efficiency Metrics ===")
ci = pipeline["ci_pipeline"]
cd = pipeline["cd_pipeline"]
dx = pipeline["developer_experience"]
total_ci_time = ci["avg_build_time_minutes"] + ci["avg_test_suite_minutes"]
print(f"Total CI pipeline time: {total_ci_time} minutes")
print(f"Pipeline success rate: {ci['pipeline_success_rate_percent']}%")
print(f"Flaky test percentage: {ci['flaky_test_percentage']}%")
if ci["flaky_test_percentage"] > 5:
print(" ⚠ Flaky test rate above 5% — prioritize test stabilization")
print(f"\nAvg deploy time: {cd['avg_deploy_time_minutes']} minutes")
print(f"Rollback rate: {cd['rollback_rate_percent']}%")
print(f"Manual intervention required: {cd['manual_intervention_required_percent']}% of deployments")
print(f"\nLocal build time: {dx['local_build_time_minutes']} minutes")
print(f"Hot reload latency: {dx['hot_reload_latency_seconds']} seconds")
print(f"Time to first code review: {dx['time_to_first_review_hours']} hours")
# Efficiency score calculation
efficiency_score = 100
if total_ci_time > 30:
efficiency_score -= 15
print(" -15: CI pipeline exceeds 30 minutes")
if ci["flaky_test_percentage"] > 5:
efficiency_score -= 10
print(" -10: Flaky tests above threshold")
if cd["manual_intervention_required_percent"] > 10:
efficiency_score -= 15
print(" -15: High manual intervention in deployments")
if dx["time_to_first_review_hours"] > 24:
efficiency_score -= 10
print(" -10: Time to first review exceeds 24 hours")
print(f"\nEfficiency score: {efficiency_score}/100")
assess_communication_and_efficiency(collaboration_data, pipeline_data)
Combining DORA and SPACE: A Unified Dashboard
The true power of these frameworks emerges when you combine them. DORA tells you how well your delivery pipeline is performing; SPACE tells you whether that performance is sustainable and aligned with developer experience. Below is a unified reporting script that integrates both frameworks into a single dashboard output:
from datetime import datetime
import statistics
import json
class EngineeringMetricsDashboard:
"""
Unified dashboard that combines DORA and SPACE metrics
into a single comprehensive engineering health report.
"""
def __init__(self, team_name, reporting_period):
self.team_name = team_name
self.reporting_period = reporting_period
self.dora_metrics = {}
self.space_metrics = {}
def load_dora_data(self, deployment_frequency, lead_time_minutes,
mttr_minutes, change_failure_rate_percent):
"""Ingest DORA metrics."""
self.dora_metrics = {
"deployment_frequency_daily": deployment_frequency,
"lead_time_median_minutes": lead_time_minutes,
"mttr_minutes": mttr_minutes,
"change_failure_rate_percent": change_failure_rate_percent
}
def load_space_data(self, satisfaction_score, performance_score,
activity_summary, communication_score, efficiency_score):
"""Ingest SPACE metrics."""
self.space_metrics = {
"satisfaction": satisfaction_score,
"performance": performance_score,
"activity": activity_summary,
"communication": communication_score,
"efficiency": efficiency_score
}
def classify_dora_performance(self):
"""Classify team into DORA performance category."""
d = self.dora_metrics
scores = []
# Deployment frequency scoring
if d["deployment_frequency_daily"] >= 1:
scores.append(4) # Elite: on-demand / daily
elif d["deployment_frequency_daily"] >= 0.14: # Weekly
scores.append(3) # High
elif d["deployment_frequency_daily"] >= 0.03: # Monthly
scores.append(2) # Medium
else:
scores.append(1) # Low
# Lead time scoring
lt = d["lead_time_median_minutes"]
if lt <= 60:
scores.append(4)
elif lt <= 1440: # One day
scores.append(3)
elif lt <= 10080: # One week
scores.append(2)
else:
scores.append(1)
# MTTR scoring
mttr = d["mttr_minutes"]
if mttr <= 60:
scores.append(4)
elif mttr <= 1440:
scores.append(3)
elif mttr <= 10080:
scores.append(2)
else:
scores.append(1)
# Change failure rate scoring
cfr = d["change_failure_rate_percent"]
if cfr <= 5:
scores.append(4)
elif cfr <= 10:
scores.append(3)
elif cfr <= 15:
scores.append(2)
else:
scores.append(1)
avg_score = statistics.mean(scores)
if avg_score >= 3.5:
return "Elite"
elif avg_score >= 2.5:
return "High"
elif avg_score >= 1.5:
return "Medium"
else:
return "Low"
def generate_report(self):
"""Generate the full unified engineering metrics report."""
performance_level = self.classify_dora_performance()
report = f"""
═══════════════════════════════════════════════════
ENGINEERING METRICS DASHBOARD
Team: {self.team_name}
Period: {self.reporting_period}
Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
═══════════════════════════════════════════════════
>>> DORA METRICS (Delivery Performance)
Deployment Frequency: {self.dora_metrics['deployment_frequency_daily']:.2f} per day
Lead Time (median): {self.dora_metrics['lead_time_median_minutes']:.0f} minutes
Mean Time to Restore: {self.dora_metrics['mttr_minutes']:.0f} minutes
Change Failure Rate: {self.dora_metrics['change_failure_rate_percent']:.1f}%
Overall DORA Classification: {performance_level}
>>> SPACE METRICS (Developer Experience & Productivity)
Satisfaction: {self.space_metrics['satisfaction']:.1f}/5.0
Performance (Quality): {self.space_metrics['performance']}/100
Communication Health: {self.space_metrics['communication']}/100
Efficiency: {self.space_metrics['efficiency']}/100
Activity Summary:
{self.space_metrics['activity']}
═══════════════════════════════════════════════════
RECOMMENDATIONS
═══════════════════════════════════════════════════
"""
# Generate contextual recommendations
recommendations = []
d = self.dora_metrics
s = self.space_metrics
if d["change_failure_rate_percent"] > 10:
recommendations.append("→ Reduce change failure rate: implement more rigorous testing and canary deployments")
if d["mttr_minutes"] > 1440:
recommendations.append("→ Improve MTTR: invest in monitoring, runbooks, and incident response automation")
if d["lead_time_median_minutes"] > 1440:
recommendations.append("→ Reduce lead time: break down large changes, optimize CI pipeline, increase