← back to AbramsOS
health: BP-medication context — show active meds that raise/lower blood pressure alongside the trend
355389e5e428270d3884139a53acf5c302950dec · 2026-07-13 01:10:57 -0700 · Steve
- lib/bp-meds.js: deterministic classifier (curated generic stems + names -> effect/class; never LLM-guessed) — ACE/ARB/beta-blocker/CCB/diuretic (lowers) + NSAID/decongestant/steroid/stimulant (raises)
- routes/vitals.js: read-only join to medication table, bpRelevant() into /health
- views/vitals.ejs: 'Medications that affect blood pressure' panel (color-coded lowers/raises), informational-not-advice
- works when med list is populated (currently empty)
Files touched
A lib/bp-meds.jsM routes/vitals.jsM views/vitals.ejs
Diff
commit 355389e5e428270d3884139a53acf5c302950dec
Author: Steve <steve@designerwallcoverings.com>
Date: Mon Jul 13 01:10:57 2026 -0700
health: BP-medication context — show active meds that raise/lower blood pressure alongside the trend
- lib/bp-meds.js: deterministic classifier (curated generic stems + names -> effect/class; never LLM-guessed) — ACE/ARB/beta-blocker/CCB/diuretic (lowers) + NSAID/decongestant/steroid/stimulant (raises)
- routes/vitals.js: read-only join to medication table, bpRelevant() into /health
- views/vitals.ejs: 'Medications that affect blood pressure' panel (color-coded lowers/raises), informational-not-advice
- works when med list is populated (currently empty)
---
lib/bp-meds.js | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
routes/vitals.js | 9 ++++++-
views/vitals.ejs | 14 +++++++++++
3 files changed, 94 insertions(+), 1 deletion(-)
diff --git a/lib/bp-meds.js b/lib/bp-meds.js
new file mode 100644
index 0000000..04b7a29
--- /dev/null
+++ b/lib/bp-meds.js
@@ -0,0 +1,72 @@
+// bp-meds.js — deterministic classifier: does an active medication affect blood pressure?
+//
+// Drug pharmacology is NEVER guessed by an LLM here — it's a curated map of well-known
+// generic stems / drug names to their BP effect. Matching is substring on the lowercased
+// name + generic_name. This powers the read-only "meds that affect your BP" context on
+// /health so a BP trend can be read alongside what the person actually takes.
+//
+// effect: 'lowers' — antihypertensives (taken to bring BP down)
+// 'raises' — can elevate BP as a side effect (NSAIDs, decongestants, stimulants)
+// This is context, NOT medical advice.
+
+// Explicit generic/brand names (highest confidence).
+const NAMED = [
+ // ACE inhibitors / ARBs
+ ['lisinopril', 'lowers', 'ACE inhibitor'], ['enalapril', 'lowers', 'ACE inhibitor'],
+ ['ramipril', 'lowers', 'ACE inhibitor'], ['benazepril', 'lowers', 'ACE inhibitor'],
+ ['losartan', 'lowers', 'ARB'], ['valsartan', 'lowers', 'ARB'], ['olmesartan', 'lowers', 'ARB'],
+ ['telmisartan', 'lowers', 'ARB'], ['irbesartan', 'lowers', 'ARB'],
+ // Beta-blockers
+ ['metoprolol', 'lowers', 'beta-blocker'], ['atenolol', 'lowers', 'beta-blocker'],
+ ['propranolol', 'lowers', 'beta-blocker'], ['carvedilol', 'lowers', 'beta-blocker'],
+ ['bisoprolol', 'lowers', 'beta-blocker'], ['labetalol', 'lowers', 'beta-blocker'], ['nadolol', 'lowers', 'beta-blocker'],
+ // Calcium channel blockers
+ ['amlodipine', 'lowers', 'calcium channel blocker'], ['nifedipine', 'lowers', 'calcium channel blocker'],
+ ['diltiazem', 'lowers', 'calcium channel blocker'], ['verapamil', 'lowers', 'calcium channel blocker'],
+ // Diuretics
+ ['hydrochlorothiazide', 'lowers', 'diuretic'], ['hctz', 'lowers', 'diuretic'],
+ ['chlorthalidone', 'lowers', 'diuretic'], ['furosemide', 'lowers', 'diuretic'],
+ ['spironolactone', 'lowers', 'diuretic'], ['triamterene', 'lowers', 'diuretic'],
+ // Other antihypertensives
+ ['clonidine', 'lowers', 'antihypertensive'], ['hydralazine', 'lowers', 'antihypertensive'],
+ ['doxazosin', 'lowers', 'alpha-blocker'], ['prazosin', 'lowers', 'alpha-blocker'], ['terazosin', 'lowers', 'alpha-blocker'],
+ // Can RAISE BP
+ ['ibuprofen', 'raises', 'NSAID'], ['naproxen', 'raises', 'NSAID'], ['celecoxib', 'raises', 'NSAID'],
+ ['indomethacin', 'raises', 'NSAID'], ['meloxicam', 'raises', 'NSAID'], ['diclofenac', 'raises', 'NSAID'], ['ketorolac', 'raises', 'NSAID'],
+ ['pseudoephedrine', 'raises', 'decongestant'], ['phenylephrine', 'raises', 'decongestant'],
+ ['prednisone', 'raises', 'corticosteroid'], ['methylprednisolone', 'raises', 'corticosteroid'],
+ ['amphetamine', 'raises', 'stimulant'], ['methylphenidate', 'raises', 'stimulant'], ['venlafaxine', 'raises', 'SNRI (dose-dependent)'],
+];
+
+// Suffix stems (catch generics not explicitly listed). Anchored to word end to limit false hits.
+const STEMS = [
+ [/\w{3,}pril\b/, 'lowers', 'ACE inhibitor'],
+ [/\w{3,}sartan\b/, 'lowers', 'ARB'],
+ [/\w{3,}olol\b/, 'lowers', 'beta-blocker'],
+ [/\w{3,}dipine\b/, 'lowers', 'calcium channel blocker'],
+];
+
+// Classify one medication → { effect, klass } or null.
+function classify(med) {
+ const hay = `${(med.name || '')} ${(med.generic_name || '')}`.toLowerCase();
+ for (const [name, effect, klass] of NAMED) {
+ if (hay.includes(name)) return { effect, klass, matched: name };
+ }
+ for (const [re, effect, klass] of STEMS) {
+ const m = hay.match(re);
+ if (m) return { effect, klass, matched: m[0] };
+ }
+ return null;
+}
+
+// Given a list of active medication rows, return only the BP-relevant ones tagged.
+function bpRelevant(meds) {
+ const out = [];
+ for (const m of meds) {
+ const c = classify(m);
+ if (c) out.push({ name: m.name, generic_name: m.generic_name, person: m.person_name || null, ...c });
+ }
+ return out;
+}
+
+module.exports = { classify, bpRelevant };
diff --git a/routes/vitals.js b/routes/vitals.js
index f1fcc56..45231cc 100644
--- a/routes/vitals.js
+++ b/routes/vitals.js
@@ -8,6 +8,7 @@ const audit = require('../lib/audit');
const { id } = require('../lib/ids');
const hm = require('../lib/health-metrics');
const vitalsInsights = require('../lib/vitals-insights');
+const bpMedsLib = require('../lib/bp-meds');
const router = express.Router();
const DEV_USER_ID = 'user_steve';
@@ -56,7 +57,8 @@ router.get('/health', async (req, res) => {
`SELECT value, measured_at FROM health_reading WHERE ${where} AND metric = '${metric}'
ORDER BY measured_at DESC LIMIT 40`, params);
- const [bp, latest, counts, weight, rhr, trends] = await Promise.all([
+ const medWhere = personId ? 'm.user_id = $1 AND m.person_id = $2' : 'm.user_id = $1';
+ const [bp, latest, counts, weight, rhr, trends, meds] = await Promise.all([
db.query(
`SELECT id, systolic, diastolic, category, measured_at, device_name, source
FROM health_reading WHERE ${where} AND metric = 'blood_pressure'
@@ -69,6 +71,10 @@ router.get('/health', async (req, res) => {
series('weight'),
series('resting_heart_rate'),
vitalsInsights.computeTrends(DEV_USER_ID, personId), // cheap, DB-only (no LLM)
+ db.query(
+ `SELECT m.name, m.generic_name, p.full_name AS person_name
+ FROM medication m LEFT JOIN person p ON p.id = m.person_id
+ WHERE ${medWhere} AND m.is_active`, params),
]);
res.render('vitals', {
@@ -80,6 +86,7 @@ router.get('/health', async (req, res) => {
rhrSeries: rhr.rows,
trends,
recheck: vitalsInsights.recheckFlag(trends),
+ bpMeds: bpMedsLib.bpRelevant(meds.rows),
});
});
diff --git a/views/vitals.ejs b/views/vitals.ejs
index 8800952..485b1d3 100644
--- a/views/vitals.ejs
+++ b/views/vitals.ejs
@@ -99,6 +99,20 @@
</section>
<% } %>
+<% if (typeof bpMeds !== 'undefined' && bpMeds.length) { %>
+ <section class="glass" style="padding:.9rem 1.25rem;margin-bottom:1rem">
+ <h3 style="margin:.1rem 0 .5rem">Medications that affect blood pressure</h3>
+ <div style="display:flex;gap:.5rem;flex-wrap:wrap">
+ <% bpMeds.forEach(m => { %>
+ <span class="chip" style="background:<%= m.effect==='raises' ? 'rgba(229,83,75,.15)' : 'rgba(63,185,80,.15)' %>">
+ <strong><%= m.name %></strong> · <%= m.klass %> · <%= m.effect==='raises' ? 'may raise BP' : 'lowers BP' %><% if (m.person) { %> · <%= m.person %><% } %>
+ </span>
+ <% }) %>
+ </div>
+ <p class="subtle" style="font-size:.72rem;margin:.4rem 0 0">Context from your medication list — informational, not medical advice.</p>
+ </section>
+<% } %>
+
<% if (latestBp) { %>
<section class="glass" style="padding:1.25rem 1.5rem;margin-bottom:1rem">
<div style="display:flex;align-items:baseline;gap:1rem;flex-wrap:wrap">
← 8f58fa2 health: vitals trends + insights (weight/RHR sparklines, loc
·
back to AbramsOS
·
health: lapsed-habit BP reading reminders (only nudges prior 8598287 →