Skip to main content

Check out Port for yourself ➜ 

Set up delivery performance scorecards

Scorecards let you define maturity levels for your services and teams, giving engineering leaders a clear view of which areas meet delivery standards and which need attention.

This guide demonstrates how to set up Delivery Performance scorecards at different levels of the organization:

  • Service: PR throughput, cycle time, stale PR management for individual services.
  • Team: Same metrics normalized per-service so teams are compared fairly.
  • Group: Executive-level delivery health for business units.
  • Organization: Top-level delivery view across the entire org.

Each scorecard evaluates entities against progressive maturity levels: Bronze, Silver, and Gold. Thresholds are informed by DORA benchmarks and industry engineering data.

Common use cases

  • Set clear, measurable delivery expectations across engineering teams.
  • Identify services that are falling behind on cycle time, throughput, or stale PR hygiene.
  • Compare team and group delivery maturity at a glance and drive improvement initiatives.
  • Gate deployments or trigger alerts when services drop below a minimum scorecard level.

Prerequisites

This guide assumes the following:

Maturity levels

Port scorecards use four fixed maturity levels. An entity must pass all rules at a given level (and all levels below it) to achieve that level.

LevelProgressionDescription
Basic0 (default)Entity exists but meets no scorecard criteria
Bronze1Foundational hygiene and minimum viable standards
Silver2Good practices — actively maintained, reasonable thresholds
Gold3Excellence — elite-tier metrics

Create the scorecards

PR throughput and cycle time targets aligned with DORA benchmarks and industry engineering data. Select the level you want to configure:

Blueprint: Service

#RuleLevelPropertyCondition
1Merged PRs >= 2/weekBronzemerged_prs_last_month>= 8
2Open PRs <= 8Bronzeopen_prs<= 8
3Stale PR share <= 10%Bronzestale_pr_share_percent<= 10
4PR cycle time < 7 daysBronzepr_cycle_time< 168 hours
5Open PRs <= 5Silveropen_prs<= 5
6Stale PRs <= 1Silverstale_prs_7d<= 1
7PR cycle time < 24hSilverpr_cycle_time< 24 hours
8Throughput not degradingSilverthroughput_trend!= "Degrading"
9Merged PRs >= 5/weekSilvermerged_prs_last_month>= 20
10Open PRs <= 3Goldopen_prs<= 3
11Stale PRs = 0Goldstale_prs_7d= 0
12PR cycle time < 1hGoldpr_cycle_time< 1 hour
13PR cycle time not degradingGoldcycle_time_trend!= "Degrading"
14Merged PRs >= 10/weekGoldmerged_prs_last_month>= 40

