From 68b428e3f530ff4578b8733ac97ecba9fff6969f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 22 Oct 2026 14:50:10 +0200 Subject: [PATCH] feat(ai): screen Brazilian Portuguese model output for dose, risk and causal claims MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The outbound screen runs the reader's bank plus English. For a pt-BR reader that meant English only, so "Pare de tomar a metformina", "Dobre a dose de insulina" or "Seu risco é de cerca de 12%" passed every surface unchanged. Add the pt-BR banks the other locales carry: the dose-change class (change, take, skip, stop, halve, double and one-more imperatives in the você form, with the medication nouns, drug classes and stop words a Brazilian reply uses), the target-dose patterns, the risk-figure and risk-engine patterns, the causal phrases, and the continuation and change-stem pair that keeps "continue tomando conforme prescrito" exempt. UI joins the insulin units, and the count words and dosage forms gain the Portuguese spellings. Tests pin each class blocking in pt-BR and the restatements, negated warnings, referrals, past changes and dietary advice passing. --- .../outbound-screen-named-meds.test.ts | 23 +++ .../safety/__tests__/outbound-screen.test.ts | 32 ++++ src/lib/ai/safety/outbound-screen.ts | 144 +++++++++++++++++- .../__tests__/describe-screen.test.ts | 2 + .../__tests__/status-summary-screen.test.ts | 4 + 5 files changed, 200 insertions(+), 5 deletions(-) diff --git a/src/lib/ai/safety/__tests__/outbound-screen-named-meds.test.ts b/src/lib/ai/safety/__tests__/outbound-screen-named-meds.test.ts index a9c3d3b14..609fa81fb 100644 --- a/src/lib/ai/safety/__tests__/outbound-screen-named-meds.test.ts +++ b/src/lib/ai/safety/__tests__/outbound-screen-named-meds.test.ts @@ -34,6 +34,7 @@ const NAMES: Record = { it: ["Ramipril", "Amlodipina", "Eliquis"], pl: ["Ramipril", "Amlodypina", "Eliquis"], nl: ["Ramipril 5 mg", "Amlodipine", "Eliquis"], + "pt-BR": ["Ramipril 5 mg", "Anlodipino", "Eliquis"], ko: ["라미프릴", "암로디핀", "엘리퀴스"], }; @@ -105,6 +106,17 @@ const VIOLATIONS: Record = { "Neem een halve tablet amlodipine.", "Neem vanavond een tablet meer.", ], + "pt-BR": [ + "Depois tome um ramipril a mais.", + "Pule o anlodipino amanhã.", + "Tome mais um comprimido de ramipril.", + "Pare o anticoagulante antes da corrida.", + "Tome dois comprimidos de ramipril hoje à noite.", + "Tome outro anlodipino se a pressão estiver alta.", + "Suspenda o Eliquis no fim de semana.", + "Tire o Eliquis amanhã.", + "Tome meio comprimido de anlodipino.", + ], ko: [ "라미프릴을 한 알 더 드세요.", "암로디핀은 내일 건너뛰세요.", @@ -194,6 +206,17 @@ const CLEAN: Record = { "Laat de suiker in je koffie weg.", "Neem nog een meting vanavond.", ], + "pt-BR": [ + "Seu registro de ramipril mostra todas as doses tomadas.", + "Não pule o ramipril.", + "Nunca tome um anlodipino a mais para compensar um esquecido.", + "Você tomou um ramipril a mais na terça-feira, segundo o seu registro.", + "Faça uma caminhada a mais depois do jantar.", + "Tome o seu ramipril conforme prescrito.", + "Ramipril e anlodipino baixam a pressão.", + "Tire o açúcar do café.", + "Faça mais uma medição hoje à noite.", + ], ko: [ "라미프릴 기록을 보면 모두 복용하셨어요.", "암로디핀을 건너뛰지 마세요.", diff --git a/src/lib/ai/safety/__tests__/outbound-screen.test.ts b/src/lib/ai/safety/__tests__/outbound-screen.test.ts index b361b2f22..6c80e8528 100644 --- a/src/lib/ai/safety/__tests__/outbound-screen.test.ts +++ b/src/lib/ai/safety/__tests__/outbound-screen.test.ts @@ -24,6 +24,7 @@ const DOSE_VIOLATIONS: Record = { it: "Aumenti la sua dose a 2,4 mg la prossima settimana.", pl: "Proszę zwiększyć dawkę do 2,4 mg w przyszłym tygodniu.", nl: "Verhoog je dosis volgende week naar 2,4 mg.", + "pt-BR": "Aumente sua dose para 2,4 mg na próxima semana.", ko: "다음 주에 2.4mg으로 늘려 보세요.", }; @@ -40,6 +41,7 @@ const DOSE_CLEAN: Record = { it: "È alla settimana 3 con 7,5 mg, e il suo peso sta calando.", pl: "Jest Pan w 3. tygodniu na 7,5 mg, a waga spada.", nl: "Je zit in week 3 op 7,5 mg, en je gewicht gaat omlaag.", + "pt-BR": "Você está na semana 3 com 7,5 mg, e seu peso está diminuindo.", ko: "3주째 7.5mg을 복용 중이고, 체중은 내려가고 있어요.", }; @@ -99,6 +101,7 @@ const RISK_VIOLATIONS: Record = { it: "Il suo rischio è del 12%.", pl: "Pana ryzyko wynosi 12%.", nl: "Je risico ligt op ongeveer 12%.", + "pt-BR": "Seu risco é de cerca de 12%.", ko: "10년 심혈관 위험도는 약 12%예요.", }; @@ -111,6 +114,7 @@ const RISK_CLEAN: Record = { it: "La sua aderenza è stata del 92% negli ultimi 30 giorni.", pl: "Przestrzeganie zaleceń wyniosło 92% w ostatnich 30 dniach.", nl: "Je therapietrouw was 92% in de afgelopen 30 dagen.", + "pt-BR": "Sua adesão foi de 92% nos últimos 30 dias.", ko: "지난 30일 복약 순응도는 92%였어요.", }; @@ -355,6 +359,7 @@ const CAUSAL_VIOLATIONS: Record = { it: "Il suo peso è calato perché ha dormito di più.", pl: "Waga spadła, ponieważ spał Pan więcej.", nl: "Je gewicht daalde omdat je meer sliep.", + "pt-BR": "Seu peso caiu porque você dormiu mais.", ko: "수면 때문에 체중이 줄었어요.", }; @@ -367,6 +372,7 @@ const CAUSAL_CLEAN: Record = { it: "Il suo peso si è mosso insieme al suo sonno questa settimana.", pl: "Waga zmieniała się razem ze snem w tym tygodniu.", nl: "Je gewicht bewoog deze week mee met je slaap.", + "pt-BR": "Seu peso acompanhou o seu sono nesta semana.", ko: "이번 주 체중은 수면과 함께 움직였어요.", }; @@ -530,6 +536,18 @@ const DOSE_CLASS_VIOLATIONS: Record = { "Je zou de metformine een week kunnen stoppen.", "Bouw je dosis af naar 5 mg.", ], + "pt-BR": [ + "Aumente sua metformina para 2000 mg por dia.", + "Reduza sua insulina em 4 unidades.", + "Tome 2 comprimidos à noite.", + "Pule a dose da noite.", + "Pare de tomar a metformina.", + "Você poderia suspender a metformina por uma semana.", + "Diminua a dose para 5 mg.", + "Dobre a dose de insulina.", + "Passe a tomar 10 mg pela manhã.", + "Aplique 12 UI de insulina antes do jantar.", + ], ko: [ "메트포르민을 2000mg으로 증량하세요.", "하루 2정 드세요.", @@ -599,6 +617,20 @@ const DOSE_CLASS_CLEAN: Record = { "Probeer je glucose na de maaltijd onder 140 mg/dL te houden.", "Vraag je arts voordat je stopt met metformine.", ], + "pt-BR": [ + "Você toma 1000 mg duas vezes ao dia.", + "Seu registro mostra 2000 mg.", + "Não pule a dose da noite.", + "Não deixe de tomar a metformina.", + "Pergunte ao seu médico se deve aumentar a dose.", + "Sua dose foi aumentada para 2000 mg em maio.", + "A dose passou para 2000 mg em maio, segundo o registro.", + "Dobre seus passos por duas semanas.", + "Reduza o sódio para 1500 mg por dia.", + "Aumente a ingestão de água para 2000 ml.", + "Tente manter a glicose abaixo de 140 mg/dL depois das refeições.", + "Converse com seu médico antes de parar de tomar a metformina.", + ], ko: [ "하루 두 번 1000mg을 복용하고 계세요.", "저녁 용량을 건너뛰지 마세요.", diff --git a/src/lib/ai/safety/outbound-screen.ts b/src/lib/ai/safety/outbound-screen.ts index 0eade54fc..daadc593c 100644 --- a/src/lib/ai/safety/outbound-screen.ts +++ b/src/lib/ai/safety/outbound-screen.ts @@ -64,19 +64,20 @@ export interface OutboundDecision { * the screen recognised and passed every pattern. */ const COUNT_UNIT = - "(?:tablets?|pills?|capsules?|drops?|puffs?|sprays?|tabletten?|kapseln?|tropfen|hübe|hub|sprühstöße|comprimés?|gélules?|gouttes?|bouffées?|comprimidos?|pastillas?|c[áa]psulas?|gotas?|compress[ae]|pastigli[ae]|gocce|goccia|tabletk[aęiy]|tabletek|kapsułk[aęiy]|kapsułek|krople|kropli|druppels?|pillen|pufjes?)"; + "(?:tablets?|pills?|capsules?|drops?|puffs?|sprays?|tabletten?|kapseln?|tropfen|hübe|hub|sprühstöße|comprimés?|gélules?|gouttes?|bouffées?|comprimidos?|pastillas?|c[áa]psulas?|gotas?|compress[ae]|pastigli[ae]|gocce|goccia|tabletk[aęiy]|tabletek|kapsułk[aęiy]|kapsułek|krople|kropli|druppels?|pillen|pufjes?|pastilhas?|dr[áa]geas?|jatos?|borrifadas?)"; /** * Dose units for a target dose a model must never name. Mass and insulin * units plus the count forms; `ml` is left out on purpose because * "increase your water intake to 2000 ml" is ordinary hydration advice. */ -const DRUG_UNIT = `(?:mg|mcg|µg|iu|i\\.u\\.|units?|unidades?|unità|unités?|jednostek|jednostki|eenheden|eenheid|ie|i\\.e\\.|einheiten|${COUNT_UNIT})`; +const DRUG_UNIT = `(?:mg|mcg|µg|iu|ui|i\\.u\\.|units?|unidades?|unità|unités?|jednostek|jednostki|eenheden|eenheid|ie|i\\.e\\.|einheiten|${COUNT_UNIT})`; /** * Dose units, shared across every locale. The GLP-1 + general oral-dose * vocabulary; `ie` / `i.e.` / `einheiten` / `unità` / `jednostk` cover the - * insulin-unit spellings the locales use (`eenheden` is the Dutch one). + * insulin-unit spellings the locales use (`eenheden` is the Dutch one, `ui` + * the Portuguese abbreviation of "unidades internacionais"). */ const DOSE_UNIT = `(?:ml|${DRUG_UNIT})`; @@ -120,6 +121,8 @@ const PL_STOP = const NL_STOP = "niet|nooit|geen|en|of|maar|als|wanneer|natrium|zout|cafeïne|suiker\\S*|water|vocht|eiwit\\S*|vezels?|koffie|alcohol|stappen|calorieën"; +const PT_STOP = + "não|nao|nunca|jamais|e|ou|mas|se|sódio|sal|cafeína|açúcar\\S*|água|proteínas?|fibras?|café|álcool|passos|calorias"; const EN_SLOT = objectSlot(EN_STOP); const DE_SLOT = objectSlot(DE_STOP); @@ -128,6 +131,7 @@ const ES_SLOT = objectSlot(ES_STOP); const IT_SLOT = objectSlot(IT_STOP); const PL_SLOT = objectSlot(PL_STOP); const NL_SLOT = objectSlot(NL_STOP); +const PT_SLOT = objectSlot(PT_STOP); /** * A tighter slot for the shapes that carry no number: an extra / one more / @@ -154,6 +158,10 @@ const NL_SHORT = objectSlot( `${NL_STOP}|om|voor|met|bij|in|op|van|naar|tot|na|zonder|tijdens|over|dan|te`, 2, ); +const PT_SHORT = objectSlot( + `${PT_STOP}|para|pra|com|antes|depois|sem|a|ao|à|de|do|da|em|no|na|por|durante|sobre`, + 2, +); /** * A count written as a word: "take two tablets", "nimm zwei Tabletten", @@ -163,7 +171,7 @@ const NL_SHORT = objectSlot( * count, and the "eine Tablette mehr" shape is its own pattern below. */ const SPELLED_COUNT = - "(?:one|two|three|four|five|six|half\\s+an?|zwei|drei|vier|fünf|sechs|eine?n?\\s+halbe|halbe|deux|trois|quatre|cinq|demi|dos|tres|cuatro|cinco|media|due|tre|quattro|cinque|mezza|dwie|dwa|trzy|cztery|pięć|jedn[ąa]|pół|één|twee|drie|vier|vijf|zes|halve)"; + "(?:one|two|three|four|five|six|half\\s+an?|zwei|drei|vier|fünf|sechs|eine?n?\\s+halbe|halbe|deux|trois|quatre|cinq|demi|dos|tres|cuatro|cinco|media|due|tre|quattro|cinque|mezza|dwie|dwa|trzy|cztery|pięć|jedn[ąa]|pół|één|twee|drie|vier|vijf|zes|halve|dois|duas|três|quatro|meia|meio)"; /** A target dose: a number, a drug unit, and an honest unit end. */ const TARGET = `(?:[\\d.,]+\\s*${DRUG_UNIT}${UNIT_END}|(? = { pl: "(?:lek\\S*\\s+przeciwzakrzepow\\S*|antykoagulant\\S*|statyn\\S*|beta-?bloker\\S*|diuretyk\\S*|lek\\S*\\s+moczopędn\\S*|antydepresant\\S*)", nl: "\\S*(?:bloedverdunner\\w*|antistolling\\w*|anticoagul\\w*|plaatjesremmer\\w*|statine\\w*|b[eè]tablokker\\w*|plastablet\\w*|diuretic\\w*|diuretica|antidepressiv\\w*|antidepressiva|slaappil\\w*|slaapmiddel\\w*|bloeddrukverlager\\w*|ace-remmer\\w*)", ko: "(?:혈액\\s*희석제|항응고제|항혈소판제|스타틴|베타\\s*차단제|이뇨제|항우울제)", + "pt-BR": + "(?:anticoagulantes?|antiagregantes?|estatinas?|beta-?bloqueadores?|diur[ée]ticos?|antidepressivos?|anti-?hipertensivos?|sonífer[oa]s?|rem[ée]dios?\\s+(?:para\\s+(?:a\\s+)?|de\\s+)(?:press[ãa]o|dormir|afinar\\s+o\\s+sangue))", }; /** @@ -315,6 +327,7 @@ function medVocabulary(names: string | null): MedVocabulary { pl: `(?:${PL_MED_NOUN}|${extra("pl")})`, nl: `(?:${NL_MED_NOUN}|${extra("nl")})`, ko: `(?:${KO_MED_NOUN}|(?:${extra("ko")})\\S{0,3})`, + "pt-BR": `(?:${PT_MED_NOUN}|${extra("pt-BR")})`, }; } @@ -647,6 +660,46 @@ function buildDoseChangeClass( "i", ), ], + "pt-BR": [ + // Brazilian Portuguese addresses the reader as "você", whose imperative + // is the subjunctive form ("aumente", "tome", "pule"). The indicative a + // log restatement uses ("você toma 1000 mg", "a dose passou para 2000 + // mg") is not in any verb list. + new RegExp( + `(? = { "i", ), /\b(?:halveer|halveren|verdubbel|verdubbelen|sla\s+\S+\s+over|overslaan|stop\s+met)\b[^.?!]{0,25}(?:dosis|tablet\w*|medicijn\w*|medicatie|insuline|injectie\w*)[^.?!]{0,40}\b(?:(?:een|twee|drie|vier|\d+)\s+(?:dag|dagen|week|weken|maand|maanden)|om\s+te\s+(?:zien|testen|kijken|proberen))\b/i, - ], // Korean is verb-final: the dose comes first and the change verb closes the + ], + "pt-BR": [ + // aumente / suba / passe a dose para|a|até|em N unit + new RegExp( + `\\b(?:aumente|aumentar|suba|subir|passe|passar|eleve|elevar)\\s+(?:a\\s+|sua\\s+|a\\s+sua\\s+)?(?:dose\\s+)?(?:para|pra|a|até|em)\\s+[\\d.,]+\\s*${DOSE_UNIT}${UNIT_END}`, + "i", + ), + // reduza / diminua / baixe a dose para|a|até|em N unit + new RegExp( + `\\b(?:reduza|reduzir|diminua|diminuir|baixe|baixar|abaixe|abaixar)\\s+(?:a\\s+|sua\\s+|a\\s+sua\\s+)?(?:dose\\s+)?(?:para|pra|a|até|em)\\s+[\\d.,]+\\s*${DOSE_UNIT}${UNIT_END}`, + "i", + ), + // A recommending cue in front of a drug amount. `ml` is left out: "tente + // beber 2000 ml de água por dia" is hydration advice. + new RegExp( + `\\b(?:considere|experimente|tente|voc[êe]\\s+deveria|voc[êe]\\s+deve|recomendo|sugiro)\\b[^.?!]{0,40}\\b[\\d.,]+\\s*${DRUG_UNIT}${UNIT_END}`, + "i", + ), + new RegExp( + `\\b(?:pr[óo]xima|nova)\\s+(?:dose|etapa)\\b[^.?!]{0,30}\\b[\\d.,]+\\s*${DOSE_UNIT}${UNIT_END}`, + "i", + ), + /\b(?:dobr\w*|duplic\w*|pul(?:e|ar)|suspend\w*|interromp\w*|pare\s+de\s+tomar|parar\s+de\s+tomar)\b[^.?!]{0,25}\b(?:doses?|comprimidos?|rem[ée]dios?|medicamentos?|medica[çc]|insulina|inje[çc])[^.?!]{0,40}\b(?:por\s+(?:\d+|um|uma|dois|duas|tr[êe]s|quatro)\s+(?:dias?|semanas?|m[êe]s|meses)|para\s+(?:ver|testar|experimentar))\b/i, + ], + // Korean is verb-final: the dose comes first and the change verb closes the // clause ("2.4mg으로 늘려 보세요"), the mirror image of the Latin banks. A // target-marking particle on the dose is required, the same demand the Latin // banks make of "to" / "by" / "auf" — without it the permitted restatement @@ -988,6 +1065,21 @@ const RISK_LEVEL_NL = "(?:verhoogd|hoog|hoger|matig|gemiddeld|zorgwekkend|aanzienlijk)"; const RISK_VERDICT_NL = `(?:is|ligt|lijkt|blijft|wijst\\s+op|komt\\s+uit\\s+op)\\s+(?:\\w+\\s+){0,2}${RISK_LEVEL_NL}`; +const SPELLED_PT = + "(?:zero|um|uma|dois|duas|tr[êe]s|quatro|cinco|seis|sete|oito|nove|dez|onze|doze|treze|quatorze|catorze|quinze|dezesseis|dezessete|dezoito|dezenove|vinte|trinta|quarenta|cinquenta|sessenta)"; +const PCT_WORD_PT = `${SPELLED_PT}\\s+por\\s+cento`; +const QUAL_NUM_PT = `(?:\\d{1,3}\\s*%|${PCT_WORD_PT})`; +const RISK_NOUN_PT = "(?:risco|probabilidade|chance)"; +const HORIZON_PT = + "(?:risco\\s+(?:cardiovascular|card[íi]aco|de\\s+(?:avc|infarto|morte|mortalidade))\\s+(?:em|para|nos\\s+pr[óo]ximos)\\s+(?:10|dez)\\s+anos)"; +const RESULT_VERB_PT = + "(?:(?:te|lhe|o|a)\\s+(?:coloca|classifica|situa|enquadra|p[õo]e)|coloca\\s+voc[êe]|voc[êe]\\s+(?:est[áa]|fica|cai|se\\s+enquadra)\\s+(?:n[oa]s?|em))"; +const RISK_BAND_PT = + "(?:(?:faixa|categoria|grupo)\\s+de\\s+)?(?:alto|baixo|m[ée]dio|moderado|elevado|intermedi[áa]rio)\\s+risco"; +const RISK_LEVEL_PT = + "(?:elevad[oa]|alt[oa]|moderad[oa]|intermedi[áa]ri[oa]|lim[íi]trofe|preocupante|significativ[oa])"; +const RISK_VERDICT_PT = `(?:é|est[áa]|parece|fica|continua|indica|mostra|aponta\\s+para)\\s+(?:\\S+\\s+){0,2}${RISK_LEVEL_PT}`; + // Korean carries no `\\b` next to a Hangul token: `\\b` is an ASCII \\w boundary // and Hangul is not \\w, so /\\b위험/ matches nothing. Spelled-out percentages are // omitted deliberately — Korean writes "12%", and the Sino-Korean numerals @@ -1139,6 +1231,30 @@ const RISK_PATTERNS: Record = { "i", ), ], + "pt-BR": [ + /\brisco\s+(?:de|é\s+de|fica\s+em|está\s+em)\s+(?:aproximadamente\s+|cerca\s+de\s+|uns\s+|~)?\d{1,3}\s*%/i, + /\b\d{1,3}\s*%\s+(?:de\s+)?(?:risco|probabilidade|chance)/i, + new RegExp(`\\b${RISK_NOUN_PT}\\b[^.?!]{0,40}\\d{1,3}\\s*%`, "i"), + new RegExp(`\\d{1,3}\\s*%[^.?!]{0,40}\\b${RISK_NOUN_PT}\\b`, "i"), + new RegExp(`\\b${RISK_NOUN_PT}\\b[^.?!]{0,40}\\b${PCT_WORD_PT}\\b`, "i"), + new RegExp(`\\b${PCT_WORD_PT}\\b[^.?!]{0,40}\\b${RISK_NOUN_PT}\\b`, "i"), + new RegExp(`\\b${HORIZON_PT}\\b[^.?!]{0,40}${QUAL_NUM_PT}`, "i"), + new RegExp(`${QUAL_NUM_PT}[^.?!]{0,40}\\b${HORIZON_PT}\\b`, "i"), + new RegExp(`\\b${ENGINE}\\b[^.?!]{0,50}${QUAL_NUM_PT}`, "i"), + new RegExp(`${QUAL_NUM_PT}[^.?!]{0,50}\\b${ENGINE}\\b`, "i"), + new RegExp( + `\\b(?:${ENGINE}|${HORIZON_PT})\\b[^.?!]{0,60}${RESULT_VERB_PT}`, + "i", + ), + new RegExp( + `\\b(?:${ENGINE}|${HORIZON_PT})\\b[^.?!]{0,60}${RISK_BAND_PT}`, + "i", + ), + new RegExp( + `\\b(?:${ENGINE}|${HORIZON_PT})\\b[^.?!]{0,40}${RISK_VERDICT_PT}`, + "i", + ), + ], ko: [ new RegExp(`${RISK_NOUN_KO}[^.?!]{0,15}${QUAL_NUM_KO}`, "i"), new RegExp(`${QUAL_NUM_KO}[^.?!]{0,15}${RISK_NOUN_KO}`, "i"), @@ -1251,6 +1367,20 @@ const CAUSAL_PATTERNS: Record = { /\boorzaak\b/i, /\bverantwoordelijk\s+voor\b/i, ], + "pt-BR": [ + /\bporque\b/i, + /\bpor\s+causa\s+d/i, + /\bdevido\s+[aà]/i, + /\bcaus(?:a|am|ou|aram|ado|ada|ando)\b/i, + /\bprovoc\w*/i, + /\blev(?:ou|aram)\s+[aà]/i, + /\bresult(?:ou|aram)\s+(?:em|d[eoa])\b/i, + /\bem\s+raz[ãa]o\s+d/i, + /\bem\s+decorr[êe]ncia\s+d/i, + /\bgra[çc]as\s+[aà]/i, + /\brespons[áa]ve(?:l|is)\s+(?:por|pel[oa]s?)\b/i, + /\bculpa\s+d/i, + ], ko: [ /때문(?:에|이)/, /(?:으로|로)\s*인(?:해|하여|한)/, @@ -1314,6 +1444,8 @@ const DOSE_CONTINUATION: Record = { it: /\b(?:continui\s+(?:a|con|il)|mantenga|come\s+prescritto)\b/i, pl: /\b(?:kontynuuj|przyjmuj\s+dalej|zgodnie\s+z\s+zaleceniem|pozosta[ńn]\s+przy)\b/i, nl: /\b(?:blijf|ga\s+door\s+met|neem\s+\S+\s+(?:gewoon\s+)?verder|zoals\s+(?:voorgeschreven|afgesproken|gebruikelijk|gewoonlijk|altijd|eerder|je\s+gewend\s+bent)|volgens\s+(?:voorschrift|afspraak))\b/i, + "pt-BR": + /\b(?:continue\s+(?:tomando|usando|com|a\s+tomar)|siga\s+(?:tomando|com)|mantenha|conforme\s+(?:prescrito|receitado|orientado|combinado)|como\s+(?:prescrito|receitado|de\s+costume|sempre|antes)|de\s+acordo\s+com\s+a\s+(?:receita|prescri))/i, ko: /(?:그대로\s*(?:유지|복용)|계속\s*(?:복용|드시|유지)|처방(?:대로|받은\s*대로)|지시(?:대로|받은\s*대로)|유지하세요)/, }; @@ -1325,6 +1457,8 @@ const DOSE_CHANGE_STEM: Record = { it: /\b(?:aument|riduc|ridur|abbass|diminu|raddoppi|salt|dimezz)\w*/i, pl: /\b(?:zwiększ|zmniejsz|obniż|podnie[śs]|podwyższ|zreduk|pomi[ńn]|opu[śs][ćc])\w*/i, nl: /\b(?:verho|verla|vermind|halve|verdubbel|ophog|afbouw|bouw\s+af|overslaan|sla\s+\S+\s+over|stop)\w*/i, + "pt-BR": + /\b(?:aument|reduz|diminu|dobr|duplic|omit|suspend|interromp|baix(?:e|ar|ando)\b|abaix|elev(?:e|ar)\b|sub(?:a|ir)\b|pul(?:e|ar|ando)\b|parar?\s+de\s+tomar)\w*/i, ko: /(?:올리|올려|늘리|늘려|증량|높이|높여|인상|내리|내려|줄이|줄여|감량|낮추|낮춰|감축|절반|반으로|두\s?배|건너뛰|중단|끊)/, }; diff --git a/src/lib/documents/__tests__/describe-screen.test.ts b/src/lib/documents/__tests__/describe-screen.test.ts index e881687fc..e7a146a8a 100644 --- a/src/lib/documents/__tests__/describe-screen.test.ts +++ b/src/lib/documents/__tests__/describe-screen.test.ts @@ -32,6 +32,8 @@ describe("runDocumentSummary — screened prose", () => { pl: "Ten raport sugeruje, aby zwiększyć dawkę do 2,4 mg w przyszłym tygodniu.", ko: "이 보고서는 다음 주에 2.4mg으로 늘려 보라고 권해요.", nl: "Dit rapport stelt voor om de dosis volgende week naar 2,4 mg te verhogen.", + "pt-BR": + "Este laudo sugere aumentar sua dose para 2,4 mg na próxima semana.", }; for (const locale of locales) { diff --git a/src/lib/insights/__tests__/status-summary-screen.test.ts b/src/lib/insights/__tests__/status-summary-screen.test.ts index 9250fb45a..7a2be6255 100644 --- a/src/lib/insights/__tests__/status-summary-screen.test.ts +++ b/src/lib/insights/__tests__/status-summary-screen.test.ts @@ -23,6 +23,7 @@ const DOSE_VIOLATION: Record = { pl: '{"summary":"Proszę zwiększyć dawkę do 2,4 mg w przyszłym tygodniu."}', ko: '{"summary":"다음 주에 2.4mg으로 늘려 보세요."}', nl: '{"summary":"Verhoog je dosis volgende week naar 2,4 mg."}', + "pt-BR": '{"summary":"Aumente sua dose para 2,4 mg na próxima semana."}', }; const CAUSAL_VIOLATION: Record = { @@ -34,6 +35,7 @@ const CAUSAL_VIOLATION: Record = { pl: '{"summary":"Waga spadła, ponieważ spał Pan więcej."}', ko: '{"summary":"수면 때문에 체중이 줄었어요."}', nl: '{"summary":"Je gewicht daalde omdat je meer sliep."}', + "pt-BR": '{"summary":"Seu peso caiu porque você dormiu mais."}', }; const CLEAN: Record = { @@ -45,6 +47,8 @@ const CLEAN: Record = { pl: '{"summary":"Waga wynosi średnio 82 kg w tym tygodniu, stabilnie wobec zeszłego miesiąca."}', ko: '{"summary":"이번 주 체중은 평균 82kg으로, 지난달과 비슷해요."}', nl: '{"summary":"Je gewicht ligt deze week gemiddeld op 82 kg, stabiel ten opzichte van vorige maand."}', + "pt-BR": + '{"summary":"Seu peso está em média em 82 kg nesta semana, estável em relação ao mês passado."}', }; describe("finalizeStatusSummary — dose prescriptions are withheld", () => {