import { SupabaseClient } from '@supabase/supabase-js'; import { Database } from '@kit/supabase/database'; import type { UuringElement, UuringuVastus } from '@kit/shared/types/medipost-analysis'; import type { AnalysisResultDetails, AnalysisResultDetailsMapped, UserAnalysis } from '../types/analysis-results'; import type { AnalysisOrder } from '../types/analysis-orders'; /** * Class representing an API for interacting with user accounts. * @constructor * @param {SupabaseClient} client - The Supabase client instance. */ class UserAnalysesApi { constructor(private readonly client: SupabaseClient) { } async getAnalysisOrder({ medusaOrderId, analysisOrderId, }: { medusaOrderId?: string; analysisOrderId?: number; }) { const query = this.client .schema('medreport') .from('analysis_orders') .select('*') if (medusaOrderId) { query.eq('medusa_order_id', medusaOrderId); } else if (analysisOrderId) { query.eq('id', analysisOrderId); } else { throw new Error('Either medusaOrderId or orderId must be provided'); } const { data: order, error } = await query.single(); if (error) { throw new Error(`Failed to get order by medusaOrderId=${medusaOrderId} or analysisOrderId=${analysisOrderId}, message=${error.message}, data=${JSON.stringify(order)}`); } return order as AnalysisOrder; } async getUserAnalysis( analysisOrderId: number, ): Promise { const analysisOrder = await this.getAnalysisOrder({ analysisOrderId }); const orderedAnalysisElementIds = analysisOrder.analysis_element_ids ?? []; if (orderedAnalysisElementIds.length === 0) { console.error('No ordered analysis element ids found for analysis order id=', analysisOrderId); return null; } const orderedAnalysisElements = await this.getOrderedAnalysisElements({ analysisOrderId, orderedAnalysisElementIds }); const orderedAnalysisElementOriginalIds = orderedAnalysisElements.map(({ analysis_id_original }) => analysis_id_original); if (orderedAnalysisElementOriginalIds.length === 0) { console.error('No ordered analysis element original ids found for analysis order id=', analysisOrderId); return null; } const responseWithElements = await this.getAnalysisResponseWithElements({ analysisOrderId }); if (!responseWithElements) { return null; } const mappedOrderedAnalysisElements = await this.getMappedOrderedAnalysisElements({ analysisResponseElements: responseWithElements.elements, orderedAnalysisElements, }); const feedback = responseWithElements.summary?.doctor_analysis_feedback?.[0]; return { id: analysisOrderId, order: { status: analysisOrder.status, medusaOrderId: analysisOrder.medusa_order_id, createdAt: new Date(analysisOrder.created_at), }, orderedAnalysisElementIds, orderedAnalysisElements: mappedOrderedAnalysisElements, summary: feedback?.status === 'COMPLETED' ? (responseWithElements.summary?.doctor_analysis_feedback?.[0] ?? null) : null, }; } async getAnalysisResponseWithElements({ analysisOrderId, }: { analysisOrderId: number; }) { const { data, error: userError } = await this.client.auth.getUser(); if (userError) { throw userError; } const { user } = data; const { data: analysisResponse } = await this.client .schema('medreport') .from('analysis_responses') .select( `*, elements:analysis_response_elements(analysis_name,norm_status,response_value,unit,norm_lower_included,norm_upper_included,norm_lower,norm_upper,response_time,status,analysis_element_original_id,original_response_element,response_value_is_negative,response_value_is_within_norm), summary:analysis_order_id(doctor_analysis_feedback(*))`, ) .eq('user_id', user.id) .eq('analysis_order_id', analysisOrderId) .throwOnError(); return analysisResponse?.[0] as AnalysisResultDetails | null; } async getOrderedAnalysisElements({ analysisOrderId, orderedAnalysisElementIds, }: { analysisOrderId: number; orderedAnalysisElementIds: number[]; }) { const { data: orderedAnalysisElements, error: orderedAnalysisElementsError } = await this.client .schema('medreport') .from('analysis_elements') .select('analysis_id_original,analysis_name_lab') .in('id', orderedAnalysisElementIds); if (orderedAnalysisElementsError) { console.error(`Failed to get ordered analysis elements for analysis order id=${analysisOrderId}`, orderedAnalysisElementsError); throw orderedAnalysisElementsError; } return orderedAnalysisElements; } async getMappedOrderedAnalysisElements({ analysisResponseElements, orderedAnalysisElements, }: { analysisResponseElements: AnalysisResultDetails['elements']; orderedAnalysisElements: { analysis_id_original: string; analysis_name_lab: string }[]; }) { const mappedOrderedAnalysisElements = orderedAnalysisElements.map(({ analysis_id_original, analysis_name_lab }) => { return this.getOrderedAnalysisElement({ analysisIdOriginal: analysis_id_original, analysisNameLab: analysis_name_lab, analysisResponseElements, }); }).sort((a, b) => a.analysisName.localeCompare(b.analysisName)); const nestedAnalysisElementIds = mappedOrderedAnalysisElements .map(({ results }) => results?.nestedElements.map(({ analysisElementOriginalId }) => analysisElementOriginalId)) .flat().filter(Boolean); if (nestedAnalysisElementIds.length > 0) { const { data: nestedAnalysisElements, error: nestedAnalysisElementsError } = await this.client .schema('medreport') .from('analysis_elements') .select('*') .in('id', nestedAnalysisElementIds); if (!nestedAnalysisElementsError && nestedAnalysisElements) { for (const mappedOrderedAnalysisElement of mappedOrderedAnalysisElements) { const { results } = mappedOrderedAnalysisElement; if (!results) { continue; } for (const nestedElement of results.nestedElements) { const { analysisElementOriginalId } = nestedElement; const nestedAnalysisElement = nestedAnalysisElements.find(({ id }) => id === analysisElementOriginalId); if (!nestedAnalysisElement) { continue; } results.nestedElements.push({ ...nestedAnalysisElement, analysisElementOriginalId, analysisName: nestedAnalysisElement.analysis_name_lab, }); } } mappedOrderedAnalysisElements.forEach(({ results }) => { results?.nestedElements.forEach(({ analysisElementOriginalId }) => { const nestedAnalysisElement = nestedAnalysisElements.find(({ id }) => id === analysisElementOriginalId); if (nestedAnalysisElement) { results?.nestedElements.push({ ...nestedAnalysisElement, analysisElementOriginalId, analysisName: nestedAnalysisElement.analysis_name_lab, }); } else { console.error('Nested analysis element not found for analysis element original id=', analysisElementOriginalId); } }); }); } } return mappedOrderedAnalysisElements; } getOrderedAnalysisElement({ analysisIdOriginal, analysisNameLab, analysisResponseElements, }: { analysisIdOriginal: string; analysisNameLab: string; analysisResponseElements: AnalysisResultDetails['elements']; }) { const elementResponse = analysisResponseElements.find((element) => element.analysis_element_original_id === analysisIdOriginal); if (!elementResponse) { return { analysisIdOriginal, isWaitingForResults: true, analysisName: analysisNameLab, }; } const labComment = elementResponse.original_response_element?.UuringuKommentaar; return { analysisIdOriginal, isWaitingForResults: false, analysisName: analysisNameLab, results: { nestedElements: (() => { const nestedElements = elementResponse.original_response_element?.UuringuElement as UuringElement[] | undefined; if (!nestedElements) { return []; } return nestedElements.map((element) => { const elementVastus = element.UuringuVastus as UuringuVastus | undefined; const responseValue = elementVastus?.VastuseVaartus; const responseValueIsNumeric = !isNaN(Number(responseValue)); const responseValueIsNegative = responseValue === 'Negatiivne'; const responseValueIsWithinNorm = responseValue === 'Normi piires'; return { status: element.UuringOlek, unit: element.Mootyhik, normLower: elementVastus?.NormAlum?.['#text'], normUpper: elementVastus?.NormYlem?.['#text'], normStatus: elementVastus?.NormiStaatus, responseTime: elementVastus?.VastuseAeg, response_value: responseValueIsNegative || !responseValueIsNumeric ? null : (responseValue ?? null), response_value_is_negative: responseValueIsNumeric ? null : responseValueIsNegative, response_value_is_within_norm: responseValueIsNumeric ? null : responseValueIsWithinNorm, normLowerIncluded: elementVastus?.NormAlum?.['@_kaasaarvatud'] === 'JAH', normUpperIncluded: elementVastus?.NormYlem?.['@_kaasaarvatud'] === 'JAH', analysisElementOriginalId: element.UuringId, }; }); })(), labComment, //originalResponseElement: elementResponse.original_response_element ?? null, unit: elementResponse.unit, normLower: elementResponse.norm_lower, normUpper: elementResponse.norm_upper, normStatus: elementResponse.norm_status, responseTime: elementResponse.response_time, responseValue: elementResponse.response_value, responseValueIsNegative: elementResponse.response_value_is_negative === true, responseValueIsWithinNorm: elementResponse.response_value_is_within_norm === true, normLowerIncluded: elementResponse.norm_lower_included, normUpperIncluded: elementResponse.norm_upper_included, status: elementResponse.status, analysisElementOriginalId: elementResponse.analysis_element_original_id, } }; } // @TODO unused currently async getUserAnalyses(): Promise { const authUser = await this.client.auth.getUser(); const { data, error: userError } = authUser; if (userError) { console.error('Failed to get user', userError); throw userError; } const { user } = data; const { data: analysisResponses } = await this.client .schema('medreport') .from('analysis_responses') .select('*') .eq('user_id', user.id); if (!analysisResponses) { return null; } const analysisResponseIds = analysisResponses.map((r) => r.id); const { data: analysisResponseElements } = await this.client .schema('medreport') .from('analysis_response_elements') .select('*') .in('analysis_response_id', analysisResponseIds); if (!analysisResponseElements) { return null; } return analysisResponses.map((r) => ({ ...r, elements: analysisResponseElements.filter( (e) => e.analysis_response_id === r.id, ), })); } async hasAccountTeamMembership(accountId?: string) { if (!accountId) { return false; } const { count, error } = await this.client .schema('medreport') .from('accounts_memberships') .select('account_id', { count: 'exact', head: true }) .eq('account_id', accountId); if (error) { throw error; } return (count ?? 0) > 0; } async fetchBmiThresholds() { // Fetch BMI const { data, error } = await this.client .schema('medreport') .from('bmi_thresholds') .select( 'age_min,age_max,underweight_max,normal_min,normal_max,overweight_min,strong_min,obesity_min', ) .order('age_min', { ascending: true }); if (error) { console.error('Error fetching BMI thresholds:', error); throw error; } return data; } } export function createUserAnalysesApi(client: SupabaseClient) { return new UserAnalysesApi(client); }