Create the delivery performance scorecard on the service blueprint

  1. Go to the Builder page of your portal.

  2. Search for the Service blueprint and select it.

  3. Click on the Scorecards tab.

  4. If the delivery_performance scorecard does not exist, click + New Scorecard. If it already exists, open it instead.

  5. Click on the {...} button in the top right corner, and choose Edit JSON.

  6. Paste the following JSON configuration:

    Service Delivery Performance scorecard (click to expand)
    {
    "identifier": "delivery_performance",
    "title": "Delivery Performance",
    "levels": [
    {
    "color": "paleBlue",
    "title": "Basic"
    },
    {
    "color": "bronze",
    "title": "Bronze"
    },
    {
    "color": "silver",
    "title": "Silver"
    },
    {
    "color": "gold",
    "title": "Gold"
    }
    ],
    "rules": [
    {
    "identifier": "has_merged_prs",
    "title": "Merged PRs ≥ 2/week",
    "description": "Service merges at least 8 PRs per month, indicating active development.",
    "level": "Bronze",
    "query": {
    "combinator": "and",
    "conditions": [
    {
    "operator": ">=",
    "property": "merged_prs_last_month",
    "value": 8
    }
    ]
    }
    },
    {
    "identifier": "manageable_open_prs",
    "title": "Open PRs ≤ 8",
    "description": "Keeps the number of open PRs manageable to avoid review overload.",
    "level": "Bronze",
    "query": {
    "combinator": "and",
    "conditions": [
    {
    "operator": "<=",
    "property": "open_prs",
    "value": 8
    }
    ]
    }
    },
    {
    "identifier": "low_stale_prs",
    "title": "Stale PR share ≤ 10%",
    "description": "No more than 10% of open PRs are older than 7 days.",
    "level": "Bronze",
    "query": {
    "combinator": "and",
    "conditions": [
    {
    "operator": "<=",
    "property": "stale_pr_share_percent",
    "value": 10
    }
    ]
    }
    },
    {
    "identifier": "cycle_time_under_7d",
    "title": "PR cycle time < 7 days",
    "description": "Average time from PR creation to merge is under one week.",
    "level": "Bronze",
    "query": {
    "combinator": "and",
    "conditions": [
    {
    "operator": "<",
    "property": "pr_cycle_time",
    "value": 168
    }
    ]
    }
    },
    {
    "identifier": "good_open_pr_management",
    "title": "Open PRs ≤ 5",
    "description": "Tighter open PR limit indicating strong review discipline.",
    "level": "Silver",
    "query": {
    "combinator": "and",
    "conditions": [
    {
    "operator": "<=",
    "property": "open_prs",
    "value": 5
    }
    ]
    }
    },
    {
    "identifier": "minimal_stale_prs",
    "title": "Stale PRs ≤ 1",
    "description": "At most one PR has been open longer than 7 days.",
    "level": "Silver",
    "query": {
    "combinator": "and",
    "conditions": [
    {
    "operator": "<=",
    "property": "stale_prs_7d",
    "value": 1
    }
    ]
    }
    },
    {
    "identifier": "cycle_time_under_24h",
    "title": "PR cycle time < 24h",
    "description": "PRs are merged within a day on average.",
    "level": "Silver",
    "query": {
    "combinator": "and",
    "conditions": [
    {
    "operator": "<",
    "property": "pr_cycle_time",
    "value": 24
    }
    ]
    }
    },
    {
    "identifier": "throughput_not_degrading",
    "title": "Throughput not degrading",
    "description": "Weekly merge rate is not declining compared to the monthly average.",
    "level": "Silver",
    "query": {
    "combinator": "and",
    "conditions": [
    {
    "operator": "!=",
    "property": "throughput_trend",
    "value": "Degrading"
    }
    ]
    }
    },
    {
    "identifier": "good_throughput",
    "title": "Merged PRs ≥ 5/week",
    "description": "Service merges at least 20 PRs per month, showing strong delivery pace.",
    "level": "Silver",
    "query": {
    "combinator": "and",
    "conditions": [
    {
    "operator": ">=",
    "property": "merged_prs_last_month",
    "value": 20
    }
    ]
    }
    },
    {
    "identifier": "excellent_open_pr_management",
    "title": "Open PRs ≤ 3",
    "description": "Very few PRs are open at any time, indicating rapid review cycles.",
    "level": "Gold",
    "query": {
    "combinator": "and",
    "conditions": [
    {
    "operator": "<=",
    "property": "open_prs",
    "value": 3
    }
    ]
    }
    },
    {
    "identifier": "no_stale_prs",
    "title": "Stale PRs = 0",
    "description": "No open PRs have been sitting for more than 7 days.",
    "level": "Gold",
    "query": {
    "combinator": "and",
    "conditions": [
    {
    "operator": "=",
    "property": "stale_prs_7d",
    "value": 0
    }
    ]
    }
    },
    {
    "identifier": "cycle_time_under_1h",
    "title": "PR cycle time < 1h",
    "description": "PRs are merged in under an hour on average — elite performance.",
    "level": "Gold",
    "query": {
    "combinator": "and",
    "conditions": [
    {
    "operator": "<",
    "property": "pr_cycle_time",
    "value": 1
    }
    ]
    }
    },
    {
    "identifier": "cycle_time_not_degrading",
    "title": "PR cycle time not degrading",
    "description": "Weekly cycle time is not increasing compared to the monthly average.",
    "level": "Gold",
    "query": {
    "combinator": "and",
    "conditions": [
    {
    "operator": "!=",
    "property": "cycle_time_trend",
    "value": "Degrading"
    }
    ]
    }
    },
    {
    "identifier": "excellent_throughput",
    "title": "Merged PRs ≥ 10/week",
    "description": "Service merges at least 40 PRs per month — top-tier throughput.",
    "level": "Gold",
    "query": {
    "combinator": "and",
    "conditions": [
    {
    "operator": ">=",
    "property": "merged_prs_last_month",
    "value": 40
    }
    ]
    }
    }
    ]
    }
  7. Click Save to create the scorecard.


Visualize scorecard results

Once the scorecards are in place, create a dashboard to monitor delivery maturity across services and teams.

Create the dashboard

  1. Navigate to your software catalog.
  2. Click on the + New button in the left sidebar.
  3. Select New folder.
  4. Name the folder Engineering Intelligence and click Create.
  5. Inside the Engineering Intelligence folder, click + New again.
  6. Select New dashboard.
  7. Name the dashboard Delivery Performance Scorecards and click Create.

