Files
dyolink/backend/src/modules/cases/lab-case-task.util.ts

96 lines
2.9 KiB
TypeScript
Raw Normal View History

import { Prisma } from '@prisma/client';
/** Normalize the JSON `teeth` column of a lab case task into a clean string[]. */
export function normalizeTaskTeeth(value: Prisma.JsonValue | null | undefined): string[] {
if (!Array.isArray(value)) {
return [];
}
return value
.filter((v): v is string | number => typeof v === 'string' || typeof v === 'number')
.map((v) => String(v));
}
export function sortProsthesisTeeth(teeth: readonly string[]): string[] {
return [...teeth].sort((a, b) => {
const rank = (tooth: string) => (tooth === 'UA' ? 0 : tooth === 'LA' ? 1 : 2);
const diff = rank(a) - rank(b);
if (diff !== 0) return diff;
return a.localeCompare(b, undefined, { numeric: true });
});
}
/** Group ids that span more than one tooth (bridges). */
export function connectedSelectionGroupIds(
rows: Array<{
selectionGroupId?: string | null;
tooth?: string;
teeth?: Prisma.JsonValue;
}>,
): Set<string> {
const byGroup = new Map<string, Set<string>>();
for (const row of rows) {
const id = row.selectionGroupId?.trim();
if (!id) continue;
const teeth =
row.tooth != null && row.tooth !== ''
? [row.tooth]
: normalizeTaskTeeth(row.teeth);
const set = byGroup.get(id) ?? new Set<string>();
for (const tooth of teeth) set.add(tooth);
byGroup.set(id, set);
}
const connected = new Set<string>();
for (const [id, teeth] of byGroup) {
if (teeth.size > 1) connected.add(id);
}
return connected;
}
export function aggregateProsthesisGroupsFromTasks(
tasks: Array<{
prosthesisTypeCode: string;
teeth: Prisma.JsonValue;
selectionGroupId?: string | null;
}>,
): Array<{
prosthesisTypeCode: string;
teeth: string[];
connected?: boolean;
selectionGroupId?: string;
}> {
const connectedIds = connectedSelectionGroupIds(tasks);
const byKey = new Map<
string,
{ prosthesisTypeCode: string; teeth: string[]; selectionGroupId: string }
>();
for (const task of tasks) {
if (!task.prosthesisTypeCode) continue;
const selectionGroupId = task.selectionGroupId?.trim() ?? '';
const teeth = normalizeTaskTeeth(task.teeth);
const key = `${teeth.join(',')}::${task.prosthesisTypeCode}`;
const entry = byKey.get(key) ?? {
prosthesisTypeCode: task.prosthesisTypeCode,
teeth: [],
selectionGroupId,
};
entry.teeth.push(...teeth);
if (!entry.selectionGroupId && selectionGroupId) {
entry.selectionGroupId = selectionGroupId;
}
byKey.set(key, entry);
}
return [...byKey.values()]
.map((group) => {
const teeth = sortProsthesisTeeth([...new Set(group.teeth)]);
return {
prosthesisTypeCode: group.prosthesisTypeCode,
teeth,
selectionGroupId: group.selectionGroupId || undefined,
connected: Boolean(group.selectionGroupId) && connectedIds.has(group.selectionGroupId),
};
})
.sort((a, b) => a.prosthesisTypeCode.localeCompare(b.prosthesisTypeCode));
}