+ ⚠ Protocol selection requires review
+
+ )}
+ {row.sourceSlug && (
+
⚠ Confirm preparation with pharmacy
@@ -363,8 +464,13 @@ const TestingPlanPrintView = ({ patient, data, drugCategories, onProceed }: Test
{row.concentration}
+ {row.preparation && (
+
+ {row.preparation}
+
+ )}
{row.diluent && (
-
+
{row.diluent.startsWith('Neat') ? row.diluent : `in ${row.diluent}`}
)}
diff --git a/src/features/testing/types.ts b/src/features/testing/types.ts
index b84a9a5..0095535 100644
--- a/src/features/testing/types.ts
+++ b/src/features/testing/types.ts
@@ -1,6 +1,7 @@
export interface IDTStep {
ratio: string; // e.g. "1:1,000"
concentration: string; // e.g. "0.01mg/mL"
+ preparation?: string;
}
export interface ChallengeStep {
@@ -24,6 +25,7 @@ export interface DrugProtocol {
protocolLabel: string;
sourceSlug?: string;
underReview?: boolean;
+ reviewNote?: string;
lastReviewed?: string;
}
diff --git a/src/shared/data/__tests__/verifyOrder.test.ts b/src/shared/data/__tests__/verifyOrder.test.ts
new file mode 100644
index 0000000..69453d6
--- /dev/null
+++ b/src/shared/data/__tests__/verifyOrder.test.ts
@@ -0,0 +1,261 @@
+import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
+import { tmpdir } from 'node:os';
+import { join } from 'node:path';
+import { afterAll, beforeAll, describe, expect, it } from 'vitest';
+import expectedOrderFixture from '../expectedProtocolOrder.json';
+import {
+ FROZEN_PRE_SNAPSHOT_COUNT,
+ MINIMUM_BASELINE_COUNT,
+ FROZEN_PRE_SNAPSHOT_PREFIX_SHA256,
+ FROZEN_PREFIX_CHECKPOINTS,
+ canonicalizeTuple,
+ canonicalizeTuples,
+ computeTuplesHash,
+ validateFixtureSchema,
+ validateFrozenPrefix,
+ verifyOrder,
+} from '../../../../scripts/verify-order.mjs';
+
+describe('verify-order frozen baseline constants & checkpoints', () => {
+ it('defines frozen pre-snapshot count as 116', () => {
+ expect(FROZEN_PRE_SNAPSHOT_COUNT).toBe(116);
+ });
+
+ it('defines minimum baseline count as 117 (frozen prefix + index 116 Flucloxacillin checkpoint)', () => {
+ expect(MINIMUM_BASELINE_COUNT).toBe(117);
+ });
+
+ it('defines the canonical SHA-256 hash for the 116 frozen records', () => {
+ expect(FROZEN_PRE_SNAPSHOT_PREFIX_SHA256).toBe(
+ 'e1441bb00c906da6bdd7138b2206f5e6a5735f54805bfa2d9cecd5d00c007096'
+ );
+ });
+
+ it('defines expected boundary checkpoints for head, tail, and deliberate appended record', () => {
+ expect(FROZEN_PREFIX_CHECKPOINTS.HEAD_0.index).toBe(0);
+ expect(FROZEN_PREFIX_CHECKPOINTS.HEAD_0.tuple).toEqual({
+ drugName: 'Cis-atracurium',
+ testType: 'skin',
+ protocolLabel: 'IV',
+ });
+
+ expect(FROZEN_PREFIX_CHECKPOINTS.TAIL_115.index).toBe(115);
+ expect(FROZEN_PREFIX_CHECKPOINTS.TAIL_115.tuple).toEqual({
+ drugName: 'Voltaren (Diclofenac)',
+ testType: 'challenge',
+ protocolLabel: 'Graded Challenge',
+ });
+
+ expect(FROZEN_PREFIX_CHECKPOINTS.APPENDED_116.index).toBe(116);
+ expect(FROZEN_PREFIX_CHECKPOINTS.APPENDED_116.tuple).toEqual({
+ drugName: 'Flucloxacillin',
+ testType: 'skin',
+ protocolLabel: 'IV',
+ });
+ });
+});
+
+describe('verify-order canonical serialization & hashing', () => {
+ it('canonicalizes a single tuple cleanly', () => {
+ const input = {
+ drugName: 'Rocuronium',
+ testType: 'skin',
+ protocolLabel: 'IV',
+ extraField: 'should be ignored',
+ };
+ expect(canonicalizeTuple(input)).toEqual({
+ drugName: 'Rocuronium',
+ testType: 'skin',
+ protocolLabel: 'IV',
+ });
+ });
+
+ it('canonicalizes an array of tuples deterministically', () => {
+ const input = [
+ { drugName: 'Cis-atracurium', testType: 'skin', protocolLabel: 'IV', extraneous: 1 },
+ { drugName: 'Rocuronium', testType: 'skin', protocolLabel: 'IV', extra: false },
+ ];
+ const canonicalJson = canonicalizeTuples(input);
+ expect(canonicalJson).toBe(
+ JSON.stringify([
+ { drugName: 'Cis-atracurium', testType: 'skin', protocolLabel: 'IV' },
+ { drugName: 'Rocuronium', testType: 'skin', protocolLabel: 'IV' },
+ ])
+ );
+ });
+
+ it('computes exact canonical hash matching committed baseline for fixture prefix', () => {
+ const prefix116 = expectedOrderFixture.order.slice(0, 116);
+ const computedHash = computeTuplesHash(prefix116, 116);
+ expect(computedHash).toBe(FROZEN_PRE_SNAPSHOT_PREFIX_SHA256);
+ });
+
+ it('produces different hashes if tuple fields differ or positions change', () => {
+ const prefix116 = expectedOrderFixture.order.slice(0, 116);
+ const modified = [...prefix116];
+ // Swap index 0 and 1
+ modified[0] = prefix116[1];
+ modified[1] = prefix116[0];
+
+ const modifiedHash = computeTuplesHash(modified, 116);
+ expect(modifiedHash).not.toBe(FROZEN_PRE_SNAPSHOT_PREFIX_SHA256);
+ });
+});
+
+describe('verify-order fixture schema validation', () => {
+ it('accepts the committed expectedProtocolOrder fixture', () => {
+ expect(() => validateFixtureSchema(expectedOrderFixture)).not.toThrow();
+ });
+
+ it('rejects non-object or null fixture', () => {
+ expect(() => validateFixtureSchema(null)).toThrow(/Fixture must be a non-null JSON object/);
+ expect(() => validateFixtureSchema([])).toThrow(/Fixture must be a non-null JSON object/);
+ expect(() => validateFixtureSchema('string')).toThrow(/Fixture must be a non-null JSON object/);
+ });
+
+ it('rejects count mismatch or malformed count', () => {
+ expect(() => validateFixtureSchema({ count: '117', order: [] })).toThrow(/non-negative integer/);
+ expect(() => validateFixtureSchema({ count: -1, order: [] })).toThrow(/non-negative integer/);
+ expect(() =>
+ validateFixtureSchema({ count: 2, order: [{ drugName: 'A', testType: 'skin', protocolLabel: 'IV' }] })
+ ).toThrow(/Fixture count mismatch/);
+ });
+
+ it('rejects malformed items in order array', () => {
+ expect(() =>
+ validateFixtureSchema({
+ count: 1,
+ order: [{ drugName: '', testType: 'skin', protocolLabel: 'IV' }],
+ })
+ ).toThrow(/Fixture entry at index 0 is malformed/);
+
+ expect(() =>
+ validateFixtureSchema({
+ count: 1,
+ order: [{ drugName: 'A', testType: 123, protocolLabel: 'IV' }],
+ })
+ ).toThrow(/Fixture entry at index 0 is malformed/);
+ });
+});
+
+describe('verify-order independent frozen prefix guard', () => {
+ it('validates the committed fixture prefix successfully', () => {
+ const res = validateFrozenPrefix(expectedOrderFixture.order, 'expectedProtocolOrder.json fixture');
+ expect(res.valid).toBe(true);
+ expect(res.actualHash).toBe(FROZEN_PRE_SNAPSHOT_PREFIX_SHA256);
+ });
+
+ it('detects and rejects truncation below 116 records', () => {
+ const truncated = expectedOrderFixture.order.slice(0, 100);
+ const res = validateFrozenPrefix(truncated, 'truncated test list');
+ expect(res.valid).toBe(false);
+ expect(res.error).toContain('contains only 100 records');
+ expect(res.error).toContain('fewer than the frozen pre-snapshot baseline of 116 records');
+ });
+
+ it('detects and rejects reordering within the 116 prefix with actionable message', () => {
+ const tampered = expectedOrderFixture.order.slice(0, 116);
+ // Swap first two records
+ const temp = tampered[0];
+ tampered[0] = tampered[1];
+ tampered[1] = temp;
+
+ const res = validateFrozenPrefix(tampered, 'tampered test list');
+ expect(res.valid).toBe(false);
+ expect(res.error).toContain('CRITICAL SAFETY FAILURE');
+ expect(res.error).toContain('Head checkpoint (index 0) mismatch');
+ expect(res.error).toContain(`Expected SHA-256: ${FROZEN_PRE_SNAPSHOT_PREFIX_SHA256}`);
+ });
+
+ it('detects and rejects modification at an arbitrary index in the prefix', () => {
+ const tampered = expectedOrderFixture.order.slice(0, 116).map((t, idx) => {
+ if (idx === 50) {
+ return { ...t, drugName: 'Tampered Drug Name' };
+ }
+ return t;
+ });
+
+ const res = validateFrozenPrefix(tampered, 'tampered item list');
+ expect(res.valid).toBe(false);
+ expect(res.actualHash).not.toBe(FROZEN_PRE_SNAPSHOT_PREFIX_SHA256);
+ });
+
+ it('allows append-only records beyond index 115 without altering prefix hash', () => {
+ const allRecords = expectedOrderFixture.order; // 117 records
+ expect(allRecords).toHaveLength(117);
+
+ const prefixCheck = validateFrozenPrefix(allRecords, 'full fixture');
+ expect(prefixCheck.valid).toBe(true);
+ expect(prefixCheck.actualHash).toBe(FROZEN_PRE_SNAPSHOT_PREFIX_SHA256);
+ });
+});
+
+describe('verify-order full pipeline execution', () => {
+ let testTempDir: string;
+
+ beforeAll(() => {
+ testTempDir = mkdtempSync(join(tmpdir(), 'dream-verify-order-test-'));
+ });
+
+ afterAll(() => {
+ if (testTempDir) {
+ rmSync(testTempDir, { recursive: true, force: true });
+ }
+ });
+
+ it('passes complete verification on current repository masterlist and fixture', () => {
+ const result = verifyOrder();
+ expect(result.success).toBe(true);
+ expect(result.fixtureCount).toBe(117);
+ expect(result.actualCount).toBe(117);
+ expect(result.frozenPrefixCount).toBe(116);
+ expect(result.fixturePrefixValid).toBe(true);
+ expect(result.masterlistPrefixValid).toBe(true);
+ expect(result.mismatches).toEqual([]);
+ expect(result.errors).toEqual([]);
+ });
+
+ it('fails pipeline verification with minimum baseline/index-116 error when fixture has only 116 records', () => {
+ const fixture116 = {
+ count: 116,
+ order: expectedOrderFixture.order.slice(0, 116),
+ };
+ const fixturePath116 = join(testTempDir, 'fixture-116.json');
+ writeFileSync(fixturePath116, JSON.stringify(fixture116, null, 2), 'utf8');
+
+ const result = verifyOrder({ fixturePath: fixturePath116 });
+ expect(result.success).toBe(false);
+ expect(result.fixtureCount).toBe(116);
+ expect(result.fixturePrefixValid).toBe(true);
+ expect(result.errors).toContainEqual(
+ expect.stringContaining('fewer than the required baseline count of 117 records (missing committed index 116 checkpoint)')
+ );
+ });
+
+ it('fails pipeline verification with appended-record error when index 116 tuple is wrong', () => {
+ const fixtureWrong116 = {
+ count: 117,
+ order: [
+ ...expectedOrderFixture.order.slice(0, 116),
+ { drugName: 'WrongDrug', testType: 'skin', protocolLabel: 'IV' },
+ ],
+ };
+ const fixturePathWrong116 = join(testTempDir, 'fixture-wrong-116.json');
+ writeFileSync(fixturePathWrong116, JSON.stringify(fixtureWrong116, null, 2), 'utf8');
+
+ const result = verifyOrder({ fixturePath: fixturePathWrong116 });
+ expect(result.success).toBe(false);
+ expect(result.fixtureCount).toBe(117);
+ expect(result.fixturePrefixValid).toBe(true);
+ expect(result.errors).toContainEqual(
+ expect.stringContaining('Fixture appended record at index 116 mismatch')
+ );
+ expect(result.mismatches).toContainEqual(
+ expect.objectContaining({
+ index: 116,
+ expected: { drugName: 'WrongDrug', testType: 'skin', protocolLabel: 'IV' },
+ actual: { drugName: 'Flucloxacillin', testType: 'skin', protocolLabel: 'IV' },
+ })
+ );
+ });
+});
diff --git a/src/shared/data/drugMasterlist.generated.ts b/src/shared/data/drugMasterlist.generated.ts
index 7495672..ca7cd48 100644
--- a/src/shared/data/drugMasterlist.generated.ts
+++ b/src/shared/data/drugMasterlist.generated.ts
@@ -4,10 +4,85 @@
import type { DrugProtocol, IDTStep, ChallengeStep } from '@features/testing/types';
// Compact helpers for readability
-const s = (ratio: string, concentration: string): IDTStep => ({ ratio, concentration });
+const s = (ratio: string, concentration: string, preparation?: string): IDTStep => (preparation ? { ratio, concentration, preparation } : { ratio, concentration });
const c = (step: number, dose: string, volume: string, cumulative: string): ChallengeStep => ({ step, dose, volume, cumulative });
export const GENERATED_PROTOCOLS: DrugProtocol[] = [
+ {
+ id: "sc",
+ drugName: "Actrapid (Insulin)",
+ category: "Others",
+ testType: "skin",
+ presentation: "100 units/mL (10 mL vial)",
+ sptNeatConcentration: "Neat (100 U/mL)",
+ diluent: "19 mL N/S 0.9% for SPT dilution",
+ idtSteps: [s("1:20", "5 U/mL", "0.1 mL stock (100 U/mL) + 1.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "S/C",
+ sourceSlug: "actrapid",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "iv",
+ drugName: "Alfentanil",
+ category: "Opioids",
+ testType: "skin",
+ presentation: "1 mg/2 mL (0.5 mg/mL)",
+ sptNeatConcentration: "Neat (0.5 mg/mL)",
+ diluent: "0.9% sodium chloride",
+ idtSteps: [s("1:100", "0.005 mg/mL", "0.1 mL of 0.05 mg/mL + 0.9 mL NS"), s("1:10", "0.05 mg/mL", "0.1 mL neat + 0.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "IV",
+ sourceSlug: "alfentanil",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "iv",
+ drugName: "Azithromycin",
+ category: "Others",
+ testType: "skin",
+ presentation: "500 mg powder for injection",
+ sptNeatConcentration: "Neat (100 mg/mL)",
+ diluent: "0.9% sodium chloride (reconstitute with 4.8 mL WFI)",
+ idtSteps: [s("1:10,000", "0.01 mg/mL", "0.1 mL of 1 mg/mL + 0.9 mL NS"), s("1:1,000", "0.1 mg/mL", "0.1 mL of 10 mg/mL + 0.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "IV",
+ sourceSlug: "azithromycin",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "iv",
+ drugName: "Betamethasone",
+ category: "Others",
+ testType: "experimental",
+ presentation: "Betamethasone sodium phosphate 5.7 mg/mL",
+ sptNeatConcentration: "Neat (5.7 mg/mL)",
+ diluent: "0.9% sodium chloride",
+ idtSteps: [s("1:100", "0.057 mg/mL", "0.1 mL of 0.57 mg/mL + 0.9 mL NS"), s("1:15", "0.38 mg/mL", "0.2 mL of 5.7 mg/mL + 2.8 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "IV",
+ sourceSlug: "betamethasone",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "epidural",
+ drugName: "Bupivacaine",
+ category: "Local Anaesthetics",
+ testType: "skin",
+ presentation: "50 mg/20 mL (2.5 mg/mL) or 0.25% solution",
+ sptNeatConcentration: "Neat (2.5 mg/mL)",
+ diluent: "0.9% sodium chloride",
+ idtSteps: [s("1:1,000", "0.0025 mg/mL", "0.1 mL of 0.025 mg/mL + 0.9 mL NS"), s("1:100", "0.025 mg/mL", "0.1 mL of 0.25 mg/mL + 0.9 mL NS"), s("1:10", "0.25 mg/mL", "0.1 mL neat + 0.9 mL NS"), s("Neat", "2.5 mg/mL", "Undiluted stock")],
+ challengeSteps: [],
+ protocolLabel: "Epidural",
+ sourceSlug: "bupivacaine",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
{
id: "iv",
drugName: "Cefazolin",
@@ -16,7 +91,7 @@ export const GENERATED_PROTOCOLS: DrugProtocol[] = [
presentation: "1 g powder for injection",
sptNeatConcentration: "Neat (100 mg/mL)",
diluent: "0.9% sodium chloride (reconstitute with 10 mL WFI)",
- idtSteps: [s("1:100", "1 mg/mL"), s("1:10", "10 mg/mL")],
+ idtSteps: [s("1:100", "1 mg/mL", "0.1 mL of 10 mg/mL + 0.9 mL NS"), s("1:10", "10 mg/mL", "0.1 mL neat + 0.9 mL NS")],
challengeSteps: [],
protocolLabel: "IV",
sourceSlug: "cefazolin",
@@ -38,6 +113,96 @@ export const GENERATED_PROTOCOLS: DrugProtocol[] = [
underReview: false,
lastReviewed: "2026-03-28",
},
+ {
+ id: "iv",
+ drugName: "Cefepime",
+ category: "Cephalosporins",
+ testType: "skin",
+ presentation: "1 g powder for injection",
+ sptNeatConcentration: "Neat (100 mg/mL)",
+ diluent: "0.9% sodium chloride (reconstitute with 10 mL WFI)",
+ idtSteps: [s("1:100", "1 mg/mL", "0.1 mL of 10 mg/mL + 0.9 mL NS"), s("1:10", "10 mg/mL", "0.1 mL neat + 0.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "IV",
+ sourceSlug: "cefepime",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "iv",
+ drugName: "Cefotaxime",
+ category: "Cephalosporins",
+ testType: "skin",
+ presentation: "1 g powder for injection",
+ sptNeatConcentration: "Neat (100 mg/mL)",
+ diluent: "0.9% sodium chloride (reconstitute with 10 mL WFI)",
+ idtSteps: [s("1:100", "1 mg/mL", "0.1 mL of 10 mg/mL + 0.9 mL NS"), s("1:10", "10 mg/mL", "0.1 mL neat + 0.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "IV",
+ sourceSlug: "cefotaxime",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "iv",
+ drugName: "Ceftazidime",
+ category: "Cephalosporins",
+ testType: "skin",
+ presentation: "2 g powder for injection",
+ sptNeatConcentration: "Neat (100 mg/mL)",
+ diluent: "0.9% sodium chloride (reconstitute with 20 mL WFI)",
+ idtSteps: [s("1:100", "1 mg/mL", "0.1 mL of 10 mg/mL + 0.9 mL NS"), s("1:10", "10 mg/mL", "0.1 mL neat + 0.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "IV",
+ sourceSlug: "ceftazidime",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "iv",
+ drugName: "Ceftriaxone",
+ category: "Cephalosporins",
+ testType: "skin",
+ presentation: "1 g powder for injection",
+ sptNeatConcentration: "Neat (100 mg/mL)",
+ diluent: "0.9% sodium chloride (reconstitute with 10 mL WFI)",
+ idtSteps: [s("1:100", "1 mg/mL", "0.1 mL of 10 mg/mL + 0.9 mL NS"), s("1:10", "10 mg/mL", "0.1 mL neat + 0.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "IV",
+ sourceSlug: "ceftriaxone",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "iv",
+ drugName: "Cefuroxime",
+ category: "Cephalosporins",
+ testType: "skin",
+ presentation: "750 mg powder for injection or 125 mg/5 mL suspension",
+ sptNeatConcentration: "Neat (25 mg/mL)",
+ diluent: "0.9% sodium chloride (750 mg + 30 mL NS) or use 125 mg/5 mL (25 mg/mL) suspension",
+ idtSteps: [s("1:100", "0.25 mg/mL", "0.1 mL of 2.5 mg/mL + 0.9 mL NS"), s("1:10", "2.5 mg/mL", "0.1 mL of 25 mg/mL + 0.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "IV",
+ sourceSlug: "cefuroxime",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "0-02",
+ drugName: "Chlorhexidine",
+ category: "Antiseptics",
+ testType: "skin",
+ presentation: "0.02% solution (0.2 mg/mL) or 0.1% solution (1 mg/mL)",
+ sptNeatConcentration: "Neat (0.2 mg/mL)",
+ diluent: "0.9% sodium chloride",
+ idtSteps: [s("1:100", "0.002 mg/mL", "0.1 mL of 0.02 mg/mL + 0.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "0.02%",
+ sourceSlug: "chlorhexidine",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
{
id: "iv",
drugName: "Cis-atracurium",
@@ -46,13 +211,495 @@ export const GENERATED_PROTOCOLS: DrugProtocol[] = [
presentation: "5 mg/2.5 mL (2 mg/mL)",
sptNeatConcentration: "Neat (2 mg/mL)",
diluent: "0.9% sodium chloride",
- idtSteps: [s("1:1,000", "0.002 mg/mL")],
+ idtSteps: [s("1:1,000", "0.002 mg/mL", "0.1 mL of 0.02 mg/mL + 0.9 mL NS")],
challengeSteps: [],
protocolLabel: "IV",
sourceSlug: "cis-atracurium",
underReview: false,
lastReviewed: "2026-03-28",
},
+ {
+ id: "iv",
+ drugName: "Clindamycin",
+ category: "Others",
+ testType: "skin",
+ presentation: "600 mg/4 mL (150 mg/mL) or 300 mg/2 mL (150 mg/mL)",
+ sptNeatConcentration: "Neat (150 mg/mL)",
+ diluent: "0.9% sodium chloride",
+ idtSteps: [s("1:10", "15 mg/mL", "0.1 mL neat + 0.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "IV",
+ sourceSlug: "clindamycin",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "sc",
+ drugName: "Dalteparin",
+ category: "Others",
+ testType: "skin",
+ presentation: "10,000 U/mL pre-filled syringe or vial",
+ sptNeatConcentration: "Neat (10,000 U/mL)",
+ diluent: "0.9% sodium chloride",
+ idtSteps: [s("1:1,000", "10 U/mL", "0.1 mL of 100 U/mL + 0.9 mL NS"), s("1:100", "100 U/mL", "0.1 mL of 1,000 U/mL + 0.9 mL NS"), s("1:10", "1,000 U/mL", "0.1 mL neat + 0.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "SC",
+ sourceSlug: "dalteparin",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "iv",
+ drugName: "Dexamethasone",
+ category: "Others",
+ testType: "skin",
+ presentation: "4 mg/mL or 8 mg/2 mL (4 mg/mL)",
+ sptNeatConcentration: "Neat (4 mg/mL)",
+ diluent: "0.9% sodium chloride",
+ idtSteps: [s("1:100", "0.04 mg/mL", "0.1 mL of 0.4 mg/mL + 0.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "IV",
+ sourceSlug: "dexamethasone",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "iv",
+ drugName: "Droperidol",
+ category: "Others",
+ testType: "skin",
+ presentation: "10 mg/2 mL",
+ sptNeatConcentration: "Neat (5 mg/mL)",
+ diluent: "0.9% sodium chloride",
+ idtSteps: [s("1:1,000", "0.005 mg/mL", "0.1 mL of 0.5 mg/mL + 0.9 mL NS"), s("1:100", "0.05 mg/mL", "0.1 mL of 5 mg/mL + 0.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "IV",
+ sourceSlug: "droperidol",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "sc",
+ drugName: "Enoxaparin",
+ category: "Others",
+ testType: "skin",
+ presentation: "100 mg/mL pre-filled syringe or vial",
+ sptNeatConcentration: "Neat (100 mg/mL)",
+ diluent: "0.9% sodium chloride",
+ idtSteps: [s("1:1,000", "0.1 mg/mL", "0.1 mL of 1 mg/mL + 0.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "SC",
+ sourceSlug: "enoxaparin",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "standard",
+ drugName: "Esomeprazole",
+ category: "Proton Pump Inhibitors",
+ testType: "skin",
+ presentation: "20 mg tablets / capsules",
+ sptNeatConcentration: "Neat (20 mg/mL)",
+ diluent: "0.9% sodium chloride (dissolve in 1 mL)",
+ idtSteps: [],
+ challengeSteps: [],
+ protocolLabel: "",
+ sourceSlug: "esomeprazole",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "iv",
+ drugName: "Fentanyl",
+ category: "Opioids",
+ testType: "skin",
+ presentation: "100 mcg/2 mL (0.05 mg/mL)",
+ sptNeatConcentration: "Neat (0.05 mg/mL)",
+ diluent: "0.9% sodium chloride",
+ idtSteps: [s("1:100", "0.0005 mg/mL", "0.1 mL of 0.005 mg/mL + 0.9 mL NS"), s("1:10", "0.005 mg/mL", "0.1 mL neat + 0.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "IV",
+ sourceSlug: "fentanyl",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "iv",
+ drugName: "Flucloxacillin",
+ category: "Penicillins",
+ testType: "skin",
+ presentation: "500 mg powder for injection",
+ sptNeatConcentration: "0.2 mg/mL",
+ diluent: "0.9% sodium chloride (Initial Reconstitution: Add 4.6 mL NS to 500 mg vial to obtain 100 mg/mL; Intermediate Dilution: Draw 1.0 mL of 100 mg/mL solution, add 4.0 mL NS to result in 20 mg/mL)",
+ idtSteps: [s("1:100", "0.2 mg/mL", "0.1 mL of 2 mg/mL + 0.9 mL NS"), s("1:10", "2 mg/mL", "0.1 mL neat (20mg/ml) + 0.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "IV",
+ sourceSlug: "flucloxacillin",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "iv",
+ drugName: "Fluconazole",
+ category: "Others",
+ testType: "skin",
+ presentation: "200 mg/100 mL IV (2 mg/mL)",
+ sptNeatConcentration: "1:10 (0.2 mg/mL)",
+ diluent: "0.9% sodium chloride",
+ idtSteps: [s("1:1,000", "0.002 mg/mL", "0.1 mL of 0.02 mg/mL + 0.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "IV",
+ sourceSlug: "fluconazole",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "standard",
+ drugName: "Glycopyrronium",
+ category: "Others",
+ testType: "experimental",
+ presentation: "200 µg/1 mL (0.2 mg/mL)",
+ sptNeatConcentration: "Neat (0.2 mg/mL)",
+ diluent: "0.9% sodium chloride",
+ idtSteps: [s("1:1,000", "0.0002 mg/mL", "0.1 mL of 0.02 mg/mL + 0.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "",
+ sourceSlug: "glycopyrronium",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "iv",
+ drugName: "Granisetron",
+ category: "Others",
+ testType: "skin",
+ presentation: "3 mg/3 mL",
+ sptNeatConcentration: "Neat (1 mg/mL)",
+ diluent: "0.9% sodium chloride",
+ idtSteps: [s("1:100", "0.01 mg/mL", "0.1 mL of 0.1 mg/mL + 0.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "IV",
+ sourceSlug: "granisetron",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "sc",
+ drugName: "Heparin",
+ category: "Others",
+ testType: "skin",
+ presentation: "5000 U/mL (Common) or 25,000 U/5 mL (5000 U/mL)",
+ sptNeatConcentration: "Neat (5000 U/mL)",
+ diluent: "0.9% sodium chloride",
+ idtSteps: [s("1:1,000", "5 U/mL", "0.1 mL of 50 U/mL + 0.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "SC",
+ sourceSlug: "heparin",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "sc",
+ drugName: "Humulin NPH (Insulin)",
+ category: "Others",
+ testType: "skin",
+ presentation: "100 units/mL (10 mL vial)",
+ sptNeatConcentration: "Neat (100 U/mL)",
+ diluent: "19 mL N/S 0.9% for SPT dilution",
+ idtSteps: [s("1:20", "5 U/mL", "0.1 mL stock (100 U/mL) + 1.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "S/C",
+ sourceSlug: "humulin-nph",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "sc",
+ drugName: "Humulin R (Insulin)",
+ category: "Others",
+ testType: "skin",
+ presentation: "100 units/mL (10 mL vial)",
+ sptNeatConcentration: "Neat (100 U/mL)",
+ diluent: "19 mL N/S 0.9% for SPT dilution",
+ idtSteps: [s("1:20", "5 U/mL", "0.1 mL stock (100 U/mL) + 1.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "S/C",
+ sourceSlug: "humulin-r",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "iv",
+ drugName: "Hydrocortisone",
+ category: "Others",
+ testType: "experimental",
+ presentation: "100 mg powder for injection",
+ sptNeatConcentration: "Neat (50 mg/mL)",
+ diluent: "0.9% sodium chloride (reconstitute with 2 mL WFI/NS)",
+ idtSteps: [s("1:100", "0.5 mg/mL", "0.1 mL of 5 mg/mL + 0.9 mL NS"), s("1:10", "5 mg/mL", "0.1 mL neat + 0.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "IV",
+ sourceSlug: "hydrocortisone",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "standard",
+ drugName: "Lansoprazole",
+ category: "Proton Pump Inhibitors",
+ testType: "skin",
+ presentation: "30 mg capsules / orodispersible tablets",
+ sptNeatConcentration: "Neat (30 mg/mL)",
+ diluent: "0.9% sodium chloride (dissolve in 1 mL)",
+ idtSteps: [],
+ challengeSteps: [],
+ protocolLabel: "",
+ sourceSlug: "lansoprazole",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "standard",
+ drugName: "Latex",
+ category: "Others",
+ testType: "skin",
+ presentation: "Natural rubber latex extract",
+ sptNeatConcentration: "Neat extract",
+ diluent: "",
+ idtSteps: [],
+ challengeSteps: [],
+ protocolLabel: "",
+ sourceSlug: "latex",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "tablet",
+ drugName: "Levofloxacin",
+ needsPharmacyVerification: true,
+ category: "Others",
+ testType: "skin",
+ presentation: "500 mg tablets or IV formulation",
+ sptNeatConcentration: "Neat (5 mg/mL)",
+ diluent: "Normal saline for IDT dilutions (consult the Manufacturing Pharmacist for the exact diluent and method if using tablets)",
+ idtSteps: [s("1:100", "0.05 mg/mL", "0.1 mL of 0.5 mg/mL + 0.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "Tablet",
+ sourceSlug: "levofloxacin",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "oral",
+ drugName: "Levonorgestrel",
+ needsPharmacyVerification: true,
+ category: "Others",
+ testType: "skin",
+ presentation: "Levonorgestrel 750 µg tablet",
+ sptNeatConcentration: "Neat (crushed tablet solution)",
+ diluent: "0.9% sodium chloride",
+ idtSteps: [],
+ challengeSteps: [],
+ protocolLabel: "Oral",
+ sourceSlug: "levonorgestrel",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "inj",
+ drugName: "Medroxyprogesterone",
+ category: "Others",
+ testType: "skin",
+ presentation: "150 mg/1 mL",
+ sptNeatConcentration: "1:3 (50 mg/mL)",
+ diluent: "0.9% sodium chloride",
+ idtSteps: [s("1:100", "0.05 mg/mL", "0.1 mL of 1.5 mg/mL + 0.9 mL NS"), s("1:10", "5 mg/mL", "0.1 mL of 15 mg/mL + 0.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "Inj",
+ sourceSlug: "medroxyprogesterone",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "epidural",
+ drugName: "Mepivacaine",
+ category: "Local Anaesthetics",
+ testType: "skin",
+ presentation: "66 mg/2.2 mL (30 mg/mL) or 3% solution",
+ sptNeatConcentration: "Neat (10 mg/mL)",
+ diluent: "0.9% sodium chloride (reconstitute with 4.4 mL WFI to 2.2 mL stock)",
+ idtSteps: [s("1:1,000", "0.01 mg/mL", "0.1 mL of 0.1 mg/mL + 0.9 mL NS"), s("1:100", "0.1 mg/mL", "0.1 mL of 1 mg/mL + 0.9 mL NS"), s("1:10", "1 mg/mL", "0.1 mL neat + 0.9 mL NS"), s("Neat", "10 mg/mL", "10 mg/mL solution prepared above")],
+ challengeSteps: [],
+ protocolLabel: "Epidural",
+ sourceSlug: "mepivacaine",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "iv",
+ drugName: "Methylprednisolone",
+ category: "Others",
+ testType: "experimental",
+ presentation: "1 g powder for injection",
+ sptNeatConcentration: "Neat (20 mg/mL)",
+ diluent: "0.9% sodium chloride (reconstitute with 5 mL NS)",
+ idtSteps: [s("1:100", "0.2 mg/mL", "0.1 mL of 2 mg/mL + 0.9 mL NS"), s("1:10", "2 mg/mL", "0.1 mL neat + 0.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "IV",
+ sourceSlug: "methylprednisolone",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "iv",
+ drugName: "Metoclopramide",
+ category: "Others",
+ testType: "skin",
+ presentation: "10 mg/2 mL",
+ sptNeatConcentration: "Neat (5 mg/mL)",
+ diluent: "0.9% sodium chloride",
+ idtSteps: [s("1:1,000", "0.005 mg/mL", "0.1 mL of 0.5 mg/mL + 0.9 mL NS"), s("1:100", "0.05 mg/mL", "0.1 mL of 5 mg/mL + 0.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "IV",
+ sourceSlug: "metoclopramide",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "iv",
+ drugName: "Metronidazole",
+ category: "Others",
+ testType: "skin",
+ presentation: "500 mg/100 mL IV (5 mg/mL) or 200 mg tablets",
+ sptNeatConcentration: "Neat (5 mg/mL)",
+ diluent: "0.9% sodium chloride",
+ idtSteps: [s("1:100", "0.05 mg/mL", "0.1 mL of 0.5 mg/mL + 0.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "IV",
+ sourceSlug: "metronidazole",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "iv",
+ drugName: "Midazolam",
+ category: "Hypnotics",
+ testType: "skin",
+ presentation: "1 mg/mL or 5 mg/5 mL (1 mg/mL)",
+ sptNeatConcentration: "Neat (1 mg/mL)",
+ diluent: "0.9% sodium chloride",
+ idtSteps: [s("1:100", "0.01 mg/mL", "0.1 mL of 0.1 mg/mL + 0.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "IV",
+ sourceSlug: "midazolam",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "inj",
+ drugName: "Neostigmine",
+ category: "Others",
+ testType: "experimental",
+ presentation: "2.5 mg/1 mL",
+ sptNeatConcentration: "Neat (2.5 mg/mL)",
+ diluent: "0.9% sodium chloride",
+ idtSteps: [s("1:1,000", "0.0025 mg/mL", "0.1 mL of 0.25 mg/mL + 0.9 mL NS"), s("1:100", "0.025 mg/mL", "0.1 mL of 2.5 mg/mL + 0.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "Inj",
+ sourceSlug: "neostigmine",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "sc",
+ drugName: "Novorapid (Insulin)",
+ category: "Others",
+ testType: "skin",
+ presentation: "100 units/mL (10 mL vial / penfill)",
+ sptNeatConcentration: "Neat (100 U/mL)",
+ diluent: "19 mL N/S 0.9% for SPT dilution",
+ idtSteps: [s("1:20", "5 U/mL", "0.1 mL stock (100 U/mL) + 1.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "S/C",
+ sourceSlug: "novorapid",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "standard",
+ drugName: "Omeprazole",
+ category: "Proton Pump Inhibitors",
+ testType: "skin",
+ presentation: "20 mg tablets / capsules",
+ sptNeatConcentration: "Neat (20 mg/mL)",
+ diluent: "0.9% sodium chloride (dissolve in 1 mL)",
+ idtSteps: [],
+ challengeSteps: [],
+ protocolLabel: "",
+ sourceSlug: "omeprazole",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "iv-contrast",
+ drugName: "Omnipaque",
+ category: "Others",
+ testType: "skin",
+ presentation: "350 mg I/mL (Common) or 300 mg I/mL",
+ sptNeatConcentration: "Neat (350 mg I/mL)",
+ diluent: "0.9% sodium chloride",
+ idtSteps: [s("1:100", "3.5 mg I/mL", "0.1 mL of 1:10 + 0.9 mL NS"), s("1:10", "35 mg I/mL", "0.1 mL neat + 0.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "IV Contrast",
+ sourceSlug: "omnipaque",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "iv",
+ drugName: "Ondansetron",
+ category: "Others",
+ testType: "skin",
+ presentation: "4 mg/2 mL",
+ sptNeatConcentration: "Neat (2 mg/mL)",
+ diluent: "0.9% sodium chloride",
+ idtSteps: [s("1:1,000", "0.002 mg/mL", "0.1 mL of 0.2 mg/mL + 0.9 mL NS"), s("1:100", "0.02 mg/mL", "0.1 mL of 2 mg/mL + 0.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "IV",
+ sourceSlug: "ondansetron",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "sc",
+ drugName: "Optisulin (Insulin)",
+ category: "Others",
+ testType: "skin",
+ presentation: "100 units/mL (10 mL vial)",
+ sptNeatConcentration: "Neat (100 U/mL)",
+ diluent: "19 mL N/S 0.9% for SPT dilution",
+ idtSteps: [s("1:20", "5 U/mL", "0.1 mL stock (100 U/mL) + 1.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "S/C",
+ sourceSlug: "optisulin",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "iv",
+ drugName: "Oxycodone",
+ category: "Opioids",
+ testType: "skin",
+ presentation: "10 mg/mL",
+ sptNeatConcentration: "Neat (10 mg/mL)",
+ diluent: "0.9% sodium chloride",
+ idtSteps: [s("1:1,000", "0.01 mg/mL", "0.1 mL of 0.1 mg/mL + 0.9 mL NS"), s("1:100", "0.1 mg/mL", "0.1 mL neat + 0.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "IV",
+ sourceSlug: "oxycodone",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
{
id: "iv",
drugName: "Pancuronium",
@@ -61,13 +708,134 @@ export const GENERATED_PROTOCOLS: DrugProtocol[] = [
presentation: "4 mg/2 mL (2 mg/mL)",
sptNeatConcentration: "Neat (2 mg/mL)",
diluent: "0.9% sodium chloride",
- idtSteps: [s("1:1,000", "0.002 mg/mL"), s("1:100", "0.02 mg/mL")],
+ idtSteps: [s("1:1,000", "0.002 mg/mL", "0.1 mL of 0.02 mg/mL + 0.9 mL NS"), s("1:100", "0.02 mg/mL", "0.1 mL of 0.2 mg/mL + 0.9 mL NS")],
challengeSteps: [],
protocolLabel: "IV",
sourceSlug: "pancuronium",
underReview: false,
lastReviewed: "2026-03-28",
},
+ {
+ id: "iv",
+ drugName: "Pantoprazole",
+ category: "Proton Pump Inhibitors",
+ testType: "skin",
+ presentation: "40 mg powder for injection",
+ sptNeatConcentration: "Neat (4 mg/mL)",
+ diluent: "0.9% sodium chloride (reconstitute with 10 mL NS)",
+ idtSteps: [s("1:1,000", "0.004 mg/mL", "0.1 mL of 0.04 mg/mL + 0.9 mL NS"), s("1:100", "0.04 mg/mL", "0.1 mL of 0.4 mg/mL + 0.9 mL NS"), s("1:10", "0.4 mg/mL", "0.1 mL neat + 0.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "IV",
+ sourceSlug: "pantoprazole",
+ underReview: true,
+ reviewNote: "The Spreadsheet 2 spreadsheet labels the SPT concentration as \"Neat (40 mg/mL)\". This is a spreadsheet labelling error — the correct reconstituted concentration is 4 mg/mL (40 mg powder + 10 mL NS).",
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "iv",
+ drugName: "Paracetamol",
+ category: "Others",
+ testType: "skin",
+ presentation: "1 g/100 mL IV (10 mg/mL) or 500 mg tablets",
+ sptNeatConcentration: "Neat (10 mg/mL)",
+ diluent: "0.9% sodium chloride",
+ idtSteps: [s("1:10", "1 mg/mL", "0.1 mL of stock (10 mg/mL) + 0.9 mL NS"), s("1:100", "0.1 mg/mL", "0.1 mL of 1 mg/mL + 0.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "IV",
+ sourceSlug: "paracetamol",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "iv",
+ drugName: "Parecoxib",
+ category: "Others",
+ testType: "skin",
+ presentation: "40 mg powder for injection",
+ sptNeatConcentration: "Neat (8 mg/mL)",
+ diluent: "0.9% sodium chloride (reconstitute with 5 mL NS)",
+ idtSteps: [s("1:100", "0.08 mg/mL", "0.1 mL of 0.8 mg/mL + 0.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "IV",
+ sourceSlug: "parecoxib",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "sc",
+ drugName: "Patent Blue",
+ category: "Others",
+ testType: "skin",
+ presentation: "2.5% (25 mg/mL) or 10% (100 mg/mL)",
+ sptNeatConcentration: "Neat (25 mg/mL)",
+ diluent: "0.9% sodium chloride",
+ idtSteps: [s("1:1,000", "0.025 mg/mL", "0.1 mL of 0.25 mg/mL + 0.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "SC",
+ sourceSlug: "patent-blue",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "iv",
+ drugName: "Propofol",
+ category: "Hypnotics",
+ testType: "skin",
+ presentation: "200 mg/20 mL (10 mg/mL) or 500 mg/50 mL (10 mg/mL)",
+ sptNeatConcentration: "Neat (10 mg/mL)",
+ diluent: "0.9% sodium chloride",
+ idtSteps: [s("1:1,000", "0.01 mg/mL", "0.1 mL of 0.1 mg/mL + 0.9 mL NS"), s("1:100", "0.1 mg/mL", "0.1 mL of 1 mg/mL + 0.9 mL NS"), s("1:10", "1 mg/mL", "0.1 mL neat + 0.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "IV",
+ sourceSlug: "propofol",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "iv",
+ drugName: "Protamine",
+ category: "Others",
+ testType: "skin",
+ presentation: "50 mg/5 mL (10 mg/mL)",
+ sptNeatConcentration: "Neat (10 mg/mL)",
+ diluent: "0.9% sodium chloride",
+ idtSteps: [s("1:1,000", "0.01 mg/mL", "0.1 mL of 1 mg/mL + 0.9 mL NS"), s("0.1:20", "0.05 mg/mL", "0.1 mL of 10 mg/mL + 1.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "IV",
+ sourceSlug: "protamine",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "sc",
+ drugName: "Protaphane (Insulin)",
+ category: "Others",
+ testType: "skin",
+ presentation: "100 units/mL (10 mL vial)",
+ sptNeatConcentration: "Neat (100 U/mL)",
+ diluent: "19 mL N/S 0.9% for SPT dilution",
+ idtSteps: [s("1:20", "5 U/mL", "0.1 mL stock (100 U/mL) + 1.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "S/C",
+ sourceSlug: "protaphane",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "standard",
+ drugName: "Rabeprazole",
+ category: "Proton Pump Inhibitors",
+ testType: "skin",
+ presentation: "20 mg tablets",
+ sptNeatConcentration: "Neat (40 mg/mL)",
+ diluent: "0.9% sodium chloride (dissolve in 1 mL)",
+ idtSteps: [],
+ challengeSteps: [],
+ protocolLabel: "",
+ sourceSlug: "rabeprazole",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
{
id: "iv",
drugName: "Rocuronium",
@@ -76,13 +844,28 @@ export const GENERATED_PROTOCOLS: DrugProtocol[] = [
presentation: "50 mg/5 mL (10 mg/mL)",
sptNeatConcentration: "Neat (10 mg/mL)",
diluent: "0.9% sodium chloride",
- idtSteps: [s("1:1,000", "0.01 mg/mL"), s("1:100", "0.1 mg/mL")],
+ idtSteps: [s("1:1,000", "0.01 mg/mL", "0.1 mL of 0.1 mg/mL + 0.9 mL NS"), s("1:100", "0.1 mg/mL", "0.1 mL of 1 mg/mL + 0.9 mL NS")],
challengeSteps: [],
protocolLabel: "IV",
sourceSlug: "rocuronium",
underReview: false,
lastReviewed: "2026-03-28",
},
+ {
+ id: "alone",
+ drugName: "Sugammadex (Alone)",
+ category: "Reversal Agents",
+ testType: "skin",
+ presentation: "200 mg/2 mL (100 mg/mL) or 500 mg/5 mL (100 mg/mL)",
+ sptNeatConcentration: "Neat (100 mg/mL)",
+ diluent: "0.9% sodium chloride",
+ idtSteps: [s("1:1,000", "0.1 mg/mL", "0.1 mL of 1 mg/mL + 0.9 mL NS"), s("1:100", "1 mg/mL", "0.1 mL of 10 mg/mL + 0.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "Alone",
+ sourceSlug: "sugammadex",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
{
id: "iv",
drugName: "Suxamethonium",
@@ -91,13 +874,104 @@ export const GENERATED_PROTOCOLS: DrugProtocol[] = [
presentation: "100 mg/2 mL (50 mg/mL)",
sptNeatConcentration: "1:5 (10 mg/mL)",
diluent: "0.9% sodium chloride",
- idtSteps: [s("1:50,000", "0.001 mg/mL"), s("1:5,000", "0.01 mg/mL"), s("1:500", "0.1 mg/mL")],
+ idtSteps: [s("1:50,000", "0.001 mg/mL", "0.1 mL of 0.01 mg/mL + 0.9 mL NS"), s("1:5,000", "0.01 mg/mL", "0.1 mL of 0.1 mg/mL + 0.9 mL NS"), s("1:500", "0.1 mg/mL", "0.1 mL of 1 mg/mL + 0.9 mL NS")],
challengeSteps: [],
protocolLabel: "IV",
sourceSlug: "suxamethonium",
underReview: false,
lastReviewed: "2026-03-28",
},
+ {
+ id: "iv",
+ drugName: "Tazocin",
+ category: "Penicillins",
+ testType: "skin",
+ presentation: "4 g / 500 mg powder for injection",
+ sptNeatConcentration: "1:10 — ⚠️ concentration under review (Medication List: 2 mg/mL; calculation: 20 mg/mL)",
+ diluent: "0.9% sodium chloride (reconstitute with 20 mL NS)",
+ idtSteps: [s("1:100", "2/0.2 mg/mL", "0.1 mL of 20/2 mg/mL + 0.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "IV",
+ sourceSlug: "tazocin",
+ underReview: true,
+ reviewNote: "Concentration discrepancy: Medication List specifies SPT at 1:10 (2 mg/mL Piperacillin), whereas calculation of 1:10 of 200 mg/mL gives 20 mg/mL. Concentration under clinical review.",
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "iv",
+ drugName: "Tramadol",
+ category: "Others",
+ testType: "experimental",
+ presentation: "100 mg/2 mL",
+ sptNeatConcentration: "Neat (50 mg/mL)",
+ diluent: "0.9% sodium chloride",
+ idtSteps: [s("1:100", "0.5 mg/mL", "0.1 mL of 5 mg/mL + 0.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "IV",
+ sourceSlug: "tramadol",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "iv",
+ drugName: "Tranexamic Acid",
+ category: "Others",
+ testType: "skin",
+ presentation: "500 mg/5 mL (100 mg/mL)",
+ sptNeatConcentration: "Neat (100 mg/mL)",
+ diluent: "0.9% sodium chloride",
+ idtSteps: [s("1:100", "1 mg/mL", "0.1 mL of 10 mg/mL + 0.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "IV",
+ sourceSlug: "tranexamic-acid",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "inj",
+ drugName: "Triamcinolone",
+ category: "Others",
+ testType: "experimental",
+ presentation: "40 mg/mL",
+ sptNeatConcentration: "1:10 (4 mg/mL)",
+ diluent: "0.9% sodium chloride",
+ idtSteps: [s("1:1,000", "0.04 mg/mL", "0.1 mL of 4 mg/mL + 0.9 mL NS"), s("1:100", "0.4 mg/mL", "0.1 mL of 40 mg/mL + 0.9 mL NS"), s("1:10", "4 mg/mL", "0.1 mL of 40 mg/mL + 0.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "Inj",
+ sourceSlug: "triamcinolone",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "iv-contrast",
+ drugName: "Urografin",
+ category: "Others",
+ testType: "skin",
+ presentation: "Urografin 150 (150 mg I/mL) or Urografin 370 (370 mg I/mL)",
+ sptNeatConcentration: "Neat",
+ diluent: "0.9% sodium chloride",
+ idtSteps: [s("1:100", "—", "0.1 mL of 1:10 + 0.9 mL NS"), s("1:10", "—", "0.1 mL neat + 0.9 mL NS"), s("Neat", "500 U/mL", "Undiluted stock")],
+ challengeSteps: [],
+ protocolLabel: "IV Contrast",
+ sourceSlug: "urografin",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "iv",
+ drugName: "Vancomycin",
+ category: "Others",
+ testType: "skin",
+ presentation: "500 mg or 1 g powder for injection",
+ sptNeatConcentration: "Neat (100 mg/mL)",
+ diluent: "0.9% sodium chloride (add 5 mL NS to 500 mg or 10 mL NS to 1 g)",
+ idtSteps: [s("1:1,000,000", "0.0001 mg/mL", "0.1 mL of 0.001 mg/mL + 0.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "IV",
+ sourceSlug: "vancomycin",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
{
id: "iv",
drugName: "Vecuronium",
@@ -106,11 +980,41 @@ export const GENERATED_PROTOCOLS: DrugProtocol[] = [
presentation: "10 mg powder for injection",
sptNeatConcentration: "Neat (4 mg/mL)",
diluent: "0.9% sodium chloride (reconstitute with 2.5 mL WFI)",
- idtSteps: [s("1:1,000", "0.004 mg/mL"), s("1:100", "0.04 mg/mL")],
+ idtSteps: [s("1:1,000", "0.004 mg/mL", "0.1 mL of 0.04 mg/mL + 0.9 mL NS"), s("1:100", "0.04 mg/mL", "0.1 mL of 0.4 mg/mL + 0.9 mL NS")],
challengeSteps: [],
protocolLabel: "IV",
sourceSlug: "vecuronium",
underReview: false,
lastReviewed: "2026-03-28",
},
+ {
+ id: "iv-contrast",
+ drugName: "Visipaque",
+ category: "Others",
+ testType: "skin",
+ presentation: "320 mg I/mL (Common) or 270 mg I/mL",
+ sptNeatConcentration: "Neat (320 mg I/mL)",
+ diluent: "0.9% sodium chloride",
+ idtSteps: [s("1:100", "3.2 mg I/mL", "0.1 mL of 1:10 + 0.9 mL NS"), s("1:10", "32 mg I/mL", "0.1 mL neat + 0.9 mL NS")],
+ challengeSteps: [],
+ protocolLabel: "IV Contrast",
+ sourceSlug: "visipaque",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
+ {
+ id: "iv",
+ drugName: "Xylocaine",
+ category: "Others",
+ testType: "skin",
+ presentation: "50 mg/5 mL (10 mg/mL)",
+ sptNeatConcentration: "Neat (10 mg/mL)",
+ diluent: "0.9% sodium chloride",
+ idtSteps: [s("1:1,000", "0.01 mg/mL", "0.1 mL of 0.1 mg/mL + 0.9 mL NS"), s("1:100", "0.1 mg/mL", "0.1 mL of 1 mg/mL + 0.9 mL NS"), s("1:10", "1 mg/mL", "0.1 mL neat + 0.9 mL NS"), s("Neat", "10 mg/mL", "Undiluted stock")],
+ challengeSteps: [],
+ protocolLabel: "IV",
+ sourceSlug: "xylocaine",
+ underReview: false,
+ lastReviewed: "2026-03-28",
+ },
];
diff --git a/src/shared/data/drugMasterlist.test.ts b/src/shared/data/drugMasterlist.test.ts
index 54585a4..ec568af 100644
--- a/src/shared/data/drugMasterlist.test.ts
+++ b/src/shared/data/drugMasterlist.test.ts
@@ -9,11 +9,13 @@ import {
getDrugsByCategory,
getChallengeDrugsByCategory,
} from './drugMasterlist';
+import { GENERATED_PROTOCOLS } from './drugMasterlist.generated';
describe('drugMasterlist structure & integrity', () => {
- it('contains exactly 116 protocol records in the merged masterlist', () => {
- expect(DRUG_MASTERLIST).toHaveLength(116);
+ it('contains exactly 117 protocol records in the merged masterlist', () => {
+ expect(DRUG_MASTERLIST).toHaveLength(117);
expect(DREAM_ONLY_PROTOCOLS).toHaveLength(109);
+ expect(GENERATED_PROTOCOLS).toHaveLength(67);
});
it('defines a non-empty id for every protocol entry', () => {
@@ -64,10 +66,10 @@ describe('drugMasterlist array ordering & backwards compatibility', () => {
// order is a committed fixture rather than a `git show` of a previous revision:
// that baseline disappears the moment this change merges, and a check that quietly
// stops working is worse than no check.
- it('preserves the frozen positional ordering of all 116 records', () => {
- expect(expectedOrder.count).toBe(116);
- expect(expectedOrder.order).toHaveLength(116);
- expect(DRUG_MASTERLIST).toHaveLength(116);
+ it('preserves the frozen positional ordering of all 117 records', () => {
+ expect(expectedOrder.count).toBe(117);
+ expect(expectedOrder.order).toHaveLength(117);
+ expect(DRUG_MASTERLIST).toHaveLength(117);
for (let i = 0; i < expectedOrder.order.length; i++) {
const want = expectedOrder.order[i];
@@ -77,6 +79,14 @@ describe('drugMasterlist array ordering & backwards compatibility', () => {
expect(got.protocolLabel, `Position ${i} protocolLabel mismatch`).toBe(want.protocolLabel);
}
});
+
+ it('preserves the canonical SHA-256 hash of the 116 frozen pre-snapshot protocols', async () => {
+ const { computeTuplesHash, FROZEN_PRE_SNAPSHOT_PREFIX_SHA256 } = await import(
+ '../../../scripts/verify-order.mjs'
+ );
+ expect(computeTuplesHash(expectedOrder.order, 116)).toBe(FROZEN_PRE_SNAPSHOT_PREFIX_SHA256);
+ expect(computeTuplesHash(DRUG_MASTERLIST, 116)).toBe(FROZEN_PRE_SNAPSHOT_PREFIX_SHA256);
+ });
});
describe('drugMasterlist diluents & snapshot data', () => {
@@ -85,7 +95,7 @@ describe('drugMasterlist diluents & snapshot data', () => {
expect(getSkinProtocolsForDrug('Cis-atracurium')[0].diluent).toBe('0.9% sodium chloride');
expect(getSkinProtocolsForDrug('Vecuronium')[0].diluent).toBe('0.9% sodium chloride (reconstitute with 2.5 mL WFI)');
expect(getSkinProtocolsForDrug('Cefazolin')[0].diluent).toBe('0.9% sodium chloride (reconstitute with 10 mL WFI)');
- expect(getSkinProtocolsForDrug('Pantoprazole')[0].diluent).toBe('0.9% sodium chloride (reconstitute with 10 mL)');
+ expect(getSkinProtocolsForDrug('Pantoprazole')[0].diluent).toBe('0.9% sodium chloride (reconstitute with 10 mL NS)');
expect(getSkinProtocolsForDrug('Penicillin Major')[0].diluent).toBe('Phosphate-buffered saline (1 mL supplied diluent — not plain saline)');
});
@@ -119,6 +129,145 @@ describe('drugMasterlist diluents & snapshot data', () => {
});
});
+describe('snapshot-synchronized drugs exact values & clinical safety checks', () => {
+ it('verifies Cefuroxime uses source iv skin record and keeps Cefuroxime Suspension separate', () => {
+ const cefuroximeProtocols = getProtocolsForDrug('Cefuroxime');
+ expect(cefuroximeProtocols).toHaveLength(1);
+ const cef = cefuroximeProtocols[0];
+ expect(cef.id).toBe('iv');
+ expect(cef.testType).toBe('skin');
+ expect(cef.sourceSlug).toBe('cefuroxime');
+ expect(cef.sptNeatConcentration).toBe('Neat (25 mg/mL)');
+ expect(cef.idtSteps).toEqual([
+ { ratio: '1:100', concentration: '0.25 mg/mL', preparation: '0.1 mL of 2.5 mg/mL + 0.9 mL NS' },
+ { ratio: '1:10', concentration: '2.5 mg/mL', preparation: '0.1 mL of 25 mg/mL + 0.9 mL NS' },
+ ]);
+ expect(cef.underReview).toBe(false);
+
+ const suspension = getProtocolsForDrug('Cefuroxime Suspension');
+ expect(suspension).toHaveLength(1);
+ expect(suspension[0].id).toBe('suspension');
+ expect(suspension[0].needsPharmacyVerification).toBe(true);
+ });
+
+ it('verifies Flucloxacillin preserves DREAM challenge at index 110 and appends source skin record at index 116', () => {
+ const flucAll = getProtocolsForDrug('Flucloxacillin');
+ expect(flucAll).toHaveLength(2);
+
+ const challenge = DRUG_MASTERLIST[110];
+ expect(challenge.drugName).toBe('Flucloxacillin');
+ expect(challenge.testType).toBe('challenge');
+ expect(challenge.protocolLabel).toBe('Oral Graded Challenge');
+ expect(challenge.challengeSteps).toHaveLength(3);
+
+ const skin = DRUG_MASTERLIST[116];
+ expect(skin.drugName).toBe('Flucloxacillin');
+ expect(skin.testType).toBe('skin');
+ expect(skin.id).toBe('iv');
+ expect(skin.protocolLabel).toBe('IV');
+ expect(skin.sourceSlug).toBe('flucloxacillin');
+ expect(skin.sptNeatConcentration).toBe('0.2 mg/mL');
+ expect(skin.diluent).toBe('0.9% sodium chloride (Initial Reconstitution: Add 4.6 mL NS to 500 mg vial to obtain 100 mg/mL; Intermediate Dilution: Draw 1.0 mL of 100 mg/mL solution, add 4.0 mL NS to result in 20 mg/mL)');
+ expect(skin.idtSteps).toEqual([
+ { ratio: '1:100', concentration: '0.2 mg/mL', preparation: '0.1 mL of 2 mg/mL + 0.9 mL NS' },
+ { ratio: '1:10', concentration: '2 mg/mL', preparation: '0.1 mL neat (20mg/ml) + 0.9 mL NS' },
+ ]);
+ });
+
+ it('verifies Levofloxacin retains pharmacy verification flag and exact source IDT steps', () => {
+ const levo = getSkinProtocolsForDrug('Levofloxacin')[0];
+ expect(levo.id).toBe('tablet');
+ expect(levo.protocolLabel).toBe('Tablet');
+ expect(levo.needsPharmacyVerification).toBe(true);
+ expect(levo.sourceSlug).toBe('levofloxacin');
+ expect(levo.presentation).toBe('500 mg tablets or IV formulation');
+ expect(levo.sptNeatConcentration).toBe('Neat (5 mg/mL)');
+ expect(levo.diluent).toBe('Normal saline for IDT dilutions (consult the Manufacturing Pharmacist for the exact diluent and method if using tablets)');
+ expect(levo.idtSteps).toEqual([
+ { ratio: '1:100', concentration: '0.05 mg/mL', preparation: '0.1 mL of 0.5 mg/mL + 0.9 mL NS' },
+ ]);
+ });
+
+ it('verifies Levonorgestrel is source SPT-only with stale DREAM IDT removed', () => {
+ const levo = getSkinProtocolsForDrug('Levonorgestrel')[0];
+ expect(levo.id).toBe('oral');
+ expect(levo.protocolLabel).toBe('Oral');
+ expect(levo.needsPharmacyVerification).toBe(true);
+ expect(levo.sourceSlug).toBe('levonorgestrel');
+ expect(levo.sptNeatConcentration).toBe('Neat (crushed tablet solution)');
+ expect(levo.idtSteps).toEqual([]);
+ });
+
+ it('verifies Pantoprazole retains underReview flag, reviewNote, and 4 mg/mL concentration', () => {
+ const panto = getSkinProtocolsForDrug('Pantoprazole')[0];
+ expect(panto.id).toBe('iv');
+ expect(panto.sourceSlug).toBe('pantoprazole');
+ expect(panto.underReview).toBe(true);
+ expect(panto.reviewNote).toBe(
+ 'The Spreadsheet 2 spreadsheet labels the SPT concentration as "Neat (40 mg/mL)". This is a spreadsheet labelling error — the correct reconstituted concentration is 4 mg/mL (40 mg powder + 10 mL NS).'
+ );
+ expect(panto.sptNeatConcentration).toBe('Neat (4 mg/mL)');
+ expect(panto.diluent).toBe('0.9% sodium chloride (reconstitute with 10 mL NS)');
+ expect(panto.idtSteps).toHaveLength(3);
+ });
+
+ it('verifies Sugammadex (Alone) uses source id alone and keeps Sugammadex (+ Rocuronium) DREAM-only', () => {
+ const alone = DRUG_MASTERLIST[5];
+ expect(alone.drugName).toBe('Sugammadex (Alone)');
+ expect(alone.id).toBe('alone');
+ expect(alone.sourceSlug).toBe('sugammadex');
+ expect(alone.sptNeatConcentration).toBe('Neat (100 mg/mL)');
+ expect(alone.idtSteps).toEqual([
+ { ratio: '1:1,000', concentration: '0.1 mg/mL', preparation: '0.1 mL of 1 mg/mL + 0.9 mL NS' },
+ { ratio: '1:100', concentration: '1 mg/mL', preparation: '0.1 mL of 10 mg/mL + 0.9 mL NS' },
+ ]);
+
+ const combo = DRUG_MASTERLIST[6];
+ expect(combo.drugName).toBe('Sugammadex (+ Rocuronium)');
+ expect(combo.id).toBe('rocuronium');
+ expect(combo.sourceSlug).toBeUndefined();
+ });
+
+ it('verifies Tazocin retains underReview flag, reviewNote, and only the single source IDT row', () => {
+ const taz = getSkinProtocolsForDrug('Tazocin')[0];
+ expect(taz.id).toBe('iv');
+ expect(taz.sourceSlug).toBe('tazocin');
+ expect(taz.underReview).toBe(true);
+ expect(taz.reviewNote).toBe(
+ 'Concentration discrepancy: Medication List specifies SPT at 1:10 (2 mg/mL Piperacillin), whereas calculation of 1:10 of 200 mg/mL gives 20 mg/mL. Concentration under clinical review.'
+ );
+ expect(taz.sptNeatConcentration).toBe('1:10 — ⚠️ concentration under review (Medication List: 2 mg/mL; calculation: 20 mg/mL)');
+ expect(taz.diluent).toBe('0.9% sodium chloride (reconstitute with 20 mL NS)');
+ expect(taz.idtSteps).toEqual([
+ { ratio: '1:100', concentration: '2/0.2 mg/mL', preparation: '0.1 mL of 20/2 mg/mL + 0.9 mL NS' },
+ ]);
+ });
+
+ it('verifies Urografin retains all three literal source rows', () => {
+ const uro = getSkinProtocolsForDrug('Urografin')[0];
+ expect(uro.id).toBe('iv-contrast');
+ expect(uro.sourceSlug).toBe('urografin');
+ expect(uro.sptNeatConcentration).toBe('Neat');
+ expect(uro.diluent).toBe('0.9% sodium chloride');
+ expect(uro.idtSteps).toEqual([
+ { ratio: '1:100', concentration: '—', preparation: '0.1 mL of 1:10 + 0.9 mL NS' },
+ { ratio: '1:10', concentration: '—', preparation: '0.1 mL neat + 0.9 mL NS' },
+ { ratio: 'Neat', concentration: '500 U/mL', preparation: 'Undiluted stock' },
+ ]);
+ });
+
+ it('verifies Vancomycin retains only the single source IDT row and source diluent', () => {
+ const vanc = getSkinProtocolsForDrug('Vancomycin')[0];
+ expect(vanc.id).toBe('iv');
+ expect(vanc.sourceSlug).toBe('vancomycin');
+ expect(vanc.sptNeatConcentration).toBe('Neat (100 mg/mL)');
+ expect(vanc.diluent).toBe('0.9% sodium chloride (add 5 mL NS to 500 mg or 10 mL NS to 1 g)');
+ expect(vanc.idtSteps).toEqual([
+ { ratio: '1:1,000,000', concentration: '0.0001 mg/mL', preparation: '0.1 mL of 0.001 mg/mL + 0.9 mL NS' },
+ ]);
+ });
+});
+
describe('drugMasterlist pharmacy verification flags', () => {
it('flags exactly the unresolved skin protocols named in the release warning', () => {
const flaggedProtocols = DRUG_MASTERLIST.filter(protocol => protocol.needsPharmacyVerification === true);
diff --git a/src/shared/data/drugMasterlist.ts b/src/shared/data/drugMasterlist.ts
index 7d371f6..76ca0b7 100644
--- a/src/shared/data/drugMasterlist.ts
+++ b/src/shared/data/drugMasterlist.ts
@@ -21,6 +21,23 @@ function findGenerated(
return match;
}
+function findDreamOnly(
+ drugName: string,
+ testType: 'skin' | 'challenge' | 'control' | 'experimental',
+ protocolLabel?: string
+): DrugProtocol {
+ const match = DREAM_ONLY_PROTOCOLS.find(
+ (p) =>
+ p.drugName === drugName &&
+ p.testType === testType &&
+ (!protocolLabel || p.protocolLabel === protocolLabel)
+ );
+ if (!match) {
+ throw new Error(`Missing DREAM-only protocol for ${drugName} (${testType}${protocolLabel ? ` - ${protocolLabel}` : ''})`);
+ }
+ return match;
+}
+
/**
* Complete drug masterlist for allergy testing and challenges.
* Combines generated protocols from SCRATCH (protocols.snapshot.json) with
@@ -30,27 +47,240 @@ function findGenerated(
* with saved plans that reference protocolIndex.
*/
export const DRUG_MASTERLIST: DrugProtocol[] = [
- // ── MUSCLE RELAXANTS (from SCRATCH snapshot) ────────────────────────────────
- findGenerated('Cis-atracurium', 'skin'),
- findGenerated('Rocuronium', 'skin'),
- findGenerated('Pancuronium', 'skin'),
- findGenerated('Vecuronium', 'skin'),
- findGenerated('Suxamethonium', 'skin'),
-
- // ── REVERSAL AGENTS & PENICILLINS (DREAM-only) ──────────────────────────────
- ...DREAM_ONLY_PROTOCOLS.slice(0, 17),
-
- // ── CEPHALOSPORINS: Cefazolin skin (from SCRATCH snapshot) ──────────────────
- findGenerated('Cefazolin', 'skin'),
-
- // ── REST OF DRUGS (DREAM-only) ──────────────────────────────────────────────
- ...DREAM_ONLY_PROTOCOLS.slice(17, 100),
-
- // ── CEFAZOLIN CHALLENGE (from SCRATCH snapshot) ─────────────────────────────
- findGenerated('Cefazolin', 'challenge'),
-
- // ── REMAINING CHALLENGES (DREAM-only) ───────────────────────────────────────
- ...DREAM_ONLY_PROTOCOLS.slice(100),
+ // [0] Cis-atracurium (skin, IV)
+ findGenerated("Cis-atracurium", "skin", "IV"),
+ // [1] Rocuronium (skin, IV)
+ findGenerated("Rocuronium", "skin", "IV"),
+ // [2] Pancuronium (skin, IV)
+ findGenerated("Pancuronium", "skin", "IV"),
+ // [3] Vecuronium (skin, IV)
+ findGenerated("Vecuronium", "skin", "IV"),
+ // [4] Suxamethonium (skin, IV)
+ findGenerated("Suxamethonium", "skin", "IV"),
+ // [5] Sugammadex (Alone) (skin, Alone)
+ findGenerated("Sugammadex (Alone)", "skin", "Alone"),
+ // [6] Sugammadex (+ Rocuronium) (skin, + Rocuronium)
+ findDreamOnly("Sugammadex (+ Rocuronium)", "skin", "+ Rocuronium"),
+ // [7] Penicillin Major (skin, PPL)
+ findDreamOnly("Penicillin Major", "skin", "PPL"),
+ // [8] Penicillin Minor (skin, MD)
+ findDreamOnly("Penicillin Minor", "skin", "MD"),
+ // [9] Ampicillin (skin, Neat SPT)
+ findDreamOnly("Ampicillin", "skin", "Neat SPT"),
+ // [10] Ampicillin (skin, 1:5 SPT)
+ findDreamOnly("Ampicillin", "skin", "1:5 SPT"),
+ // [11] Ampicillin (control, Control)
+ findDreamOnly("Ampicillin", "control", "Control"),
+ // [12] Amoxycillin (skin, Neat SPT)
+ findDreamOnly("Amoxycillin", "skin", "Neat SPT"),
+ // [13] Amoxycillin (skin, 1:5 SPT)
+ findDreamOnly("Amoxycillin", "skin", "1:5 SPT"),
+ // [14] Benzylpenicillin (skin, 1:1,000 start)
+ findDreamOnly("Benzylpenicillin", "skin", "1:1,000 start"),
+ // [15] Benzylpenicillin (skin, 1:100 start)
+ findDreamOnly("Benzylpenicillin", "skin", "1:100 start"),
+ // [16] Benzylpenicillin (control, Control)
+ findDreamOnly("Benzylpenicillin", "control", "Control"),
+ // [17] Augmentin (skin, 1:1,000 start)
+ findDreamOnly("Augmentin", "skin", "1:1,000 start"),
+ // [18] Augmentin (skin, 1:100 start)
+ findDreamOnly("Augmentin", "skin", "1:100 start"),
+ // [19] Cephalexin (skin, IV)
+ findDreamOnly("Cephalexin", "skin", "IV"),
+ // [20] Tazocin (skin, IV)
+ findGenerated("Tazocin", "skin", "IV"),
+ // [21] Methoxybenzylpenicillin (skin)
+ findDreamOnly("Methoxybenzylpenicillin", "skin"),
+ // [22] Cefazolin (skin, IV)
+ findGenerated("Cefazolin", "skin", "IV"),
+ // [23] Cefepime (skin, IV)
+ findGenerated("Cefepime", "skin", "IV"),
+ // [24] Cefotaxime (skin, IV)
+ findGenerated("Cefotaxime", "skin", "IV"),
+ // [25] Ceftazidime (skin, IV)
+ findGenerated("Ceftazidime", "skin", "IV"),
+ // [26] Ceftriaxone (skin, IV)
+ findGenerated("Ceftriaxone", "skin", "IV"),
+ // [27] Cefuroxime (skin, IV)
+ findGenerated("Cefuroxime", "skin", "IV"),
+ // [28] Midazolam (skin, IV)
+ findGenerated("Midazolam", "skin", "IV"),
+ // [29] Propofol (skin, IV)
+ findGenerated("Propofol", "skin", "IV"),
+ // [30] Ketamine (skin, 1:1,000 start)
+ findDreamOnly("Ketamine", "skin", "1:1,000 start"),
+ // [31] Ketamine (skin, 1:100 start)
+ findDreamOnly("Ketamine", "skin", "1:100 start"),
+ // [32] Thiopental (skin, 1:1,000 start)
+ findDreamOnly("Thiopental", "skin", "1:1,000 start"),
+ // [33] Thiopental (skin, 1:100 start)
+ findDreamOnly("Thiopental", "skin", "1:100 start"),
+ // [34] Lignocaine (skin, IV)
+ findDreamOnly("Lignocaine", "skin", "IV"),
+ // [35] Mepivacaine (skin, Epidural)
+ findGenerated("Mepivacaine", "skin", "Epidural"),
+ // [36] Bupivacaine (skin, Epidural)
+ findGenerated("Bupivacaine", "skin", "Epidural"),
+ // [37] Ropivacaine (skin, Epidural Protocol 1)
+ findDreamOnly("Ropivacaine", "skin", "Epidural Protocol 1"),
+ // [38] Ropivacaine (skin, Epidural Protocol 2)
+ findDreamOnly("Ropivacaine", "skin", "Epidural Protocol 2"),
+ // [39] Alfentanil (skin, IV)
+ findGenerated("Alfentanil", "skin", "IV"),
+ // [40] Fentanyl (skin, IV)
+ findGenerated("Fentanyl", "skin", "IV"),
+ // [41] Morphine (skin, 1:1,000 start)
+ findDreamOnly("Morphine", "skin", "1:1,000 start"),
+ // [42] Morphine (skin, 1:100 start)
+ findDreamOnly("Morphine", "skin", "1:100 start"),
+ // [43] Remifentanil (skin, 1:1,000 start)
+ findDreamOnly("Remifentanil", "skin", "1:1,000 start"),
+ // [44] Remifentanil (skin, 1:100 start)
+ findDreamOnly("Remifentanil", "skin", "1:100 start"),
+ // [45] Oxycodone (skin, IV)
+ findGenerated("Oxycodone", "skin", "IV"),
+ // [46] Chlorhexidine (skin, 0.02%)
+ findGenerated("Chlorhexidine", "skin", "0.02%"),
+ // [47] Povidone Iodine (skin, 1:1,000 start)
+ findDreamOnly("Povidone Iodine", "skin", "1:1,000 start"),
+ // [48] Povidone Iodine (skin, 1:100 start)
+ findDreamOnly("Povidone Iodine", "skin", "1:100 start"),
+ // [49] Esomeprazole (skin)
+ findGenerated("Esomeprazole", "skin"),
+ // [50] Lansoprazole (skin)
+ findGenerated("Lansoprazole", "skin"),
+ // [51] Omeprazole (skin)
+ findGenerated("Omeprazole", "skin"),
+ // [52] Pantoprazole (skin, IV)
+ findGenerated("Pantoprazole", "skin", "IV"),
+ // [53] Rabeprazole (skin)
+ findGenerated("Rabeprazole", "skin"),
+ // [54] Actrapid (Insulin) (skin, S/C)
+ findGenerated("Actrapid (Insulin)", "skin", "S/C"),
+ // [55] Azithromycin (skin, IV)
+ findGenerated("Azithromycin", "skin", "IV"),
+ // [56] Betamethasone (experimental, IV)
+ findGenerated("Betamethasone", "experimental", "IV"),
+ // [57] Cefuroxime Suspension (skin, Suspension)
+ findDreamOnly("Cefuroxime Suspension", "skin", "Suspension"),
+ // [58] Ciprofloxacin (skin, IV)
+ findDreamOnly("Ciprofloxacin", "skin", "IV"),
+ // [59] Clindamycin (skin, IV)
+ findGenerated("Clindamycin", "skin", "IV"),
+ // [60] Dalteparin (skin, SC)
+ findGenerated("Dalteparin", "skin", "SC"),
+ // [61] Dexamethasone (skin, IV)
+ findGenerated("Dexamethasone", "skin", "IV"),
+ // [62] Doxycycline (skin, 1:1,000 start)
+ findDreamOnly("Doxycycline", "skin", "1:1,000 start"),
+ // [63] Doxycycline (skin, 1:100 start)
+ findDreamOnly("Doxycycline", "skin", "1:100 start"),
+ // [64] Droperidol (skin, IV)
+ findGenerated("Droperidol", "skin", "IV"),
+ // [65] Enoxaparin (skin, SC)
+ findGenerated("Enoxaparin", "skin", "SC"),
+ // [66] Fluconazole (skin, IV)
+ findGenerated("Fluconazole", "skin", "IV"),
+ // [67] Glycopyrronium (experimental)
+ findGenerated("Glycopyrronium", "experimental"),
+ // [68] Granisetron (skin, IV)
+ findGenerated("Granisetron", "skin", "IV"),
+ // [69] Heparin (skin, SC)
+ findGenerated("Heparin", "skin", "SC"),
+ // [70] Humulin NPH (Insulin) (skin, S/C)
+ findGenerated("Humulin NPH (Insulin)", "skin", "S/C"),
+ // [71] Humulin R (Insulin) (skin, S/C)
+ findGenerated("Humulin R (Insulin)", "skin", "S/C"),
+ // [72] Hydrocortisone (experimental, IV)
+ findGenerated("Hydrocortisone", "experimental", "IV"),
+ // [73] Latex (skin)
+ findGenerated("Latex", "skin"),
+ // [74] Levofloxacin (skin, Tablet)
+ findGenerated("Levofloxacin", "skin", "Tablet"),
+ // [75] Levonorgestrel (skin, Oral)
+ findGenerated("Levonorgestrel", "skin", "Oral"),
+ // [76] Medroxyprogesterone (skin, Inj)
+ findGenerated("Medroxyprogesterone", "skin", "Inj"),
+ // [77] Metacresol (skin, 1:1,000 start)
+ findDreamOnly("Metacresol", "skin", "1:1,000 start"),
+ // [78] Metacresol (skin, 1:100 start)
+ findDreamOnly("Metacresol", "skin", "1:100 start"),
+ // [79] Methylprednisolone (experimental, IV)
+ findGenerated("Methylprednisolone", "experimental", "IV"),
+ // [80] Metoclopramide (skin, IV)
+ findGenerated("Metoclopramide", "skin", "IV"),
+ // [81] Metronidazole (skin, IV)
+ findGenerated("Metronidazole", "skin", "IV"),
+ // [82] Neostigmine (experimental, Inj)
+ findGenerated("Neostigmine", "experimental", "Inj"),
+ // [83] Novorapid (Insulin) (skin, S/C)
+ findGenerated("Novorapid (Insulin)", "skin", "S/C"),
+ // [84] Omnipaque (skin, IV Contrast)
+ findGenerated("Omnipaque", "skin", "IV Contrast"),
+ // [85] Ondansetron (skin, IV)
+ findGenerated("Ondansetron", "skin", "IV"),
+ // [86] Optisulin (Insulin) (skin, S/C)
+ findGenerated("Optisulin (Insulin)", "skin", "S/C"),
+ // [87] Paracetamol (skin, IV)
+ findGenerated("Paracetamol", "skin", "IV"),
+ // [88] Parecoxib (skin, IV)
+ findGenerated("Parecoxib", "skin", "IV"),
+ // [89] Patent Blue (skin, SC)
+ findGenerated("Patent Blue", "skin", "SC"),
+ // [90] Protamine (skin, IV)
+ findGenerated("Protamine", "skin", "IV"),
+ // [91] Protaphane (Insulin) (skin, S/C)
+ findGenerated("Protaphane (Insulin)", "skin", "S/C"),
+ // [92] Tranexamic Acid (skin, IV)
+ findGenerated("Tranexamic Acid", "skin", "IV"),
+ // [93] Tramadol (experimental, IV)
+ findGenerated("Tramadol", "experimental", "IV"),
+ // [94] Triamcinolone (experimental, Inj)
+ findGenerated("Triamcinolone", "experimental", "Inj"),
+ // [95] Ultravist (skin, IV Contrast)
+ findDreamOnly("Ultravist", "skin", "IV Contrast"),
+ // [96] Ultravist (control, Control)
+ findDreamOnly("Ultravist", "control", "Control"),
+ // [97] Urografin (skin, IV Contrast)
+ findGenerated("Urografin", "skin", "IV Contrast"),
+ // [98] Vancomycin (skin, IV)
+ findGenerated("Vancomycin", "skin", "IV"),
+ // [99] Visipaque (skin, IV Contrast)
+ findGenerated("Visipaque", "skin", "IV Contrast"),
+ // [100] Xylocaine (skin, IV)
+ findGenerated("Xylocaine", "skin", "IV"),
+ // [101] Methylene Blue (skin)
+ findDreamOnly("Methylene Blue", "skin"),
+ // [102] IV Contrast (skin)
+ findDreamOnly("IV Contrast", "skin"),
+ // [103] Atropine (skin)
+ findDreamOnly("Atropine", "skin"),
+ // [104] Amoxycillin Suspension (challenge, Oral Graded Challenge)
+ findDreamOnly("Amoxycillin Suspension", "challenge", "Oral Graded Challenge"),
+ // [105] Amoxycillin/Clavulanic Acid (challenge, Oral Graded Challenge)
+ findDreamOnly("Amoxycillin/Clavulanic Acid", "challenge", "Oral Graded Challenge"),
+ // [106] Cefazolin (challenge, IV Challenge)
+ findGenerated("Cefazolin", "challenge", "IV Challenge"),
+ // [107] Cephalexin (challenge, Oral Graded Challenge)
+ findDreamOnly("Cephalexin", "challenge", "Oral Graded Challenge"),
+ // [108] Ciprofloxacin (challenge, Oral Graded Challenge)
+ findDreamOnly("Ciprofloxacin", "challenge", "Oral Graded Challenge"),
+ // [109] Doxycycline (challenge, Oral Graded Challenge)
+ findDreamOnly("Doxycycline", "challenge", "Oral Graded Challenge"),
+ // [110] Flucloxacillin (challenge, Oral Graded Challenge)
+ findDreamOnly("Flucloxacillin", "challenge", "Oral Graded Challenge"),
+ // [111] Lignocaine (challenge, Challenge)
+ findDreamOnly("Lignocaine", "challenge", "Challenge"),
+ // [112] Meloxicam (challenge, Graded Challenge)
+ findDreamOnly("Meloxicam", "challenge", "Graded Challenge"),
+ // [113] Trimethoprim/Sulfamethoxazole (challenge, Oral Graded Challenge)
+ findDreamOnly("Trimethoprim/Sulfamethoxazole", "challenge", "Oral Graded Challenge"),
+ // [114] Trimethoprim (challenge, Oral Graded Challenge)
+ findDreamOnly("Trimethoprim", "challenge", "Oral Graded Challenge"),
+ // [115] Voltaren (Diclofenac) (challenge, Graded Challenge)
+ findDreamOnly("Voltaren (Diclofenac)", "challenge", "Graded Challenge"),
+ // [116] Flucloxacillin (skin, IV)
+ findGenerated("Flucloxacillin", "skin", "IV"),
];
// ── Category ordering ──────────────────────────────────────────────────────
diff --git a/src/shared/data/expectedProtocolOrder.json b/src/shared/data/expectedProtocolOrder.json
index c6d3f59..95f0499 100644
--- a/src/shared/data/expectedProtocolOrder.json
+++ b/src/shared/data/expectedProtocolOrder.json
@@ -1,7 +1,7 @@
{
"_comment": "Frozen positional order of DRUG_MASTERLIST as it existed before generation from SCRATCH. DREAM selects protocol variants by array index, so a shift here re-points saved clinical plans at different doses. Captured from origin/main at the Phase 2 cutover. Only change this deliberately, alongside a migration for stored protocolIndex values.",
"capturedFrom": "origin/main @ pre-snapshot cutover",
- "count": 116,
+ "count": 117,
"order": [
{
"drugName": "Cis-atracurium",
@@ -582,6 +582,11 @@
"drugName": "Voltaren (Diclofenac)",
"testType": "challenge",
"protocolLabel": "Graded Challenge"
+ },
+ {
+ "drugName": "Flucloxacillin",
+ "testType": "skin",
+ "protocolLabel": "IV"
}
]
}
diff --git a/src/shared/data/protocols.snapshot.json b/src/shared/data/protocols.snapshot.json
index 1807879..2c01356 100644
--- a/src/shared/data/protocols.snapshot.json
+++ b/src/shared/data/protocols.snapshot.json
@@ -1,8 +1,209 @@
{
"schema_version": "1.1",
- "generated_at": "2026-08-20T20:20:12Z",
- "source_commit": "d9bc1e3",
+ "generated_at": "2026-08-20T23:24:09Z",
+ "source_commit": "2f820d8",
"drugs": [
+ {
+ "slug": "actrapid",
+ "title": "Actrapid",
+ "version": "1.1",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Others",
+ "drug_name": "Actrapid (Insulin)"
+ },
+ "protocols": [
+ {
+ "id": "sc",
+ "label": "S/C",
+ "test_type": "skin",
+ "presentation": "100 units/mL (10 mL vial)",
+ "diluent": "19 mL N/S 0.9% for SPT dilution",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "100 U/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:20",
+ "concentration": "5 U/mL",
+ "preparation": "0.1 mL stock (100 U/mL) + 1.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "alfentanil",
+ "title": "Alfentanil",
+ "version": "1.2",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Opioids"
+ },
+ "protocols": [
+ {
+ "id": "iv",
+ "label": "IV",
+ "test_type": "skin",
+ "presentation": "1 mg/2 mL (0.5 mg/mL)",
+ "diluent": "0.9% sodium chloride",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "0.5 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:100",
+ "concentration": "0.005 mg/mL",
+ "preparation": "0.1 mL of 0.05 mg/mL + 0.9 mL NS"
+ },
+ {
+ "dilution": "1:10",
+ "concentration": "0.05 mg/mL",
+ "preparation": "0.1 mL neat + 0.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "azithromycin",
+ "title": "Azithromycin",
+ "version": "1.1",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Others"
+ },
+ "protocols": [
+ {
+ "id": "iv",
+ "label": "IV",
+ "test_type": "skin",
+ "presentation": "500 mg powder for injection",
+ "diluent": "0.9% sodium chloride (reconstitute with 4.8 mL WFI)",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "100 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:10,000",
+ "concentration": "0.01 mg/mL",
+ "preparation": "0.1 mL of 1 mg/mL + 0.9 mL NS"
+ },
+ {
+ "dilution": "1:1,000",
+ "concentration": "0.1 mg/mL",
+ "preparation": "0.1 mL of 10 mg/mL + 0.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "betamethasone",
+ "title": "Betamethasone",
+ "version": "1.1",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Others"
+ },
+ "protocols": [
+ {
+ "id": "iv",
+ "label": "IV",
+ "test_type": "experimental",
+ "presentation": "Betamethasone sodium phosphate 5.7 mg/mL",
+ "diluent": "0.9% sodium chloride",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "5.7 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:100",
+ "concentration": "0.057 mg/mL",
+ "preparation": "0.1 mL of 0.57 mg/mL + 0.9 mL NS"
+ },
+ {
+ "dilution": "1:15",
+ "concentration": "0.38 mg/mL",
+ "preparation": "0.2 mL of 5.7 mg/mL + 2.8 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "bupivacaine",
+ "title": "Bupivacaine",
+ "version": "1.2",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Local Anaesthetics"
+ },
+ "protocols": [
+ {
+ "id": "epidural",
+ "label": "Epidural",
+ "test_type": "skin",
+ "presentation": "50 mg/20 mL (2.5 mg/mL) or 0.25% solution",
+ "diluent": "0.9% sodium chloride",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "2.5 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:1,000",
+ "concentration": "0.0025 mg/mL",
+ "preparation": "0.1 mL of 0.025 mg/mL + 0.9 mL NS"
+ },
+ {
+ "dilution": "1:100",
+ "concentration": "0.025 mg/mL",
+ "preparation": "0.1 mL of 0.25 mg/mL + 0.9 mL NS"
+ },
+ {
+ "dilution": "1:10",
+ "concentration": "0.25 mg/mL",
+ "preparation": "0.1 mL neat + 0.9 mL NS"
+ },
+ {
+ "dilution": "Neat",
+ "concentration": "2.5 mg/mL",
+ "preparation": "Undiluted stock"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
{
"slug": "cefazolin",
"title": "Cefazolin",
@@ -61,31 +262,36 @@
]
},
{
- "slug": "cis-atracurium",
- "title": "Cis-atracurium",
- "version": "1.4",
+ "slug": "cefepime",
+ "title": "Cefepime",
+ "version": "1.2",
"last_reviewed": "2026-03-28",
"dream": {
- "category": "Muscle Relaxants"
+ "category": "Cephalosporins"
},
"protocols": [
{
"id": "iv",
"label": "IV",
"test_type": "skin",
- "presentation": "5 mg/2.5 mL (2 mg/mL)",
- "diluent": "0.9% sodium chloride",
+ "presentation": "1 g powder for injection",
+ "diluent": "0.9% sodium chloride (reconstitute with 10 mL WFI)",
"spt": {
"dilution": "Neat",
- "concentration": "2 mg/mL",
+ "concentration": "100 mg/mL",
"positive_control": "Histamine 10 mg/mL",
"negative_control": "Normal saline"
},
"idt": [
{
- "dilution": "1:1,000",
- "concentration": "0.002 mg/mL",
- "preparation": "0.1 mL of 0.02 mg/mL + 0.9 mL NS"
+ "dilution": "1:100",
+ "concentration": "1 mg/mL",
+ "preparation": "0.1 mL of 10 mg/mL + 0.9 mL NS"
+ },
+ {
+ "dilution": "1:10",
+ "concentration": "10 mg/mL",
+ "preparation": "0.1 mL neat + 0.9 mL NS"
}
],
"under_review": false,
@@ -95,36 +301,36 @@
]
},
{
- "slug": "pancuronium",
- "title": "Pancuronium",
- "version": "1.4",
+ "slug": "cefotaxime",
+ "title": "Cefotaxime",
+ "version": "1.2",
"last_reviewed": "2026-03-28",
"dream": {
- "category": "Muscle Relaxants"
+ "category": "Cephalosporins"
},
"protocols": [
{
"id": "iv",
"label": "IV",
"test_type": "skin",
- "presentation": "4 mg/2 mL (2 mg/mL)",
- "diluent": "0.9% sodium chloride",
+ "presentation": "1 g powder for injection",
+ "diluent": "0.9% sodium chloride (reconstitute with 10 mL WFI)",
"spt": {
"dilution": "Neat",
- "concentration": "2 mg/mL",
+ "concentration": "100 mg/mL",
"positive_control": "Histamine 10 mg/mL",
"negative_control": "Normal saline"
},
"idt": [
{
- "dilution": "1:1,000",
- "concentration": "0.002 mg/mL",
- "preparation": "0.1 mL of 0.02 mg/mL + 0.9 mL NS"
+ "dilution": "1:100",
+ "concentration": "1 mg/mL",
+ "preparation": "0.1 mL of 10 mg/mL + 0.9 mL NS"
},
{
- "dilution": "1:100",
- "concentration": "0.02 mg/mL",
- "preparation": "0.1 mL of 0.2 mg/mL + 0.9 mL NS"
+ "dilution": "1:10",
+ "concentration": "10 mg/mL",
+ "preparation": "0.1 mL neat + 0.9 mL NS"
}
],
"under_review": false,
@@ -134,36 +340,36 @@
]
},
{
- "slug": "rocuronium",
- "title": "Rocuronium",
- "version": "1.4",
+ "slug": "ceftazidime",
+ "title": "Ceftazidime",
+ "version": "1.2",
"last_reviewed": "2026-03-28",
"dream": {
- "category": "Muscle Relaxants"
+ "category": "Cephalosporins"
},
"protocols": [
{
"id": "iv",
"label": "IV",
"test_type": "skin",
- "presentation": "50 mg/5 mL (10 mg/mL)",
- "diluent": "0.9% sodium chloride",
+ "presentation": "2 g powder for injection",
+ "diluent": "0.9% sodium chloride (reconstitute with 20 mL WFI)",
"spt": {
"dilution": "Neat",
- "concentration": "10 mg/mL",
+ "concentration": "100 mg/mL",
"positive_control": "Histamine 10 mg/mL",
"negative_control": "Normal saline"
},
"idt": [
{
- "dilution": "1:1,000",
- "concentration": "0.01 mg/mL",
- "preparation": "0.1 mL of 0.1 mg/mL + 0.9 mL NS"
+ "dilution": "1:100",
+ "concentration": "1 mg/mL",
+ "preparation": "0.1 mL of 10 mg/mL + 0.9 mL NS"
},
{
- "dilution": "1:100",
- "concentration": "0.1 mg/mL",
- "preparation": "0.1 mL of 1 mg/mL + 0.9 mL NS"
+ "dilution": "1:10",
+ "concentration": "10 mg/mL",
+ "preparation": "0.1 mL neat + 0.9 mL NS"
}
],
"under_review": false,
@@ -173,41 +379,36 @@
]
},
{
- "slug": "suxamethonium",
- "title": "Suxamethonium",
- "version": "1.4",
+ "slug": "ceftriaxone",
+ "title": "Ceftriaxone",
+ "version": "1.2",
"last_reviewed": "2026-03-28",
"dream": {
- "category": "Muscle Relaxants"
+ "category": "Cephalosporins"
},
"protocols": [
{
"id": "iv",
"label": "IV",
"test_type": "skin",
- "presentation": "100 mg/2 mL (50 mg/mL)",
- "diluent": "0.9% sodium chloride",
+ "presentation": "1 g powder for injection",
+ "diluent": "0.9% sodium chloride (reconstitute with 10 mL WFI)",
"spt": {
- "dilution": "1:5",
- "concentration": "10 mg/mL",
+ "dilution": "Neat",
+ "concentration": "100 mg/mL",
"positive_control": "Histamine 10 mg/mL",
"negative_control": "Normal saline"
},
"idt": [
{
- "dilution": "1:50,000",
- "concentration": "0.001 mg/mL",
- "preparation": "0.1 mL of 0.01 mg/mL + 0.9 mL NS"
- },
- {
- "dilution": "1:5,000",
- "concentration": "0.01 mg/mL",
- "preparation": "0.1 mL of 0.1 mg/mL + 0.9 mL NS"
+ "dilution": "1:100",
+ "concentration": "1 mg/mL",
+ "preparation": "0.1 mL of 10 mg/mL + 0.9 mL NS"
},
{
- "dilution": "1:500",
- "concentration": "0.1 mg/mL",
- "preparation": "0.1 mL of 1 mg/mL + 0.9 mL NS"
+ "dilution": "1:10",
+ "concentration": "10 mg/mL",
+ "preparation": "0.1 mL neat + 0.9 mL NS"
}
],
"under_review": false,
@@ -217,30 +418,1951 @@
]
},
{
- "slug": "vecuronium",
- "title": "Vecuronium",
- "version": "1.4",
+ "slug": "cefuroxime",
+ "title": "Cefuroxime",
+ "version": "1.3",
"last_reviewed": "2026-03-28",
"dream": {
- "category": "Muscle Relaxants"
+ "category": "Cephalosporins"
},
"protocols": [
{
"id": "iv",
"label": "IV",
"test_type": "skin",
- "presentation": "10 mg powder for injection",
- "diluent": "0.9% sodium chloride (reconstitute with 2.5 mL WFI)",
+ "presentation": "750 mg powder for injection or 125 mg/5 mL suspension",
+ "diluent": "0.9% sodium chloride (750 mg + 30 mL NS) or use 125 mg/5 mL (25 mg/mL) suspension",
"spt": {
"dilution": "Neat",
- "concentration": "4 mg/mL",
+ "concentration": "25 mg/mL",
"positive_control": "Histamine 10 mg/mL",
"negative_control": "Normal saline"
},
"idt": [
{
- "dilution": "1:1,000",
- "concentration": "0.004 mg/mL",
+ "dilution": "1:100",
+ "concentration": "0.25 mg/mL",
+ "preparation": "0.1 mL of 2.5 mg/mL + 0.9 mL NS"
+ },
+ {
+ "dilution": "1:10",
+ "concentration": "2.5 mg/mL",
+ "preparation": "0.1 mL of 25 mg/mL + 0.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "chlorhexidine",
+ "title": "Chlorhexidine",
+ "version": "1.2",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Antiseptics"
+ },
+ "protocols": [
+ {
+ "id": "0-02",
+ "label": "0.02%",
+ "test_type": "skin",
+ "presentation": "0.02% solution (0.2 mg/mL) or 0.1% solution (1 mg/mL)",
+ "diluent": "0.9% sodium chloride",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "0.2 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:100",
+ "concentration": "0.002 mg/mL",
+ "preparation": "0.1 mL of 0.02 mg/mL + 0.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "cis-atracurium",
+ "title": "Cis-atracurium",
+ "version": "1.4",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Muscle Relaxants"
+ },
+ "protocols": [
+ {
+ "id": "iv",
+ "label": "IV",
+ "test_type": "skin",
+ "presentation": "5 mg/2.5 mL (2 mg/mL)",
+ "diluent": "0.9% sodium chloride",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "2 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:1,000",
+ "concentration": "0.002 mg/mL",
+ "preparation": "0.1 mL of 0.02 mg/mL + 0.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "clindamycin",
+ "title": "Clindamycin",
+ "version": "1.2",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Others"
+ },
+ "protocols": [
+ {
+ "id": "iv",
+ "label": "IV",
+ "test_type": "skin",
+ "presentation": "600 mg/4 mL (150 mg/mL) or 300 mg/2 mL (150 mg/mL)",
+ "diluent": "0.9% sodium chloride",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "150 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:10",
+ "concentration": "15 mg/mL",
+ "preparation": "0.1 mL neat + 0.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "dalteparin",
+ "title": "Dalteparin",
+ "version": "1.2",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Others"
+ },
+ "protocols": [
+ {
+ "id": "sc",
+ "label": "SC",
+ "test_type": "skin",
+ "presentation": "10,000 U/mL pre-filled syringe or vial",
+ "diluent": "0.9% sodium chloride",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "10,000 U/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:1,000",
+ "concentration": "10 U/mL",
+ "preparation": "0.1 mL of 100 U/mL + 0.9 mL NS"
+ },
+ {
+ "dilution": "1:100",
+ "concentration": "100 U/mL",
+ "preparation": "0.1 mL of 1,000 U/mL + 0.9 mL NS"
+ },
+ {
+ "dilution": "1:10",
+ "concentration": "1,000 U/mL",
+ "preparation": "0.1 mL neat + 0.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "dexamethasone",
+ "title": "Dexamethasone",
+ "version": "1.2",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Others"
+ },
+ "protocols": [
+ {
+ "id": "iv",
+ "label": "IV",
+ "test_type": "skin",
+ "presentation": "4 mg/mL or 8 mg/2 mL (4 mg/mL)",
+ "diluent": "0.9% sodium chloride",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "4 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:100",
+ "concentration": "0.04 mg/mL",
+ "preparation": "0.1 mL of 0.4 mg/mL + 0.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "droperidol",
+ "title": "Droperidol",
+ "version": "1.1",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Others"
+ },
+ "protocols": [
+ {
+ "id": "iv",
+ "label": "IV",
+ "test_type": "skin",
+ "presentation": "10 mg/2 mL",
+ "diluent": "0.9% sodium chloride",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "5 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:1,000",
+ "concentration": "0.005 mg/mL",
+ "preparation": "0.1 mL of 0.5 mg/mL + 0.9 mL NS"
+ },
+ {
+ "dilution": "1:100",
+ "concentration": "0.05 mg/mL",
+ "preparation": "0.1 mL of 5 mg/mL + 0.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "enoxaparin",
+ "title": "Enoxaparin",
+ "version": "1.2",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Others"
+ },
+ "protocols": [
+ {
+ "id": "sc",
+ "label": "SC",
+ "test_type": "skin",
+ "presentation": "100 mg/mL pre-filled syringe or vial",
+ "diluent": "0.9% sodium chloride",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "100 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:1,000",
+ "concentration": "0.1 mg/mL",
+ "preparation": "0.1 mL of 1 mg/mL + 0.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "esomeprazole",
+ "title": "Esomeprazole",
+ "version": "1.2",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Proton Pump Inhibitors"
+ },
+ "protocols": [
+ {
+ "id": "standard",
+ "label": "",
+ "test_type": "skin",
+ "presentation": "20 mg tablets / capsules",
+ "diluent": "0.9% sodium chloride (dissolve in 1 mL)",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "20 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "fentanyl",
+ "title": "Fentanyl",
+ "version": "1.2",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Opioids"
+ },
+ "protocols": [
+ {
+ "id": "iv",
+ "label": "IV",
+ "test_type": "skin",
+ "presentation": "100 mcg/2 mL (0.05 mg/mL)",
+ "diluent": "0.9% sodium chloride",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "0.05 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:100",
+ "concentration": "0.0005 mg/mL",
+ "preparation": "0.1 mL of 0.005 mg/mL + 0.9 mL NS"
+ },
+ {
+ "dilution": "1:10",
+ "concentration": "0.005 mg/mL",
+ "preparation": "0.1 mL neat + 0.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "flucloxacillin",
+ "title": "Flucloxacillin",
+ "version": "1.2",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Penicillins"
+ },
+ "protocols": [
+ {
+ "id": "iv",
+ "label": "IV",
+ "test_type": "skin",
+ "presentation": "500 mg powder for injection",
+ "diluent": "0.9% sodium chloride (Initial Reconstitution: Add 4.6 mL NS to 500 mg vial to obtain 100 mg/mL; Intermediate Dilution: Draw 1.0 mL of 100 mg/mL solution, add 4.0 mL NS to result in 20 mg/mL)",
+ "spt": {
+ "concentration": "0.2 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:100",
+ "concentration": "0.2 mg/mL",
+ "preparation": "0.1 mL of 2 mg/mL + 0.9 mL NS"
+ },
+ {
+ "dilution": "1:10",
+ "concentration": "2 mg/mL",
+ "preparation": "0.1 mL neat (20mg/ml) + 0.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "fluconazole",
+ "title": "Fluconazole",
+ "version": "1.2",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Others"
+ },
+ "protocols": [
+ {
+ "id": "iv",
+ "label": "IV",
+ "test_type": "skin",
+ "presentation": "200 mg/100 mL IV (2 mg/mL)",
+ "diluent": "0.9% sodium chloride",
+ "spt": {
+ "dilution": "1:10",
+ "concentration": "0.2 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:1,000",
+ "concentration": "0.002 mg/mL",
+ "preparation": "0.1 mL of 0.02 mg/mL + 0.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "glycopyrronium",
+ "title": "Glycopyrronium",
+ "version": "1.1",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Others"
+ },
+ "protocols": [
+ {
+ "id": "standard",
+ "label": "",
+ "test_type": "experimental",
+ "presentation": "200 µg/1 mL (0.2 mg/mL)",
+ "diluent": "0.9% sodium chloride",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "0.2 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:1,000",
+ "concentration": "0.0002 mg/mL",
+ "preparation": "0.1 mL of 0.02 mg/mL + 0.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "granisetron",
+ "title": "Granisetron",
+ "version": "1.1",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Others"
+ },
+ "protocols": [
+ {
+ "id": "iv",
+ "label": "IV",
+ "test_type": "skin",
+ "presentation": "3 mg/3 mL",
+ "diluent": "0.9% sodium chloride",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "1 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:100",
+ "concentration": "0.01 mg/mL",
+ "preparation": "0.1 mL of 0.1 mg/mL + 0.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "heparin",
+ "title": "Heparin",
+ "version": "1.2",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Others"
+ },
+ "protocols": [
+ {
+ "id": "sc",
+ "label": "SC",
+ "test_type": "skin",
+ "presentation": "5000 U/mL (Common) or 25,000 U/5 mL (5000 U/mL)",
+ "diluent": "0.9% sodium chloride",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "5000 U/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:1,000",
+ "concentration": "5 U/mL",
+ "preparation": "0.1 mL of 50 U/mL + 0.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "humulin-nph",
+ "title": "Humulin NPH",
+ "version": "1.1",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Others",
+ "drug_name": "Humulin NPH (Insulin)"
+ },
+ "protocols": [
+ {
+ "id": "sc",
+ "label": "S/C",
+ "test_type": "skin",
+ "presentation": "100 units/mL (10 mL vial)",
+ "diluent": "19 mL N/S 0.9% for SPT dilution",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "100 U/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:20",
+ "concentration": "5 U/mL",
+ "preparation": "0.1 mL stock (100 U/mL) + 1.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "humulin-r",
+ "title": "Humulin R",
+ "version": "1.1",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Others",
+ "drug_name": "Humulin R (Insulin)"
+ },
+ "protocols": [
+ {
+ "id": "sc",
+ "label": "S/C",
+ "test_type": "skin",
+ "presentation": "100 units/mL (10 mL vial)",
+ "diluent": "19 mL N/S 0.9% for SPT dilution",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "100 U/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:20",
+ "concentration": "5 U/mL",
+ "preparation": "0.1 mL stock (100 U/mL) + 1.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "hydrocortisone",
+ "title": "Hydrocortisone",
+ "version": "1.2",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Others"
+ },
+ "protocols": [
+ {
+ "id": "iv",
+ "label": "IV",
+ "test_type": "experimental",
+ "presentation": "100 mg powder for injection",
+ "diluent": "0.9% sodium chloride (reconstitute with 2 mL WFI/NS)",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "50 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:100",
+ "concentration": "0.5 mg/mL",
+ "preparation": "0.1 mL of 5 mg/mL + 0.9 mL NS"
+ },
+ {
+ "dilution": "1:10",
+ "concentration": "5 mg/mL",
+ "preparation": "0.1 mL neat + 0.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "lansoprazole",
+ "title": "Lansoprazole",
+ "version": "1.2",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Proton Pump Inhibitors"
+ },
+ "protocols": [
+ {
+ "id": "standard",
+ "label": "",
+ "test_type": "skin",
+ "presentation": "30 mg capsules / orodispersible tablets",
+ "diluent": "0.9% sodium chloride (dissolve in 1 mL)",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "30 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "latex",
+ "title": "Latex",
+ "version": "1.2",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Others"
+ },
+ "protocols": [
+ {
+ "id": "standard",
+ "label": "",
+ "test_type": "skin",
+ "presentation": "Natural rubber latex extract",
+ "spt": {
+ "dilution": "Neat extract",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "levofloxacin",
+ "title": "Levofloxacin",
+ "version": "1.2",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Others"
+ },
+ "protocols": [
+ {
+ "id": "tablet",
+ "label": "Tablet",
+ "test_type": "skin",
+ "presentation": "500 mg tablets or IV formulation",
+ "diluent": "Normal saline for IDT dilutions (consult the Manufacturing Pharmacist for the exact diluent and method if using tablets)",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "5 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:100",
+ "concentration": "0.05 mg/mL",
+ "preparation": "0.1 mL of 0.5 mg/mL + 0.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": true
+ }
+ ]
+ },
+ {
+ "slug": "levonorgestrel",
+ "title": "Levonorgestrel",
+ "version": "1.1",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Others"
+ },
+ "protocols": [
+ {
+ "id": "oral",
+ "label": "Oral",
+ "test_type": "skin",
+ "presentation": "Levonorgestrel 750 µg tablet",
+ "diluent": "0.9% sodium chloride",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "crushed tablet solution",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": true
+ }
+ ]
+ },
+ {
+ "slug": "medroxyprogesterone",
+ "title": "Medroxyprogesterone",
+ "version": "1.1",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Others"
+ },
+ "protocols": [
+ {
+ "id": "inj",
+ "label": "Inj",
+ "test_type": "skin",
+ "presentation": "150 mg/1 mL",
+ "diluent": "0.9% sodium chloride",
+ "spt": {
+ "dilution": "1:3",
+ "concentration": "50 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:100",
+ "concentration": "0.05 mg/mL",
+ "preparation": "0.1 mL of 1.5 mg/mL + 0.9 mL NS"
+ },
+ {
+ "dilution": "1:10",
+ "concentration": "5 mg/mL",
+ "preparation": "0.1 mL of 15 mg/mL + 0.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "mepivacaine",
+ "title": "Mepivacaine",
+ "version": "1.2",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Local Anaesthetics"
+ },
+ "protocols": [
+ {
+ "id": "epidural",
+ "label": "Epidural",
+ "test_type": "skin",
+ "presentation": "66 mg/2.2 mL (30 mg/mL) or 3% solution",
+ "diluent": "0.9% sodium chloride (reconstitute with 4.4 mL WFI to 2.2 mL stock)",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "10 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:1,000",
+ "concentration": "0.01 mg/mL",
+ "preparation": "0.1 mL of 0.1 mg/mL + 0.9 mL NS"
+ },
+ {
+ "dilution": "1:100",
+ "concentration": "0.1 mg/mL",
+ "preparation": "0.1 mL of 1 mg/mL + 0.9 mL NS"
+ },
+ {
+ "dilution": "1:10",
+ "concentration": "1 mg/mL",
+ "preparation": "0.1 mL neat + 0.9 mL NS"
+ },
+ {
+ "dilution": "Neat",
+ "concentration": "10 mg/mL",
+ "preparation": "10 mg/mL solution prepared above"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "methylprednisolone",
+ "title": "Methylprednisolone",
+ "version": "1.2",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Others"
+ },
+ "protocols": [
+ {
+ "id": "iv",
+ "label": "IV",
+ "test_type": "experimental",
+ "presentation": "1 g powder for injection",
+ "diluent": "0.9% sodium chloride (reconstitute with 5 mL NS)",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "20 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:100",
+ "concentration": "0.2 mg/mL",
+ "preparation": "0.1 mL of 2 mg/mL + 0.9 mL NS"
+ },
+ {
+ "dilution": "1:10",
+ "concentration": "2 mg/mL",
+ "preparation": "0.1 mL neat + 0.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "metoclopramide",
+ "title": "Metoclopramide",
+ "version": "1.1",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Others"
+ },
+ "protocols": [
+ {
+ "id": "iv",
+ "label": "IV",
+ "test_type": "skin",
+ "presentation": "10 mg/2 mL",
+ "diluent": "0.9% sodium chloride",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "5 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:1,000",
+ "concentration": "0.005 mg/mL",
+ "preparation": "0.1 mL of 0.5 mg/mL + 0.9 mL NS"
+ },
+ {
+ "dilution": "1:100",
+ "concentration": "0.05 mg/mL",
+ "preparation": "0.1 mL of 5 mg/mL + 0.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "metronidazole",
+ "title": "Metronidazole",
+ "version": "1.2",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Others"
+ },
+ "protocols": [
+ {
+ "id": "iv",
+ "label": "IV",
+ "test_type": "skin",
+ "presentation": "500 mg/100 mL IV (5 mg/mL) or 200 mg tablets",
+ "diluent": "0.9% sodium chloride",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "5 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:100",
+ "concentration": "0.05 mg/mL",
+ "preparation": "0.1 mL of 0.5 mg/mL + 0.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "midazolam",
+ "title": "Midazolam",
+ "version": "1.2",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Hypnotics"
+ },
+ "protocols": [
+ {
+ "id": "iv",
+ "label": "IV",
+ "test_type": "skin",
+ "presentation": "1 mg/mL or 5 mg/5 mL (1 mg/mL)",
+ "diluent": "0.9% sodium chloride",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "1 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:100",
+ "concentration": "0.01 mg/mL",
+ "preparation": "0.1 mL of 0.1 mg/mL + 0.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "neostigmine",
+ "title": "Neostigmine",
+ "version": "1.1",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Others"
+ },
+ "protocols": [
+ {
+ "id": "inj",
+ "label": "Inj",
+ "test_type": "experimental",
+ "presentation": "2.5 mg/1 mL",
+ "diluent": "0.9% sodium chloride",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "2.5 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:1,000",
+ "concentration": "0.0025 mg/mL",
+ "preparation": "0.1 mL of 0.25 mg/mL + 0.9 mL NS"
+ },
+ {
+ "dilution": "1:100",
+ "concentration": "0.025 mg/mL",
+ "preparation": "0.1 mL of 2.5 mg/mL + 0.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "novorapid",
+ "title": "Novorapid",
+ "version": "1.1",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Others",
+ "drug_name": "Novorapid (Insulin)"
+ },
+ "protocols": [
+ {
+ "id": "sc",
+ "label": "S/C",
+ "test_type": "skin",
+ "presentation": "100 units/mL (10 mL vial / penfill)",
+ "diluent": "19 mL N/S 0.9% for SPT dilution",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "100 U/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:20",
+ "concentration": "5 U/mL",
+ "preparation": "0.1 mL stock (100 U/mL) + 1.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "omeprazole",
+ "title": "Omeprazole",
+ "version": "1.2",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Proton Pump Inhibitors"
+ },
+ "protocols": [
+ {
+ "id": "standard",
+ "label": "",
+ "test_type": "skin",
+ "presentation": "20 mg tablets / capsules",
+ "diluent": "0.9% sodium chloride (dissolve in 1 mL)",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "20 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "omnipaque",
+ "title": "Omnipaque",
+ "version": "1.2",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Others"
+ },
+ "protocols": [
+ {
+ "id": "iv-contrast",
+ "label": "IV Contrast",
+ "test_type": "skin",
+ "presentation": "350 mg I/mL (Common) or 300 mg I/mL",
+ "diluent": "0.9% sodium chloride",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "350 mg I/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:100",
+ "concentration": "3.5 mg I/mL",
+ "preparation": "0.1 mL of 1:10 + 0.9 mL NS"
+ },
+ {
+ "dilution": "1:10",
+ "concentration": "35 mg I/mL",
+ "preparation": "0.1 mL neat + 0.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "ondansetron",
+ "title": "Ondansetron",
+ "version": "1.1",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Others"
+ },
+ "protocols": [
+ {
+ "id": "iv",
+ "label": "IV",
+ "test_type": "skin",
+ "presentation": "4 mg/2 mL",
+ "diluent": "0.9% sodium chloride",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "2 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:1,000",
+ "concentration": "0.002 mg/mL",
+ "preparation": "0.1 mL of 0.2 mg/mL + 0.9 mL NS"
+ },
+ {
+ "dilution": "1:100",
+ "concentration": "0.02 mg/mL",
+ "preparation": "0.1 mL of 2 mg/mL + 0.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "optisulin",
+ "title": "Optisulin",
+ "version": "1.1",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Others",
+ "drug_name": "Optisulin (Insulin)"
+ },
+ "protocols": [
+ {
+ "id": "sc",
+ "label": "S/C",
+ "test_type": "skin",
+ "presentation": "100 units/mL (10 mL vial)",
+ "diluent": "19 mL N/S 0.9% for SPT dilution",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "100 U/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:20",
+ "concentration": "5 U/mL",
+ "preparation": "0.1 mL stock (100 U/mL) + 1.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "oxycodone",
+ "title": "Oxycodone",
+ "version": "1.2",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Opioids"
+ },
+ "protocols": [
+ {
+ "id": "iv",
+ "label": "IV",
+ "test_type": "skin",
+ "presentation": "10 mg/mL",
+ "diluent": "0.9% sodium chloride",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "10 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:1,000",
+ "concentration": "0.01 mg/mL",
+ "preparation": "0.1 mL of 0.1 mg/mL + 0.9 mL NS"
+ },
+ {
+ "dilution": "1:100",
+ "concentration": "0.1 mg/mL",
+ "preparation": "0.1 mL neat + 0.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "pancuronium",
+ "title": "Pancuronium",
+ "version": "1.4",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Muscle Relaxants"
+ },
+ "protocols": [
+ {
+ "id": "iv",
+ "label": "IV",
+ "test_type": "skin",
+ "presentation": "4 mg/2 mL (2 mg/mL)",
+ "diluent": "0.9% sodium chloride",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "2 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:1,000",
+ "concentration": "0.002 mg/mL",
+ "preparation": "0.1 mL of 0.02 mg/mL + 0.9 mL NS"
+ },
+ {
+ "dilution": "1:100",
+ "concentration": "0.02 mg/mL",
+ "preparation": "0.1 mL of 0.2 mg/mL + 0.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "pantoprazole",
+ "title": "Pantoprazole",
+ "version": "1.3",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Proton Pump Inhibitors"
+ },
+ "protocols": [
+ {
+ "id": "iv",
+ "label": "IV",
+ "test_type": "skin",
+ "presentation": "40 mg powder for injection",
+ "diluent": "0.9% sodium chloride (reconstitute with 10 mL NS)",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "4 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:1,000",
+ "concentration": "0.004 mg/mL",
+ "preparation": "0.1 mL of 0.04 mg/mL + 0.9 mL NS"
+ },
+ {
+ "dilution": "1:100",
+ "concentration": "0.04 mg/mL",
+ "preparation": "0.1 mL of 0.4 mg/mL + 0.9 mL NS"
+ },
+ {
+ "dilution": "1:10",
+ "concentration": "0.4 mg/mL",
+ "preparation": "0.1 mL neat + 0.9 mL NS"
+ }
+ ],
+ "under_review": true,
+ "review_note": "The Spreadsheet 2 spreadsheet labels the SPT concentration as \"Neat (40 mg/mL)\". This is a spreadsheet labelling error — the correct reconstituted concentration is 4 mg/mL (40 mg powder + 10 mL NS).",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "paracetamol",
+ "title": "Paracetamol",
+ "version": "1.2",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Others"
+ },
+ "protocols": [
+ {
+ "id": "iv",
+ "label": "IV",
+ "test_type": "skin",
+ "presentation": "1 g/100 mL IV (10 mg/mL) or 500 mg tablets",
+ "diluent": "0.9% sodium chloride",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "10 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:10",
+ "concentration": "1 mg/mL",
+ "preparation": "0.1 mL of stock (10 mg/mL) + 0.9 mL NS"
+ },
+ {
+ "dilution": "1:100",
+ "concentration": "0.1 mg/mL",
+ "preparation": "0.1 mL of 1 mg/mL + 0.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "parecoxib",
+ "title": "Parecoxib",
+ "version": "1.2",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Others"
+ },
+ "protocols": [
+ {
+ "id": "iv",
+ "label": "IV",
+ "test_type": "skin",
+ "presentation": "40 mg powder for injection",
+ "diluent": "0.9% sodium chloride (reconstitute with 5 mL NS)",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "8 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:100",
+ "concentration": "0.08 mg/mL",
+ "preparation": "0.1 mL of 0.8 mg/mL + 0.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "patent-blue",
+ "title": "Patent Blue",
+ "version": "1.2",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Others"
+ },
+ "protocols": [
+ {
+ "id": "sc",
+ "label": "SC",
+ "test_type": "skin",
+ "presentation": "2.5% (25 mg/mL) or 10% (100 mg/mL)",
+ "diluent": "0.9% sodium chloride",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "25 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:1,000",
+ "concentration": "0.025 mg/mL",
+ "preparation": "0.1 mL of 0.25 mg/mL + 0.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "propofol",
+ "title": "Propofol",
+ "version": "1.2",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Hypnotics"
+ },
+ "protocols": [
+ {
+ "id": "iv",
+ "label": "IV",
+ "test_type": "skin",
+ "presentation": "200 mg/20 mL (10 mg/mL) or 500 mg/50 mL (10 mg/mL)",
+ "diluent": "0.9% sodium chloride",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "10 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:1,000",
+ "concentration": "0.01 mg/mL",
+ "preparation": "0.1 mL of 0.1 mg/mL + 0.9 mL NS"
+ },
+ {
+ "dilution": "1:100",
+ "concentration": "0.1 mg/mL",
+ "preparation": "0.1 mL of 1 mg/mL + 0.9 mL NS"
+ },
+ {
+ "dilution": "1:10",
+ "concentration": "1 mg/mL",
+ "preparation": "0.1 mL neat + 0.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "protamine",
+ "title": "Protamine",
+ "version": "1.1",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Others"
+ },
+ "protocols": [
+ {
+ "id": "iv",
+ "label": "IV",
+ "test_type": "skin",
+ "presentation": "50 mg/5 mL (10 mg/mL)",
+ "diluent": "0.9% sodium chloride",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "10 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:1,000",
+ "concentration": "0.01 mg/mL",
+ "preparation": "0.1 mL of 1 mg/mL + 0.9 mL NS"
+ },
+ {
+ "dilution": "0.1:20",
+ "concentration": "0.05 mg/mL",
+ "preparation": "0.1 mL of 10 mg/mL + 1.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "protaphane",
+ "title": "Protaphane",
+ "version": "1.1",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Others",
+ "drug_name": "Protaphane (Insulin)"
+ },
+ "protocols": [
+ {
+ "id": "sc",
+ "label": "S/C",
+ "test_type": "skin",
+ "presentation": "100 units/mL (10 mL vial)",
+ "diluent": "19 mL N/S 0.9% for SPT dilution",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "100 U/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:20",
+ "concentration": "5 U/mL",
+ "preparation": "0.1 mL stock (100 U/mL) + 1.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "rabeprazole",
+ "title": "Rabeprazole",
+ "version": "1.2",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Proton Pump Inhibitors"
+ },
+ "protocols": [
+ {
+ "id": "standard",
+ "label": "",
+ "test_type": "skin",
+ "presentation": "20 mg tablets",
+ "diluent": "0.9% sodium chloride (dissolve in 1 mL)",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "40 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "rocuronium",
+ "title": "Rocuronium",
+ "version": "1.4",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Muscle Relaxants"
+ },
+ "protocols": [
+ {
+ "id": "iv",
+ "label": "IV",
+ "test_type": "skin",
+ "presentation": "50 mg/5 mL (10 mg/mL)",
+ "diluent": "0.9% sodium chloride",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "10 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:1,000",
+ "concentration": "0.01 mg/mL",
+ "preparation": "0.1 mL of 0.1 mg/mL + 0.9 mL NS"
+ },
+ {
+ "dilution": "1:100",
+ "concentration": "0.1 mg/mL",
+ "preparation": "0.1 mL of 1 mg/mL + 0.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "sugammadex",
+ "title": "Sugammadex",
+ "version": "1.3",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Reversal Agents",
+ "drug_name": "Sugammadex (Alone)"
+ },
+ "protocols": [
+ {
+ "id": "alone",
+ "label": "Alone",
+ "test_type": "skin",
+ "presentation": "200 mg/2 mL (100 mg/mL) or 500 mg/5 mL (100 mg/mL)",
+ "diluent": "0.9% sodium chloride",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "100 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:1,000",
+ "concentration": "0.1 mg/mL",
+ "preparation": "0.1 mL of 1 mg/mL + 0.9 mL NS"
+ },
+ {
+ "dilution": "1:100",
+ "concentration": "1 mg/mL",
+ "preparation": "0.1 mL of 10 mg/mL + 0.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "suxamethonium",
+ "title": "Suxamethonium",
+ "version": "1.4",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Muscle Relaxants"
+ },
+ "protocols": [
+ {
+ "id": "iv",
+ "label": "IV",
+ "test_type": "skin",
+ "presentation": "100 mg/2 mL (50 mg/mL)",
+ "diluent": "0.9% sodium chloride",
+ "spt": {
+ "dilution": "1:5",
+ "concentration": "10 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:50,000",
+ "concentration": "0.001 mg/mL",
+ "preparation": "0.1 mL of 0.01 mg/mL + 0.9 mL NS"
+ },
+ {
+ "dilution": "1:5,000",
+ "concentration": "0.01 mg/mL",
+ "preparation": "0.1 mL of 0.1 mg/mL + 0.9 mL NS"
+ },
+ {
+ "dilution": "1:500",
+ "concentration": "0.1 mg/mL",
+ "preparation": "0.1 mL of 1 mg/mL + 0.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "tazocin",
+ "title": "Tazocin",
+ "version": "1.3",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Penicillins"
+ },
+ "protocols": [
+ {
+ "id": "iv",
+ "label": "IV",
+ "test_type": "skin",
+ "presentation": "4 g / 500 mg powder for injection",
+ "diluent": "0.9% sodium chloride (reconstitute with 20 mL NS)",
+ "spt": {
+ "dilution": "1:10 — ⚠️ concentration under review (Medication List: 2 mg/mL; calculation: 20 mg/mL)",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:100",
+ "concentration": "2/0.2 mg/mL",
+ "preparation": "0.1 mL of 20/2 mg/mL + 0.9 mL NS"
+ }
+ ],
+ "under_review": true,
+ "review_note": "Concentration discrepancy: Medication List specifies SPT at 1:10 (2 mg/mL Piperacillin), whereas calculation of 1:10 of 200 mg/mL gives 20 mg/mL. Concentration under clinical review.",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "tramadol",
+ "title": "Tramadol",
+ "version": "1.1",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Others"
+ },
+ "protocols": [
+ {
+ "id": "iv",
+ "label": "IV",
+ "test_type": "experimental",
+ "presentation": "100 mg/2 mL",
+ "diluent": "0.9% sodium chloride",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "50 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:100",
+ "concentration": "0.5 mg/mL",
+ "preparation": "0.1 mL of 5 mg/mL + 0.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "tranexamic-acid",
+ "title": "Tranexamic Acid",
+ "version": "1.2",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Others"
+ },
+ "protocols": [
+ {
+ "id": "iv",
+ "label": "IV",
+ "test_type": "skin",
+ "presentation": "500 mg/5 mL (100 mg/mL)",
+ "diluent": "0.9% sodium chloride",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "100 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:100",
+ "concentration": "1 mg/mL",
+ "preparation": "0.1 mL of 10 mg/mL + 0.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "triamcinolone",
+ "title": "Triamcinolone",
+ "version": "1.1",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Others"
+ },
+ "protocols": [
+ {
+ "id": "inj",
+ "label": "Inj",
+ "test_type": "experimental",
+ "presentation": "40 mg/mL",
+ "diluent": "0.9% sodium chloride",
+ "spt": {
+ "dilution": "1:10",
+ "concentration": "4 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:1,000",
+ "concentration": "0.04 mg/mL",
+ "preparation": "0.1 mL of 4 mg/mL + 0.9 mL NS"
+ },
+ {
+ "dilution": "1:100",
+ "concentration": "0.4 mg/mL",
+ "preparation": "0.1 mL of 40 mg/mL + 0.9 mL NS"
+ },
+ {
+ "dilution": "1:10",
+ "concentration": "4 mg/mL",
+ "preparation": "0.1 mL of 40 mg/mL + 0.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "urografin",
+ "title": "Urografin",
+ "version": "1.1",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Others"
+ },
+ "protocols": [
+ {
+ "id": "iv-contrast",
+ "label": "IV Contrast",
+ "test_type": "skin",
+ "presentation": "Urografin 150 (150 mg I/mL) or Urografin 370 (370 mg I/mL)",
+ "diluent": "0.9% sodium chloride",
+ "spt": {
+ "dilution": "Neat",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:100",
+ "concentration": "—",
+ "preparation": "0.1 mL of 1:10 + 0.9 mL NS"
+ },
+ {
+ "dilution": "1:10",
+ "concentration": "—",
+ "preparation": "0.1 mL neat + 0.9 mL NS"
+ },
+ {
+ "dilution": "Neat",
+ "concentration": "500 U/mL",
+ "preparation": "Undiluted stock"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "vancomycin",
+ "title": "Vancomycin",
+ "version": "1.2",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Others"
+ },
+ "protocols": [
+ {
+ "id": "iv",
+ "label": "IV",
+ "test_type": "skin",
+ "presentation": "500 mg or 1 g powder for injection",
+ "diluent": "0.9% sodium chloride (add 5 mL NS to 500 mg or 10 mL NS to 1 g)",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "100 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:1,000,000",
+ "concentration": "0.0001 mg/mL",
+ "preparation": "0.1 mL of 0.001 mg/mL + 0.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "vecuronium",
+ "title": "Vecuronium",
+ "version": "1.4",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Muscle Relaxants"
+ },
+ "protocols": [
+ {
+ "id": "iv",
+ "label": "IV",
+ "test_type": "skin",
+ "presentation": "10 mg powder for injection",
+ "diluent": "0.9% sodium chloride (reconstitute with 2.5 mL WFI)",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "4 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:1,000",
+ "concentration": "0.004 mg/mL",
"preparation": "0.1 mL of 0.04 mg/mL + 0.9 mL NS"
},
{
@@ -254,6 +2376,94 @@
"needs_pharmacy_verification": false
}
]
+ },
+ {
+ "slug": "visipaque",
+ "title": "Visipaque",
+ "version": "1.2",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Others"
+ },
+ "protocols": [
+ {
+ "id": "iv-contrast",
+ "label": "IV Contrast",
+ "test_type": "skin",
+ "presentation": "320 mg I/mL (Common) or 270 mg I/mL",
+ "diluent": "0.9% sodium chloride",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "320 mg I/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:100",
+ "concentration": "3.2 mg I/mL",
+ "preparation": "0.1 mL of 1:10 + 0.9 mL NS"
+ },
+ {
+ "dilution": "1:10",
+ "concentration": "32 mg I/mL",
+ "preparation": "0.1 mL neat + 0.9 mL NS"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
+ },
+ {
+ "slug": "xylocaine",
+ "title": "Xylocaine",
+ "version": "1.1",
+ "last_reviewed": "2026-03-28",
+ "dream": {
+ "category": "Others"
+ },
+ "protocols": [
+ {
+ "id": "iv",
+ "label": "IV",
+ "test_type": "skin",
+ "presentation": "50 mg/5 mL (10 mg/mL)",
+ "diluent": "0.9% sodium chloride",
+ "spt": {
+ "dilution": "Neat",
+ "concentration": "10 mg/mL",
+ "positive_control": "Histamine 10 mg/mL",
+ "negative_control": "Normal saline"
+ },
+ "idt": [
+ {
+ "dilution": "1:1,000",
+ "concentration": "0.01 mg/mL",
+ "preparation": "0.1 mL of 0.1 mg/mL + 0.9 mL NS"
+ },
+ {
+ "dilution": "1:100",
+ "concentration": "0.1 mg/mL",
+ "preparation": "0.1 mL of 1 mg/mL + 0.9 mL NS"
+ },
+ {
+ "dilution": "1:10",
+ "concentration": "1 mg/mL",
+ "preparation": "0.1 mL neat + 0.9 mL NS"
+ },
+ {
+ "dilution": "Neat",
+ "concentration": "10 mg/mL",
+ "preparation": "Undiluted stock"
+ }
+ ],
+ "under_review": false,
+ "review_note": "",
+ "needs_pharmacy_verification": false
+ }
+ ]
}
],
"cross_reactivity": {
diff --git a/src/shared/utils/index.ts b/src/shared/utils/index.ts
index 73838bd..25a77f4 100644
--- a/src/shared/utils/index.ts
+++ b/src/shared/utils/index.ts
@@ -12,4 +12,5 @@ export {
ACTIVE_REPORT_TTL_MS, ACTIVE_REPORT_KEY, TESTING_DRAFT_KEY, PATIENT_DB_KEY, PATIENT_DATA_KEYS,
setWithTTL, refreshTTL, getIfFresh, getSavedAt, removeStored, purgeStale,
} from './ttlStorage';
+export { resolveSelectedProtocol, type ProtocolResolution } from './protocolResolver';
export { CATEGORY_THEMES, DRUG_CATEGORIES, FLAT_DRUG_OPTIONS, DEFAULT_SELECTED_DRUGS, APP_CONFIG } from './constants';
diff --git a/src/shared/utils/protocolResolver.test.ts b/src/shared/utils/protocolResolver.test.ts
new file mode 100644
index 0000000..f6e7bce
--- /dev/null
+++ b/src/shared/utils/protocolResolver.test.ts
@@ -0,0 +1,116 @@
+import { describe, expect, it } from 'vitest';
+import { resolveSelectedProtocol } from './protocolResolver';
+import type { DrugProtocol } from '@features/testing/types';
+
+const mockProtocols: DrugProtocol[] = [
+ {
+ id: 'p1',
+ drugName: 'Ketamine',
+ category: 'Hypnotics',
+ testType: 'skin',
+ presentation: '100 mg/2 mL',
+ sptNeatConcentration: 'Neat (50 mg/mL)',
+ diluent: '0.9% sodium chloride',
+ idtSteps: [{ ratio: '1:1,000', concentration: '0.05 mg/mL' }],
+ challengeSteps: [],
+ protocolLabel: '1:1,000 start',
+ },
+ {
+ id: 'p2',
+ drugName: 'Ketamine',
+ category: 'Hypnotics',
+ testType: 'skin',
+ presentation: '100 mg/2 mL',
+ sptNeatConcentration: 'Neat (50 mg/mL)',
+ diluent: '0.9% sodium chloride',
+ idtSteps: [{ ratio: '1:100', concentration: '0.5 mg/mL' }],
+ challengeSteps: [],
+ protocolLabel: '1:100 start',
+ },
+];
+
+describe('resolveSelectedProtocol', () => {
+ it('resolves undefined index to the first protocol (index 0 / established default)', () => {
+ const result = resolveSelectedProtocol(mockProtocols, undefined);
+ expect(result.status).toBe('valid');
+ if (result.status === 'valid') {
+ expect(result.index).toBe(0);
+ expect(result.protocol).toBe(mockProtocols[0]);
+ expect(result.isDefault).toBe(true);
+ }
+ });
+
+ it('resolves explicit valid index 0 to the first protocol', () => {
+ const result = resolveSelectedProtocol(mockProtocols, 0);
+ expect(result.status).toBe('valid');
+ if (result.status === 'valid') {
+ expect(result.index).toBe(0);
+ expect(result.protocol).toBe(mockProtocols[0]);
+ expect(result.isDefault).toBe(true);
+ }
+ });
+
+ it('resolves explicit valid index 1 to the second protocol', () => {
+ const result = resolveSelectedProtocol(mockProtocols, 1);
+ expect(result.status).toBe('valid');
+ if (result.status === 'valid') {
+ expect(result.index).toBe(1);
+ expect(result.protocol).toBe(mockProtocols[1]);
+ expect(result.isDefault).toBe(false);
+ }
+ });
+
+ it('fails closed when selected index is out of bounds (>= length)', () => {
+ const result = resolveSelectedProtocol(mockProtocols, 2);
+ expect(result.status).toBe('invalid');
+ if (result.status === 'invalid') {
+ expect(result.protocol).toBeUndefined();
+ expect(result.reason).toContain('out of bounds');
+ }
+ });
+
+ it('fails closed on large out-of-bounds index without silently clamping', () => {
+ const result = resolveSelectedProtocol(mockProtocols, 99);
+ expect(result.status).toBe('invalid');
+ if (result.status === 'invalid') {
+ expect(result.protocol).toBeUndefined();
+ }
+ });
+
+ it('fails closed when selected index is negative', () => {
+ const result = resolveSelectedProtocol(mockProtocols, -1);
+ expect(result.status).toBe('invalid');
+ if (result.status === 'invalid') {
+ expect(result.protocol).toBeUndefined();
+ expect(result.reason).toContain('out of bounds');
+ }
+ });
+
+ it('fails closed on non-integer float index', () => {
+ const result = resolveSelectedProtocol(mockProtocols, 1.5);
+ expect(result.status).toBe('invalid');
+ if (result.status === 'invalid') {
+ expect(result.protocol).toBeUndefined();
+ expect(result.reason).toContain('must be an integer');
+ }
+ });
+
+ it('fails closed on string index', () => {
+ const result = resolveSelectedProtocol(mockProtocols, '0' as unknown as number);
+ expect(result.status).toBe('invalid');
+ });
+
+ it('fails closed on null, NaN, Infinity, and object values', () => {
+ expect(resolveSelectedProtocol(mockProtocols, null).status).toBe('invalid');
+ expect(resolveSelectedProtocol(mockProtocols, NaN).status).toBe('invalid');
+ expect(resolveSelectedProtocol(mockProtocols, Infinity).status).toBe('invalid');
+ expect(resolveSelectedProtocol(mockProtocols, {}).status).toBe('invalid');
+ expect(resolveSelectedProtocol(mockProtocols, []).status).toBe('invalid');
+ });
+
+ it('returns empty status on empty or null protocol array', () => {
+ expect(resolveSelectedProtocol([], 0).status).toBe('empty');
+ expect(resolveSelectedProtocol(null, 0).status).toBe('empty');
+ expect(resolveSelectedProtocol(undefined, 0).status).toBe('empty');
+ });
+});
diff --git a/src/shared/utils/protocolResolver.ts b/src/shared/utils/protocolResolver.ts
new file mode 100644
index 0000000..1073c19
--- /dev/null
+++ b/src/shared/utils/protocolResolver.ts
@@ -0,0 +1,81 @@
+import type { DrugProtocol } from '@features/testing/types';
+
+export type ProtocolResolution =
+ | {
+ status: 'valid';
+ protocol: DrugProtocol;
+ index: number;
+ isDefault: boolean;
+ }
+ | {
+ status: 'invalid';
+ protocol: undefined;
+ index: unknown;
+ reason: string;
+ }
+ | {
+ status: 'empty';
+ protocol: undefined;
+ index: unknown;
+ reason: string;
+ };
+
+/**
+ * Resolves a protocol from a drug's protocol list against a requested index.
+ *
+ * CLINICAL SAFETY RULES:
+ * - Missing / undefined index resolves to index 0 (the established default).
+ * - Any non-integer, negative, or out-of-range value fails closed: returns
+ * status: 'invalid' so clinical UI and outbound formatters never guess a dose variant.
+ * - An empty protocol array returns status: 'empty'.
+ */
+export function resolveSelectedProtocol(
+ protocols: readonly DrugProtocol[] | DrugProtocol[] | undefined | null,
+ selectedIndex: unknown
+): ProtocolResolution {
+ if (!protocols || !Array.isArray(protocols) || protocols.length === 0) {
+ return {
+ status: 'empty',
+ protocol: undefined,
+ index: selectedIndex,
+ reason: 'No protocols available for drug',
+ };
+ }
+
+ // Missing or undefined defaults to the first protocol (index 0)
+ if (selectedIndex === undefined) {
+ return {
+ status: 'valid',
+ protocol: protocols[0],
+ index: 0,
+ isDefault: true,
+ };
+ }
+
+ // Fail closed on non-integers, null, strings, booleans, NaN, Infinity, etc.
+ if (typeof selectedIndex !== 'number' || !Number.isInteger(selectedIndex)) {
+ return {
+ status: 'invalid',
+ protocol: undefined,
+ index: selectedIndex,
+ reason: 'Protocol selection index must be an integer',
+ };
+ }
+
+ // Fail closed on negative or out-of-range indices
+ if (selectedIndex < 0 || selectedIndex >= protocols.length) {
+ return {
+ status: 'invalid',
+ protocol: undefined,
+ index: selectedIndex,
+ reason: `Protocol selection index ${selectedIndex} is out of bounds (0..${protocols.length - 1})`,
+ };
+ }
+
+ return {
+ status: 'valid',
+ protocol: protocols[selectedIndex],
+ index: selectedIndex,
+ isDefault: selectedIndex === 0,
+ };
+}
diff --git a/src/shared/utils/testingPlanFormatter.test.ts b/src/shared/utils/testingPlanFormatter.test.ts
index b3953e6..cf05360 100644
--- a/src/shared/utils/testingPlanFormatter.test.ts
+++ b/src/shared/utils/testingPlanFormatter.test.ts
@@ -8,6 +8,10 @@ describe('testingPlanFormatter', () => {
'Neuromuscular Blocking Agents (NMBAs)': ['Rocuronium', 'Vecuronium', 'Suxamethonium'],
'Induction Agents': ['Propofol', 'Thiopentone'],
'Opioids': ['Fentanyl', 'Morphine'],
+ 'Proton Pump Inhibitors': ['Pantoprazole'],
+ 'Penicillins': ['Tazocin', 'Cephalexin'],
+ 'Hypnotics': ['Ketamine'],
+ 'Others': ['Levofloxacin'],
};
it('formats a complete standard testing plan with protocol details', () => {
@@ -68,6 +72,125 @@ describe('testingPlanFormatter', () => {
expect(result).toContain('Request Date:');
});
+ it('formats exact clinical fields for Pantoprazole including diluent, preparation, review note, and SCRATCH link', () => {
+ const patient = createMockPatient();
+ const planData = createMockTestingPlanData({
+ selectedDrugs: ['Pantoprazole'],
+ selectedProtocols: { Pantoprazole: 0 },
+ });
+
+ const result = formatTestingPlanAsText(patient, planData, sampleDrugCategories);
+
+ expect(result).toContain('Proton Pump Inhibitors:');
+ expect(result).toContain('- Pantoprazole (40 mg powder for injection)');
+ expect(result).toContain('SPT: Neat (4 mg/mL) | Diluent: 0.9% sodium chloride (reconstitute with 10 mL NS)');
+ expect(result).toContain('IDT: 1:1,000 (0.004 mg/mL) [0.1 mL of 0.04 mg/mL + 0.9 mL NS] → 1:100 (0.04 mg/mL) [0.1 mL of 0.4 mg/mL + 0.9 mL NS] → 1:10 (0.4 mg/mL) [0.1 mL neat + 0.9 mL NS]');
+ expect(result).toContain('⚠ Under review: The Spreadsheet 2 spreadsheet labels the SPT concentration as "Neat (40 mg/mL)". This is a spreadsheet labelling error — the correct reconstituted concentration is 4 mg/mL (40 mg powder + 10 mL NS).');
+ expect(result).toContain('Source: https://scratch.yuson.au/drugs/pantoprazole/');
+ });
+
+ it('formats exact clinical fields for Tazocin including diluent, preparation, review note, and SCRATCH link', () => {
+ const patient = createMockPatient();
+ const planData = createMockTestingPlanData({
+ selectedDrugs: ['Tazocin'],
+ selectedProtocols: { Tazocin: 0 },
+ });
+
+ const result = formatTestingPlanAsText(patient, planData, sampleDrugCategories);
+
+ expect(result).toContain('Penicillins:');
+ expect(result).toContain('- Tazocin (4 g / 500 mg powder for injection)');
+ expect(result).toContain('SPT: 1:10 — ⚠️ concentration under review (Medication List: 2 mg/mL; calculation: 20 mg/mL) | Diluent: 0.9% sodium chloride (reconstitute with 20 mL NS)');
+ expect(result).toContain('IDT: 1:100 (2/0.2 mg/mL) [0.1 mL of 20/2 mg/mL + 0.9 mL NS]');
+ expect(result).toContain('⚠ Under review: Concentration discrepancy: Medication List specifies SPT at 1:10 (2 mg/mL Piperacillin), whereas calculation of 1:10 of 200 mg/mL gives 20 mg/mL. Concentration under clinical review.');
+ expect(result).toContain('Source: https://scratch.yuson.au/drugs/tazocin/');
+ });
+
+ it('formats pharmacy-verification warning and omits SCRATCH link for DREAM-only Cephalexin', () => {
+ const patient = createMockPatient();
+ const planData = createMockTestingPlanData({
+ selectedDrugs: ['Cephalexin'],
+ selectedProtocols: { Cephalexin: 0 },
+ });
+
+ const result = formatTestingPlanAsText(patient, planData, sampleDrugCategories);
+
+ expect(result).toContain('Penicillins:');
+ expect(result).toContain('- Cephalexin (2mg/mL)');
+ expect(result).toContain('SPT: Neat (2mg/mL) | Diluent: 0.9% sodium chloride');
+ expect(result).toContain('IDT: 1:100 (0.02mg/mL) → 1:10 (0.2mg/mL) → Neat (2mg/mL)');
+ expect(result).toContain('⚠ Confirm preparation with pharmacy');
+ expect(result).not.toContain('scratch.yuson.au');
+ });
+
+
+ it('formats pharmacy warning and source URL for generated Levofloxacin', () => {
+ const patient = createMockPatient();
+ const planData = createMockTestingPlanData({
+ selectedDrugs: ['Levofloxacin'],
+ selectedProtocols: { Levofloxacin: 0 },
+ });
+
+ const result = formatTestingPlanAsText(patient, planData, sampleDrugCategories);
+
+ expect(result).toContain('- Levofloxacin (500 mg tablets or IV formulation)');
+ expect(result).toContain('Protocol: Tablet');
+ expect(result).toContain('⚠ Confirm preparation with pharmacy');
+ expect(result).toContain('Source: https://scratch.yuson.au/drugs/levofloxacin/');
+ });
+
+ it('formats multi-protocol DREAM-only Ketamine with explicit protocol label and no SCRATCH URL', () => {
+ const patient = createMockPatient();
+ const planData = createMockTestingPlanData({
+ selectedDrugs: ['Ketamine'],
+ selectedProtocols: { Ketamine: 0 },
+ });
+
+ const result = formatTestingPlanAsText(patient, planData, sampleDrugCategories);
+
+ expect(result).toContain('Hypnotics:');
+ expect(result).toContain('- Ketamine (100mg/mL)');
+ expect(result).toContain('Protocol: 1:1,000 start');
+ expect(result).toContain('SPT: Neat (100mg/mL) | Diluent: 0.9% sodium chloride');
+ expect(result).toContain('IDT: 1:1,000 (0.1mg/mL) → 1:100 (1mg/mL) → 1:10 (10mg/mL)');
+ expect(result).not.toContain('scratch.yuson.au');
+ });
+
+ it('formats alternative protocol selection for Ketamine with explicit protocol label', () => {
+ const patient = createMockPatient();
+ const planData = createMockTestingPlanData({
+ selectedDrugs: ['Ketamine'],
+ selectedProtocols: { Ketamine: 1 },
+ });
+
+ const result = formatTestingPlanAsText(patient, planData, sampleDrugCategories);
+
+ expect(result).toContain('Hypnotics:');
+ expect(result).toContain('- Ketamine (100mg/mL)');
+ expect(result).toContain('Protocol: 1:100 start');
+ expect(result).toContain('SPT: Neat (100mg/mL) | Diluent: 0.9% sodium chloride');
+ expect(result).toContain('IDT: 1:1,000 (0.1mg/mL)');
+ expect(result).not.toContain('scratch.yuson.au');
+ });
+
+ it('fails closed on invalid protocol index and does not render guessed doses', () => {
+ const patient = createMockPatient();
+ const planData = createMockTestingPlanData({
+ selectedDrugs: ['Ketamine'],
+ selectedProtocols: { Ketamine: 99 }, // Out of bounds
+ });
+
+ const result = formatTestingPlanAsText(patient, planData, sampleDrugCategories);
+
+ expect(result).toContain('Hypnotics:');
+ expect(result).toContain('- Ketamine');
+ expect(result).toContain('⚠ Protocol selection requires review');
+ // Ensure guessed dose steps are NOT rendered
+ expect(result).not.toContain('1:1,000');
+ expect(result).not.toContain('1:100');
+ expect(result).not.toContain('Neat (50 mg/mL)');
+ });
+
it('omits urgent banner when urgent is false', () => {
const patient = createMockPatient();
const planData = createMockTestingPlanData({ urgent: false });
@@ -135,10 +258,10 @@ describe('testingPlanFormatter', () => {
expect(result).not.toContain('DOCUMENTS TO CHASE');
});
- it('formats custom drugs and additional entries', () => {
+ it('formats custom drugs and additional entries, including exact custom preparation strings', () => {
const patient = createMockPatient();
const planData = createMockTestingPlanData({
- selectedDrugs: ['CustomDrugA', 'CustomDrugB'],
+ selectedDrugs: ['CustomDrugA', 'CustomDrugB', 'CustomDrugWithPrep'],
customDrugs: [
{
name: 'CustomDrugA',
@@ -148,6 +271,11 @@ describe('testingPlanFormatter', () => {
{
name: 'CustomDrugB',
},
+ {
+ name: 'CustomDrugWithPrep',
+ sptConcentration: '5mg/mL',
+ idtSteps: [{ ratio: '1:100', concentration: '0.05mg/mL', preparation: '0.1 mL stock + 0.9 mL saline' }],
+ },
],
});
const result = formatTestingPlanAsText(patient, planData, sampleDrugCategories);
@@ -155,6 +283,7 @@ describe('testingPlanFormatter', () => {
expect(result).toContain('Additional:');
expect(result).toContain('- CustomDrugA | SPT: 10mg/ml | IDT: 1:100, 1:10');
expect(result).toContain('- CustomDrugB');
+ expect(result).toContain('- CustomDrugWithPrep | SPT: 5mg/mL | IDT: 1:100 (0.05mg/mL) [0.1 mL stock + 0.9 mL saline]');
});
it('displays "No drugs selected." when selectedDrugs is empty', () => {
diff --git a/src/shared/utils/testingPlanFormatter.ts b/src/shared/utils/testingPlanFormatter.ts
index 9290285..6c7b4d3 100644
--- a/src/shared/utils/testingPlanFormatter.ts
+++ b/src/shared/utils/testingPlanFormatter.ts
@@ -1,5 +1,6 @@
import { Patient, TestingPlanData } from '@shared/types';
import { getSkinProtocolsForDrug } from '@shared/data/drugMasterlist';
+import { resolveSelectedProtocol } from './protocolResolver';
export function formatTestingPlanAsText(
patient: Patient,
@@ -57,15 +58,60 @@ export function formatTestingPlanAsText(
lines.push(`${category}:`);
active.forEach(d => {
const protocols = getSkinProtocolsForDrug(d);
- const protocolIdx = data.selectedProtocols?.[d] ?? 0;
- const protocol = protocols[protocolIdx] ?? protocols[0];
- if (protocol?.sptNeatConcentration) {
- const idtChain = protocol.idtSteps.map(s => `${s.ratio}${s.concentration ? ` (${s.concentration})` : ''}`).join(' → ');
- const protocolNote = idtChain ? `SPT: ${protocol.sptNeatConcentration} | IDT: ${idtChain}` : `SPT: ${protocol.sptNeatConcentration}`;
- lines.push(` - ${d}${protocol.presentation ? ` (${protocol.presentation})` : ''}`);
- lines.push(` ${protocolNote}`);
- } else {
+ const resolution = resolveSelectedProtocol(protocols, data.selectedProtocols?.[d]);
+
+ if (resolution.status === 'invalid') {
lines.push(` - ${d}`);
+ lines.push(' ⚠ Protocol selection requires review');
+ return;
+ }
+
+ if (resolution.status === 'empty') {
+ lines.push(` - ${d}`);
+ return;
+ }
+
+ const protocol = resolution.protocol;
+ lines.push(` - ${d}${protocol.presentation ? ` (${protocol.presentation})` : ''}`);
+
+ // Protocol label if present
+ if (protocol.protocolLabel) {
+ lines.push(` Protocol: ${protocol.protocolLabel}`);
+ }
+
+ // SPT Neat Concentration & Diluent
+ if (protocol.sptNeatConcentration || protocol.diluent) {
+ const parts: string[] = [];
+ if (protocol.sptNeatConcentration) parts.push(`SPT: ${protocol.sptNeatConcentration}`);
+ if (protocol.diluent) parts.push(`Diluent: ${protocol.diluent}`);
+ lines.push(` ${parts.join(' | ')}`);
+ }
+
+ // IDT steps in source order with optional preparation
+ if (protocol.idtSteps && protocol.idtSteps.length > 0) {
+ const idtChain = protocol.idtSteps
+ .map(s => {
+ const conc = s.concentration ? ` (${s.concentration})` : '';
+ const prep = s.preparation ? ` [${s.preparation}]` : '';
+ return `${s.ratio}${conc}${prep}`;
+ })
+ .join(' → ');
+ lines.push(` IDT: ${idtChain}`);
+ }
+
+ // Under review note
+ if (protocol.underReview) {
+ lines.push(` ⚠ Under review${protocol.reviewNote ? `: ${protocol.reviewNote}` : ''}`);
+ }
+
+ // Pharmacy verification warning
+ if (protocol.needsPharmacyVerification) {
+ lines.push(' ⚠ Confirm preparation with pharmacy');
+ }
+
+ // SCRATCH source deep-link
+ if (protocol.sourceSlug) {
+ lines.push(` Source: https://scratch.yuson.au/drugs/${protocol.sourceSlug}/`);
}
});
hasAnyDrug = true;
@@ -76,7 +122,16 @@ export function formatTestingPlanAsText(
lines.push('Additional:');
activeCustom.forEach(e => {
const spt = e.sptConcentration ? ` | SPT: ${e.sptConcentration}` : '';
- const idt = e.idtSteps?.length ? ` | IDT: ${e.idtSteps.map(s => s.ratio).filter(Boolean).join(', ')}` : '';
+ const idt = e.idtSteps?.length
+ ? ` | IDT: ${e.idtSteps
+ .map(s => {
+ const conc = s.concentration ? ` (${s.concentration})` : '';
+ const prep = s.preparation ? ` [${s.preparation}]` : '';
+ return `${s.ratio}${conc}${prep}`;
+ })
+ .filter(Boolean)
+ .join(', ')}`
+ : '';
lines.push(` - ${e.name}${spt}${idt}`);
});
hasAnyDrug = true;
|