# Insights (https://form.dimah.dev/docs/plugins/insights)



`@dimah-form/insights` computes read-side summaries and crosstabs from
response snapshots. It adds no tables and has no metadata namespace.

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

```ts title="lib/form.ts"
import { insightsPlugin } from "@dimah-form/insights";

export const form = dimahForm({
  database,
  forms,
  plugins: [insightsPlugin({ maxRows: 10_000 })],
});
```

For a browser client, install `insightsClientPlugin()` from
`@dimah-form/insights/client`. Server plugins are not inferred by
`createFormClient`. Authorize `getFormInsights` and `getFormCrosstab` as you
would `listResponses`.

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

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

```ts
import type { InsightsPluginOptions } from "@dimah-form/insights";
```

<AutoTypeTable path="packages/insights/src/plugin.ts" name="InsightsPluginOptions" />

<Callout>
  Insights is compute-on-read, not a warehouse. For large datasets, project
  records with Dataset and aggregate in your own database.
</Callout>

## Summary [#summary]

```ts
const summary = await form.api.getFormInsights({
  query: {
    formId: "gad7",
    status: "submitted",
    bucket: "day",
    timeZone: "Asia/Tehran",
  },
});
```

The result is an `InsightsSummary`:

```ts
import type { InsightsCrosstab, InsightsSummary } from "@dimah-form/insights";
```

<AutoTypeTable path="packages/insights/src/spec.ts" name="InsightsSummary" />

Use the normal response filters (`respondentId`, `submittedFrom`,
`submittedTo`, and `updatedAfter`) to constrain the fold. There is no default
status filter.

Field summaries include visibility-correct completion, categorical counts,
finite-number statistics, date ranges, and scoring bands when the snapshot
contains scoring metadata.

## Segment and cap a query [#segment-and-cap-a-query]

Use `whereField` and `whereValue` together to fold one segment. Values are
matched against the stored snapshot answer; numeric values use their string
form.

```ts
const summary = await form.api.getFormInsights({
  query: {
    formId: "gad7",
    status: "submitted",
    whereField: "city",
    whereValue: "tehran",
    maxRows: 5_000,
  },
});
```

`maxRows` may lower the plugin cap, not raise it. Results expose `scanned` and
`truncated`; surface `truncated` in administrative UI instead of presenting a
partial fold as complete.

## Crosstabs [#crosstabs]

```ts
const table = await form.api.getFormCrosstab({
  query: {
    formId: "gad7",
    row: "city",
    col: "remote",
    status: "submitted",
  },
});
```

Each axis uses categorical values from the snapshots that contain it. A live
form edit does not erase a historical category or invalidate an older response.

<AutoTypeTable path="packages/insights/src/spec.ts" name="InsightsCrosstab" />

Walks are capped at 10,000 rows by default. When `truncated` is true, display
that limit in your application rather than presenting a partial result as a
complete report.

## Out of scope [#out-of-scope]

Charts, weighted samples, saved summaries, LLM analysis, attachment processing,
and warehouse storage live in your application. The optional UI package renders
a respondent fill session; it is not an admin dashboard.

## Next [#next]

<Cards>
  <Card title="Dataset" href="/docs/plugins/dataset" description="Project records to a warehouse-friendly interchange format." />

  <Card title="Scoring" href="/docs/plugins/scoring" description="Configure the scores shown by this plugin." />
</Cards>
