diff --git a/docs/api/openapi.yaml b/docs/api/openapi.yaml index f5cc0e154..14d3a2fe2 100644 --- a/docs/api/openapi.yaml +++ b/docs/api/openapi.yaml @@ -20434,6 +20434,10 @@ components: type: integer minimum: -9007199254740991 maximum: 9007199254740991 + gapDriverType: + anyOf: + - type: string + - type: "null" required: - windowDays - episodeCount @@ -20442,10 +20446,12 @@ components: - gapSampleSize - byMonth - byType + - gapDriverType additionalProperties: false description: "Cross-episode retrospective summary over the trailing window: episode + resolved counts, the typical - (median) recovery gap (null below the min-sample floor — withholds a thin claim), a recurrence-by-month tally, - and a per-type breakdown. Retrospective only." + (median) recovery gap (null below the min-sample floor — withholds a thin claim), a recurrence-by-month tally, a + per-type breakdown, and the dominant driving vital (`gapDriverType`, null when no gap is surfaced). + Retrospective only." DisclaimerAckEnvelope: type: object properties: diff --git a/messages/de.json b/messages/de.json index 6674ca67f..c15520593 100644 --- a/messages/de.json +++ b/messages/de.json @@ -6880,6 +6880,7 @@ "countOne": "Du hast in diesem Zeitraum 1 Episode erfasst.", "countOther": "Du hast in diesem Zeitraum {count} Episoden erfasst.", "typicalGap": "Typische Erholungslücke: {days} Tage zwischen Besserung und normalisierten Werten.", + "typicalGapWithDriver": "Typische Erholungslücke: {days} Tage – du fühlst dich meist besser, bevor sich {vital} normalisiert.", "typicalGapLearning": "Noch ein paar abgeschlossene Episoden und hier erscheint eine typische Erholungslücke.", "none": "In diesem Zeitraum keine Episoden erfasst.", "analyze": "Erholungslücke analysieren" diff --git a/messages/en.json b/messages/en.json index 2f753c94a..4ad161b92 100644 --- a/messages/en.json +++ b/messages/en.json @@ -6880,6 +6880,7 @@ "countOne": "You logged 1 episode in this period.", "countOther": "You logged {count} episodes in this period.", "typicalGap": "Typical recovery gap: {days} days between feeling better and your numbers settling.", + "typicalGapWithDriver": "Typical recovery gap: {days} days — you tend to feel better before your {vital} settles.", "typicalGapLearning": "A few more resolved episodes and a typical recovery gap will appear here.", "none": "No episodes logged in this period.", "analyze": "Analyse the recovery gap" diff --git a/messages/es.json b/messages/es.json index ff16bdc8f..66d9d2421 100644 --- a/messages/es.json +++ b/messages/es.json @@ -6880,6 +6880,7 @@ "countOne": "Registraste 1 episodio en este período.", "countOther": "Registraste {count} episodios en este período.", "typicalGap": "Brecha de recuperación típica: {days} días entre sentirte mejor y la estabilización de tus valores.", + "typicalGapWithDriver": "Brecha de recuperación típica: {days} días: sueles sentirte mejor antes de que {vital} se estabilice.", "typicalGapLearning": "Con unos pocos episodios resueltos más aparecerá aquí una brecha de recuperación típica.", "none": "Sin episodios registrados en este período.", "analyze": "Analizar la brecha de recuperación" diff --git a/messages/fr.json b/messages/fr.json index a3c2c476c..608aef1a6 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -6880,6 +6880,7 @@ "countOne": "Tu as enregistré 1 épisode sur cette période.", "countOther": "Tu as enregistré {count} épisodes sur cette période.", "typicalGap": "Écart de récupération typique : {days} jours entre le mieux-être et la stabilisation de tes valeurs.", + "typicalGapWithDriver": "Écart de récupération typique : {days} jours – tu te sens généralement mieux avant que {vital} ne se stabilise.", "typicalGapLearning": "Encore quelques épisodes résolus et un écart de récupération typique apparaîtra ici.", "none": "Aucun épisode enregistré sur cette période.", "analyze": "Analyser l'écart de récupération" diff --git a/messages/it.json b/messages/it.json index 3e512e59b..226eaebb0 100644 --- a/messages/it.json +++ b/messages/it.json @@ -6880,6 +6880,7 @@ "countOne": "Hai registrato 1 episodio in questo periodo.", "countOther": "Hai registrato {count} episodi in questo periodo.", "typicalGap": "Divario di recupero tipico: {days} giorni tra il sentirti meglio e la stabilizzazione dei valori.", + "typicalGapWithDriver": "Divario di recupero tipico: {days} giorni: di solito ti senti meglio prima che {vital} si stabilizzi.", "typicalGapLearning": "Ancora qualche episodio risolto e qui apparirà un divario di recupero tipico.", "none": "Nessun episodio registrato in questo periodo.", "analyze": "Analizza il divario di recupero" diff --git a/messages/pl.json b/messages/pl.json index 9709e45af..4853b2972 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -6880,6 +6880,7 @@ "countOne": "W tym okresie zapisałeś 1 epizod.", "countOther": "W tym okresie zapisałeś {count} epizodów.", "typicalGap": "Typowa luka rekonwalescencji: {days} dni między poprawą a ustabilizowaniem wartości.", + "typicalGapWithDriver": "Typowa luka rekonwalescencji: {days} dni – zwykle czujesz się lepiej, zanim {vital} się ustabilizuje.", "typicalGapLearning": "Jeszcze kilka zakończonych epizodów i pojawi się tu typowa luka rekonwalescencji.", "none": "Brak epizodów zapisanych w tym okresie.", "analyze": "Przeanalizuj lukę rekonwalescencji" diff --git a/src/app/api/illness/insights/route.ts b/src/app/api/illness/insights/route.ts index f11ec4bab..0e31fb6d3 100644 --- a/src/app/api/illness/insights/route.ts +++ b/src/app/api/illness/insights/route.ts @@ -85,7 +85,11 @@ export const GET = apiHandler(async (request: NextRequest) => { // out of the typical-gap median by the aggregate's signal-density gates. const gapById = new Map< string, - { recoveryGapDays: number | null; gapMeasurementDays: number } + { + recoveryGapDays: number | null; + gapMeasurementDays: number; + gapReturnTypes: string[]; + } >(); if (includeRecoveryGap) { const gapCandidates = rows @@ -98,7 +102,11 @@ export const GET = apiHandler(async (request: NextRequest) => { async (): Promise< [ string, - { recoveryGapDays: number | null; gapMeasurementDays: number }, + { + recoveryGapDays: number | null; + gapMeasurementDays: number; + gapReturnTypes: string[]; + }, ] > => { const derived = await computeEpisodeCorrelation( @@ -130,6 +138,15 @@ export const GET = apiHandler(async (request: NextRequest) => { derived.status === "ok" ? derived.value.adverseCoverageDays : 0, + // The vitals that produced a real physiological return — + // tallied across the qualifying episodes to name the dominant + // driving vital in the summary copy. + gapReturnTypes: + derived.status === "ok" + ? derived.value.returns + .filter((r) => r.gapDays !== null) + .map((r) => String(r.type)) + : [], }, ]; }, @@ -148,6 +165,7 @@ export const GET = apiHandler(async (request: NextRequest) => { resolved: r.resolvedAt !== null, recoveryGapDays: gap?.recoveryGapDays ?? null, gapMeasurementDays: gap?.gapMeasurementDays ?? 0, + gapReturnTypes: gap?.gapReturnTypes ?? [], lifecycle: r.lifecycle, }; }); @@ -160,6 +178,7 @@ export const GET = apiHandler(async (request: NextRequest) => { episode_count: summary.episodeCount, gap_sample_size: summary.gapSampleSize, typical_gap_days: summary.typicalRecoveryGapDays, + gap_driver_type: summary.gapDriverType, include_recovery_gap: includeRecoveryGap, }, }); diff --git a/src/components/illness/illness-insights-card.tsx b/src/components/illness/illness-insights-card.tsx index 15ea9f02f..86beede7a 100644 --- a/src/components/illness/illness-insights-card.tsx +++ b/src/components/illness/illness-insights-card.tsx @@ -87,13 +87,22 @@ export function IllnessInsightsCard({
{t("illness.insights.typicalGapLearning")}
+ ) : gap.data.typicalRecoveryGapDays === null ? ( ++ {t("illness.insights.typicalGapLearning")} +
+ ) : gap.data.gapDriverType ? ( ++ {t("illness.insights.typicalGapWithDriver", { + days: gap.data.typicalRecoveryGapDays, + vital: t(`illness.vital.${gap.data.gapDriverType}`), + })} +
) : (- {gap.data.typicalRecoveryGapDays !== null - ? t("illness.insights.typicalGap", { - days: gap.data.typicalRecoveryGapDays, - }) - : t("illness.insights.typicalGapLearning")} + {t("illness.insights.typicalGap", { + days: gap.data.typicalRecoveryGapDays, + })}
)} diff --git a/src/components/illness/types.ts b/src/components/illness/types.ts index ec3563e1b..6bda26316 100644 --- a/src/components/illness/types.ts +++ b/src/components/illness/types.ts @@ -149,4 +149,5 @@ export interface IllnessInsightsResponse { gapSampleSize: number; byMonth: Record