Skip to content

Commit 67bbe75

Browse files
committed
v2.4: Optimize clinical report — hybrid template + AI insights
- Replace full-prose LLM prompt with short JSON schema prompt (~85% token reduction) - buildMockReport() generates deterministic template, Nova Pro adds clinical insights - mergeAiInsights() enriches template with severity, DSM-5 mappings, recommendations - maxTokens 2048 → 512, fallback preserved if Bedrock fails - Update all docs: Cohere Command R+ → Nova Pro references
1 parent 8d6c4fc commit 67bbe75

4 files changed

Lines changed: 125 additions & 83 deletions

File tree

app/api/report/clinical/route.ts

Lines changed: 96 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
/**
22
* POST /api/report/clinical
33
*
4-
* Generates a full DSM-5 aligned clinical report using Amazon Nova Pro
5-
* via Amazon Bedrock. Falls back to a detailed mock report when AWS
6-
* credentials are not configured.
4+
* Generates a DSM-5 aligned clinical report using a hybrid approach:
5+
* 1. Deterministic template (buildMockReport) generates the full report
6+
* 2. Amazon Nova Pro provides structured clinical insights via short JSON prompt
7+
* 3. Insights are merged into the template for clinical depth
8+
* Falls back to the template-only report when Bedrock is unavailable.
79
*
810
* Request body:
911
* { sessionId: string, biomarkers: BiomarkerAggregate, childAge?: number }
@@ -44,6 +46,15 @@ interface ClinicalResponse {
4446
};
4547
}
4648

49+
interface AiInsights {
50+
severityLevel: string;
51+
criterionA_interpretation: string;
52+
criterionB_interpretation: string;
53+
clinicalImpression: string;
54+
priorityRecommendations: string[];
55+
differentialConsiderations: string;
56+
}
57+
4758
const BEDROCK_REGION = process.env.BEDROCK_REGION || "us-east-1";
4859

4960
function getBedrockClient(): BedrockRuntimeClient {
@@ -119,34 +130,81 @@ IMPORTANT DISCLAIMER: This report is generated by a computer-assisted screening
119130
return { report, sections: { criterionA, criterionB, motor, recommendations } };
120131
}
121132

122-
function parseSections(text: string): ClinicalResponse["sections"] {
123-
const sectionPatterns = {
124-
criterionA: /(?:CRITERION\s*A|Social\s*Communication)[^\n]*\n([\s\S]*?)(?=(?:CRITERION\s*B|Restricted|MOTOR|={3,})|$)/i,
125-
criterionB: /(?:CRITERION\s*B|Restricted\s*&?\s*Repetitive)[^\n]*\n([\s\S]*?)(?=(?:MOTOR|RECOMMENDATION|={3,})|$)/i,
126-
motor: /(?:MOTOR)[^\n]*\n([\s\S]*?)(?=(?:RECOMMENDATION|={3,})|$)/i,
127-
recommendations: /(?:RECOMMENDATION)[^\n]*\n([\s\S]*?)$/i,
128-
};
129-
130-
const sections: ClinicalResponse["sections"] = {
131-
criterionA: "",
132-
criterionB: "",
133-
motor: "",
134-
recommendations: "",
135-
};
133+
function buildInsightsPrompt(
134+
biomarkers: BiomarkerAggregate,
135+
childAge?: number,
136+
): string {
137+
const age = childAge
138+
? `${Math.floor(childAge / 12)}y${childAge % 12}m`
139+
: "unknown";
140+
return `Autism screening biomarkers for child (age: ${age}): ${JSON.stringify(biomarkers)}
141+
142+
Return ONLY valid JSON (no markdown, no explanation):
143+
{"severityLevel":"mild|moderate|significant","criterionA_interpretation":"1-2 sentences mapping gaze/vocalization/face scores to DSM-5 A.1/A.2/A.3","criterionB_interpretation":"1-2 sentences mapping motor/behavior patterns to DSM-5 B.1/B.2/B.4","clinicalImpression":"2-3 sentence overall clinical impression","priorityRecommendations":["2-3 specific recommendations for this child"],"differentialConsiderations":"1 sentence on differential diagnosis considerations"}`;
144+
}
136145

137-
for (const [key, pattern] of Object.entries(sectionPatterns)) {
138-
const match = text.match(pattern);
139-
if (match) {
140-
sections[key as keyof typeof sections] = match[1].trim();
141-
}
146+
function parseInsights(text: string): AiInsights | null {
147+
try {
148+
// Try to extract JSON from the response (handle possible markdown wrapping)
149+
const jsonMatch = text.match(/\{[\s\S]*\}/);
150+
if (!jsonMatch) return null;
151+
const parsed = JSON.parse(jsonMatch[0]);
152+
if (!parsed.severityLevel || !parsed.clinicalImpression) return null;
153+
return parsed as AiInsights;
154+
} catch {
155+
return null;
142156
}
157+
}
143158

144-
// If parsing failed, put everything in criterionA
145-
if (!sections.criterionA && !sections.criterionB) {
146-
sections.criterionA = text;
147-
}
159+
function mergeAiInsights(
160+
base: ClinicalResponse,
161+
insights: AiInsights,
162+
): ClinicalResponse {
163+
const { criterionA, criterionB, motor, recommendations } = base.sections;
164+
165+
// Enrich Criterion A with AI interpretation
166+
const enrichedA = criterionA.replace(
167+
/Social Communication Flag:/,
168+
`\nClinical Interpretation:\n${insights.criterionA_interpretation}\n\nSocial Communication Flag:`,
169+
);
170+
171+
// Enrich Criterion B with AI interpretation
172+
const enrichedB = criterionB.replace(
173+
/Restricted Behavior Flag:/,
174+
`\nClinical Interpretation:\n${insights.criterionB_interpretation}\n\nRestricted Behavior Flag:`,
175+
);
176+
177+
// Enrich Recommendations with clinical impression and specific recommendations
178+
const severityLine = `Clinical Severity Assessment: ${insights.severityLevel.toUpperCase()}\n\n`;
179+
const impressionBlock = `Clinical Impression:\n${insights.clinicalImpression}\n\n`;
180+
const aiRecs = insights.priorityRecommendations
181+
.map((r, i) => `${i + 1}. ${r}`)
182+
.join("\n");
183+
const differential = `\nDifferential Considerations:\n${insights.differentialConsiderations}`;
184+
185+
const enrichedRecs = recommendations.replace(
186+
/Based on this screening:/,
187+
`${severityLine}${impressionBlock}Based on this screening:`,
188+
).replace(
189+
/IMPORTANT DISCLAIMER:/,
190+
`${aiRecs ? `\nPriority Recommendations:\n${aiRecs}\n\n` : ""}${differential}\n\nIMPORTANT DISCLAIMER:`,
191+
);
192+
193+
const sections = {
194+
criterionA: enrichedA,
195+
criterionB: enrichedB,
196+
motor,
197+
recommendations: enrichedRecs,
198+
};
148199

149-
return sections;
200+
const report = [
201+
sections.criterionA,
202+
sections.criterionB,
203+
sections.motor,
204+
sections.recommendations,
205+
].join("\n\n---\n\n");
206+
207+
return { report, sections };
150208
}
151209

152210
export async function POST(req: NextRequest) {
@@ -170,43 +228,15 @@ export async function POST(req: NextRequest) {
170228

171229
const { biomarkers, childAge } = body;
172230

173-
const ageContext = childAge
174-
? `The child is ${Math.floor(childAge / 12)} years and ${childAge % 12} months old.`
175-
: "The child's age was not specified.";
176-
177-
const prompt = `You are a clinical report generator for an autism screening platform. Generate a comprehensive DSM-5 aligned clinical screening report based on the following biomarker data.
178-
179-
${ageContext}
180-
181-
Biomarker Data:
182-
${JSON.stringify(biomarkers, null, 2)}
183-
184-
Structure the report with these EXACT section headers:
185-
186-
CRITERION A -- SOCIAL COMMUNICATION & INTERACTION
187-
Analyze social gaze patterns (avgGazeScore), vocalization quality (avgVocalizationScore), facial affect (dominantFaceBehavior if available), and their mapping to DSM-5 Criterion A sub-criteria (A.1 social-emotional reciprocity, A.2 nonverbal communication, A.3 relationships).
188-
189-
CRITERION B -- RESTRICTED & REPETITIVE BEHAVIOURS
190-
Analyze motor patterns (avgMotorScore), response latency, body behavior classification (dominantBodyBehavior, behaviorClassDistribution if available), and mapping to DSM-5 Criterion B sub-criteria (B.1 stereotyped movements, B.2 insistence on sameness, B.3 restricted interests, B.4 sensory reactivity).
191-
192-
MOTOR DEVELOPMENT ASSESSMENT
193-
Provide a focused motor development analysis based on the motor score and any detected body behavior patterns. Note co-occurring motor differences common in ASD.
194-
195-
RECOMMENDATIONS
196-
Provide actionable next steps based on the screening results: referral recommendations, suggested interventions, monitoring frequency. Include a disclaimer that this is a screening tool, not a diagnostic instrument.
197-
198-
Guidelines:
199-
- Use clinical but accessible language
200-
- Reference specific DSM-5 criteria codes where appropriate
201-
- Include specific scores and thresholds in your analysis
202-
- Be factual and evidence-based, avoid speculation
203-
- End with a clear disclaimer about the screening nature of this tool`;
231+
// Always generate the deterministic template first
232+
const baseReport = buildMockReport(biomarkers, childAge);
204233

205234
try {
206235
const client = getBedrockClient();
236+
const prompt = buildInsightsPrompt(biomarkers, childAge);
207237
const invokeBody = JSON.stringify({
208238
messages: [{ role: "user", content: [{ text: prompt }] }],
209-
inferenceConfig: { maxTokens: 2048, temperature: 0.5 },
239+
inferenceConfig: { maxTokens: 512, temperature: 0.5 },
210240
});
211241

212242
const command = new InvokeModelCommand({
@@ -219,24 +249,18 @@ Guidelines:
219249
const response = await client.send(command);
220250
const responseBody = JSON.parse(new TextDecoder().decode(response.body));
221251

222-
// Nova Pro returns: { output: { message: { content: [{ text: "..." }] } } }
223-
const reportText: string =
252+
const text: string =
224253
responseBody?.output?.message?.content?.[0]?.text ?? "";
225254

226-
if (!reportText) {
227-
console.warn("[Report/Clinical] Empty response from Bedrock, using mock");
228-
return NextResponse.json(buildMockReport(biomarkers, childAge));
255+
const insights = parseInsights(text);
256+
if (!insights) {
257+
console.warn("[Report/Clinical] Could not parse AI insights, using template only");
258+
return NextResponse.json(baseReport);
229259
}
230260

231-
const sections = parseSections(reportText);
232-
233-
return NextResponse.json({
234-
report: reportText,
235-
sections,
236-
});
261+
return NextResponse.json(mergeAiInsights(baseReport, insights));
237262
} catch (err) {
238263
console.error("[Report/Clinical] Bedrock invocation failed:", err);
239-
// Graceful degradation: return mock report on error
240-
return NextResponse.json(buildMockReport(biomarkers, childAge));
264+
return NextResponse.json(baseReport);
241265
}
242266
}

docs/Amazon_usage.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
| Amazon Bedrock (Nova Lite) | `amazon.nova-lite-v1:0` | app/api/chat/conversation/route.ts | Used (fallback when quota=0) |
88
| Amazon Bedrock (Nova Lite) | `amazon.nova-lite-v1:0` | app/api/chat/generate-words/route.ts | Used (fallback when quota=0) |
99
| Amazon Bedrock (Nova Lite) | `amazon.nova-lite-v1:0` | app/api/report/summary/route.ts | Used (fallback when quota=0) |
10-
| Amazon Bedrock (Command R+) | `cohere.command-r-plus-v1:0` | app/api/report/clinical/route.ts | Used (fallback when quota=0) |
10+
| Amazon Bedrock (Nova Pro) | `amazon.nova-pro-v1:0` | app/api/report/clinical/route.ts | Active (hybrid template + AI insights) |
1111
| Amazon Polly | Neural TTS (Joanna) | app/api/tts/route.ts | Active, working |
1212
| ONNX Runtime (on-device) | 4 models in `public/models/` | app/lib/inference/ (13 files), workers/inference.worker.ts | Active, working |
1313

@@ -36,6 +36,6 @@
3636
1. **AI voice conversation with child** (Step 7 screening + kid chat) — app/api/chat/conversation/route.ts — Nova Lite generates adaptive multi-turn questions across social/cognitive/language/motor domains
3737
2. **Dynamic word/sentence generation** (speech stages) — app/api/chat/generate-words/route.ts — Nova Lite generates age-appropriate vocabulary
3838
3. **Parent-friendly summary** (end of screening) — app/api/report/summary/route.ts — Nova Lite translates biomarker scores to plain language
39-
4. **DSM-5 clinical report** (end of screening) — app/api/report/clinical/route.ts — Command R+ generates full Criterion A/B/Motor/Recommendations report
39+
4. **DSM-5 clinical report** (end of screening) — app/api/report/clinical/route.ts — Nova Pro returns structured JSON insights merged with deterministic template (hybrid approach, ~85% fewer tokens)
4040

4141
All 4 Bedrock routes have comprehensive mock fallbacks, so the app is fully functional even with your current quota=0 situation. The on-device ONNX inference (the core screening AI) needs zero cloud — it runs entirely in the browser.

docs/DOCS.md

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
AutiSense is a Next.js 16 web application that provides AI-powered autism screening for children. It combines:
3131

3232
- **Edge AI** — Real-time ONNX inference (YOLO pose detection + TCN behavior classification + FER+ emotion analysis) running entirely in the browser via Web Workers
33-
- **Generative AI** — Amazon Bedrock (Nova Lite + Cohere Command R+) for generating DSM-5 aligned clinical reports from biomarker data
33+
- **Generative AI** — Amazon Bedrock (Nova Lite + Nova Pro) for generating DSM-5 aligned clinical reports from biomarker data
3434
- **Offline-first data** — IndexedDB (Dexie.js) for local storage with DynamoDB sync when online
3535
- **Adaptive therapy** — 7 post-diagnosis games with dynamic difficulty adjustment
3636

@@ -59,7 +59,7 @@ Browser (Client)
5959
Server (Amplify SSR / Lambda)
6060
├── POST /api/chat/conversation → Amazon Bedrock Nova Lite (voice agent)
6161
├── POST /api/report/summary → Amazon Bedrock Nova Lite
62-
├── POST /api/report/clinical → Amazon Bedrock Cohere Command R+
62+
├── POST /api/report/clinical → Amazon Bedrock Nova Pro (hybrid template + AI insights)
6363
├── POST /api/report/pdf → pdf-lib PDF generation
6464
├── POST /api/tts → Amazon Polly
6565
├── GET/POST /api/auth/* → Google OAuth + DynamoDB sessions
@@ -132,7 +132,7 @@ AutiSense_2/
132132
│ │ ├── nearby/route.ts — Overpass API proxy for nearby institutes
133133
│ │ ├── report/
134134
│ │ │ ├── summary/route.ts — Bedrock Nova Lite session summary
135-
│ │ │ ├── clinical/route.ts — Bedrock Command R+ clinical report
135+
│ │ │ ├── clinical/route.ts — Bedrock Nova Pro clinical report
136136
│ │ │ ├── pdf/route.ts — PDF generation via pdf-lib
137137
│ │ │ └── weekly/route.ts — Weekly progress report generation
138138
│ │ ├── sync/route.ts — Upload session + biomarkers to DynamoDB
@@ -312,7 +312,7 @@ AutiSense_2/
312312
| S3 bucket + ONNX models | Deployed | 4 models, ~47MB total |
313313
| IAM policies (3) | Created | Bedrock, Polly, DynamoDB+S3 |
314314
| IAM user + Amplify role | Created | For local dev + production |
315-
| Bedrock model access | Auto-enabled | Nova Lite + Command R+ |
315+
| Bedrock model access | Auto-enabled | Nova Lite + Nova Pro |
316316
| Budget alarm ($10/mo) | Active | Email alerts at 80% and 100% |
317317
| Amplify hosting | Live | Auto-deploy from GitHub main |
318318

@@ -374,7 +374,7 @@ All inference runs client-side in a Web Worker via ONNX Runtime Web (WebGPU or W
374374
| Service | Usage | API Endpoint |
375375
|---------|-------|-------------|
376376
| **Bedrock** (Nova Lite) | Parent-friendly session summaries | `POST /api/report/summary` |
377-
| **Bedrock** (Command R+) | DSM-5 aligned clinical reports | `POST /api/report/clinical` |
377+
| **Bedrock** (Nova Pro) | DSM-5 aligned clinical reports | `POST /api/report/clinical` |
378378
| **Polly** | Neural TTS voice prompts (Joanna) | `POST /api/tts` |
379379
| **DynamoDB** | User accounts, auth sessions, biomarkers, child profiles, feed posts | Via AWS SDK v3 |
380380
| **S3** | ONNX model file hosting (presigned URLs) | Via `@aws-sdk/s3-request-presigner` |
@@ -463,7 +463,7 @@ Difficulty engine (`app/lib/games/difficultyEngine.ts`) auto-adjusts based on re
463463
| POST | `/api/auth/logout` | Public | Delete session |
464464
| POST | `/api/chat/conversation` | Public | Dynamic voice agent conversation via Bedrock Nova Lite |
465465
| POST | `/api/report/summary` | Public | Generate summary via Bedrock Nova Lite |
466-
| POST | `/api/report/clinical` | Public | Generate clinical report via Bedrock Command R+ |
466+
| POST | `/api/report/clinical` | Public | Generate clinical report via Bedrock Nova Pro |
467467
| POST | `/api/report/pdf` | Public | Generate downloadable PDF |
468468
| POST | `/api/tts` | Public | Text-to-speech via Amazon Polly |
469469
| POST | `/api/chat/generate-words` | Public | Generate age-appropriate words/sentences via Bedrock |
@@ -547,7 +547,7 @@ npx playwright test # Run all 30 tests
547547

548548
### Phase 3 — Bedrock Reports (Complete)
549549
- [x] Summary API (Nova Lite) with mock fallback
550-
- [x] Clinical API (Command R+) with DSM-5 section extraction
550+
- [x] Clinical API (Nova Pro) with DSM-5 section extraction
551551
- [x] PDF generation with pdf-lib
552552
- [x] Amazon Polly TTS API
553553
- [x] Report page with dual report types + PDF download
@@ -629,7 +629,7 @@ npx playwright test # Run all 30 tests
629629
**Added:**
630630
- Complete 12-step autism screening intake flow
631631
- Real-time ONNX behavioral video analysis (YOLO + TCN + FER+)
632-
- Amazon Bedrock AI report generation (Nova Lite summaries + Command R+ clinical reports)
632+
- Amazon Bedrock AI report generation (Nova Lite summaries + Nova Pro clinical reports)
633633
- PDF report download with scores and clinical text
634634
- Amazon Polly text-to-speech for child-facing prompts
635635
- Google OAuth 2.0 authentication with DynamoDB sessions
@@ -1005,6 +1005,24 @@ A complete kids-facing dashboard with bottom tab navigation, daily games, AI cha
10051005
**Files modified:** 11 files across components, games, pages, DB layer, and types.
10061006
**Testing:** TypeScript clean. ESLint clean. Build clean.
10071007

1008+
### v2.4.0 — 2026-03-06 (Bedrock Optimization — Hybrid Clinical Reports)
1009+
1010+
**Clinical Report Optimization:**
1011+
- Replaced full-prose LLM prompt with hybrid template + AI insights approach
1012+
- Deterministic template (`buildMockReport`) generates the complete report with scores, thresholds, flags, and disclaimers
1013+
- Nova Pro now returns only a small structured JSON with clinical interpretations (severity level, DSM-5 criterion mappings, clinical impression, priority recommendations, differential considerations)
1014+
- `mergeAiInsights()` enriches the template sections with AI clinical depth
1015+
- ~85% token reduction per clinical report (~400 tokens vs ~2700 previously)
1016+
- `maxTokens` reduced from 2048 to 512
1017+
- Fallback preserved: if Bedrock fails or JSON unparseable, returns template-only report
1018+
1019+
**Bedrock Model Updates:**
1020+
- Replaced Cohere Command R+ with Amazon Nova Pro for clinical reports (v2.3.0)
1021+
- Fixed `maxNewTokens``maxTokens` in generate-words route (v2.3.0)
1022+
- Updated all documentation references from Cohere to Nova Pro
1023+
1024+
**Files modified:** `app/api/report/clinical/route.ts`, `docs/DOCS.md`, `docs/SETUP_GUIDE.md`, `docs/Amazon_usage.md`
1025+
10081026
### v2.2.0 — 2026-03-06 (Game Staging, Nav Fix, Dark Mode, Feed Toggle)
10091027

10101028
**Navigation & Layout (Phase 1):**

docs/SETUP_GUIDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
|---------|----------|--------|
2323
| DynamoDB | 7 tables (`autisense-*`) | ap-south-1 |
2424
| S3 | `autisense-models-762099405044` (4 ONNX models) | ap-south-1 |
25-
| Bedrock | Nova Lite + Command R+ | us-east-1 |
25+
| Bedrock | Nova Lite + Nova Pro | us-east-1 |
2626
| Polly | Joanna (Neural voice) | ap-south-1 |
2727
| Amplify | WEB_COMPUTE (Next.js SSR) | ap-south-1 |
2828
| IAM | `autisense-app` user + `AutiSenseAmplifyRole` | Global |

0 commit comments

Comments
 (0)