Loading documentation

Access required

Your account doesn't have access to the documentation. Contact your administrator.

Sign out
Skip to main content

Opioid Risk Tool

Status: stable Category: iatrogenic Added: 2026-04-20 Last reviewed: 2026-04-21

Summary

The Opioid Risk Tool (ORT) is Trinity's pre-prescription risk screen for opioid misuse. Five yes/no items, with item weights that differ by self-reported sex. Totals map to three tiers from low to high.

⚠ Sex-conditional weighting

The ORT is unusual in the catalog: item weights change based on the caller's reported demographics.sex. Male and female subjects who answer identically can receive different scores.

  • Weights for sex = "male": item_1=3, item_2=4, item_3=1, item_4=0, item_5=2.
  • Weights for sex = "female": item_1=1, item_2=2, item_3=1, item_4=3, item_5=1.
  • If demographics.sex is missing, null, or any value other than "male" or "female", Trinity falls back to the male weights as the default.

The default-to-male fallback matches the original ORT's behavior when sex is not recorded, but it means reporting the wrong sex value silently produces a wrong score. Confirm demographics.sex is filled out and accurate before relying on ORT scores.

Non-binary and non-disclosed subjects are not accommodated by the published ORT; clinical interpretation in those populations is outside what the score was validated on.

Reduced-item form

The published ORT has ten sub-items (items 1 and 2 each split into three sub-items for alcohol, illegal drugs, and prescription drugs). Trinity implements a five-item flattened form where items 1 and 2 are single yes/no questions covering all substance classes.

A score from this five-item form is not directly comparable to a score from a full ten-sub-item ORT administered elsewhere. Keep your clinical interpretation anchored to the five-item tiers documented below.

Resource identity

FieldValue
Categoryiatrogenic
Resourceopioid_risk
Instrument IDopioid_risk
Rubric version1.0.0
Path prefix/api/iatrogenic/opioid_risk

Operations

OperationMethodPathContract
QuestionsGET/api/iatrogenic/opioid_risk/questionspatterns-questions.md
ScorePOST/api/iatrogenic/opioid_risk/scorepatterns-score.md

Items

Five binary yes/no items.

IDPrompt (paraphrased)
item_1Family history of substance abuse (alcohol / illegal drugs / prescription).
item_2Personal history of substance abuse (alcohol / illegal drugs / prescription).
item_3Age between 16 and 45.
item_4History of preadolescent sexual abuse.
item_5Psychological disease (ADD, OCD, bipolar, schizophrenia, depression).

Answer choices per item:

ValueLabel
0No
1Yes

Scoring

