Loading documentation

Access required

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

Sign out
Skip to main content

CDRI Plan (Value of Information)

Status: beta Category: composite Added: 2026-07-12 Last reviewed: 2026-07-12

Summary

Given a partial CDRI input, /cdri/plan tells you which questions to ask next, in what order, and how much each one raises the reliability of the CDRI result. It accepts the same body as /cdri/compute but inverts the output: instead of the risk readout, it returns a ranked plan of the remaining question batches (instruments) most worth asking, each annotated with the marginal confidence it buys and the projected confidence and tier after completing it.

It is the value-of-information inverse of /cdri/compute, and the quantified counterpart to /intake/next: where /intake/next hands back a single next batch, /plan returns the whole ranked map so you can decide "ask the top 3" or "ask until we reach medium confidence" up front.

How it works (for integrators)

  • Read-only and deterministic. The plan is computed from coverage, not from the subject's answers, so the same input always yields the same plan. No AI call is made.
  • The unit of value is the instrument, not the item. A question batch earns its confidence only when all of its required items are answered — a half-finished batch scores nothing. There is no defensible independent per-question probability, and the endpoint does not pretend otherwise (see Mistaken use).
  • Gains are cumulative and submodular — never additive. Each row's marginal_confidence_gain assumes the rows above it are completed. The first instrument covering an uncovered domain delivers a large jump; a second instrument in an already-covered domain delivers almost nothing. Do not sum the gains — read projected_confidence_after for the cumulative value.
  • Coverage drives the plan. CDRI confidence rises as you cover the four required domains (pain_load, psychosocial_load, function_load, rtw_load). The plan front-loads the cheapest batch that covers each missing domain.

Endpoint

FieldValue
MethodPOST
Path/api/cdri/plan
Full URLhttps://engine.tripod-health.com/api/cdri/plan
AuthenticationAPI key (required)
Request bodyapplication/json — identical to /cdri/compute
Response bodyapplication/json
IdempotentYes (deterministic; no model-generated fields)
Rate-limitedYes

Purpose

A caller who wants a reliable CDRI must decide which instruments to administer and in what order — under a real question budget. /cdri/compute scores whatever you send and reports a confidence tier after the fact. /cdri/plan answers the question before you spend a single question: given what you have so far, what is the highest-value thing to ask next, and what reliability will it buy?

Typical use: drive a questionnaire UI that shows "answering these raises our confidence from low to medium," or pre-compute a fixed battery for a cohort.

Authentication

Every request must include a valid API key in the apikey header. Keys are issued per consumer; see authentication. A missing or unknown key returns 401 Unauthenticated at the gateway before the request reaches the engine.

Request

Headers

HeaderRequiredDescription
apikeyYesConsumer API key.
Content-TypeYesMust be application/json.
X-Request-IDNoOptional client-generated correlation ID; echoed in the response.

Query parameters

ParameterTypeRequiredDefaultDescription
include_optionalbooleanNofalseWhen true, also plans optional domains (e.g. iatrogenic_loadopioid_risk). Optional domains do not raise completeness, so their confidence gain is negligible; they are surfaced for their phenotype coverage (see informs_phenotypes).
unitstringNoinstrumentinstrument (one row per question batch) or domain (collapse to one row per uncovered domain, using its highest-ranked instrument).
budgetintegerNocatalog maxCap on plan length. Must be a positive integer.

Body

Byte-identical to /cdri/compute: a partial instruments map, optional demographics, and optional situation/onset. An empty body {} is valid and returns the full cold-start plan (every required domain uncovered).

{
"instruments": {
"pain_scale": { "responses": { "item_1": 6 } }
},
"demographics": { "age_bracket": "45-54", "occupation_class": "construction", "region": "OH" }
}

Body fields

FieldTypeRequiredDescription
instrumentsobjectNoMap of instrument_id{ "responses": { item_id: value } }. Any subset, any partial responses. Omit or {} for a cold-start plan.
demographicsobject | nullNoStructural descriptors (age_bracket, sex, occupation_class, naics_code, region). Selects the cohort used for the imputation term.
situationobject | nullNoAccepted for input parity with /cdri/compute. Does not influence the plan (acuity does not feed confidence); a note is added to the response when present.
onsetstring | nullNoSame as situation — accepted for parity, not used in planning. The raw date never enters the planner.

De-identification requirement

/cdri/plan is subject to the same de-identification filter as every endpoint. Send structural descriptors only; an identifier-shaped value anywhere in the body returns 400 DeIdentificationViolation.

