From 8c11bb83854b630cb708e86ea20619211e109a1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 22 Oct 2026 15:21:10 +0200 Subject: [PATCH] refactor(coach): keep the population-norm banks in one module per locale The reference-sentence exemption's patterns move out of coach-prose-grounding.ts into population-norms/.ts, composed into the same Record the check already reads. The patterns themselves are unchanged. A test feeds every locale one reference sentence the English bank cannot match, so a module that is not wired fails. --- .../__tests__/coach-prose-grounding.test.ts | 33 ++++++ src/lib/ai/coach/coach-prose-grounding.ts | 101 +----------------- src/lib/ai/coach/population-norms/de.ts | 11 ++ src/lib/ai/coach/population-norms/en.ts | 10 ++ src/lib/ai/coach/population-norms/es.ts | 10 ++ src/lib/ai/coach/population-norms/fr.ts | 10 ++ src/lib/ai/coach/population-norms/index.ts | 31 ++++++ src/lib/ai/coach/population-norms/it.ts | 10 ++ src/lib/ai/coach/population-norms/ko.ts | 16 +++ src/lib/ai/coach/population-norms/nl.ts | 11 ++ src/lib/ai/coach/population-norms/pl.ts | 10 ++ src/lib/ai/coach/population-norms/pt-br.ts | 17 +++ 12 files changed, 174 insertions(+), 96 deletions(-) create mode 100644 src/lib/ai/coach/population-norms/de.ts create mode 100644 src/lib/ai/coach/population-norms/en.ts create mode 100644 src/lib/ai/coach/population-norms/es.ts create mode 100644 src/lib/ai/coach/population-norms/fr.ts create mode 100644 src/lib/ai/coach/population-norms/index.ts create mode 100644 src/lib/ai/coach/population-norms/it.ts create mode 100644 src/lib/ai/coach/population-norms/ko.ts create mode 100644 src/lib/ai/coach/population-norms/nl.ts create mode 100644 src/lib/ai/coach/population-norms/pl.ts create mode 100644 src/lib/ai/coach/population-norms/pt-br.ts diff --git a/src/lib/ai/coach/__tests__/coach-prose-grounding.test.ts b/src/lib/ai/coach/__tests__/coach-prose-grounding.test.ts index c3d6c2bf4..89ed32629 100644 --- a/src/lib/ai/coach/__tests__/coach-prose-grounding.test.ts +++ b/src/lib/ai/coach/__tests__/coach-prose-grounding.test.ts @@ -14,9 +14,12 @@ */ import { describe, expect, it } from "vitest"; +import { locales, type Locale } from "@/lib/i18n/config"; + import { collectNumericLeaves, findUnverifiedCoachNumbers, + hasPopulationNormFraming, stripUnverifiedNumbers, UNVERIFIED_ELISION_MARK, unitWordBank, @@ -659,3 +662,33 @@ describe("Brazilian Portuguese population-norm framing", () => { expect(findUnverifiedCoachNumbers(prose, payloads, "pt-BR")).toEqual([]); }); }); + +/** + * Each locale's population-norm bank lives in its own module. One reference + * sentence per locale, worded so the English bank (which always runs too) + * cannot be what matched it, pins that every module is wired and fires. + */ +describe("population-norm banks, one per locale", () => { + const REFERENCE_SENTENCE: Record = { + en: "Healthy adults usually sleep 7 to 9 hours.", + de: "Gesunde Erwachsene schlafen 7 bis 9 Stunden.", + fr: "La plupart des adultes dorment 7 à 9 heures.", + es: "Los adultos sanos duermen de 7 a 9 horas.", + it: "Gli adulti sani dormono da 7 a 9 ore.", + pl: "Większość dorosłych potrzebuje od 7 do 9 godzin snu.", + nl: "Gezonde volwassenen slapen 7 tot 9 uur.", + ko: "건강한 성인은 7~9시간 자요.", + "pt-BR": "A maioria dos adultos precisa de 7 a 9 horas de sono.", + }; + + it.each(locales)("%s reads its reference sentence as a norm", (locale) => { + expect(hasPopulationNormFraming(REFERENCE_SENTENCE[locale], locale)).toBe( + true, + ); + if (locale !== "en") { + expect(hasPopulationNormFraming(REFERENCE_SENTENCE[locale], "en")).toBe( + false, + ); + } + }); +}); diff --git a/src/lib/ai/coach/coach-prose-grounding.ts b/src/lib/ai/coach/coach-prose-grounding.ts index 027667237..7f1a050c3 100644 --- a/src/lib/ai/coach/coach-prose-grounding.ts +++ b/src/lib/ai/coach/coach-prose-grounding.ts @@ -65,6 +65,7 @@ */ import { extractNumbers } from "@/lib/ai/briefing-grounding"; import type { Locale } from "@/lib/i18n/config"; +import { POPULATION_NORM_PATTERNS } from "./population-norms"; /** Absolute + relative tolerance — identical basis to the briefing verifier. */ const ABS_TOLERANCE = 0.15; @@ -818,104 +819,12 @@ export function collectNumericLeaves(value: unknown, out: Set): void { * Rung 2 — the reference-sentence exemption. A sentence framed as population * guidance ("adults generally need 7 to 9 hours", "die normale Ruheherzfrequenz * liegt bei 60 bis 100") cites a REFERENCE value, not a claim about the user's - * data, so its numbers are exempt from the strip path. Six-locale banks — the - * user base is DE-first and a fallback provider answers a French reader in - * English, so the reader's bank plus EN both run (the screen's dual-bank rule). + * data, so its numbers are exempt from the strip path. One bank per locale, + * in `population-norms/`. The user base is DE-first and a fallback provider + * answers a French reader in English, so the reader's bank plus EN both run + * (the screen's dual-bank rule). * ────────────────────────────────────────────────────────────────────────── */ -const POPULATION_NORM_PATTERNS: Record = { - en: [ - /\bthe\s+normal\s+range\s+(?:is|for)\b/i, - /\b(?:healthy|most|the\s+average)\s+(?:adults?|people|population)\b/i, - /\bthe\s+general\s+population\b/i, - /\b(?:guidelines?|doctors?|experts?)\s+(?:say|recommend|suggest|consider|advise)\b/i, - /\b(?:adults?|people|most\s+people)\s+(?:generally|typically|usually|normally)\s+(?:need|require|get|aim)\b/i, - /\b(?:general|reference|normal|typical|healthy)\s+(?:reference\s+)?range\b/i, - /\b(?:recommended|typical|normal|healthy)\s+(?:amount|level|range|target)\s+(?:is|for|of)\b/i, - ], - de: [ - /\bder\s+normal(?:e|bereich)\b/i, - /\bdie\s+normale\b/i, - /\bnormal(?:er|e)?\s+(?:ruhe)?(?:herzfrequenz|puls|bereich|wert)\b/i, - /\b(?:gesunde|die\s+meisten|der\s+durchschnittliche?)\s+(?:erwachsene|menschen|bevölkerung)\b/i, - /\b(?:leitlinien|ärzte|expert\w*)\s+(?:sagen|empfehlen|raten|betrachten)\b/i, - /\b(?:erwachsene|menschen)\s+(?:brauchen|benötigen)\s+(?:in\s+der\s+regel|generell|typischerweise|normalerweise)\b/i, - /\b(?:allgemeine|normale|typische)\s+(?:referenz)?(?:bereich|spanne|werte)\b/i, - /\bliegt\s+(?:normalerweise|üblicherweise|in\s+der\s+regel)\s+(?:bei|zwischen)\b/i, - ], - fr: [ - /\bla\s+(?:plage|fourchette)\s+normale\b/i, - /\b(?:les\s+)?(?:adultes|personnes)\s+en\s+bonne\s+santé\b/i, - /\b(?:la\s+plupart\s+des|la\s+moyenne\s+des)\s+(?:adultes|gens|personnes)\b/i, - /\b(?:les\s+)?(?:recommandations|médecins|experts?)\s+(?:disent|recommandent|conseillent|suggèrent)\b/i, - /\b(?:les\s+)?adultes\s+(?:ont\s+généralement|ont\s+besoin|nécessitent)\b/i, - /\bplage\s+(?:de\s+référence|normale|recommandée)\b/i, - /\bse\s+situe\s+(?:généralement|normalement|habituellement)\s+(?:entre|autour)\b/i, - ], - es: [ - /\b(?:el|la)\s+(?:rango|intervalo)\s+normal\b/i, - /\b(?:los\s+)?adultos\s+sanos\b/i, - /\b(?:la\s+mayoría\s+de|el\s+promedio\s+de)\s+(?:los\s+)?(?:adultos|personas|gente)\b/i, - /\b(?:las\s+)?(?:guías|directrices|médicos|expertos)\s+(?:dicen|recomiendan|aconsejan|sugieren)\b/i, - /\b(?:los\s+)?adultos\s+(?:generalmente|normalmente|suelen)\s+(?:necesitan|requieren)\b/i, - /\b(?:rango|intervalo)\s+(?:de\s+referencia|normal|recomendado)\b/i, - /\bse\s+sitúa\s+(?:generalmente|normalmente|habitualmente)\s+(?:entre|alrededor)\b/i, - ], - it: [ - /\b(?:l'|il\s+|la\s+)?intervallo\s+normale\b/i, - /\b(?:gli\s+)?adulti\s+sani\b/i, - /\b(?:la\s+maggior\s+parte\s+(?:degli|delle)|la\s+media\s+(?:degli|delle))\s+(?:adulti|persone)\b/i, - /\b(?:le\s+)?(?:linee\s+guida|medici|esperti)\s+(?:dicono|raccomandano|consigliano|suggeriscono)\b/i, - /\b(?:gli\s+)?adulti\s+(?:generalmente|di\s+solito|normalmente)\s+(?:hanno\s+bisogno|necessitano)\b/i, - /\bintervallo\s+(?:di\s+riferimento|normale|raccomandato)\b/i, - /\bsi\s+(?:colloca|attesta)\s+(?:generalmente|normalmente|di\s+solito)\s+(?:tra|intorno)\b/i, - ], - pl: [ - /\b(?:normalny|prawidłowy)\s+(?:zakres|przedział)\b/i, - /\bzdrow(?:i|ych)\s+doros(?:li|łych)\b/i, - /\b(?:większość|średnia)\s+(?:doros(?:łych|li)|ludzi|osób)\b/i, - /\b(?:wytyczne|lekarze|eksperci)\s+(?:mówią|zalecają|radzą|sugerują)\b/i, - /\bdoros(?:li|łych)\s+(?:zazwyczaj|zwykle|na\s+ogół)\s+(?:potrzebują|wymagają)\b/i, - /\b(?:zakres|przedział)\s+(?:referencyjny|normy|prawidłowy|zalecany)\b/i, - /\b(?:wynosi|mieści\s+się)\s+(?:zazwyczaj|zwykle|na\s+ogół)\s+(?:od|między|około)\b/i, - ], - nl: [ - /\bhet\s+normale\s+(?:bereik|niveau)\b/i, - /\b(?:normale?|gezonde)\s+(?:rust)?(?:hartslag|pols|waarde|waarden|bereik)\b/i, - /\b(?:gezonde|de\s+meeste|de\s+gemiddelde)\s+(?:volwassenen|mensen|volwassene)\b/i, - /\bde\s+algemene\s+bevolking\b/i, - /\b(?:richtlijnen|artsen|experts?|deskundigen)\s+(?:zeggen|adviseren|raden|bevelen)\b/i, - /\b(?:volwassenen|mensen)\s+(?:hebben\s+)?(?:meestal|doorgaans|gewoonlijk|normaal\s+gesproken|over\s+het\s+algemeen)\s+(?:nodig|hebben)\b/i, - /\b(?:referentie|normaal|aanbevolen)\w*\s*(?:bereik|waarden?)\b/i, - /\bligt\s+(?:normaal\s+gesproken|meestal|doorgaans|gewoonlijk)\s+(?:tussen|rond|op)\b/i, - ], - // pt-BR. Every entry needs a population subject or a reference noun: a - // reading of the reader's own ("Sua pressão de 158/97 geralmente fica…", - // "Seu médico recomenda…") is a claim about their data, and an exempted - // sentence is one whose numbers are never checked. - "pt-BR": [ - /\bfaixa\s+(?:de\s+refer[eê]ncia|normal)\b/i, - /\bvalor(?:es)?\s+de\s+refer[eê]ncia\b/i, - /\b(?:adultos?|pessoas?)\s+saud[áa]ve(?:l|is)\b/i, - /\bmaioria\s+(?:dos\s+)?(?:adultos?|das\s+pessoas)\b/i, - /\b(?:as\s+|os\s+)?(?:diretrizes|m[eé]dicos|especialistas)\s+(?:geralmente\s+|em\s+geral\s+)?(?:dizem|recomendam|aconselham|sugerem|indicam|consideram)\b/i, - /\b(?:os\s+)?(?:adultos|as\s+pessoas)\s+(?:geralmente\s+|normalmente\s+|em\s+geral\s+)?(?:precisam|necessitam)\s+de\b/i, - /\b(?:a|o)\s+(?:frequ[êe]ncia\s+card[íi]aca|pulso|press[ãa]o(?:\s+arterial)?|glicemia|glicose)(?:\s+de\s+repouso)?\s+(?:normal|saud[áa]vel|t[íi]pica|t[íi]pico)\b/i, - ], - // No `\b` next to Hangul (it is an ASCII \w boundary), and the object sits - // between subject and verb in Korean, so each entry allows a gap where the - // Latin banks put \s+. - ko: [ - /정상\s*범위(?:는|가|를|로|의)/, - /(?:참고|기준|권장)\s*(?:범위|치|값)/, - /(?:건강한|대부분의)\s*(?:성인|사람들?|인구)/, - /일반\s*인구(?:는|에서|의)/, - /(?:지침|가이드라인|의사들?|전문가들?)(?:은|이|에서|에서는|에 따르면)?[^.?!]{0,25}(?:권(?:장|해)|권고|제안|조언)/, - /(?:성인|사람들?|대부분의?\s*사람들?)(?:은|이|의)?\s*(?:보통|일반적으로|대개|평균적으로)\s*[^.?!]{0,20}(?:필요|권장|적당)/, - /(?:보통|일반적으로|대개)\s*[^.?!]{0,15}(?:사이|정도|범위)(?:예요|에요|입니다|이에요)/, - ], -}; - /** * True when the prose cites a POPULATION norm (vs the user's own baseline). * The reader's-locale bank plus EN always run (a fallback provider answers a diff --git a/src/lib/ai/coach/population-norms/de.ts b/src/lib/ai/coach/population-norms/de.ts new file mode 100644 index 000000000..3a4780b96 --- /dev/null +++ b/src/lib/ai/coach/population-norms/de.ts @@ -0,0 +1,11 @@ +/** Population-norm framing, de. */ +export const patterns: readonly RegExp[] = [ + /\bder\s+normal(?:e|bereich)\b/i, + /\bdie\s+normale\b/i, + /\bnormal(?:er|e)?\s+(?:ruhe)?(?:herzfrequenz|puls|bereich|wert)\b/i, + /\b(?:gesunde|die\s+meisten|der\s+durchschnittliche?)\s+(?:erwachsene|menschen|bevölkerung)\b/i, + /\b(?:leitlinien|ärzte|expert\w*)\s+(?:sagen|empfehlen|raten|betrachten)\b/i, + /\b(?:erwachsene|menschen)\s+(?:brauchen|benötigen)\s+(?:in\s+der\s+regel|generell|typischerweise|normalerweise)\b/i, + /\b(?:allgemeine|normale|typische)\s+(?:referenz)?(?:bereich|spanne|werte)\b/i, + /\bliegt\s+(?:normalerweise|üblicherweise|in\s+der\s+regel)\s+(?:bei|zwischen)\b/i, +]; diff --git a/src/lib/ai/coach/population-norms/en.ts b/src/lib/ai/coach/population-norms/en.ts new file mode 100644 index 000000000..ae7beb2ef --- /dev/null +++ b/src/lib/ai/coach/population-norms/en.ts @@ -0,0 +1,10 @@ +/** Population-norm framing, en. */ +export const patterns: readonly RegExp[] = [ + /\bthe\s+normal\s+range\s+(?:is|for)\b/i, + /\b(?:healthy|most|the\s+average)\s+(?:adults?|people|population)\b/i, + /\bthe\s+general\s+population\b/i, + /\b(?:guidelines?|doctors?|experts?)\s+(?:say|recommend|suggest|consider|advise)\b/i, + /\b(?:adults?|people|most\s+people)\s+(?:generally|typically|usually|normally)\s+(?:need|require|get|aim)\b/i, + /\b(?:general|reference|normal|typical|healthy)\s+(?:reference\s+)?range\b/i, + /\b(?:recommended|typical|normal|healthy)\s+(?:amount|level|range|target)\s+(?:is|for|of)\b/i, +]; diff --git a/src/lib/ai/coach/population-norms/es.ts b/src/lib/ai/coach/population-norms/es.ts new file mode 100644 index 000000000..eebb90824 --- /dev/null +++ b/src/lib/ai/coach/population-norms/es.ts @@ -0,0 +1,10 @@ +/** Population-norm framing, es. */ +export const patterns: readonly RegExp[] = [ + /\b(?:el|la)\s+(?:rango|intervalo)\s+normal\b/i, + /\b(?:los\s+)?adultos\s+sanos\b/i, + /\b(?:la\s+mayoría\s+de|el\s+promedio\s+de)\s+(?:los\s+)?(?:adultos|personas|gente)\b/i, + /\b(?:las\s+)?(?:guías|directrices|médicos|expertos)\s+(?:dicen|recomiendan|aconsejan|sugieren)\b/i, + /\b(?:los\s+)?adultos\s+(?:generalmente|normalmente|suelen)\s+(?:necesitan|requieren)\b/i, + /\b(?:rango|intervalo)\s+(?:de\s+referencia|normal|recomendado)\b/i, + /\bse\s+sitúa\s+(?:generalmente|normalmente|habitualmente)\s+(?:entre|alrededor)\b/i, +]; diff --git a/src/lib/ai/coach/population-norms/fr.ts b/src/lib/ai/coach/population-norms/fr.ts new file mode 100644 index 000000000..b193b6048 --- /dev/null +++ b/src/lib/ai/coach/population-norms/fr.ts @@ -0,0 +1,10 @@ +/** Population-norm framing, fr. */ +export const patterns: readonly RegExp[] = [ + /\bla\s+(?:plage|fourchette)\s+normale\b/i, + /\b(?:les\s+)?(?:adultes|personnes)\s+en\s+bonne\s+santé\b/i, + /\b(?:la\s+plupart\s+des|la\s+moyenne\s+des)\s+(?:adultes|gens|personnes)\b/i, + /\b(?:les\s+)?(?:recommandations|médecins|experts?)\s+(?:disent|recommandent|conseillent|suggèrent)\b/i, + /\b(?:les\s+)?adultes\s+(?:ont\s+généralement|ont\s+besoin|nécessitent)\b/i, + /\bplage\s+(?:de\s+référence|normale|recommandée)\b/i, + /\bse\s+situe\s+(?:généralement|normalement|habituellement)\s+(?:entre|autour)\b/i, +]; diff --git a/src/lib/ai/coach/population-norms/index.ts b/src/lib/ai/coach/population-norms/index.ts new file mode 100644 index 000000000..46ff3161f --- /dev/null +++ b/src/lib/ai/coach/population-norms/index.ts @@ -0,0 +1,31 @@ +/** + * Population-norm framing per locale, one module per language. A sentence + * that matches is read as population guidance, and its numbers are exempt + * from the evidence check (`../coach-prose-grounding.ts`, rung 2), so every + * entry needs a population subject or a reference noun. + */ +import type { Locale } from "@/lib/i18n/config"; + +import { patterns as en } from "./en"; +import { patterns as de } from "./de"; +import { patterns as fr } from "./fr"; +import { patterns as es } from "./es"; +import { patterns as it } from "./it"; +import { patterns as pl } from "./pl"; +import { patterns as nl } from "./nl"; +import { patterns as ptBR } from "./pt-br"; +import { patterns as ko } from "./ko"; + +export const POPULATION_NORM_PATTERNS: Readonly< + Record +> = { + en, + de, + fr, + es, + it, + pl, + nl, + "pt-BR": ptBR, + ko, +}; diff --git a/src/lib/ai/coach/population-norms/it.ts b/src/lib/ai/coach/population-norms/it.ts new file mode 100644 index 000000000..4c4c9767d --- /dev/null +++ b/src/lib/ai/coach/population-norms/it.ts @@ -0,0 +1,10 @@ +/** Population-norm framing, it. */ +export const patterns: readonly RegExp[] = [ + /\b(?:l'|il\s+|la\s+)?intervallo\s+normale\b/i, + /\b(?:gli\s+)?adulti\s+sani\b/i, + /\b(?:la\s+maggior\s+parte\s+(?:degli|delle)|la\s+media\s+(?:degli|delle))\s+(?:adulti|persone)\b/i, + /\b(?:le\s+)?(?:linee\s+guida|medici|esperti)\s+(?:dicono|raccomandano|consigliano|suggeriscono)\b/i, + /\b(?:gli\s+)?adulti\s+(?:generalmente|di\s+solito|normalmente)\s+(?:hanno\s+bisogno|necessitano)\b/i, + /\bintervallo\s+(?:di\s+riferimento|normale|raccomandato)\b/i, + /\bsi\s+(?:colloca|attesta)\s+(?:generalmente|normalmente|di\s+solito)\s+(?:tra|intorno)\b/i, +]; diff --git a/src/lib/ai/coach/population-norms/ko.ts b/src/lib/ai/coach/population-norms/ko.ts new file mode 100644 index 000000000..7ae999471 --- /dev/null +++ b/src/lib/ai/coach/population-norms/ko.ts @@ -0,0 +1,16 @@ +/** + * Population-norm framing, ko. + * + * No `\b` next to Hangul (it is an ASCII \w boundary), and the object sits + * between subject and verb in Korean, so each entry allows a gap where the + * Latin banks put \s+. + */ +export const patterns: readonly RegExp[] = [ + /정상\s*범위(?:는|가|를|로|의)/, + /(?:참고|기준|권장)\s*(?:범위|치|값)/, + /(?:건강한|대부분의)\s*(?:성인|사람들?|인구)/, + /일반\s*인구(?:는|에서|의)/, + /(?:지침|가이드라인|의사들?|전문가들?)(?:은|이|에서|에서는|에 따르면)?[^.?!]{0,25}(?:권(?:장|해)|권고|제안|조언)/, + /(?:성인|사람들?|대부분의?\s*사람들?)(?:은|이|의)?\s*(?:보통|일반적으로|대개|평균적으로)\s*[^.?!]{0,20}(?:필요|권장|적당)/, + /(?:보통|일반적으로|대개)\s*[^.?!]{0,15}(?:사이|정도|범위)(?:예요|에요|입니다|이에요)/, +]; diff --git a/src/lib/ai/coach/population-norms/nl.ts b/src/lib/ai/coach/population-norms/nl.ts new file mode 100644 index 000000000..92a79eca8 --- /dev/null +++ b/src/lib/ai/coach/population-norms/nl.ts @@ -0,0 +1,11 @@ +/** Population-norm framing, nl. */ +export const patterns: readonly RegExp[] = [ + /\bhet\s+normale\s+(?:bereik|niveau)\b/i, + /\b(?:normale?|gezonde)\s+(?:rust)?(?:hartslag|pols|waarde|waarden|bereik)\b/i, + /\b(?:gezonde|de\s+meeste|de\s+gemiddelde)\s+(?:volwassenen|mensen|volwassene)\b/i, + /\bde\s+algemene\s+bevolking\b/i, + /\b(?:richtlijnen|artsen|experts?|deskundigen)\s+(?:zeggen|adviseren|raden|bevelen)\b/i, + /\b(?:volwassenen|mensen)\s+(?:hebben\s+)?(?:meestal|doorgaans|gewoonlijk|normaal\s+gesproken|over\s+het\s+algemeen)\s+(?:nodig|hebben)\b/i, + /\b(?:referentie|normaal|aanbevolen)\w*\s*(?:bereik|waarden?)\b/i, + /\bligt\s+(?:normaal\s+gesproken|meestal|doorgaans|gewoonlijk)\s+(?:tussen|rond|op)\b/i, +]; diff --git a/src/lib/ai/coach/population-norms/pl.ts b/src/lib/ai/coach/population-norms/pl.ts new file mode 100644 index 000000000..0ca306e87 --- /dev/null +++ b/src/lib/ai/coach/population-norms/pl.ts @@ -0,0 +1,10 @@ +/** Population-norm framing, pl. */ +export const patterns: readonly RegExp[] = [ + /\b(?:normalny|prawidłowy)\s+(?:zakres|przedział)\b/i, + /\bzdrow(?:i|ych)\s+doros(?:li|łych)\b/i, + /\b(?:większość|średnia)\s+(?:doros(?:łych|li)|ludzi|osób)\b/i, + /\b(?:wytyczne|lekarze|eksperci)\s+(?:mówią|zalecają|radzą|sugerują)\b/i, + /\bdoros(?:li|łych)\s+(?:zazwyczaj|zwykle|na\s+ogół)\s+(?:potrzebują|wymagają)\b/i, + /\b(?:zakres|przedział)\s+(?:referencyjny|normy|prawidłowy|zalecany)\b/i, + /\b(?:wynosi|mieści\s+się)\s+(?:zazwyczaj|zwykle|na\s+ogół)\s+(?:od|między|około)\b/i, +]; diff --git a/src/lib/ai/coach/population-norms/pt-br.ts b/src/lib/ai/coach/population-norms/pt-br.ts new file mode 100644 index 000000000..e7830692d --- /dev/null +++ b/src/lib/ai/coach/population-norms/pt-br.ts @@ -0,0 +1,17 @@ +/** + * Population-norm framing, pt-BR. + * + * Every entry needs a population subject or a reference noun: a reading of + * the reader's own ("Sua pressão de 158/97 geralmente fica…", "Seu médico + * recomenda…") is a claim about their data, and an exempted sentence is one + * whose numbers are never checked. + */ +export const patterns: readonly RegExp[] = [ + /\bfaixa\s+(?:de\s+refer[eê]ncia|normal)\b/i, + /\bvalor(?:es)?\s+de\s+refer[eê]ncia\b/i, + /\b(?:adultos?|pessoas?)\s+saud[áa]ve(?:l|is)\b/i, + /\bmaioria\s+(?:dos\s+)?(?:adultos?|das\s+pessoas)\b/i, + /\b(?:as\s+|os\s+)?(?:diretrizes|m[eé]dicos|especialistas)\s+(?:geralmente\s+|em\s+geral\s+)?(?:dizem|recomendam|aconselham|sugerem|indicam|consideram)\b/i, + /\b(?:os\s+)?(?:adultos|as\s+pessoas)\s+(?:geralmente\s+|normalmente\s+|em\s+geral\s+)?(?:precisam|necessitam)\s+de\b/i, + /\b(?:a|o)\s+(?:frequ[êe]ncia\s+card[íi]aca|pulso|press[ãa]o(?:\s+arterial)?|glicemia|glicose)(?:\s+de\s+repouso)?\s+(?:normal|saud[áa]vel|t[íi]pica|t[íi]pico)\b/i, +];