FieldValue
Methodweighted_sum_conditional with conditional_on = "sex".
Score range010 in practice. (The rubric's high band extends to 30 to absorb any upstream scoring change; no realistic input produces a score above 10 on either weight table.)

Weight tables

Male weights (also the default when sex is missing or not one of "male"/"female"):

ItemWeight
item_13
item_24
item_31
item_40
item_52

Female weights:

ItemWeight
item_11
item_22
item_31
item_43
item_51

Bands

BandRange (inclusive)Clinical meaning
low03Low risk for opioid misuse.
moderate47Moderate risk — consider monitoring / alternative plans.
high830High risk — generally a contraindication absent compensating controls.

The 0–3, 4–7, 8+ cutoffs follow the published ORT interpretation for the categorical risk tier.

Required items

All five items are required for a strict /score call. demographics.sex is not a required item for request validation (the request will succeed without it), but its absence means the Trinity uses male-default weights, which may not be what the caller intends.

Worked example

Every item answered with 1 (every flag raised), sex = male. See quickstart.md for per-language scaffolding and patterns-score.md for the shared /score contract.

Send:

POST /api/iatrogenic/opioid_risk/score HTTP/1.1
Host: engine.tripod-health.com
apikey: $ENGINE_API_KEY
Content-Type: application/json
X-Request-ID: docs-opioid_risk-score-basic

{
"responses": {
"item_1": 1, "item_2": 1, "item_3": 1, "item_4": 1, "item_5": 1,
"sex": "male"
}
}

Receive (200 OK):

{
"instrument": "opioid_risk",
"category": "iatrogenic",
"finding": {
"algorithm_id": "opioid_risk",
"algorithm_version": "1.0.0",
"score": 10,
"band": "high",
"raw": {
"rubric_version": "1.0.0",
"responded_items": 6
},
"context": null,
"imputed": false,
"sd": null,
"prior_source": null
},
"confidence": {
"overall": 1,
"tier": "high",
"breakdown": {
"input_completeness": 1,
"imputation_variance": null,
"model_certainty": null
}
},
"recommendations": null,
"depth": "basic",
"catalog_version": "0.13.0",
"request_id": "docs-opioid_risk-score-basic",
"timestamp": "2026-04-21T23:15:03.422789+00:00"
}

Computation with male weights: 1 × 3 + 1 × 4 + 1 × 1 + 1 × 0 + 1 × 2 = 10high tier (≥ 8).

Sex-conditional scoring

Identical yes/no answers can produce different bands depending on the value of responses.sex. Trinity's weighted_sum_conditional method reads the condition value from responses.sex, not from demographics.sex. Two subjects answering {item_1: 1, item_2: 0, item_3: 1, item_4: 0, item_5: 0}:

  • With sex: "male": 1 × 3 + 0 × 4 + 1 × 1 + 0 × 0 + 0 × 2 = 4moderate.
  • With sex: "female": 1 × 1 + 0 × 2 + 1 × 1 + 0 × 3 + 0 × 1 = 2low.

Omitting responses.sex makes Trinity fall back to the _default weights (which match male today) — which can inflate the score for a female subject. Always pass sex inside responses to pin the weight table explicitly. A future rubric revision may move this condition into demographics; today it lives in responses.

Calling in your language

curl

curl -sS -X POST "https://engine.tripod-health.com/api/iatrogenic/opioid_risk/score" \
-H "apikey: $ENGINE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"responses":{"item_1":1,"item_2":1,"item_3":1,"item_4":1,"item_5":1,"sex":"male"}}'

Python (requests)

# Uses the call() helper from quickstart.md.
result = call("POST", "/api/iatrogenic/opioid_risk/score", {
"responses": {
"item_1": 1, "item_2": 1, "item_3": 1, "item_4": 1, "item_5": 1,
"sex": "male",
},
})
print(result["finding"]["score"], result["finding"]["band"])

Node.js (native fetch)

// Uses the call() helper from quickstart.md.
const result = await call("POST", "/api/iatrogenic/opioid_risk/score", {
responses: {
item_1: 1, item_2: 1, item_3: 1, item_4: 1, item_5: 1,
sex: "male",
},
});
console.log(result.finding.score, result.finding.band);

TypeScript

// Uses the typed call<T>() helper from quickstart.md.
const result = await call<ScoreResponse>(
"POST",
"/api/iatrogenic/opioid_risk/score",
{
responses: {
item_1: 1, item_2: 1, item_3: 1, item_4: 1, item_5: 1,
sex: "male",
},
},
);

Go

// Uses the call() helper from quickstart.md.
responses := map[string]any{
"item_1": 1, "item_2": 1, "item_3": 1, "item_4": 1, "item_5": 1,
"sex": "male",
}
result, err := call("POST", "/api/iatrogenic/opioid_risk/score",
map[string]any{"responses": responses}, 3)
if err != nil {
panic(err)
}
finding := result["finding"].(map[string]any)
fmt.Printf("ORT: %v (%v)\n", finding["score"], finding["band"])

Java

// Uses the call() helper from quickstart.md.
String body = "{\"responses\":{"
+ "\"item_1\":1,\"item_2\":1,\"item_3\":1,\"item_4\":1,\"item_5\":1,"
+ "\"sex\":\"male\"}}";
String result = call("POST",
"/api/iatrogenic/opioid_risk/score", body, 3);
System.out.println(result);

C# (.NET HttpClient)

// Uses the Call() helper from quickstart.md.
var result = await Call(HttpMethod.Post,
"/api/iatrogenic/opioid_risk/score",
new {
responses = new Dictionary<string, object> {
["item_1"] = 1, ["item_2"] = 1, ["item_3"] = 1,
["item_4"] = 1, ["item_5"] = 1,
["sex"] = "male",
},
});
var finding = result.GetProperty("finding");
Console.WriteLine($"ORT: {finding.GetProperty("score").GetDouble()} ({finding.GetProperty("band").GetString()})");

PowerShell

# Uses the Invoke-Engine helper from quickstart.md.
$result = Invoke-Engine -Method Post -Path "/api/iatrogenic/opioid_risk/score" -Body @{
responses = @{
item_1 = 1; item_2 = 1; item_3 = 1; item_4 = 1; item_5 = 1
sex = "male"
}
}
"ORT: $($result.finding.score) ($($result.finding.band))"

Clinical interpretation

  • What it screens. Risk of aberrant opioid-related behavior before starting opioid therapy.
  • Not a diagnosis. The ORT flags candidates for heightened monitoring or alternative plans; it does not diagnose opioid-use disorder.
  • Population. Validated primarily in adult chronic-pain populations considering long-term opioid therapy.

Intended use

  • Pre-prescription risk stratification in workflows considering opioid therapy.
  • Prompting alternative-plan conversations at moderate or high tiers.
  • Feeding the CDRI composite as the iatrogenic-risk signal.

Mistaken use

  • Do not omit sex and accept the resulting score as unisex-appropriate. The default is male-weighted; for female subjects the default yields an inflated score.
  • Do not use the ORT as a clinical gatekeeper in isolation. It is one signal among several (history, clinical interview, medication reconciliation). A low tier does not guarantee safety; a high tier does not preclude careful opioid therapy with adequate controls.
  • Do not compare ORT scores from this endpoint to ORT scores from a full ten-sub-item administration. Trinity uses the five-item flattened form.
  • Do not administer to non-binary or non-disclosed subjects and expect a validated result. The published ORT scoring assumes binary sex; Trinity does the same.

Debugging

  1. Capture request_id. Present in every response body.
  2. Score does not change when sex is flipped. Confirm sex is sent inside responses, not inside demographics, per the example above. Trinity's conditional scoring reads from the responses object.
  3. Unexpectedly moderate or high tier for a female subject with low risk factors. You likely forgot the sex key; the Trinity applied the male-default weights, which tend to assign higher weight to items 1 and 2.
  4. 400 ValidationError with missing_items. Send all five item responses. Omitting sex does not raise this error (it is not in the rubric's required_items).

Change history

DateChange
2026-04-20Initial publication.