|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export const SCHEMA_VERSION = '1.0';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export const SCHEMA_VERSIONS = [
|
|
|
{
|
|
|
version: '1.0',
|
|
|
releaseDate: '2024-01-20',
|
|
|
changes: ['Initial schema definition with canonical structure'],
|
|
|
backwardCompatible: true
|
|
|
}
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export const MARKET_DATA_SCHEMA = {
|
|
|
|
|
|
schemaVersion: SCHEMA_VERSION,
|
|
|
|
|
|
|
|
|
required: [
|
|
|
'marketTitle',
|
|
|
'executiveOverview',
|
|
|
'pastYear_2023',
|
|
|
'currentYear_2025',
|
|
|
'forecastYear_2033',
|
|
|
'global_cagr_Forecast',
|
|
|
'marketSegments',
|
|
|
'marketDrivers',
|
|
|
'competitiveLandscape'
|
|
|
],
|
|
|
|
|
|
|
|
|
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 }
|
|
|
}
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export class SchemaValidator {
|
|
|
constructor(schema = MARKET_DATA_SCHEMA) {
|
|
|
this.schema = schema;
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
validate(data) {
|
|
|
const errors = [];
|
|
|
const warnings = [];
|
|
|
|
|
|
if (!data || typeof data !== 'object') {
|
|
|
errors.push('Data must be an object');
|
|
|
return { valid: false, errors, warnings };
|
|
|
}
|
|
|
|
|
|
|
|
|
for (const field of this.schema.required) {
|
|
|
if (!(field in data) || data[field] === null || data[field] === undefined) {
|
|
|
errors.push(`Missing required field: ${field}`);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
this.validateFields(data, this.schema.fields, '', errors, warnings);
|
|
|
|
|
|
|
|
|
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`);
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
|
|
|
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
|
|
|
};
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
validateFields(data, fieldDefs, path, errors, warnings) {
|
|
|
for (const [fieldName, fieldDef] of Object.entries(fieldDefs)) {
|
|
|
const fullPath = path ? `${path}.${fieldName}` : fieldName;
|
|
|
const value = data[fieldName];
|
|
|
|
|
|
|
|
|
if (fieldDef.required && (value === null || value === undefined)) {
|
|
|
errors.push(`Missing required field: ${fullPath}`);
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
|
|
|
if (!fieldDef.required && (value === null || value === undefined)) {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
|
|
|
if (fieldDef.type === 'array') {
|
|
|
if (!Array.isArray(value)) {
|
|
|
errors.push(`${fullPath} must be an array`);
|
|
|
} else if (fieldDef.items && value.length > 0) {
|
|
|
|
|
|
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`);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
getVersion() {
|
|
|
return this.schema.schemaVersion;
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
getDocumentation() {
|
|
|
return {
|
|
|
version: this.schema.schemaVersion,
|
|
|
required: this.schema.required,
|
|
|
fields: this.schema.fields,
|
|
|
versions: SCHEMA_VERSIONS
|
|
|
};
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function validateMarketData(data) {
|
|
|
const validator = new SchemaValidator();
|
|
|
return validator.validate(data);
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
}
|
|
|
}
|
|
|
|