Response

Success — 200 OK

A verbatim cold-start capture (empty body {}). The questions arrays are trimmed to the first item of each batch for readability — the real response carries every item, in the same shape as /questions.

{
"composite_id": "cdri",
"current": {
"confidence": {
"overall": 0.2143,
"tier": "low",
"breakdown": { "input_completeness": 0.0, "imputation_variance": 0.5, "model_certainty": null }
},
"domains_required": ["pain_load", "psychosocial_load", "function_load", "rtw_load"],
"domains_covered": [],
"instruments_complete": [],
"instruments_partial": []
},
"plan": [
{
"rank": 1,
"instrument_id": "pain_scale",
"instrument_version": "1.0.0",
"domain": "pain_load",
"cost_items": 1,
"missing_required_items": ["item_1"],
"questions": [
{ "instrument_id": "pain_scale", "item_id": "item_1", "prompt": "Pain intensity right now (0 = no pain, 10 = worst imaginable)", "required": true, "response_type": "number", "min": 0, "max": 10 }
],
"marginal_confidence_gain": 0.3571,
"projected_confidence_after": 0.5714,
"projected_tier_after": "medium",
"value_per_item": 0.3571,
"informs_phenotypes": ["established_chronicity", "multi_domain_catastrophic", "pain_dominant"],
"rationale": "First instrument covering pain_load; 1 item; raises required-domain coverage to 1 of 4."
},
{
"rank": 2,
"instrument_id": "recovery_expectation",
"instrument_version": "1.0.0",
"domain": "rtw_load",
"cost_items": 1,
"missing_required_items": ["item_1"],
"questions": [
{ "instrument_id": "recovery_expectation", "item_id": "item_1", "prompt": "How likely is it that you will be able to do your usual work 6 months from now?", "required": true, "response_type": "number", "min": 0, "max": 10 }
],
"marginal_confidence_gain": 0.1429,
"projected_confidence_after": 0.7143,
"projected_tier_after": "medium",
"value_per_item": 0.1429,
"informs_phenotypes": ["established_chronicity", "multi_domain_catastrophic", "rtw_barrier"],
"rationale": "First instrument covering rtw_load; 1 item; raises required-domain coverage to 2 of 4."
},
{
"rank": 3,
"instrument_id": "anxiety_screen",
"instrument_version": "1.0.0",
"domain": "psychosocial_load",
"cost_items": 7,
"missing_required_items": ["item_1", "item_2", "item_3", "item_4", "item_5", "item_6", "item_7"],
"questions": [
{ "instrument_id": "anxiety_screen", "item_id": "item_1", "prompt": "Feeling nervous, anxious, or on edge", "required": true, "response_type": "single_select", "options": [{ "value": 0, "label": "Not at all" }, { "value": 1, "label": "Several days" }, { "value": 2, "label": "More than half the days" }, { "value": 3, "label": "Nearly every day" }] }
],
"marginal_confidence_gain": 0.1428,
"projected_confidence_after": 0.8571,
"projected_tier_after": "high",
"value_per_item": 0.0204,
"informs_phenotypes": ["multi_domain_catastrophic", "psychosocial_dominant"],
"rationale": "First instrument covering psychosocial_load; 7 items; raises required-domain coverage to 3 of 4."
},
{
"rank": 4,
"instrument_id": "oswestry",
"instrument_version": "1.0.0",
"domain": "function_load",
"cost_items": 10,
"missing_required_items": ["item_1", "item_2", "item_3", "item_4", "item_5", "item_6", "item_7", "item_8", "item_9", "item_10"],
"questions": [
{ "instrument_id": "oswestry", "item_id": "item_1", "prompt": "Pain intensity", "required": true, "response_type": "single_select", "options": [{ "value": 0, "label": "No problem" }, { "value": 1, "label": "Mild problem" }, { "value": 2, "label": "Moderate problem" }, { "value": 3, "label": "Fairly severe problem" }, { "value": 4, "label": "Very severe problem" }, { "value": 5, "label": "Complete disability" }] }
],
"marginal_confidence_gain": 0.1429,
"projected_confidence_after": 1.0,
"projected_tier_after": "high",
"value_per_item": 0.0143,
"informs_phenotypes": ["established_chronicity", "multi_domain_catastrophic", "pain_dominant"],
"rationale": "First instrument covering function_load; 10 items; raises required-domain coverage to 4 of 4."
}
],
"confidence_formula": {
"weights": { "completeness": 0.4, "model_certainty": 0.3, "imputation_variance": 0.3 },
"active_terms": ["completeness", "imputation_variance"],
"note": "model_certainty is held out at CONTEXTUAL depth; the two active weights renormalize to sum to 1."
},
"notes": [
"Gains are the deterministic, coverage-driven confidence floor and are independent of the subject's answers.",
"An instrument earns its value only when ALL its required items are answered; partial batches score nothing.",
"Gains are cumulative and submodular -- each row's gain assumes the rows above it are completed. Do not sum them.",
"value_per_item is amortized (gain / missing items), not an independent per-question probability.",
"model_certainty (answer-dependent) is held out at CONTEXTUAL depth; realized confidence after scoring may be higher."
],
"catalog_version": "0.22.0",
"matrix_version": "2026-06-02-first-pass",
"request_id": "9b02322b-0ff9-4df6-a8e3-3ed3c1c67b97",
"timestamp": "2026-07-12T23:12:14.500180+00:00"
}

