mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 04:50:59 +00:00
fix(safety): screen the dose-change class, not one sentence shape
"Increase your metformin to 2000 mg daily" passed the outbound dose
screen. The English change pattern accepted only "it" or "your dose"
between the verb and "to", so a drug name in that slot walked past it,
and no pattern covered an imperative to take a stated amount, a count
unit (tablets, capsules, puffs), or an imperative to skip or stop a
medication. Each locale bank had the same narrow object slot.
Add a per-locale dose-change class (en, de, fr, es, it, pl, ko) with
three shapes: an imperative or infinitive change verb with up to four
words of object and a target dose; an imperative to take or inject a
stated amount; and an imperative to halve, double, skip, pause or stop
a medication. Negated imperatives, past-tense log restatements,
present-tense descriptions ("you take 1000 mg"), clinician referrals
without a target, and nutrition advice (sodium, water, steps) still pass.
A unit now ends on an explicit lookahead instead of \b, so a lab
concentration ("under 140 mg/dL") no longer trips the dose bank while a
rate ("2000 mg/day") still does. "as usual" and "wie gewohnt" join the
continuation anchors, still gated on the user's schedule.
This commit is contained in:
1 parent
8e7fd0b873
commit
c7844c3bc6
3 files changed
+529
-33
No files matched your search
@@ -124,3 +124,24 @@ describe("coachOutboundFallback", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("screenCoachReply — a drug-named dose change", () => {
|
||||
it("replaces 'Increase your metformin to 2000 mg daily'", () => {
|
||||
const d = screenCoachReply(
|
||||
"Increase your metformin to 2000 mg daily.",
|
||||
"en",
|
||||
[1000],
|
||||
);
|
||||
expect(d.block).toBe(true);
|
||||
expect(d.reason).toBe("dose_prescription");
|
||||
});
|
||||
|
||||
it("passes a restatement of the logged schedule", () => {
|
||||
const d = screenCoachReply(
|
||||
"Your log shows you take 1000 mg twice daily.",
|
||||
"en",
|
||||
[1000],
|
||||
);
|
||||
expect(d.block).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -445,3 +445,199 @@ describe("screenModelOutput — empty input", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// ── the dose-change class ────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* "Increase your metformin to 2000 mg daily" passed the screen: the English
|
||||
* change pattern allowed only "it" or "your dose" between the verb and "to",
|
||||
* so naming the drug walked past it. These cases pin the class — a change
|
||||
* verb with any short object and a target, an imperative to take a stated
|
||||
* amount, and an imperative to halve / skip / stop a medication — in every
|
||||
* locale the banks cover.
|
||||
*/
|
||||
const DOSE_CLASS_VIOLATIONS: Record<Locale, readonly string[]> = {
|
||||
en: [
|
||||
"Increase your metformin to 2000 mg daily.",
|
||||
"Raise your insulin by 4 units tonight.",
|
||||
"Up your metformin to 1500 mg.",
|
||||
"Bump the semaglutide to 1 mg next week.",
|
||||
"Titrate your evening insulin up to 20 units.",
|
||||
"Reduce your metformin from 2000 mg to 1000 mg.",
|
||||
"Lower your lisinopril to 5 mg.",
|
||||
"Halve your metformin to 500 mg.",
|
||||
"Double your vitamin D to 4000 IU.",
|
||||
"Take 2000 mg daily.",
|
||||
"Take 3 tablets of metformin with dinner.",
|
||||
"Here's the plan: take 2 pills in the morning.",
|
||||
"Your glucose ran high.\nTake 1000 mg more tonight.",
|
||||
"It might be worth increasing your metformin to 2000 mg.",
|
||||
"Skip your evening dose tonight.",
|
||||
"Consider skipping tonight's insulin dose.",
|
||||
"Stop taking metformin.",
|
||||
"You should stop taking your lisinopril.",
|
||||
],
|
||||
de: [
|
||||
"Erhöhe dein Metformin auf 2000 mg täglich.",
|
||||
"Reduzieren Sie Ihr Insulin um 4 Einheiten.",
|
||||
"Nimm 2 Tabletten am Abend.",
|
||||
"Halbiere deine Insulindosis.",
|
||||
"Lass die Abenddosis heute aus.",
|
||||
"Setz dein Metformin ab.",
|
||||
"Du solltest das Metformin absetzen.",
|
||||
],
|
||||
fr: [
|
||||
"Augmentez votre metformine à 2000 mg.",
|
||||
"Réduisez votre insuline de 4 unités.",
|
||||
"Prenez 2 comprimés le soir.",
|
||||
"Sautez la dose du soir.",
|
||||
"Arrêtez de prendre la metformine.",
|
||||
],
|
||||
es: [
|
||||
"Aumente su metformina a 2000 mg.",
|
||||
"Reduzca la insulina en 4 unidades.",
|
||||
"Tome 2 pastillas por la noche.",
|
||||
"Omita la dosis de la noche.",
|
||||
"Deje de tomar la metformina.",
|
||||
],
|
||||
it: [
|
||||
"Aumenti la metformina a 2000 mg.",
|
||||
"Riduca l'insulina di 4 unità.",
|
||||
"Prenda 2 compresse la sera.",
|
||||
"Salti la dose serale.",
|
||||
"Smetta di prendere la metformina.",
|
||||
],
|
||||
pl: [
|
||||
"Zwiększ metforminę do 2000 mg.",
|
||||
"Zmniejsz insulinę o 4 jednostki.",
|
||||
"Proszę przyjąć 2 tabletki wieczorem.",
|
||||
"Pomiń wieczorną dawkę.",
|
||||
"Przestań brać metforminę.",
|
||||
],
|
||||
ko: [
|
||||
"메트포르민을 2000mg으로 증량하세요.",
|
||||
"하루 2정 드세요.",
|
||||
"저녁 인슐린 용량을 건너뛰세요.",
|
||||
"메트포르민 복용을 중단하세요.",
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* What must keep passing: a restatement of the logged schedule, a clinician
|
||||
* referral that names no target, a negated imperative (a warning), a past
|
||||
* change described from the log, and the ordinary non-dose advice that shares
|
||||
* a verb or a unit with the class — steps, walks, sodium, water, and lab
|
||||
* targets written as a concentration.
|
||||
*/
|
||||
const DOSE_CLASS_CLEAN: Record<Locale, readonly string[]> = {
|
||||
en: [
|
||||
"You take 1000 mg twice daily.",
|
||||
"Your log shows 2000 mg.",
|
||||
"Your log shows you take 1000 mg twice daily, and you haven't missed a dose.",
|
||||
"Ask your doctor whether to increase your dose.",
|
||||
"Talk to your doctor before you stop taking your metformin.",
|
||||
"Don't skip your evening dose.",
|
||||
"Never stop taking metformin without talking to your prescriber.",
|
||||
"Your dose was increased to 2000 mg in May.",
|
||||
"Since your metformin was raised to 2000 mg, your fasting glucose dropped.",
|
||||
"Double your steps for two weeks.",
|
||||
"Skip the evening walk if it rains.",
|
||||
"Stop using your phone an hour before bed.",
|
||||
"Lower your sodium to 1500 mg a day.",
|
||||
"Increase your water intake to 2000 ml.",
|
||||
"Try to keep your glucose under 140 mg/dL after meals.",
|
||||
"Aim to bring your LDL down to 100 mg/dL with your doctor.",
|
||||
],
|
||||
de: [
|
||||
"Du nimmst 1000 mg zweimal täglich.",
|
||||
"Dein Protokoll zeigt 2000 mg.",
|
||||
"Setz dein Metformin nicht ab.",
|
||||
"Lass keine Dosis aus.",
|
||||
"Frag deine Ärztin, ob du die Dosis erhöhen sollst.",
|
||||
],
|
||||
fr: [
|
||||
"Vous prenez 1000 mg deux fois par jour.",
|
||||
"Ne sautez pas votre dose du soir.",
|
||||
"N'arrêtez pas de prendre la metformine.",
|
||||
],
|
||||
es: [
|
||||
"Usted toma 1000 mg dos veces al día.",
|
||||
"No deje de tomar la metformina.",
|
||||
],
|
||||
it: [
|
||||
"Lei prende 1000 mg due volte al giorno.",
|
||||
"Non smetta di prendere la metformina.",
|
||||
],
|
||||
pl: [
|
||||
"Przyjmuje Pan 1000 mg dwa razy dziennie.",
|
||||
"Nie przestawaj brać leków.",
|
||||
],
|
||||
ko: [
|
||||
"하루 두 번 1000mg을 복용하고 계세요.",
|
||||
"저녁 용량을 건너뛰지 마세요.",
|
||||
"용량을 늘릴지 의사와 상담하세요.",
|
||||
"담배를 끊으세요.",
|
||||
"혈당을 140mg/dL 아래로 유지해 보세요.",
|
||||
],
|
||||
};
|
||||
|
||||
describe("screenModelOutput — the dose-change class, every locale", () => {
|
||||
for (const locale of locales) {
|
||||
it.each(DOSE_CLASS_VIOLATIONS[locale])(
|
||||
`blocks in ${locale}: %s`,
|
||||
(text) => {
|
||||
const d = screenModelOutput(text, locale, CONVERSATIONAL_CONTRACTS);
|
||||
expect(d.block).toBe(true);
|
||||
expect(d.reason).toBe("dose_prescription");
|
||||
},
|
||||
);
|
||||
|
||||
it.each(DOSE_CLASS_CLEAN[locale])(`passes in ${locale}: %s`, (text) => {
|
||||
const d = screenModelOutput(text, locale, CONVERSATIONAL_CONTRACTS);
|
||||
expect(d.block).toBe(false);
|
||||
expect(d.reason).toBeNull();
|
||||
});
|
||||
}
|
||||
|
||||
it("blocks the English drug-named change for a reader of every locale", () => {
|
||||
for (const locale of locales) {
|
||||
expect(
|
||||
screenModelOutput(
|
||||
"Increase your metformin to 2000 mg daily.",
|
||||
locale,
|
||||
CONVERSATIONAL_CONTRACTS,
|
||||
).block,
|
||||
).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it("blocks the class under the insights contracts too", () => {
|
||||
const d = screenModelOutput(
|
||||
"Increase your metformin to 2000 mg daily.",
|
||||
"en",
|
||||
INSIGHTS_CONTRACTS,
|
||||
);
|
||||
expect(d.reason).toBe("dose_prescription");
|
||||
});
|
||||
|
||||
it("keeps the schedule-gated continuation exemption for a take imperative", () => {
|
||||
// On schedule: the maintenance anchor exempts it.
|
||||
expect(
|
||||
screenModelOutput(
|
||||
"Take your 1000 mg as usual.",
|
||||
"en",
|
||||
CONVERSATIONAL_CONTRACTS,
|
||||
{ scheduleDoses: [1000] },
|
||||
).block,
|
||||
).toBe(false);
|
||||
// Off schedule: the same phrasing names a dose the user is not on.
|
||||
expect(
|
||||
screenModelOutput(
|
||||
"Take your 2000 mg as usual.",
|
||||
"en",
|
||||
CONVERSATIONAL_CONTRACTS,
|
||||
{ scheduleDoses: [1000] },
|
||||
).block,
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -56,13 +56,117 @@ export interface OutboundDecision {
|
||||
reason: OutboundReason | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count units — a dose expressed as a number of dosage forms ("2 tablets",
|
||||
* "zwei Hübe" written as "2 Hübe", "1 comprimé"). The same forms the
|
||||
* medication wizard offers (`src/lib/medications/dose-units.ts`), in every
|
||||
* locale the banks cover. Without them "take 3 tablets a day" carried no unit
|
||||
* the screen recognised and passed every pattern.
|
||||
*/
|
||||
const COUNT_UNIT =
|
||||
"(?:tablets?|pills?|capsules?|drops?|puffs?|sprays?|tabletten?|kapseln?|tropfen|hübe|hub|sprühstöße|comprimés?|gélules?|gouttes?|bouffées?|comprimidos?|pastillas?|c[áa]psulas?|gotas?|compress[ae]|pastigli[ae]|gocce|goccia|tabletk[aęiy]|tabletek|kapsułk[aęiy]|kapsułek|krople|kropli)";
|
||||
|
||||
/**
|
||||
* Dose units for a target dose a model must never name. Mass and insulin
|
||||
* units plus the count forms; `ml` is left out on purpose because
|
||||
* "increase your water intake to 2000 ml" is ordinary hydration advice.
|
||||
*/
|
||||
const DRUG_UNIT = `(?:mg|mcg|µg|iu|i\\.u\\.|units?|unidades?|unità|unités?|jednostek|jednostki|ie|i\\.e\\.|einheiten|${COUNT_UNIT})`;
|
||||
|
||||
/**
|
||||
* Dose units, shared across every locale. The GLP-1 + general oral-dose
|
||||
* vocabulary; `ie` / `i.e.` / `einheiten` / `unità` / `jednostk` cover the
|
||||
* insulin-unit spellings the six locales use.
|
||||
*/
|
||||
const DOSE_UNIT =
|
||||
"(?:mg|mcg|µg|ml|units?|unidades?|unità|unités?|jednostek|jednostki|ie|i\\.e\\.|einheiten)";
|
||||
const DOSE_UNIT = `(?:ml|${DRUG_UNIT})`;
|
||||
|
||||
/**
|
||||
* What may follow a dose unit. A concentration is a lab value, not a dose:
|
||||
* "keep your glucose under 140 mg/dL" and "eGFR above 60 ml/min" name no
|
||||
* medication, and a bare `\b` let both trip the "try … N mg" pattern. A
|
||||
* per-day or per-kilogram rate ("2000 mg/day", "10 mg/kg") IS a dose and still
|
||||
* matches. The lookahead also ends a unit that closes on a non-word letter
|
||||
* ("unità", "i.e.") where `\b` cannot.
|
||||
*/
|
||||
const UNIT_END = "(?!\\w|\\s*\\/\\s*(?:dl|l|min|mmol)\\b)";
|
||||
|
||||
/**
|
||||
* One to four words of object between a change verb and its target — "your
|
||||
* metformin", "the evening insulin dose", "votre metformine". The original
|
||||
* English pattern accepted only "it" or "your dose" in that slot, so naming the
|
||||
* drug ("increase your metformin to 2000 mg") walked past the screen.
|
||||
*
|
||||
* A word may not be a digit run, a sentence break, a negation (a negated
|
||||
* imperative is a warning, not an instruction), a clause joiner, or a
|
||||
* dietary noun: "lower your sodium to 1500 mg" and "cut caffeine to 200 mg"
|
||||
* are nutrition guidance, not a dose change.
|
||||
*/
|
||||
function objectSlot(stopWords: string): string {
|
||||
return `(?:(?!(?:${stopWords})(?=\\s))[^\\s\\d.?!,;:]+\\s+){0,4}?`;
|
||||
}
|
||||
|
||||
const EN_SLOT = objectSlot(
|
||||
"not|never|and|or|but|if|whether|when|sodium|salt|caffeine|sugars?|fib(?:er|re)|protein|carbs?|carbohydrates?|cholesterol|water|fluids?|coffee|alcohol|steps",
|
||||
);
|
||||
const DE_SLOT = objectSlot(
|
||||
"nicht|nie|niemals|kein\\w*|und|oder|aber|wenn|ob|natrium|salz|koffein|zucker|wasser|eiweiß|protein|ballaststoff\\w*|kalorien",
|
||||
);
|
||||
const FR_SLOT = objectSlot(
|
||||
"pas|jamais|ne|et|ou|mais|si|sodium|sel|caféine|sucres?|eau|protéines?|fibres?",
|
||||
);
|
||||
const ES_SLOT = objectSlot(
|
||||
"no|nunca|y|o|pero|si|sodio|sal|cafeína|azúcar|agua|proteínas?|fibra",
|
||||
);
|
||||
const IT_SLOT = objectSlot(
|
||||
"non|mai|e|o|ma|se|sodio|sale|caffeina|zucchero|acqua|proteine|fibre",
|
||||
);
|
||||
const PL_SLOT = objectSlot(
|
||||
"nie|i|lub|albo|ale|jeśli|czy|sód|sodu|sól|soli|kofein\\S*|cukr\\S*|wod\\S*|białk\\S*|błonnik\\S*",
|
||||
);
|
||||
|
||||
/** A target dose: a number, a drug unit, and an honest unit end. */
|
||||
const TARGET = `[\\d.,]+\\s*${DRUG_UNIT}${UNIT_END}`;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* ("don't skip your dose") never reaches the verb from any of these.
|
||||
*/
|
||||
const EN_LEAD =
|
||||
"(?:^\\s*(?:[-*•]\\s*|\\d+[.)]\\s*)?|[:;—–]\\s*|\\b(?:please|just|then|now|instead|so|you\\s+(?:should|need\\s+to|must|ought\\s+to)|try\\s+to|go\\s+ahead\\s+and|i'?d|i\\s+would|time\\s+to)\\s+)";
|
||||
|
||||
/** A recommending cue that may sit in front of a gerund / infinitive change. */
|
||||
const EN_CUE =
|
||||
"\\b(?:consider|try|recommend|suggest|worth|might\\s+want\\s+to|may\\s+want\\s+to|need\\s+to|time\\s+to|i'?d|i\\s+would|should|could)\\b";
|
||||
|
||||
const EN_MED =
|
||||
"(?:dose|doses|dosage|pill|pills|tablet|tablets|capsule|capsules|medication|medications|medicine|meds|insulin|injection|injections|shot|jab|inhaler)\\b";
|
||||
const DE_MED =
|
||||
"\\S*(?:dosis|tablette\\w*|medikament\\w*|medikation|spritze\\w*|insulin|pille\\w*|kapsel\\w*)";
|
||||
const FR_MED =
|
||||
"(?:doses?|comprimés?|médicaments?|traitement|insuline|injections?|piqûres?|pilules?|gélules?|cachets?)";
|
||||
const ES_MED =
|
||||
"(?:dosis|pastillas?|comprimidos?|medicamentos?|medicaci[óo]n|insulina|inyecci[óo]n|inyecciones|c[áa]psulas?|tratamiento)";
|
||||
const IT_MED =
|
||||
"(?:dos[ei]|compress[ae]|pastigli[ae]|farmac[oi]|medicinal[ei]|insulina|iniezion[ei]|capsul[ae]|terapia)";
|
||||
const PL_MED =
|
||||
"(?: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.,;:!?]|$)";
|
||||
|
||||
/**
|
||||
* Korean dosage-form counters. They attach to the digit with no space ("2정",
|
||||
* "10단위"); `정` is guarded against "정상" (normal) and `알` against "알레르기".
|
||||
*/
|
||||
const KO_COUNT = "(?:단위|유닛|캡슐|정(?!상|도|확|말)|알(?!레))";
|
||||
const KO_TARGET = `[\\d.,]+(?:\\s*${DRUG_UNIT}${UNIT_END}|${KO_COUNT})`;
|
||||
|
||||
/**
|
||||
* A Korean imperative or recommendation that closes a dose clause. The stem
|
||||
* must meet the polite-imperative ending directly, so the prohibitive
|
||||
* "건너뛰지 마세요" (don't skip) and "줄이지 마세요" (don't lower) never match.
|
||||
*/
|
||||
const KO_IMPERATIVE =
|
||||
"(?:(?:늘리|올리|높이|줄이|내리|낮추|증량하|감량하|중단하|끊으|건너뛰|멈추|복용하|드시|드|투여하|맞으|주사하|바꾸|변경하)(?:세요|십시오|시기\\s*바랍니다|셔야|는\\s*(?:것이|게)\\s*좋)|(?:늘려|올려|높여|줄여|내려|낮춰|끊어|멈춰|바꿔|드셔|복용해|투여해|증량해|감량해|중단해)\\s*(?:보세요|주세요|보십시오|야\\s*(?:해요|합니다)))";
|
||||
const KO_MED = "(?:약을|약은|약\\s|알약|정제|인슐린|주사|용량|복용량|투약)";
|
||||
|
||||
/**
|
||||
* Dose-prescription banks. Each entry requires a CHANGE verb plus a target
|
||||
@@ -83,21 +187,195 @@ const DOSE_UNIT =
|
||||
const DE_DOSE_OBJECT =
|
||||
"(?:\\s+(?:(?:deine|die|ihre|eure|seine)\\s+)?(?:dosis|dosierung|medikation|wochendosis|tagesdosis))?";
|
||||
|
||||
/**
|
||||
* The dose-change class, per locale. The banks below this one were written
|
||||
* one reported sentence at a time and each fixed the exact shape it was handed,
|
||||
* so "Increase your metformin to 2000 mg daily" passed: the English change
|
||||
* pattern let only "it" or "your dose" stand between the verb and "to", and a
|
||||
* drug name is neither. These patterns state the class instead, in three
|
||||
* shapes every locale shares:
|
||||
*
|
||||
* 1. a change verb in its imperative / infinitive form, up to four words of
|
||||
* object (a drug name, "your dose", "the evening insulin"), and a target
|
||||
* dose — "increase your metformin to 2000 mg", "réduisez votre insuline
|
||||
* à 10 unités";
|
||||
* 2. an imperative to take or inject a stated amount — "take 2000 mg daily",
|
||||
* "nimm 2 Tabletten", "prenda 1000 mg";
|
||||
* 3. an imperative to halve, double, skip, pause or stop a medication, which
|
||||
* needs no number at all — "skip your evening dose", "stop taking
|
||||
* metformin". A medication noun is required except for the explicit
|
||||
* "stop taking X" form, so "skip your walk" and "double your steps" pass.
|
||||
*
|
||||
* Verb forms are imperative or infinitive on purpose: the past forms a log
|
||||
* restatement uses ("your dose was increased to 2000 mg", "du nimmst 1000 mg")
|
||||
* do not match, and the subject pronoun of a present-tense description ("you
|
||||
* take", "vous prenez") is outside every lead-in. A clinician referral without
|
||||
* a target ("ask your doctor whether to increase your dose") names no dose and
|
||||
* passes; one that names a target ("… whether to increase to 2000 mg") stays
|
||||
* blocked, the same accepted fail-safe residual as "discussing the 2.4 mg step".
|
||||
*/
|
||||
const DOSE_CHANGE_CLASS: Record<Locale, readonly RegExp[]> = {
|
||||
en: [
|
||||
new RegExp(
|
||||
`\\b(?:increase|raise|up|bump|boost|titrate|step|move|go|ramp|push|switch|change|adjust|lower|reduce|cut|drop|decrease|taper|halve|double|trim|bring)\\s+${EN_SLOT}(?:from\\s+${TARGET}\\s+)?(?:up\\s+|down\\s+|back\\s+)?(?:to|by)\\s+${TARGET}`,
|
||||
"i",
|
||||
),
|
||||
new RegExp(
|
||||
`${EN_CUE}[^.?!]{0,20}?\\b(?:increas(?:e|ing)|rais(?:e|ing)|bump(?:ing)?|boost(?:ing)?|titrat(?:e|ing)|lower(?:ing)?|reduc(?:e|ing)|cut(?:ting)?|drop(?:ping)?|decreas(?:e|ing)|taper(?:ing)?|halv(?:e|ing)|doubl(?:e|ing)|switch(?:ing)?|adjust(?:ing)?|go(?:ing)?\\s+up|step(?:ping)?\\s+up)\\s+${EN_SLOT}(?:from\\s+${TARGET}\\s+)?(?:up\\s+|down\\s+)?(?:to|by)\\s+${TARGET}`,
|
||||
"i",
|
||||
),
|
||||
new RegExp(
|
||||
`${EN_LEAD}(?:take|start\\s+(?:taking|on|with|at)|begin\\s+(?:taking|with|at)|inject|use|give\\s+yourself)\\s+${EN_SLOT}${TARGET}`,
|
||||
"im",
|
||||
),
|
||||
new RegExp(
|
||||
`${EN_LEAD}(?:halve|double|skip|pause|hold|drop|miss|split|cut)\\s+${EN_SLOT}${EN_MED}`,
|
||||
"im",
|
||||
),
|
||||
new RegExp(
|
||||
`${EN_LEAD}(?:(?:(?:stop|quit)\\s+(?:taking|injecting)|discontinue)\\s+\\S|(?:come|wean\\s+(?:yourself\\s+)?)\\s*off\\s+${EN_SLOT}${EN_MED})`,
|
||||
"im",
|
||||
),
|
||||
new RegExp(
|
||||
`${EN_CUE}[^.?!]{0,20}?\\b(?:(?:halv|doubl|skipp|paus|hold|dropp|miss|splitt|cutt)ing\\s+${EN_SLOT}${EN_MED}|(?:stopping|quitting)\\s+(?:taking|injecting)\\s+\\S|discontinuing\\s+\\S|coming\\s+off\\s+${EN_SLOT}${EN_MED})`,
|
||||
"i",
|
||||
),
|
||||
],
|
||||
de: [
|
||||
new RegExp(
|
||||
`\\b(?:erhöh(?:e|en)?|steiger(?:e|n)?|reduzier(?:e|en)?|senk(?:e|en)?|verringer(?:e|n)?|halbier(?:e|en)?|verdopp(?:e?le|eln)|stell(?:e|en)?|geh(?:e)?|setz(?:e|en)?|bring(?:e|en)?)(?:\\s+sie)?\\s+${DE_SLOT}(?:(?:hoch|rauf|runter|herunter)\\s+)?(?:auf|um)\\s+${TARGET}`,
|
||||
"i",
|
||||
),
|
||||
new RegExp(
|
||||
`\\b(?:nimm|nehmen\\s+sie|spritz|spritzen\\s+sie|injizier(?:e)?|injizieren\\s+sie|beginn(?:e)?\\s+mit|beginnen\\s+sie\\s+mit|starte\\s+mit)\\s+${DE_SLOT}${TARGET}`,
|
||||
"i",
|
||||
),
|
||||
new RegExp(
|
||||
`\\b(?:halbier(?:e|en)?|verdopp(?:e?le|eln)|pausier(?:e|en)?|überspring(?:e|en)?)(?:\\s+sie)?\\s+${DE_SLOT}${DE_MED}`,
|
||||
"i",
|
||||
),
|
||||
new RegExp(
|
||||
`\\b(?:setz(?:e|en)?)(?:\\s+sie)?(?:\\s+(?!(?:nicht|nie|niemals|kein\\w*)(?=\\s))[^\\s.?!,;:]+){1,4}?\\s+ab(?=\\s*(?:[.?!,;:]|$|und\\s|oder\\s))`,
|
||||
"i",
|
||||
),
|
||||
new RegExp(
|
||||
`\\b(?:lass|lassen\\s+sie)\\s+${DE_SLOT}${DE_MED}(?:(?!\\b(?:nicht|nie|niemals)\\b)[^.?!]){0,20}?\\b(?:aus|weg)\\b`,
|
||||
"i",
|
||||
),
|
||||
new RegExp(
|
||||
`\\bh(?:ör(?:e)?|ören\\s+sie)\\s+(?:mit\\s+\\S+\\s+)?auf\\b[^.?!]{0,40}?\\b(?:zu\\s+nehmen|einzunehmen|zu\\s+spritzen)`,
|
||||
"i",
|
||||
),
|
||||
new RegExp(
|
||||
`\\b(?:solltest|sollten\\s+sie|empfehle|rate\\s+(?:dir|ihnen)|würde\\s+ich)\\b(?:(?!\\b(?:nicht|nie|niemals|kein\\w*)\\b)[^.?!]){0,40}?\\b(?:absetzen|abzusetzen|halbieren|zu\\s+halbieren|verdoppeln|zu\\s+verdoppeln|auslassen|auszulassen|weglassen|wegzulassen|pausieren|zu\\s+pausieren)\\b`,
|
||||
"i",
|
||||
),
|
||||
],
|
||||
fr: [
|
||||
new RegExp(
|
||||
`(?<!\\b(?:vous|nous|je|tu|il|elle|on)\\s)(?<!n['’])\\b(?:(?:augment|mont|pass|port|major|diminu|baiss|abaiss|doubl|ramen)(?:ez|e|er)|r[ée]dui(?:sez|s|re))\\s+${FR_SLOT}(?:de\\s+${TARGET}\\s+)?(?:[àa]|de|jusqu'[àa])\\s+${TARGET}`,
|
||||
"i",
|
||||
),
|
||||
new RegExp(
|
||||
`(?<!\\b(?:vous|nous|je|tu|il|elle|on)\\s)\\b(?:prenez|prends|injectez|injecte|commencez\\s+(?:par|à|avec))\\s+${FR_SLOT}${TARGET}`,
|
||||
"i",
|
||||
),
|
||||
new RegExp(
|
||||
`(?<!\\b(?:vous|nous|je|tu|il|elle|on)\\s)(?<!n['’])\\b(?:divisez|doublez|sautez|suspendez|interrompez|oubliez|arrêtez|stoppez|supprimez)\\s+${FR_SLOT}${FR_MED}`,
|
||||
"i",
|
||||
),
|
||||
new RegExp(
|
||||
`(?<!n['’])\\b(?:arrêtez|cessez|stoppez)\\s+de\\s+(?:prendre|vous\\s+injecter)\\b`,
|
||||
"i",
|
||||
),
|
||||
new RegExp(
|
||||
`\\b(?:vous\\s+devriez|je\\s+recommande\\s+de|je\\s+sugg[èe]re\\s+de|envisagez\\s+de|essayez\\s+de)\\s+(?:sauter|suspendre|arrêter|interrompre|diviser|doubler)\\s+${FR_SLOT}(?:de\\s+prendre\\s+\\S|${FR_MED})`,
|
||||
"i",
|
||||
),
|
||||
],
|
||||
es: [
|
||||
new RegExp(
|
||||
`(?<!\\b(?:no|nunca)\\s)\\b(?:aumente|aumenta|aumentar|suba|sube|subir|pase|pasar|incremente|incrementar|reduzca|reduce|reducir|baje|bajar|disminuya|disminuir|duplique|duplicar|lleve|llevar|ajuste|ajustar)\\s+${ES_SLOT}(?:de\\s+${TARGET}\\s+)?(?:a|en|hasta)\\s+${TARGET}`,
|
||||
"i",
|
||||
),
|
||||
new RegExp(
|
||||
`(?<!\\b(?:no|nunca)\\s)\\b(?:tome|tómese|inyecte|inyéctese|administre|empiece\\s+con|comience\\s+con)\\s+${ES_SLOT}${TARGET}`,
|
||||
"i",
|
||||
),
|
||||
new RegExp(
|
||||
`(?<!\\b(?:no|nunca)\\s)\\b(?:omita|sáltese|salte|suspenda|interrumpa|duplique|divida|parta)\\s+${ES_SLOT}${ES_MED}`,
|
||||
"i",
|
||||
),
|
||||
new RegExp(
|
||||
`(?<!\\b(?:no|nunca)\\s)\\b(?:deje|deja|dejar)\\s+de\\s+(?:tomar|usar|inyectarse)\\b`,
|
||||
"i",
|
||||
),
|
||||
],
|
||||
it: [
|
||||
new RegExp(
|
||||
`(?<!\\b(?:non|mai)\\s)\\b(?:aumenti|aumentare|incrementi|incrementare|porti|portare|passi|passare|salga|salire|riduca|ridurre|abbassi|abbassare|diminuisca|diminuire|dimezzi|dimezzare|raddoppi|raddoppiare)\\s+${IT_SLOT}(?:da\\s+${TARGET}\\s+)?(?:a|di|fino\\s+a)\\s+${TARGET}`,
|
||||
"i",
|
||||
),
|
||||
new RegExp(
|
||||
`(?<!\\b(?:non|mai)\\s)\\b(?:prenda|assuma|inietti|si\\s+inietti|inizi\\s+con|cominci\\s+con)\\s+${IT_SLOT}${TARGET}`,
|
||||
"i",
|
||||
),
|
||||
new RegExp(
|
||||
`(?<!\\b(?:non|mai)\\s)\\b(?:salti|sospenda|interrompa|dimezzi|raddoppi|divida|ometta|tralasci)\\s+${IT_SLOT}${IT_MED}`,
|
||||
"i",
|
||||
),
|
||||
new RegExp(
|
||||
`(?<!\\b(?:non|mai)\\s)\\bsmett(?:a|i|ere)\\s+di\\s+(?:prendere|assumere|usare)\\b`,
|
||||
"i",
|
||||
),
|
||||
],
|
||||
pl: [
|
||||
new RegExp(
|
||||
`(?<!\\bnie\\s)\\b(?:zwi[ęe]ksz(?:y[ćc]|cie)?|podnie[śs](?:[ćc]|cie)?|podwy[żz]sz(?:y[ćc])?|zmniejsz(?:y[ćc]|cie)?|obni[żz](?:y[ćc]|cie)?|zredukuj(?:cie)?|zredukowa[ćc]|przejd[źz](?:cie)?|przej[śs][ćc]|zmie[ńn]|zmieni[ćc])(?=\\s)\\s+${PL_SLOT}(?:z\\s+${TARGET}\\s+)?(?:do|o|na)\\s+${TARGET}`,
|
||||
"i",
|
||||
),
|
||||
new RegExp(
|
||||
`(?:(?<!\\bnie\\s)\\b(?:przyjmij|przyjmuj|we[źz]|bierz|wstrzyknij|wstrzykuj|zacznij\\s+od)|\\b(?:proszę|należy|warto)\\s+(?:przyj[ąa][ćc]|wzi[ąa][ćc]|bra[ćc]|przyjmowa[ćc]|wstrzykiwa[ćc]|wstrzykn[ąa][ćc]|zacz[ąa][ćc]\\s+od))(?=\\s)\\s+${PL_SLOT}${TARGET}`,
|
||||
"i",
|
||||
),
|
||||
new RegExp(
|
||||
`(?:(?<!\\bnie\\s)\\b(?:pomi[ńn](?:cie)?|opu[śs][ćc]|odstaw(?:cie)?|wstrzymaj|podziel|podwój)|\\b(?:proszę|należy|warto)\\s+(?:pomin[ąa][ćc]|odstawi[ćc]|wstrzyma[ćc]|przerwa[ćc]|podzieli[ćc]|podwoi[ćc]))(?=\\s)\\s+${PL_SLOT}${PL_MED}`,
|
||||
"i",
|
||||
),
|
||||
new RegExp(
|
||||
`(?:(?<!\\bnie\\s)\\bprzesta[ńn](?:cie)?|\\b(?:proszę|należy)\\s+przesta[ćc])\\s+(?:brać|przyjmować|stosować)(?=[\\s.,;:!?]|$)`,
|
||||
"i",
|
||||
),
|
||||
],
|
||||
ko: [
|
||||
// A stated amount closed by an imperative or a recommendation, with or
|
||||
// without a target particle: "2000mg으로 증량하세요", "하루 2정 드세요".
|
||||
new RegExp(`${KO_TARGET}[^.?!]{0,25}${KO_IMPERATIVE}`, "i"),
|
||||
// A medication object halved, doubled, skipped or stopped by imperative.
|
||||
new RegExp(
|
||||
`${KO_MED}[^.?!]{0,20}(?:(?:절반|반)\\s*으로\\s*(?:줄이|나누)(?:세요|십시오)|(?:절반|반)\\s*으로\\s*(?:줄여|나눠)\\s*(?:보세요|주세요)|두\\s?배로\\s*(?:늘리|올리)(?:세요|십시오)|두\\s?배로\\s*(?:늘려|올려)\\s*(?:보세요|주세요)|(?:건너뛰|중단하|끊으|멈추|그만\\s*드시|그만\\s*복용하)(?:세요|십시오))`,
|
||||
),
|
||||
new RegExp(
|
||||
`(?:복용|투약|투여|주사)(?:을|를)?\\s*(?:중단하|멈추|그만하|끊으)(?:세요|십시오)`,
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
const DOSE_PATTERNS: Record<Locale, readonly RegExp[]> = {
|
||||
en: [
|
||||
// step/move/increase/raise/bump/titrate/go up to|by N unit
|
||||
new RegExp(
|
||||
`\\b(?:step|move|increase|raise|bump|titrat\\w*|go|ramp|push|up)\\s+(?:it\\s+)?(?:up\\s+|your\\s+dose\\s+)?(?:to|by)\\s+[\\d.,]+\\s*${DOSE_UNIT}\\b`,
|
||||
`\\b(?:step|move|increase|raise|bump|titrat\\w*|go|ramp|push|up)\\s+(?:it\\s+)?(?:up\\s+|your\\s+dose\\s+)?(?:to|by)\\s+[\\d.,]+\\s*${DOSE_UNIT}${UNIT_END}`,
|
||||
"i",
|
||||
),
|
||||
// lower/reduce/cut/drop/decrease/back off/taper(ing) to|by N unit
|
||||
new RegExp(
|
||||
`\\b(?:lower|reduce|cut|drop|decrease|back\\s+off|taper\\w*)\\s+(?:it\\s+|your\\s+dose\\s+)?(?:to|by)\\s+[\\d.,]+\\s*${DOSE_UNIT}\\b`,
|
||||
`\\b(?:lower|reduce|cut|drop|decrease|back\\s+off|taper\\w*)\\s+(?:it\\s+|your\\s+dose\\s+)?(?:to|by)\\s+[\\d.,]+\\s*${DOSE_UNIT}${UNIT_END}`,
|
||||
"i",
|
||||
),
|
||||
// consider/try/should/recommend ... N unit
|
||||
new RegExp(
|
||||
`\\b(?:consider|try|you\\s+should|i'?d?\\s+recommend|i\\s+suggest)\\b[^.?!]{0,40}\\b[\\d.,]+\\s*${DOSE_UNIT}\\b`,
|
||||
`\\b(?:consider|try|you\\s+should|i'?d?\\s+recommend|i\\s+suggest)\\b[^.?!]{0,40}\\b[\\d.,]+\\s*${DOSE_UNIT}${UNIT_END}`,
|
||||
"i",
|
||||
),
|
||||
// Experiment-shaped dose changes the "to/by N unit" patterns miss. BOTH a
|
||||
@@ -107,26 +385,26 @@ const DOSE_PATTERNS: Record<Locale, readonly RegExp[]> = {
|
||||
],
|
||||
de: [
|
||||
new RegExp(
|
||||
`\\b(?:erhöh\\w*|steiger\\w*|setz\\w*\\s+(?:hoch|rauf)|geh\\w*\\s+(?:hoch|rauf))${DE_DOSE_OBJECT}\\s+(?:auf|um)\\s+[\\d.,]+\\s*${DOSE_UNIT}\\b`,
|
||||
`\\b(?:erhöh\\w*|steiger\\w*|setz\\w*\\s+(?:hoch|rauf)|geh\\w*\\s+(?:hoch|rauf))${DE_DOSE_OBJECT}\\s+(?:auf|um)\\s+[\\d.,]+\\s*${DOSE_UNIT}${UNIT_END}`,
|
||||
"i",
|
||||
),
|
||||
new RegExp(
|
||||
`\\b(?:reduzier\\w*|senk\\w*|verringer\\w*|nimm\\s+(?:weniger|runter))${DE_DOSE_OBJECT}\\s+(?:auf|um)\\s+[\\d.,]+\\s*${DOSE_UNIT}\\b`,
|
||||
`\\b(?:reduzier\\w*|senk\\w*|verringer\\w*|nimm\\s+(?:weniger|runter))${DE_DOSE_OBJECT}\\s+(?:auf|um)\\s+[\\d.,]+\\s*${DOSE_UNIT}${UNIT_END}`,
|
||||
"i",
|
||||
),
|
||||
new RegExp(
|
||||
`\\bn[äa]chste\\s+(?:stufe|dosis)\\b[^.?!]{0,30}\\b[\\d.,]+\\s*${DOSE_UNIT}\\b`,
|
||||
`\\bn[äa]chste\\s+(?:stufe|dosis)\\b[^.?!]{0,30}\\b[\\d.,]+\\s*${DOSE_UNIT}${UNIT_END}`,
|
||||
"i",
|
||||
),
|
||||
// German puts the verb last in a subordinate clause ("auf 7,5 mg zu
|
||||
// erhöhen"), so the verb-first patterns above miss the most natural
|
||||
// phrasing of the instruction. Match the inverted order too.
|
||||
new RegExp(
|
||||
`\\b(?:auf|um)\\s+[\\d.,]+\\s*${DOSE_UNIT}\\b[^.?!]{0,30}\\b(?:erhöh\\S*|steiger\\S*|reduzier\\S*|senk\\S*|verringer\\S*|hochsetz\\S*)`,
|
||||
`\\b(?:auf|um)\\s+[\\d.,]+\\s*${DOSE_UNIT}${UNIT_END}[^.?!]{0,30}\\b(?:erhöh\\S*|steiger\\S*|reduzier\\S*|senk\\S*|verringer\\S*|hochsetz\\S*)`,
|
||||
"i",
|
||||
),
|
||||
new RegExp(
|
||||
`\\b(?:erwäg\\w*|probier\\w*|du\\s+solltest|ich\\s+empfehle|ich\\s+schlage\\s+vor)\\b[^.?!]{0,40}\\b[\\d.,]+\\s*${DOSE_UNIT}\\b`,
|
||||
`\\b(?:erwäg\\w*|probier\\w*|du\\s+solltest|ich\\s+empfehle|ich\\s+schlage\\s+vor)\\b[^.?!]{0,40}\\b[\\d.,]+\\s*${DOSE_UNIT}${UNIT_END}`,
|
||||
"i",
|
||||
),
|
||||
/\b(?:halbier\w*|verdoppel\w*|lass\w*\s+aus|setz\w*\s+ab|pausier\w*)\b[^.?!]{0,25}\b(?:dosis|tablette\w*|medikament\w*|spritze\w*|insulin)\b[^.?!]{0,40}\b(?:für\s+\w+\s+(?:tag|tage|woche|wochen|monat\w*)|um\s+zu\s+(?:sehen|testen)|zum\s+(?:test|ausprobieren))\b/i,
|
||||
@@ -134,80 +412,80 @@ const DOSE_PATTERNS: Record<Locale, readonly RegExp[]> = {
|
||||
fr: [
|
||||
// augmentez / montez / passez / portez à|de N unit
|
||||
new RegExp(
|
||||
`\\b(?:augment\\w*|mont\\w*|pass\\w*|port\\w*|majo\\w*)\\s+(?:votre\\s+dose\\s+)?(?:[àa]|de|jusqu'[àa])\\s+[\\d.,]+\\s*${DOSE_UNIT}\\b`,
|
||||
`\\b(?:augment\\w*|mont\\w*|pass\\w*|port\\w*|majo\\w*)\\s+(?:votre\\s+dose\\s+)?(?:[àa]|de|jusqu'[àa])\\s+[\\d.,]+\\s*${DOSE_UNIT}${UNIT_END}`,
|
||||
"i",
|
||||
),
|
||||
// réduisez / diminuez / baissez à|de N unit
|
||||
new RegExp(
|
||||
`\\b(?:r[ée]duis\\w*|r[ée]duire|diminu\\w*|baiss\\w*|abaiss\\w*)\\s+(?:votre\\s+dose\\s+)?(?:[àa]|de|jusqu'[àa])\\s+[\\d.,]+\\s*${DOSE_UNIT}\\b`,
|
||||
`\\b(?:r[ée]duis\\w*|r[ée]duire|diminu\\w*|baiss\\w*|abaiss\\w*)\\s+(?:votre\\s+dose\\s+)?(?:[àa]|de|jusqu'[àa])\\s+[\\d.,]+\\s*${DOSE_UNIT}${UNIT_END}`,
|
||||
"i",
|
||||
),
|
||||
new RegExp(
|
||||
`\\b(?:envisagez|essayez|vous\\s+devriez|je\\s+recommande|je\\s+sugg[èe]re)\\b[^.?!]{0,40}\\b[\\d.,]+\\s*${DOSE_UNIT}\\b`,
|
||||
`\\b(?:envisagez|essayez|vous\\s+devriez|je\\s+recommande|je\\s+sugg[èe]re)\\b[^.?!]{0,40}\\b[\\d.,]+\\s*${DOSE_UNIT}${UNIT_END}`,
|
||||
"i",
|
||||
),
|
||||
new RegExp(
|
||||
`\\b(?:prochaine|nouvelle)\\s+dose\\b[^.?!]{0,30}\\b[\\d.,]+\\s*${DOSE_UNIT}\\b`,
|
||||
`\\b(?:prochaine|nouvelle)\\s+dose\\b[^.?!]{0,30}\\b[\\d.,]+\\s*${DOSE_UNIT}${UNIT_END}`,
|
||||
"i",
|
||||
),
|
||||
],
|
||||
es: [
|
||||
// aumente / suba / pase a|en N unit
|
||||
new RegExp(
|
||||
`\\b(?:aument\\w*|sub\\w*|pas\\w*|increment\\w*)\\s+(?:su\\s+dosis\\s+)?(?:a|en|hasta)\\s+[\\d.,]+\\s*${DOSE_UNIT}\\b`,
|
||||
`\\b(?:aument\\w*|sub\\w*|pas\\w*|increment\\w*)\\s+(?:su\\s+dosis\\s+)?(?:a|en|hasta)\\s+[\\d.,]+\\s*${DOSE_UNIT}${UNIT_END}`,
|
||||
"i",
|
||||
),
|
||||
// reduzca / baje / disminuya a|en N unit
|
||||
new RegExp(
|
||||
`\\b(?:reduzc\\w*|reduc\\w*|baj\\w*|disminu\\w*)\\s+(?:su\\s+dosis\\s+)?(?:a|en|hasta)\\s+[\\d.,]+\\s*${DOSE_UNIT}\\b`,
|
||||
`\\b(?:reduzc\\w*|reduc\\w*|baj\\w*|disminu\\w*)\\s+(?:su\\s+dosis\\s+)?(?:a|en|hasta)\\s+[\\d.,]+\\s*${DOSE_UNIT}${UNIT_END}`,
|
||||
"i",
|
||||
),
|
||||
new RegExp(
|
||||
`\\b(?:considere|pruebe|deber[íi]a|recomiendo|sugiero)\\b[^.?!]{0,40}\\b[\\d.,]+\\s*${DOSE_UNIT}\\b`,
|
||||
`\\b(?:considere|pruebe|deber[íi]a|recomiendo|sugiero)\\b[^.?!]{0,40}\\b[\\d.,]+\\s*${DOSE_UNIT}${UNIT_END}`,
|
||||
"i",
|
||||
),
|
||||
new RegExp(
|
||||
`\\b(?:pr[óo]xima|siguiente|nueva)\\s+dosis\\b[^.?!]{0,30}\\b[\\d.,]+\\s*${DOSE_UNIT}\\b`,
|
||||
`\\b(?:pr[óo]xima|siguiente|nueva)\\s+dosis\\b[^.?!]{0,30}\\b[\\d.,]+\\s*${DOSE_UNIT}${UNIT_END}`,
|
||||
"i",
|
||||
),
|
||||
],
|
||||
it: [
|
||||
// aumenti / salga / passi a|di N unit
|
||||
new RegExp(
|
||||
`\\b(?:aument\\w*|sal\\w*|pass\\w*|increment\\w*)\\s+(?:la\\s+sua\\s+dose\\s+)?(?:a|di|fino\\s+a)\\s+[\\d.,]+\\s*${DOSE_UNIT}\\b`,
|
||||
`\\b(?:aument\\w*|sal\\w*|pass\\w*|increment\\w*)\\s+(?:la\\s+sua\\s+dose\\s+)?(?:a|di|fino\\s+a)\\s+[\\d.,]+\\s*${DOSE_UNIT}${UNIT_END}`,
|
||||
"i",
|
||||
),
|
||||
// riduca / abbassi / diminuisca a|di N unit
|
||||
new RegExp(
|
||||
`\\b(?:riduc\\w*|ridur\\w*|abbass\\w*|diminu\\w*|cal\\w*)\\s+(?:la\\s+sua\\s+dose\\s+)?(?:a|di|fino\\s+a)\\s+[\\d.,]+\\s*${DOSE_UNIT}\\b`,
|
||||
`\\b(?:riduc\\w*|ridur\\w*|abbass\\w*|diminu\\w*|cal\\w*)\\s+(?:la\\s+sua\\s+dose\\s+)?(?:a|di|fino\\s+a)\\s+[\\d.,]+\\s*${DOSE_UNIT}${UNIT_END}`,
|
||||
"i",
|
||||
),
|
||||
new RegExp(
|
||||
`\\b(?:consideri|provi|dovrebbe|raccomando|suggerisco)\\b[^.?!]{0,40}\\b[\\d.,]+\\s*${DOSE_UNIT}\\b`,
|
||||
`\\b(?:consideri|provi|dovrebbe|raccomando|suggerisco)\\b[^.?!]{0,40}\\b[\\d.,]+\\s*${DOSE_UNIT}${UNIT_END}`,
|
||||
"i",
|
||||
),
|
||||
new RegExp(
|
||||
`\\b(?:prossima|nuova)\\s+dose\\b[^.?!]{0,30}\\b[\\d.,]+\\s*${DOSE_UNIT}\\b`,
|
||||
`\\b(?:prossima|nuova)\\s+dose\\b[^.?!]{0,30}\\b[\\d.,]+\\s*${DOSE_UNIT}${UNIT_END}`,
|
||||
"i",
|
||||
),
|
||||
],
|
||||
pl: [
|
||||
// zwiększ / podnieś / przejdź do|o N unit
|
||||
new RegExp(
|
||||
`\\b(?:zwi[ęe]ksz\\S*|podnie[śs]\\S*|przejd[źz]\\S*|podwy[żz]sz\\S*)\\s+(?:dawk\\S*\\s+)?(?:do|o)\\s+[\\d.,]+\\s*${DOSE_UNIT}\\b`,
|
||||
`\\b(?:zwi[ęe]ksz\\S*|podnie[śs]\\S*|przejd[źz]\\S*|podwy[żz]sz\\S*)\\s+(?:dawk\\S*\\s+)?(?:do|o)\\s+[\\d.,]+\\s*${DOSE_UNIT}${UNIT_END}`,
|
||||
"i",
|
||||
),
|
||||
// zmniejsz / obniż / zredukuj do|o N unit
|
||||
new RegExp(
|
||||
`\\b(?:zmniejsz\\S*|obni[żz]\\S*|zreduk\\S*|reduk\\S*)\\s+(?:dawk\\S*\\s+)?(?:do|o)\\s+[\\d.,]+\\s*${DOSE_UNIT}\\b`,
|
||||
`\\b(?:zmniejsz\\S*|obni[żz]\\S*|zreduk\\S*|reduk\\S*)\\s+(?:dawk\\S*\\s+)?(?:do|o)\\s+[\\d.,]+\\s*${DOSE_UNIT}${UNIT_END}`,
|
||||
"i",
|
||||
),
|
||||
new RegExp(
|
||||
`\\b(?:rozwa[żz]\\S*|spr[óo]buj\\S*|powinien|powinna|zalecam|sugeruj[ęe])[^.?!]{0,40}\\b[\\d.,]+\\s*${DOSE_UNIT}\\b`,
|
||||
`\\b(?:rozwa[żz]\\S*|spr[óo]buj\\S*|powinien|powinna|zalecam|sugeruj[ęe])[^.?!]{0,40}\\b[\\d.,]+\\s*${DOSE_UNIT}${UNIT_END}`,
|
||||
"i",
|
||||
),
|
||||
new RegExp(
|
||||
`\\b(?:nast[ęe]pna|kolejna|nowa)\\s+dawka\\b[^.?!]{0,30}\\b[\\d.,]+\\s*${DOSE_UNIT}\\b`,
|
||||
`\\b(?:nast[ęe]pna|kolejna|nowa)\\s+dawka\\b[^.?!]{0,30}\\b[\\d.,]+\\s*${DOSE_UNIT}${UNIT_END}`,
|
||||
"i",
|
||||
),
|
||||
], // Korean is verb-final: the dose comes first and the change verb closes the
|
||||
@@ -225,15 +503,15 @@ const DOSE_PATTERNS: Record<Locale, readonly RegExp[]> = {
|
||||
"i",
|
||||
),
|
||||
new RegExp(
|
||||
`(?:권해|권장|권고|추천|제안)[^.?!]{0,40}[\\d.,]+\\s*${DOSE_UNIT}\\b`,
|
||||
`(?:권해|권장|권고|추천|제안)[^.?!]{0,40}[\\d.,]+\\s*${DOSE_UNIT}${UNIT_END}`,
|
||||
"i",
|
||||
),
|
||||
new RegExp(
|
||||
`[\\d.,]+\\s*${DOSE_UNIT}\\b[^.?!]{0,40}(?:권해|권장|권고|추천|제안|드심|드셔|복용하세요|시도해)`,
|
||||
`[\\d.,]+\\s*${DOSE_UNIT}${UNIT_END}[^.?!]{0,40}(?:권해|권장|권고|추천|제안|드심|드셔|복용하세요|시도해)`,
|
||||
"i",
|
||||
),
|
||||
new RegExp(
|
||||
`(?:다음|새)\\s*(?:용량|단계|복용량)[^.?!]{0,30}[\\d.,]+\\s*${DOSE_UNIT}\\b`,
|
||||
`(?:다음|새)\\s*(?:용량|단계|복용량)[^.?!]{0,30}[\\d.,]+\\s*${DOSE_UNIT}${UNIT_END}`,
|
||||
"i",
|
||||
),
|
||||
// Both a medication object AND a trial cue are required, as in the EN bank,
|
||||
@@ -619,8 +897,8 @@ export const INSIGHTS_CONTRACTS: readonly OutboundContract[] = [
|
||||
* step with your doctor" stays blocked ("step" is a change stem).
|
||||
*/
|
||||
const DOSE_CONTINUATION: Record<Locale, RegExp> = {
|
||||
en: /\b(?:keep\s+taking|keep\s+on\s+taking|continue\s+(?:taking|with|on)|stay(?:ing)?\s+(?:on|at)|remain(?:ing)?\s+on|as\s+prescribed|as\s+directed)\b/i,
|
||||
de: /\b(?:weiterhin|weiter\s+(?:einnehmen|nehmen)|nimm\s+weiter|beibehalten|behalte\s+bei|wie\s+(?:verordnet|verschrieben|besprochen)|bleib(?:e|st)?\s+bei)\b/i,
|
||||
en: /\b(?:keep\s+taking|keep\s+on\s+taking|continue\s+(?:taking|with|on)|stay(?:ing)?\s+(?:on|at)|remain(?:ing)?\s+on|as\s+prescribed|as\s+directed|as\s+usual|as\s+before|as\s+you\s+have\s+been)\b/i,
|
||||
de: /\b(?:weiterhin|weiter\s+(?:einnehmen|nehmen)|nimm\s+weiter|beibehalten|behalte\s+bei|wie\s+(?:verordnet|verschrieben|besprochen|gewohnt|bisher)|bleib(?:e|st)?\s+bei)\b/i,
|
||||
fr: /\b(?:continuez?\s+(?:à|de|le)|gardez|comme\s+prescrit|tel\s+que\s+prescrit)\b/i,
|
||||
es: /\b(?:siga\s+(?:tomando|con)|continúe|mantenga|según\s+lo\s+prescrito)\b/i,
|
||||
it: /\b(?:continui\s+(?:a|con|il)|mantenga|come\s+prescritto)\b/i,
|
||||
@@ -644,7 +922,7 @@ function splitSentences(text: string): string[] {
|
||||
}
|
||||
|
||||
/** Every leading-magnitude dose value in one sentence ("keep your 7.5 mg" → [7.5]). */
|
||||
const DOSE_VALUE_RE = new RegExp(`([\\d.,]+)\\s*${DOSE_UNIT}\\b`, "gi");
|
||||
const DOSE_VALUE_RE = new RegExp(`([\\d.,]+)\\s*${DOSE_UNIT}${UNIT_END}`, "gi");
|
||||
function sentenceDoseValues(sentence: string): number[] {
|
||||
const values: number[] = [];
|
||||
let m: RegExpExecArray | null;
|
||||
@@ -685,7 +963,8 @@ function doseTrips(
|
||||
scheduleDoses?: readonly number[],
|
||||
): boolean {
|
||||
const bank = BANKS.dose;
|
||||
const patterns = locale === "en" ? bank.en : [...bank[locale], ...bank.en];
|
||||
const own = (l: Locale) => [...bank[l], ...DOSE_CHANGE_CLASS[l]];
|
||||
const patterns = locale === "en" ? own("en") : [...own(locale), ...own("en")];
|
||||
const continuation =
|
||||
locale === "en"
|
||||
? [DOSE_CONTINUATION.en]
|
||||
|
||||
Reference in new issue
Block a user