justlabs-mcp-server 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/analysis.test.d.ts +5 -0
- package/dist/__tests__/analysis.test.js +203 -0
- package/dist/analysis/analyzePanelResults.d.ts +45 -0
- package/dist/analysis/analyzePanelResults.js +137 -0
- package/dist/analysis/generatePatientSummary.d.ts +25 -0
- package/dist/analysis/generatePatientSummary.js +94 -0
- package/dist/analysis/suggestNextSteps.d.ts +37 -0
- package/dist/analysis/suggestNextSteps.js +136 -0
- package/dist/index.js +164 -0
- package/dist/ranges/panels.d.ts +28 -0
- package/dist/ranges/panels.js +115 -0
- package/dist/ranges/questRanges.d.ts +53 -0
- package/dist/ranges/questRanges.js +335 -0
- package/package.json +2 -2
- package/src/__tests__/analysis.test.ts +235 -0
- package/src/analysis/analyzePanelResults.ts +205 -0
- package/src/analysis/generatePatientSummary.ts +131 -0
- package/src/analysis/suggestNextSteps.ts +183 -0
- package/src/index.ts +180 -0
- package/src/ranges/panels.ts +145 -0
- package/src/ranges/questRanges.ts +403 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Suggest next steps based on panel analysis
|
|
3
|
+
* Returns at most ONE follow-up panel, framed as "more detail" not diagnosis
|
|
4
|
+
*/
|
|
5
|
+
import { getPanel, getFollowUpRules } from "../ranges/panels.js";
|
|
6
|
+
/**
|
|
7
|
+
* Suggest next steps based on analysis
|
|
8
|
+
* Conservative approach: one follow-up max, educational framing
|
|
9
|
+
*/
|
|
10
|
+
export function suggestNextSteps(panelId, analysis) {
|
|
11
|
+
// If red flags detected, always recommend clinician
|
|
12
|
+
if (analysis.red_flags_detected) {
|
|
13
|
+
return {
|
|
14
|
+
next_step: {
|
|
15
|
+
type: "clinician_discussion",
|
|
16
|
+
reason: "Based on your results, we recommend speaking with a healthcare provider before any additional testing.",
|
|
17
|
+
},
|
|
18
|
+
not_recommended: ["Additional testing through JustLabs at this time"],
|
|
19
|
+
safety: {
|
|
20
|
+
red_flags_detected: true,
|
|
21
|
+
message: analysis.red_flag_message,
|
|
22
|
+
},
|
|
23
|
+
clinician_guidance: "Please contact your healthcare provider or seek medical attention promptly.",
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
const rules = getFollowUpRules(panelId);
|
|
27
|
+
// Default response if no rules defined
|
|
28
|
+
if (!rules) {
|
|
29
|
+
return {
|
|
30
|
+
next_step: {
|
|
31
|
+
type: "clinician_discussion",
|
|
32
|
+
reason: "Consider discussing these results with a healthcare provider who can provide personalized guidance.",
|
|
33
|
+
},
|
|
34
|
+
not_recommended: [],
|
|
35
|
+
safety: {
|
|
36
|
+
red_flags_detected: false,
|
|
37
|
+
message: null,
|
|
38
|
+
},
|
|
39
|
+
clinician_guidance: "A healthcare provider can help interpret these results in the context of your overall health.",
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
// Get markers that are outside range
|
|
43
|
+
const abnormalMarkerKeys = analysis.key_findings
|
|
44
|
+
.filter((f) => f.status === "low" || f.status === "high")
|
|
45
|
+
.map((f) => f.biomarker_key.toLowerCase());
|
|
46
|
+
// Check each condition in order (first match wins)
|
|
47
|
+
for (const condition of rules.conditions) {
|
|
48
|
+
if (!condition.abnormalMarkers)
|
|
49
|
+
continue;
|
|
50
|
+
const matchesCondition = condition.abnormalMarkers.some((marker) => abnormalMarkerKeys.includes(marker.toLowerCase()));
|
|
51
|
+
if (matchesCondition) {
|
|
52
|
+
// If followUpPanelId is empty, suggest clinician discussion
|
|
53
|
+
if (!condition.followUpPanelId) {
|
|
54
|
+
return {
|
|
55
|
+
next_step: {
|
|
56
|
+
type: "clinician_discussion",
|
|
57
|
+
reason: condition.reason,
|
|
58
|
+
},
|
|
59
|
+
not_recommended: ["Repeating the same panel within 4-6 weeks"],
|
|
60
|
+
safety: {
|
|
61
|
+
red_flags_detected: false,
|
|
62
|
+
message: null,
|
|
63
|
+
},
|
|
64
|
+
clinician_guidance: "A healthcare provider can help determine the best next steps based on your complete health picture.",
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const followUpPanel = getPanel(condition.followUpPanelId);
|
|
68
|
+
if (followUpPanel) {
|
|
69
|
+
return {
|
|
70
|
+
next_step: {
|
|
71
|
+
type: "additional_testing",
|
|
72
|
+
recommended_panel_id: followUpPanel.id,
|
|
73
|
+
recommended_panel_name: followUpPanel.name,
|
|
74
|
+
reason: condition.reason,
|
|
75
|
+
confidence: "may_be_helpful",
|
|
76
|
+
order_url: followUpPanel.url,
|
|
77
|
+
price: followUpPanel.price,
|
|
78
|
+
},
|
|
79
|
+
not_recommended: [
|
|
80
|
+
"Repeating the same panel within 4-6 weeks",
|
|
81
|
+
"Multiple additional panels at once",
|
|
82
|
+
],
|
|
83
|
+
safety: {
|
|
84
|
+
red_flags_detected: false,
|
|
85
|
+
message: null,
|
|
86
|
+
},
|
|
87
|
+
clinician_guidance: "While additional testing may provide more detail, a healthcare provider can help you decide if it's right for you.",
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// No conditions matched, use default
|
|
93
|
+
if (rules.defaultFollowUp) {
|
|
94
|
+
if (rules.defaultFollowUp.type === "no_additional_testing") {
|
|
95
|
+
return {
|
|
96
|
+
next_step: {
|
|
97
|
+
type: "no_additional_testing",
|
|
98
|
+
reason: rules.defaultFollowUp.reason,
|
|
99
|
+
},
|
|
100
|
+
not_recommended: ["Unnecessary repeat testing"],
|
|
101
|
+
safety: {
|
|
102
|
+
red_flags_detected: false,
|
|
103
|
+
message: null,
|
|
104
|
+
},
|
|
105
|
+
clinician_guidance: "Your results look reassuring. Consider sharing them with your healthcare provider at your next visit.",
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
return {
|
|
110
|
+
next_step: {
|
|
111
|
+
type: "clinician_discussion",
|
|
112
|
+
reason: rules.defaultFollowUp.reason,
|
|
113
|
+
},
|
|
114
|
+
not_recommended: [],
|
|
115
|
+
safety: {
|
|
116
|
+
red_flags_detected: false,
|
|
117
|
+
message: null,
|
|
118
|
+
},
|
|
119
|
+
clinician_guidance: "A healthcare provider can help interpret these results and suggest appropriate next steps.",
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Fallback
|
|
124
|
+
return {
|
|
125
|
+
next_step: {
|
|
126
|
+
type: "clinician_discussion",
|
|
127
|
+
reason: "Consider discussing these results with a healthcare provider.",
|
|
128
|
+
},
|
|
129
|
+
not_recommended: [],
|
|
130
|
+
safety: {
|
|
131
|
+
red_flags_detected: false,
|
|
132
|
+
message: null,
|
|
133
|
+
},
|
|
134
|
+
clinician_guidance: "A healthcare provider can provide personalized guidance based on your complete health picture.",
|
|
135
|
+
};
|
|
136
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
// Analysis modules for results interpretation
|
|
6
|
+
import { analyzePanelResults } from "./analysis/analyzePanelResults.js";
|
|
7
|
+
import { suggestNextSteps } from "./analysis/suggestNextSteps.js";
|
|
8
|
+
import { generatePatientSummary } from "./analysis/generatePatientSummary.js";
|
|
5
9
|
// Lab test data (subset of popular tests)
|
|
6
10
|
const labTests = [
|
|
7
11
|
{
|
|
@@ -280,6 +284,91 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
280
284
|
required: ["testIds"],
|
|
281
285
|
},
|
|
282
286
|
},
|
|
287
|
+
// Results analysis tools (educational, conservative)
|
|
288
|
+
{
|
|
289
|
+
name: "analyze_panel_results",
|
|
290
|
+
description: "Analyze lab panel results and provide educational context. IMPORTANT: This provides educational information only, not medical diagnosis. User must acknowledge disclaimer before viewing analysis.",
|
|
291
|
+
inputSchema: {
|
|
292
|
+
type: "object",
|
|
293
|
+
properties: {
|
|
294
|
+
panel_id: {
|
|
295
|
+
type: "string",
|
|
296
|
+
description: "Panel ID (e.g., 'fatigue_panel', 'thyroid_deep_dive', 'metabolic_panel', 'wellness_basic')",
|
|
297
|
+
},
|
|
298
|
+
results: {
|
|
299
|
+
type: "array",
|
|
300
|
+
items: {
|
|
301
|
+
type: "object",
|
|
302
|
+
properties: {
|
|
303
|
+
biomarker_key: { type: "string", description: "Biomarker key (e.g., 'tsh', 'ferritin', 'vitamin-d')" },
|
|
304
|
+
value: { type: "number", description: "The numeric value" },
|
|
305
|
+
unit: { type: "string", description: "The unit (e.g., 'mIU/L', 'ng/mL')" },
|
|
306
|
+
},
|
|
307
|
+
required: ["biomarker_key", "value", "unit"],
|
|
308
|
+
},
|
|
309
|
+
description: "Array of biomarker results",
|
|
310
|
+
},
|
|
311
|
+
sex_at_birth: {
|
|
312
|
+
type: "string",
|
|
313
|
+
enum: ["male", "female"],
|
|
314
|
+
description: "Sex at birth (affects reference ranges for some markers)",
|
|
315
|
+
},
|
|
316
|
+
age: {
|
|
317
|
+
type: "number",
|
|
318
|
+
description: "Age in years (optional, may affect interpretation)",
|
|
319
|
+
},
|
|
320
|
+
fasting: {
|
|
321
|
+
type: "boolean",
|
|
322
|
+
description: "Whether the sample was taken after fasting",
|
|
323
|
+
},
|
|
324
|
+
on_thyroid_meds: {
|
|
325
|
+
type: "boolean",
|
|
326
|
+
description: "Whether currently taking thyroid medication",
|
|
327
|
+
},
|
|
328
|
+
},
|
|
329
|
+
required: ["panel_id", "results"],
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
name: "suggest_next_steps",
|
|
334
|
+
description: "Based on panel analysis, suggest whether additional testing may provide more detail. Returns at most ONE follow-up panel suggestion. Educational only, not medical advice.",
|
|
335
|
+
inputSchema: {
|
|
336
|
+
type: "object",
|
|
337
|
+
properties: {
|
|
338
|
+
panel_id: {
|
|
339
|
+
type: "string",
|
|
340
|
+
description: "The panel ID that was analyzed",
|
|
341
|
+
},
|
|
342
|
+
analysis: {
|
|
343
|
+
type: "object",
|
|
344
|
+
description: "The analysis object from analyze_panel_results",
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
required: ["panel_id", "analysis"],
|
|
348
|
+
},
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
name: "generate_patient_summary",
|
|
352
|
+
description: "Generate a patient-friendly summary of results with educational context. Includes required disclaimer. MUST be shown to user with disclaimer acknowledgment.",
|
|
353
|
+
inputSchema: {
|
|
354
|
+
type: "object",
|
|
355
|
+
properties: {
|
|
356
|
+
panel_id: {
|
|
357
|
+
type: "string",
|
|
358
|
+
description: "The panel ID",
|
|
359
|
+
},
|
|
360
|
+
analysis: {
|
|
361
|
+
type: "object",
|
|
362
|
+
description: "The analysis object from analyze_panel_results",
|
|
363
|
+
},
|
|
364
|
+
next_steps: {
|
|
365
|
+
type: "object",
|
|
366
|
+
description: "The next steps object from suggest_next_steps",
|
|
367
|
+
},
|
|
368
|
+
},
|
|
369
|
+
required: ["panel_id", "analysis", "next_steps"],
|
|
370
|
+
},
|
|
371
|
+
},
|
|
283
372
|
],
|
|
284
373
|
};
|
|
285
374
|
});
|
|
@@ -426,6 +515,81 @@ No doctor visit or insurance needed.`,
|
|
|
426
515
|
],
|
|
427
516
|
};
|
|
428
517
|
}
|
|
518
|
+
// Results analysis tools
|
|
519
|
+
case "analyze_panel_results": {
|
|
520
|
+
const input = {
|
|
521
|
+
panel_id: args?.panel_id,
|
|
522
|
+
results: args?.results,
|
|
523
|
+
sex_at_birth: args?.sex_at_birth,
|
|
524
|
+
age: args?.age,
|
|
525
|
+
fasting: args?.fasting,
|
|
526
|
+
on_thyroid_meds: args?.on_thyroid_meds,
|
|
527
|
+
};
|
|
528
|
+
const analysis = analyzePanelResults(input);
|
|
529
|
+
// If red flags detected, return safety message immediately
|
|
530
|
+
if (analysis.red_flags_detected) {
|
|
531
|
+
return {
|
|
532
|
+
content: [
|
|
533
|
+
{
|
|
534
|
+
type: "text",
|
|
535
|
+
text: `# IMPORTANT: Please Contact a Healthcare Provider
|
|
536
|
+
|
|
537
|
+
${analysis.red_flag_message}
|
|
538
|
+
|
|
539
|
+
This analysis cannot continue until these values have been reviewed by a healthcare professional.
|
|
540
|
+
|
|
541
|
+
---
|
|
542
|
+
**Disclaimer:** This is educational information only, not medical advice.`,
|
|
543
|
+
},
|
|
544
|
+
],
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
return {
|
|
548
|
+
content: [
|
|
549
|
+
{
|
|
550
|
+
type: "text",
|
|
551
|
+
text: JSON.stringify(analysis, null, 2),
|
|
552
|
+
},
|
|
553
|
+
],
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
case "suggest_next_steps": {
|
|
557
|
+
const panelId = args?.panel_id;
|
|
558
|
+
const analysis = args?.analysis;
|
|
559
|
+
const nextSteps = suggestNextSteps(panelId, analysis);
|
|
560
|
+
return {
|
|
561
|
+
content: [
|
|
562
|
+
{
|
|
563
|
+
type: "text",
|
|
564
|
+
text: JSON.stringify(nextSteps, null, 2),
|
|
565
|
+
},
|
|
566
|
+
],
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
case "generate_patient_summary": {
|
|
570
|
+
const panelId = args?.panel_id;
|
|
571
|
+
const analysis = args?.analysis;
|
|
572
|
+
const nextSteps = args?.next_steps;
|
|
573
|
+
const summary = generatePatientSummary(panelId, analysis, nextSteps);
|
|
574
|
+
// Format as readable text for the user
|
|
575
|
+
let output = `# ${summary.patient_summary.headline}\n\n`;
|
|
576
|
+
if (summary.safety_warning) {
|
|
577
|
+
output += `**IMPORTANT:** ${summary.safety_warning}\n\n`;
|
|
578
|
+
}
|
|
579
|
+
output += summary.patient_summary.bullets.map((b) => `- ${b}`).join("\n");
|
|
580
|
+
if (summary.patient_summary.cta) {
|
|
581
|
+
output += `\n\n**Next step for more detail:** [${summary.patient_summary.cta.label}](${summary.patient_summary.cta.url})`;
|
|
582
|
+
}
|
|
583
|
+
output += `\n\n---\n**Disclaimer:** ${summary.patient_summary.disclaimer}`;
|
|
584
|
+
return {
|
|
585
|
+
content: [
|
|
586
|
+
{
|
|
587
|
+
type: "text",
|
|
588
|
+
text: output,
|
|
589
|
+
},
|
|
590
|
+
],
|
|
591
|
+
};
|
|
592
|
+
}
|
|
429
593
|
default:
|
|
430
594
|
return {
|
|
431
595
|
content: [
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Panel definitions with required markers and follow-up logic
|
|
3
|
+
*/
|
|
4
|
+
export interface PanelDefinition {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
description: string;
|
|
8
|
+
requiredMarkers: string[];
|
|
9
|
+
optionalMarkers?: string[];
|
|
10
|
+
price: number;
|
|
11
|
+
url: string;
|
|
12
|
+
}
|
|
13
|
+
export interface FollowUpRule {
|
|
14
|
+
panelId: string;
|
|
15
|
+
conditions: {
|
|
16
|
+
abnormalMarkers?: string[];
|
|
17
|
+
followUpPanelId: string;
|
|
18
|
+
reason: string;
|
|
19
|
+
}[];
|
|
20
|
+
defaultFollowUp?: {
|
|
21
|
+
type: "no_additional_testing" | "clinician_discussion";
|
|
22
|
+
reason: string;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export declare const PANELS: PanelDefinition[];
|
|
26
|
+
export declare const FOLLOW_UP_RULES: FollowUpRule[];
|
|
27
|
+
export declare function getPanel(panelId: string): PanelDefinition | undefined;
|
|
28
|
+
export declare function getFollowUpRules(panelId: string): FollowUpRule | undefined;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Panel definitions with required markers and follow-up logic
|
|
3
|
+
*/
|
|
4
|
+
export const PANELS = [
|
|
5
|
+
{
|
|
6
|
+
id: "fatigue_panel",
|
|
7
|
+
name: "Fatigue Panel",
|
|
8
|
+
description: "Comprehensive testing for common causes of fatigue including thyroid, iron, B12, and vitamin D.",
|
|
9
|
+
requiredMarkers: ["tsh", "ferritin", "vitamin-b12", "vitamin-d", "wbc", "hemoglobin"],
|
|
10
|
+
optionalMarkers: ["free-t4", "folate"],
|
|
11
|
+
price: 55,
|
|
12
|
+
url: "https://justlabs.health/tests/panel/panel-fatigue",
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
id: "thyroid_deep_dive",
|
|
16
|
+
name: "Complete Thyroid Panel",
|
|
17
|
+
description: "Comprehensive thyroid evaluation including TSH, Free T4, Free T3, and thyroid antibodies.",
|
|
18
|
+
requiredMarkers: ["tsh", "free-t4", "free-t3", "tpo-antibodies", "thyroglobulin-antibodies"],
|
|
19
|
+
price: 65,
|
|
20
|
+
url: "https://justlabs.health/tests/panel/panel-thyroid-complete",
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: "metabolic_panel",
|
|
24
|
+
name: "Metabolic Health Panel",
|
|
25
|
+
description: "Assess blood sugar regulation and cardiovascular markers.",
|
|
26
|
+
requiredMarkers: ["hemoglobin-a1c", "glucose", "insulin", "total-cholesterol", "hdl-cholesterol", "ldl-cholesterol", "triglycerides"],
|
|
27
|
+
optionalMarkers: ["hs-crp"],
|
|
28
|
+
price: 75,
|
|
29
|
+
url: "https://justlabs.health/tests/panel/panel-metabolic",
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: "wellness_basic",
|
|
33
|
+
name: "Basic Wellness Panel",
|
|
34
|
+
description: "Essential health screening including blood counts, metabolic markers, lipids, and thyroid.",
|
|
35
|
+
requiredMarkers: ["wbc", "hemoglobin", "glucose", "creatinine", "alt", "total-cholesterol", "tsh"],
|
|
36
|
+
price: 35,
|
|
37
|
+
url: "https://justlabs.health/tests/panel/panel-wellness-basic",
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
export const FOLLOW_UP_RULES = [
|
|
41
|
+
{
|
|
42
|
+
panelId: "fatigue_panel",
|
|
43
|
+
conditions: [
|
|
44
|
+
{
|
|
45
|
+
// If thyroid markers abnormal, suggest deep dive
|
|
46
|
+
abnormalMarkers: ["tsh"],
|
|
47
|
+
followUpPanelId: "thyroid_deep_dive",
|
|
48
|
+
reason: "Your TSH result suggests thyroid function may benefit from a closer look. A complete thyroid panel provides more detail about how your thyroid is functioning.",
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
// If metabolic markers present and abnormal, suggest metabolic panel
|
|
52
|
+
abnormalMarkers: ["glucose"],
|
|
53
|
+
followUpPanelId: "metabolic_panel",
|
|
54
|
+
reason: "Your glucose result suggests metabolic health may benefit from a closer look. A metabolic panel provides more detail about blood sugar regulation and cardiovascular markers.",
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
defaultFollowUp: {
|
|
58
|
+
type: "clinician_discussion",
|
|
59
|
+
reason: "Your results are within expected ranges. If fatigue persists, consider discussing with a healthcare provider who can review your complete health picture.",
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
panelId: "thyroid_deep_dive",
|
|
64
|
+
conditions: [
|
|
65
|
+
{
|
|
66
|
+
// If antibodies elevated, no further JustLabs testing needed
|
|
67
|
+
abnormalMarkers: ["tpo-antibodies", "thyroglobulin-antibodies"],
|
|
68
|
+
followUpPanelId: "", // Empty means clinician discussion
|
|
69
|
+
reason: "Your antibody results provide useful information. We recommend discussing these findings with a healthcare provider who can provide personalized guidance.",
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
defaultFollowUp: {
|
|
73
|
+
type: "clinician_discussion",
|
|
74
|
+
reason: "Thyroid results are best interpreted by a healthcare provider who can consider your symptoms and health history.",
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
panelId: "metabolic_panel",
|
|
79
|
+
conditions: [],
|
|
80
|
+
defaultFollowUp: {
|
|
81
|
+
type: "clinician_discussion",
|
|
82
|
+
reason: "Metabolic results are best interpreted by a healthcare provider who can provide personalized guidance on lifestyle and any next steps.",
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
panelId: "wellness_basic",
|
|
87
|
+
conditions: [
|
|
88
|
+
{
|
|
89
|
+
abnormalMarkers: ["tsh"],
|
|
90
|
+
followUpPanelId: "thyroid_deep_dive",
|
|
91
|
+
reason: "Your TSH result suggests thyroid function may benefit from a closer look.",
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
abnormalMarkers: ["glucose"],
|
|
95
|
+
followUpPanelId: "metabolic_panel",
|
|
96
|
+
reason: "Your glucose result suggests metabolic health may benefit from a closer look.",
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
abnormalMarkers: ["hemoglobin", "wbc"],
|
|
100
|
+
followUpPanelId: "fatigue_panel",
|
|
101
|
+
reason: "Your blood count results suggest looking at additional markers that can affect energy levels.",
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
defaultFollowUp: {
|
|
105
|
+
type: "no_additional_testing",
|
|
106
|
+
reason: "Your basic wellness markers are within expected ranges. Consider retesting annually or if new symptoms develop.",
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
];
|
|
110
|
+
export function getPanel(panelId) {
|
|
111
|
+
return PANELS.find((p) => p.id === panelId);
|
|
112
|
+
}
|
|
113
|
+
export function getFollowUpRules(panelId) {
|
|
114
|
+
return FOLLOW_UP_RULES.find((r) => r.panelId === panelId);
|
|
115
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quest-style reference ranges for common biomarkers
|
|
3
|
+
* These are educational reference points, not diagnostic criteria
|
|
4
|
+
*/
|
|
5
|
+
export interface ReferenceRange {
|
|
6
|
+
low?: number;
|
|
7
|
+
high?: number;
|
|
8
|
+
unit: string;
|
|
9
|
+
note?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface BiomarkerRange {
|
|
12
|
+
key: string;
|
|
13
|
+
displayName: string;
|
|
14
|
+
unit: string;
|
|
15
|
+
defaultRange: ReferenceRange;
|
|
16
|
+
maleRange?: ReferenceRange;
|
|
17
|
+
femaleRange?: ReferenceRange;
|
|
18
|
+
educationalContext: string;
|
|
19
|
+
lowContext?: string;
|
|
20
|
+
highContext?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface CriticalThreshold {
|
|
23
|
+
key: string;
|
|
24
|
+
criticalLow?: number;
|
|
25
|
+
criticalHigh?: number;
|
|
26
|
+
unit: string;
|
|
27
|
+
message: string;
|
|
28
|
+
}
|
|
29
|
+
export declare const CRITICAL_THRESHOLDS: CriticalThreshold[];
|
|
30
|
+
export declare const BIOMARKER_RANGES: BiomarkerRange[];
|
|
31
|
+
/**
|
|
32
|
+
* Get reference range for a biomarker, considering sex if provided
|
|
33
|
+
*/
|
|
34
|
+
export declare function getReferenceRange(biomarkerKey: string, sex?: "male" | "female"): BiomarkerRange | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Get the appropriate range values for a biomarker based on sex
|
|
37
|
+
*/
|
|
38
|
+
export declare function getApplicableRange(biomarker: BiomarkerRange, sex?: "male" | "female"): ReferenceRange;
|
|
39
|
+
/**
|
|
40
|
+
* Check if a value is within reference range
|
|
41
|
+
*/
|
|
42
|
+
export declare function classifyValue(value: number, range: ReferenceRange): "low" | "normal" | "high" | "unknown";
|
|
43
|
+
/**
|
|
44
|
+
* Check for critical (red flag) values
|
|
45
|
+
*/
|
|
46
|
+
export declare function checkCriticalValue(biomarkerKey: string, value: number): {
|
|
47
|
+
isCritical: boolean;
|
|
48
|
+
message?: string;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Format a reference range for display
|
|
52
|
+
*/
|
|
53
|
+
export declare function formatReferenceRange(range: ReferenceRange): string;
|