Response fields

FieldTypeAlways presentDescription
composite_idstringYesAlways "cdri".
currentobjectYesThe state before any planned batch — see below.
planarrayYesGreedy-ranked plan rows — see below. Empty when nothing is worth asking.
confidence_formulaobjectYesThe confidence weights, the terms active at this depth, and a note. Makes the numbers auditable.
notesarrayYesPlain-English statements of how to read the gains (submodular, coverage-driven, value_per_item amortized).
catalog_versionstringYesCatalog version served.
matrix_versionstring | nullYesActive phenotype-matrix version behind informs_phenotypes.
request_idstringYesCorrelation ID.
timestampstring (ISO 8601 UTC)YesServer time at response emission.
current
FieldTypeDescription
confidenceobjectConfidence of the input as-is. Same shape as CDRI confidence; model_certainty is null here (see below).
domains_requiredarrayThe four required CDRI domains.
domains_coveredarrayRequired domains already covered by a complete instrument.
instruments_completearrayInstruments whose required items are all answered.
instruments_partialarrayInstruments present but below their required-items bar (they currently score nothing).
plan[]
FieldTypeDescription
rankinteger1-based rank. Ask in this order.
instrument_idstringThe instrument (question batch) to administer.
instrument_versionstringRubric version.
domainstringThe CDRI domain this instrument maps to.
cost_itemsintegerNumber of still-missing required items — the burden of completing this batch.
missing_required_itemsarrayThe specific item ids still needed.
questionsarrayRender-ready items, same shape as /questions.
marginal_confidence_gainnumberConfidence gained by completing this batch, given the batches ranked above it are completed. Cumulative — do not sum across rows.
projected_confidence_afternumberCumulative overall confidence after completing this batch and all above it.
projected_tier_afterstringTier (low/medium/high) at that projected confidence.
value_per_itemnumberAmortized marginal_confidence_gain / cost_items. A UI-ranking convenience, not an independent per-question probability.
informs_phenotypesarrayPhenotypes this instrument feeds in the active matrix. Reference metadata, not part of the gain math.
rationalestringOne-line explanation of why the batch is ranked here.

Why model_certainty is null

CDRI confidence normally combines three terms: completeness, imputation_variance, and model_certainty. The first two depend only on which instruments are present and complete and on the demographic cohort — never on the answer values — so they can be projected before the answers exist. model_certainty depends on the answers themselves and is therefore not knowable in advance. /cdri/plan holds it out and reports the honest, coverage-driven confidence floor; the remaining two weights renormalize to sum to 1. Realized confidence after scoring via /cdri/compute may be higher.

Partial input

Sending one complete instrument covers its domain and raises current confidence. The plan then targets only the still-uncovered domains. Verbatim capture for {"instruments":{"pain_scale":{"responses":{"item_1":6}}}, ...} (plan[].questions omitted here for brevity):

{
"current": {
"confidence": { "overall": 0.5714, "tier": "medium", "breakdown": { "input_completeness": 0.25, "imputation_variance": 1.0, "model_certainty": null } },
"domains_required": ["pain_load", "psychosocial_load", "function_load", "rtw_load"],
"domains_covered": ["pain_load"],
"instruments_complete": ["pain_scale"],
"instruments_partial": []
},
"plan": [
{ "rank": 1, "instrument_id": "recovery_expectation", "domain": "rtw_load", "cost_items": 1, "marginal_confidence_gain": 0.1429, "projected_confidence_after": 0.7143, "projected_tier_after": "medium" },
{ "rank": 2, "instrument_id": "anxiety_screen", "domain": "psychosocial_load", "cost_items": 7, "marginal_confidence_gain": 0.1428, "projected_confidence_after": 0.8571, "projected_tier_after": "high" },
{ "rank": 3, "instrument_id": "oswestry", "domain": "function_load", "cost_items": 10, "marginal_confidence_gain": 0.1429, "projected_confidence_after": 1.0, "projected_tier_after": "high" }
]
}

