diff --git a/messages/de.json b/messages/de.json index 81db8b629..10d2b5101 100644 --- a/messages/de.json +++ b/messages/de.json @@ -3231,11 +3231,11 @@ "last30days": "{days} Tage", "last90days": "{days} Tage", "lastYear": "{months} Monate", - "allTime": "{months} Monate im Detail, alle Laborwerte" + "allTime": "{months} Monate im Detail, Labor länger" }, "detailDays": "Der Coach liest nichts, was älter als {days} Tage ist, in keiner Antwort.", "detailMonths": "Der Coach liest nichts, was älter als {months} Monate ist, in keiner Antwort.", - "detailAll": "Alles, was du erfasst hast: {dailyMonths} Monate tagesgenau, ältere Werte als Monatswerte bis {years} Jahre zurück und alle Laborwerte, ältere pro Jahr zusammengefasst.", + "detailAll": "Ohne Begrenzung liest der Coach {dailyMonths} Monate tagesgenau und ältere Werte als Monatswerte bis {years} Jahre zurück. Von Laborwerten liest er den jüngsten Wert der zuletzt gemessenen Marker und, wenn du nach einem Marker fragst, dessen {labRecent} vorherige Werte; ältere Werte sind pro Jahr zusammengefasst, bis {labYears} Jahre zurück, noch frühere Jahre werden nur gezählt.", "note": "Dein Profil, aktuelle Medikamente, eine laufende Erkrankung und anstehende Termine bleiben sichtbar; Auswertungen, die mehr Verlauf brauchen, werden gekürzt oder weggelassen. Antworten, die der Coach früher in älteren Unterhaltungen gegeben hat, können noch ältere Werte nennen." }, "showConversations": "Unterhaltungen anzeigen", diff --git a/messages/en.json b/messages/en.json index 2820dd01f..567eb2fe3 100644 --- a/messages/en.json +++ b/messages/en.json @@ -3231,11 +3231,11 @@ "last30days": "{days} days", "last90days": "{days} days", "lastYear": "{months} months", - "allTime": "{months} months in detail, all lab results" + "allTime": "{months} months in detail, longer for labs" }, "detailDays": "The Coach reads nothing older than {days} days, in any answer.", "detailMonths": "The Coach reads nothing older than {months} months, in any answer.", - "detailAll": "Everything you recorded: {dailyMonths} months in daily detail, older readings as monthly values up to {years} years back, and every lab result, older ones summarised per year.", + "detailAll": "Without a limit, the Coach reads {dailyMonths} months of readings in daily detail and older readings as monthly values up to {years} years back. For lab results it reads the latest value of the most recently measured markers and, for a marker you ask about, its {labRecent} previous results, with older ones summarised per year up to {labYears} years back; earlier years are only counted.", "note": "Your profile, current medications, an ongoing illness and upcoming visits stay visible; summaries that need more history are shortened or left out. Answers the Coach gave earlier in older conversations may still quote older values." }, "showConversations": "Show conversations", diff --git a/messages/es.json b/messages/es.json index 594405877..ff8fcbfb5 100644 --- a/messages/es.json +++ b/messages/es.json @@ -3231,11 +3231,11 @@ "last30days": "{days} días", "last90days": "{days} días", "lastYear": "{months} meses", - "allTime": "{months} meses en detalle, todos los análisis" + "allTime": "{months} meses en detalle, más para los análisis" }, "detailDays": "El Coach no lee nada con más de {days} días de antigüedad, en ninguna respuesta.", "detailMonths": "El Coach no lee nada con más de {months} meses de antigüedad, en ninguna respuesta.", - "detailAll": "Todo lo que has registrado: {dailyMonths} meses al detalle diario, las mediciones más antiguas como valores mensuales hasta {years} años atrás y todos los análisis, los más antiguos resumidos por año.", + "detailAll": "Sin límite, el Coach lee {dailyMonths} meses de mediciones al detalle diario y las más antiguas como valores mensuales hasta {years} años atrás. De los análisis lee el último valor de los marcadores medidos más recientemente y, de un marcador por el que preguntes, sus {labRecent} resultados anteriores; los más antiguos se resumen por año hasta {labYears} años atrás y los años anteriores solo se cuentan.", "note": "Tu perfil, tu medicación actual, una enfermedad en curso y las próximas citas siguen visibles; los resúmenes que necesitan más historial se acortan o se omiten. Las respuestas que el Coach dio antes en conversaciones anteriores pueden seguir citando valores más antiguos." }, "showConversations": "Mostrar conversaciones", diff --git a/messages/fr.json b/messages/fr.json index a15af2316..ef428a625 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -3231,11 +3231,11 @@ "last30days": "{days} jours", "last90days": "{days} jours", "lastYear": "{months} mois", - "allTime": "{months} mois en détail, toutes les analyses" + "allTime": "{months} mois en détail, plus pour les analyses" }, "detailDays": "Le Coach ne lit rien de plus ancien que {days} jours, dans aucune réponse.", "detailMonths": "Le Coach ne lit rien de plus ancien que {months} mois, dans aucune réponse.", - "detailAll": "Tout ce que vous avez enregistré : {dailyMonths} mois au jour près, les mesures plus anciennes en valeurs mensuelles jusqu’à {years} ans en arrière, et toutes les analyses, les plus anciennes résumées par année.", + "detailAll": "Sans limite, le Coach lit {dailyMonths} mois de mesures au jour près et les mesures plus anciennes en valeurs mensuelles jusqu’à {years} ans en arrière. Pour les analyses, il lit la dernière valeur des marqueurs mesurés le plus récemment et, pour un marqueur sur lequel vous l’interrogez, ses {labRecent} résultats précédents ; les plus anciens sont résumés par année jusqu’à {labYears} ans en arrière, les années antérieures sont seulement comptées.", "note": "Votre profil, vos médicaments actuels, une maladie en cours et vos prochains rendez-vous restent visibles ; les synthèses qui demandent plus d’historique sont raccourcies ou omises. Les réponses données plus tôt par le Coach dans d’anciennes conversations peuvent encore citer des valeurs plus anciennes." }, "showConversations": "Afficher les conversations", diff --git a/messages/it.json b/messages/it.json index 74164001e..f3e5d5620 100644 --- a/messages/it.json +++ b/messages/it.json @@ -3231,11 +3231,11 @@ "last30days": "{days} giorni", "last90days": "{days} giorni", "lastYear": "{months} mesi", - "allTime": "{months} mesi in dettaglio, tutti gli esami" + "allTime": "{months} mesi in dettaglio, di più per gli esami" }, "detailDays": "Il Coach non legge nulla di più vecchio di {days} giorni, in nessuna risposta.", "detailMonths": "Il Coach non legge nulla di più vecchio di {months} mesi, in nessuna risposta.", - "detailAll": "Tutto ciò che hai registrato: {dailyMonths} mesi giorno per giorno, le misurazioni più vecchie come valori mensili fino a {years} anni fa e tutti gli esami, quelli più vecchi riassunti per anno.", + "detailAll": "Senza limite, il Coach legge {dailyMonths} mesi di misurazioni giorno per giorno e quelle più vecchie come valori mensili fino a {years} anni fa. Degli esami legge l’ultimo valore dei marcatori misurati più di recente e, per un marcatore su cui chiedi, i suoi {labRecent} risultati precedenti; quelli più vecchi sono riassunti per anno fino a {labYears} anni fa, gli anni precedenti vengono solo contati.", "note": "Il tuo profilo, i farmaci attuali, una malattia in corso e le prossime visite restano visibili; i riepiloghi che richiedono più storico vengono accorciati o omessi. Le risposte date in precedenza dal Coach nelle conversazioni più vecchie possono ancora citare valori più vecchi." }, "showConversations": "Mostra conversazioni", diff --git a/messages/ko.json b/messages/ko.json index 5462b7e39..a7994951e 100644 --- a/messages/ko.json +++ b/messages/ko.json @@ -3231,11 +3231,11 @@ "last30days": "{days}일", "last90days": "{days}일", "lastYear": "{months}개월", - "allTime": "{months}개월 상세, 모든 검사 결과" + "allTime": "{months}개월 상세, 검사 결과는 더 길게" }, "detailDays": "코치는 어떤 답변에서도 {days}일보다 오래된 기록은 읽지 않아요.", "detailMonths": "코치는 어떤 답변에서도 {months}개월보다 오래된 기록은 읽지 않아요.", - "detailAll": "기록한 모든 것: 최근 {dailyMonths}개월은 하루 단위로, 그 이전 측정값은 최대 {years}년 전까지 월별 값으로, 검사 결과는 전부 읽고 오래된 결과는 연도별로 요약해요.", + "detailAll": "제한이 없으면 코치는 최근 {dailyMonths}개월은 하루 단위로, 그 이전 측정값은 최대 {years}년 전까지 월별 값으로 읽어요. 검사 결과는 가장 최근에 측정한 항목들의 최신 값을 읽고, 물어본 항목은 이전 결과 {labRecent}개도 읽어요. 더 오래된 결과는 최대 {labYears}년 전까지 연도별로 요약하고, 그보다 이전 연도는 개수만 세요.", "note": "프로필, 현재 복용 중인 약, 진행 중인 질병, 예정된 진료는 계속 보여요. 더 긴 기록이 필요한 요약은 줄이거나 빼요. 예전 대화에서 코치가 이미 한 답변에는 더 오래된 값이 남아 있을 수 있어요." }, "showConversations": "대화 보기", diff --git a/messages/nl.json b/messages/nl.json index f5877d23f..5c5998d61 100644 --- a/messages/nl.json +++ b/messages/nl.json @@ -3231,11 +3231,11 @@ "last30days": "{days} dagen", "last90days": "{days} dagen", "lastYear": "{months} maanden", - "allTime": "{months} maanden in detail, alle labuitslagen" + "allTime": "{months} maanden in detail, labuitslagen langer" }, "detailDays": "De Coach leest in geen enkel antwoord iets dat ouder is dan {days} dagen.", "detailMonths": "De Coach leest in geen enkel antwoord iets dat ouder is dan {months} maanden.", - "detailAll": "Alles wat je hebt vastgelegd: {dailyMonths} maanden per dag, oudere metingen als maandwaarden tot {years} jaar terug en alle labuitslagen, oudere per jaar samengevat.", + "detailAll": "Zonder grens leest de Coach {dailyMonths} maanden aan metingen per dag en oudere metingen als maandwaarden tot {years} jaar terug. Van labuitslagen leest hij de laatste waarde van de meest recent gemeten markers en, voor een marker waar je naar vraagt, ook de {labRecent} vorige uitslagen; oudere worden per jaar samengevat tot {labYears} jaar terug, nog oudere jaren alleen geteld.", "note": "Je profiel, je huidige medicatie, een lopende ziekte en komende afspraken blijven zichtbaar; samenvattingen die meer geschiedenis nodig hebben, worden ingekort of weggelaten. Antwoorden die de Coach eerder in oudere gesprekken gaf, kunnen nog oudere waarden noemen." }, "showConversations": "Gesprekken tonen", diff --git a/messages/pl.json b/messages/pl.json index 08a8e9e82..7590291fd 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -3231,11 +3231,11 @@ "last30days": "{days} dni", "last90days": "{days} dni", "lastYear": "{months} miesięcy", - "allTime": "{months} miesięcy szczegółowo, wszystkie wyniki badań" + "allTime": "{months} miesięcy szczegółowo, wyniki badań dłużej" }, "detailDays": "Coach nie czyta niczego starszego niż {days} dni, w żadnej odpowiedzi.", "detailMonths": "Coach nie czyta niczego starszego niż {months} miesięcy, w żadnej odpowiedzi.", - "detailAll": "Wszystko, co zostało zapisane: {dailyMonths} miesięcy z dokładnością do dnia, starsze pomiary jako wartości miesięczne do {years} lat wstecz oraz wszystkie wyniki badań, starsze podsumowane rocznie.", + "detailAll": "Bez limitu Coach czyta {dailyMonths} miesięcy pomiarów z dokładnością do dnia, a starsze pomiary jako wartości miesięczne do {years} lat wstecz. Z wyników badań czyta ostatnią wartość ostatnio mierzonych markerów, a dla markera, o który pytasz, także {labRecent} poprzednich wyników; starsze są podsumowane rocznie do {labYears} lat wstecz, a wcześniejsze lata tylko zliczane.", "note": "Twój profil, obecne leki, trwająca choroba i nadchodzące wizyty pozostają widoczne; podsumowania wymagające dłuższej historii są skracane lub pomijane. Odpowiedzi, których Coach udzielił wcześniej w starszych rozmowach, mogą nadal przytaczać starsze wartości." }, "showConversations": "Pokaż rozmowy", diff --git a/messages/pt-BR.json b/messages/pt-BR.json index f57008858..31a83e353 100644 --- a/messages/pt-BR.json +++ b/messages/pt-BR.json @@ -3231,11 +3231,11 @@ "last30days": "{days} dias", "last90days": "{days} dias", "lastYear": "{months} meses", - "allTime": "{months} meses em detalhe, todos os exames" + "allTime": "{months} meses em detalhe, exames por mais tempo" }, "detailDays": "O Coach não lê nada mais antigo que {days} dias, em nenhuma resposta.", "detailMonths": "O Coach não lê nada mais antigo que {months} meses, em nenhuma resposta.", - "detailAll": "Tudo o que você registrou: {dailyMonths} meses em detalhe diário, leituras mais antigas como valores mensais até {years} anos atrás, e todos os resultados de exames, os mais antigos resumidos por ano.", + "detailAll": "Sem limite, o Coach lê {dailyMonths} meses de medições em detalhe diário e as mais antigas como valores mensais até {years} anos atrás. Dos exames, lê o valor mais recente dos marcadores medidos por último e, para um marcador sobre o qual você perguntar, os {labRecent} resultados anteriores; os mais antigos são resumidos por ano até {labYears} anos atrás, e os anos anteriores são apenas contados.", "note": "Seu perfil, medicamentos atuais, uma doença em curso e consultas futuras permanecem visíveis; resumos que precisam de mais histórico são encurtados ou omitidos. Respostas que o Coach deu antes em conversas mais antigas ainda podem citar valores antigos." }, "showConversations": "Mostrar conversas", diff --git a/src/components/insights/coach-panel/__tests__/sources-rail.test.tsx b/src/components/insights/coach-panel/__tests__/sources-rail.test.tsx index c6d716c45..8a47217db 100644 --- a/src/components/insights/coach-panel/__tests__/sources-rail.test.tsx +++ b/src/components/insights/coach-panel/__tests__/sources-rail.test.tsx @@ -151,7 +151,7 @@ describe("", () => { // `allTime` is the default: the sentence carries the numbers the // readers use (`history-reach.ts`). expect(html).toContain( - "Everything you recorded: 12 months in daily detail, older readings as monthly values up to 10 years back, and every lab result, older ones summarised per year.", + "Without a limit, the Coach reads 12 months of readings in daily detail and older readings as monthly values up to 10 years back. For lab results it reads the latest value of the most recently measured markers and, for a marker you ask about, its 5 previous results, with older ones summarised per year up to 10 years back; earlier years are only counted.", ); expect(html).not.toContain("stay visible"); }); diff --git a/src/lib/ai/coach/__tests__/coach-history-reach.test.ts b/src/lib/ai/coach/__tests__/coach-history-reach.test.ts index e8dd8b35a..5b50cd4ed 100644 --- a/src/lib/ai/coach/__tests__/coach-history-reach.test.ts +++ b/src/lib/ai/coach/__tests__/coach-history-reach.test.ts @@ -21,9 +21,17 @@ vi.mock("@/lib/tz/resolver", () => ({ resolveUserTimezone: () => Promise.resolve("UTC"), })); +import { locales } from "@/lib/i18n/config"; +import { getServerTranslator } from "@/lib/i18n/server-translator"; +import { + LAB_HISTORY_MAX_YEARS, + LAB_HISTORY_RECENT_POINTS, +} from "@/lib/ai/coach/labs-snapshot"; import { COACH_ALL_TIME_TABLE_DAYS, COACH_DAILY_DETAIL_DAYS, + COACH_LAB_HISTORY_YEARS, + COACH_LAB_RECENT_POINTS, COACH_HISTORY_WINDOWS, HISTORY_REACH_DAYS, UNBOUNDED_REACH, @@ -122,13 +130,43 @@ describe("the numbers the settings text states", () => { }); expect(lookbackText("lastYear").detailParams).toEqual({ months: 12 }); expect(lookbackText("allTime")).toEqual({ - // "12 months in detail, every lab result": the option says what it + // "12 months in detail, longer for labs": the option says what it // reads, not "All" (#1263). optionParams: { months: 12 }, detailKey: "detailAll", - detailParams: { dailyMonths: 12, years: 10 }, + detailParams: { dailyMonths: 12, years: 10, labRecent: 5, labYears: 10 }, }); }); + + it("states the lab history the named-marker read actually carries", () => { + expect(COACH_LAB_RECENT_POINTS).toBe(LAB_HISTORY_RECENT_POINTS); + expect(COACH_LAB_HISTORY_YEARS).toBe(LAB_HISTORY_MAX_YEARS); + }); + + it("words the no-limit detail as a complete sentence that claims no more than is read", () => { + const { detailParams } = lookbackText("allTime"); + for (const locale of locales) { + const { t } = getServerTranslator(locale); + const option = t("insights.coach.lookback.option.allTime", { + months: 12, + }); + const detail = t("insights.coach.lookback.detailAll", detailParams); + // Every number reaches the sentence, none stays a raw placeholder. + expect(detail, locale).not.toMatch(/\{\w+\}/); + for (const n of ["12", "10", "5"]) expect(detail, locale).toContain(n); + // Ends as a sentence, not on a dangling clause. + expect(detail.trim(), locale).toMatch(/[.。]$|요\.?$/); + // "every lab result" was a claim the bounded history does not keep. + expect(`${option} ${detail}`, locale).not.toMatch( + /every lab|all lab|alle Laborwerte|toutes les analyses|todos los análisis|tutti gli esami|wszystkie wyniki|모든 검사|alle labuitslagen|todos os exames/i, + ); + } + const de = getServerTranslator("de").t( + "insights.coach.lookback.detailAll", + detailParams, + ); + expect(de).toContain("pro Jahr zusammengefasst"); + }); }); describe("a history beyond the limit", () => { diff --git a/src/lib/ai/coach/history-reach.ts b/src/lib/ai/coach/history-reach.ts index 0bfcb0d0c..13675f869 100644 --- a/src/lib/ai/coach/history-reach.ts +++ b/src/lib/ai/coach/history-reach.ts @@ -163,8 +163,17 @@ export const COACH_DAILY_DETAIL_DAYS = 365; /** How far back an all-time table reaches, as monthly values. */ export const COACH_ALL_TIME_TABLE_DAYS = 3_650; -// Lab results are not held to a window of their own: without a limit the -// Coach reads all of them, summarised per marker (`labs-snapshot.ts`, #1263). +// Lab results are not held to a window of their own (#1263). Without a limit +// the snapshot carries the latest value of the most recently measured +// markers; a read for one named marker adds its previous results in full and +// older ones summarised per year (`labs-snapshot.ts`). The two numbers the +// sentence states are pinned to that module's constants by the test. + +/** Earlier results a named-marker read lists in full (`LAB_HISTORY_RECENT_POINTS`). */ +export const COACH_LAB_RECENT_POINTS = 5; + +/** Years of per-year lab summaries before years are only counted (`LAB_HISTORY_MAX_YEARS`). */ +export const COACH_LAB_HISTORY_YEARS = 10; /** The i18n key and parameters describing one lookback option. */ export function lookbackText(window: CoachDefaultWindow): { @@ -176,13 +185,15 @@ export function lookbackText(window: CoachDefaultWindow): { if (days === null) { const dailyMonths = Math.round(COACH_DAILY_DETAIL_DAYS / 30.4); // The option names what "no limit" actually reads (#1263): the dense - // readings in daily detail for a year, the lab results in full. + // readings in daily detail for a year, the lab results further back. return { optionParams: { months: dailyMonths }, detailKey: "detailAll", detailParams: { dailyMonths, years: Math.round(COACH_ALL_TIME_TABLE_DAYS / 365), + labRecent: COACH_LAB_RECENT_POINTS, + labYears: COACH_LAB_HISTORY_YEARS, }, }; }