# Scoring (https://form.dimah.dev/docs/plugins/scoring)



`@dimah-form/scoring` computes named scores from a response definition snapshot
and its answers. It never writes scores into `answers` and adds no tables.

```bash
npm i @dimah-form/scoring
```

```ts title="lib/form-plugins.ts"
import { scoringPlugin } from "@dimah-form/scoring";
import { createDefineForm } from "@dimah-form/server";

export const plugins = [scoringPlugin()] as const;
export const defineScoredForm = createDefineForm({ plugins });
```

```ts title="lib/form.ts"
import { dimahForm } from "@dimah-form/server";
import { plugins } from "@/lib/form-plugins";
import { forms } from "@/lib/forms";

export const form = dimahForm({
  database,
  forms,
  plugins,
});
```

For browser-side previews and typed plugin endpoints, add the matching
`scoringClientPlugin()` from `@dimah-form/scoring/client`. Server plugins are
not inferred by `createFormClient`. Authorize `getResponseScores` as you would
any other admin operation.

```ts title="lib/form-client.ts"
import { scoringClientPlugin } from "@dimah-form/scoring/client";
import { createFormClient } from "@dimah-form/react";
import type { Form } from "@/lib/form";

const plugins = [scoringClientPlugin()] as const;
export const formClient = createFormClient<Form, typeof plugins>({ plugins });
```

Use the root entry on the server, `/client` in browser bundles, and
`@dimah-form/scoring/document` in isomorphic packages that need document
readers without loading the server plugin.

## Author a score [#author-a-score]

There is no `likert` field type. Use `select`, `multiSelect`, `number`, or
`boolean`, then configure scoring in `meta.scoring`.

```ts title="lib/forms/gad7.ts"
import { defineScoredForm } from "@/lib/form-plugins";

const likert = [
  { value: "0", label: "Not at all", meta: { scoring: { points: 0 } } },
  { value: "1", label: "Several days", meta: { scoring: { points: 1 } } },
  { value: "2", label: "More than half", meta: { scoring: { points: 2 } } },
  { value: "3", label: "Nearly every day", meta: { scoring: { points: 3 } } },
];

export const gad7 = defineScoredForm({
  title: "GAD-7",
  meta: {
    scoring: {
      variables: [{ id: "gad7", label: "GAD-7", min: 0, max: 21 }],
      bands: [
        { variable: "gad7", from: 0, to: 4, label: "Minimal" },
        { variable: "gad7", from: 5, to: 9, label: "Mild" },
        { variable: "gad7", from: 10, to: 14, label: "Moderate" },
        { variable: "gad7", from: 15, to: 21, label: "Severe" },
      ],
    },
  },
  fields: [
    {
      id: "q1",
      type: "select",
      options: likert,
      meta: { scoring: { variable: "gad7" } },
    },
    // Add q2 through q7 with the same mapping.
  ],
});
```

`option.points` contributes to a field's `variable`. Use `option.add` when one
choice contributes to one or more variables; never mix it with `points`.
`reverse` works for `select`, `number`, and `boolean`, not `multiSelect`.

```ts
import type {
  ScoringAdd,
  ScoringBand,
  ScoringFieldMeta,
  ScoringFormMeta,
  ScoringFormula,
  ScoringPluginOptions,
  ScoringVariable,
} from "@dimah-form/scoring";
```

### ScoringPluginOptions [#scoringpluginoptions]

<AutoTypeTable path="packages/scoring/src/plugin.ts" name="ScoringPluginOptions" />

### ScoringFormMeta [#scoringformmeta]

<AutoTypeTable path="packages/scoring/src/meta.ts" name="ScoringFormMeta" />

### ScoringVariable [#scoringvariable]

<AutoTypeTable path="packages/scoring/src/meta.ts" name="ScoringVariable" />

### ScoringBand [#scoringband]

<AutoTypeTable path="packages/scoring/src/meta.ts" name="ScoringBand" />

### ScoringFormula [#scoringformula]

<AutoTypeTable path="packages/scoring/src/meta.ts" name="ScoringFormula" />

### ScoringFieldMeta [#scoringfieldmeta]

<AutoTypeTable path="packages/scoring/src/meta.ts" name="ScoringFieldMeta" />

### ScoringAdd [#scoringadd]

Likert options use `{ points }` on `option.meta.scoring` instead. Never mix `points` and `add`.

<AutoTypeTable path="packages/scoring/src/meta.ts" name="ScoringAdd" />

## Read results [#read-results]

```ts
import { scoreResponse } from "@dimah-form/scoring/client";

const preview = scoreResponse(session.snapshot, session.answers);
const stored = await form.api.getResponseScores({ query: { responseId } });
```

Compute with the session snapshot or stored `response.definition`, never a
later live form.

```ts
import type { ScoreResult } from "@dimah-form/scoring";
```

<AutoTypeTable path="packages/scoring/src/score.ts" name="ScoreResult" />

Missing items default to `"incomplete"`. Set a variable's `missing` policy to
`"zero"` or `"omit"` when the instrument requires it. Bands are labels;
formulas support sums of distinct variables only.

`onScore` runs after submit persistence. It may project scores to your own
database, but should not mutate `response.answers`.

## Next [#next]

<Cards>
  <Card title="Dataset" href="/docs/plugins/dataset" description="Export snapshot-correct responses and score values." />

  <Card title="Insights" href="/docs/plugins/insights" description="Aggregate score bands across responses." />
</Cards>
