OKR Progress Calculator
business.okr_progress
Overview
You send the key results — a metric moving from a start toward a target, a milestone that is done or not, or a figure that is already a proportion — and we return each one's progress, the weighted objective progress, the percentage and the status band it falls in. Progress is defined rather than guessed: a metric is the distance travelled from start to target, which is why the two must differ, and a target below the start reads correctly for a metric you want to bring down. Overshoot and backsliding are clamped to nothing-to-fully-done by default and flagged where they happened, so a key result at 120 per cent cannot carry an objective past complete. Every contribution is shown and they add up to exactly the progress beside them.
Every OKR tool computes progress, and no two agree on how. The disagreements are always the same three: whether a metric's progress is measured from zero or from where it started, what a key result at 120 per cent does to the objective, and where 'behind' ends and 'on track' begins. Each of those is an explicit input here, and the answer comes back with the arithmetic and the boundaries attached, which is what makes two teams' numbers comparable.
Reliability
Fully deterministic: BigDecimal at 34 significant digits, HALF_UP, rounded to the scale you ask for. METRIC progress is (current − start) ÷ (target − start), which is why start and target must differ and why a target below the start reads correctly for a DECREASE key result. MILESTONE is the 0 or 1 you send; PERCENT is passed through after its range is checked. Contributions are each normalizedWeight × progress, rounded so they sum to exactly the objective's progress, residue assigned by largest remainder with ties going to the earlier key result. The remainders compared are those of the unrounded 34-digit contributions, not of the figures published beside them, so two contributions that print identically can still be ranked apart — recomputing the residue from the published numbers will not always reproduce it. Status bands are half-open — [from, to) — except the top band, whose upper bound is inclusive so progress of exactly 1.0 has a status. A key result carries at most one note, so where several apply the clamp is reported, then the direction mismatch, then the ignored weight. An enum value — weightMode, type, direction — is stripped of surrounding whitespace and upper-cased before it is matched, so ' weighted ' is WEIGHTED; ids are never folded. Failures: OKR_KEY_RESULTS_INVALID means a key result, a band or a flag cannot be read — no key results, a MILESTONE that is not 0 or 1, a repeated band id, a scale outside 0 to 12, or an unaccepted property. OKR_KEY_RESULT_ID_DUPLICATE means an id appears twice. OKR_TYPE_UNSUPPORTED means type is missing or not one of the three. OKR_RANGE_INVALID means a METRIC has an equal start and target, so there is no distance to measure. OKR_PERCENT_OUT_OF_RANGE means a PERCENT key result is outside 0 to 1. OKR_WEIGHTS_INVALID means a weight cannot be read, is negative — refused in either weight mode — or they all add to zero. OKR_BANDS_GAP and OKR_BANDS_OVERLAP mean the band ladder is not contiguous, or does not cover the progress that came out — which is what happens if you turn clamping off and leave the default ladder, since it stops at 1.0. OKR_LIMIT_EXCEEDED means more than 200 key results or 10 bands. None is worth retrying unchanged.
Context footprint
One line per key result plus the resolved bands, so the response tracks the size of your objective and nothing else. 200 key results is roughly 30 KB. A successful call costs 1 execution credit.
When to use it
When you have the current figures and need one progress number, one percentage and one status, computed the same way for everybody.
When the figures still have to be gathered, or when what you actually need is a score or a ranking rather than progress toward targets.
Scope
Non-goals
- Does not write the objectives or the key results, and does not judge whether they are good ones
- Does not read a metric from anywhere: current is a number you send
- Does not forecast, and does not compare this quarter with the last one
- Does not store progress or notify anybody about it
Properties
Capabilities
Cost & billing
Flat rate per invocation
File handling
How we handle your data
What this means
This skill may receive sensitive content, but it does not target sensitive data and returns only a structured non-sensitive result.
Squillify guarantees
- Runs inside Squillify
- This skill runs inside Squillify.
- Payloads are not sent to an external service by this skill.
- Squillify does not permanently store submitted payloads.
- Telemetry records metadata only, not submitted content.
- Submitted payloads are not used for training.
- Submitted payloads are not reviewed by humans.
- This skill does not target sensitive data.
- The response does not contain submitted content.
Your responsibility
Squillify does not determine whether submitted content is sensitive. Your application is responsible for deciding what data it is allowed to submit. Your application is responsible for securing the response.
Input schema
| Field | Type | Required | Description |
|---|---|---|---|
| keyResults | array | Yes | Bounded array of 1 through 200 closed key results with unique ids. |
| objective | object | No | Closed optional objective settings; omit the object for EQUAL weighting and no objectiveId. |
| statusBands | array | No | Bounded array of 1 through 10 contiguous bands; omit it for the default AT_RISK/BEHIND/ON_TRACK ladder. |
| clampProgress | boolean | No | Whether each key result is clamped to 0 through 1; omit it for true. |
| scale | integer | No | Published decimal places; omit it for 4. |
Output schema
| Field | Type | Required | Description |
|---|---|---|---|
| progress | number | Yes | Weighted objective progress, above 1 only when clamping is off. |
| progressPercent | number | Yes | The same progress times 100 at two decimal places. |
| status | string | Yes | Id of the status band the progress falls in. |
| statusLabel | string | No | Band label, present only when the band carries one. |
| keyResults | array | Yes | Bounded array of 1 through 200 computed key results in request order. |
| weightMode | string | Yes | Weight mode actually applied. |
| statusBands | array | Yes | Bounded array of 1 through 10 status bands actually applied, including defaults. |
| objectiveId | string | No | Echoed objective.id, present only when you supplied one. |
API contract
POST /v1/executecurl -sS -X POST "$BASE/v1/execute" -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' -d '{"skillKey":"business.okr_progress","input":{"keyResults":[{"id":"kr1","type":"METRIC","start":0,"target":100,"current":40}]}}'import { Squillify } from "@squillify/sdk";
const sq = new Squillify({ apiKey: process.env.SQUILLIFY_API_KEY });
const { output } = await sq.execute("business.okr_progress", {"keyResults":[{"id":"kr1","type":"METRIC","start":0,"target":100,"current":40},{"current":1,"type":"MILESTONE","id":"kr2"},{"current":0.25,"type":"PERCENT","id":"kr3"}],"objective":{"id":"OBJ-1"}});Errors
| Code | HTTP | Retryable | Description |
|---|---|---|---|
| VALIDATION_ERROR | 400 | No | The request payload failed schema validation. |
| UNAUTHORIZED | 401 | No | Missing or invalid API key. |
| SKILL_NOT_ACCESSIBLE | 403 | No | The API key is not granted access to this skill. |
| SKILL_NOT_AVAILABLE_FOR_PLAN | 403 | No | This skill is not included in your current plan. |
| PLAN_ELIGIBILITY_UNAVAILABLE | 503 | Yes | Plan eligibility could not be decided. Retry after backoff. |
| SKILL_NOT_FOUND | 404 | No | The requested skill or version does not exist. |
| TENANT_RATE_LIMITED | 429 | Yes | Request rate limit exceeded. Retry after backoff. |
| QUOTA_EXCEEDED | 429 | Yes | Plan quota exhausted. Retryable once the quota period resets. |
| EXECUTION_TIMEOUT | 504 | Yes | Execution exceeded the allowed time budget. |
| OKR_KEY_RESULTS_INVALID | 400 | No | The request payload failed this skill's input validation. |
| OKR_KEY_RESULT_ID_DUPLICATE | 400 | No | The request payload failed this skill's input validation. |
| OKR_TYPE_UNSUPPORTED | 400 | No | The request payload failed this skill's input validation. |
| OKR_RANGE_INVALID | 400 | No | The request payload failed this skill's input validation. |
| OKR_PERCENT_OUT_OF_RANGE | 400 | No | The request payload failed this skill's input validation. |
| OKR_WEIGHTS_INVALID | 400 | No | The request payload failed this skill's input validation. |
| OKR_BANDS_GAP | 400 | No | The request payload failed this skill's input validation. |
| OKR_BANDS_OVERLAP | 400 | No | The request payload failed this skill's input validation. |
| OKR_LIMIT_EXCEEDED | 429 | No | A size, quota, rate, or time limit was exceeded. |
Other limits
| Limit | Value |
|---|---|
| Recommended inline bytes | 32 KiB |
Limitations
- Does not write the objectives or the key results, and does not judge whether they are good ones
- Does not read a metric from anywhere: current is a number you send
- Does not forecast, and does not compare this quarter with the last one
- Does not store progress or notify anybody about it
- Deriving current from a data source — fetch it first, then send it
- Scoring or ranking things, which is business.priority_score and business.decision_matrix
- Objectives with more than 200 key results, which is a planning problem
How to call
POST /v1/execute (requires authentication)
{
"skillKey": "business.okr_progress",
"input": {
"keyResults": [
{
"id": "kr1",
"type": "METRIC",
"start": 0,
"target": 100,
"current": 40
},
{
"current": 1,
"type": "MILESTONE",
"id": "kr2"
},
{
"current": 0.25,
"type": "PERCENT",
"id": "kr3"
}
],
"objective": {
"id": "OBJ-1"
}
}
}Examples
One objective, three key results of different types, counted equally
{
"keyResults": [
{
"id": "kr1",
"type": "METRIC",
"start": 0,
"target": 100,
"current": 40
},
{
"current": 1,
"type": "MILESTONE",
"id": "kr2"
},
{
"current": 0.25,
"type": "PERCENT",
"id": "kr3"
}
],
"objective": {
"id": "OBJ-1"
}
}{
"progressPercent": 55,
"weightMode": "EQUAL",
"statusBands": [
{
"id": "AT_RISK",
"label": "At risk",
"from": 0,
"to": 0.4
},
{
"id": "BEHIND",
"label": "Behind",
"from": 0.4,
"to": 0.7
},
{
"id": "ON_TRACK",
"label": "On track",
"from": 0.7,
"to": 1
}
],
"progress": 0.55,
"status": "BEHIND",
"statusLabel": "Behind",
"objectiveId": "OBJ-1",
"keyResults": [
{
"id": "kr1",
"type": "METRIC",
"progress": 0.4,
"weight": 1,
"normalizedWeight": 0.3333,
"contribution": 0.1333,
"clamped": false
},
{
"id": "kr2",
"type": "MILESTONE",
"progress": 1,
"weight": 1,
"normalizedWeight": 0.3333,
"contribution": 0.3333,
"clamped": false
},
{
"id": "kr3",
"type": "PERCENT",
"progress": 0.25,
"weight": 1,
"normalizedWeight": 0.3333,
"contribution": 0.0834,
"clamped": false
}
]
}Weighted mode with a metric that must come down, overshooting its target and clamped
{
"keyResults": [
{
"id": "churn",
"type": "METRIC",
"start": 5,
"target": 3,
"current": 2.5,
"weight": 3,
"direction": "DECREASE"
},
{
"id": "nps",
"type": "METRIC",
"start": 30,
"target": 50,
"current": 35,
"weight": 1
}
],
"objective": {
"id": "OBJ-CHURN",
"weightMode": "WEIGHTED"
}
}{
"progressPercent": 81.25,
"weightMode": "WEIGHTED",
"statusBands": [
{
"id": "AT_RISK",
"label": "At risk",
"from": 0,
"to": 0.4
},
{
"id": "BEHIND",
"label": "Behind",
"from": 0.4,
"to": 0.7
},
{
"id": "ON_TRACK",
"label": "On track",
"from": 0.7,
"to": 1
}
],
"progress": 0.8125,
"status": "ON_TRACK",
"statusLabel": "On track",
"objectiveId": "OBJ-CHURN",
"keyResults": [
{
"id": "churn",
"type": "METRIC",
"progress": 1,
"weight": 3,
"normalizedWeight": 0.75,
"contribution": 0.75,
"clamped": true,
"note": "CLAMPED_HIGH"
},
{
"id": "nps",
"type": "METRIC",
"progress": 0.25,
"weight": 1,
"normalizedWeight": 0.25,
"contribution": 0.0625,
"clamped": false
}
]
}Recommended use cases
- Compute objective progress the same way for every team, from figures they already have
- Mix a metric, a milestone and an already-percentage key result under one objective
- Weight a key result that carries most of the objective without hiding the arithmetic
- Publish the status band and its boundaries together, so nobody argues about 'behind'
Avoid for
- Deriving current from a data source — fetch it first, then send it
- Scoring or ranking things, which is business.priority_score and business.decision_matrix
- Objectives with more than 200 key results, which is a planning problem
Related skills
Composable with
Safety & privacy
- Runs without calling any external service.
- Your payloads are never stored permanently.
- Telemetry captures metadata only — never your payload contents.
- We never train models on your payloads.
- No human reviews your payloads.