Note that current.confidence.overall here (0.5714) equals the cold-start plan's rank-1 projected_confidence_after — the projection matched the real subsequent state.

include_optional=true

Adds optional-domain instruments after the required plan. They carry negligible confidence gain but are surfaced for their unique phenotype coverage — e.g. opioid_risk is the sole feeder of iatrogenic_escalation, so skipping it leaves that phenotype permanently at 0. Verbatim tail row:

{
"rank": 5,
"instrument_id": "opioid_risk",
"domain": "iatrogenic_load",
"marginal_confidence_gain": 0.0,
"informs_phenotypes": ["iatrogenic_escalation", "multi_domain_catastrophic"],
"rationale": "Optional domain iatrogenic_load; negligible confidence gain -- its value is the phenotype coverage in informs_phenotypes."
}

Status codes

CodeMeaningWhen
200OKPlan computed (including an empty plan when nothing is worth asking).
400ValidationErrorMalformed JSON, an unknown instrument_id, or a bad query param (unit, budget).
400DeIdentificationViolationRequest body contained an identifier.
401UnauthenticatedMissing or invalid API key.
413PayloadTooLargeBody exceeds the 512 KB limit.
415UnsupportedMediaTypeContent-Type is not application/json.
429RateLimitedPer-consumer rate limit exceeded.
500InternalErrorUnexpected server-side failure.
503ServiceUnavailableA required dependency is temporarily unhealthy.

Error envelope

All non-2xx responses use the uniform envelope. Verbatim capture for ?unit=bogus:

{
"timestamp": "2026-07-12T22:12:12.439439+00:00",
"error": "ValidationError",
"message": "unit must be 'instrument' or 'domain'",
"request_id": "docs-plan-err"
}

See errors for the full list.

Examples

curl — cold-start plan

curl -sS -X POST "https://engine.tripod-health.com/api/cdri/plan" \
-H "apikey: $ENGINE_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'

curl — plan from a partial input

curl -sS -X POST "https://engine.tripod-health.com/api/cdri/plan?budget=3" \
-H "apikey: $ENGINE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "instruments": { "pain_scale": { "responses": { "item_1": 6 } } }, "demographics": { "age_bracket": "45-54", "occupation_class": "construction", "region": "OH" } }'

Python — ask the top N, then score

# Uses the call() helper from quickstart.md.
plan = call("POST", "/api/cdri/plan", {"instruments": {}, "demographics": {"age_bracket": "45-54"}})

budget_items, asked = 8, []
for step in plan["plan"]:
if step["cost_items"] > budget_items:
break
budget_items -= step["cost_items"]
asked.append(step["instrument_id"])
# render step["questions"], collect answers into your instruments map...

print("ask these batches:", asked)
print("projected confidence if all asked:", plan["plan"][len(asked) - 1]["projected_confidence_after"])
# Later, submit the collected answers to /api/cdri/compute for the scored result.

Intended use

  • Planning a questionnaire battery under a question budget: "ask the top 3."
  • Showing a user "answering these raises our confidence from low to medium" before they start.
  • Deciding whether an optional instrument is worth its burden (via include_optional + informs_phenotypes).

Mistaken use

  • Do not sum marginal_confidence_gain across rows. The gains are submodular and cumulative — read projected_confidence_after instead.
  • Do not treat value_per_item as an independent per-question probability. It is an amortized ranking convenience; a batch buys its confidence only when fully completed.
  • Do not use /cdri/plan to score. It never returns a CDRI score, magnitudes, or a phenotype distribution — that is /cdri/compute.
  • Do not expect situation/onset to change the plan. They are accepted for input parity only; acuity does not feed confidence.
  • Do not confuse this with /intake/next. /intake/next returns a single next batch in a stateful-feeling loop; /plan returns the whole ranked value map in one shot.

Rate limits

Per-consumer limits are enforced at the gateway; see rate limits.

Change history

DateChange
2026-07-12Initial publication.