apple-dev-mcp
Version:
Complete Apple development guidance: Human Interface Guidelines (design) + Technical Documentation for iOS, macOS, watchOS, tvOS, and visionOS
320 lines (317 loc) ⢠13.9 kB
JavaScript
/**
* ContentQualityValidatorService
*
* Validates content quality and monitors extraction success rates
* to ensure compliance with the 95%+ real content SLA.
*/
export class ContentQualityValidatorService {
validatedSections = new Map();
extractionHistory = [];
thresholds;
constructor(customThresholds) {
this.thresholds = {
minQualityScore: 0.5,
minConfidence: 0.4,
minContentLength: 200,
maxFallbackRate: 5, // 5% max fallback usage for 95% SLA
minStructureScore: 0.2,
minAppleTermsScore: 0.1,
...customThresholds
};
}
/**
* Validate content quality against established thresholds
*/
async validateContent(content, section) {
const sectionId = section.id;
const issues = [];
const recommendations = [];
// Basic content validation
if (!content || content.trim().length === 0) {
issues.push('Content is empty or null');
return this.createValidationResult(false, 0, 0, issues, recommendations, sectionId);
}
// Calculate content metrics if not provided
let quality;
if (section.quality) {
quality = section.quality;
}
else {
// Basic quality calculation for content without metrics
quality = this.calculateBasicQuality(content, section);
}
// Validate against thresholds
const validationChecks = [
this.validateQualityScore(quality, issues, recommendations),
this.validateConfidence(quality, issues, recommendations),
this.validateContentLength(quality, issues, recommendations),
this.validateStructure(quality, issues, recommendations),
this.validateAppleTerms(quality, issues, recommendations),
this.validateFallbackContent(quality, issues, recommendations)
];
const passedChecks = validationChecks.filter(check => check).length;
const isValid = passedChecks >= 4; // At least 4 out of 6 checks must pass
const validationScore = passedChecks / validationChecks.length;
const result = this.createValidationResult(isValid, validationScore, quality.confidence, issues, recommendations, sectionId);
// Store validation result
this.validatedSections.set(sectionId, result);
return result;
}
/**
* Calculate quality score for content without existing metrics
*/
calculateQualityScore(content) {
if (!content)
return 0;
let score = 0;
const contentLower = content.toLowerCase();
// Length score (0-0.3)
score += Math.min(content.length / 2000, 1) * 0.3;
// Apple terms score (0-0.3)
const appleTerms = ['apple', 'ios', 'macos', 'interface', 'design', 'guidelines'];
const foundTerms = appleTerms.filter(term => contentLower.includes(term)).length;
score += (foundTerms / appleTerms.length) * 0.3;
// Structure score (0-0.2)
const headings = (content.match(/^#+\s/gm) || []).length;
score += Math.min(headings / 5, 1) * 0.2;
// Content richness (0-0.2)
if (content.includes('```') || content.includes('`'))
score += 0.1; // Has code
if (content.includes('![') || content.includes('<img'))
score += 0.1; // Has images
return Math.min(score, 1.0);
}
/**
* Check if content meets high quality standards
*/
isHighQualityContent(metrics) {
return (metrics.score >= this.thresholds.minQualityScore &&
metrics.confidence >= this.thresholds.minConfidence &&
metrics.length >= this.thresholds.minContentLength &&
!metrics.isFallbackContent &&
metrics.structureScore >= this.thresholds.minStructureScore);
}
/**
* Record extraction for monitoring
*/
recordExtraction(section, quality) {
this.extractionHistory.push({
section,
quality,
timestamp: new Date()
});
// Keep only last 1000 extractions for memory management
if (this.extractionHistory.length > 1000) {
this.extractionHistory = this.extractionHistory.slice(-1000);
}
}
/**
* Get current extraction statistics
*/
getStatistics() {
if (this.extractionHistory.length === 0) {
return {
totalSections: 0,
successfulExtractions: 0,
fallbackUsage: 0,
averageQuality: 0,
averageConfidence: 0,
extractionSuccessRate: 0
};
}
const totalSections = this.extractionHistory.length;
const fallbackUsage = this.extractionHistory.filter(entry => entry.quality.isFallbackContent || entry.quality.extractionMethod === 'fallback').length;
const successfulExtractions = totalSections - fallbackUsage;
const extractionSuccessRate = (successfulExtractions / totalSections) * 100;
const avgQuality = this.extractionHistory.reduce((sum, entry) => sum + entry.quality.score, 0) / totalSections;
const avgConfidence = this.extractionHistory.reduce((sum, entry) => sum + entry.quality.confidence, 0) / totalSections;
return {
totalSections,
successfulExtractions,
fallbackUsage,
averageQuality: avgQuality,
averageConfidence: avgConfidence,
extractionSuccessRate
};
}
/**
* Generate comprehensive quality report
*/
generateReport() {
const stats = this.getStatistics();
const validationResults = Array.from(this.validatedSections.values());
const passedValidation = validationResults.filter(r => r.isValid).length;
const totalValidated = validationResults.length;
const overallScore = totalValidated > 0 ? passedValidation / totalValidated : 0;
const slaCompliance = stats.extractionSuccessRate >= 95;
const report = this.generateQualityReport(stats, {
totalValidated,
passedValidation,
failedValidation: totalValidated - passedValidation,
overallScore,
slaCompliance
});
return this.formatQualityReport(report);
}
// Private helper methods
calculateBasicQuality(content, _section) {
const score = this.calculateQualityScore(content);
const isFallbackContent = this.detectFallbackContent(content);
return {
score,
length: content.length,
structureScore: Math.min((content.match(/^#+\s/gm) || []).length / 5, 1),
appleTermsScore: score * 0.3, // Approximate
codeExamplesCount: (content.match(/```/g) || []).length / 2,
imageReferencesCount: (content.match(/!\[.*?\]\(.*?\)/g) || []).length,
headingCount: (content.match(/^#+\s/gm) || []).length,
isFallbackContent,
extractionMethod: isFallbackContent ? 'fallback' : 'crawlee',
confidence: isFallbackContent ? 0.1 : score
};
}
detectFallbackContent(content) {
const fallbackIndicators = [
'this page requires javascript',
'single page application',
'content extraction failed',
'please visit the official documentation',
'fallback information'
];
const contentLower = content.toLowerCase();
return fallbackIndicators.some(indicator => contentLower.includes(indicator));
}
validateQualityScore(quality, issues, recommendations) {
if (quality.score < this.thresholds.minQualityScore) {
issues.push(`Quality score too low: ${quality.score.toFixed(3)} (min: ${this.thresholds.minQualityScore})`);
recommendations.push('Review content extraction patterns and selectors');
return false;
}
return true;
}
validateConfidence(quality, issues, recommendations) {
if (quality.confidence < this.thresholds.minConfidence) {
issues.push(`Confidence too low: ${quality.confidence.toFixed(3)} (min: ${this.thresholds.minConfidence})`);
recommendations.push('Improve extraction accuracy or review source content');
return false;
}
return true;
}
validateContentLength(quality, issues, recommendations) {
if (quality.length < this.thresholds.minContentLength) {
issues.push(`Content too short: ${quality.length} characters (min: ${this.thresholds.minContentLength})`);
recommendations.push('Verify complete content extraction or check source availability');
return false;
}
return true;
}
validateStructure(quality, issues, recommendations) {
if (quality.structureScore < this.thresholds.minStructureScore) {
issues.push(`Poor content structure: ${quality.structureScore.toFixed(3)} (min: ${this.thresholds.minStructureScore})`);
recommendations.push('Review heading extraction and content organization');
return false;
}
return true;
}
validateAppleTerms(quality, issues, recommendations) {
if (quality.appleTermsScore < this.thresholds.minAppleTermsScore) {
issues.push(`Insufficient Apple-specific content: ${quality.appleTermsScore.toFixed(3)} (min: ${this.thresholds.minAppleTermsScore})`);
recommendations.push('Verify extraction from correct Apple HIG pages');
return false;
}
return true;
}
validateFallbackContent(quality, issues, recommendations) {
if (quality.isFallbackContent) {
issues.push('Content appears to be fallback/placeholder content');
recommendations.push('Enable JavaScript execution and review SPA loading');
return false;
}
return true;
}
createValidationResult(isValid, score, confidence, issues, recommendations, _sectionId) {
return {
isValid,
score,
confidence,
issues: [...issues],
recommendations: [...recommendations]
};
}
generateQualityReport(stats, summary) {
const issues = {
highPriority: [],
mediumPriority: [],
lowPriority: []
};
const recommendations = [];
// Analyze SLA compliance
if (!summary.slaCompliance) {
issues.highPriority.push(`SLA NOT MET: Extraction success rate is ${stats.extractionSuccessRate.toFixed(1)}% (target: ā„95%)`);
recommendations.push('Review Crawlee configuration and Apple website changes');
}
// Analyze quality scores
if (stats.averageQuality < 0.7) {
issues.mediumPriority.push(`Low average quality score: ${stats.averageQuality.toFixed(3)}`);
recommendations.push('Optimize content extraction selectors');
}
// Analyze fallback usage
const fallbackRate = (stats.fallbackUsage / stats.totalSections) * 100;
if (fallbackRate > this.thresholds.maxFallbackRate) {
issues.highPriority.push(`High fallback usage: ${fallbackRate.toFixed(1)}% (max: ${this.thresholds.maxFallbackRate}%)`);
recommendations.push('Investigate JavaScript execution and page loading issues');
}
return {
summary,
issues,
recommendations,
detailedMetrics: stats
};
}
formatQualityReport(report) {
const { summary, issues, recommendations, detailedMetrics } = report;
let output = `
š Content Quality Validation Report
====================================
šÆ SLA Compliance: ${summary.slaCompliance ? 'ā
ACHIEVED' : 'ā NOT MET'}
š Extraction Success Rate: ${detailedMetrics.extractionSuccessRate.toFixed(1)}%
ā Average Quality Score: ${detailedMetrics.averageQuality.toFixed(3)}
š Average Confidence: ${detailedMetrics.averageConfidence.toFixed(3)}
š Total Sections: ${detailedMetrics.totalSections}
š Fallback Usage: ${detailedMetrics.fallbackUsage} (${(detailedMetrics.fallbackUsage / detailedMetrics.totalSections * 100).toFixed(1)}%)
š Validation Summary:
⢠Total Validated: ${summary.totalValidated}
⢠Passed Validation: ${summary.passedValidation}
⢠Failed Validation: ${summary.failedValidation}
⢠Overall Validation Score: ${(summary.overallScore * 100).toFixed(1)}%
`;
if (issues.highPriority.length > 0) {
output += `šØ High Priority Issues:\n`;
issues.highPriority.forEach(issue => output += ` ⢠${issue}\n`);
output += '\n';
}
if (issues.mediumPriority.length > 0) {
output += `ā ļø Medium Priority Issues:\n`;
issues.mediumPriority.forEach(issue => output += ` ⢠${issue}\n`);
output += '\n';
}
if (issues.lowPriority.length > 0) {
output += `ā¹ļø Low Priority Issues:\n`;
issues.lowPriority.forEach(issue => output += ` ⢠${issue}\n`);
output += '\n';
}
if (recommendations.length > 0) {
output += `š” Recommendations:\n`;
recommendations.forEach(rec => output += ` ⢠${rec}\n`);
output += '\n';
}
if (summary.slaCompliance) {
output += `ā
Congratulations! The system is meeting the 95%+ real content SLA target.\n`;
}
else {
output += `š§ Action Required: System is not meeting the 95%+ real content SLA target.\n`;
}
return output;
}
}
//# sourceMappingURL=content-quality-validator.service.js.map