Weighted Decision Matrix
business.decision_matrix
Overview
You send the options and the criteria that decide between them; we return the ranking, every option's score, and the contribution of every criterion to it. Criteria measured in different units — euros, days, a score out of ten — are put on a common footing first, by the method you name per criterion: min-max across the options, share of the total, z-score, a fixed rubric, or none at all. The consequence is worth stating plainly: because normalisation looks at the options in this request, adding or removing an option can change the scores of the others. That is what comparative ranking means, and it is exactly why business.priority_score exists for the absolute case. Equal scores share a rank, competition style, ordered by the tie-break you choose. Degenerate cases are defined rather than divided by zero: one option, or a criterion where every option is equal, is reported as such instead of failing.
Decision matrices are usually built in a spreadsheet, where the normalisation is implicit, the arithmetic is invisible, and nobody notices that the winner changed because a row was added. Publishing the method per criterion, the observed range it normalised over, and the contribution of every criterion to every score turns 'the tool says B' into an argument that can be checked.
Reliability
Fully deterministic and reproducible to the digit: BigDecimal at 34 significant digits, HALF_UP, rounded once to your declared decimal places, no floating point anywhere. Contributions are reconciled to each score by largest remainder, so a breakdown never drifts from the score beside it. Reordering the options changes no score, and under OPTION_ID it changes no ranking either. Degenerate normalisation is defined, not an error: a single option is reported SINGLE_OPTION, a criterion where every option is equal is CONSTANT_CRITERION and scores the same for all of them — so it shifts every score alike and cannot change the order — and a SUM criterion whose values total zero splits equally as ZERO_SUM. Z_SCORE is centred on zero, so scores under it are commonly negative, and so is any LOWER_IS_BETTER criterion, which subtracts. Failures: MATRIX_OPTIONS_INVALID means options cannot be read — none sent, an id that is empty, too long, padded or carrying a control character, a value that is not a decimal, or a value for an undeclared criterion. MATRIX_OPTION_ID_DUPLICATE means an option id appears twice. MATRIX_CRITERIA_INVALID means a criterion is unusable: none sent, a repeated id, a negative weight, FIXED without a scale, a scale whose min is not below its max, or SUM with a negative value. MATRIX_VALUE_MISSING means an option lacks a value for a criterion whose missing is REJECT. MATRIX_NORMALIZATION_UNSUPPORTED means a method outside MIN_MAX, SUM, Z_SCORE, FIXED and NONE. MATRIX_LIMIT_EXCEEDED means more than 500 options or 64 criteria. None is worth retrying unchanged.
Context footprint
The response carries one breakdown row per option and criterion, so it is bounded by options times criteria — 500 by 64 at the ceiling. Ask for fewer criteria, or fewer options, when the response matters more than the detail. A successful call costs 1 execution credit.
When to use it
When you have a shortlist and criteria you can write down, and you need a defensible ranking with the working shown.
When the score must be stable regardless of the other options, when a criterion is really a hard requirement, or when the decision is not reducible to weighted numbers.
Scope
Non-goals
- Does not produce absolute scores: they are relative to the options you sent, and adding or removing one can reorder the rest — business.priority_score is the absolute variant, and shares this skill's weighted-sum engine
- Does not invent weights, criteria or normalisation you did not declare
- Does not make the decision, and does not store it
- Does not fill in a missing value unless you say how
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 |
|---|---|---|---|
| options | array | Yes | Bounded array of 1 through 500 closed options, each with unique id and criterion values. |
| criteria | array | Yes | Bounded array of 1 through 64 closed criteria declaring weight and normalisation. |
| tieBreak | string | No | How equal scores are ordered; omit it for INPUT_ORDER. |
| scale | integer | No | Published decimal places; omit it for 4. |
Output schema
| Field | Type | Required | Description |
|---|---|---|---|
| ranking | array | Yes | Bounded array of 1 through 500 ranking rows, best first, with shared ranks on ties. |
| scores | array | Yes | Bounded array of 1 through 500 score rows in the order the options were sent. |
| criteriaSummary | array | Yes | Bounded array of 1 through 64 criterion summaries naming the observed normalisation range. |
| tieBreak | string | Yes | Tie-break actually applied to order equal scores. |
API contract
POST /v1/executecurl -sS -X POST "$BASE/v1/execute" -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' -d '{"skillKey":"business.decision_matrix","input":{"options":[{"id":"alpha","values":{"quality":6,"support":10}},{"id":"beta","values":{"quality":9,"support":7}}],"criteria":[{"id":"quality","weight":0.7},{"id":"support","weight":0.3}]}}'import { Squillify } from "@squillify/sdk";
const sq = new Squillify({ apiKey: process.env.SQUILLIFY_API_KEY });
const { output } = await sq.execute("business.decision_matrix", {"options":[{"id":"alpha","values":{"quality":6,"support":10}},{"id":"beta","values":{"quality":9,"support":7}},{"id":"gamma","values":{"quality":9,"support":7}}],"criteria":[{"id":"quality","weight":0.7},{"id":"support","weight":0.3}]});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. |
| MATRIX_OPTIONS_INVALID | 400 | No | The request payload failed this skill's input validation. |
| MATRIX_OPTION_ID_DUPLICATE | 400 | No | The request payload failed this skill's input validation. |
| MATRIX_CRITERIA_INVALID | 400 | No | The request payload failed this skill's input validation. |
| MATRIX_VALUE_MISSING | 400 | No | The request payload failed this skill's input validation. |
| MATRIX_NORMALIZATION_UNSUPPORTED | 400 | No | The request payload failed this skill's input validation. |
| MATRIX_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 produce absolute scores: they are relative to the options you sent, and adding or removing one can reorder the rest — business.priority_score is the absolute variant, and shares this skill's weighted-sum engine
- Does not invent weights, criteria or normalisation you did not declare
- Does not make the decision, and does not store it
- Does not fill in a missing value unless you say how
- Scoring one item on its own, or scores that must not move when the set changes — use business.priority_score
- Comparisons where one criterion is a veto rather than a weight
- Ranking thousands of rows: this is a decision aid, not a query engine
How to call
POST /v1/execute (requires authentication)
{
"skillKey": "business.decision_matrix",
"input": {
"options": [
{
"id": "alpha",
"values": {
"quality": 6,
"support": 10
}
},
{
"id": "beta",
"values": {
"quality": 9,
"support": 7
}
},
{
"id": "gamma",
"values": {
"quality": 9,
"support": 7
}
}
],
"criteria": [
{
"id": "quality",
"weight": 0.7
},
{
"id": "support",
"weight": 0.3
}
]
}
}Examples
Three candidates on quality and support, with two of them tied
{
"options": [
{
"id": "alpha",
"values": {
"quality": 6,
"support": 10
}
},
{
"id": "beta",
"values": {
"quality": 9,
"support": 7
}
},
{
"id": "gamma",
"values": {
"quality": 9,
"support": 7
}
}
],
"criteria": [
{
"id": "quality",
"weight": 0.7
},
{
"id": "support",
"weight": 0.3
}
]
}{
"ranking": [
{
"rank": 1,
"optionId": "beta",
"score": 0.7,
"tied": true
},
{
"rank": 1,
"optionId": "gamma",
"score": 0.7,
"tied": true
},
{
"rank": 3,
"optionId": "alpha",
"score": 0.3,
"tied": false
}
],
"criteriaSummary": [
{
"id": "quality",
"normalization": "MIN_MAX",
"observedMin": 6,
"observedMax": 9
},
{
"id": "support",
"normalization": "MIN_MAX",
"observedMin": 7,
"observedMax": 10
}
],
"tieBreak": "INPUT_ORDER",
"scores": [
{
"optionId": "alpha",
"score": 0.3,
"breakdown": [
{
"criterion": "quality",
"rawValue": 6,
"scaledValue": 0,
"weight": 0.7,
"normalizedWeight": 0.7,
"contribution": 0,
"direction": "HIGHER_IS_BETTER"
},
{
"criterion": "support",
"rawValue": 10,
"scaledValue": 1,
"weight": 0.3,
"normalizedWeight": 0.3,
"contribution": 0.3,
"direction": "HIGHER_IS_BETTER"
}
]
},
{
"optionId": "beta",
"score": 0.7,
"breakdown": [
{
"criterion": "quality",
"rawValue": 9,
"scaledValue": 1,
"weight": 0.7,
"normalizedWeight": 0.7,
"contribution": 0.7,
"direction": "HIGHER_IS_BETTER"
},
{
"criterion": "support",
"rawValue": 7,
"scaledValue": 0,
"weight": 0.3,
"normalizedWeight": 0.3,
"contribution": 0,
"direction": "HIGHER_IS_BETTER"
}
]
},
{
"optionId": "gamma",
"score": 0.7,
"breakdown": [
{
"criterion": "quality",
"rawValue": 9,
"scaledValue": 1,
"weight": 0.7,
"normalizedWeight": 0.7,
"contribution": 0.7,
"direction": "HIGHER_IS_BETTER"
},
{
"criterion": "support",
"rawValue": 7,
"scaledValue": 0,
"weight": 0.3,
"normalizedWeight": 0.3,
"contribution": 0,
"direction": "HIGHER_IS_BETTER"
}
]
}
]
}Two options where cost is inverted against a fixed rubric and the service level is used as it stands
{
"options": [
{
"id": "in-house",
"values": {
"sla": 8,
"cost": 50
}
},
{
"id": "managed",
"values": {
"sla": 10,
"cost": 100
}
}
],
"criteria": [
{
"id": "cost",
"weight": 1,
"direction": "LOWER_IS_BETTER",
"normalization": "FIXED",
"scale": {
"min": 0,
"max": 200
}
},
{
"id": "sla",
"weight": 1,
"normalization": "NONE"
}
]
}{
"ranking": [
{
"rank": 1,
"optionId": "managed",
"score": 9.5,
"tied": false
},
{
"rank": 2,
"optionId": "in-house",
"score": 7.75,
"tied": false
}
],
"criteriaSummary": [
{
"id": "cost",
"normalization": "FIXED",
"observedMin": 50,
"observedMax": 100
},
{
"id": "sla",
"normalization": "NONE",
"observedMin": 8,
"observedMax": 10
}
],
"tieBreak": "INPUT_ORDER",
"scores": [
{
"optionId": "in-house",
"score": 7.75,
"breakdown": [
{
"criterion": "cost",
"rawValue": 50,
"scaledValue": -0.25,
"weight": 1,
"normalizedWeight": 1,
"contribution": -0.25,
"direction": "LOWER_IS_BETTER"
},
{
"criterion": "sla",
"rawValue": 8,
"scaledValue": 8,
"weight": 1,
"normalizedWeight": 1,
"contribution": 8,
"direction": "HIGHER_IS_BETTER"
}
]
},
{
"optionId": "managed",
"score": 9.5,
"breakdown": [
{
"criterion": "cost",
"rawValue": 100,
"scaledValue": -0.5,
"weight": 1,
"normalizedWeight": 1,
"contribution": -0.5,
"direction": "LOWER_IS_BETTER"
},
{
"criterion": "sla",
"rawValue": 10,
"scaledValue": 10,
"weight": 1,
"normalizedWeight": 1,
"contribution": 10,
"direction": "HIGHER_IS_BETTER"
}
]
}
]
}Recommended use cases
- Compare vendors, tools or designs against agreed criteria, reproducibly
- Show a committee why one option came out ahead, criterion by criterion
- Re-run a comparison after a weight changes, and see exactly what moved
- Rank a shortlist where the criteria are measured in different units
Avoid for
- Scoring one item on its own, or scores that must not move when the set changes — use business.priority_score
- Comparisons where one criterion is a veto rather than a weight
- Ranking thousands of rows: this is a decision aid, not a query engine
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.