Merge branch 'fix/v147-screen-negations' into release/v1.47.0

This commit is contained in:
Marc-André Bombeck committed 2026-11-04 00:17:01 +01:00
commit 1bbd2d97c7
13 files changed
+945 -5

No files matched your search

@@ -84,6 +84,10 @@ const SENTENCES: Record<Locale, readonly string[]> = {
"Owing to the heat, it rose.",
"Salt is responsible for it.",
"Keep taking your 7.5 mg as prescribed.",
"Don't take atorvastatin tonight.",
"Take a break from enalapril.",
"Add 4 more units of insulin.",
"Take 10 ml of the syrup.",
],
de: [
"Reduzieren Sie die Dosis um die Hälfte.",
@@ -127,6 +131,12 @@ const SENTENCES: Record<Locale, readonly string[]> = {
"Der Auslöser war Salz.",
"Salz ist verantwortlich für den Anstieg.",
"Nimm dein Metformin weiter wie verordnet.",
"Nehmen Sie kein Atorvastatin.",
"Nimm heute Abend dein Metoprolol nicht.",
"Mach eine Pause mit dem Enalapril.",
"Setzen Sie das Ramipril für ein paar Tage aus.",
"Geben Sie 2 zusätzliche Einheiten Insulin dazu.",
"Nehmen Sie 10 ml Hustensaft.",
],
fr: [
"Réduisez la dose de moitié.",
@@ -161,6 +171,11 @@ const SENTENCES: Record<Locale, readonly string[]> = {
"Il a baissé grâce à la marche.",
"Le stress provoque une hausse.",
"Continuez à prendre votre metformine.",
"Ne prenez pas votre atorvastatine ce soir.",
"Faites une pause avec l’énalapril.",
"Mettez le ramipril en pause.",
"Rajoutez 2 unités d’insuline.",
"Prenez 10 ml de sirop.",
],
es: [
"Reduzca la dosis a la mitad.",
@@ -194,6 +209,10 @@ const SENTENCES: Record<Locale, readonly string[]> = {
"Bajó gracias a la caminata.",
"Fue impulsado por el estrés.",
"Siga tomando su metformina.",
"No tome atorvastatina esta noche.",
"Tome un descanso del enalapril.",
"Añada 4 unidades más de insulina.",
"Tome 10 ml del jarabe.",
],
it: [
"Riduca la dose della metà.",
@@ -226,6 +245,10 @@ const SENTENCES: Record<Locale, readonly string[]> = {
"È calato grazie a una camminata.",
"È stato guidato da stress.",
"Continui a prendere la metformina.",
"Non prenda l’atorvastatina stasera.",
"Faccia una pausa dall’enalapril.",
"Prenda 10 ml di sciroppo.",
"Aggiunga 2 unità alla dose della sera.",
],
pl: [
"Zmniejsz dawkę o połowę.",
@@ -258,6 +281,10 @@ const SENTENCES: Record<Locale, readonly string[]> = {
"Spadło dzięki spacerom.",
"Sól jest odpowiedzialna za wzrost.",
"Kontynuuj przyjmowanie leku.",
"Nie bierz dziś wieczorem atorwastatyny.",
"Zrób przerwę w przyjmowaniu enalaprylu.",
"Weź 10 ml syropu.",
"Dodaj 2 jednostki do wieczornej dawki.",
],
nl: [
"Verlaag je dosis met de helft.",
@@ -304,6 +331,11 @@ const SENTENCES: Record<Locale, readonly string[]> = {
"De oorzaak is zout.",
"Zout is verantwoordelijk voor de stijging.",
"Blijf je metformine nemen.",
"Neem vandaag geen tabletten.",
"Neem uw metoprolol niet.",
"Neem een pauze van enalapril.",
"Neem 10 ml van de siroop.",
"Voeg 2 eenheden toe aan je avonddosis.",
],
ko: [
"복용량을 늘리세요.",
@@ -336,6 +368,10 @@ const SENTENCES: Record<Locale, readonly string[]> = {
"산책 덕분에 내려갔어요.",
"스트레스가 영향을 줘서 올랐어요.",
"처방대로 복용하세요.",
"오늘 밤에는 아토르바스타틴을 복용하지 마세요.",
"에날라프릴 복용을 잠시 쉬세요.",
"시럽 10ml를 드세요.",
"저녁 인슐린에 2단위를 추가하세요.",
],
"pt-BR": [
"Reduza a dose pela metade.",
@@ -377,6 +413,10 @@ const SENTENCES: Record<Locale, readonly string[]> = {
"O sal é responsável pela alta.",
"Isso é culpa do sal.",
"Continue tomando sua metformina conforme prescrito.",
"Não tome atorvastatina hoje à noite.",
"Faça uma pausa no enalapril.",
"Adicione 4 unidades a mais de insulina.",
"Tome 10 ml do xarope.",
],
ru: [
"2000 мг увеличьте.",
@@ -418,6 +458,10 @@ const SENTENCES: Record<Locale, readonly string[]> = {
"Благодаря прогулкам это снизилось.",
"Это является результатом нагрузки.",
"Продолжайте принимать 7,5 мг как назначено.",
"Не принимайте аторвастатин сегодня вечером.",
"Сделайте перерыв в приёме эналаприла.",
"Добавьте ещё 4 единицы инсулина.",
"Примите 10 мл сиропа.",
],
};
@@ -0,0 +1,428 @@
/**
* Four instruction shapes that walked past the dose screen in every language:
*
* 1. a negated take ("Don't take atorvastatin tonight", «Не принимайте
* аторвастатин»): a negation in front of the intake verb is a hold or a
* stop, not a warning, when its object is a medication and nothing
* protective ("a double dose", "more than", "half") sits in between;
* 2. a pause ("Take a break from enalapril", «Сделайте перерыв в приёме
* эналаприла», "Mach eine Pause mit Ramipril");
* 3. an increment with no target ("Add 4 more units of insulin", «Добавьте
* ещё 4 единицы инсулина», "Spritzen Sie 2 Einheiten mehr");
* 4. a liquid dose of a drug form ("Take 10 ml of the syrup", «Примите 10 мл
* сиропа»). `ml` alone stays out of the drug units, because "drink 500 ml
* of water" is hydration advice; the shape keys on the drug-form noun.
*
* Every shape is anchored on a medication noun, a drug unit or a drug form,
* never on the verb alone, so the near-misses below (a reading not taken
* after coffee, a break from screens, two more walks, water by the
* millilitre, a repeat lab test, the protective "don't take a double dose")
* pass.
*/
import { describe, expect, it } from "vitest";
import type { Locale } from "@/lib/i18n/config";
import {
CONVERSATIONAL_CONTRACTS,
screenModelOutput,
} from "../outbound-screen";
type Shape = "negated" | "pause" | "increment" | "liquid";
const BLOCKED: Record<Locale, Record<Shape, readonly string[]>> = {
en: {
negated: [
"Don't take atorvastatin tonight.",
"Do not take your metoprolol.",
"Don't take your blood pressure medication today.",
"Please don't take your evening insulin tonight.",
],
pause: [
"Take a break from enalapril.",
"Take a short break from your statin for two weeks.",
"Give yourself a break from the metformin this weekend.",
],
increment: [
"Add 4 more units of insulin.",
"Take 2 more tablets tonight.",
"Inject 3 extra units before dinner.",
"Add 4 units to your evening dose.",
],
liquid: [
"Take 10 ml of the syrup.",
"Take 5 ml of the oral solution twice a day.",
"Give yourself 15 ml of the suspension.",
],
},
de: {
negated: [
"Nehmen Sie kein Atorvastatin.",
"Nimm heute Abend dein Metoprolol nicht.",
"Nehmen Sie Ihre Tabletten heute nicht ein.",
"Nimm heute keine Tabletten.",
],
pause: [
"Pausieren Sie Ramipril.",
"Mach eine Pause mit dem Enalapril.",
"Legen Sie eine Pause bei Ihrem Ramipril ein.",
"Setzen Sie das Ramipril für ein paar Tage aus.",
],
increment: [
"Spritzen Sie 2 Einheiten mehr.",
"Spritz heute Abend 4 Einheiten mehr Insulin.",
"Geben Sie 2 zusätzliche Einheiten Insulin dazu.",
"Spritz zum Abendessen 2 Einheiten dazu.",
],
liquid: [
"Nehmen Sie 10 ml Hustensaft.",
"Nimm 5 ml von dem Sirup.",
"Nehmen Sie 10 ml der Lösung ein.",
],
},
fr: {
negated: [
"Ne prenez pas votre atorvastatine ce soir.",
"Ne prends pas ton métoprolol.",
"Ne prenez pas vos comprimés aujourd’hui.",
],
pause: [
"Faites une pause avec l’énalapril.",
"Faites une pause dans votre ramipril pendant une semaine.",
"Mettez le ramipril en pause.",
],
increment: [
"Ajoutez 4 unités d’insuline de plus.",
"Injectez 2 unités supplémentaires.",
"Prenez 2 comprimés de plus ce soir.",
"Rajoutez 2 unités d’insuline.",
],
liquid: ["Prenez 10 ml de sirop.", "Prenez 5 ml de la solution buvable."],
},
es: {
negated: [
"No tome atorvastatina esta noche.",
"No tomes tu metoprolol.",
"No tome sus pastillas hoy.",
],
pause: [
"Tome un descanso del enalapril.",
"Haga una pausa con el ramipril durante una semana.",
],
increment: [
"Añada 4 unidades más de insulina.",
"Póngase 2 unidades más esta noche.",
"Tome 2 pastillas más.",
"Añada 2 unidades a su dosis de la noche.",
],
liquid: ["Tome 10 ml del jarabe.", "Tome 5 ml de la solución oral."],
},
it: {
negated: [
"Non prenda l’atorvastatina stasera.",
"Non prendere il tuo metoprololo.",
"Non prenda le compresse oggi.",
],
pause: [
"Faccia una pausa dall’enalapril.",
"Prenditi una pausa dal ramipril per una settimana.",
],
increment: [
"Aggiunga 4 unità di insulina in più.",
"Inietti 2 unità in più.",
"Prenda 2 compresse in più.",
"Aggiunga 2 unità alla dose della sera.",
"Faccia 2 unità in più di insulina.",
],
liquid: ["Prenda 10 ml di sciroppo.", "Prendi 5 ml della soluzione orale."],
},
pl: {
negated: [
"Nie bierz dziś wieczorem atorwastatyny.",
"Proszę nie przyjmować metoprololu.",
"Nie przyjmuj dziś tabletek.",
],
pause: [
"Zrób przerwę w przyjmowaniu enalaprylu.",
"Zrób sobie przerwę od ramiprylu na tydzień.",
],
increment: [
"Dodaj 4 jednostki insuliny więcej.",
"Wstrzyknij 2 jednostki więcej.",
"Weź 2 tabletki więcej.",
"Dodaj 2 jednostki do wieczornej dawki.",
"Podaj sobie 2 jednostki insuliny więcej.",
],
liquid: ["Weź 10 ml syropu.", "Przyjmij 5 ml roztworu doustnego."],
},
nl: {
negated: [
"Neem vanavond je atorvastatine niet in.",
"Neem uw metoprolol niet.",
"Neem vandaag geen tabletten.",
],
pause: [
"Neem een pauze van enalapril.",
"Las een pauze in met je ramipril.",
],
increment: [
"Voeg 4 eenheden insuline extra toe.",
"Spuit 2 eenheden meer.",
"Neem 2 tabletten extra.",
"Voeg 2 eenheden toe aan je avonddosis.",
"Doe er 2 eenheden insuline bij.",
],
liquid: ["Neem 10 ml van de siroop.", "Neem 5 ml van de oplossing in."],
},
ko: {
negated: [
"오늘 밤에는 아토르바스타틴을 복용하지 마세요.",
"메토프롤롤을 드시지 마세요.",
"오늘은 약을 먹지 마세요.",
],
pause: [
"에날라프릴 복용을 잠시 쉬세요.",
"라미프릴을 일주일 동안 쉬어 보세요.",
],
increment: [
"인슐린을 4단위 더 맞으세요.",
"2정 더 복용하세요.",
"인슐린 4단위를 추가로 주사하세요.",
"저녁 인슐린에 2단위를 추가하세요.",
"인슐린을 2단위 더 놓으세요.",
],
liquid: ["시럽 10ml를 드세요.", "물약을 5 mL 복용하세요."],
},
"pt-BR": {
negated: [
"Não tome atorvastatina hoje à noite.",
"Não tome o seu metoprolol.",
"Não tome seus comprimidos hoje.",
],
pause: [
"Faça uma pausa no enalapril.",
"Dê um tempo do ramipril por uma semana.",
],
increment: [
"Aplique mais 4 unidades de insulina.",
"Adicione 4 unidades a mais de insulina.",
"Tome mais 2 comprimidos.",
"Acrescente 2 unidades à dose da noite.",
],
liquid: ["Tome 10 ml do xarope.", "Tome 5 ml da solução oral."],
},
ru: {
negated: [
"Не принимайте аторвастатин сегодня вечером.",
"Не принимайте ваш метопролол.",
"Не пейте таблетки сегодня.",
],
pause: [
"Сделайте перерыв в приёме эналаприла.",
"Сделайте паузу с рамиприлом на неделю.",
],
increment: [
"Добавьте ещё 4 единицы инсулина.",
"Введите на 2 единицы больше.",
"Добавьте 2 таблетки сверху.",
"Прибавьте 2 единицы к вечерней дозе.",
],
liquid: [
"Примите 10 мл сиропа.",
"Принимайте по 5 мл раствора дважды в день.",
],
},
};
/** Near-misses that share a verb, a number or a unit with the shapes above. */
const PASSES: Record<Locale, readonly string[]> = {
en: [
"Don't take your readings right after coffee.",
"Don't take your blood pressure right after exercise.",
"Take a break from screens before bed.",
"Add 2 more walks per week.",
"Drink 500 ml of water.",
"Don't skip your morning walk.",
"Repeat the test in 3 months.",
"Don't take a double dose to make up for a missed one.",
"Do not take more than 4 tablets in a day.",
"Don't stop taking your metoprolol without talking to your doctor.",
"Take a 10-minute break from sitting.",
"Add 1 more serving of vegetables to dinner.",
"Don't take two doses at once.",
"Take 10 minutes to log your readings.",
"Don't take your medication on an empty stomach.",
"Don't take ibuprofen with your blood thinner.",
"If you don't take your metformin tonight, log it.",
"You don't take insulin.",
"Take a break from logging for a day.",
"Take 5 ml of honey in warm water.",
"Do not take your blood pressure reading right after coffee.",
"Don't take your dose with grapefruit juice.",
"Don't take statins lightly.",
"Take a rest from training this week.",
"Why don't you take your readings tonight?",
],
de: [
"Messen Sie nicht direkt nach dem Kaffee.",
"Nimm deine Messung nicht direkt nach dem Kaffee vor.",
"Mach vor dem Schlafen eine Pause vom Bildschirm.",
"Pausieren Sie das Training für zwei Tage.",
"Plane 2 Spaziergänge mehr pro Woche ein.",
"Trinken Sie 500 ml Wasser.",
"Trink 200 ml Orangensaft zum Frühstück.",
"Lass deinen Morgenspaziergang nicht ausfallen.",
"Wiederholen Sie den Test in 3 Monaten.",
"Nehmen Sie keine doppelte Dosis, um eine vergessene nachzuholen.",
"Setzen Sie Ramipril nicht ohne Rücksprache ab.",
"Nimm keine zusätzliche Tablette.",
"Nehmen Sie nicht mehr als 4 Tabletten am Tag.",
"Nehmen Sie keine Tabletten auf nüchternen Magen.",
"Nimm deine Tabletten nicht mit Grapefruitsaft.",
"Machen Sie eine Pause vom Sitzen.",
"Nehmen Sie 10 ml Olivenöl.",
"Gib 2 Löffel Haferflocken dazu.",
"Nimm dir eine Auszeit vom Handy.",
],
fr: [
"Ne prenez pas vos mesures juste après un café.",
"Faites une pause d’écran avant de dormir.",
"Ajoutez 2 marches de plus par semaine.",
"Buvez 500 ml d’eau.",
"Ne sautez pas votre marche du matin.",
"Refaites le test dans 3 mois.",
"Ne prenez pas de double dose pour rattraper un oubli.",
"N’arrêtez pas votre ramipril sans avis médical.",
"Faites une pause de 10 minutes après le repas.",
"Ne prenez pas plus de 4 comprimés par jour.",
"Ne prenez pas vos comprimés avec du jus de pamplemousse.",
"Prenez une pause après le repas.",
"Ne prenez pas votre tension juste après le café.",
],
es: [
"No se tome la presión justo después del café.",
"Tómese un descanso de las pantallas antes de dormir.",
"Añada 2 caminatas más por semana.",
"Beba 500 ml de agua.",
"No se salte su caminata matutina.",
"Repita el análisis en 3 meses.",
"No tome una dosis doble para compensar una olvidada.",
"No deje de tomar el ramipril sin hablar con su médico.",
"No tome más de 4 pastillas al día.",
"Haga una pausa de 10 minutos después de comer.",
"No tome sus pastillas con zumo de pomelo.",
"No se tome la tensión después de comer.",
],
it: [
"Non misuri la pressione subito dopo il caffè.",
"Faccia una pausa dagli schermi prima di dormire.",
"Aggiunga 2 passeggiate in più a settimana.",
"Beva 500 ml d’acqua.",
"Non salti la passeggiata del mattino.",
"Ripeta l’esame tra 3 mesi.",
"Non prenda una dose doppia per compensare quella dimenticata.",
"Non sospenda il ramipril senza parlarne con il medico.",
"Non prenda più di 4 compresse al giorno.",
"Non prenda le compresse a stomaco vuoto.",
"Prendi una pausa dal lavoro.",
],
pl: [
"Nie mierz ciśnienia zaraz po kawie.",
"Zrób przerwę od ekranów przed snem.",
"Dodaj 2 spacery więcej w tygodniu.",
"Wypij 500 ml wody.",
"Nie pomijaj porannego spaceru.",
"Powtórz badanie za 3 miesiące.",
"Nie bierz podwójnej dawki, aby nadrobić pominiętą.",
"Nie odstawiaj ramiprylu bez rozmowy z lekarzem.",
"Nie bierz więcej niż 4 tabletki dziennie.",
"Nie bierz tabletek na pusty żołądek.",
],
nl: [
"Neem je metingen niet direct na de koffie.",
"Neem een pauze van schermen voor het slapengaan.",
"Voeg 2 wandelingen extra per week toe.",
"Drink 500 ml water.",
"Sla je ochtendwandeling niet over.",
"Herhaal de test over 3 maanden.",
"Neem geen dubbele dosis om een vergeten dosis in te halen.",
"Stop niet met ramipril zonder overleg met je arts.",
"Neem niet meer dan 4 tabletten per dag.",
"Neem geen tabletten op een lege maag.",
"Neem even pauze van het werk.",
],
ko: [
"커피를 마신 직후에는 혈압을 재지 마세요.",
"자기 전에는 화면을 잠시 쉬세요.",
"일주일에 산책을 2번 더 하세요.",
"물을 500ml 드세요.",
"아침 산책을 거르지 마세요.",
"3개월 후에 검사를 다시 하세요.",
"잊은 약을 보충하려고 두 배로 복용하지 마세요.",
"의사와 상의 없이 라미프릴을 끊지 마세요.",
"하루에 4정보다 많이 복용하지 마세요.",
"약을 공복에 먹지 마세요.",
"자몽 주스와 함께 약을 드시지 마세요.",
"약 2주 동안 커피를 마시지 마세요.",
],
"pt-BR": [
"Não meça a pressão logo depois do café.",
"Faça uma pausa das telas antes de dormir.",
"Adicione mais 2 caminhadas por semana.",
"Beba 500 ml de água.",
"Não pule sua caminhada da manhã.",
"Repita o exame em 3 meses.",
"Não tome dose dobrada para compensar a esquecida.",
"Não pare o ramipril sem falar com seu médico.",
"Não tome mais de 4 comprimidos por dia.",
"Não tome seus comprimidos com suco de toranja.",
],
ru: [
"Не измеряйте давление сразу после кофе.",
"Сделайте перерыв от экранов перед сном.",
"Добавьте ещё 2 прогулки в неделю.",
"Выпейте 500 мл воды.",
"Не пропускайте утреннюю прогулку.",
"Повторите анализ через 3 месяца.",
"Не принимайте двойную дозу, чтобы восполнить пропущенную.",
"Не прекращайте приём рамиприла без консультации с врачом.",
"Не принимайте больше 4 таблеток в день.",
"Не принимайте таблетки натощак.",
"Не принимайте таблетки с грейпфрутовым соком.",
"Сделайте перерыв в приёме пищи перед анализом.",
"Добавьте 2 ложки овсянки.",
],
};
const PLAN = [
{ name: "Ramipril", dose: 5 },
{ name: "Metoprolol", dose: 47.5 },
];
describe("dose screen: negated take, pause, increment and liquid dose", () => {
for (const [locale, shapes] of Object.entries(BLOCKED) as Array<
[Locale, Record<Shape, readonly string[]>]
>) {
for (const [shape, sentences] of Object.entries(shapes)) {
it.each(sentences)(`blocks the ${shape} shape in ${locale}: %s`, (s) => {
const d = screenModelOutput(s, locale, CONVERSATIONAL_CONTRACTS);
expect(d.block).toBe(true);
expect(d.reason).toBe("dose_prescription");
});
}
}
for (const [locale, sentences] of Object.entries(PASSES) as Array<
[Locale, readonly string[]]
>) {
it.each(sentences)(`passes in ${locale}: %s`, (s) => {
const bare = screenModelOutput(s, locale, CONVERSATIONAL_CONTRACTS);
const named = screenModelOutput(s, locale, CONVERSATIONAL_CONTRACTS, {
dosePlan: PLAN,
medicationNames: PLAN.map((entry) => entry.name),
});
expect(bare.block).toBe(false);
expect(named.block).toBe(false);
});
}
});
@@ -1,6 +1,9 @@
/** Outbound-screen banks for German, composed by `../../outbound-screen.ts`. */
import {
DOSE_UNIT,
DRUG_UNIT,
LIQUID_UNIT,
SPELLED_COUNT,
UNIT_END,
objectSlot,
TARGET,
@@ -37,6 +40,23 @@ const DE_DOSE_OBJECT =
/** A German count in front of a medication noun ("eine Tablette mehr"). */
const DE_COUNT = "ein(?:e|en)?|zwei|drei|vier|halbe|\\d+(?:[.,]\\d+)?";
/** The German intake imperatives a hold, an increment or a liquid dose uses. */
const DE_TAKE =
"(?:nimm|nehmen\\s+sie|spritz|spritzen\\s+sie|injizier(?:e)?|injizieren\\s+sie)";
/**
* The object of a negated intake. A protective qualifier ends it, so "nimm
* keine doppelte Dosis", "keine zusätzliche Tablette" and "nimm die halbe
* Dosis nicht" stay warnings.
*/
const DE_HOLD_SLOT = objectSlot(
`${DE_STOP}|als|zu|mit|für|gegen|statt|ohne|über|auf|doppelt\\w*|zusätzlich\\w*|extra|weitere\\w*|halbe\\w*|hälfte|zweite\\w*|mehr|zwei|andere\\w*`,
4,
);
/** A liquid drug form a measured volume may be a dose of; juice is not one. */
const DE_LIQUID = `(?<![${LETTER}])(?:husten|fieber|schmerz)?saft(?:e?s)?(?![${LETTER}])|[${LETTER}]*(?:sirup|lösung|suspension|tinktur|tropfen|elixier)[${LETTER}]*`;
const SPELLED_DE =
"(?:null|eins?|zwei|drei|vier|fünf|sechs|sieben|acht|neun|zehn|elf|zwölf|dreizehn|vierzehn|fünfzehn|sechzehn|siebzehn|achtzehn|neunzehn|zwanzig|dreißig|vierzig|fünfzig|sechzig|siebzig|achtzig|neunzig)";
@@ -146,6 +166,40 @@ export const bank: LocaleBank = {
"i",
),
),
// A negated intake is a hold: "Nehmen Sie kein Atorvastatin", "nimm heute
// keine Tabletten", "nimm dein Metoprolol heute nicht". A protective
// qualifier or an interaction ("keine Tabletten mit Grapefruitsaft")
// keeps it a warning.
new RegExp(
`\\b${DE_TAKE}\\s+${objectSlot(DE_STOP, 3)}kein(?:e|en|er|es)?\\s+${DE_HOLD_SLOT}${med}(?![${LETTER}])(?!\\s+(?:doppelt|zusätzlich|extra|auf|gleichzeitig|zusammen|mit|ohne|zu|zur|zum|als|gegen)(?![${LETTER}]))`,
"i",
),
new RegExp(
`\\b${DE_TAKE}\\s+${DE_HOLD_SLOT}${med}\\s+(?:(?:heute|morgen|abends?|früh|vorerst|zunächst|erst\\s*mal|diese\\s+woche)\\s+){0,3}nicht(?:\\s+mehr)?(?:\\s+ein)?(?=\\s*(?:[.!;),]|$)|\\s+(?:und|oder|bis|vor|ab)\\s)`,
"i",
),
// A pause: "Mach eine Pause mit dem Enalapril", "legen Sie eine Pause bei
// Ihrem Ramipril ein", "setzen Sie das Ramipril für ein paar Tage aus".
new RegExp(
`\\b(?:mach(?:e|en\\s+sie)?|leg(?:e|en\\s+sie)?|gönn(?:e|en\\s+sie)?)\\s+(?:(?:dir|sich)\\s+)?(?:eine|ein)\\s+(?:[^\\s\\d.?!,;:]+\\s+)?(?:pause|einnahmepause|auszeit)\\s+(?:mit|bei|von|vom|beim|in)\\s+(?:(?:der\\s+)?einnahme\\s+(?:von\\s+)?)?${DE_SHORT}${med}`,
"i",
),
new RegExp(
`\\bsetz(?:e|en)?(?:\\s+sie)?\\s+${DE_SLOT}${med}(?:(?!\\b(?:nicht|nie|niemals)\\b)[^.?!]){0,30}?\\saus(?=\\s*(?:[.!?,;:]|$))`,
"i",
),
// An increment: "Geben Sie 2 zusätzliche Einheiten dazu", "gib noch 2
// Einheiten". The amount carries a drug unit.
new RegExp(
`\\b(?:gib|geben\\s+sie|${DE_TAKE})\\s+(?:(?:dir|sich)\\s+)?${DE_SHORT}(?:noch\\s+)?(?:[\\d.,]+|${SPELLED_COUNT})\\s*(?:(?:zusätzlich\\w*|weitere\\w*|extra)\\s+)?${DRUG_UNIT}${UNIT_END}`,
"i",
),
// A measured volume of a liquid drug form: "Nehmen Sie 10 ml Hustensaft".
// "Trinken Sie 500 ml Wasser" and orange juice pass.
new RegExp(
`\\b(?:nimm|nehmen\\s+sie|gib|geben\\s+sie)\\s+(?:(?:dir|sich)\\s+)?${DE_SHORT}[\\d.,]+\\s*${LIQUID_UNIT}${UNIT_END}\\s+${DE_SHORT}(?:${DE_LIQUID}|${med})`,
"i",
),
],
dose: [
new RegExp(
@@ -1,6 +1,9 @@
/** Outbound-screen banks for English, composed by `../../outbound-screen.ts`. */
import {
DOSE_UNIT,
DRUG_UNIT,
LIQUID_UNIT,
SPELLED_COUNT,
UNIT_END,
objectSlot,
TARGET,
@@ -10,8 +13,14 @@ import {
type LocaleBank,
} from "../shared";
/**
* Words the English object slot may not cross. The other languages'
* negations are here because the English bank runs on every reader's text:
* the Dutch warning "Stop niet met ramipril zonder overleg" otherwise read as
* "stop" + two slot words + a generic name.
*/
const EN_STOP =
"not|never|and|or|but|if|whether|when|sodium|salt|caffeine|sugars?|fib(?:er|re)|protein|carbs?|carbohydrates?|cholesterol|water|fluids?|coffee|alcohol|steps";
"not|never|no|niet|nicht|nie|non|não|nao|nunca|nooit|jamais|and|or|but|if|whether|when|sodium|salt|caffeine|sugars?|fib(?:er|re)|protein|carbs?|carbohydrates?|cholesterol|water|fluids?|coffee|alcohol|steps";
const EN_SLOT = objectSlot(EN_STOP);
@@ -20,6 +29,28 @@ const EN_SHORT = objectSlot(
2,
);
/**
* The object of a negated intake verb. A protective count or qualifier ends
* it, so "don't take a double dose", "do not take more than 4 tablets" and
* "don't take half a tablet" stay warnings.
*/
const EN_HOLD_SLOT = objectSlot(
`${EN_STOP}|to|for|with|at|in|on|of|from|into|than|while|during|like|half|double|more|extra|additional|another|second|two|both|multiple|any|other|same`,
3,
);
/**
* Where a negated intake ends to read as a hold: the clause closes, or a time
* word follows ("don't take your metoprolol tonight"). "Don't take your pills
* with grapefruit" meets neither and passes.
*/
const EN_HOLD_END =
"(?=\\s*(?:[.!;)]|$)|\\s+(?:today|tonight|tomorrow|this\\s+(?:morning|evening|afternoon|week|weekend)|for\\s+(?:now|a\\s+(?:few|couple|while|week|day)|the\\s+(?:next|rest)|\\d+|two|three|several)|any\\s*more|until|before|in\\s+the\\s+(?:morning|evening))\\b)";
/** A liquid drug form a measured volume may be a dose of. */
const EN_LIQUID =
"(?:syrups?|solutions?|suspensions?|elixirs?|tinctures?|linctus|mixtures?|liquid\\s+medicine)\\b";
/**
* Where an English imperative can start: the sentence head (or a list bullet),
* after a colon / dash, or after a recommending lead-in. A negated imperative
@@ -146,6 +177,32 @@ export const bank: LocaleBank = {
"im",
),
),
// A negated intake is a hold: "Don't take atorvastatin tonight", "Do not
// take your metoprolol." A description ("if you don't take", "you
// don't take") and the "why don't you take" nudge sit outside it.
new RegExp(
`(?<!\\b(?:why|if|you|they|we|i|when|that|who)\\s)\\b(?:(?:don['’]?t|do\\s+not|you\\s+should(?:n['’]t|\\s+not))\\s+(?:take|use|inject|give\\s+yourself)|avoid\\s+(?:taking|using|injecting))\\s+${EN_HOLD_SLOT}${med}${EN_HOLD_END}`,
"i",
),
// A pause: "Take a break from enalapril", "give yourself a break from
// the metformin". A break from screens or sitting names no medication.
new RegExp(
`${EN_LEAD}(?:take|have|give\\s+(?:yourself|your\\s+body))\\s+(?:an?\\s+)?(?:[^\\s\\d.?!,;:]+\\s+){0,2}?(?:break|pause|holiday|rest|vacation)\\s+from\\s+(?:taking\\s+)?${EN_SHORT}${med}`,
"im",
),
// An increment with no target: "Add 4 more units of insulin", "inject 3
// extra units". The amount has to carry a drug unit, so "add 2 more
// walks" and "take 10 more minutes" pass.
new RegExp(
`${EN_LEAD}(?:add|take|inject|use|give\\s+yourself|pop)\\s+(?:another\\s+|an?\\s+(?:extra|additional)\\s+)?(?:[\\d.,]+|${SPELLED_COUNT})\\s*(?:(?:more|extra|additional|further)\\s+)?${DRUG_UNIT}${UNIT_END}`,
"im",
),
// A measured volume of a liquid drug form: "Take 10 ml of the syrup".
// "Drink 500 ml of water" names no drug form and passes.
new RegExp(
`${EN_LEAD}(?:take|use|give\\s+yourself|swallow|measure\\s+out)\\s+(?:[\\d.,]+|${SPELLED_COUNT}|half\\s+a)\\s*(?:${LIQUID_UNIT}|teaspoons?|tsp|tablespoons?|tbsp)${UNIT_END}\\s+(?:of\\s+)?${EN_SHORT}(?:${EN_LIQUID}|${med})`,
"im",
),
],
dose: [
// step/move/increase/raise/bump/titrate/go up to|by N unit
@@ -10,6 +10,9 @@ import {
unlessDeferred,
WORD_START,
WORD_END,
DRUG_UNIT,
LIQUID_UNIT,
SPELLED_COUNT,
type LocaleBank,
} from "../shared";
@@ -23,6 +26,21 @@ const ES_SHORT = objectSlot(`${ES_STOP}|para|con|antes|después|sin|a|al`, 2);
const ES_MED_NOUN =
"(?:dosis|pastillas?|comprimidos?|medicamentos?|medicaci[óo]n|insulina|inyecci[óo]n|inyecciones|c[áa]psulas?|tratamiento)";
/**
* The object of a negated intake. A protective qualifier ends it, so "no tome
* una dosis doble" and "no tome más de 4 pastillas" stay warnings.
*/
const ES_HOLD_SLOT = objectSlot(
`${ES_STOP}|para|con|antes|después|sin|a|al|más|dobles?|media|medio|mitad|dos|otra|otro|extra|adicional|segunda`,
3,
);
/** Where a negated intake reads as a hold: the clause closes or a time word follows. */
const ES_HOLD_END = `(?=\\s*(?:[.!;)]|$)|\\s+(?:hoy|esta\\s+(?:noche|mañana|tarde|semana)|mañana|por\\s+(?:ahora|el\\s+momento|unos\\s+días)|durante|hasta|antes)${WORD_END})`;
/** A liquid drug form a measured volume may be a dose of. */
const ES_LIQUID = `(?:jarabes?|soluci[óo]n|soluciones|suspensi[óo]n|gotas|elixir|tintura)${WORD_END}`;
const SPELLED_ES =
"(?:cero|uno|dos|tres|cuatro|cinco|seis|siete|ocho|nueve|diez|once|doce|trece|catorce|quince|dieciséis|diecisiete|dieciocho|diecinueve|veinte|treinta|cuarenta|cincuenta|sesenta)";
@@ -96,6 +114,29 @@ export const bank: LocaleBank = {
"i",
),
),
// A negated intake is a hold: "No tome atorvastatina esta noche", "no
// tomes tu metoprolol".
new RegExp(
`${WORD_START}no\\s+(?:se\\s+)?(?:tome|tomes|tomen|tomar|use|uses|usen|inyecte|inyectes|aplique|apliques|ponga)(?:se)?${WORD_END}\\s+${ES_HOLD_SLOT}${med}${ES_HOLD_END}`,
"i",
),
// A pause: "Tome un descanso del enalapril", "haga una pausa con el
// ramipril". A break from screens names no medication.
new RegExp(
`${WORD_START}(?:tome|toma|tómese|tómate|haga|haz|hágase)\\s+(?:un|una)\\s+(?:[^\\s\\d.?!,;:]+\\s+)?(?:descanso|pausa)\\s+(?:(?:del|de\\s+la|de\\s+los|de\\s+las|de|con\\s+el|con\\s+la|con|en\\s+el|en\\s+la|en)\\s+)?${ES_SHORT}${med}`,
"i",
),
// An increment: "Añada 4 unidades más de insulina", "póngase 2 unidades".
new RegExp(
`${WORD_START}(?:añada|añade|agregue|agrega|póngase|ponte|inyecte|inyéctese|aplique|aplíquese)\\s+(?:otr[ao]s?\\s+)?(?:[\\d.,]+|${SPELLED_COUNT})\\s*${DRUG_UNIT}${UNIT_END}`,
"i",
),
// A measured volume of a liquid drug form: "Tome 10 ml del jarabe".
// "Beba 500 ml de agua" passes.
new RegExp(
`${WORD_START}(?:tome|toma|tómese|tómate|administre)\\s+${ES_SHORT}[\\d.,]+\\s*${LIQUID_UNIT}${UNIT_END}\\s+(?:de\\s+la\\s+|de\\s+los\\s+|del\\s+|de\\s+)?${ES_SHORT}(?:${ES_LIQUID}|${med})`,
"i",
),
],
dose: [
// aumente / suba / pase a|en N unit
@@ -10,6 +10,9 @@ import {
medicationExtras,
unlessDeferred,
WORD_START,
DRUG_UNIT,
LIQUID_UNIT,
SPELLED_COUNT,
type LocaleBank,
} from "../shared";
@@ -26,6 +29,22 @@ const FR_SHORT = objectSlot(
const FR_MED_NOUN =
"(?:doses?|comprimés?|médicaments?|traitement|insuline|injections?|piqûres?|pilules?|gélules?|cachets?)";
/**
* The object of a negated intake. A protective qualifier ends it, so "ne
* prenez pas de double dose" and "ne prenez pas plus de 4 comprimés" stay
* warnings.
*/
const FR_HOLD_SLOT = objectSlot(
`${FR_STOP}|pour|avec|avant|après|sans|à|au|aux|double|plus|moitié|demi\\S*|deux|autres?|suppl[ée]mentaires?|en`,
3,
);
/** Where a negated intake reads as a hold: the clause closes or a time word follows. */
const FR_HOLD_END = `(?=\\s*(?:[.!;)]|$)|\\s+(?:ce\\s+soir|ce\\s+matin|aujourd|demain|cette\\s+semaine|pour\\s+l['’]instant|pour\\s+le\\s+moment|pendant|jusqu|avant)${WORD_END})`;
/** A liquid drug form a measured volume may be a dose of. */
const FR_LIQUID = `(?:sirops?|solutions?|suspensions?|gouttes|élixirs?|teintures?)${WORD_END}`;
const SPELLED_FR =
"(?:zéro|un|deux|trois|quatre|cinq|six|sept|huit|neuf|dix|onze|douze|treize|quatorze|quinze|seize|dix-sept|dix-huit|dix-neuf|vingt|trente|quarante|cinquante|soixante)";
@@ -99,6 +118,33 @@ export const bank: LocaleBank = {
"i",
),
),
// A negated intake is a hold: "Ne prenez pas votre atorvastatine ce
// soir", "ne prends pas ton métoprolol", "ne prenez plus le ramipril".
new RegExp(
`${WORD_START}(?:ne\\s+|n['’])(?:prenez|prends|injectez|injecte|utilisez|utilise)\\s+(?:pas|plus)\\s+${FR_HOLD_SLOT}${med}${FR_HOLD_END}`,
"i",
),
// A pause: "Faites une pause avec l'énalapril", "mettez le ramipril en
// pause". A pause from screens or after a meal names no medication.
new RegExp(
`${WORD_START}(?:faites|fais|prenez|prends|observez)\\s+(?:une\\s+)?(?:petite\\s+|courte\\s+)?(?:pause|interruption|fenêtre\\s+thérapeutique)\\s+(?:(?:avec|dans|de|du|des|sur)\\s+)?${FR_SHORT}${med}`,
"i",
),
new RegExp(
`${WORD_START}(?:mettez|mets)\\s+${FR_SHORT}${med}\\s+en\\s+pause`,
"i",
),
// An increment: "Ajoutez 2 unités à votre dose du soir".
new RegExp(
`${WORD_START}(?:ajoutez|ajoute|rajoutez|rajoute)\\s+${FR_SHORT}(?:encore\\s+)?(?:[\\d.,]+|${SPELLED_COUNT})\\s*${DRUG_UNIT}${UNIT_END}`,
"i",
),
// A measured volume of a liquid drug form: "Prenez 10 ml de sirop".
// "Buvez 500 ml d'eau" passes.
new RegExp(
`${WORD_START}(?:prenez|prends|administrez)\\s+${FR_SHORT}[\\d.,]+\\s*${LIQUID_UNIT}${UNIT_END}\\s+(?:d[eu]s?\\s+|d['’])?${FR_SHORT}(?:${ELIDED}${FR_LIQUID}|${med})`,
"i",
),
],
dose: [
// augmentez / montez / passez / portez à|de N unit
@@ -10,6 +10,9 @@ import {
medicationExtras,
unlessDeferred,
WORD_START,
LIQUID_UNIT,
DRUG_UNIT,
SPELLED_COUNT,
type LocaleBank,
} from "../shared";
@@ -23,6 +26,21 @@ const IT_SHORT = objectSlot(`${IT_STOP}|per|con|prima|dopo|senza|a|al`, 2);
const IT_MED_NOUN =
"(?:dos[ei]|compress[ae]|pastigli[ae]|farmac[oi]|medicinal[ei]|insulina|iniezion[ei]|capsul[ae]|terapia)";
/**
* The object of a negated intake. A protective qualifier ends it, so "non
* prenda una dose doppia" and "non prenda più di 4 compresse" stay warnings.
*/
const IT_HOLD_SLOT = objectSlot(
`${IT_STOP}|per|con|prima|dopo|senza|a|al|più|doppi[aoe]|mezz[ao]|metà|due|altr[ao]|extra|aggiuntiv\\S*|seconda`,
3,
);
/** Where a negated intake reads as a hold: the clause closes or a time word follows. */
const IT_HOLD_END = `(?=\\s*(?:[.!;)]|$)|\\s+(?:oggi|stasera|stamattina|domani|questa\\s+settimana|per\\s+ora|per\\s+il\\s+momento|per\\s+qualche|fino|prima)${WORD_END})`;
/** A liquid drug form a measured volume may be a dose of. */
const IT_LIQUID = `(?:sciropp[oi]|soluzion[ei]|sospension[ei]|gocce|elisir)${WORD_END}`;
const SPELLED_IT =
"(?:zero|uno|due|tre|quattro|cinque|sei|sette|otto|nove|dieci|undici|dodici|tredici|quattordici|quindici|sedici|diciassette|diciotto|diciannove|venti|trenta|quaranta|cinquanta|sessanta)";
@@ -91,6 +109,31 @@ export const bank: LocaleBank = {
"i",
),
),
// A negated intake is a hold: "Non prenda l'atorvastatina stasera", "non
// prendere il tuo metoprololo".
new RegExp(
`${WORD_START}non\\s+(?:prend(?:a|ere|ete|ano|i)|assum(?:a|ere|ete|i)|inietti|iniettare|usi|usare|utilizzi)${WORD_END}\\s+${IT_HOLD_SLOT}${med}${IT_HOLD_END}`,
"i",
),
// A pause: "Faccia una pausa dall'enalapril", "prenditi una pausa dal
// ramipril". A pause from screens names no medication.
new RegExp(
`${WORD_START}(?:faccia|fai|prenda|prendi|prenditi|si\\s+prenda)\\s+(?:una\\s+)?(?:[^\\s\\d.?!,;:]+\\s+)?pausa\\s+(?:(?:dal|dalla|dallo|da|con\\s+il|con\\s+la|con|col|nel|nella|sul|sulla)\\s+)?${IT_SHORT}${med}`,
"i",
),
// A measured volume of a liquid drug form: "Prenda 10 ml di sciroppo".
// "Beva 500 ml d'acqua" passes.
new RegExp(
`${WORD_START}(?:prenda|prendi|assuma|assumi)\\s+${IT_SHORT}[\\d.,]+\\s*${LIQUID_UNIT}${UNIT_END}\\s+(?:di\\s+|del(?:la|lo)?\\s+|dell['’])?${IT_SHORT}(?:${IT_LIQUID}|${med})`,
"i",
),
// An increment: "Aggiunga 2 unità alla dose della sera", "faccia 2 unità
// in più". The amount carries a drug unit, so "aggiunga 2 passeggiate"
// passes.
new RegExp(
`${WORD_START}(?:aggiunga|aggiungi|faccia|fai|si\\s+faccia|inietti|si\\s+inietti)\\s+(?:altr[ei]\\s+|ancora\\s+)?(?:[\\d.,]+|${SPELLED_COUNT})\\s*${DRUG_UNIT}${UNIT_END}`,
"i",
),
],
dose: [
// aumenti / salga / passi a|di N unit
@@ -6,6 +6,8 @@ import {
ENGINE,
medicationExtras,
unlessDeferred,
LIQUID_UNIT,
INN_STEM_HANGUL,
type LocaleBank,
} from "../shared";
@@ -48,8 +50,17 @@ const RISK_LEVEL_KO = "(?:높|상승|증가|중등도|경계|우려)";
const RISK_VERDICT_KO = `(?:은|는|이|가)\\s*[^.?!]{0,10}${RISK_LEVEL_KO}`;
const KO_MED_CLASS =
"(?:혈액\\s*희석제|항응고제|항혈소판제|스타틴|베타\\s*차단제|이뇨제|항우울제)";
const KO_MED_CLASS = `(?:혈액\\s*희석제|항응고제|항혈소판제|스타틴|베타\\s*차단제|이뇨제|항우울제|${INN_STEM_HANGUL})`;
/**
* A liquid drug form a measured volume may be a dose of. "물을 500ml 드세요"
* (drink 500 ml of water) names none.
*/
const KO_LIQUID = "(?:시럽|물약|현탁액|내복액|액제|용액)";
/** The polite intake imperative a liquid dose closes on. */
const KO_INTAKE =
"(?:드세요|드십시오|복용하세요|복용하십시오|드시고|먹으세요|드셔\\s*보세요)";
/** Signals that the number-free change shapes stand down for (`unlessDeferred`). */
const KO_DEFERRAL = `\\?|의사|약사|전문의|주치의|상의|라고|지\\s*마`;
@@ -84,6 +95,32 @@ export const bank: LocaleBank = {
`${med}[^.?!\\d]{0,8}?(?:(?:늘리|올리|높이|줄이|내리|낮추|증량하|감량하)(?:세요|십시오)|(?:늘려|올려|높여|줄여|내려|낮춰)\\s*(?:보세요|주세요|보십시오))`,
),
),
// A negated intake is a hold: "아토르바스타틴을 복용하지 마세요", "오늘은
// 약을 먹지 마세요". A protective qualifier between the medication and the
// verb, or in front of it ("두 배로", "한꺼번에", "더", "공복에", "함께"),
// keeps it a warning ("자몽 주스와 함께 약을 드시지 마세요"), and no
// digit may sit between, so "약 2주" ("about two weeks") is not a medication.
new RegExp(
`(?<!(?:함께|같이|동시에|공복에|한꺼번에|두\\s*배로?)\\s*)${med}\\s*(?:(?!두\\s*배|한꺼번에|동시에|추가|더\\s|이상|많이|반\\s*알|절반|함께|같이|공복)[^.?!\\d]){0,12}?(?:복용하지|드시지|먹지|맞지|투여하지|주사하지|사용하지)\\s*(?:마세요|마십시오|말아\\s*주세요|마(?![가-힣]))`,
),
// A pause: "에날라프릴 복용을 잠시 쉬세요", "라미프릴을 일주일 동안 쉬어
// 보세요". Resting the eyes from a screen names no medication.
new RegExp(
`${med}[^.?!\\d]{0,15}?(?:쉬세요|쉬십시오|쉬어\\s*(?:보세요|주세요)|휴약하세요|휴약해\\s*보세요)`,
),
// A measured volume of a liquid drug form, in either order: "시럽 10ml를
// 드세요", "10ml의 시럽을 드세요".
new RegExp(
`(?:${KO_LIQUID}[^.?!]{0,8}?[\\d.,]+\\s*${LIQUID_UNIT}${UNIT_END}|[\\d.,]+\\s*${LIQUID_UNIT}${UNIT_END}\\s*(?:의\\s*)?${KO_LIQUID})[^.?!]{0,10}?${KO_INTAKE}`,
"i",
),
// An increment closed by "add" or "put in": "저녁 인슐린에 2단위를
// 추가하세요", "2단위 더 놓으세요". "산책을 2번 더 하세요" carries no dose
// counter and passes.
new RegExp(
`${KO_TARGET}[^.?!]{0,12}?(?:(?:추가하|놓으|주입하)(?:세요|십시오)|(?:추가해|놓아)\\s*(?:보세요|주세요))`,
"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
@@ -11,6 +11,8 @@ import {
medicationExtras,
unlessDeferred,
WORD_END,
LIQUID_UNIT,
SPELLED_COUNT,
type LocaleBank,
} from "../shared";
@@ -26,6 +28,18 @@ const NL_SHORT = objectSlot(
const NL_MED_NOUN = `(?:[${LETTER}]*(?:dosis|doses|dosering|tablet(?:ten|je|jes)?|medicijn(?:en)?|medicatie|medicament(?:en)?|geneesmiddel(?:en)?|insuline|injecties?|capsules?)|pil(?:len|letje|letjes)?|prik(?:ken)?|spuitjes?|inhalator)(?![${LETTER}])`;
/**
* The object of a negated intake. A protective qualifier ends it, so "neem
* geen dubbele dosis" and "neem niet meer dan 4 tabletten" stay warnings.
*/
const NL_HOLD_SLOT = objectSlot(
`${NL_STOP}|om|voor|met|bij|in|op|van|naar|tot|na|zonder|tijdens|over|dan|te|meer|dubbele?|halve|helft|twee|extra|tweede|andere`,
3,
);
/** A liquid drug form a measured volume may be a dose of. */
const NL_LIQUID = `[${LETTER}]*(?:siroop|stroop|oplossing|suspensie|druppels|elixer)(?![${LETTER}])`;
const SPELLED_NL =
"(?:nul|een|één|twee|drie|vier|vijf|zes|zeven|acht|negen|tien|elf|twaalf|dertien|veertien|vijftien|zestien|zeventien|achttien|negentien|twintig|dertig|veertig|vijftig|zestig)";
@@ -141,6 +155,36 @@ export const bank: LocaleBank = {
"i",
),
),
// A negated intake is a hold: "Neem vandaag geen tabletten", "neem
// vanavond je atorvastatine niet in". A protective qualifier or an
// interaction ("geen tabletten met grapefruitsap") keeps it a warning.
new RegExp(
`${WORD_START}(?:neem|gebruik|spuit|injecteer)\\s+${objectSlot(NL_STOP, 3)}geen\\s+${NL_HOLD_SLOT}${med}(?![${LETTER}])(?!\\s+(?:dubbel|extra|tegelijk|samen|met|op|om|zonder|bij|tegen)${WORD_END})`,
"i",
),
new RegExp(
`${WORD_START}(?:neem|gebruik|spuit|injecteer)\\s+${NL_HOLD_SLOT}${med}\\s+(?:(?:vandaag|vanavond|vanochtend|morgen|voorlopig|even|deze\\s+week)\\s+){0,2}niet(?:\\s+meer)?(?:\\s+in)?(?=\\s*(?:[.!;),]|$))`,
"i",
),
// A pause: "Neem een pauze van enalapril", "las een pauze in met je
// ramipril". A pause from screens names no medication.
new RegExp(
`${WORD_START}(?:neem|las|houd)\\s+(?:even\\s+)?(?:een\\s+)?(?:[^\\s\\d.?!,;:]+\\s+)?pauze\\s+(?:in\\s+)?(?:van|met|bij)\\s+${NL_SHORT}${med}`,
"i",
),
// A measured volume of a liquid drug form: "Neem 10 ml van de siroop".
// "Drink 500 ml water" passes.
new RegExp(
`${WORD_START}(?:neem|gebruik)\\s+${NL_SHORT}[\\d.,]+\\s*${LIQUID_UNIT}${UNIT_END}\\s+(?:van\\s+)?${NL_SHORT}(?:${NL_LIQUID}|${med})`,
"i",
),
// An increment: "Voeg 2 eenheden toe aan je avonddosis", "doe er 2
// eenheden insuline bij". The amount carries a drug unit, so "voeg 2
// wandelingen toe" passes.
new RegExp(
`${WORD_START}(?:voeg|doe)\\s+(?:er\\s+)?(?:nog\\s+)?(?:[\\d.,]+|${SPELLED_COUNT})\\s*${DRUG_UNIT}${UNIT_END}`,
"i",
),
],
dose: [
// verhoog / ga omhoog / bouw op naar|tot|met N unit
@@ -9,6 +9,9 @@ import {
unlessDeferred,
WORD_START,
WORD_END,
LIQUID_UNIT,
DRUG_UNIT,
SPELLED_COUNT,
type LocaleBank,
} from "../shared";
@@ -22,6 +25,21 @@ const PL_SHORT = objectSlot(`${PL_STOP}|z|ze|do|na|po|przed|bez|dla`, 2);
const PL_MED_NOUN =
"(?:dawk[aęiy]|dawek|tabletk[aęiy]|tabletek|lek|leki|leku|leków|insulin[aęy]|zastrzyk[ia]?|zastrzyków|kapsułk[aęiy]|pigułk[aęiy])(?=[\\s.,;:!?]|$)";
/**
* The object of a negated intake. A protective qualifier ends it, so "nie
* bierz podwójnej dawki" and "nie bierz więcej niż 4 tabletki" stay warnings.
*/
const PL_HOLD_SLOT = objectSlot(
`${PL_STOP}|z|ze|do|na|po|przed|bez|dla|więcej|niż|podwójn\\S*|pół|połow\\S*|dwie|dwa|dodatkow\\S*|kolejn\\S*|drug\\S*`,
3,
);
/** Where a negated intake reads as a hold: the clause closes or a time word follows. */
const PL_HOLD_END = `(?=\\s*(?:[.!;)]|$)|\\s+(?:dziś|dzisiaj|jutro|wieczorem|rano|teraz|na\\s+razie|przez|przed|aż)${WORD_END})`;
/** A liquid drug form a measured volume may be a dose of. */
const PL_LIQUID = "(?:syrop|roztw[oó]r|zawiesin|kropl|eliksir)[a-ząćęłńóśźż]*";
const SPELLED_PL =
"(?:zero|jeden|dwa|trzy|cztery|pięć|sześć|siedem|osiem|dziewięć|dziesięć|jedenaście|dwanaście|trzynaście|czternaście|piętnaście|szesnaście|siedemnaście|osiemnaście|dziewiętnaście|dwadzieścia|trzydzieści|czterdzieści|pięćdziesiąt|sześćdziesiąt)";
@@ -91,6 +109,31 @@ export const bank: LocaleBank = {
"i",
),
),
// A negated intake is a hold: "Nie bierz dziś atorwastatyny", "proszę nie
// przyjmować metoprololu".
new RegExp(
`${WORD_START}(?:proszę\\s+|lepiej\\s+)?nie\\s+(?:bierz|bierzcie|brać|przyjmuj|przyjmujcie|przyjmować|zażywaj|zażywać|stosuj|stosować|wstrzykuj|wstrzykiwać)${WORD_END}\\s+${PL_HOLD_SLOT}${med}${PL_HOLD_END}`,
"i",
),
// A pause: "Zrób przerwę w przyjmowaniu enalaprylu", "zrób sobie przerwę
// od ramiprylu". A break from screens names no medication.
new RegExp(
`${WORD_START}(?:zrób|zróbcie|zrobić|proszę\\s+zrobić)\\s+(?:sobie\\s+)?(?:[^\\s\\d.?!,;:]+\\s+)?przerw[ęe]${WORD_END}\\s+(?:od|w|z)\\s+(?:(?:przyjmowani|brani|zażywani|stosowani)[a-ząćęłńóśźż]*\\s+)?${PL_SHORT}${med}`,
"i",
),
// A measured volume of a liquid drug form: "Weź 10 ml syropu". "Wypij
// 500 ml wody" passes.
new RegExp(
`${WORD_START}(?:weź|weźcie|przyjmij|przyjmuj|wypij|zażyj|proszę\\s+(?:przyjąć|wziąć|przyjmować|wypić))\\s+${PL_SHORT}(?:po\\s+)?[\\d.,]+\\s*${LIQUID_UNIT}${UNIT_END}\\s+${PL_SHORT}(?:${PL_LIQUID}|${med})`,
"i",
),
// An increment: "Dodaj 2 jednostki do wieczornej dawki", "podaj sobie 2
// jednostki więcej". The amount carries a drug unit, so "dodaj 2 spacery"
// passes.
new RegExp(
`${WORD_START}(?:dodaj|dodajcie|podaj(?:\\s+sobie)?|proszę\\s+(?:dodać|podać))\\s+(?:jeszcze\\s+|dodatkowo\\s+)?(?:[\\d.,]+|${SPELLED_COUNT})\\s*${DRUG_UNIT}${UNIT_END}`,
"i",
),
],
dose: [
// zwiększ / podnieś / przejdź do|o N unit
@@ -11,6 +11,8 @@ import {
unlessDeferred,
WORD_START,
WORD_END,
LIQUID_UNIT,
SPELLED_COUNT,
type LocaleBank,
} from "../shared";
@@ -27,6 +29,21 @@ const PT_SHORT = objectSlot(
const PT_MED_NOUN =
"(?:doses?|comprimidos?|pastilhas?|dr[áa]geas?|c[áa]psulas?|medicamentos?|rem[ée]dios?|medica[çc](?:[ãa]o|[õo]es)|insulina|inje[çc](?:[ãa]o|[õo]es)|tratamento)";
/**
* The object of a negated intake. A protective qualifier ends it, so "não
* tome dose dobrada" and "não tome mais de 4 comprimidos" stay warnings.
*/
const PT_HOLD_SLOT = objectSlot(
`${PT_STOP}|para|pra|com|antes|depois|sem|a|ao|à|em|por|durante|sobre|mais|dobrad[ao]s?|dupl[ao]|mei[ao]|metade|dois|duas|outr[ao]s?|extras?|adiciona\\S*|segunda`,
3,
);
/** Where a negated intake reads as a hold: the clause closes or a time word follows. */
const PT_HOLD_END = `(?=\\s*(?:[.!;)]|$)|\\s+(?:hoje|amanhã|esta\\s+noite|à\\s+noite|por\\s+(?:enquanto|agora|alguns\\s+dias)|durante|até|antes)${WORD_END})`;
/** A liquid drug form a measured volume may be a dose of. */
const PT_LIQUID = `(?:xaropes?|solu[çc][ãa]o|solu[çc][õo]es|suspens[ãa]o|gotas|elixir)${WORD_END}`;
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)";
@@ -123,6 +140,29 @@ export const bank: LocaleBank = {
"i",
),
),
// A negated intake is a hold: "Não tome atorvastatina hoje à noite",
// "não tome o seu metoprolol".
new RegExp(
`${WORD_START}n[ãa]o\\s+(?:tome|tomem|tomar|use|usem|aplique|apliquem|injete|injetem)${WORD_END}\\s+${PT_HOLD_SLOT}${med}${PT_HOLD_END}`,
"i",
),
// A pause: "Faça uma pausa no enalapril", "dê um tempo do ramipril". A
// pause from screens names no medication.
new RegExp(
`${WORD_START}(?:faça|faz|dê|dá|tire|tira)\\s+(?:uma|um)\\s+(?:[^\\s\\d.?!,;:]+\\s+)?(?:pausa|tempo|intervalo|descanso)\\s+(?:(?:n[oa]s?|d[oa]s?|com\\s+o|com\\s+a|com|de|em)\\s+)?${PT_SHORT}${med}`,
"i",
),
// An increment: "Adicione 4 unidades a mais de insulina".
new RegExp(
`${WORD_START}(?:adicione|acrescente|aplique|injete)\\s+(?:mais\\s+)?(?:[\\d.,]+|${SPELLED_COUNT})\\s*${DRUG_UNIT}${UNIT_END}`,
"i",
),
// A measured volume of a liquid drug form: "Tome 10 ml do xarope". "Beba
// 500 ml de água" passes.
new RegExp(
`${WORD_START}(?:tome|toma|use|administre)\\s+${PT_SHORT}[\\d.,]+\\s*${LIQUID_UNIT}${UNIT_END}\\s+(?:(?:d[oa]s?|de)\\s+)?${PT_SHORT}(?:${PT_LIQUID}|${med})`,
"i",
),
],
dose: [
// aumente / suba / passe a dose para|a|até|em N unit
@@ -6,6 +6,8 @@ import {
ENGINE,
medicationExtras,
unlessDeferred,
LIQUID_UNIT,
INN_STEM_CYRILLIC,
type LocaleBank,
} from "../shared";
@@ -48,8 +50,23 @@ const RISK_LEVEL_RU = "(?:высок|повышен|умеренн|низк|по
const RISK_VERDICT_RU = `(?:[—–-]|это|является|находится|означает)\\s*[^.?!]{0,10}${RISK_LEVEL_RU}`;
const RU_MED_CLASS =
"(?:антикоагулянт[а-яё]*|антиагрегант[а-яё]*|статины|статин[а-яё]*|бета[- ]блокатор[а-яё]*|диуретик[а-яё]*|антидепрессант[а-яё]*|метформин[а-яё]*|инсулин[а-яё]*|витамин[а-яё]*|кроворазжижающ[а-яё]*|кроверазжижающ[а-яё]*|рамиприл[а-яё]*|амлодипин[а-яё]*|лизиноприл[а-яё]*|эналаприл[а-яё]*|лозартан[а-яё]*|бисопролол[а-яё]*|аторвастатин[а-яё]*|розувастатин[а-яё]*|семаглутид[а-яё]*|оземпик[а-яё]*|апиксабан[а-яё]*|эликвис[а-яё]*|варфарин[а-яё]*|левотироксин[а-яё]*|эутирокс[а-яё]*|омепразол[а-яё]*|пантопразол[а-яё]*)";
const RU_MED_CLASS = `(?:антикоагулянт[а-яё]*|антиагрегант[а-яё]*|статины|статин[а-яё]*|бета[- ]блокатор[а-яё]*|диуретик[а-яё]*|антидепрессант[а-яё]*|метформин[а-яё]*|инсулин[а-яё]*|витамин[а-яё]*|кроворазжижающ[а-яё]*|кроверазжижающ[а-яё]*|рамиприл[а-яё]*|амлодипин[а-яё]*|лизиноприл[а-яё]*|эналаприл[а-яё]*|лозартан[а-яё]*|бисопролол[а-яё]*|аторвастатин[а-яё]*|розувастатин[а-яё]*|семаглутид[а-яё]*|оземпик[а-яё]*|апиксабан[а-яё]*|эликвис[а-яё]*|варфарин[а-яё]*|левотироксин[а-яё]*|эутирокс[а-яё]*|омепразол[а-яё]*|пантопразол[а-яё]*|${INN_STEM_CYRILLIC})`;
/**
* The object of a negated intake, up to three words. A protective qualifier
* or a preposition ends it, so «не принимайте двойную дозу», «не принимайте
* больше 4 таблеток» and «не принимайте таблетки вместе с …» stay warnings.
*/
const RU_HOLD_SLOT =
"(?:(?!(?:двойн[а-яё]*|половин[а-яё]*|больше|более|лишн[а-яё]*|дополнительн[а-яё]*|ещ[её]|сразу|одновременно|вместе|две|два|натощак|с|со|вместо|при|после|на|без|по|и|или)(?=\\s))[^\\s\\d.?!,;:]+\\s+){0,3}?";
/** Where a negated intake reads as a hold: the clause closes or a time word follows. */
const RU_HOLD_END =
"(?=\\s*(?:[.!;),]|$)|\\s+(?:сегодня|завтра|вечером|утром|пока|сейчас|перед|несколько|в\\s+течение|эти|до\\s+(?:утра|вечера|завтра|конца))(?![а-яё]))";
/** A liquid drug form a measured volume may be a dose of; water is not one. */
const RU_LIQUID =
"(?:сироп|раствор|суспензи|капел|капл|микстур|настойк|эликсир)[а-яё]*";
/** Signals that the number-free change shapes stand down for (`unlessDeferred`). */
const RU_DEFERRAL =
@@ -157,6 +174,32 @@ export const bank: LocaleBank = {
"i",
),
),
// A negated intake is a hold: «Не принимайте аторвастатин сегодня
// вечером», «Не пейте таблетки сегодня».
new RegExp(
`(?<![а-яё])не\\s+(?:принимайте|принимай|пейте|пей|колите|коли|вводите|используйте|применяйте|принимать|пить|колоть|вводить)(?![а-яё])\\s+${RU_HOLD_SLOT}${med}${RU_HOLD_END}`,
"i",
),
// A pause: «Сделайте перерыв в приёме эналаприла», «сделайте паузу с
// рамиприлом». A break from screens, or from meals («в приёме пищи»),
// names no medication.
new RegExp(
`(?<![а-яё])(?:сделайте|сделай|возьмите|возьми)\\s+(?:(?:небольш|коротк|временн)[а-яё]*\\s+)?(?:перерыв|паузу)(?![а-яё])[^.?!]{0,20}?${med}(?![^.?!]{0,3}(?:пищ|ед[ыуа]))`,
"i",
),
// An increment: «Добавьте ещё 4 единицы инсулина», «введите на 2 единицы
// больше». The amount carries a dose counter or unit, so «добавьте ещё 2
// прогулки» passes.
new RegExp(
`(?<![а-яё])(?:добавьте|добавь|введите|введи|вколите|уколите|прибавьте)(?![а-яё])\\s+(?:ещ[её]\\s+|дополнительно\\s+|на\\s+)?(?:[\\d.,]+|одну|одна|две|два|три|четыре|пять)\\s*(?:${RU_COUNT}|мг|мкг|ме)(?![а-яё])`,
"i",
),
// A measured volume of a liquid drug form: «Примите 10 мл сиропа».
// «Выпейте 500 мл воды» passes.
new RegExp(
`(?<![а-яё])(?:примите|принимайте|выпейте|пейте|прими|выпей)(?![а-яё])\\s+(?:по\\s+)?[\\d.,]+\\s*${LIQUID_UNIT}(?![а-яё\\w])\\s+(?:[^\\s\\d.?!,;:]+\\s+){0,2}?(?:${RU_LIQUID}|${med})`,
"i",
),
],
// A target dose: the unit sits on the number and a change verb or a
// recommending lead-in frames it. The permitted restatement ("вы на 7,5 мг
@@ -94,6 +94,26 @@ export const LETTER = "a-zà-ÿąćęłńóśźż";
*/
export const INN_STEM = `(?<![${LETTER}])[${LETTER}]{2,}(?:pril|pryl|sartan|olol|statin|statyn|dipin|dypin|formin|glutid|glutyd|gliflozin|gliptin|xaban|ksaban|prazol|semid)[${LETTER}]{0,4}(?![${LETTER}])|(?<![${LETTER}])(?:enoxa|dalte)?heparin[${LETTER}]{0,4}`;
/**
* The generic-name stems of `INN_STEM` as Russian and Korean write them
* ("метопролол", "аторвастатин", "메토프롤롤", "아토르바스타틴"). JavaScript's
* `\b` and `\w` are ASCII-only, so the edges are spelled out per script.
* The Korean "-xaban" stems carry their first syllable ("픽사반", "록사반") so
* a word like "조사반" (an inspection team) is not read as a drug.
*/
export const INN_STEM_CYRILLIC =
"(?<![а-яё])[а-яё]{2,}(?:прил|сартан|олол|статин|дипин|формин|глутид|глифлозин|глиптин|ксабан|празол|семид)[а-яё]{0,4}(?![а-яё])";
export const INN_STEM_HANGUL =
"[가-힣]*(?:프릴|사르탄|프롤롤|롤롤|스타틴|디핀|포르민|트포민|글루타이드|글루티드|플로진|글립틴|픽사반|록사반|독사반|프라졸|세미드)";
/**
* A liquid volume. Kept apart from `DRUG_UNIT` because "drink 500 ml of
* water" is hydration advice: a pattern may use it only beside a liquid drug
* form ("syrup", "Hustensaft", "сироп") or a medication noun.
*/
export const LIQUID_UNIT =
"(?:ml|мл|millilit(?:er|re)s?|milliliters?|mililitros?|millilitri|mililitr[a-zó]*|миллилитр[а-яё]*|밀리리터|cc)";
export const ENGINE = "(?:framingham|ascvd|score2|qrisk)";
/*