62 lines
2.3 KiB
TypeScript
62 lines
2.3 KiB
TypeScript
|
|
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<FdiToothId> {
|
||
|
|
const connected = new Set<FdiToothId>();
|
||
|
|
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);
|
||
|
|
}
|