Add user analysis view bars + nested element logic to doctor view also
This commit is contained in:
@@ -52,6 +52,23 @@ export const AnalysisResponsesSchema = z.object({
|
||||
});
|
||||
export type AnalysisResponses = z.infer<typeof AnalysisResponsesSchema>;
|
||||
|
||||
// Nested element schema (used recursively)
|
||||
export const NestedAnalysisElementSchema = z.object({
|
||||
analysisElementOriginalId: z.string(),
|
||||
analysisName: z.string().optional().nullable(),
|
||||
unit: z.string().nullable(),
|
||||
normLower: z.number().nullable(),
|
||||
normUpper: z.number().nullable(),
|
||||
normStatus: z.number().nullable(),
|
||||
responseTime: z.string().nullable(),
|
||||
responseValue: z.number().nullable(),
|
||||
normLowerIncluded: z.boolean(),
|
||||
normUpperIncluded: z.boolean(),
|
||||
status: z.number(),
|
||||
analysisNameLab: z.string().optional().nullable(),
|
||||
});
|
||||
export type NestedAnalysisElement = z.infer<typeof NestedAnalysisElementSchema>;
|
||||
|
||||
export const AnalysisResponseSchema = z.object({
|
||||
id: z.number(),
|
||||
analysis_response_id: z.number(),
|
||||
@@ -69,6 +86,7 @@ export const AnalysisResponseSchema = z.object({
|
||||
analysis_name: z.string().nullable(),
|
||||
analysis_responses: AnalysisResponsesSchema,
|
||||
comment: z.string().nullable(),
|
||||
nestedElements: z.array(NestedAnalysisElementSchema).optional(),
|
||||
latestPreviousAnalysis: z
|
||||
.object({
|
||||
id: z.number(),
|
||||
@@ -86,6 +104,7 @@ export const AnalysisResponseSchema = z.object({
|
||||
updated_at: z.string().nullable(),
|
||||
analysis_name: z.string().nullable(),
|
||||
comment: z.string().nullable(),
|
||||
nestedElements: z.array(NestedAnalysisElementSchema).optional(),
|
||||
})
|
||||
.optional()
|
||||
.nullable(),
|
||||
|
||||
@@ -5,12 +5,16 @@ import { isBefore } from 'date-fns';
|
||||
|
||||
import { renderDoctorSummaryReceivedEmail } from '@kit/email-templates';
|
||||
import { getLogger } from '@kit/shared/logger';
|
||||
import { getFullName } from '@kit/shared/utils';
|
||||
import type { UuringuVastus, ResponseUuring } from '@kit/shared/types/medipost-analysis';
|
||||
import { getFullName, toArray } from '@kit/shared/utils';
|
||||
import { getSupabaseServerClient } from '@kit/supabase/server-client';
|
||||
import { createUserAnalysesApi } from '@kit/user-analyses/api';
|
||||
|
||||
import { sendEmailFromTemplate } from '../../../../../../../lib/services/mailer.service';
|
||||
import { AnalysisResultDetails } from '../schema/doctor-analysis-detail-view.schema';
|
||||
import {
|
||||
AnalysisResultDetails,
|
||||
NestedAnalysisElement,
|
||||
} from '../schema/doctor-analysis-detail-view.schema';
|
||||
import {
|
||||
AnalysisResponseBase,
|
||||
DoctorAnalysisFeedbackTable,
|
||||
@@ -20,6 +24,63 @@ import {
|
||||
} from '../schema/doctor-analysis.schema';
|
||||
import { ErrorReason } from '../schema/error.type';
|
||||
|
||||
function mapUuringVastus({ uuringVastus }: { uuringVastus?: UuringuVastus }) {
|
||||
const vastuseVaartus = uuringVastus?.VastuseVaartus;
|
||||
const responseValue = (() => {
|
||||
const valueAsNumber = Number(vastuseVaartus);
|
||||
if (isNaN(valueAsNumber)) {
|
||||
return null;
|
||||
}
|
||||
return valueAsNumber;
|
||||
})();
|
||||
const responseValueNumber = Number(responseValue);
|
||||
const responseValueIsNumeric = !isNaN(responseValueNumber);
|
||||
return {
|
||||
normLower: uuringVastus?.NormAlum?.['#text'] ?? null,
|
||||
normUpper: uuringVastus?.NormYlem?.['#text'] ?? null,
|
||||
normStatus: (uuringVastus?.NormiStaatus ?? null) as number | null,
|
||||
responseTime: uuringVastus?.VastuseAeg ?? null,
|
||||
responseValue:
|
||||
responseValueIsNumeric ? (responseValueNumber ?? null) : null,
|
||||
normLowerIncluded:
|
||||
uuringVastus?.NormAlum?.['@_kaasaarvatud']?.toLowerCase() === 'jah',
|
||||
normUpperIncluded:
|
||||
uuringVastus?.NormYlem?.['@_kaasaarvatud']?.toLowerCase() === 'jah',
|
||||
};
|
||||
}
|
||||
|
||||
function parseNestedElements(
|
||||
originalResponseElement: ResponseUuring | null | undefined,
|
||||
status: number,
|
||||
): NestedAnalysisElement[] {
|
||||
if (!originalResponseElement?.UuringuElement) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const nestedElements = toArray(originalResponseElement.UuringuElement);
|
||||
|
||||
return nestedElements.map<NestedAnalysisElement>((element) => {
|
||||
const mappedResponse = mapUuringVastus({
|
||||
uuringVastus: element.UuringuVastus as UuringuVastus | undefined,
|
||||
});
|
||||
|
||||
return {
|
||||
analysisElementOriginalId: element.UuringId,
|
||||
analysisName: undefined, // Will be populated later from analysis_elements table
|
||||
unit: element.Mootyhik ?? null,
|
||||
normLower: mappedResponse.normLower,
|
||||
normUpper: mappedResponse.normUpper,
|
||||
normStatus: mappedResponse.normStatus,
|
||||
responseTime: mappedResponse.responseTime,
|
||||
responseValue: mappedResponse.responseValue,
|
||||
normLowerIncluded: mappedResponse.normLowerIncluded,
|
||||
normUpperIncluded: mappedResponse.normUpperIncluded,
|
||||
status,
|
||||
analysisNameLab: element.UuringNimi,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async function enrichAnalysisData(analysisResponses?: AnalysisResponseBase[]) {
|
||||
const supabase = getSupabaseServerClient();
|
||||
|
||||
@@ -388,7 +449,8 @@ export async function getAnalysisResultsForDoctor(
|
||||
.from(`analysis_response_elements`)
|
||||
.select(
|
||||
`*,
|
||||
analysis_responses(user_id, analysis_order:analysis_order_id(id,medusa_order_id, analysis_element_ids))`,
|
||||
analysis_responses(user_id, analysis_order:analysis_order_id(id,medusa_order_id, analysis_element_ids)),
|
||||
original_response_element`,
|
||||
)
|
||||
.eq('analysis_response_id', analysisResponseId);
|
||||
|
||||
@@ -452,7 +514,8 @@ export async function getAnalysisResultsForDoctor(
|
||||
*,
|
||||
analysis_responses!inner(
|
||||
user_id
|
||||
)
|
||||
),
|
||||
original_response_element
|
||||
`,
|
||||
)
|
||||
.in(
|
||||
@@ -491,8 +554,14 @@ export async function getAnalysisResultsForDoctor(
|
||||
preferred_locale,
|
||||
} = accountWithParams[0];
|
||||
|
||||
// Parse nested elements for current and previous analyses
|
||||
const analysisResponseElementsWithPreviousData = [];
|
||||
for (const analysisResponseElement of analysisResponsesData) {
|
||||
const nestedElements = parseNestedElements(
|
||||
analysisResponseElement.original_response_element as ResponseUuring,
|
||||
Number(analysisResponseElement.status),
|
||||
);
|
||||
|
||||
const latestPreviousAnalysis = previousAnalyses.find(
|
||||
({ analysis_element_original_id, response_time }) => {
|
||||
if (response_time && analysisResponseElement.response_time) {
|
||||
@@ -507,12 +576,95 @@ export async function getAnalysisResultsForDoctor(
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Parse nested elements for previous analysis if it exists
|
||||
const latestPreviousAnalysisWithNested = latestPreviousAnalysis
|
||||
? {
|
||||
...latestPreviousAnalysis,
|
||||
nestedElements: parseNestedElements(
|
||||
latestPreviousAnalysis.original_response_element as ResponseUuring,
|
||||
Number(latestPreviousAnalysis.status),
|
||||
),
|
||||
}
|
||||
: undefined;
|
||||
|
||||
analysisResponseElementsWithPreviousData.push({
|
||||
...analysisResponseElement,
|
||||
latestPreviousAnalysis,
|
||||
nestedElements,
|
||||
latestPreviousAnalysis: latestPreviousAnalysisWithNested,
|
||||
});
|
||||
}
|
||||
|
||||
// Collect all nested element IDs to fetch their names
|
||||
const nestedElementIds = analysisResponseElementsWithPreviousData
|
||||
.flatMap((element) => [
|
||||
...(element.nestedElements?.map((ne) => ne.analysisElementOriginalId) ??
|
||||
[]),
|
||||
...(element.latestPreviousAnalysis?.nestedElements?.map(
|
||||
(ne) => ne.analysisElementOriginalId,
|
||||
) ?? []),
|
||||
])
|
||||
.filter(Boolean);
|
||||
|
||||
// Fetch analysis names for nested elements
|
||||
if (nestedElementIds.length > 0) {
|
||||
const { data: nestedAnalysisElements, error: nestedAnalysisElementsError } =
|
||||
await supabase
|
||||
.schema('medreport')
|
||||
.from('analysis_elements')
|
||||
.select('*')
|
||||
.in('analysis_id_original', nestedElementIds);
|
||||
|
||||
if (!nestedAnalysisElementsError && nestedAnalysisElements) {
|
||||
// Populate analysis names for current nested elements
|
||||
for (const element of analysisResponseElementsWithPreviousData) {
|
||||
if (element.nestedElements) {
|
||||
for (const nestedElement of element.nestedElements) {
|
||||
const analysisElement = nestedAnalysisElements.find(
|
||||
(ae) =>
|
||||
ae.analysis_id_original ===
|
||||
nestedElement.analysisElementOriginalId,
|
||||
);
|
||||
if (analysisElement) {
|
||||
nestedElement.analysisName =
|
||||
analysisElement.analysis_name_lab as string | undefined;
|
||||
}
|
||||
}
|
||||
// Sort nested elements by name
|
||||
element.nestedElements.sort(
|
||||
(a, b) => a.analysisName?.localeCompare(b.analysisName ?? '') ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
// Populate analysis names for previous nested elements
|
||||
if (element.latestPreviousAnalysis?.nestedElements) {
|
||||
for (const nestedElement of element.latestPreviousAnalysis
|
||||
.nestedElements) {
|
||||
const analysisElement = nestedAnalysisElements.find(
|
||||
(ae) =>
|
||||
ae.analysis_id_original ===
|
||||
nestedElement.analysisElementOriginalId,
|
||||
);
|
||||
if (analysisElement) {
|
||||
nestedElement.analysisName =
|
||||
analysisElement.analysis_name_lab as string | undefined;
|
||||
}
|
||||
}
|
||||
// Sort nested elements by name
|
||||
element.latestPreviousAnalysis.nestedElements.sort(
|
||||
(a, b) => a.analysisName?.localeCompare(b.analysisName ?? '') ?? 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.error(
|
||||
'Failed to get nested analysis elements by ids=',
|
||||
nestedElementIds,
|
||||
nestedAnalysisElementsError,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
analysisResponse: analysisResponseElementsWithPreviousData,
|
||||
order: {
|
||||
|
||||
Reference in New Issue
Block a user