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,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Analyze panel results - educational context only, no diagnostic language
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
getReferenceRange,
|
|
7
|
+
getApplicableRange,
|
|
8
|
+
classifyValue,
|
|
9
|
+
checkCriticalValue,
|
|
10
|
+
formatReferenceRange,
|
|
11
|
+
BiomarkerRange,
|
|
12
|
+
} from "../ranges/questRanges.js";
|
|
13
|
+
import { getPanel, PanelDefinition } from "../ranges/panels.js";
|
|
14
|
+
|
|
15
|
+
export interface ResultInput {
|
|
16
|
+
biomarker_key: string;
|
|
17
|
+
value: number;
|
|
18
|
+
unit: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface AnalysisInput {
|
|
22
|
+
panel_id: string;
|
|
23
|
+
results: ResultInput[];
|
|
24
|
+
sex_at_birth?: "male" | "female";
|
|
25
|
+
age?: number;
|
|
26
|
+
fasting?: boolean;
|
|
27
|
+
on_thyroid_meds?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface Finding {
|
|
31
|
+
biomarker_key: string;
|
|
32
|
+
display_name: string;
|
|
33
|
+
value: number;
|
|
34
|
+
unit: string;
|
|
35
|
+
status: "low" | "high" | "normal" | "unknown";
|
|
36
|
+
reference_range: string;
|
|
37
|
+
educational_context: string;
|
|
38
|
+
note?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface AnalysisOutput {
|
|
42
|
+
panel_id: string;
|
|
43
|
+
panel_name: string;
|
|
44
|
+
summary: {
|
|
45
|
+
overall_status: "all_normal" | "some_outside_range" | "needs_attention" | "critical";
|
|
46
|
+
headline: string;
|
|
47
|
+
};
|
|
48
|
+
key_findings: Finding[];
|
|
49
|
+
reassuring_findings: string[];
|
|
50
|
+
missing_required_markers: string[];
|
|
51
|
+
red_flags_detected: boolean;
|
|
52
|
+
red_flag_message: string | null;
|
|
53
|
+
disclaimer_acknowledged: boolean;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Analyze panel results with educational context
|
|
58
|
+
* Returns structured findings without diagnostic language
|
|
59
|
+
*/
|
|
60
|
+
export function analyzePanelResults(input: AnalysisInput): AnalysisOutput {
|
|
61
|
+
const panel = getPanel(input.panel_id);
|
|
62
|
+
|
|
63
|
+
if (!panel) {
|
|
64
|
+
return {
|
|
65
|
+
panel_id: input.panel_id,
|
|
66
|
+
panel_name: "Unknown Panel",
|
|
67
|
+
summary: {
|
|
68
|
+
overall_status: "needs_attention",
|
|
69
|
+
headline: "Unable to analyze: panel not found",
|
|
70
|
+
},
|
|
71
|
+
key_findings: [],
|
|
72
|
+
reassuring_findings: [],
|
|
73
|
+
missing_required_markers: [],
|
|
74
|
+
red_flags_detected: false,
|
|
75
|
+
red_flag_message: null,
|
|
76
|
+
disclaimer_acknowledged: false,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Check for critical values first
|
|
81
|
+
const criticalFindings: { key: string; message: string }[] = [];
|
|
82
|
+
for (const result of input.results) {
|
|
83
|
+
const critical = checkCriticalValue(result.biomarker_key, result.value);
|
|
84
|
+
if (critical.isCritical && critical.message) {
|
|
85
|
+
criticalFindings.push({
|
|
86
|
+
key: result.biomarker_key,
|
|
87
|
+
message: critical.message,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (criticalFindings.length > 0) {
|
|
93
|
+
return {
|
|
94
|
+
panel_id: input.panel_id,
|
|
95
|
+
panel_name: panel.name,
|
|
96
|
+
summary: {
|
|
97
|
+
overall_status: "critical",
|
|
98
|
+
headline: "Important: Please contact a healthcare provider promptly",
|
|
99
|
+
},
|
|
100
|
+
key_findings: [],
|
|
101
|
+
reassuring_findings: [],
|
|
102
|
+
missing_required_markers: [],
|
|
103
|
+
red_flags_detected: true,
|
|
104
|
+
red_flag_message: `One or more values require prompt medical attention. ${criticalFindings.map((f) => f.message).join(" ")} Please contact your healthcare provider or seek medical care.`,
|
|
105
|
+
disclaimer_acknowledged: false,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Check for missing required markers
|
|
110
|
+
const providedKeys = input.results.map((r) => r.biomarker_key.toLowerCase());
|
|
111
|
+
const missingMarkers = panel.requiredMarkers.filter(
|
|
112
|
+
(m) => !providedKeys.includes(m.toLowerCase())
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
// Analyze each result
|
|
116
|
+
const keyFindings: Finding[] = [];
|
|
117
|
+
const reassuringFindings: string[] = [];
|
|
118
|
+
|
|
119
|
+
for (const result of input.results) {
|
|
120
|
+
const biomarkerRange = getReferenceRange(result.biomarker_key);
|
|
121
|
+
|
|
122
|
+
if (!biomarkerRange) {
|
|
123
|
+
keyFindings.push({
|
|
124
|
+
biomarker_key: result.biomarker_key,
|
|
125
|
+
display_name: result.biomarker_key,
|
|
126
|
+
value: result.value,
|
|
127
|
+
unit: result.unit,
|
|
128
|
+
status: "unknown",
|
|
129
|
+
reference_range: "Not available",
|
|
130
|
+
educational_context: "Reference range not available for this marker.",
|
|
131
|
+
});
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const applicableRange = getApplicableRange(biomarkerRange, input.sex_at_birth);
|
|
136
|
+
const status = classifyValue(result.value, applicableRange);
|
|
137
|
+
const referenceRangeStr = formatReferenceRange(applicableRange);
|
|
138
|
+
|
|
139
|
+
// Build educational context based on status
|
|
140
|
+
let educationalContext = biomarkerRange.educationalContext;
|
|
141
|
+
let note: string | undefined;
|
|
142
|
+
|
|
143
|
+
if (status === "low" && biomarkerRange.lowContext) {
|
|
144
|
+
educationalContext = biomarkerRange.lowContext;
|
|
145
|
+
} else if (status === "high" && biomarkerRange.highContext) {
|
|
146
|
+
educationalContext = biomarkerRange.highContext;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Add medication notes if relevant
|
|
150
|
+
if (input.on_thyroid_meds && ["tsh", "free-t4", "free-t3"].includes(result.biomarker_key.toLowerCase())) {
|
|
151
|
+
note = "Note: Thyroid medication can affect these values. Results are best interpreted by your prescribing clinician.";
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Add fasting note if relevant
|
|
155
|
+
if (!input.fasting && ["glucose", "insulin", "triglycerides"].includes(result.biomarker_key.toLowerCase())) {
|
|
156
|
+
note = "Note: This marker is ideally measured after fasting. Non-fasting values may be higher.";
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const finding: Finding = {
|
|
160
|
+
biomarker_key: result.biomarker_key,
|
|
161
|
+
display_name: biomarkerRange.displayName,
|
|
162
|
+
value: result.value,
|
|
163
|
+
unit: result.unit,
|
|
164
|
+
status,
|
|
165
|
+
reference_range: referenceRangeStr,
|
|
166
|
+
educational_context: educationalContext,
|
|
167
|
+
note,
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
if (status === "normal") {
|
|
171
|
+
reassuringFindings.push(`${biomarkerRange.displayName} is within the expected range.`);
|
|
172
|
+
} else {
|
|
173
|
+
keyFindings.push(finding);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Determine overall status
|
|
178
|
+
let overallStatus: "all_normal" | "some_outside_range" | "needs_attention" = "all_normal";
|
|
179
|
+
let headline = "Your results are within expected ranges.";
|
|
180
|
+
|
|
181
|
+
if (keyFindings.length > 0) {
|
|
182
|
+
overallStatus = "some_outside_range";
|
|
183
|
+
const outsideRangeCount = keyFindings.filter((f) => f.status !== "unknown").length;
|
|
184
|
+
headline = `${outsideRangeCount} marker${outsideRangeCount !== 1 ? "s" : ""} outside the typical range. See details below.`;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (missingMarkers.length > 0) {
|
|
188
|
+
headline += ` Note: ${missingMarkers.length} expected marker${missingMarkers.length !== 1 ? "s" : ""} not provided.`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
panel_id: input.panel_id,
|
|
193
|
+
panel_name: panel.name,
|
|
194
|
+
summary: {
|
|
195
|
+
overall_status: overallStatus,
|
|
196
|
+
headline,
|
|
197
|
+
},
|
|
198
|
+
key_findings: keyFindings,
|
|
199
|
+
reassuring_findings: reassuringFindings,
|
|
200
|
+
missing_required_markers: missingMarkers,
|
|
201
|
+
red_flags_detected: false,
|
|
202
|
+
red_flag_message: null,
|
|
203
|
+
disclaimer_acknowledged: false,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate patient-friendly summary with required disclaimer
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { AnalysisOutput } from "./analyzePanelResults.js";
|
|
6
|
+
import { NextStepsOutput } from "./suggestNextSteps.js";
|
|
7
|
+
|
|
8
|
+
export interface PatientSummaryCTA {
|
|
9
|
+
label: string;
|
|
10
|
+
url: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface PatientSummary {
|
|
14
|
+
headline: string;
|
|
15
|
+
bullets: string[];
|
|
16
|
+
cta: PatientSummaryCTA | null;
|
|
17
|
+
disclaimer: string;
|
|
18
|
+
requires_acknowledgment: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface PatientSummaryOutput {
|
|
22
|
+
patient_summary: PatientSummary;
|
|
23
|
+
safety_warning: string | null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const DISCLAIMER = `This summary is for educational purposes only and is not medical advice. Reference ranges may vary by lab and individual factors. Always discuss your results with a qualified healthcare provider before making any health decisions.`;
|
|
27
|
+
|
|
28
|
+
const SAFETY_DISCLAIMER = `IMPORTANT: One or more of your results requires prompt medical attention. Please contact your healthcare provider or seek medical care before reviewing this summary further.`;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Generate a patient-friendly summary
|
|
32
|
+
* Requires disclaimer acknowledgment before showing analysis
|
|
33
|
+
*/
|
|
34
|
+
export function generatePatientSummary(
|
|
35
|
+
panelId: string,
|
|
36
|
+
analysis: AnalysisOutput,
|
|
37
|
+
nextSteps: NextStepsOutput
|
|
38
|
+
): PatientSummaryOutput {
|
|
39
|
+
// Safety check first
|
|
40
|
+
if (analysis.red_flags_detected || nextSteps.safety.red_flags_detected) {
|
|
41
|
+
return {
|
|
42
|
+
patient_summary: {
|
|
43
|
+
headline: "Please contact a healthcare provider",
|
|
44
|
+
bullets: [
|
|
45
|
+
"One or more values in your results need prompt medical review.",
|
|
46
|
+
"Do not delay seeking care based on this summary.",
|
|
47
|
+
"Your healthcare provider can evaluate these findings properly.",
|
|
48
|
+
],
|
|
49
|
+
cta: null,
|
|
50
|
+
disclaimer: DISCLAIMER,
|
|
51
|
+
requires_acknowledgment: true,
|
|
52
|
+
},
|
|
53
|
+
safety_warning: nextSteps.safety.message || SAFETY_DISCLAIMER,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Build summary based on analysis and next steps
|
|
58
|
+
const bullets: string[] = [];
|
|
59
|
+
|
|
60
|
+
// Add headline context
|
|
61
|
+
if (analysis.summary.overall_status === "all_normal") {
|
|
62
|
+
bullets.push("All markers reviewed are within expected ranges.");
|
|
63
|
+
} else if (analysis.key_findings.length > 0) {
|
|
64
|
+
bullets.push(`${analysis.key_findings.length} marker${analysis.key_findings.length !== 1 ? "s" : ""} outside the typical range.`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Add key findings (max 2)
|
|
68
|
+
const significantFindings = analysis.key_findings
|
|
69
|
+
.filter((f) => f.status === "low" || f.status === "high")
|
|
70
|
+
.slice(0, 2);
|
|
71
|
+
|
|
72
|
+
for (const finding of significantFindings) {
|
|
73
|
+
const direction = finding.status === "low" ? "below" : "above";
|
|
74
|
+
bullets.push(
|
|
75
|
+
`${finding.display_name}: ${finding.value} ${finding.unit} (${direction} typical range of ${finding.reference_range})`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Add reassuring note if some things are normal
|
|
80
|
+
if (analysis.reassuring_findings.length > 0 && analysis.key_findings.length > 0) {
|
|
81
|
+
bullets.push(`${analysis.reassuring_findings.length} other marker${analysis.reassuring_findings.length !== 1 ? "s" : ""} within expected ranges.`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Add next step guidance
|
|
85
|
+
if (nextSteps.next_step.type === "additional_testing") {
|
|
86
|
+
bullets.push(`For more detail, consider: ${nextSteps.next_step.recommended_panel_name}`);
|
|
87
|
+
} else if (nextSteps.next_step.type === "clinician_discussion") {
|
|
88
|
+
bullets.push("Consider discussing these results with your healthcare provider.");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Ensure we have 3-5 bullets
|
|
92
|
+
if (bullets.length < 3) {
|
|
93
|
+
bullets.push("These results provide a snapshot of selected health markers.");
|
|
94
|
+
}
|
|
95
|
+
if (bullets.length < 3) {
|
|
96
|
+
bullets.push("A healthcare provider can help put these in context with your overall health.");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Cap at 5 bullets
|
|
100
|
+
const finalBullets = bullets.slice(0, 5);
|
|
101
|
+
|
|
102
|
+
// Build CTA only for additional testing
|
|
103
|
+
let cta: PatientSummaryCTA | null = null;
|
|
104
|
+
if (nextSteps.next_step.type === "additional_testing") {
|
|
105
|
+
cta = {
|
|
106
|
+
label: `Learn more: ${nextSteps.next_step.recommended_panel_name} ($${nextSteps.next_step.price})`,
|
|
107
|
+
url: nextSteps.next_step.order_url,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Build headline
|
|
112
|
+
let headline: string;
|
|
113
|
+
if (analysis.summary.overall_status === "all_normal") {
|
|
114
|
+
headline = "Your results are within expected ranges";
|
|
115
|
+
} else if (analysis.key_findings.length === 1) {
|
|
116
|
+
headline = "One marker to note in your results";
|
|
117
|
+
} else {
|
|
118
|
+
headline = "A few markers to review in your results";
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
patient_summary: {
|
|
123
|
+
headline,
|
|
124
|
+
bullets: finalBullets,
|
|
125
|
+
cta,
|
|
126
|
+
disclaimer: DISCLAIMER,
|
|
127
|
+
requires_acknowledgment: true,
|
|
128
|
+
},
|
|
129
|
+
safety_warning: null,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
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
|
+
|
|
6
|
+
import { AnalysisOutput } from "./analyzePanelResults.js";
|
|
7
|
+
import { getPanel, getFollowUpRules } from "../ranges/panels.js";
|
|
8
|
+
|
|
9
|
+
export interface NextStepAdditionalTesting {
|
|
10
|
+
type: "additional_testing";
|
|
11
|
+
recommended_panel_id: string;
|
|
12
|
+
recommended_panel_name: string;
|
|
13
|
+
reason: string;
|
|
14
|
+
confidence: "suggested" | "may_be_helpful";
|
|
15
|
+
order_url: string;
|
|
16
|
+
price: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface NextStepNoTesting {
|
|
20
|
+
type: "no_additional_testing";
|
|
21
|
+
reason: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface NextStepClinicianDiscussion {
|
|
25
|
+
type: "clinician_discussion";
|
|
26
|
+
reason: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type NextStep = NextStepAdditionalTesting | NextStepNoTesting | NextStepClinicianDiscussion;
|
|
30
|
+
|
|
31
|
+
export interface NextStepsOutput {
|
|
32
|
+
next_step: NextStep;
|
|
33
|
+
not_recommended: string[];
|
|
34
|
+
safety: {
|
|
35
|
+
red_flags_detected: boolean;
|
|
36
|
+
message: string | null;
|
|
37
|
+
};
|
|
38
|
+
clinician_guidance: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Suggest next steps based on analysis
|
|
43
|
+
* Conservative approach: one follow-up max, educational framing
|
|
44
|
+
*/
|
|
45
|
+
export function suggestNextSteps(
|
|
46
|
+
panelId: string,
|
|
47
|
+
analysis: AnalysisOutput
|
|
48
|
+
): NextStepsOutput {
|
|
49
|
+
// If red flags detected, always recommend clinician
|
|
50
|
+
if (analysis.red_flags_detected) {
|
|
51
|
+
return {
|
|
52
|
+
next_step: {
|
|
53
|
+
type: "clinician_discussion",
|
|
54
|
+
reason: "Based on your results, we recommend speaking with a healthcare provider before any additional testing.",
|
|
55
|
+
},
|
|
56
|
+
not_recommended: ["Additional testing through JustLabs at this time"],
|
|
57
|
+
safety: {
|
|
58
|
+
red_flags_detected: true,
|
|
59
|
+
message: analysis.red_flag_message,
|
|
60
|
+
},
|
|
61
|
+
clinician_guidance: "Please contact your healthcare provider or seek medical attention promptly.",
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const rules = getFollowUpRules(panelId);
|
|
66
|
+
|
|
67
|
+
// Default response if no rules defined
|
|
68
|
+
if (!rules) {
|
|
69
|
+
return {
|
|
70
|
+
next_step: {
|
|
71
|
+
type: "clinician_discussion",
|
|
72
|
+
reason: "Consider discussing these results with a healthcare provider who can provide personalized guidance.",
|
|
73
|
+
},
|
|
74
|
+
not_recommended: [],
|
|
75
|
+
safety: {
|
|
76
|
+
red_flags_detected: false,
|
|
77
|
+
message: null,
|
|
78
|
+
},
|
|
79
|
+
clinician_guidance: "A healthcare provider can help interpret these results in the context of your overall health.",
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Get markers that are outside range
|
|
84
|
+
const abnormalMarkerKeys = analysis.key_findings
|
|
85
|
+
.filter((f) => f.status === "low" || f.status === "high")
|
|
86
|
+
.map((f) => f.biomarker_key.toLowerCase());
|
|
87
|
+
|
|
88
|
+
// Check each condition in order (first match wins)
|
|
89
|
+
for (const condition of rules.conditions) {
|
|
90
|
+
if (!condition.abnormalMarkers) continue;
|
|
91
|
+
|
|
92
|
+
const matchesCondition = condition.abnormalMarkers.some((marker) =>
|
|
93
|
+
abnormalMarkerKeys.includes(marker.toLowerCase())
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
if (matchesCondition) {
|
|
97
|
+
// If followUpPanelId is empty, suggest clinician discussion
|
|
98
|
+
if (!condition.followUpPanelId) {
|
|
99
|
+
return {
|
|
100
|
+
next_step: {
|
|
101
|
+
type: "clinician_discussion",
|
|
102
|
+
reason: condition.reason,
|
|
103
|
+
},
|
|
104
|
+
not_recommended: ["Repeating the same panel within 4-6 weeks"],
|
|
105
|
+
safety: {
|
|
106
|
+
red_flags_detected: false,
|
|
107
|
+
message: null,
|
|
108
|
+
},
|
|
109
|
+
clinician_guidance: "A healthcare provider can help determine the best next steps based on your complete health picture.",
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const followUpPanel = getPanel(condition.followUpPanelId);
|
|
114
|
+
if (followUpPanel) {
|
|
115
|
+
return {
|
|
116
|
+
next_step: {
|
|
117
|
+
type: "additional_testing",
|
|
118
|
+
recommended_panel_id: followUpPanel.id,
|
|
119
|
+
recommended_panel_name: followUpPanel.name,
|
|
120
|
+
reason: condition.reason,
|
|
121
|
+
confidence: "may_be_helpful",
|
|
122
|
+
order_url: followUpPanel.url,
|
|
123
|
+
price: followUpPanel.price,
|
|
124
|
+
},
|
|
125
|
+
not_recommended: [
|
|
126
|
+
"Repeating the same panel within 4-6 weeks",
|
|
127
|
+
"Multiple additional panels at once",
|
|
128
|
+
],
|
|
129
|
+
safety: {
|
|
130
|
+
red_flags_detected: false,
|
|
131
|
+
message: null,
|
|
132
|
+
},
|
|
133
|
+
clinician_guidance: "While additional testing may provide more detail, a healthcare provider can help you decide if it's right for you.",
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// No conditions matched, use default
|
|
140
|
+
if (rules.defaultFollowUp) {
|
|
141
|
+
if (rules.defaultFollowUp.type === "no_additional_testing") {
|
|
142
|
+
return {
|
|
143
|
+
next_step: {
|
|
144
|
+
type: "no_additional_testing",
|
|
145
|
+
reason: rules.defaultFollowUp.reason,
|
|
146
|
+
},
|
|
147
|
+
not_recommended: ["Unnecessary repeat testing"],
|
|
148
|
+
safety: {
|
|
149
|
+
red_flags_detected: false,
|
|
150
|
+
message: null,
|
|
151
|
+
},
|
|
152
|
+
clinician_guidance: "Your results look reassuring. Consider sharing them with your healthcare provider at your next visit.",
|
|
153
|
+
};
|
|
154
|
+
} else {
|
|
155
|
+
return {
|
|
156
|
+
next_step: {
|
|
157
|
+
type: "clinician_discussion",
|
|
158
|
+
reason: rules.defaultFollowUp.reason,
|
|
159
|
+
},
|
|
160
|
+
not_recommended: [],
|
|
161
|
+
safety: {
|
|
162
|
+
red_flags_detected: false,
|
|
163
|
+
message: null,
|
|
164
|
+
},
|
|
165
|
+
clinician_guidance: "A healthcare provider can help interpret these results and suggest appropriate next steps.",
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Fallback
|
|
171
|
+
return {
|
|
172
|
+
next_step: {
|
|
173
|
+
type: "clinician_discussion",
|
|
174
|
+
reason: "Consider discussing these results with a healthcare provider.",
|
|
175
|
+
},
|
|
176
|
+
not_recommended: [],
|
|
177
|
+
safety: {
|
|
178
|
+
red_flags_detected: false,
|
|
179
|
+
message: null,
|
|
180
|
+
},
|
|
181
|
+
clinician_guidance: "A healthcare provider can provide personalized guidance based on your complete health picture.",
|
|
182
|
+
};
|
|
183
|
+
}
|