I Used Claude to Fill Inspection Forms by Voice, Without Building a Voice Stack
I just added voice form fill to GroundCheck, and the most useful decision was the boring one: I did not build a voice assistant.
The feature is scoped very tightly. An inspector is already inside one inspection form. They tap Fill with Voice, dictate something like:
all four guys had hard hats and vests on, scaffold tags were current, one trip hazard by the east gate, no incidents
The app sends that transcript plus the visible form schema to a Supabase Edge Function. Claude maps the narration onto the matching fields. The app shows a review sheet. The inspector accepts or rejects each proposed value.
That is it. No chat UI. No cross-report memory. No “AI assistant for the whole day.” Just spoken notes → structured form fields.
The client-side rule is deliberately small:
const FILLABLE_TYPES = new Set(["text", "checkbox", "multiple_choice", "rating"]);
export function fillableQuestions(section, responses) {
return section.questions.filter(
(q) =>
FILLABLE_TYPES.has(q.type) &&
(!q.show_when || evaluateCondition(q.show_when, responses))
);
}
Photos and signatures are excluded because narration cannot produce them. Conditional questions hidden by show_when are excluded too. That matters on a whole-form fill: if the user says the vest check passed, Claude should not even see the “why did hi-vis fail?” follow-up field.
The Edge Function builds a JSON schema dynamically from the fields in scope. Choice fields become enums. Rating fields become enums from the score map. Unknown fields and illegal values are dropped again server-side after the model returns, because this eventually lands on a safety-compliance record and “the schema probably handled it” is not good enough.
I also kept the API key entirely server-side. The mobile app never sees the Anthropic key. The function checks auth, org tier, and a per-org monthly cap before making the paid call, then logs usage rows with input/output token counts.
The nice lazy bit: speech capture uses the iOS keyboard dictation button in an autofocused multiline text field. No speech-recognition dependency, no native module, no new permission strings, no rebuild just to test the first version. If field testing proves people need a big in-app hold-to-talk button, the transcript box can be swapped later. The contract downstream is still just text.
The review step is non-negotiable. Claude proposes; the human applies. Already-answered fields show as changes instead of silent overwrites. Identical values are dropped. Everything is displayed in form order so it feels like reviewing the inspection, not debugging JSON.
The part I like most is that the whole feature is smaller because it refused to be a general assistant. It only knows about the form on screen, only sees fields that can currently be answered, and only writes after a human says yes.
That shape is usually where LLM features get useful: narrow input, constrained output, boring gates, human review at the boundary.
Top comments (0)