- Re-architect VoiceSession into full-bleed studio cockpit with overlay slide-over companion drawer - Eliminate width squishing defect on LiveInsightPanel and add native dark/light theme support - Integrate authentic Google Gemini 3.8 Flash TTS with Fenrir persona and fallback audio pipeline - Implement comprehensive Playwright E2E visual regression suite across Desktop, iPad, and iPhone (21 tests, 45 snapshots) - Migrate core application to Svelte 5 runes (, , ) with reactive stores - Maintain .NET 8 Azure Functions serverless endpoints and clean build hygiene
120 lines
3.9 KiB
TypeScript
120 lines
3.9 KiB
TypeScript
/**
|
|
* Scenario Validation Helper Script
|
|
*
|
|
* Verifies that all built-in and custom CBT scenarios conform to schema rules,
|
|
* category invariants, and clinical requirements.
|
|
*/
|
|
|
|
import { SCENARIOS, CBT_GLOSSARY } from '../constants';
|
|
import { Scenario } from '../types';
|
|
|
|
const VALID_CATEGORIES: Scenario['category'][] = ['Social Anxiety', 'Workplace', 'Relationships', 'Self-Talk'];
|
|
const VALID_DIFFICULTIES: Scenario['difficulty'][] = ['Beginner', 'Intermediate', 'Advanced'];
|
|
|
|
interface ValidationResult {
|
|
scenarioId: string;
|
|
title: string;
|
|
isValid: boolean;
|
|
errors: string[];
|
|
warnings: string[];
|
|
}
|
|
|
|
export function validateScenario(scenario: Scenario): ValidationResult {
|
|
const errors: string[] = [];
|
|
const warnings: string[] = [];
|
|
|
|
if (!scenario.id || typeof scenario.id !== 'string') {
|
|
errors.push('Missing or invalid "id"');
|
|
}
|
|
|
|
if (!scenario.title || scenario.title.trim().length === 0) {
|
|
errors.push('Missing or empty "title"');
|
|
}
|
|
|
|
if (!scenario.description || scenario.description.trim().length === 0) {
|
|
errors.push('Missing or empty "description"');
|
|
}
|
|
|
|
if (!VALID_CATEGORIES.includes(scenario.category)) {
|
|
errors.push(`Invalid category: "${scenario.category}". Expected one of: ${VALID_CATEGORIES.join(', ')}`);
|
|
}
|
|
|
|
if (!VALID_DIFFICULTIES.includes(scenario.difficulty)) {
|
|
errors.push(`Invalid difficulty: "${scenario.difficulty}". Expected one of: ${VALID_DIFFICULTIES.join(', ')}`);
|
|
}
|
|
|
|
if (!scenario.characterName || scenario.characterName.trim().length === 0) {
|
|
errors.push('Missing or empty "characterName"');
|
|
}
|
|
|
|
if (!scenario.initialMessage || scenario.initialMessage.trim().length === 0) {
|
|
errors.push('Missing or empty "initialMessage"');
|
|
}
|
|
|
|
if (!Array.isArray(scenario.cbtFocus) || scenario.cbtFocus.length === 0) {
|
|
errors.push('cbtFocus must be a non-empty array of strings');
|
|
} else {
|
|
// Check against glossary terms
|
|
const glossaryTerms = CBT_GLOSSARY.map(g => g.term.toLowerCase());
|
|
scenario.cbtFocus.forEach(focus => {
|
|
const match = glossaryTerms.some(term => term === focus.toLowerCase() || focus.toLowerCase().includes(term));
|
|
if (!match) {
|
|
warnings.push(`CBT Focus item "${focus}" does not directly match any term in the CBT_GLOSSARY.`);
|
|
}
|
|
});
|
|
}
|
|
|
|
if (!scenario.systemInstruction || scenario.systemInstruction.trim().length === 0) {
|
|
errors.push('Missing or empty "systemInstruction"');
|
|
} else {
|
|
if (!scenario.systemInstruction.includes('INTERNAL CBT LOGIC') && !scenario.systemInstruction.includes('LOGIC:')) {
|
|
warnings.push('systemInstruction does not contain explicit "INTERNAL CBT LOGIC" branching rules.');
|
|
}
|
|
}
|
|
|
|
return {
|
|
scenarioId: scenario.id,
|
|
title: scenario.title,
|
|
isValid: errors.length === 0,
|
|
errors,
|
|
warnings,
|
|
};
|
|
}
|
|
|
|
function run() {
|
|
console.log('🔍 Validating Cognitive Clarity Scenario Catalog...\n');
|
|
let totalErrors = 0;
|
|
let totalWarnings = 0;
|
|
|
|
SCENARIOS.forEach(scenario => {
|
|
const result = validateScenario(scenario);
|
|
const statusIcon = result.isValid ? '✅' : '❌';
|
|
console.log(`${statusIcon} [${scenario.difficulty.toUpperCase()}] ${scenario.title} (${scenario.id})`);
|
|
|
|
if (result.errors.length > 0) {
|
|
console.log(' Errors:');
|
|
result.errors.forEach(e => console.log(` - ❌ ${e}`));
|
|
totalErrors += result.errors.length;
|
|
}
|
|
|
|
if (result.warnings.length > 0) {
|
|
console.log(' Warnings:');
|
|
result.warnings.forEach(w => console.log(` - ⚠️ ${w}`));
|
|
totalWarnings += result.warnings.length;
|
|
}
|
|
});
|
|
|
|
console.log('\n========================================');
|
|
console.log(`Summary: ${SCENARIOS.length} scenarios analyzed.`);
|
|
console.log(`Errors: ${totalErrors}, Warnings: ${totalWarnings}`);
|
|
console.log('========================================\n');
|
|
|
|
if (totalErrors > 0) {
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
if (process.argv[1]?.endsWith('validate-scenarios.ts') || process.argv[1]?.endsWith('validate-scenarios.js')) {
|
|
run();
|
|
}
|