Add widgets

You can populate the dashboard using either an API script or by manually creating each widget through the UI.

The fastest way to set up the dashboard is by using Port's API to create all widgets at once.

Get your Port API token

  1. In your Port portal, click on your profile picture in the bottom left corner.

  2. Select Credentials.

  3. Click Generate API token.

  4. Copy the generated token and store it as an environment variable:

    export PORT_ACCESS_TOKEN="YOUR_GENERATED_TOKEN"
EU region

If your portal is hosted in the EU region, replace api.port.io with api.port-eu.io in the dashboard creation command below.

Create the dashboard with widgets

Save the following JSON to a file named dp_scorecards_dashboard.json:

Dashboard JSON payload (click to expand)
{
"identifier": "delivery_performance_scorecards",
"title": "Delivery Performance Scorecards",
"icon": "Star",
"type": "dashboard",
"parent": "engineering_intelligence",
"widgets": [
{
"id": "dpScorecardsDashboardWidget",
"type": "dashboard-widget",
"layout": [
{
"height": 400,
"columns": [
{"id": "serviceLevelDistribution", "size": 4},
{"id": "teamLevelDistribution", "size": 4},
{"id": "servicesBelowBronze", "size": 4}
]
},
{
"height": 400,
"columns": [
{"id": "serviceScorecardsTable", "size": 12}
]
},
{
"height": 400,
"columns": [
{"id": "teamScorecardsTable", "size": 12}
]
},
{
"height": 400,
"columns": [
{"id": "groupScorecardsTable", "size": 12}
]
},
{
"height": 400,
"columns": [
{"id": "orgScorecardsTable", "size": 12}
]
}
],
"widgets": [
{
"id": "serviceLevelDistribution",
"type": "entities-pie-chart",
"title": "Service Delivery Performance Levels",
"icon": "Pie",
"description": "Distribution of delivery performance scorecard levels across services",
"blueprint": "service",
"property": "scorecard#delivery_performance",
"dataset": {
"combinator": "and",
"rules": []
}
},
{
"id": "teamLevelDistribution",
"type": "entities-pie-chart",
"title": "Team Delivery Performance Levels",
"icon": "Pie",
"description": "Distribution of delivery performance scorecard levels across teams",
"blueprint": "_team",
"property": "scorecard#team_delivery_performance",
"dataset": {
"combinator": "and",
"rules": [
{
"operator": "=",
"property": "type",
"value": "team"
}
]
}
},
{
"id": "servicesBelowBronze",
"type": "entities-number-chart",
"title": "Services Below Bronze",
"icon": "Alert",
"description": "Number of services that have not reached Bronze level",
"blueprint": "service",
"chartType": "countEntities",
"calculationBy": "entities",
"func": "count",
"unit": "none",
"dataset": {
"combinator": "and",
"rules": [
{
"operator": "=",
"property": "scorecard#delivery_performance",
"value": "Basic"
}
]
}
},
{
"id": "serviceScorecardsTable",
"type": "table-entities-explorer",
"displayMode": "widget",
"title": "Service Delivery Scorecard Overview",
"icon": "Table",
"description": "Services with their delivery performance scorecard level and key metrics",
"blueprint": "service",
"dataset": {"combinator": "and", "rules": []},
"excludedFields": [],
"blueprintConfig": {
"service": {
"groupSettings": {"groupBy": ["team"]},
"propertiesSettings": {
"order": ["$title", "team", "scorecard-property#delivery_performance", "pr_cycle_time", "cycle_time_trend", "merged_prs_last_month", "throughput_trend", "open_prs", "stale_prs_7d", "stale_pr_share_percent"],
"shown": ["$title", "team", "scorecard-property#delivery_performance", "pr_cycle_time", "cycle_time_trend", "merged_prs_last_month", "throughput_trend", "open_prs", "stale_prs_7d", "stale_pr_share_percent"]
},
"filterSettings": {"filterBy": {"combinator": "and", "rules": []}},
"sortSettings": {"sortBy": []}
}
}
},
{
"id": "teamScorecardsTable",
"type": "table-entities-explorer",
"displayMode": "widget",
"title": "Team Delivery Scorecard Overview",
"icon": "Table",
"description": "Teams with their delivery performance scorecard level and key metrics",
"blueprint": "_team",
"dataset": {"combinator": "and", "rules": []},
"excludedFields": [],
"blueprintConfig": {
"_team": {
"groupSettings": {"groupBy": ["parent_team_name"]},
"propertiesSettings": {
"order": ["$title", "scorecard-property#team_delivery_performance", "pr_cycle_time", "cycle_time_trend", "merged_prs_per_service_last_month", "throughput_trend", "open_prs_per_service", "stale_prs_per_service_7d", "stale_pr_share_percent", "services_count"],
"shown": ["$title", "scorecard-property#team_delivery_performance", "pr_cycle_time", "cycle_time_trend", "merged_prs_per_service_last_month", "throughput_trend", "open_prs_per_service", "stale_prs_per_service_7d", "stale_pr_share_percent", "services_count"]
},
"filterSettings": {
"filterBy": {
"combinator": "and",
"rules": [
{
"operator": "=",
"property": "type",
"value": "team"
}
]
}
},
"sortSettings": {"sortBy": []}
}
}
},
{
"id": "groupScorecardsTable",
"type": "table-entities-explorer",
"displayMode": "widget",
"title": "Group Delivery Scorecard Overview",
"icon": "Table",
"description": "Business units with their delivery performance scorecard level and key metrics",
"blueprint": "_team",
"dataset": {"combinator": "and", "rules": []},
"excludedFields": [],
"blueprintConfig": {
"_team": {
"groupSettings": {},
"propertiesSettings": {
"order": ["$title", "scorecard-property#group_delivery_performance", "pr_cycle_time", "cycle_time_trend", "merged_prs_per_service_last_month", "throughput_trend", "stale_pr_share_percent"],
"shown": ["$title", "scorecard-property#group_delivery_performance", "pr_cycle_time", "cycle_time_trend", "merged_prs_per_service_last_month", "throughput_trend", "stale_pr_share_percent"]
},
"filterSettings": {
"filterBy": {
"combinator": "and",
"rules": [
{
"operator": "=",
"property": "type",
"value": "group"
}
]
}
},
"sortSettings": {"sortBy": []}
}
}
},
{
"id": "orgScorecardsTable",
"type": "table-entities-explorer",
"displayMode": "widget",
"title": "Organization Delivery Scorecard Overview",
"icon": "Table",
"description": "Organization with its delivery performance scorecard level and key metrics",
"blueprint": "organization",
"dataset": {"combinator": "and", "rules": []},
"excludedFields": [],
"blueprintConfig": {
"organization": {
"groupSettings": {},
"propertiesSettings": {
"order": ["$title", "scorecard-property#org_delivery_performance", "pr_cycle_time", "cycle_time_trend", "merged_prs_per_service_last_month", "throughput_trend", "stale_pr_share_percent"],
"shown": ["$title", "scorecard-property#org_delivery_performance", "pr_cycle_time", "cycle_time_trend", "merged_prs_per_service_last_month", "throughput_trend", "stale_pr_share_percent"]
},
"filterSettings": {"filterBy": {"combinator": "and", "rules": []}},
"sortSettings": {"sortBy": []}
}
}
}
]
}
]
}

