dimah-formv0.6.0

Insights

Read-side counts from response snapshots — status, completion, field stats, score bands, optional day series and crosstab.

@dimah-form/insights is the first-party summary plugin for admin reporting. It walks stored responses (same 100-row pages as list, capped at 10_000 by default) and folds snapshot answers into:

  • totals by draft / submitted / abandoned
  • visible-required completion among submitted rows (completion.rate is complete / submitted, or null)
  • per-field stats: n is visible occurrences; hidden is skip-logic; unanswered is visible and empty
  • value counts + pct (0–1) for categorical fields (select / multiSelect / boolean, or any field with options). Levels follow the document catalog, including unused n: 0, in document order — not by frequency
  • numeric min / max / mean / stdev for any finite number answer (number, or a custom type such as rating); dates min / max for date
  • scoring band / mean / stdev from @dimah-form/scoring (a direct dependency). Bands follow meta.scoring order, including unused n: 0
  • optional bucket=day series of submittedAt (timeZone, default UTC)
  • GET /insights/crosstab for two categorical fields. n is respondents who contributed a cell, not multiSelect tokens

Derived data only. No tables, no meta namespace, no live-form flatten. The live questionnaire only supplies field order and catalog levels that snapshots did not already list. Text / email fields are answered / unanswered only.

This is not a warehouse. For large N persist rows with datasetPlugin({ onProject }) and aggregate in your database. When the walk hits the cap, truncated is true — lower maxRows on the query, or raise the plugin cap and project instead.


Install

pnpm add @dimah-form/insights
lib/form.ts
import { insightsPlugin } from "@dimah-form/insights";
import { dimahForm } from "@dimah-form/server";

export const form = dimahForm({
  database,
  forms,
  plugins: [insightsPlugin()],
});
lib/client.ts
"use client";

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,
});

Guard getFormInsights and getFormCrosstab like listResponses (admin).

There is no default status filter (unlike dataset, which defaults to submitted). Pass status, respondentId, submittedFrom / submittedTo, and updatedAfter to match listResponses. Segment in the fold with whereField + whereValue (unknown field → empty counts, not 404). A finite number matches the decimal string of the stored value (5 matches "5"). multiSelect % is of answered respondents (can sum above 1). Categorical % and score bands include every catalog level, so a scale stays in document order when a level has n: 0.

const summary = await form.api.getFormInsights({
  query: { formId: "gad7", status: "submitted" },
});

const byDay = await form.api.getFormInsights({
  query: { formId: "gad7", status: "submitted", bucket: "day" },
});

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

summary.byStatus is the census. completion uses visible required fields on each row’s snapshot. fields follow the live questionnaire order; ids that exist only on older snapshots come after. scores is omitted when the snapshot has no meta.scoring, or when that snapshot’s scoring meta fails validation. A crosstab axis counts only on snapshots where that field is categorical. Changing the live field to text does not drop older select rows, and it does not reject the query. An id that is only on older snapshots still folds. table.n is the respondent base for the cells.

bucket=day groups submittedAt in an IANA timeZone (query overrides the plugin option; default UTC). The series includes that timeZone.

GET /insights/summary and GET /insights/crosstab. Filename, charts, and zip stay in the app. Optional UI belongs in the consumer — @dimah-form/ui is the fill renderer, not an admin dashboard.


Out of this package

Weighted samples, LLM summaries, persisting the summary, and charts. Export files are @dimah-form/dataset.

On this page