import type { FdiToothId } from '@/types/treatment'; import type { VoiceApplySelection, VoiceExtractionResult } from '@/types/voice'; /** Which rows the review sheet renders at all — a row with nothing extracted is noise. */ export function voiceRowAvailability(result: VoiceExtractionResult) { return { treatmentType: result.treatmentType != null, teeth: result.teeth.length > 0, comment: Boolean(result.comment?.trim()), prosthesis: result.prosthesis != null, lab: result.labId != null, dueDate: result.dueDate != null, }; } /** * Which rows start ticked. * * Everything available ticks itself, with two deliberate exceptions: * * - **lab, when the name only approximately matched.** Shipping a case to a lab is the one * extracted value whose error leaves the building, so it always requires a deliberate tick. * - **prosthesis, when the map is incomplete.** A prosthesis detail with an untyped tooth * cannot ship at all, so applying it would just move the failure to dispatch. */ export function initialVoiceSelection(result: VoiceExtractionResult): VoiceApplySelection { const available = voiceRowAvailability(result); return { treatmentType: available.treatmentType, teeth: available.teeth, comment: available.comment, prosthesis: available.prosthesis && result.prosthesis?.complete === true, lab: available.lab && result.labMatchExact, dueDate: available.dueDate, }; } /** How many rows will actually be applied — drives the confirm button's label. */ export function countSelected(selection: VoiceApplySelection): number { return Object.values(selection).filter(Boolean).length; } /** Teeth that are part of a bridge, for the read-only chart's connection marks. */ export function connectedTeethFromResult(result: VoiceExtractionResult): Set { const connected = new Set(); for (const group of result.toothSelectionGroups) { if (group.kind !== 'connected') continue; for (const tooth of group.teeth) connected.add(tooth); } return connected; } /** * Whether the sheet has anything worth showing. * * A recording that produced nothing usable should say so plainly rather than present an * empty form of checkboxes. */ export function hasAnythingToApply(result: VoiceExtractionResult): boolean { return Object.values(voiceRowAvailability(result)).some(Boolean); }