Then run the following command to create the dashboard with all widgets:

curl -s -X POST "https://api.port.io/v1/pages" \
-H "Authorization: Bearer $PORT_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d @dp_scorecards_dashboard.json | python3 -m json.tool
Engineering Intelligence folder

The script assumes an engineering_intelligence folder already exists in your catalog. If you haven't created it yet, follow steps 1-4 in the create the dashboard section first.

Design decisions

Team-level normalization

Team-level scorecards use per-service normalized metrics for absolute count rules (open PRs, merged PRs, stale PRs).
This ensures teams are evaluated fairly regardless of how many services they own. For example, a team with 5 services and 40 open PRs is evaluated as 8 open PRs per service, the same threshold as a single service with 8.

Rate metrics (stale PR share), trend metrics (throughput, cycle time), and time-based metrics (PR cycle time) are not normalized as they already scale naturally across any number of services.

Executive-level scorecards (Group and Organization)

Group and Organization scorecards use rate, trend, and per-service normalized throughput metrics only.
Absolute count rules are omitted because groups and organizations span varying numbers of teams and services, making raw counts misleading.

Threshold sources

  • DORA: Industry-standard DevOps Research and Assessment benchmarks for software delivery performance.

Next steps

  • Add automations: Use Port automations to send Slack notifications when a service drops below Bronze, or create Jira tickets for teams with degrading trends.
  • Customize thresholds: Adjust the numeric values in the scorecard rules to match your organization's delivery standards. For example, if your teams typically merge fewer PRs, lower the throughput thresholds.