← back to AbramsOS
recall-watch: apply contrarian FIX-FIRST — E-known+in-date=>POSSIBLE regardless of FDA publish date (fix false-clear); assess ALL recalls per NDC (worst class never hidden); EXP-anchored expiry parser (no MFG/ref-date mis-parse); local notification on new alerts; per-NDC worst-status UI + N-of-M affected fills
4ccbda564cebd68bc41d68a769075b33a68aac8d · 2026-07-08 15:58:17 -0700 · Steve
Files touched
M data/recall-alerts.jsonlM data/recall-watch-state.jsonM routes/recalls.jsM scripts/recall-watch.jsM views/prescriptions.ejsM views/recalls.ejs
Diff
commit 4ccbda564cebd68bc41d68a769075b33a68aac8d
Author: Steve <steve@designerwallcoverings.com>
Date: Wed Jul 8 15:58:17 2026 -0700
recall-watch: apply contrarian FIX-FIRST — E-known+in-date=>POSSIBLE regardless of FDA publish date (fix false-clear); assess ALL recalls per NDC (worst class never hidden); EXP-anchored expiry parser (no MFG/ref-date mis-parse); local notification on new alerts; per-NDC worst-status UI + N-of-M affected fills
---
data/recall-alerts.jsonl | 4 ++
data/recall-watch-state.json | 18 ++++-----
routes/recalls.js | 21 +++++++---
scripts/recall-watch.js | 91 +++++++++++++++++++++++++++-----------------
views/prescriptions.ejs | 2 +-
views/recalls.ejs | 8 ++--
6 files changed, 89 insertions(+), 55 deletions(-)
diff --git a/data/recall-alerts.jsonl b/data/recall-alerts.jsonl
index d505454..b5d21c5 100644
--- a/data/recall-alerts.jsonl
+++ b/data/recall-alerts.jsonl
@@ -1,2 +1,6 @@
{"ndc":"00032-3016-13","drug":"CREON","recall":"D-0402-2021","class":"Class II","status":"confirm","at":"2026-07-08T22:46:31.161Z"}
{"ndc":"60505-0829-01","drug":"FLUTICASONE PROP 50 MCG SPR","recall":"D-0326-2024","class":"Class II","status":"possible","at":"2026-07-08T22:46:33.007Z"}
+{"ndc":"00032-3016-13","drug":"CREON","class":"Class II","status":"confirm","at":"2026-07-08T22:55:37.034Z"}
+{"ndc":"60505-0829-01","drug":"FLUTICASONE PROP 50 MCG SPR","class":"Class II","status":"possible","at":"2026-07-08T22:55:37.314Z"}
+{"ndc":"00032-3016-13","drug":"CREON","class":"Class II","status":"confirm","at":"2026-07-08T22:58:01.224Z"}
+{"ndc":"60505-0829-01","drug":"FLUTICASONE PROP 50 MCG SPR","class":"Class II","status":"possible","at":"2026-07-08T22:58:01.528Z"}
diff --git a/data/recall-watch-state.json b/data/recall-watch-state.json
index cd93abe..cae9826 100644
--- a/data/recall-watch-state.json
+++ b/data/recall-watch-state.json
@@ -1,25 +1,25 @@
{
"seen": {
- "00032-3016-13|D-0402-2021": {
+ "00032-3016-13|Class II": {
"status": "confirm",
"class": "Class II",
"drug": "CREON"
},
- "00121-0868-16|D-0721-2021": {
- "status": "cleared",
- "class": "Class II",
- "drug": "NYSTATIN"
- },
- "60505-0829-01|D-0326-2024": {
+ "60505-0829-01|Class II": {
"status": "possible",
"class": "Class II",
"drug": "FLUTICASONE PROP 50 MCG SPR"
},
- "60505-2579-08|D-0153-2020": {
+ "00121-0868-16|Class II": {
+ "status": "cleared",
+ "class": "Class II",
+ "drug": "NYSTATIN"
+ },
+ "60505-2579-08|Class III": {
"status": "cleared",
"class": "Class III",
"drug": "ATORVASTATIN 20 MG TABLET"
}
},
- "ran_at": "2026-07-08T22:46:45.031Z"
+ "ran_at": "2026-07-08T22:58:14.009Z"
}
\ No newline at end of file
diff --git a/routes/recalls.js b/routes/recalls.js
index 586981f..1fa8070 100644
--- a/routes/recalls.js
+++ b/routes/recalls.js
@@ -53,16 +53,25 @@ router.get('/recalls', async (req, res) => {
// NDC-PRECISE: fills whose ACTUAL dispensed product (NDC) is on an FDA recall list. This is the
// actionable signal (vs the by-name advisory above). Grouped per product, most-severe first.
+ // One row per (product, person). Status/assessment now vary per fill (a 2023 vs 2026 fill of the
+ // same drug can differ), so we aggregate to the WORST relevance and surface how many fills are
+ // affected. rk() = relevance rank (possible > confirm > cleared).
+ const rk = `(CASE pf.ndc_recall_status WHEN 'possible' THEN 3 WHEN 'confirm' THEN 2 ELSE 1 END)`;
const ndcFlags = (await db.query(
- `SELECT pf.drug_name, pf.ndc, pf.ndc_recall_number, pf.ndc_recall_class, pf.ndc_recall_reason,
- coalesce(pp.full_name,'—') AS person, count(*)::int AS fills,
+ `SELECT pf.drug_name, pf.ndc, pf.ndc_recall_class, coalesce(pp.full_name,'—') AS person,
+ count(*)::int AS fills,
+ count(*) FILTER (WHERE pf.ndc_recall_status <> 'cleared')::int AS relevant_fills,
min(pf.fill_date) AS first_fill, max(pf.fill_date) AS last_fill,
- (array_agg(pf.ndc_recall_links ORDER BY pf.fill_date DESC))[1] AS links,
- max(pf.ndc_recall_status) AS status, max(pf.ndc_recall_assessment) AS assessment
+ (array_agg(pf.ndc_recall_links ORDER BY pf.fill_date DESC))[1] AS links,
+ (array_agg(pf.ndc_recall_status ORDER BY ${rk} DESC))[1] AS status,
+ (array_agg(pf.ndc_recall_assessment ORDER BY ${rk} DESC))[1] AS assessment,
+ (array_agg(pf.ndc_recall_number ORDER BY ${rk} DESC))[1] AS ndc_recall_number
FROM prescription_fill pf LEFT JOIN person pp ON pp.id = pf.person_id
WHERE pf.user_id = $1 AND pf.ndc_recalled
- GROUP BY pf.drug_name, pf.ndc, pf.ndc_recall_number, pf.ndc_recall_class, pf.ndc_recall_reason, pp.full_name
- ORDER BY (CASE pf.ndc_recall_class WHEN 'Class I' THEN 1 WHEN 'Class II' THEN 2 ELSE 3 END), max(pf.fill_date) DESC`,
+ GROUP BY pf.drug_name, pf.ndc, pf.ndc_recall_class, pp.full_name
+ ORDER BY max(${rk}) DESC,
+ (CASE pf.ndc_recall_class WHEN 'Class I' THEN 1 WHEN 'Class II' THEN 2 ELSE 3 END),
+ max(pf.fill_date) DESC`,
[DEV_USER_ID]
)).rows;
diff --git a/scripts/recall-watch.js b/scripts/recall-watch.js
index ea0de18..c8bb25d 100644
--- a/scripts/recall-watch.js
+++ b/scripts/recall-watch.js
@@ -31,35 +31,44 @@ const segPair = (ndc) => { const p = String(ndc || '').split('-'); return (p.len
const candidates = (ndc) => { const p = String(ndc).split('-'); if (p.length < 2) return []; const c = new Set([`${p[0]}-${p[1]}`]); if (p[0][0] === '0') c.add(`${p[0].replace(/^0/, '')}-${p[1]}`); return [...c]; };
const fdaDate = (d) => (d && d.length === 8 ? new Date(`${d.slice(0, 4)}-${d.slice(4, 6)}-${d.slice(6, 8)}`) : null);
-// parse the LATEST expiration date from recall code_info / product_description free text
+// parse the LATEST EXPIRATION date from recall free text. ANCHORED on an EXP/EXPIRE/USE-BY
+// keyword so a manufacturing date ("MFG APR 2021") or a stray "MON YYYY" is never mistaken for
+// an expiry (which would wrongly CLEAR a genuinely-recalled fill). Unanchored dates are ignored.
const MON = { JAN: 0, FEB: 1, MAR: 2, APR: 3, MAY: 4, JUN: 5, JUL: 6, AUG: 7, SEP: 8, OCT: 9, NOV: 10, DEC: 11 };
function parseExpiry(text) {
const s = String(text || '').toUpperCase(); let best = null; const push = (d) => { if (d && !isNaN(d) && (!best || d > best)) best = d; };
- let m; const re1 = /(\d{1,2})\s*([A-Z]{3})\s*(\d{4})/g; // 30SEP2022
- while ((m = re1.exec(s))) if (m[2] in MON) push(new Date(Date.UTC(+m[3], MON[m[2]], +m[1])));
- const re2 = /EXP[.:]*\s*(\d{1,2})\/(\d{4})/g; // Exp. 03/2022 (MM/YYYY)
- while ((m = re2.exec(s))) push(new Date(Date.UTC(+m[2], +m[1], 0))); // end of that month
- const re3 = /EXP[.:]*\s*(\d{1,2})\/(\d{1,2})\/(\d{4})/g; // 09/30/2026
- while ((m = re3.exec(s))) push(new Date(Date.UTC(+m[3], +m[1] - 1, +m[2])));
- const re4 = /([A-Z]{3})\s*(\d{4})/g; // SEP 2022
- while ((m = re4.exec(s))) if (m[1] in MON) push(new Date(Date.UTC(+m[2], MON[m[1]] + 1, 0)));
+ // grab the token(s) immediately after each EXP/EXPIRE/EXPIRATION/USE BY marker
+ const segs = s.match(/(?:EXPIRATION|EXPIRES?|EXPIR\w*|EXP|USE\s*BY)[.:\s]*([A-Z0-9][A-Z0-9/.\- ]{3,14})/g) || [];
+ for (const seg of segs) {
+ const v = seg.replace(/^(?:EXPIRATION|EXPIRES?|EXPIR\w*|EXP|USE\s*BY)[.:\s]*/, '').trim();
+ let m;
+ if ((m = v.match(/^(\d{1,2})\s*([A-Z]{3})\s*(\d{4})/)) && m[2] in MON) push(new Date(Date.UTC(+m[3], MON[m[2]], +m[1]))); // 30SEP2022
+ else if ((m = v.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})/))) push(new Date(Date.UTC(+m[3], +m[1] - 1, +m[2]))); // 09/30/2026
+ else if ((m = v.match(/^(\d{1,2})\/(\d{4})/))) push(new Date(Date.UTC(+m[2], +m[1], 0))); // 03/2022
+ else if ((m = v.match(/^(\d{4})-(\d{2})-(\d{2})/))) push(new Date(Date.UTC(+m[1], +m[2] - 1, +m[3]))); // 2022-10-31
+ else if ((m = v.match(/^([A-Z]{3})\s*(\d{4})/)) && m[1] in MON) push(new Date(Date.UTC(+m[2], MON[m[1]] + 1, 0))); // SEP 2022
+ }
return best;
}
+const RANK = { possible: 3, confirm: 2, cleared: 1 };
+const worse = (a, b) => ((RANK[a] || 0) >= (RANK[b] || 0) ? a : b);
function assess(fill, init, exp) {
const F = new Date(fill); if (isNaN(F)) return { status: 'confirm', reason: 'no fill date' };
+ const iso = (d) => d.toISOString().slice(0, 10);
if (exp && !isNaN(exp)) {
- if (exp < F) return { status: 'cleared', reason: `recalled lot expired ${exp.toISOString().slice(0, 10)}, before this ${F.toISOString().slice(0, 10)} fill — different lot.` };
- if (init && init <= new Date(+F + 183 * DAY)) return { status: 'possible', reason: `recalled lot was in-date (exp ${exp.toISOString().slice(0, 10)}) at your ${F.toISOString().slice(0, 10)} fill — confirm your lot #.` };
- return { status: 'cleared', reason: `recall postdates this fill.` };
+ if (exp < F) return { status: 'cleared', reason: `recalled lot expired ${iso(exp)}, before this ${iso(F)} fill — a different lot.` };
+ // E >= F: that lot was still IN-DATE when this was dispensed, so it could have been in the
+ // bottle — regardless of when FDA later announced the recall (a defect found later was present
+ // when dispensed). Exposure is set by the lot window, not the FDA publish date.
+ return { status: 'possible', reason: `recalled lot was in-date (exp ${iso(exp)}) at your ${iso(F)} fill — check your lot # against the recall.` };
}
if (init) {
if (init >= new Date(+F - 913 * DAY) && init <= new Date(+F + 183 * DAY))
- return { status: 'confirm', reason: `recall (${init.toISOString().slice(0, 10)}) is near your ${F.toISOString().slice(0, 10)} fill and no lot expiry is published — confirm your lot #.` };
- return { status: 'cleared', reason: `recall (${init.toISOString().slice(0, 10)}) is far from your ${F.toISOString().slice(0, 10)} fill.` };
+ return { status: 'confirm', reason: `recall (${iso(init)}) is near your ${iso(F)} fill and no lot expiry is published — check your lot #.` };
+ return { status: 'cleared', reason: `recall (${iso(init)}) is far from your ${iso(F)} fill.` };
}
- return { status: 'confirm', reason: 'recall dates unavailable — confirm your lot #.' };
+ return { status: 'confirm', reason: 'recall dates unavailable — check your lot #.' };
}
-const worse = (a, b) => { const rank = { possible: 3, confirm: 2, cleared: 1 }; return (rank[a] || 0) >= (rank[b] || 0) ? a : b; };
async function fetchRecallsForNdc(ndc) {
const want = segPair(ndc); if (!want) return [];
@@ -96,41 +105,53 @@ async function main() {
// reset the source's recall flags, then recompute
await db.query(`UPDATE prescription_fill SET ndc_recalled=false, ndc_recall_number=NULL, ndc_recall_class=NULL, ndc_recall_reason=NULL, ndc_recall_status=NULL, ndc_recall_assessment=NULL WHERE user_id=$1 AND source=$2`, [USER, SOURCE]);
+ const clsRank = { 'Class I': 3, 'Class II': 2, 'Class III': 1 };
+ const worseClass = (a, b) => ((clsRank[a] || 0) >= (clsRank[b] || 0) ? a : b);
for (const [ndc, ndcFills] of byNdc) {
const recs = await fetchRecallsForNdc(ndc);
if (!recs.length) continue;
flaggedNdcs++;
- // pick the recall with the latest lot expiry (most likely to overlap) + capture worst class
- let worstClassRank = 0, chosen = null, chosenExp = null;
- const clsRank = { 'Class I': 3, 'Class II': 2, 'Class III': 1 };
- for (const rec of recs) {
- const exp = parseExpiry(`${rec.code_info || ''} ${rec.product_description || ''}`);
- const cr = clsRank[rec.classification] || 0;
- if (!chosen || (exp && (!chosenExp || exp > chosenExp)) || cr > worstClassRank) { chosen = rec; chosenExp = exp; worstClassRank = Math.max(worstClassRank, cr); }
- }
- const init = fdaDate(chosen.recall_initiation_date);
+ // pre-parse EVERY matching recall (don't collapse to one — a Class I must never hide behind a Class II)
+ const parsed = recs.map((rec) => ({ rec, exp: parseExpiry(`${rec.code_info || ''} ${rec.product_description || ''}`), init: fdaDate(rec.recall_initiation_date), cls: rec.classification }));
+ const worstClass = parsed.reduce((w, p) => worseClass(w, p.cls), null); // severity shown for the NDC (over all recalls)
let ndcStatus = 'cleared';
for (const f of ndcFills) {
- const a = assess(f.fill_date, init, chosenExp);
- ndcStatus = worse(ndcStatus, a.status);
+ // assess this fill against ALL matching recalls; keep the one with the worst relevance/class
+ let best = null;
+ for (const p of parsed) {
+ const a = assess(f.fill_date, p.init, p.exp);
+ if (!best || (RANK[a.status] || 0) > (RANK[best.status] || 0)
+ || (a.status === best.status && (clsRank[p.cls] || 0) > (clsRank[best.cls] || 0))) best = { ...a, rec: p.rec, cls: p.cls };
+ }
+ ndcStatus = worse(ndcStatus, best.status);
await db.query(
`UPDATE prescription_fill SET ndc_recalled=true, ndc_recall_number=$1, ndc_recall_class=$2,
ndc_recall_reason=$3, ndc_recall_status=$4, ndc_recall_assessment=$5 WHERE id=$6`,
- [chosen.recall_number || null, chosen.classification || null,
- (chosen.reason_for_recall || '').slice(0, 300), a.status, a.reason, f.id]);
+ [best.rec.recall_number || null, worstClass, // show the WORST class across all matching recalls
+ (best.rec.reason_for_recall || '').slice(0, 300), best.status, best.reason, f.id]);
flaggedFills++;
}
- const key = `${ndc}|${chosen.recall_number}`;
- seen[key] = { status: ndcStatus, class: chosen.classification, drug: ndcFills[0].drug_name };
- // NEW alert = a recall we haven't seen for this NDC before, AND it's relevant (not cleared)
+ const key = `${ndc}|${worstClass}`; // baseline keyed by NDC+severity (survives which single recall wins the row)
+ seen[key] = { status: ndcStatus, class: worstClass, drug: ndcFills[0].drug_name };
+ // NEW alert = an (NDC, severity) we haven't seen before AND it's date-relevant (not cleared)
if (!prev.seen[key] && ndcStatus !== 'cleared') {
- newAlerts.push({ ndc, drug: ndcFills[0].drug_name, recall: chosen.recall_number, class: chosen.classification, status: ndcStatus, at: new Date().toISOString() });
+ newAlerts.push({ ndc, drug: ndcFills[0].drug_name, class: worstClass, status: ndcStatus, at: new Date().toISOString() });
}
}
fs.mkdirSync(path.dirname(STATE), { recursive: true });
fs.writeFileSync(STATE, JSON.stringify({ seen, ran_at: new Date().toISOString() }, null, 1));
- if (newAlerts.length) { for (const a of newAlerts) fs.appendFileSync(ALERTS, JSON.stringify(a) + '\n'); }
+ if (newAlerts.length) {
+ for (const a of newAlerts) fs.appendFileSync(ALERTS, JSON.stringify(a) + '\n');
+ // surface it — a silent nightly health check is not an alert. Local macOS notification ($0,
+ // no external send). Email/SMS would be outward-facing (gated) — left for Steve to approve.
+ try {
+ const { execFileSync } = require('child_process');
+ const drugs = [...new Set(newAlerts.map((a) => a.drug))].slice(0, 3).join(', ');
+ const body = `${newAlerts.length} new FDA recall(s) may affect your fills: ${drugs}. Open AbramsOS → Recalls.`;
+ execFileSync('osascript', ['-e', `display notification "${body.replace(/"/g, "'")}" with title "AbramsOS · Rx recall" sound name "Basso"`]);
+ } catch { /* headless / no GUI — the JSONL + audit log still captured it */ }
+ }
await audit.log({ actorType: 'system', actorId: 'recall-watch', objectType: 'prescription_fill', objectId: SOURCE,
eventType: 'recall_watch_run', metadata: { medical: true, source: 'openFDA', ndcs_recalled: flaggedNdcs, fills_flagged: flaggedFills, new_alerts: newAlerts.length } });
@@ -138,7 +159,7 @@ async function main() {
const sum = {}; for (const k of Object.keys(seen)) { const s = seen[k].status; sum[s] = (sum[s] || 0) + 1; }
console.log(`\nDONE: ${flaggedNdcs} NDCs on a recall list · ${flaggedFills} fills assessed`);
console.log(` by relevance: ${Object.entries(sum).map(([k, v]) => `${k}=${v}`).join(', ') || 'none'}`);
- if (newAlerts.length) { console.log(` 🔔 ${newAlerts.length} NEW relevant alert(s):`); newAlerts.forEach((a) => console.log(` ${a.drug} ${a.recall} (${a.class}, ${a.status})`)); }
+ if (newAlerts.length) { console.log(` 🔔 ${newAlerts.length} NEW relevant alert(s):`); newAlerts.forEach((a) => console.log(` ${a.drug} — ${a.class}, ${a.status} (NDC ${a.ndc})`)); }
else console.log(' ✓ no new relevant recalls since last run.');
process.exit(0);
}
diff --git a/views/prescriptions.ejs b/views/prescriptions.ejs
index 2dac895..5bd1095 100644
--- a/views/prescriptions.ejs
+++ b/views/prescriptions.ejs
@@ -46,7 +46,7 @@
data-find="<%= ((f.drug_name||'')+' '+(f.doctor||'')+' '+(f.rx_number||'')).toLowerCase() %>">
<td class="mono"><%= f.fill_date ? String(f.fill_date).slice(0,10) : '' %></td>
<td><%= f.person_name || '' %></td>
- <td><%= f.drug_name %><% if (f.ndc_recalled) { var clr = f.ndc_recall_status==='cleared' || f.ndc_recall_status==='likely_clear'; %> <span class="recall-flag <%= clr ? 'clear' : '' %>" title="<%= f.ndc_recall_class %> <%= f.ndc_recall_number %> — <%= (f.ndc_recall_assessment||'on an FDA recall list').replace(/&/g,'&').replace(/\"/g,'"') %>"><%= clr ? '✓ recall (lot clear)' : '⚠ RECALL' %></span><% } %></td>
+ <td><%= f.drug_name %><% if (f.ndc_recalled) { var clr = f.ndc_recall_status==='cleared'; %> <span class="recall-flag <%= clr ? 'clear' : '' %>" title="<%= f.ndc_recall_class %> <%= f.ndc_recall_number %> — <%= (f.ndc_recall_assessment||'on an FDA recall list').replace(/&/g,'&').replace(/\"/g,'"') %>"><%= clr ? '✓ recall (lot clear)' : (f.ndc_recall_status==='possible' ? '⚠ RECALL — check lot' : '⚠ recall — check lot') %></span><% } %></td>
<td class="mono"><%= f.rx_number || '' %><% if (f.refill) { %><span class="subtle"> · rf<%= f.refill %></span><% } %></td>
<td class="num"><%= f.quantity != null ? f.quantity : '' %></td>
<td class="num"><%= f.days_supply != null ? f.days_supply : '' %></td>
diff --git a/views/recalls.ejs b/views/recalls.ejs
index f8af624..eb40c43 100644
--- a/views/recalls.ejs
+++ b/views/recalls.ejs
@@ -23,9 +23,9 @@
<tbody>
<% ndcF.forEach(function(x){
var badge = x.ndc_recall_class==='Class I' ? '#f87171' : x.ndc_recall_class==='Class II' ? '#fbbf24' : '#9aa0aa';
- var clear = x.status==='cleared', amber = x.status==='likely_clear';
- var sBg = clear ? '#7fd0a0' : amber ? '#fbbf24' : '#f87171';
- var sTxt = clear ? '✓ Clear' : amber ? '⚠ Confirm lot' : '⚠ Review'; %>
+ var poss = x.status==='possible', conf = x.status==='confirm';
+ var sBg = poss ? '#f87171' : conf ? '#fbbf24' : '#7fd0a0';
+ var sTxt = poss ? '⚠ Possible match' : conf ? '⚠ Check lot' : '✓ Clear'; %>
<tr>
<td><span class="sev" style="background:<%= sBg %>22;color:<%= sBg %>;border:1px solid <%= sBg %>66"><%= sTxt %></span></td>
<td><span class="sev" style="background:<%= badge %>22;color:<%= badge %>;border:1px solid <%= badge %>55"><%= x.ndc_recall_class || '—' %></span></td>
@@ -33,7 +33,7 @@
<td><%= x.person %></td>
<td class="mono"><%= x.ndc %></td>
<td class="mono"><a href="https://www.accessdata.fda.gov/scripts/ires/index.cfm?Product=<%= encodeURIComponent(x.ndc_recall_number||'') %>" target="_blank" rel="noopener noreferrer"><%= x.ndc_recall_number || '' %></a></td>
- <td class="num"><%= x.fills %></td>
+ <td class="num"><% if (x.relevant_fills > 0) { %><strong style="color:#f87171"><%= x.relevant_fills %></strong> of <% } %><%= x.fills %></td>
<td class="mono"><%= x.first_fill ? String(x.first_fill).slice(0,10) : '' %> → <%= x.last_fill ? String(x.last_fill).slice(0,10) : '' %></td>
<td class="reason"><%= x.assessment || ((x.ndc_recall_reason||'').slice(0,110)) %></td>
<td class="links"><% (x.links || []).forEach(function(l){ %><a href="<%= l.url %>" target="_blank" rel="noopener noreferrer"><%= l.label %></a><% }) %></td>
← 362c884 recall-watch.js: nightly-ready FDA recall watcher with PROGR
·
back to AbramsOS
·
chore: lint (node --check ✓), untrack runtime state, v0.2.0 1192d3c →