/** * Canonical Market Data Schema Definition * Version: 1.0 * * This schema defines the expected structure for healthcare market research data * across all system components (OpenClaw Agent, n8n Workflow, WordPress Dashboard) */ export const SCHEMA_VERSION = '1.0'; /** * Schema version history */ export const SCHEMA_VERSIONS = [ { version: '1.0', releaseDate: '2024-01-20', changes: ['Initial schema definition with canonical structure'], backwardCompatible: true } ]; /** * Canonical Market Data Schema * All components must conform to this structure */ export const MARKET_DATA_SCHEMA = { // Schema metadata schemaVersion: SCHEMA_VERSION, // Required top-level fields required: [ 'marketTitle', 'executiveOverview', 'pastYear_2023', 'currentYear_2025', 'forecastYear_2033', 'global_cagr_Forecast', 'marketSegments', 'marketDrivers', 'competitiveLandscape' ], // Field type definitions fields: { marketTitle: { type: 'string', required: true }, executiveOverview: { type: 'string', required: true }, pastYear_2023: { type: 'number', required: true }, currentYear_2025: { type: 'number', required: true }, forecastYear_2033: { type: 'number', required: true }, global_cagr_Forecast: { type: 'number', required: true }, marketSegments: { type: 'array', required: true, items: { segmentCategory: { type: 'string', required: true }, segmentName: { type: 'string', required: true }, segmentName_cagr_Forecast: { type: 'number', required: false }, subSegments: { type: 'array', required: true, items: { subSegmentName: { type: 'string', required: true }, segment_marketShare_2023: { type: 'number', required: false }, sub_segment_marketShare_2023: { type: 'number', required: false }, segment_marketShare_2025: { type: 'number', required: false }, sub_segment_marketShare_2025: { type: 'number', required: false }, segment_marketShare_2033: { type: 'number', required: false }, sub_segment_marketShare_2033: { type: 'number', required: false }, sub_segmentName_cagr_Forecast: { type: 'number', required: false } } } } }, marketDrivers: { type: 'array', required: true }, emergingTrends: { type: 'array', required: false }, insights: { type: 'object', required: false, fields: { largestSegment2025: { type: 'string', required: false }, fastestGrowingSegment: { type: 'string', required: false }, keyOpportunities: { type: 'array', required: false }, majorChallenges: { type: 'array', required: false } } }, competitiveLandscape: { type: 'array', required: true, items: { company: { type: 'string', required: true }, player_marketShare_2025: { type: 'number', required: true }, positioning: { type: 'string', required: false } } }, regulatoryEnvironment: { type: 'string', required: false }, geographicAnalysis: { type: 'string', required: false }, futureOutlook: { type: 'string', required: false }, strategicRecommendations: { type: 'array', required: false } } }; /** * Schema Validator Class * Validates market data against the canonical schema */ export class SchemaValidator { constructor(schema = MARKET_DATA_SCHEMA) { this.schema = schema; } /** * Validate data against schema * @param {Object} data - Data to validate * @returns {Object} - { valid: boolean, errors: string[], warnings: string[] } */ validate(data) { const errors = []; const warnings = []; if (!data || typeof data !== 'object') { errors.push('Data must be an object'); return { valid: false, errors, warnings }; } // Check required top-level fields for (const field of this.schema.required) { if (!(field in data) || data[field] === null || data[field] === undefined) { errors.push(`Missing required field: ${field}`); } } // Validate field types this.validateFields(data, this.schema.fields, '', errors, warnings); // Validate marketSegments structure if (Array.isArray(data.marketSegments)) { data.marketSegments.forEach((segment, idx) => { if (!segment.segmentName) { errors.push(`marketSegments[${idx}]: missing segmentName`); } if (!Array.isArray(segment.subSegments)) { errors.push(`marketSegments[${idx}]: subSegments must be an array`); } }); } // Validate competitiveLandscape if (Array.isArray(data.competitiveLandscape)) { if (data.competitiveLandscape.length < 5) { warnings.push('competitiveLandscape should include at least 5 companies'); } data.competitiveLandscape.forEach((company, idx) => { if (!company.company) { errors.push(`competitiveLandscape[${idx}]: missing company name`); } if (typeof company.player_marketShare_2025 !== 'number') { errors.push(`competitiveLandscape[${idx}]: player_marketShare_2025 must be a number`); } }); } return { valid: errors.length === 0, errors, warnings }; } /** * Validate individual fields recursively */ validateFields(data, fieldDefs, path, errors, warnings) { for (const [fieldName, fieldDef] of Object.entries(fieldDefs)) { const fullPath = path ? `${path}.${fieldName}` : fieldName; const value = data[fieldName]; // Check if required field is missing if (fieldDef.required && (value === null || value === undefined)) { errors.push(`Missing required field: ${fullPath}`); continue; } // Skip validation if field is optional and not present if (!fieldDef.required && (value === null || value === undefined)) { continue; } // Validate type if (fieldDef.type === 'array') { if (!Array.isArray(value)) { errors.push(`${fullPath} must be an array`); } else if (fieldDef.items && value.length > 0) { // Validate array items value.forEach((item, idx) => { if (typeof fieldDef.items === 'object' && !Array.isArray(fieldDef.items)) { this.validateFields(item, fieldDef.items, `${fullPath}[${idx}]`, errors, warnings); } }); } } else if (fieldDef.type === 'object') { if (typeof value !== 'object' || Array.isArray(value)) { errors.push(`${fullPath} must be an object`); } else if (fieldDef.fields) { this.validateFields(value, fieldDef.fields, fullPath, errors, warnings); } } else if (fieldDef.type === 'string') { if (typeof value !== 'string') { errors.push(`${fullPath} must be a string`); } } else if (fieldDef.type === 'number') { if (typeof value !== 'number' || isNaN(value)) { errors.push(`${fullPath} must be a number`); } } else if (fieldDef.type === 'boolean') { if (typeof value !== 'boolean') { errors.push(`${fullPath} must be a boolean`); } } } } /** * Get schema version */ getVersion() { return this.schema.schemaVersion; } /** * Get schema documentation */ getDocumentation() { return { version: this.schema.schemaVersion, required: this.schema.required, fields: this.schema.fields, versions: SCHEMA_VERSIONS }; } } /** * Validate market data (convenience function) * @param {Object} data - Data to validate * @returns {Object} - Validation result */ export function validateMarketData(data) { const validator = new SchemaValidator(); return validator.validate(data); } /** * Error types for schema validation */ export class ValidationError extends Error { constructor(message, errors = []) { super(message); this.name = 'ValidationError'; this.errors = errors; } } export class MissingDataError extends Error { constructor(message, missingFields = []) { super(message); this.name = 'MissingDataError'; this.missingFields = missingFields; } } export class TransformationError extends Error { constructor(message, details = {}) { super(message); this.name = 'TransformationError'; this.details = details; } }