mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-15 15:02:04 +00:00
Merge dose-change screen into release/v1.41.0
This commit is contained in:
commit
8f9c443d35
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