diff --git a/src/lib/ai/prompts/bmi.ts b/src/lib/ai/prompts/bmi.ts index 330bad4dd..c00450088 100644 --- a/src/lib/ai/prompts/bmi.ts +++ b/src/lib/ai/prompts/bmi.ts @@ -40,15 +40,20 @@ export function getBmiUserPrompt( snapshotJson: string, todayKey: string, locale: Locale, + previousContextBlock?: string, ): string { + const ctxBlock = + previousContextBlock && previousContextBlock.trim().length > 0 + ? `\n\n${previousContextBlock}\n` + : ""; if (locale === "en") { return `Date: ${todayKey} (Europe/Berlin) -Analyse the BMI trajectory taking age, sex and weight trend into account. Place the WHO classification. +Analyse the BMI trajectory taking age, sex and weight trend into account. Place the WHO classification.${ctxBlock} ${snapshotJson}`; } return `Datum: ${todayKey} (Europe/Berlin) -Analysiere den BMI-Verlauf unter Berücksichtigung von Alter, Geschlecht und Gewichtstrend. Ordne die Klassifikation nach WHO ein. +Analysiere den BMI-Verlauf unter Berücksichtigung von Alter, Geschlecht und Gewichtstrend. Ordne die Klassifikation nach WHO ein.${ctxBlock} ${snapshotJson}`; } diff --git a/src/lib/ai/prompts/medication-compliance.ts b/src/lib/ai/prompts/medication-compliance.ts index 13375577e..31cb65e69 100644 --- a/src/lib/ai/prompts/medication-compliance.ts +++ b/src/lib/ai/prompts/medication-compliance.ts @@ -52,17 +52,22 @@ export function getMedicationComplianceUserPrompt( snapshotJson: string, todayKey: string, locale: Locale, + previousContextBlock?: string, ): string { + const ctxBlock = + previousContextBlock && previousContextBlock.trim().length > 0 + ? `\n\n${previousContextBlock}\n` + : ""; if (locale === "en") { return `Date: ${todayKey} (Europe/Berlin) Analyse medication adherence with focus on patterns, effectiveness correlation and concrete suggestions for improvement. -Use the correlation data and historical comparison to back up the link between adherence and vital signs. +Use the correlation data and historical comparison to back up the link between adherence and vital signs.${ctxBlock} ${snapshotJson}`; } return `Datum: ${todayKey} (Europe/Berlin) Analysiere die Medikamenten-Einnahmetreue mit Fokus auf Muster, Wirksamkeitskorrelation und konkrete Verbesserungsvorschläge. -Nutze die Korrelationsdaten und den historischen Vergleich um den Zusammenhang zwischen Adhärenz und Vitalwerten zu belegen. +Nutze die Korrelationsdaten und den historischen Vergleich um den Zusammenhang zwischen Adhärenz und Vitalwerten zu belegen.${ctxBlock} ${snapshotJson}`; } diff --git a/src/lib/ai/prompts/mood.ts b/src/lib/ai/prompts/mood.ts index 591ca4615..7b8b1f31f 100644 --- a/src/lib/ai/prompts/mood.ts +++ b/src/lib/ai/prompts/mood.ts @@ -30,15 +30,20 @@ export function getMoodUserPrompt( snapshotJson: string, todayKey: string, locale: Locale, + previousContextBlock?: string, ): string { + const ctxBlock = + previousContextBlock && previousContextBlock.trim().length > 0 + ? `\n\n${previousContextBlock}\n` + : ""; if (locale === "en") { return `Date: ${todayKey} (Europe/Berlin) -Analyse the mood data with focus on trend, stability and links to other health metrics. +Analyse the mood data with focus on trend, stability and links to other health metrics.${ctxBlock} ${snapshotJson}`; } return `Datum: ${todayKey} (Europe/Berlin) -Analysiere die Stimmungsdaten mit Fokus auf Trend, Stabilitat und Zusammenhange mit anderen Gesundheitsmetriken. +Analysiere die Stimmungsdaten mit Fokus auf Trend, Stabilitat und Zusammenhange mit anderen Gesundheitsmetriken.${ctxBlock} ${snapshotJson}`; } diff --git a/src/lib/ai/prompts/pulse.ts b/src/lib/ai/prompts/pulse.ts index e77b6fcfa..52453813d 100644 --- a/src/lib/ai/prompts/pulse.ts +++ b/src/lib/ai/prompts/pulse.ts @@ -58,17 +58,22 @@ export function getPulseUserPrompt( snapshotJson: string, todayKey: string, locale: Locale, + previousContextBlock?: string, ): string { + const ctxBlock = + previousContextBlock && previousContextBlock.trim().length > 0 + ? `\n\n${previousContextBlock}\n` + : ""; if (locale === "en") { return `Date: ${todayKey} (Europe/Berlin) Analyse the pulse / heart-rate data with focus on resting-pulse trend, variability and links to medication and mood. -Use the precomputed correlations and historical comparison for a sound analysis. +Use the precomputed correlations and historical comparison for a sound analysis.${ctxBlock} ${snapshotJson}`; } return `Datum: ${todayKey} (Europe/Berlin) Analysiere die Puls-/Herzfrequenzdaten mit Fokus auf Ruhepulstrend, Variabilität und Zusammenhänge mit Medikation und Stimmung. -Nutze die vorberechneten Korrelationen und den historischen Vergleich für eine fundierte Analyse. +Nutze die vorberechneten Korrelationen und den historischen Vergleich für eine fundierte Analyse.${ctxBlock} ${snapshotJson}`; } diff --git a/src/lib/ai/prompts/weight.ts b/src/lib/ai/prompts/weight.ts index 29968cf59..6d9581714 100644 --- a/src/lib/ai/prompts/weight.ts +++ b/src/lib/ai/prompts/weight.ts @@ -52,17 +52,22 @@ export function getWeightUserPrompt( snapshotJson: string, todayKey: string, locale: Locale, + previousContextBlock?: string, ): string { + const ctxBlock = + previousContextBlock && previousContextBlock.trim().length > 0 + ? `\n\n${previousContextBlock}\n` + : ""; if (locale === "en") { return `Date: ${todayKey} (Europe/Berlin) Analyse the weight trajectory with focus on trends, BMI classification and links to other vital signs. -Use the temporal layers (avg7, avg30, avg90, allTime) and the historical comparison for a nuanced assessment. +Use the temporal layers (avg7, avg30, avg90, allTime) and the historical comparison for a nuanced assessment.${ctxBlock} ${snapshotJson}`; } return `Datum: ${todayKey} (Europe/Berlin) Analysiere die Gewichtsentwicklung mit Fokus auf Trends, BMI-Klassifikation und Zusammenhang mit anderen Vitalwerten. -Nutze die temporalen Schichten (avg7, avg30, avg90, allTime) und den historischen Vergleich für eine differenzierte Bewertung. +Nutze die temporalen Schichten (avg7, avg30, avg90, allTime) und den historischen Vergleich für eine differenzierte Bewertung.${ctxBlock} ${snapshotJson}`; } diff --git a/src/lib/insights/bmi-status.ts b/src/lib/insights/bmi-status.ts index 1828e0344..5cf41d168 100644 --- a/src/lib/insights/bmi-status.ts +++ b/src/lib/insights/bmi-status.ts @@ -3,6 +3,10 @@ import { resolveProvider } from "@/lib/ai/provider"; import { getBmiSystemPrompt, getBmiUserPrompt } from "@/lib/ai/prompts/bmi"; import { classifyBMI } from "@/lib/analytics/classifications"; import { getNoKeyBmiStatusText } from "@/lib/insights/no-key-fallbacks"; +import { + formatPreviousContextForPrompt, + getPreviousInsightContext, +} from "@/lib/insights/memory"; const BMI_STATUS_POINTS = 30; @@ -238,9 +242,25 @@ export async function generateBmiStatusForUser( const snapshotJson = JSON.stringify(snapshot, null, 2); + const previousContext = await getPreviousInsightContext( + userId, + "bmi-status", + locale, + 12, + ); + const previousContextBlock = formatPreviousContextForPrompt( + previousContext, + locale, + ); + const result = await provider.generateCompletion({ systemPrompt: getBmiSystemPrompt(locale), - userPrompt: getBmiUserPrompt(snapshotJson, todayKey, locale), + userPrompt: getBmiUserPrompt( + snapshotJson, + todayKey, + locale, + previousContextBlock, + ), temperature: 0.3, maxTokens: 1000, }); diff --git a/src/lib/insights/medication-compliance-status.ts b/src/lib/insights/medication-compliance-status.ts index dd17bbd29..4dea86da3 100644 --- a/src/lib/insights/medication-compliance-status.ts +++ b/src/lib/insights/medication-compliance-status.ts @@ -1,10 +1,17 @@ import { prisma } from "@/lib/db"; import { resolveProvider } from "@/lib/ai/provider"; -import { getMedicationComplianceSystemPrompt, getMedicationComplianceUserPrompt } from "@/lib/ai/prompts/medication-compliance"; +import { + getMedicationComplianceSystemPrompt, + getMedicationComplianceUserPrompt, +} from "@/lib/ai/prompts/medication-compliance"; import { calculateCompliance } from "@/lib/analytics/compliance"; import { getMedicationCategories } from "@/lib/medication-category"; import { sanitizeForPrompt } from "@/lib/insights/sanitize"; import { getNoKeyMedicationComplianceStatusText } from "@/lib/insights/no-key-fallbacks"; +import { + formatPreviousContextForPrompt, + getPreviousInsightContext, +} from "@/lib/insights/memory"; const MEDICATION_COMPLIANCE_STATUS_POINTS = 30; @@ -291,9 +298,25 @@ export async function generateMedicationComplianceStatusForUser( const snapshotJson = JSON.stringify(snapshot, null, 2); + const previousContext = await getPreviousInsightContext( + userId, + "medication-compliance-status", + locale, + 12, + ); + const previousContextBlock = formatPreviousContextForPrompt( + previousContext, + locale, + ); + const result = await provider.generateCompletion({ systemPrompt: getMedicationComplianceSystemPrompt(locale), - userPrompt: getMedicationComplianceUserPrompt(snapshotJson, todayKey, locale), + userPrompt: getMedicationComplianceUserPrompt( + snapshotJson, + todayKey, + locale, + previousContextBlock, + ), temperature: 0.3, maxTokens: 1000, }); diff --git a/src/lib/insights/mood-status.ts b/src/lib/insights/mood-status.ts index 71a90950c..1b9a9e320 100644 --- a/src/lib/insights/mood-status.ts +++ b/src/lib/insights/mood-status.ts @@ -2,6 +2,10 @@ import { prisma } from "@/lib/db"; import { resolveProvider } from "@/lib/ai/provider"; import { getMoodSystemPrompt, getMoodUserPrompt } from "@/lib/ai/prompts/mood"; import { getNoKeyMoodStatusText } from "@/lib/insights/no-key-fallbacks"; +import { + formatPreviousContextForPrompt, + getPreviousInsightContext, +} from "@/lib/insights/memory"; import { pearsonCorrelation, type PairedPoint, @@ -212,8 +216,7 @@ export async function generateMoodStatusForUser( ? null : round( (moodSeries.filter( - (entry) => - entry.value >= greenMin && entry.value <= greenMax, + (entry) => entry.value >= greenMin && entry.value <= greenMax, ).length / moodSeries.length) * 100, @@ -226,9 +229,7 @@ export async function generateMoodStatusForUser( const oldestEntry = entries.length > 0 ? entries[0].moodLoggedAt : null; const newestEntry = - entries.length > 0 - ? entries[entries.length - 1].moodLoggedAt - : null; + entries.length > 0 ? entries[entries.length - 1].moodLoggedAt : null; const totalSpanDays = oldestEntry && newestEntry ? Math.round( @@ -237,9 +238,7 @@ export async function generateMoodStatusForUser( ) : 0; const newestEntryDaysAgo = newestEntry - ? Math.round( - (Date.now() - newestEntry.getTime()) / (24 * 60 * 60 * 1000), - ) + ? Math.round((Date.now() - newestEntry.getTime()) / (24 * 60 * 60 * 1000)) : null; // Fetch cross-metric context for enrichment @@ -335,7 +334,9 @@ export async function generateMoodStatusForUser( tags: tagSummary.length > 0 ? tagSummary : null, }, crossMetricContext: - weightSeries.length >= 3 || sysSeries.length >= 3 || pulseSeries.length >= 3 + weightSeries.length >= 3 || + sysSeries.length >= 3 || + pulseSeries.length >= 3 ? { weight: weightSeries.length >= 3 @@ -364,9 +365,25 @@ export async function generateMoodStatusForUser( const snapshotJson = JSON.stringify(snapshot, null, 2); + const previousContext = await getPreviousInsightContext( + userId, + "mood-status", + locale, + 12, + ); + const previousContextBlock = formatPreviousContextForPrompt( + previousContext, + locale, + ); + const result = await provider.generateCompletion({ systemPrompt: getMoodSystemPrompt(locale), - userPrompt: getMoodUserPrompt(snapshotJson, todayKey, locale), + userPrompt: getMoodUserPrompt( + snapshotJson, + todayKey, + locale, + previousContextBlock, + ), temperature: 0.3, maxTokens: 1000, }); diff --git a/src/lib/insights/pulse-status.ts b/src/lib/insights/pulse-status.ts index 982cf5c3d..eb4663e33 100644 --- a/src/lib/insights/pulse-status.ts +++ b/src/lib/insights/pulse-status.ts @@ -1,6 +1,13 @@ import { prisma } from "@/lib/db"; import { resolveProvider } from "@/lib/ai/provider"; -import { getPulseSystemPrompt, getPulseUserPrompt } from "@/lib/ai/prompts/pulse"; +import { + getPulseSystemPrompt, + getPulseUserPrompt, +} from "@/lib/ai/prompts/pulse"; +import { + formatPreviousContextForPrompt, + getPreviousInsightContext, +} from "@/lib/insights/memory"; import { getAgeFromDateOfBirth, getPersonalizedPulseTarget, @@ -286,9 +293,25 @@ export async function generatePulseStatusForUser( const snapshotJson = JSON.stringify(snapshot, null, 2); + const previousContext = await getPreviousInsightContext( + userId, + "pulse-status", + locale, + 12, + ); + const previousContextBlock = formatPreviousContextForPrompt( + previousContext, + locale, + ); + const result = await provider.generateCompletion({ systemPrompt: getPulseSystemPrompt(locale), - userPrompt: getPulseUserPrompt(snapshotJson, todayKey, locale), + userPrompt: getPulseUserPrompt( + snapshotJson, + todayKey, + locale, + previousContextBlock, + ), temperature: 0.3, maxTokens: 1000, }); diff --git a/src/lib/insights/weight-status.ts b/src/lib/insights/weight-status.ts index f70da2817..ceee5457d 100644 --- a/src/lib/insights/weight-status.ts +++ b/src/lib/insights/weight-status.ts @@ -1,11 +1,18 @@ import { prisma } from "@/lib/db"; import { resolveProvider } from "@/lib/ai/provider"; -import { getWeightSystemPrompt, getWeightUserPrompt } from "@/lib/ai/prompts/weight"; +import { + getWeightSystemPrompt, + getWeightUserPrompt, +} from "@/lib/ai/prompts/weight"; import { pearsonCorrelation, type PairedPoint, } from "@/lib/analytics/correlations"; import { getNoKeyWeightStatusText } from "@/lib/insights/no-key-fallbacks"; +import { + formatPreviousContextForPrompt, + getPreviousInsightContext, +} from "@/lib/insights/memory"; const WEIGHT_STATUS_POINTS = 30; @@ -341,7 +348,10 @@ export async function generateWeightStatusForUser( latest: dailyMoodSeries.at(-1)?.value ?? null, series: dailyMoodSeries.slice(-10), moodVsWeightCorrelation: (() => { - const moodVsWeightPairs = pairDailySeries(dailyMoodSeries, weightSeries); + const moodVsWeightPairs = pairDailySeries( + dailyMoodSeries, + weightSeries, + ); return pearsonCorrelation(moodVsWeightPairs); })(), } @@ -350,9 +360,25 @@ export async function generateWeightStatusForUser( const snapshotJson = JSON.stringify(snapshot, null, 2); + const previousContext = await getPreviousInsightContext( + userId, + "weight-status", + locale, + 12, + ); + const previousContextBlock = formatPreviousContextForPrompt( + previousContext, + locale, + ); + const result = await provider.generateCompletion({ systemPrompt: getWeightSystemPrompt(locale), - userPrompt: getWeightUserPrompt(snapshotJson, todayKey, locale), + userPrompt: getWeightUserPrompt( + snapshotJson, + todayKey, + locale, + previousContextBlock, + ), temperature: 0.3, maxTokens: 1000, });