Single-instrument scoring
Status:
stableCategory:patternsAdded:2026-04-20Last reviewed:2026-06-03
Summary
The shared contract for every POST /api/:category/:resource/score
endpoint. Each instrument exposes a /score operation that accepts
an item-response body, runs the analytical pipeline at the requested
?depth=, and returns a finding plus a confidence report.
Per-instrument docs (instruments-<resource>.md) list what is unique
to that instrument: item map, scoring method, band cutoffs, worked
example. They defer to this document for everything else.
For the flagship composite (/api/cdri/compute), which accepts
multiple instruments in one call and uses a permissive input posture,
see composite-cdri-compute.md.
Endpoint shape
| Field | Value |
|---|---|
| Method | POST |
| Path template | /api/:category/:resource/score |
| Authentication | API key (required) |
| Request body | application/json |
| Response body | application/json |
| Idempotent | No |
| Rate-limited | Yes |
:category and :resource must match a registered pair in the
catalog. See GET /api/catalog/resources.
Purpose
- Score one instrument for one subject. Send responses, get back a score, band, and confidence.
- Opt into richer output via
?depth=.?depth=basic(default) returns the instrument score and band.?depth=contextualadds population-percentile context when a matching reference cohort exists.?depth=fulladditionally enables the recommendations layer. - Strict input validation. If the body does not include every
required item for the instrument, the request fails with
400 ValidationErrorand amissing_itemslist. No partial scoring here — that is/cdri/compute's job.
Authentication
Send a valid API key in the apikey header. See
authentication.md.
Request
Headers
| Header | Required | Description |
|---|---|---|
apikey | Yes | Consumer API key. |
Content-Type | Yes | Must be application/json. |
X-Request-ID | No | Optional correlation ID, up to 128 characters. Echoed in the response body's request_id and the X-Request-ID response header. |
Path parameters
| Parameter | Type | Description |
|---|---|---|
category | string | Catalog category. |
resource | string | Catalog resource within that category. |
Query parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
depth | string | No | basic | One of basic, contextual, full. Controls how much detail the response carries. See the depth table below. |
recommend | string | No | rich | Controls the recommendations layer. One of rules, simple, rich, or off. The recommendations layer runs only when depth=full; at lower depths this parameter is accepted but recommendations is null. See ?recommend= modes on /cdri/compute. |
audience | string | No | (unset) | Steers the audience-framed narrative text (ai_summary + per-item message) toward a specific reader. One of employee, employer, provider. When unset, narrative output stays null. See ?audience= on /cdri/compute. |
Body
{
"responses": {
"item_1": 2,
"item_2": 3
},
"demographics": {
"age_bracket": "35-44",
"sex": "female",
"occupation_class": "healthcare",
"naics_code": "621111",
"region": "TX"
}
}
Body fields
| Field | Type | Required | Description |
|---|---|---|---|
responses | object | Yes | Map of item_id → numeric value. Keys match the items[].id values from /questions; values match one of the answer_choices[].value entries. |
demographics | object | null | No | Structural descriptors that improve cohort matching and classification when supplied. Ignored at ?depth=basic. |
demographics.age_bracket | string | No | Coarse age band (for example "25-34", "35-44"). Never a raw DOB or year. |
demographics.sex | string | No | Self-reported categorical: "female", "male", "nonbinary", "undisclosed". Some instruments (notably ORT) use this to select a scoring weight table — document explicitly in the per-instrument doc. |
demographics.occupation_class | string | No | Occupation-class token (for example "retail", "manufacturing", "healthcare"). |
demographics.naics_code | string | No | Six-digit NAICS industry code, as a string. |
demographics.region | string | No | US state or region code (for example "TX", "northeast"). Never a street address. |
responses is the only required field. Every item listed in the
instrument's required_items (from
/questions) must appear as a key in
responses — otherwise the request fails with 400 ValidationError
and a missing_items list.
De-identification requirement
The standard de-identification check runs on every request body. Any
identifier-shaped value or blocked field name fails with 400 DeIdentificationViolation before the pipeline runs. See
conventions.md.
Rejected payload shapes
- An empty body —
responsesis required. - A body that sends responses at the top level instead of under
responses({ "item_1": 4 }rather than{ "responses": { "item_1": 4 } }). - A body with extra top-level keys beyond
responsesanddemographics. Pydanticextra="forbid"rejects unknowns with400 ValidationError.
The ?depth= query parameter
| Value | Confidence terms populated | Response adds |
|---|---|---|
basic (default) | input_completeness only | Score + band + confidence. |
contextual | input_completeness, imputation_variance | Adds finding.context (percentile, cohort) when a matching reference cohort exists. |
full | input_completeness, imputation_variance, model_certainty | Contextual, plus the recommendations block (rules-driven items and, when ?audience= is set, audience-framed narrative). |
Increasing depth does not change the score or band. It adds
context around the score. Your integration can start at basic and
layer up without refactoring response parsing.
Response
Success — 200 OK (depth=basic)
{
"instrument": "pain_scale",
"category": "pain",
"finding": {
"algorithm_id": "pain_scale",
"algorithm_version": "1.0.0",
"score": 6.0,
"band": "moderate",
"raw": {
"rubric_version": "1.0.0",
"responded_items": 1
},
"context": null,
"imputed": false,
"sd": null,
"prior_source": null
},
"confidence": {
"overall": 1.0,
"tier": "high",
"breakdown": {
"input_completeness": 1.0,
"imputation_variance": null,
"model_certainty": null
}
},
"depth": "basic",
"catalog_version": "0.13.0",
"request_id": "8f2c3b1d-5e4a-4a9c-9b1e-2a7f1d3e4c5b",
"timestamp": "2026-04-20T17:32:10.812Z"
}
Success — 200 OK (depth=contextual)
Adds finding.context when the pipeline finds a matching cohort.
Other fields are identical.
{
"instrument": "pain_scale",
"category": "pain",
"finding": {
"algorithm_id": "pain_scale",
"algorithm_version": "1.0.0",
"score": 6.0,
"band": "moderate",
"raw": { "rubric_version": "1.0.0", "responded_items": 1 },
"context": {
"percentile": 0.74,
"cohort_key": "age:35-44|occ:healthcare",
"cohort_label": "Healthcare, 35-44",
"cohort_size": 3200,
"source": "literature:2023"
},
"imputed": false,
"sd": null,
"prior_source": null
},
"confidence": {
"overall": 0.88,
"tier": "high",
"breakdown": {
"input_completeness": 1.0,
"imputation_variance": 1.0,
"model_certainty": null
}
},
"depth": "contextual",
"catalog_version": "0.13.0",
"request_id": "...",
"timestamp": "..."
}
Success — 200 OK (depth=full)
Adds model_certainty to the confidence breakdown and populates
the recommendations block. Unlike /cdri/compute, the /score
response does not attach a phenotype block — the flagship composite
is the right surface for phenotype classification because phenotype
reasoning depends on cross-domain signals.
The recommendations block follows the same shape and the same
?recommend=rules|simple|rich|off and
?audience=employee|employer|provider
semantics as on /cdri/compute. Narrative output (ai_summary and
per-item message) is gated on ?audience= being supplied. Without
an audience, narrative output stays null regardless of
?recommend=; the deterministic action field on every item is
always populated. See
composite-cdri-compute.md#model-generated-fields-and-non-determinism
for when ai_summary is populated, how it degrades, and the
non-determinism caveat.
Abbreviated shape (rest of the fields mirror the basic / contextual examples above):
{
"instrument": "pain_scale",
"category": "pain",
"confidence": {
"overall": 0.87,
"tier": "high",
"breakdown": {
"input_completeness": 1.0,
"imputation_variance": 1.0,
"model_certainty": 0.72
}
},
"recommendations": {
"engine_version": "1.0.0",
"ruleset_version": "1.0.0",
"disclaimer": "The Trinity Engine is a decision-support tool. Output is not medical advice, not a diagnosis, not a prescription, and not a benefits determination. The calling organization is responsible for clinical governance and for the interpretation and use of these suggestions.",
"ai_summary": {
"backend": "anthropic",
"model_version": "claude-haiku-4-5-20251001",
"audience": "provider",
"text": "The individual's pain rating falls in the moderate band. Pairing this result with a function-domain instrument (Oswestry, QuickDASH, or LEFS as appropriate) on the next encounter would support a more complete picture of the presentation."
},
"items": [],
"suppressed": [],
"cardinality_cap": null
},
"depth": "full",
"catalog_version": "0.13.0",
"request_id": "...",
"timestamp": "..."
}
At ?recommend=rules (default), ai_summary is null and the
items[] list contains only deterministic rule-pack output. At
?recommend=off, the whole recommendations field is null. See
composite-cdri-compute.md#recommendations-modes.
Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
instrument | string | Yes | Resource name matching the URL's :resource. |
category | string | Yes | Category matching the URL's :category. |
finding | object | null | Yes | The scoring output. null should not occur under normal operation; present-but-empty only if the rubric and scoring layer disagreed catastrophically. |
finding.algorithm_id | string | Yes | Identifier for the scoring algorithm that produced this finding. |
finding.algorithm_version | string (SemVer) | Yes | Algorithm version. Independent of rubric version; bumps when math shape changes. |
finding.score | number | Yes | Scalar score. Range is instrument-specific. |
finding.band | string | Yes | Band name (for example "low", "moderate", "high") as defined by the instrument rubric. Never translated for audience; this is a scoring category, not a display label. |
finding.raw | object | Yes | Opaque diagnostic metadata (for example rubric_version, responded_items). Contents are not a stable contract and may change between deploys — display if useful, but do not parse or depend on specific keys. |
finding.context | object | null | Yes | Cohort-percentile context. null at depth=basic, and also null at deeper depths when no matching reference cohort exists for this score. |
finding.context.percentile | number | When context != null | Percentile of this score within the cohort (0.0 – 1.0). |
finding.context.cohort_key | string | When context != null | Canonical cohort identifier Trinity matched on. |
finding.context.cohort_label | string | When context != null | Human-readable cohort label. |
finding.context.cohort_size | integer | When context != null | Number of reference observations in the cohort. Small cohorts (< 500) signal a less reliable percentile — callers can derate accordingly. |
finding.context.source | string | When context != null | Where the distribution came from (for example "literature:2023", "corpus:2026-03"). |
finding.imputed | boolean | Yes | Always false on /score responses — imputation only happens in /cdri/compute. |
finding.sd | number | null | Yes | Imputation standard deviation (null on /score). |
finding.prior_source | string | null | Yes | Where imputation priors came from (null on /score). |
confidence.overall | number | Yes | Overall confidence, 0.0 – 1.0. |
confidence.tier | string | Yes | Threshold classification: "low" (< 0.4), "medium" (0.4 – 0.75), "high" (≥ 0.75). |
confidence.breakdown.input_completeness | number | null | Yes | 1.0 on a successful strict /score (every required item was present) or null if the term did not participate. |
confidence.breakdown.imputation_variance | number | null | Yes | null at depth=basic; populated at contextual / full as a proxy for cohort-context quality (higher is better). |
confidence.breakdown.model_certainty | number | null | Yes | null unless depth=full and classification produced a usable distribution. |
recommendations | object | null | Yes | Recommendations output. null at ?depth=basic and ?depth=contextual. Populated at ?depth=full when at least one rule fires — same shape as on /cdri/compute, including ai_summary when the caller uses ?recommend=simple or ?recommend=rich. See composite-cdri-compute.md#recommendations-modes for the full block shape and composite-cdri-compute.md#model-generated-fields-and-non-determinism for when model-generated fields populate and when they degrade. |
depth | string | Yes | The effective depth: "basic", "contextual", or "full". Matches what you sent, or the default if you omitted the query. |
catalog_version | string (SemVer) | Yes | Catalog version of the deploy that scored this request. |
request_id | string | Yes | Correlation ID. Matches the X-Request-ID response header. |
timestamp | string (ISO 8601 UTC) | Yes | Server time when the response was emitted. Carries microsecond precision and an explicit +00:00 offset (e.g. "2026-04-21T23:09:27.554373+00:00"). |
How to read confidence
confidence.overall is a 0.0 – 1.0 rollup. Use confidence.tier
(low / medium / high) for quick branching; use
confidence.breakdown to reason about why a tier landed where it
did. Terms that did not participate for a given call are null in
the breakdown and do not contribute to overall. At depth=basic,
only input_completeness is populated; at contextual,
imputation_variance is added; at full, model_certainty is
added.
Status codes
| Code | Meaning | When |
|---|---|---|
200 | OK | Scored. |
400 | ValidationError | Body shape invalid, required items missing, or ?depth= value unknown. Extras: field (typically "responses", "depth", or an item id); missing_items and instrument when items are missing. |
400 | DeIdentificationViolation | Body contains an identifier. See conventions.md. |
401 | Unauthenticated | Missing or invalid API key. |
404 | NotFound | (category, resource) pair is not registered. |
413 | PayloadTooLarge | Body exceeds 512 KB. |
415 | UnsupportedMediaType | Content-Type is not application/json. |
429 | RateLimited | Per-consumer rate limit exceeded. |
500 | InternalError | Unexpected failure. Quote request_id in the ticket. |
503 | ServiceUnavailable | Service is temporarily unhealthy. Retry with backoff. |
The missing-items error
When responses is missing any id in the instrument's
required_items, Trinity rejects with:
{
"timestamp": "2026-04-20T17:32:10.812Z",
"error": "ValidationError",
"message": "Missing required items for 'pain_scale'",
"request_id": "...",
"missing_items": ["item_1"],
"instrument": "pain_scale"
}
missing_items is an array of the item IDs that were expected but
absent. Render this back to your user so they can complete the form.
Examples
The language-specific invocations below assume the call() helper (or
its equivalent) from quickstart.md. Per-instrument
docs (for example instruments-orebro.md)
carry their own worked-example pairs with the full item set for that
instrument; this section shows the shared pattern.
1. Canonical request and response (basic depth)
Send:
POST /api/pain/pain_scale/score HTTP/1.1
Host: engine.tripod-health.com
apikey: $ENGINE_API_KEY
Content-Type: application/json
X-Request-ID: docs-pain_scale-score-basic
{"responses":{"item_1":6}}
Receive (200 OK):
{
"instrument": "pain_scale",
"category": "pain",
"finding": {
"algorithm_id": "pain_scale",
"algorithm_version": "1.0.0",
"score": 6,
"band": "moderate",
"raw": {
"rubric_version": "1.0.0",
"responded_items": 1
},
"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-pain_scale-score-basic",
"timestamp": "2026-04-21T23:09:27.554373+00:00"
}
2. Request and response with demographics (contextual depth)
Send:
POST /api/pain/pain_scale/score?depth=contextual HTTP/1.1
Host: engine.tripod-health.com
apikey: $ENGINE_API_KEY
Content-Type: application/json
X-Request-ID: docs-pain_scale-score-contextual
{
"responses": { "item_1": 6 },
"demographics": {
"age_bracket": "35-44",
"occupation_class": "healthcare",
"region": "TX"
}
}
Receive (200 OK): identical shape as the basic example above, with
finding.context populated:
"finding": {
"algorithm_id": "pain_scale",
"algorithm_version": "1.0.0",
"score": 6,
"band": "moderate",
"raw": { "rubric_version": "1.0.0", "responded_items": 1 },
"context": {
"percentile": 85,
"cohort_key": "*",
"cohort_label": "Unconditioned (all respondents)",
"cohort_size": 1000,
"source": "placeholder"
},
"imputed": false,
"sd": null,
"prior_source": null
}
confidence.breakdown.imputation_variance becomes 1 (rather than
null) when contextualization succeeded.
3. Calling the endpoint in your language
curl
# Basic
curl -sS -X POST "https://engine.tripod-health.com/api/pain/pain_scale/score" \
-H "apikey: $ENGINE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"responses":{"item_1":6}}'
# Contextual with demographics
curl -sS -X POST "https://engine.tripod-health.com/api/pain/pain_scale/score?depth=contextual" \
-H "apikey: $ENGINE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"responses": { "item_1": 6 },
"demographics": { "age_bracket": "35-44", "occupation_class": "healthcare" }
}'
Python (requests)
# Uses the call() helper from quickstart.md.
def score(category: str, resource: str, responses: dict,
demographics: dict | None = None, depth: str = "basic") -> dict:
path = f"/api/{category}/{resource}/score?depth={depth}"
body = {"responses": responses, "demographics": demographics}
return call("POST", path, body)
result = score(
"function", "oswestry",
responses={f"item_{i}": 3 for i in range(1, 11)},
demographics={"age_bracket": "35-44", "occupation_class": "manufacturing"},
depth="contextual",
)
print(f"{result['instrument']} -> {result['finding']['score']} ({result['finding']['band']})")
Node.js (native fetch)
// Uses the call() helper from quickstart.md.
async function score(category, resource, responses, demographics, depth = "basic") {
return call(
"POST",
`/api/${category}/${resource}/score?depth=${depth}`,
{ responses, demographics },
);
}
const result = await score(
"pain", "pain_scale",
{ item_1: 6 },
{ age_bracket: "35-44", occupation_class: "healthcare" },
"contextual",
);
console.log(`${result.instrument} -> ${result.finding.score} (${result.finding.band})`);
TypeScript
// Uses the typed call<T>() helper from quickstart.md.
interface Finding {
algorithm_id: string;
algorithm_version: string;
score: number;
band: string;
raw: Record<string, unknown>;
context: null | {
percentile: number;
cohort_key: string;
cohort_label: string;
cohort_size: number;
source: string;
};
imputed: boolean;
sd: number | null;
prior_source: string | null;
}
interface ScoreResponse {
instrument: string;
category: string;
finding: Finding | null;
confidence: {
overall: number;
tier: "low" | "medium" | "high";
breakdown: {
input_completeness: number | null;
imputation_variance: number | null;
model_certainty: number | null;
};
};
// `null` at depth=basic and depth=contextual.
// At depth=full, reuses the CdriResponse["recommendations"] shape -- see
// composite-cdri-compute.md for the full type definition.
recommendations: null | {
engine_version: string;
ruleset_version: string;
disclaimer: string;
ai_summary: null | {
backend: string;
model_version: string;
audience: "employee" | "employer" | "provider";
text: string;
};
items: Array<Record<string, unknown>>;
suppressed: Array<{ rule_id: string; reason: string; details: string }>;
cardinality_cap: null | { limit: number; applied: number };
};
depth: "basic" | "contextual" | "full";
catalog_version: string;
request_id: string;
timestamp: string;
}
const result = await call<ScoreResponse>(
"POST",
"/api/pain/pain_scale/score",
{ responses: { item_1: 6 } },
);
Go
// Uses the call() helper from quickstart.md.
body := map[string]any{
"responses": map[string]int{"item_1": 6},
"demographics": map[string]string{
"age_bracket": "35-44",
"occupation_class": "healthcare",
},
}
result, err := call("POST", "/api/pain/pain_scale/score?depth=contextual", body, 3)
if err != nil {
panic(err)
}
finding := result["finding"].(map[string]any)
fmt.Printf("%v -> %v (%v)\n",
result["instrument"], finding["score"], finding["band"])
Java
// Uses the call() helper from quickstart.md.
// For real code, build the body with Jackson/Gson rather than string
// concatenation; the minimal example below is readable not production.
String body = "{\"responses\":{\"item_1\":6}}";
String result = call("POST", "/api/pain/pain_scale/score", body, 3);
System.out.println("instrument: " + extract(result, "instrument"));
C# (.NET HttpClient)
// Uses the Call() helper from quickstart.md.
var result = await Call(HttpMethod.Post, "/api/pain/pain_scale/score?depth=contextual",
new {
responses = new { item_1 = 6 },
demographics = new { age_bracket = "35-44", occupation_class = "healthcare" },
});
var finding = result.GetProperty("finding");
var score = finding.GetProperty("score").GetDouble();
var band = finding.GetProperty("band").GetString();
Console.WriteLine($"{result.GetProperty("instrument").GetString()} -> {score} ({band})");
PowerShell
# Uses the Invoke-Engine helper from quickstart.md.
$result = Invoke-Engine -Method Post -Path "/api/pain/pain_scale/score?depth=contextual" -Body @{
responses = @{ item_1 = 6 }
demographics = @{ age_bracket = "35-44"; occupation_class = "healthcare" }
}
"$($result.instrument) -> $($result.finding.score) ($($result.finding.band))"
4. Error scenarios
Missing required items
Send (depression_screen requires items 1–9 but we sent only item 1):
curl -sS -X POST "https://engine.tripod-health.com/api/psychosocial/depression_screen/score" \
-H "apikey: $ENGINE_API_KEY" \
-H "Content-Type: application/json" \
-H "X-Request-ID: docs-score-missing-items" \
-d '{"responses":{"item_1":2}}' \
-w "\nHTTP %{http_code}\n"
Receive (400):
{
"timestamp": "2026-04-21T23:10:00.000Z",
"error": "ValidationError",
"message": "Missing required items for 'depression_screen'",
"missing_items": ["item_2","item_3","item_4","item_5","item_6","item_7","item_8","item_9"],
"instrument": "depression_screen",
"request_id": "docs-score-missing-items"
}
Invalid depth
Send:
curl -sS -X POST "https://engine.tripod-health.com/api/pain/pain_scale/score?depth=deep" \
-H "apikey: $ENGINE_API_KEY" \
-H "Content-Type: application/json" \
-H "X-Request-ID: docs-score-bad-depth" \
-d '{"responses":{"item_1":6}}' \
-w "\nHTTP %{http_code}\n"
Receive (400):
{
"timestamp": "2026-04-21T23:10:00.000Z",
"error": "ValidationError",
"message": "Unknown depth 'deep'. Valid values: basic, contextual, full.",
"field": "depth",
"request_id": "docs-score-bad-depth"
}
Intended use
- Score a single instrument in isolation when the caller only needs one dimension of risk (for example, a pain-only screen).
- Drive a one-instrument UI flow. The
/questionsendpoint populates the form;/scorereturns the result. - Feed an upstream pipeline that consumes instrument scores.
Layer-1
scoreandbandare the scalar contract.
Mistaken use
- Do not use
/scorefor cross-instrument scoring. Every/scorecall scores exactly one instrument. UsePOST /api/cdri/computefor a cross-domain composite — one call, many instruments, permissive input. - Do not send partial responses here.
/scoreis strict: missing anyrequired_itemsentry fails the request./cdri/computeis the permissive endpoint. - Do not retry a
400 ValidationErrorunchanged. The request will fail the same way. Fix themissing_itemsorfield, then retry. - Do not cache a score response by request body. Identical inputs
are served again on each call. Retry logic should handle
429and5xxonly. - Do not treat
confidence.overallas a diagnostic statement. Confidence reflects how much of the expected input was present and how well the supporting context matched — not clinical severity. Usefinding.bandfor severity.
Debugging
- Capture
request_id. Present in the response body and theX-Request-IDresponse header. 400 ValidationErrorwithmissing_items— you did not send every required item. Look up the currentrequired_itemsviaGET /api/.../questionsand confirm your body keys match.400 ValidationErrorwithfield: "responses"— the body did not include a top-levelresponsesobject. Wrap your keys inside{"responses": {...}}.400 ValidationErrorwithfield: "depth"—?depth=value is notbasic,contextual, orfull.400 DeIdentificationViolation— strip the offending field or value and retry. See conventions.md.404 NotFound— the(category, resource)pair is not registered. Cross-referenceGET /api/catalog/resources.- Score looks wrong for the inputs you sent. Read the
per-instrument doc for the instrument's bands and scoring
behavior, then compute the expected score by hand against the
rubric and compare with
finding.scoreandfinding.band. - Confidence tier is unexpectedly low. Read
breakdown— it tells you which term is dragging the overall down. Small reference cohorts derateimputation_variance; a noisy phenotype classification deratesmodel_certainty(atdepth=full).
Rate limits
See rate-limits.md.
Related
- patterns-questions.md — the paired
/questionsoperation, which describes the item IDs and answer choices that must feed this endpoint's body. - composite-cdri-compute.md — the cross-instrument, permissive-input counterpart.
- Per-instrument docs (
instruments-<resource>.md) — each one documents the specific item set, scoring method, band cutoffs, and clinical caveats for that instrument. - errors.md — full error taxonomy.
Change history
| Date | Change |
|---|---|
| 2026-04-20 | Initial publication. |
| 2026-06-03 | Bumped catalog_version to the live value; removed the tpa audience. |