diff --git a/app/home/(user)/(dashboard)/analysis-results/test/test-responses.ts b/app/home/(user)/(dashboard)/analysis-results/test/test-responses.ts index 52ea5b8..bbf2346 100644 --- a/app/home/(user)/(dashboard)/analysis-results/test/test-responses.ts +++ b/app/home/(user)/(dashboard)/analysis-results/test/test-responses.ts @@ -1,6 +1,6 @@ import type { AnalysisResultDetailsMapped } from '@/packages/features/user-analyses/src/types/analysis-results'; -export type AnalysisTestResponse = Omit< +type AnalysisTestResponse = Omit< AnalysisResultDetailsMapped, 'order' | 'orderedAnalysisElementIds' | 'summary' | 'elements' >; diff --git a/app/home/(user)/_components/recommendations.tsx b/app/home/(user)/_components/recommendations.tsx index 02fa895..86efd1e 100644 --- a/app/home/(user)/_components/recommendations.tsx +++ b/app/home/(user)/_components/recommendations.tsx @@ -4,10 +4,11 @@ import React from 'react'; import { AccountWithParams } from '@/packages/features/accounts/src/types/accounts'; -import { analysisResponses } from '../(dashboard)/analysis-results/test/test-responses'; +import { Trans } from '@kit/ui/makerkit/trans'; + import { loadAnalyses } from '../_lib/server/load-analyses'; import { loadRecommendations } from '../_lib/server/load-recommendations'; -import OrderAnalysesCards, { OrderAnalysisCard } from './order-analyses-cards'; +import OrderAnalysesCards from './order-analyses-cards'; export default async function Recommendations({ account, @@ -15,25 +16,21 @@ export default async function Recommendations({ account: AccountWithParams; }) { const { analyses, countryCode } = await loadAnalyses(); - const analysisResults = analysisResponses; - - const analysisRecommendations = await loadRecommendations( - analysisResults, - analyses, - account, - ); + const analysisRecommendations = await loadRecommendations(analyses, account); const orderAnalyses = analyses.filter((analysis) => analysisRecommendations.includes(analysis.title), ); - console.log('analysisRecommendations', analysisRecommendations); + if (orderAnalyses.length < 1) { return null; } return (
-

Medreport soovitab teile

+

+ +

); diff --git a/app/home/(user)/_lib/server/load-recommendations.ts b/app/home/(user)/_lib/server/load-recommendations.ts index 573ec34..76e1426 100644 --- a/app/home/(user)/_lib/server/load-recommendations.ts +++ b/app/home/(user)/_lib/server/load-recommendations.ts @@ -1,29 +1,26 @@ import { cache } from 'react'; import { AccountWithParams } from '@/packages/features/accounts/src/types/accounts'; +import { createUserAnalysesApi } from '@/packages/features/user-analyses/src/server/api'; import { getSupabaseServerClient } from '@/packages/supabase/src/clients/server-client'; -import { createServerClient } from '@supabase/ssr'; +import { Database } from '@/packages/supabase/src/database.types'; import OpenAI from 'openai'; import PersonalCode from '~/lib/utils'; -import { AnalysisTestResponse } from '../../(dashboard)/analysis-results/test/test-responses'; import { OrderAnalysisCard } from '../../_components/order-analyses-cards'; export const loadRecommendations = cache(recommendationsLoader); -type FormattedAnalysisResponse = { - value: string; - name: string; - responseTime: string; -}; +type AnalysisResponses = + Database['medreport']['Functions']['get_latest_analysis_response_elements_for_current_user']['Returns']; -const getLatestResponseTime = (items: FormattedAnalysisResponse[]) => { +const getLatestResponseTime = (items: AnalysisResponses) => { if (!items?.length) return null; let latest = null; for (const it of items) { - const d = new Date(it.responseTime); + const d = new Date(it.response_time); const t = d.getTime(); if (!Number.isNaN(t) && (latest === null || t > latest.getTime())) { latest = d; @@ -32,45 +29,10 @@ const getLatestResponseTime = (items: FormattedAnalysisResponse[]) => { return latest; }; -const getLatestUniqueAnalysResponses = ( - analysisResponses: AnalysisTestResponse[], -): { name: string; value: string; responseTime: string }[] => { - const analysisElements = analysisResponses - .map(({ orderedAnalysisElements }) => orderedAnalysisElements) - .flat(); - - console.log('analysisElements', analysisElements.length); - - const byName = analysisElements.reduce< - Record - >((acc, it) => { - const responseTime = it?.results?.responseTime; - const responseValue = it?.results?.responseValue; - if (!responseTime || !responseValue) return acc; - - const key = it.analysisName; - const cur = acc[key]; - const t = Date.parse(responseTime); - const prevT = cur ? Date.parse(cur.responseTime) : -Infinity; - - if (!cur || t > prevT) { - acc[key] = { - name: key, - value: responseValue.toString(), - responseTime, - }; - } - return acc; - }, {}); - - return Object.values(byName); -}; - async function recommendationsLoader( - analysisResponses: AnalysisTestResponse[], analyses: OrderAnalysisCard[], account: AccountWithParams | null, -): Promise { +): Promise { if (!process.env.OPENAI_API_KEY) { return []; } @@ -78,15 +40,14 @@ async function recommendationsLoader( return []; } const supabaseClient = getSupabaseServerClient(); - + const userAnalysesApi = createUserAnalysesApi(supabaseClient); + const analysisResponses = await userAnalysesApi.getAllUserAnalysisResponses(); const analysesRecommendationsPromptId = 'pmpt_68ca9c8bfa8c8193b27eadc6496c36440df449ece4f5a8dd'; - const latestUniqueAnalysResponses = - getLatestUniqueAnalysResponses(analysisResponses); - const latestResponseTime = getLatestResponseTime(latestUniqueAnalysResponses); + const latestResponseTime = getLatestResponseTime(analysisResponses); const latestISO = latestResponseTime ? new Date(latestResponseTime).toISOString() - : 'none'; + : new Date('2025').toISOString(); const previouslyRecommended = await supabaseClient .schema('medreport') @@ -97,16 +58,28 @@ async function recommendationsLoader( .eq('latest_data_change', latestISO); if (previouslyRecommended.data?.[0]?.response) { - return previouslyRecommended.data[0].response; + return JSON.parse(previouslyRecommended.data[0].response as string) + .recommended; } const openAIClient = new OpenAI(); const { gender, age } = PersonalCode.parsePersonalCode(account.personal_code); const weight = account.accountParams?.weight || 'unknown'; - console.log('analysisResponses', analysisResponses); - console.log('analyises', analyses); - const formattedAnalysisResponses = latestUniqueAnalysResponses.map( - ({ name, value }) => ({ name, value }), + + const formattedAnalysisResponses = analysisResponses.map( + ({ + analysis_name_lab, + response_value, + norm_upper, + norm_lower, + norm_status, + }) => ({ + name: analysis_name_lab, + value: response_value, + normUpper: norm_upper, + normLower: norm_lower, + normStatus: norm_status, + }), ); const formattedAnalyses = analyses.map(({ description, title }) => ({ description, @@ -128,23 +101,28 @@ async function recommendationsLoader( }); const json = JSON.parse(response.output_text); - const updateAiResponse = await supabaseClient - .schema('medreport') - .from('ai_responses') - .insert({ - account_id: account.id, - prompt_name: 'Analysis Recommendations', - prompt_id: analysesRecommendationsPromptId, - input: JSON.stringify({ - analyses: formattedAnalyses, - results: formattedAnalysisResponses, - gender, - age, - weight, - }), - latest_data_change: latestISO, - response: response.output_text, - }); + + try { + await supabaseClient + .schema('medreport') + .from('ai_responses') + .insert({ + account_id: account.id, + prompt_name: 'Analysis Recommendations', + prompt_id: analysesRecommendationsPromptId, + input: JSON.stringify({ + analyses: formattedAnalyses, + results: formattedAnalysisResponses, + gender, + age, + weight, + }), + latest_data_change: latestISO, + response: response.output_text, + }); + } catch (error) { + console.error('Error saving AI response: ', error); + } return json.recommended; } diff --git a/app/home/[account]/page.tsx b/app/home/[account]/page.tsx index de1c9f9..7283798 100644 --- a/app/home/[account]/page.tsx +++ b/app/home/[account]/page.tsx @@ -4,7 +4,6 @@ import { use } from 'react'; import { CompanyGuard } from '@/packages/features/team-accounts/src/components'; import { createTeamAccountsApi } from '@/packages/features/team-accounts/src/server/api'; -import { createUserAnalysesApi } from '@/packages/features/user-analyses/src/server/api'; import { getSupabaseServerClient } from '@/packages/supabase/src/clients/server-client'; import { PageBody } from '@kit/ui/page'; diff --git a/packages/features/user-analyses/src/server/api.ts b/packages/features/user-analyses/src/server/api.ts index 5693e32..0532886 100644 --- a/packages/features/user-analyses/src/server/api.ts +++ b/packages/features/user-analyses/src/server/api.ts @@ -425,6 +425,31 @@ class UserAnalysesApi { } return data; } + + async getAllUserAnalysisResponses(): Promise< + Database['medreport']['Functions']['get_latest_analysis_response_elements_for_current_user']['Returns'] + > { + const { + data: { user }, + } = await this.client.auth.getUser(); + + if (!user) { + return []; + } + + const { data, error } = await this.client + .schema('medreport') + .rpc('get_latest_analysis_response_elements_for_current_user', { + p_user_id: user.id, + }); + + if (error) { + console.error('Error fetching user analysis responses: ', error); + throw error; + } + + return data; + } } export function createUserAnalysesApi(client: SupabaseClient) { diff --git a/packages/supabase/src/database.types.ts b/packages/supabase/src/database.types.ts index 5c40f6b..30db3f3 100644 --- a/packages/supabase/src/database.types.ts +++ b/packages/supabase/src/database.types.ts @@ -764,8 +764,8 @@ export type Database = { norm_upper?: number | null norm_upper_included?: boolean | null original_response_element: Json - response_time: string | null - response_value: number | null + response_time?: string | null + response_value?: number | null response_value_is_negative?: boolean | null response_value_is_within_norm?: boolean | null status?: string | null @@ -1335,6 +1335,7 @@ export type Database = { medipost_private_message_id: string | null medusa_order_id: string | null response_xml: string | null + updated_at: string | null xml: string | null } Insert: { @@ -1347,6 +1348,7 @@ export type Database = { medipost_private_message_id?: string | null medusa_order_id?: string | null response_xml?: string | null + updated_at?: string | null xml?: string | null } Update: { @@ -1359,6 +1361,7 @@ export type Database = { medipost_private_message_id?: string | null medusa_order_id?: string | null response_xml?: string | null + updated_at?: string | null xml?: string | null } Relationships: [] @@ -2030,6 +2033,18 @@ export type Database = { personal_code: string }[] } + get_latest_analysis_response_elements_for_current_user: { + Args: { p_user_id: string } + Returns: { + analysis_name: string + analysis_name_lab: string + norm_lower: number + norm_status: number + norm_upper: number + response_time: string + response_value: number + }[] + } get_latest_medipost_dispatch_state_for_order: { Args: { medusa_order_id: string } Returns: { diff --git a/public/locales/en/dashboard.json b/public/locales/en/dashboard.json index 5598aba..c92f438 100644 --- a/public/locales/en/dashboard.json +++ b/public/locales/en/dashboard.json @@ -18,5 +18,8 @@ "title": "Order analysis", "description": "Select an analysis to get started" } + }, + "recommendations": { + "title": "Medreport recommends" } } diff --git a/public/locales/et/dashboard.json b/public/locales/et/dashboard.json index 916038d..d18c230 100644 --- a/public/locales/et/dashboard.json +++ b/public/locales/et/dashboard.json @@ -18,5 +18,8 @@ "title": "Telli analüüs", "description": "Telli endale sobiv analüüs" } + }, + "recommendations": { + "title": "Medreport soovitab teile" } } diff --git a/public/locales/ru/dashboard.json b/public/locales/ru/dashboard.json index 1e5685b..9c0a7d6 100644 --- a/public/locales/ru/dashboard.json +++ b/public/locales/ru/dashboard.json @@ -18,5 +18,8 @@ "title": "Заказать анализ", "description": "Закажите подходящий для вас анализ" } + }, + "recommendations": { + "title": "Medreport recommends" } } diff --git a/supabase/migrations/20250920184500_update_ai_responses.sql b/supabase/migrations/20250920184500_update_ai_responses.sql index d43d30b..ce0d5b8 100644 --- a/supabase/migrations/20250920184500_update_ai_responses.sql +++ b/supabase/migrations/20250920184500_update_ai_responses.sql @@ -15,3 +15,45 @@ USING prompt_name::text; ALTER TABLE medreport.ai_responses ADD CONSTRAINT ai_responses_id_pkey PRIMARY KEY (id); + +create or replace function medreport.get_latest_analysis_response_elements_for_current_user(p_user_id uuid) +returns table ( + analysis_name medreport.analysis_response_elements.analysis_name%type, + response_time medreport.analysis_response_elements.response_time%type, + norm_upper medreport.analysis_response_elements.norm_upper%type, + norm_lower medreport.analysis_response_elements.norm_lower%type, + norm_status medreport.analysis_response_elements.norm_status%type, + response_value medreport.analysis_response_elements.response_value%type, + analysis_name_lab medreport.analysis_elements.analysis_name_lab%type +) +language sql +as $$ + WITH ranked AS ( + SELECT + are.analysis_name, + are.response_time, + are.norm_upper, + are.norm_lower, + are.norm_status, + are.response_value, + ae.analysis_name_lab, + ROW_NUMBER() OVER ( + PARTITION BY are.analysis_name + ORDER BY are.response_time DESC, are.id DESC + ) AS rn + FROM medreport.analysis_responses ar + JOIN medreport.analysis_response_elements are + ON are.analysis_response_id = ar.id + JOIN medreport.analysis_elements ae + ON are.analysis_element_original_id = ae.analysis_id_original + WHERE ar.user_id = '9ec20b5a-a939-4e5d-9148-6733e36047f3' -- 👈 your user id + AND ar.order_status = 'COMPLETED' + ) + SELECT analysis_name, response_time, norm_upper, norm_lower, norm_status, response_value, analysis_name_lab + FROM ranked + WHERE rn = 1 + ORDER BY analysis_name; +$$; + +grant execute on function medreport.get_latest_analysis_response_elements_for_current_user(uuid) to authenticated, service_role; +