mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 04:50:59 +00:00
Merge branch 'release/v1.45.0' into fix/v145-review-data
This commit is contained in:
commit
c10446f0d7
15 files changed
+523
-111
No files matched your search
@@ -0,0 +1,195 @@
|
||||
/**
|
||||
* What the dose screen must let through: realistic, harmless Coach replies
|
||||
* in every language. Advice to talk to the doctor, negated warnings,
|
||||
* questions, conditions, a "dose" of caffeine, salt or exercise, and a
|
||||
* correct restatement of the person's own plan. The positive corpus sits in
|
||||
* `outbound-screen.test.ts` (`DOSE_CLASS_VIOLATIONS`); a pattern that widens
|
||||
* the class has to keep both green.
|
||||
*/
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import type { Locale } from "@/lib/i18n/config";
|
||||
|
||||
import {
|
||||
CONVERSATIONAL_CONTRACTS,
|
||||
screenModelOutput,
|
||||
} from "../outbound-screen";
|
||||
|
||||
const PLAN = [
|
||||
{ name: "Ramipril", dose: 5 },
|
||||
{ name: "Metformin", dose: 1000 },
|
||||
];
|
||||
|
||||
const HARMLESS: Record<Locale, readonly string[]> = {
|
||||
en: [
|
||||
"Talk to your doctor before you change your dose.",
|
||||
"Discuss with your doctor: reduce your dose?",
|
||||
"If your doctor agrees, then reduce your dose gradually.",
|
||||
"Don't reduce your dose for now.",
|
||||
"Never reduce your dose for a missed meal without guidance.",
|
||||
"Do not take half a tablet unless your pharmacist says it is scored.",
|
||||
"Increase your dose? Not without your doctor.",
|
||||
"Should you lower your insulin? That is a question for your diabetes team.",
|
||||
"Reduce your dose of caffeine in the afternoon.",
|
||||
"Cut your salt a little this week.",
|
||||
"Increase your activity gradually.",
|
||||
"Your doctor said to reduce your dose for a week.",
|
||||
"You take metformin 1000 mg twice daily, as your plan says.",
|
||||
"Keep taking ramipril 5 mg as before.",
|
||||
"Reduce your sugar intake by half.",
|
||||
"Ask your pharmacist whether the tablet can be split in half.",
|
||||
],
|
||||
de: [
|
||||
"Sprich mit deiner Ärztin, bevor du die Dosis änderst.",
|
||||
"Erhöhe dein Insulin heute nicht, auch wenn der Wert hoch ist.",
|
||||
"Reduzieren Sie die Dosis jetzt nicht eigenmächtig.",
|
||||
"Senke die Dosis bis zum Arzttermin nicht.",
|
||||
"Nimm die halbe Dosis nicht ohne Rücksprache.",
|
||||
"Teile die Tablette in zwei Hälften nur, wenn deine Apotheke zustimmt.",
|
||||
"Wenn deine Ärztin zustimmt, reduziere die Dosis schrittweise.",
|
||||
"Soll ich die Dosis erhöhen? Das klärst du mit deiner Praxis.",
|
||||
"Reduziere den Zucker um die Hälfte.",
|
||||
"Reduziere deinen Salzkonsum.",
|
||||
"Steigere deine Aktivität langsam.",
|
||||
"Frag deine Ärztin, ob du die halbe Dosis nehmen kannst.",
|
||||
"Du nimmst Metformin 1000 mg zweimal täglich, wie geplant.",
|
||||
"Nimm Ramipril 5 mg wie bisher weiter.",
|
||||
"Erhöhe die Dosis also für ein paar Tage nicht.",
|
||||
],
|
||||
fr: [
|
||||
"Parlez à votre médecin avant de changer votre dose.",
|
||||
"Ne réduisez pas votre dose maintenant.",
|
||||
"Ne prenez pas une demi-dose sans avis.",
|
||||
"Faut-il diminuer votre dose ? Demandez à votre médecin.",
|
||||
"Si votre médecin le juge utile, il peut diminuer votre dose.",
|
||||
"Réduisez le sel de moitié.",
|
||||
"Augmentez votre activité progressivement.",
|
||||
"Diminuez votre café l’après-midi.",
|
||||
"Vous prenez metformine 1000 mg deux fois par jour.",
|
||||
"Demandez à votre pharmacien si le comprimé peut être coupé en deux.",
|
||||
"Il est préférable que vous ne diminuiez pas votre dose maintenant.",
|
||||
"Avant de diminuer votre dose, parlez à votre médecin.",
|
||||
"Prenez une demi-heure pour marcher.",
|
||||
"Augmentez votre consommation d’eau.",
|
||||
"N’augmentez pas votre insuline sans votre médecin.",
|
||||
],
|
||||
es: [
|
||||
"Hable con su médico antes de que reduzca la dosis.",
|
||||
"Consulte si conviene que reduzca la dosis.",
|
||||
"Espere a su cita antes de que aumente la insulina.",
|
||||
"Cuando su médico lo indique, reduzca la dosis.",
|
||||
"No reduzca la dosis hoy.",
|
||||
"¿Debería reducir la dosis? Pregunte a su médico.",
|
||||
"Es importante que no reduzca la dosis hoy.",
|
||||
"Reduzca el azúcar a la mitad.",
|
||||
"Aumente su actividad poco a poco.",
|
||||
"Tome media hora para caminar.",
|
||||
"Usted toma metformina 1000 mg dos veces al día.",
|
||||
"Pregunte a su farmacéutico si puede partir la pastilla.",
|
||||
"Le recomiendo que no aumente la dosis ahora.",
|
||||
"Antes de que baje la dosis, consulte a su médico.",
|
||||
"Reduzca el café por la tarde.",
|
||||
],
|
||||
it: [
|
||||
"Parli con il medico prima di cambiare la dose.",
|
||||
"Non riduca la dose ora.",
|
||||
"Se il medico decide, riduca la dose per gradi.",
|
||||
"Chieda al medico se può prendere mezza dose.",
|
||||
"Ridurre la dose? Solo se il medico lo consiglia.",
|
||||
"Prima che riduca la dose, ne parli con il medico.",
|
||||
"È importante che lei non riduca la dose ora.",
|
||||
"Riduca lo zucchero della metà.",
|
||||
"Aumenti l'attività gradualmente.",
|
||||
"Prenda mezz'ora per camminare.",
|
||||
"Lei prende metformina 1000 mg due volte al giorno.",
|
||||
"Riduca il caffè nel pomeriggio.",
|
||||
"Chieda al farmacista se la compressa si può dividere.",
|
||||
"Non aumenti l'insulina senza il medico.",
|
||||
"Aumenti l'acqua durante il giorno.",
|
||||
],
|
||||
pl: [
|
||||
"Porozmawiaj z lekarzem, zanim zmienisz dawkę.",
|
||||
"Nie zwiększaj dawki teraz.",
|
||||
"Jeśli lekarz zaleci, zmniejsz dawkę stopniowo.",
|
||||
"Lekarz może zdecydować: zmniejsz dawkę leku.",
|
||||
"Zapytaj lekarza, czy możesz przyjmować pół dawki.",
|
||||
"Zmniejszyć dawkę? Tylko jeśli lekarz tak zdecyduje.",
|
||||
"Zmniejsz dawkę kofeiny.",
|
||||
"Zmniejsz dawkę cukru.",
|
||||
"Zwiększ dawkę ruchu.",
|
||||
"Zmniejsz spożycie soli o połowę.",
|
||||
"Zwiększ aktywność stopniowo.",
|
||||
"Przyjmujesz metforminę 1000 mg dwa razy dziennie.",
|
||||
"Odstaw telefon przed snem.",
|
||||
"Nie zmniejszaj dawki insuliny bez lekarza.",
|
||||
"Zapytaj farmaceutę, czy tabletkę można podzielić na pół.",
|
||||
],
|
||||
nl: [
|
||||
"Overleg met je arts voordat je je dosis aanpast.",
|
||||
"Verlaag je dosis nu niet.",
|
||||
"Verhoog je insuline vandaag niet zelf.",
|
||||
"Verlaag je dosis vanaf morgen niet zonder overleg.",
|
||||
"Vraag je arts: verlaag je dosis?",
|
||||
"Vraag je arts of je een halve dosis mag nemen.",
|
||||
"Verlaag je dosis alleen als je arts dat zegt.",
|
||||
"Verlaag je suiker met de helft.",
|
||||
"Verhoog je activiteit geleidelijk.",
|
||||
"Neem een half uur de tijd.",
|
||||
"Je neemt metformine 1000 mg twee keer per dag.",
|
||||
"Vraag je apotheker of de tablet doormidden mag.",
|
||||
"Verminder je koffie in de middag.",
|
||||
"Verdubbel je stappen twee weken lang.",
|
||||
"Neem geen halve dosis zonder overleg.",
|
||||
],
|
||||
"pt-BR": [
|
||||
"Converse com seu médico antes de mudar a dose.",
|
||||
"Não reduza a dose agora.",
|
||||
"É importante que você não reduza a dose agora.",
|
||||
"Antes que você reduza a dose, fale com seu médico.",
|
||||
"Pergunte ao seu médico se você pode tomar meia dose.",
|
||||
"Reduzir a dose? Só se o médico indicar.",
|
||||
"Reduza o açúcar pela metade.",
|
||||
"Aumente sua atividade aos poucos.",
|
||||
"Tome meia hora para caminhar.",
|
||||
"Você toma metformina 1000 mg duas vezes ao dia.",
|
||||
"Corte o pão ao meio.",
|
||||
"Reduza o café à tarde.",
|
||||
"Pergunte ao farmacêutico se o comprimido pode ser partido.",
|
||||
"Não aumente a insulina sem o médico.",
|
||||
"Aumente a ingestão de água.",
|
||||
],
|
||||
ko: [
|
||||
"의사와 상의 후 복용량을 늘리세요.",
|
||||
"복용량을 늘리세요라고 말씀드릴 수는 없어요.",
|
||||
"의사와 상의하지 않고 복용량을 줄이지 마세요.",
|
||||
"약 복용량을 바꾸고 싶으시면 먼저 의사와 상의하세요.",
|
||||
"용량을 줄이지 마세요.",
|
||||
"복용량을 늘려야 할까요? 주치의와 이야기해 보세요.",
|
||||
"걸음 수를 늘리세요.",
|
||||
"물 섭취를 늘리세요.",
|
||||
"약 2주 동안 걷는 시간을 늘리세요.",
|
||||
"커피를 줄이세요.",
|
||||
"하루 두 번 1000mg을 복용하고 계세요.",
|
||||
"약사에게 알약을 반으로 나눠도 되는지 물어보세요.",
|
||||
"설탕 섭취를 절반으로 줄이세요.",
|
||||
"운동량을 천천히 늘리세요.",
|
||||
"담배를 끊으세요.",
|
||||
],
|
||||
};
|
||||
|
||||
describe("dose screen false-positive corpus", () => {
|
||||
for (const [locale, sentences] of Object.entries(HARMLESS) as Array<
|
||||
[Locale, readonly string[]]
|
||||
>) {
|
||||
it(`${locale} carries at least 15 harmless replies`, () => {
|
||||
expect(sentences.length).toBeGreaterThanOrEqual(15);
|
||||
});
|
||||
it.each(sentences)(`passes in ${locale}: %s`, (text) => {
|
||||
const d = screenModelOutput(text, locale, CONVERSATIONAL_CONTRACTS, {
|
||||
dosePlan: PLAN,
|
||||
medicationNames: PLAN.map((entry) => entry.name),
|
||||
});
|
||||
expect(d.block).toBe(false);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -18,7 +18,7 @@ describe("screenModelOutput — dose exemption gated on the plan", () => {
|
||||
|
||||
it("passes a continuation dose that MATCHES the user's plan", () => {
|
||||
const d = screenModelOutput(
|
||||
"You should keep taking your prescribed 7.5 mg exactly as directed.",
|
||||
"You should keep taking your prescribed semaglutide 7.5 mg exactly as directed.",
|
||||
"en",
|
||||
CONVERSATIONAL_CONTRACTS,
|
||||
{ dosePlan: PLAN },
|
||||
@@ -30,7 +30,7 @@ describe("screenModelOutput — dose exemption gated on the plan", () => {
|
||||
// The plan says 7.5; "keep taking your 15 mg" is a wrong maintenance
|
||||
// dose and must not ride the continuation exemption.
|
||||
const d = screenModelOutput(
|
||||
"You should keep taking your 15 mg as prescribed.",
|
||||
"You should keep taking your semaglutide 15 mg as prescribed.",
|
||||
"en",
|
||||
CONVERSATIONAL_CONTRACTS,
|
||||
{ dosePlan: PLAN },
|
||||
@@ -61,12 +61,12 @@ describe("screenModelOutput — dose exemption gated on the plan", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("passes the same sentences when the plan holds that dose", () => {
|
||||
it("passes a named continuation when the plan holds that dose for it", () => {
|
||||
for (const [text, locale] of [
|
||||
["Take 40 mg as before.", "en"],
|
||||
["Tome 40 mg como antes.", "pt-BR"],
|
||||
["Nimm 40 mg wie bisher.", "de"],
|
||||
["Neem 40 mg zoals eerder.", "nl"],
|
||||
["Take atorvastatin 40 mg as before.", "en"],
|
||||
["Tome atorvastatina 40 mg como antes.", "pt-BR"],
|
||||
["Nimm Atorvastatin 40 mg wie bisher.", "de"],
|
||||
["Neem atorvastatine 40 mg zoals eerder.", "nl"],
|
||||
] as const) {
|
||||
expect(
|
||||
screenModelOutput(text, locale, CONVERSATIONAL_CONTRACTS, {
|
||||
@@ -77,6 +77,43 @@ describe("screenModelOutput — dose exemption gated on the plan", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("blocks an amount with no medication named beside it, even on the plan", () => {
|
||||
for (const text of [
|
||||
"Take 40 mg as before.",
|
||||
"You should keep taking your prescribed 7.5 mg exactly as directed.",
|
||||
]) {
|
||||
expect(
|
||||
screenModelOutput(text, "en", CONVERSATIONAL_CONTRACTS, {
|
||||
dosePlan: PLAN,
|
||||
}).block,
|
||||
text,
|
||||
).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it("blocks a brand the plan does not carry, and swapped doses", () => {
|
||||
for (const text of [
|
||||
"Take Eliquis 5 mg as before.",
|
||||
"Take Xarelto 40 mg as before.",
|
||||
"Take ramipril 40 mg and atorvastatin 5 mg as before.",
|
||||
]) {
|
||||
expect(
|
||||
screenModelOutput(text, "en", CONVERSATIONAL_CONTRACTS, {
|
||||
dosePlan: PLAN,
|
||||
}).block,
|
||||
text,
|
||||
).toBe(true);
|
||||
}
|
||||
expect(
|
||||
screenModelOutput(
|
||||
"Take ramipril 5 mg and atorvastatin 40 mg as before.",
|
||||
"en",
|
||||
CONVERSATIONAL_CONTRACTS,
|
||||
{ dosePlan: PLAN },
|
||||
).block,
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("holds a named medication to its own dose, not to any dose on the plan", () => {
|
||||
// 40 mg is on the plan, but for atorvastatin, not ramipril.
|
||||
expect(
|
||||
@@ -137,7 +174,7 @@ describe("screenModelOutput — dose exemption gated on the plan", () => {
|
||||
|
||||
it("matches a comma-decimal planned dose (DE reader)", () => {
|
||||
const d = screenModelOutput(
|
||||
"Nimm weiterhin deine 7,5 mg wie verordnet.",
|
||||
"Nimm weiterhin dein Semaglutid 7,5 mg wie verordnet.",
|
||||
"de",
|
||||
CONVERSATIONAL_CONTRACTS,
|
||||
{ dosePlan: PLAN },
|
||||
|
||||
@@ -290,7 +290,7 @@ describe("screenModelOutput — dose continuation-exclusion (D7)", () => {
|
||||
|
||||
it("PASSES an adherence-supporting continuation of the planned dose", () => {
|
||||
const d = screenModelOutput(
|
||||
"You should keep taking your prescribed 7.5 mg exactly as directed.",
|
||||
"You should keep taking your prescribed semaglutide 7.5 mg exactly as directed.",
|
||||
"en",
|
||||
CONVERSATIONAL_CONTRACTS,
|
||||
{ dosePlan: PLAN },
|
||||
@@ -343,7 +343,7 @@ describe("screenModelOutput — dose continuation-exclusion (D7)", () => {
|
||||
// "du solltest … 7,5 mg" trips the DE consider/should dose pattern; the
|
||||
// "weiterhin … wie verordnet" maintenance anchor exempts it.
|
||||
const d = screenModelOutput(
|
||||
"Du solltest weiterhin deine 7,5 mg wie verordnet einnehmen.",
|
||||
"Du solltest weiterhin dein Semaglutid 7,5 mg wie verordnet einnehmen.",
|
||||
"de",
|
||||
CONVERSATIONAL_CONTRACTS,
|
||||
{ dosePlan: PLAN },
|
||||
@@ -796,7 +796,7 @@ describe("screenModelOutput — the dose-change class, every locale", () => {
|
||||
// On schedule: the maintenance anchor exempts it.
|
||||
expect(
|
||||
screenModelOutput(
|
||||
"Take your 1000 mg as usual.",
|
||||
"Take your metformin 1000 mg as usual.",
|
||||
"en",
|
||||
CONVERSATIONAL_CONTRACTS,
|
||||
{ dosePlan: [{ name: "Metformin", dose: 1000 }] },
|
||||
@@ -805,7 +805,7 @@ describe("screenModelOutput — the dose-change class, every locale", () => {
|
||||
// Off schedule: the same phrasing names a dose the user is not on.
|
||||
expect(
|
||||
screenModelOutput(
|
||||
"Take your 2000 mg as usual.",
|
||||
"Take your metformin 2000 mg as usual.",
|
||||
"en",
|
||||
CONVERSATIONAL_CONTRACTS,
|
||||
{ dosePlan: [{ name: "Metformin", dose: 1000 }] },
|
||||
|
||||
@@ -315,8 +315,10 @@ export const INSIGHTS_CONTRACTS: readonly OutboundContract[] = [
|
||||
* Continuation-exclusion for the dose bank (v1.32.7 — D7). A sentence that
|
||||
* matches a dose pattern is EXEMPT only when it is anchored to a maintenance
|
||||
* object ("keep taking … as prescribed"), carries no change stem, AND every
|
||||
* amount it states is one the person is on for the medication it names. So
|
||||
* "keep taking your prescribed 7.5 mg" passes for someone on 7.5 mg, while
|
||||
* amount it states sits beside one of the person's medications, named, at
|
||||
* exactly that planned dose. So "keep taking your semaglutide 7.5 mg as
|
||||
* prescribed" passes for someone on semaglutide 7.5 mg, an unnamed "keep
|
||||
* taking your 7.5 mg" does not, while
|
||||
* "you should keep in mind that trying 5 mg" (no maintenance anchor) and
|
||||
* "you should continue tapering to 2.4 mg" (a change stem present) stay
|
||||
* blocked. The direction is exclusion-of-continuation, never
|
||||
@@ -364,26 +366,53 @@ function sameDose(value: number, planned: number): boolean {
|
||||
const INN_STEM_RE = new RegExp(INN_STEM, "i");
|
||||
|
||||
/**
|
||||
* True when every amount a continuation sentence states is on the person's
|
||||
* plan for the medication it names. A sentence naming one of their
|
||||
* medications is held to that medication's doses; one naming a drug outside
|
||||
* the plan is never trusted; one naming none is held to the whole plan.
|
||||
* Where one stated amount ends and the next begins: a comma before a space
|
||||
* (a decimal comma, "7,5", is not one), a semicolon, "and" / "or".
|
||||
*/
|
||||
const CLAUSE_SPLIT =
|
||||
/,(?=\s)|;|\s(?:and|und|en|et|y|e|i|oraz|plus|sowie|ou|o|or|oder)\s|그리고|및/i;
|
||||
|
||||
/**
|
||||
* True when every amount a continuation sentence states is paired with one
|
||||
* of the person's medications, named beside it, at exactly that dose. Each
|
||||
* clause that states an amount must name exactly one medication on the plan
|
||||
* (as it is written on the plan, the only alias the screen can know), and
|
||||
* every amount in it must be one of that medication's planned doses. An
|
||||
* amount with no name beside it, a name the plan does not carry (a brand
|
||||
* like "Eliquis" is simply not a plan name; a generic stem like "apixaban"
|
||||
* is recognised and refused), or a dose that belongs to another medication
|
||||
* on the plan ("ramipril 500 mg and metformin 5 mg") is never trusted.
|
||||
*/
|
||||
function continuationMatchesPlan(
|
||||
sentence: string,
|
||||
plan: readonly ScheduledDose[] | undefined,
|
||||
): boolean {
|
||||
const values = sentenceDoseValues(sentence);
|
||||
if (values.length === 0 || !plan || plan.length === 0) return false;
|
||||
const named = plan.filter((entry) => {
|
||||
if (!plan || plan.length === 0) return false;
|
||||
const matchers = plan.map((entry) => {
|
||||
const alternation = medicationNameAlternation([entry.name]);
|
||||
return alternation !== null && new RegExp(alternation, "i").test(sentence);
|
||||
return alternation === null ? null : new RegExp(alternation, "gi");
|
||||
});
|
||||
if (named.length === 0 && INN_STEM_RE.test(sentence)) return false;
|
||||
const pool = named.length > 0 ? named : plan;
|
||||
return values.every((value) =>
|
||||
pool.some((entry) => sameDose(value, entry.dose)),
|
||||
);
|
||||
let stated = 0;
|
||||
for (const clause of sentence.split(CLAUSE_SPLIT)) {
|
||||
const values = sentenceDoseValues(clause);
|
||||
if (values.length === 0) continue;
|
||||
stated += values.length;
|
||||
const named = plan.filter((_, i) => matchers[i]?.test(clause) ?? false);
|
||||
matchers.forEach((m) => m && (m.lastIndex = 0));
|
||||
const names = new Set(named.map((entry) => entry.name.toLowerCase()));
|
||||
if (names.size !== 1) return false;
|
||||
// A generic name left once the plan's own names are taken out is a drug
|
||||
// the plan does not carry.
|
||||
const rest = matchers.reduce(
|
||||
(text, m) => (m ? text.replace(m, " ") : text),
|
||||
clause,
|
||||
);
|
||||
if (INN_STEM_RE.test(rest)) return false;
|
||||
if (!values.every((v) => named.some((e) => sameDose(v, e.dose)))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return stated > 0;
|
||||
}
|
||||
|
||||
/** True when a dose-change imperative trips, honouring the continuation rule. */
|
||||
@@ -427,9 +456,10 @@ function doseTrips(
|
||||
export interface ScreenOptions {
|
||||
/**
|
||||
* The person's active medications with their dose magnitudes. A "keep
|
||||
* taking your N mg" is trusted only when N is on this plan for the
|
||||
* medication the sentence names; without the plan, a stated amount is never
|
||||
* exempt.
|
||||
* taking your X N mg" is trusted only when X is a medication on this plan,
|
||||
* named beside the amount, planned at exactly N; an unnamed amount, a name
|
||||
* the plan does not carry, or a dose of another medication is never
|
||||
* exempt, and without the plan no stated amount is.
|
||||
*/
|
||||
dosePlan?: readonly ScheduledDose[];
|
||||
/**
|
||||
|
||||
@@ -7,6 +7,9 @@ import {
|
||||
LETTER,
|
||||
ENGINE,
|
||||
medicationExtras,
|
||||
unlessDeferred,
|
||||
WORD_START,
|
||||
WORD_END,
|
||||
type LocaleBank,
|
||||
} from "../shared";
|
||||
|
||||
@@ -59,6 +62,9 @@ const RISK_VERDICT_DE = `(?:ist|liegt|erscheint|wirkt|bleibt|zeigt|deutet\\s+auf
|
||||
const DE_MED_CLASS =
|
||||
"\\S*(?:blutverdünner\\w*|gerinnungshemmer\\w*|antikoagul\\w*|statin\\w*|betablocker\\w*|diuretik\\w*|wassertablette\\w*|blutdrucksenker\\w*|antidepressiv\\w*|schlaftablette\\w*)";
|
||||
|
||||
/** Signals that the number-free change shapes stand down for (`unlessDeferred`). */
|
||||
const DE_DEFERRAL = `\\?|${WORD_START}(?:nicht|kein\\w*|nie|niemals|nur\\s*,?\\s*(?:wenn|falls|nach)|erst\\s*,?\\s*(?:wenn|nach)|ohne|frag\\w*|besprich\\w*|sprich\\s+mit|rücksprache|ärzt\\w*|arzt\\w*|apothek\\w*|hausärzt\\w*|diabetolog\\w*|fachperson\\w*|praxis)${WORD_END}`;
|
||||
|
||||
export const bank: LocaleBank = {
|
||||
vocabulary: (names) =>
|
||||
`(?:${DE_MED_NOUN}|${medicationExtras(DE_MED_CLASS, names)})`,
|
||||
@@ -109,24 +115,36 @@ export const bank: LocaleBank = {
|
||||
// Hälfte", "nehmen Sie nur die halbe Dosis", "teilen Sie die Tablette in
|
||||
// zwei Hälften". "Teilen" alone also means "share", so the split needs
|
||||
// its "in Hälften".
|
||||
new RegExp(
|
||||
`\\b(?:reduzier(?:e|en)?|verringer(?:e|n)?|senk(?:e|en)?|kürz(?:e|en)?)(?:\\s+sie)?\\s+${DE_SLOT}${med}\\s+um\\s+(?:die\\s+)?hälfte`,
|
||||
"i",
|
||||
unlessDeferred(
|
||||
DE_DEFERRAL,
|
||||
new RegExp(
|
||||
`\\b(?:reduzier(?:e|en)?|verringer(?:e|n)?|senk(?:e|en)?|kürz(?:e|en)?)(?:\\s+sie)?\\s+${DE_SLOT}${med}\\s+um\\s+(?:die\\s+)?hälfte`,
|
||||
"i",
|
||||
),
|
||||
),
|
||||
new RegExp(
|
||||
`\\b(?:nimm|nehmen\\s+sie|spritz|spritzen\\s+sie|injizier(?:e)?|injizieren\\s+sie)\\s+(?:nur\\s+)?(?:die\\s+|eine\\s+)?(?:halbe|hälfte\\s+(?:der|deiner|ihrer))\\s+${DE_SHORT}${med}`,
|
||||
"i",
|
||||
unlessDeferred(
|
||||
DE_DEFERRAL,
|
||||
new RegExp(
|
||||
`\\b(?:nimm|nehmen\\s+sie|spritz|spritzen\\s+sie|injizier(?:e)?|injizieren\\s+sie)\\s+(?:nur\\s+)?(?:die\\s+|eine\\s+)?(?:halbe|hälfte\\s+(?:der|deiner|ihrer))\\s+${DE_SHORT}${med}`,
|
||||
"i",
|
||||
),
|
||||
),
|
||||
new RegExp(
|
||||
`\\bteil(?:e|en)?(?:\\s+sie)?\\s+${DE_SLOT}${med}\\s+(?:in\\s+(?:zwei\\s+)?hälften|in\\s+der\\s+mitte|durch\\s+zwei)`,
|
||||
"i",
|
||||
unlessDeferred(
|
||||
DE_DEFERRAL,
|
||||
new RegExp(
|
||||
`\\bteil(?:e|en)?(?:\\s+sie)?\\s+${DE_SLOT}${med}\\s+(?:in\\s+(?:zwei\\s+)?hälften|in\\s+der\\s+mitte|durch\\s+zwei)`,
|
||||
"i",
|
||||
),
|
||||
),
|
||||
// A bare change imperative on a medication, no target: "reduzieren Sie
|
||||
// die Dosis.", "erhöhe dein Insulin etwas". The noun closes the clause or
|
||||
// meets a time or degree word; "reduziere die Dosis nicht" does neither.
|
||||
new RegExp(
|
||||
`\\b(?:erhöh(?:e|en)?|steiger(?:e|n)?|reduzier(?:e|en)?|senk(?:e|en)?|verringer(?:e|n)?)(?:\\s+sie)?\\s+${DE_SHORT}${med}(?=\\s*(?:[.!?;:)]|$)|\\s+(?:jetzt|heute|morgen|etwas|leicht|langsam|schrittweise|ab\\s+sofort|für|bis)\\b)`,
|
||||
"i",
|
||||
unlessDeferred(
|
||||
DE_DEFERRAL,
|
||||
new RegExp(
|
||||
`\\b(?:erhöh(?:e|en)?|steiger(?:e|n)?|reduzier(?:e|en)?|senk(?:e|en)?|verringer(?:e|n)?)(?:\\s+sie)?\\s+${DE_SHORT}${med}(?=\\s*(?:[.!?;:)]|$)|\\s+(?:jetzt|heute|morgen|etwas|leicht|langsam|schrittweise|ab\\s+sofort|für|bis)\\b)`,
|
||||
"i",
|
||||
),
|
||||
),
|
||||
],
|
||||
dose: [
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
TARGET,
|
||||
ENGINE,
|
||||
medicationExtras,
|
||||
unlessDeferred,
|
||||
type LocaleBank,
|
||||
} from "../shared";
|
||||
|
||||
@@ -67,6 +68,9 @@ const RISK_VERDICT_EN = `(?:is|are|looks?|appears?|seems?|sits?|remains?|comes?\
|
||||
const EN_MED_CLASS =
|
||||
"(?:blood[- ]?thinners?|anti-?coagulants?|anti-?platelets?|statins?|beta[- ]?blockers?|diuretics?|water\\s+pills?|ace\\s+inhibitors?|steroids?|antidepressants?|sleeping\\s+pills?)\\b";
|
||||
|
||||
/** Signals that the number-free change shapes stand down for (`unlessDeferred`). */
|
||||
const EN_DEFERRAL = `\\?|\\b(?:not|never|don'?t|doesn'?t|no|unless|only\\s+(?:if|when|after)|without|ask|discuss|talk\\s+to|check\\s+with|doctor|physician|gp|pharmacist|prescriber|clinician|nurse|specialist|care\\s+team|diabetes\\s+team)\\b`;
|
||||
|
||||
export const bank: LocaleBank = {
|
||||
vocabulary: (names) =>
|
||||
`(?:${EN_MED_NOUN}|${medicationExtras(EN_MED_CLASS, names)})`,
|
||||
@@ -117,21 +121,30 @@ export const bank: LocaleBank = {
|
||||
),
|
||||
// Halving or a fraction with no number: "reduce your dose by half",
|
||||
// "take half a dose". A half hour or half your sugar names no medication.
|
||||
new RegExp(
|
||||
`${EN_LEAD}(?:reduce|cut|lower|decrease|drop|split|break|divide)\\s+${EN_SHORT}${med}\\s+(?:by|in)\\s+(?:a\\s+)?half\\b`,
|
||||
"im",
|
||||
unlessDeferred(
|
||||
EN_DEFERRAL,
|
||||
new RegExp(
|
||||
`${EN_LEAD}(?:reduce|cut|lower|decrease|drop|split|break|divide)\\s+${EN_SHORT}${med}\\s+(?:by|in)\\s+(?:a\\s+)?half\\b`,
|
||||
"im",
|
||||
),
|
||||
),
|
||||
new RegExp(
|
||||
`${EN_LEAD}(?:take|use|inject|give\\s+yourself)\\s+(?:only\\s+|just\\s+)?half\\s+(?:of\\s+)?(?:(?:a|an|the|your)\\s+)?${EN_SHORT}${med}`,
|
||||
"im",
|
||||
unlessDeferred(
|
||||
EN_DEFERRAL,
|
||||
new RegExp(
|
||||
`${EN_LEAD}(?:take|use|inject|give\\s+yourself)\\s+(?:only\\s+|just\\s+)?half\\s+(?:of\\s+)?(?:(?:a|an|the|your)\\s+)?${EN_SHORT}${med}`,
|
||||
"im",
|
||||
),
|
||||
),
|
||||
// A bare change imperative on a medication, no target: "reduce your
|
||||
// dose.", "increase your insulin slightly". The medication noun has to
|
||||
// close the clause or meet a time or degree word, so "reduce your dose of
|
||||
// stress" and "cut your medication costs" are not read as one.
|
||||
new RegExp(
|
||||
`${EN_LEAD}(?:increase|raise|up|boost|bump|reduce|lower|decrease|cut|drop)\\s+${EN_SHORT}${med}(?=\\s*(?:[.!?;:)]|$)|\\s+(?:now|today|tonight|tomorrow|a\\s+(?:bit|little)|slightly|gradually|somewhat|this\\s+week|for|until|from\\s+now)\\b)`,
|
||||
"im",
|
||||
unlessDeferred(
|
||||
EN_DEFERRAL,
|
||||
new RegExp(
|
||||
`${EN_LEAD}(?:increase|raise|up|boost|bump|reduce|lower|decrease|cut|drop)\\s+${EN_SHORT}${med}(?=\\s*(?:[.!?;:)]|$)|\\s+(?:now|today|tonight|tomorrow|a\\s+(?:bit|little)|slightly|gradually|somewhat|this\\s+week|for|until|from\\s+now)\\b)`,
|
||||
"im",
|
||||
),
|
||||
),
|
||||
],
|
||||
dose: [
|
||||
|
||||
@@ -7,6 +7,9 @@ import {
|
||||
LETTER,
|
||||
ENGINE,
|
||||
medicationExtras,
|
||||
unlessDeferred,
|
||||
WORD_START,
|
||||
WORD_END,
|
||||
type LocaleBank,
|
||||
} from "../shared";
|
||||
|
||||
@@ -35,6 +38,9 @@ const RESULT_VERB_ES =
|
||||
const ES_MED_CLASS =
|
||||
"(?:anticoagulantes?|antiagregantes?|estatinas?|betabloqueantes?|diuréticos?|antidepresivos?)";
|
||||
|
||||
/** Signals that the number-free change shapes stand down for (`unlessDeferred`). */
|
||||
const ES_DEFERRAL = `\\?|¿|${WORD_START}(?:no|nunca|sin|solo\\s+si|sólo\\s+si|(?:antes\\s+de|hasta|después\\s+de|conviene|si)\\s+que|consult\\w*|pregunt\\w*|hable\\s+con|médic[oa]s?|doctor[a]?|farmac[ée]utic[oa]|enfermer[oa])${WORD_END}`;
|
||||
|
||||
export const bank: LocaleBank = {
|
||||
vocabulary: (names) =>
|
||||
`(?:${ES_MED_NOUN}|${medicationExtras(ES_MED_CLASS, names)})`,
|
||||
@@ -67,19 +73,28 @@ export const bank: LocaleBank = {
|
||||
),
|
||||
// Halving or a fraction with no number: "reduzca la dosis a la mitad",
|
||||
// "tome media dosis", "tome la mitad de la dosis".
|
||||
new RegExp(
|
||||
`(?<!\\b(?:no|nunca)\\s)\\b(?:reduzca|reduce|baje|disminuya|corte|parta|divida)\\s+${ES_SLOT}${med}\\s+(?:a|en|por)\\s+la\\s+mitad`,
|
||||
"i",
|
||||
unlessDeferred(
|
||||
ES_DEFERRAL,
|
||||
new RegExp(
|
||||
`(?<!\\b(?:no|nunca)\\s)\\b(?:reduzca|reduce|baje|disminuya|corte|parta|divida)\\s+${ES_SLOT}${med}\\s+(?:a|en|por)\\s+la\\s+mitad`,
|
||||
"i",
|
||||
),
|
||||
),
|
||||
new RegExp(
|
||||
`(?<!\\b(?:no|nunca)\\s)\\b(?:tome|tómese|inyecte|inyéctese|use)\\s+(?:s[óo]lo\\s+|solamente\\s+)?(?:media\\s+|la\\s+mitad\\s+de\\s+)${ES_SHORT}${med}`,
|
||||
"i",
|
||||
unlessDeferred(
|
||||
ES_DEFERRAL,
|
||||
new RegExp(
|
||||
`(?<!\\b(?:no|nunca)\\s)\\b(?:tome|tómese|inyecte|inyéctese|use)\\s+(?:s[óo]lo\\s+|solamente\\s+)?(?:media\\s+|la\\s+mitad\\s+de\\s+)${ES_SHORT}${med}`,
|
||||
"i",
|
||||
),
|
||||
),
|
||||
// A bare change imperative on a medication, no target: "reduzca la
|
||||
// dosis.", "aumente la insulina un poco".
|
||||
new RegExp(
|
||||
`(?<!\\b(?:no|nunca)\\s)\\b(?:aumente|suba|incremente|reduzca|baje|disminuya)\\s+${ES_SHORT}${med}(?=\\s*(?:[.!?;:)]|$)|\\s+(?:ahora|hoy|mañana|esta\\s+noche|un\\s+poco|ligeramente|gradualmente|durante|desde)(?![\\w\\u00C0-\\u024F]))`,
|
||||
"i",
|
||||
unlessDeferred(
|
||||
ES_DEFERRAL,
|
||||
new RegExp(
|
||||
`(?<!\\b(?:no|nunca)\\s)\\b(?:aumente|suba|incremente|reduzca|baje|disminuya)\\s+${ES_SHORT}${med}(?=\\s*(?:[.!?;:)]|$)|\\s+(?:ahora|hoy|mañana|esta\\s+noche|un\\s+poco|ligeramente|gradualmente|durante|desde)(?![\\w\\u00C0-\\u024F]))`,
|
||||
"i",
|
||||
),
|
||||
),
|
||||
],
|
||||
dose: [
|
||||
|
||||
@@ -8,6 +8,8 @@ import {
|
||||
WORD_END,
|
||||
ELIDED,
|
||||
medicationExtras,
|
||||
unlessDeferred,
|
||||
WORD_START,
|
||||
type LocaleBank,
|
||||
} from "../shared";
|
||||
|
||||
@@ -39,6 +41,9 @@ const RESULT_VERB_FR =
|
||||
const FR_MED_CLASS =
|
||||
"(?:anticoagulants?|antiagrégants?|statines?|bêta-?bloquants?|diurétiques?|antidépresseurs?|somnifères?)";
|
||||
|
||||
/** Signals that the number-free change shapes stand down for (`unlessDeferred`). */
|
||||
const FR_DEFERRAL = `\\?|(?:^|\\s)n['’]|${WORD_START}(?:ne|pas|jamais|sauf|seulement\\s+si|sans|demandez|parlez|médecin\\w*|pharmacien\\w*|docteur|soignant\\w*|spécialiste)${WORD_END}`;
|
||||
|
||||
export const bank: LocaleBank = {
|
||||
vocabulary: (names) =>
|
||||
`${ELIDED}(?:${FR_MED_NOUN}|${medicationExtras(FR_MED_CLASS, names)})`,
|
||||
@@ -71,19 +76,28 @@ export const bank: LocaleBank = {
|
||||
),
|
||||
// Halving or a fraction with no number: "réduisez la dose de moitié",
|
||||
// "prenez une demi-dose", "coupez le comprimé en deux".
|
||||
new RegExp(
|
||||
`(?<!\\b(?:vous|nous|je|tu|il|elle|on)\\s)(?<!n['’])\\b(?:r[ée]duisez|r[ée]duis|diminuez|diminue|baissez|baisse|coupez|coupe|divisez|divise)\\s+${FR_SLOT}${med}\\s+(?:de\\s+moiti[ée]|par\\s+deux|en\\s+deux)`,
|
||||
"i",
|
||||
unlessDeferred(
|
||||
FR_DEFERRAL,
|
||||
new RegExp(
|
||||
`(?<!\\b(?:vous|nous|je|tu|il|elle|on)\\s)(?<!n['’])\\b(?:r[ée]duisez|r[ée]duis|diminuez|diminue|baissez|baisse|coupez|coupe|divisez|divise)\\s+${FR_SLOT}${med}\\s+(?:de\\s+moiti[ée]|par\\s+deux|en\\s+deux)`,
|
||||
"i",
|
||||
),
|
||||
),
|
||||
new RegExp(
|
||||
`(?<!\\b(?:vous|nous|je|tu|il|elle|on)\\s)\\b(?:prenez|prends|injectez|injecte)\\s+(?:seulement\\s+|uniquement\\s+)?(?:une\\s+demi-?\\s?|la\\s+moiti[ée]\\s+d(?:e\\s+|u\\s+|es\\s+|['’]))${FR_SHORT}${med}`,
|
||||
"i",
|
||||
unlessDeferred(
|
||||
FR_DEFERRAL,
|
||||
new RegExp(
|
||||
`(?<!\\b(?:vous|nous|je|tu|il|elle|on)\\s)\\b(?:prenez|prends|injectez|injecte)\\s+(?:seulement\\s+|uniquement\\s+)?(?:une\\s+demi-?\\s?|la\\s+moiti[ée]\\s+d(?:e\\s+|u\\s+|es\\s+|['’]))${FR_SHORT}${med}`,
|
||||
"i",
|
||||
),
|
||||
),
|
||||
// A bare change imperative on a medication, no target: "diminuez votre
|
||||
// dose.", "augmentez votre insuline un peu".
|
||||
new RegExp(
|
||||
`(?<!\\b(?:vous|nous|je|tu|il|elle|on)\\s)(?<!n['’])\\b(?:augmentez|augmente|diminuez|diminue|r[ée]duisez|r[ée]duis|baissez|baisse)\\s+${FR_SHORT}${med}(?=\\s*(?:[.!?;:)]|$)|\\s+(?:maintenant|aujourd|demain|ce\\s+soir|un\\s+peu|progressivement|pendant)(?![\\w\\u00C0-\\u024F]))`,
|
||||
"i",
|
||||
unlessDeferred(
|
||||
FR_DEFERRAL,
|
||||
new RegExp(
|
||||
`(?<!\\b(?:vous|nous|je|tu|il|elle|on)\\s)(?<!n['’])\\b(?:augmentez|augmente|diminuez|diminue|r[ée]duisez|r[ée]duis|baissez|baisse)\\s+${FR_SHORT}${med}(?=\\s*(?:[.!?;:)]|$)|\\s+(?:maintenant|aujourd|demain|ce\\s+soir|un\\s+peu|progressivement|pendant)(?![\\w\\u00C0-\\u024F]))`,
|
||||
"i",
|
||||
),
|
||||
),
|
||||
],
|
||||
dose: [
|
||||
|
||||
@@ -8,6 +8,8 @@ import {
|
||||
WORD_END,
|
||||
ELIDED,
|
||||
medicationExtras,
|
||||
unlessDeferred,
|
||||
WORD_START,
|
||||
type LocaleBank,
|
||||
} from "../shared";
|
||||
|
||||
@@ -36,6 +38,9 @@ const RESULT_VERB_IT =
|
||||
const IT_MED_CLASS =
|
||||
"(?:anticoagulant[ei]|antiaggregant[ei]|statin[ae]|betabloccant[ei]|diuretic[oi]|antidepressiv[oi])";
|
||||
|
||||
/** Signals that the number-free change shapes stand down for (`unlessDeferred`). */
|
||||
const IT_DEFERRAL = `\\?|${WORD_START}(?:non|mai|senza|solo\\s+se|chieda|chiedi|parli|parla|consult\\w*|medic[oi]|dottor\\w*|farmacist\\w*|specialist\\w*)${WORD_END}`;
|
||||
|
||||
export const bank: LocaleBank = {
|
||||
vocabulary: (names) =>
|
||||
`${ELIDED}(?:${IT_MED_NOUN}|${medicationExtras(IT_MED_CLASS, names)})`,
|
||||
@@ -63,19 +68,28 @@ export const bank: LocaleBank = {
|
||||
),
|
||||
// Halving or a fraction with no number: "riduca la dose della metà",
|
||||
// "prenda mezza dose", "prenda metà della dose".
|
||||
new RegExp(
|
||||
`(?<!\\b(?:non|mai)\\s)\\b(?:riduca|ridurre|diminuisca|abbassi|tagli|divida)\\s+${IT_SLOT}${med}\\s+(?:della|alla|a|per)\\s+met[àa]`,
|
||||
"i",
|
||||
unlessDeferred(
|
||||
IT_DEFERRAL,
|
||||
new RegExp(
|
||||
`(?<!\\b(?:non|mai)\\s)\\b(?:riduca|ridurre|diminuisca|abbassi|tagli|divida)\\s+${IT_SLOT}${med}\\s+(?:della|alla|a|per)\\s+met[àa]`,
|
||||
"i",
|
||||
),
|
||||
),
|
||||
new RegExp(
|
||||
`(?<!\\b(?:non|mai)\\s)\\b(?:prenda|assuma|inietti)\\s+(?:solo\\s+|soltanto\\s+)?(?:mezz[ao]\\s+|(?:la\\s+)?met[àa]\\s+(?:della|del|dell['’]|di)\\s*)${IT_SHORT}${med}`,
|
||||
"i",
|
||||
unlessDeferred(
|
||||
IT_DEFERRAL,
|
||||
new RegExp(
|
||||
`(?<!\\b(?:non|mai)\\s)\\b(?:prenda|assuma|inietti)\\s+(?:solo\\s+|soltanto\\s+)?(?:mezz[ao]\\s+|(?:la\\s+)?met[àa]\\s+(?:della|del|dell['’]|di)\\s*)${IT_SHORT}${med}`,
|
||||
"i",
|
||||
),
|
||||
),
|
||||
// A bare change imperative on a medication, no target: "riduca la
|
||||
// dose.", "aumenti l'insulina un po'".
|
||||
new RegExp(
|
||||
`(?<!\\b(?:non|mai)\\s)\\b(?:aumenti|incrementi|riduca|abbassi|diminuisca)\\s+${IT_SHORT}${med}(?=\\s*(?:[.!?;:)]|$)|\\s+(?:ora|oggi|domani|stasera|un\\s+po|leggermente|gradualmente|per|da\\s+subito)(?![\\w\\u00C0-\\u024F]))`,
|
||||
"i",
|
||||
unlessDeferred(
|
||||
IT_DEFERRAL,
|
||||
new RegExp(
|
||||
`(?<!\\b(?:non|mai)\\s)\\b(?:aumenti|incrementi|riduca|abbassi|diminuisca)\\s+${IT_SHORT}${med}(?=\\s*(?:[.!?;:)]|$)|\\s+(?:ora|oggi|domani|stasera|un\\s+po|leggermente|gradualmente|per|da\\s+subito)(?![\\w\\u00C0-\\u024F]))`,
|
||||
"i",
|
||||
),
|
||||
),
|
||||
],
|
||||
dose: [
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
UNIT_END,
|
||||
ENGINE,
|
||||
medicationExtras,
|
||||
unlessDeferred,
|
||||
type LocaleBank,
|
||||
} from "../shared";
|
||||
|
||||
@@ -50,6 +51,9 @@ const RISK_VERDICT_KO = `(?:은|는|이|가)\\s*[^.?!]{0,10}${RISK_LEVEL_KO}`;
|
||||
const KO_MED_CLASS =
|
||||
"(?:혈액\\s*희석제|항응고제|항혈소판제|스타틴|베타\\s*차단제|이뇨제|항우울제)";
|
||||
|
||||
/** Signals that the number-free change shapes stand down for (`unlessDeferred`). */
|
||||
const KO_DEFERRAL = `\\?|의사|약사|전문의|주치의|상의|라고|지\\s*마`;
|
||||
|
||||
export const bank: LocaleBank = {
|
||||
vocabulary: (names) =>
|
||||
`(?:${KO_MED_NOUN}|(?:${medicationExtras(KO_MED_CLASS, names, false)})\\S{0,3})`,
|
||||
@@ -74,8 +78,11 @@ export const bank: LocaleBank = {
|
||||
// "복용량을 늘리세요", "인슐린 용량을 낮추세요". No digit may sit between,
|
||||
// so "약 2주 동안 걷는 시간을 늘리세요" ("약" = about) passes, and the
|
||||
// prohibitive "줄이지 마세요" never meets an imperative ending.
|
||||
new RegExp(
|
||||
`${med}[^.?!\\d]{0,8}?(?:(?:늘리|올리|높이|줄이|내리|낮추|증량하|감량하)(?:세요|십시오)|(?:늘려|올려|높여|줄여|내려|낮춰)\\s*(?:보세요|주세요|보십시오))`,
|
||||
unlessDeferred(
|
||||
KO_DEFERRAL,
|
||||
new RegExp(
|
||||
`${med}[^.?!\\d]{0,8}?(?:(?:늘리|올리|높이|줄이|내리|낮추|증량하|감량하)(?:세요|십시오)|(?:늘려|올려|높여|줄여|내려|낮춰)\\s*(?:보세요|주세요|보십시오))`,
|
||||
),
|
||||
),
|
||||
],
|
||||
// Korean is verb-final: the dose comes first and the change verb closes the
|
||||
@@ -106,7 +113,12 @@ export const bank: LocaleBank = {
|
||||
),
|
||||
// Both a medication object AND a trial cue are required, as in the EN bank,
|
||||
// so "걸음 수를 두 배로 늘려 2주간 해보세요" never trips.
|
||||
/(?:약|알|정|인슐린|주사|복용|용량)[^.?!]{0,25}(?:절반|반으로|두\s?배|건너뛰|중단|끊)[^.?!]{0,40}(?:주간|주 동안|일 동안|달 동안|보세요|보십시오|보자|해 ?보)/,
|
||||
// Number-free too, so it stands down where a pharmacist or doctor is
|
||||
// asked ("약사에게 … 물어보세요" ends on the same "보세요").
|
||||
unlessDeferred(
|
||||
KO_DEFERRAL,
|
||||
/(?:약|알|정|인슐린|주사|복용|용량)[^.?!]{0,25}(?:절반|반으로|두\s?배|건너뛰|중단|끊)[^.?!]{0,40}(?:주간|주 동안|일 동안|달 동안|보세요|보십시오|보자|해 ?보)/,
|
||||
),
|
||||
],
|
||||
risk: [
|
||||
new RegExp(`${RISK_NOUN_KO}[^.?!]{0,15}${QUAL_NUM_KO}`, "i"),
|
||||
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
ENGINE,
|
||||
WORD_START,
|
||||
medicationExtras,
|
||||
unlessDeferred,
|
||||
WORD_END,
|
||||
type LocaleBank,
|
||||
} from "../shared";
|
||||
|
||||
@@ -50,6 +52,9 @@ const RISK_VERDICT_NL = `(?:is|ligt|lijkt|blijft|wijst\\s+op|komt\\s+uit\\s+op)\
|
||||
const NL_MED_CLASS =
|
||||
"\\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*)";
|
||||
|
||||
/** Signals that the number-free change shapes stand down for (`unlessDeferred`). */
|
||||
const NL_DEFERRAL = `\\?|${WORD_START}(?:niet|geen|nooit|alleen\\s*,?\\s*(?:als|wanneer|na)|pas\\s*,?\\s*(?:als|na)|zonder|vraag|overleg\\w*|bespreek|arts|artsen|huisarts|apothe\\w*|specialist|dokter|zorgverlener\\w*)${WORD_END}`;
|
||||
|
||||
export const bank: LocaleBank = {
|
||||
vocabulary: (names) =>
|
||||
`(?:${NL_MED_NOUN}|${medicationExtras(NL_MED_CLASS, names)})`,
|
||||
@@ -113,19 +118,28 @@ export const bank: LocaleBank = {
|
||||
),
|
||||
// Halving or a fraction with no number: "verlaag je dosis met de helft",
|
||||
// "neem een halve dosis", "snijd de tablet doormidden".
|
||||
new RegExp(
|
||||
`(?<!\\b(?:niet|nooit)\\s)\\b(?:verlaag|verminder|deel|breek|snijd)\\s+${NL_SLOT}${med}\\s+(?:met\\s+de\\s+helft|door\\s+(?:twee|2)|doormidden|in\\s+twee)`,
|
||||
"i",
|
||||
unlessDeferred(
|
||||
NL_DEFERRAL,
|
||||
new RegExp(
|
||||
`(?<!\\b(?:niet|nooit)\\s)\\b(?:verlaag|verminder|deel|breek|snijd)\\s+${NL_SLOT}${med}\\s+(?:met\\s+de\\s+helft|door\\s+(?:twee|2)|doormidden|in\\s+twee)`,
|
||||
"i",
|
||||
),
|
||||
),
|
||||
new RegExp(
|
||||
`(?<!\\b(?:niet|nooit)\\s)\\b(?:neem|slik|spuit|injecteer)\\s+(?:alleen\\s+|maar\\s+)?(?:een\\s+halve|de\\s+helft\\s+van)\\s+${NL_SHORT}${med}`,
|
||||
"i",
|
||||
unlessDeferred(
|
||||
NL_DEFERRAL,
|
||||
new RegExp(
|
||||
`(?<!\\b(?:niet|nooit)\\s)\\b(?:neem|slik|spuit|injecteer)\\s+(?:alleen\\s+|maar\\s+)?(?:een\\s+halve|de\\s+helft\\s+van)\\s+${NL_SHORT}${med}`,
|
||||
"i",
|
||||
),
|
||||
),
|
||||
// A bare change imperative on a medication, no target: "verlaag je
|
||||
// dosis.", "verhoog je insuline wat". "Verlaag je dosis niet" is a warning.
|
||||
new RegExp(
|
||||
`(?<!\\b(?:niet|nooit)\\s)\\b(?:verhoog|verlaag|verminder)\\s+${NL_SHORT}${med}(?=\\s*(?:[.!?;:)]|$)|\\s+(?:nu|vandaag|morgen|vanavond|iets|wat|een\\s+beetje|geleidelijk|vanaf)\\b)`,
|
||||
"i",
|
||||
unlessDeferred(
|
||||
NL_DEFERRAL,
|
||||
new RegExp(
|
||||
`(?<!\\b(?:niet|nooit)\\s)\\b(?:verhoog|verlaag|verminder)\\s+${NL_SHORT}${med}(?=\\s*(?:[.!?;:)]|$)|\\s+(?:nu|vandaag|morgen|vanavond|iets|wat|een\\s+beetje|geleidelijk|vanaf)\\b)`,
|
||||
"i",
|
||||
),
|
||||
),
|
||||
],
|
||||
dose: [
|
||||
|
||||
@@ -6,6 +6,9 @@ import {
|
||||
TARGET,
|
||||
ENGINE,
|
||||
medicationExtras,
|
||||
unlessDeferred,
|
||||
WORD_START,
|
||||
WORD_END,
|
||||
type LocaleBank,
|
||||
} from "../shared";
|
||||
|
||||
@@ -34,6 +37,9 @@ const RESULT_VERB_PL =
|
||||
const PL_MED_CLASS =
|
||||
"(?:lek\\S*\\s+przeciwzakrzepow\\S*|antykoagulant\\S*|statyn\\S*|beta-?bloker\\S*|diuretyk\\S*|lek\\S*\\s+moczopędn\\S*|antydepresant\\S*)";
|
||||
|
||||
/** Signals that the number-free change shapes stand down for (`unlessDeferred`). */
|
||||
const PL_DEFERRAL = `\\?|${WORD_START}(?:nie|bez|tylko\\s+(?:jeśli|gdy|jeżeli)|zapytaj|skonsultuj|porozmawiaj|lekarz\\w*|farmaceut\\w*|aptek\\w*|specjalist\\w*)${WORD_END}`;
|
||||
|
||||
export const bank: LocaleBank = {
|
||||
vocabulary: (names) =>
|
||||
`(?:${PL_MED_NOUN}|${medicationExtras(PL_MED_CLASS, names)})`,
|
||||
@@ -61,20 +67,29 @@ export const bank: LocaleBank = {
|
||||
),
|
||||
// Halving or a fraction with no number: "zmniejsz dawkę o połowę",
|
||||
// "weź połowę dawki", "przyjmij pół dawki".
|
||||
new RegExp(
|
||||
`(?<!\\bnie\\s)\\b(?:zmniejsz|obni[żz]|zredukuj|podziel|przetnij|przełam)(?:cie)?\\s+${PL_SLOT}${med}\\s+(?:o\\s+połowę|na\\s+pół|na\\s+połowę|na\\s+dwie\\s+części)`,
|
||||
"i",
|
||||
unlessDeferred(
|
||||
PL_DEFERRAL,
|
||||
new RegExp(
|
||||
`(?<!\\bnie\\s)\\b(?:zmniejsz|obni[żz]|zredukuj|podziel|przetnij|przełam)(?:cie)?\\s+${PL_SLOT}${med}\\s+(?:o\\s+połowę|na\\s+pół|na\\s+połowę|na\\s+dwie\\s+części)`,
|
||||
"i",
|
||||
),
|
||||
),
|
||||
new RegExp(
|
||||
`(?:(?<!\\bnie\\s)\\b(?:we[źz]|bierz|przyjmij|przyjmuj|wstrzyknij)|\\b(?:proszę|należy)\\s+(?:wzi[ąa][ćc]|przyj[ąa][ćc]|przyjmowa[ćc]))\\s+(?:tylko\\s+)?(?:pół|połowę|połowy)\\s+${PL_SHORT}${med}`,
|
||||
"i",
|
||||
unlessDeferred(
|
||||
PL_DEFERRAL,
|
||||
new RegExp(
|
||||
`(?:(?<!\\bnie\\s)\\b(?:we[źz]|bierz|przyjmij|przyjmuj|wstrzyknij)|\\b(?:proszę|należy)\\s+(?:wzi[ąa][ćc]|przyj[ąa][ćc]|przyjmowa[ćc]))\\s+(?:tylko\\s+)?(?:pół|połowę|połowy)\\s+${PL_SHORT}${med}`,
|
||||
"i",
|
||||
),
|
||||
),
|
||||
// A bare change imperative on a medication, no target: "zmniejsz
|
||||
// dawkę.", "zwiększ dawkę insuliny.". The noun closes the clause, or one
|
||||
// genitive word does ("dawkę insuliny"), or a time or degree word follows.
|
||||
new RegExp(
|
||||
`(?<!\\bnie\\s)\\b(?:zwi[ęe]ksz|zmniejsz|obni[żz]|podnie[śs]|podwy[żz]sz|zredukuj)(?:cie)?\\s+${PL_SHORT}${med}(?=\\s*(?:[.!?;:)]|$)|\\s+[^\\s\\d.?!,;:]+\\s*(?:[.!?;:)]|$)|\\s+(?:teraz|dziś|dzisiaj|jutro|trochę|nieco|stopniowo|od\\s+jutra|przez)(?=[\\s.,;:!?]|$))`,
|
||||
"i",
|
||||
unlessDeferred(
|
||||
PL_DEFERRAL,
|
||||
new RegExp(
|
||||
`(?<!\\bnie\\s)\\b(?:zwi[ęe]ksz|zmniejsz|obni[żz]|podnie[śs]|podwy[żz]sz|zredukuj)(?:cie)?\\s+${PL_SHORT}${med}(?=\\s*(?:[.!?;:)]|$)|\\s+(?!(?:kofein|cukr|cukier|ruch|sol|sól|alkohol|kalori|białk|wod|kaw|herbat|tłuszcz|węglowodan|aktywnoś|ćwicze|snu|sen|błonnik)[^\\s]*\\s*(?:[.!?;:)]|$))[^\\s\\d.?!,;:]+\\s*(?:[.!?;:)]|$)|\\s+(?:teraz|dziś|dzisiaj|jutro|trochę|nieco|stopniowo|od\\s+jutra|przez)(?=[\\s.,;:!?]|$))`,
|
||||
"i",
|
||||
),
|
||||
),
|
||||
],
|
||||
dose: [
|
||||
|
||||
@@ -8,6 +8,9 @@ import {
|
||||
LETTER,
|
||||
ENGINE,
|
||||
medicationExtras,
|
||||
unlessDeferred,
|
||||
WORD_START,
|
||||
WORD_END,
|
||||
type LocaleBank,
|
||||
} from "../shared";
|
||||
|
||||
@@ -50,6 +53,9 @@ const RISK_VERDICT_PT = `(?:é|est[áa]|parece|fica|continua|indica|mostra|apont
|
||||
const PT_MED_CLASS =
|
||||
"(?: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))";
|
||||
|
||||
/** Signals that the number-free change shapes stand down for (`unlessDeferred`). */
|
||||
const PT_DEFERRAL = `\\?|${WORD_START}(?:não|nao|nunca|sem|só\\s+se|somente\\s+se|antes\\s+(?:de\\s+)?que|até\\s+que|depois\\s+que|pergunte|converse|fale\\s+com|consult\\w*|médic[oa]s?|doutor[a]?|farmacêutic[oa])${WORD_END}`;
|
||||
|
||||
export const bank: LocaleBank = {
|
||||
vocabulary: (names) =>
|
||||
`(?:${PT_MED_NOUN}|${medicationExtras(PT_MED_CLASS, names)})`,
|
||||
@@ -94,19 +100,28 @@ export const bank: LocaleBank = {
|
||||
),
|
||||
// Halving or a fraction with no number: "reduza a dose pela metade",
|
||||
// "tome meia dose", "corte o comprimido ao meio".
|
||||
new RegExp(
|
||||
`(?<!\\b(?:não|nao|nunca|jamais)\\s)\\b(?:reduza|diminua|baixe|corte|parta|divida)\\s+${PT_SLOT}${med}\\s+(?:pela\\s+metade|ao\\s+meio|em\\s+duas\\s+partes|[àa]\\s+metade)`,
|
||||
"i",
|
||||
unlessDeferred(
|
||||
PT_DEFERRAL,
|
||||
new RegExp(
|
||||
`(?<!\\b(?:não|nao|nunca|jamais)\\s)\\b(?:reduza|diminua|baixe|corte|parta|divida)\\s+${PT_SLOT}${med}\\s+(?:pela\\s+metade|ao\\s+meio|em\\s+duas\\s+partes|[àa]\\s+metade)`,
|
||||
"i",
|
||||
),
|
||||
),
|
||||
new RegExp(
|
||||
`(?<!\\b(?:não|nao|nunca|jamais)\\s)\\b(?:tome|use|aplique|injete)\\s+(?:s[óo]\\s+|apenas\\s+|somente\\s+)?(?:meia\\s+|(?:a\\s+)?metade\\s+d[aoe]s?\\s+)${PT_SHORT}${med}`,
|
||||
"i",
|
||||
unlessDeferred(
|
||||
PT_DEFERRAL,
|
||||
new RegExp(
|
||||
`(?<!\\b(?:não|nao|nunca|jamais)\\s)\\b(?:tome|use|aplique|injete)\\s+(?:s[óo]\\s+|apenas\\s+|somente\\s+)?(?:meia\\s+|(?:a\\s+)?metade\\s+d[aoe]s?\\s+)${PT_SHORT}${med}`,
|
||||
"i",
|
||||
),
|
||||
),
|
||||
// A bare change imperative on a medication, no target: "reduza a
|
||||
// dose.", "aumente a insulina um pouco".
|
||||
new RegExp(
|
||||
`(?<!\\b(?:não|nao|nunca|jamais)\\s)\\b(?:aumente|suba|eleve|reduza|diminua|baixe|abaixe)\\s+(?:(?:a|o|as|os)\\s+)?${PT_SHORT}${med}(?=\\s*(?:[.!?;:)]|$)|\\s+(?:agora|hoje|amanh[ãa]|um\\s+pouco|aos\\s+poucos|gradualmente|a\\s+partir)(?![\\w\\u00C0-\\u024F]))`,
|
||||
"i",
|
||||
unlessDeferred(
|
||||
PT_DEFERRAL,
|
||||
new RegExp(
|
||||
`(?<!\\b(?:não|nao|nunca|jamais)\\s)\\b(?:aumente|suba|eleve|reduza|diminua|baixe|abaixe)\\s+(?:(?:a|o|as|os)\\s+)?${PT_SHORT}${med}(?=\\s*(?:[.!?;:)]|$)|\\s+(?:agora|hoje|amanh[ãa]|um\\s+pouco|aos\\s+poucos|gradualmente|a\\s+partir)(?![\\w\\u00C0-\\u024F]))`,
|
||||
"i",
|
||||
),
|
||||
),
|
||||
],
|
||||
dose: [
|
||||
|
||||
@@ -146,3 +146,22 @@ export interface LocaleBank {
|
||||
/** A change stem that cancels that exemption. */
|
||||
changeStem: RegExp;
|
||||
}
|
||||
|
||||
/**
|
||||
* The number-free change shapes (halve it, take half, a bare "reduce your
|
||||
* dose") have no unit to anchor them, so they stand down in a sentence that
|
||||
* defers or declines: a question, a negation anywhere in it (a trailing
|
||||
* "heute nicht" or "vandaag niet" came after the time word the pattern
|
||||
* stops at), a restrictive condition ("only if", "nur wenn"), or a
|
||||
* clinician named in it ("if your doctor agrees", "ask your pharmacist").
|
||||
* `deferral` is the locale's alternation of those signals; it is checked
|
||||
* over the whole sentence, which the screen hands in one at a time. The
|
||||
* shapes with a stated amount do not stand down: a target dose stays
|
||||
* blocked whatever surrounds it.
|
||||
*/
|
||||
export function unlessDeferred(deferral: string, pattern: RegExp): RegExp {
|
||||
return new RegExp(
|
||||
`^(?![\\s\\S]*(?:${deferral}))[\\s\\S]*?(?:${pattern.source})`,
|
||||
pattern.flags,
|
||||
);
|
||||
}
|
||||
@@ -99,9 +99,10 @@ const US: CrisisResourceSet = {
|
||||
};
|
||||
|
||||
/**
|
||||
* Brazil (an explicit region signal: a bare "br" or "pt_BR"-style tag a
|
||||
* native client sends from the device region) — CVV by phone and chat, SAMU
|
||||
* as the emergency number. Both numbers are free and answer around the clock
|
||||
* Brazil, reached only by the bare region code "br". "pt-BR" and "pt_BR"
|
||||
* are not a region signal: the web folds every Portuguese reader into that
|
||||
* one bundle, so they get PT_BR_AMBIGUOUS. CVV by phone and chat, SAMU as
|
||||
* the emergency number. Both numbers are free and answer around the clock
|
||||
* (cvv.org.br; the Ministry of Health's SAMU 192 page).
|
||||
*/
|
||||
const BR: CrisisResourceSet = {
|
||||
|
||||
Reference in new issue
Block a user