// Trust Score Component class TrustScore { static init() { console.log('TrustScore component initialized'); this.setupScoreCalculation(); this.setupRealTimeUpdates(); } static setupScoreCalculation() { // Trust score calculation based on OneSearch specification this.weights = { recommenderReputation: 0.20, provenance: 0.20, usageDuration: 0.10, conversionRate: 0.10, afterSales: 0.10, locality: 0.05, costSatisfaction: 0.10, qualityReviews: 0.15 }; this.decayFactors = { reviews: 0.002, purchases: 0.001, tickets: 0.003 }; } static calculateScore(productId, evidence) { // Implement the OneSearch trust score formula let scoreRaw = 0; const breakdown = {}; // Calculate subscores with normalization and time decay Object.keys(this.weights).forEach(signal => { let value = evidence[signal] || 0; const weight = this.weights[signal]; // Apply time decay if timestamp is provided if (evidence.timestamp) { const daysSince = this.getDaysSince(evidence.timestamp); const decayRate = this.decayFactors[signal] || 0.001; const decayFactor = Math.exp(-decayRate * daysSince); value *= decayFactor; } // Normalize to 0-1 range value = this.normalizeSignal(signal, value); breakdown[signal] = value; scoreRaw += weight * value; }); // Apply penalties const penalty = this.calculatePenalties(evidence); const scoreNormalized = Math.max(0, Math.min(1, scoreRaw - penalty)); return { score: Math.round(scoreNormalized * 100 * 10) / 10, // Round to 1 decimal breakdown, penalty, evidence: evidence }; } static normalizeSignal(signal, value) { // Normalize different signal types to 0-1 range switch (signal) { case 'qualityReviews': // Reviews are in range [-1, 1], map to [0, 1] return Math.max(0, Math.min(1, (value + 1) / 2)); case 'costSatisfaction': // Cost satisfaction ratio, clamp to [0, 1.2] then normalize return Math.max(0, Math.min(1, value / 1.2)); case 'afterSales': // Convert issue rate to satisfaction score return Math.max(0, Math.min(1, 1 - (value || 0))); case 'usageDuration': // Normalize by expected category lifetime return Math.max(0, Math.min(1, value)); default: // Already normalized to 0-1 return Math.max(0, Math.min(1, value || 0)); } } static calculatePenalties(evidence) { let penalty = 0; // Dispute penalty if (evidence.disputeRate > 0) { penalty += Math.min(0.2, evidence.disputeRate * 0.1); } // Fraud penalty if (evidence.fraudFlags && evidence.fraudFlags.length > 0) { penalty += 0.15; } // Sybil/collusion penalty if (evidence.collusionRisk > 0.7) { penalty += 0.1; } return penalty; } static getDaysSince(timestamp) { const now = new Date(); const date = new Date(timestamp); return Math.floor((now - date) / (1000 * 60 * 60 * 24)); } static setupRealTimeUpdates() { // Setup WebSocket or polling for real-time score updates if (window.OneSearchApp && window.OneSearchApp.isAuthenticated) { this.startScorePolling(); } } static startScorePolling() { // Poll for score updates every 30 seconds setInterval(() => { this.updateVisibleScores(); }, 30000); } static updateVisibleScores() { // Update trust scores visible on the page const scoreElements = document.querySelectorAll('.trust-score'); scoreElements.forEach(element => { const productId = element.dataset.productId; if (productId) { this.updateScoreDisplay(element, productId); } }); } static updateScoreDisplay(element, productId) { // In a real implementation, this would fetch updated scores from the API // For now, we'll simulate small variations const currentScore = parseFloat(element.querySelector('.font-medium').textContent); const variation = (Math.random() - 0.5) * 2; // -1 to +1 const newScore = Math.max(0, Math.min(100, currentScore + variation)); element.querySelector('.font-medium').textContent = Math.round(newScore); // Update progress bar const progressBar = element.querySelector('.trust-score-fill'); if (progressBar) { progressBar.style.width = `${newScore}%`; } } static displayScoreBreakdown(productId, breakdown) { // Create detailed score breakdown modal or panel const modal = this.createBreakdownModal(productId, breakdown); document.body.appendChild(modal); // Show modal with animation setTimeout(() => { modal.classList.add('opacity-100'); }, 10); } static createBreakdownModal(productId, breakdown) { const modal = document.createElement('div'); modal.className = 'fixed inset-0 bg-gray-600 bg-opacity-50 flex items-center justify-center z-50 opacity-0 transition-opacity duration-300'; modal.innerHTML = `
• 23 verified purchase receipts
• 124 peer reviews with maturity status
• 31 QR-based recommendations
• 15 after-sales support tickets