SOURCE

console 命令行工具 X clear

                    
>
console
document.addEventListener('DOMContentLoaded', function() {

    // --- Historical Data (Hardcoded for Demo) ---
    // Source [3] provides 2024 revenue. Other values are plausible inventions.
    const salesData = [
        { year: 2020, volume: 1000, revenue: 500000 },
        { year: 2021, volume: 1200, revenue: 650000 },
        { year: 2022, volume: 1150, revenue: 680000 },
        { year: 2023, volume: 1300, revenue: 750000 },
        { year: 2024, volume: 1100, revenue: 678900 } // Using Source [3] revenue, invented plausible volume
    ];

    // --- Prepare Data for Charts ---
    const years = salesData.map(d => d.year);
    const volumes = salesData.map(d => d.volume);
    const revenues = salesData.map(d => d.revenue);

    // --- Configuration for Charts ---
    const chartOptions = {
        responsive: true,
        maintainAspectRatio: false,
        scales: {
            y: {
                beginAtZero: true
            }
        },
        plugins: {
            tooltip: {
                mode: 'index',
                intersect: false,
            }
        }
    };

    // --- 1. Interactive Dashboard ---
    // Sales Volume Chart
    const volCtx = document.getElementById('salesVolumeChart').getContext('2d');
    new Chart(volCtx, {
        type: 'line',
         {
            labels: years,
            datasets: [{
                label: 'Sales Volume',
                 volumes,
                borderColor: 'rgb(75, 192, 192)',
                tension: 0.1
            }]
        },
        options: chartOptions
    });

    // Sales Revenue Chart
    const revCtx = document.getElementById('salesRevenueChart').getContext('2d');
    new Chart(revCtx, {
        type: 'line',
         {
            labels: years,
            datasets: [{
                label: 'Sales Revenue',
                 revenues,
                borderColor: 'rgb(255, 99, 132)',
                tension: 0.1
            }]
        },
        options: chartOptions
    });

    // Highlight Anomalies (Example: Check 2024 revenue drop)
    const anomalyDisplay = document.getElementById('anomalyDisplay');
    const lastYearData = salesData[salesData.length - 1];
    const secondLastYearData = salesData[salesData.length - 2];
    let anomaliesFound = false;

    if (lastYearData && secondLastYearData && lastYearData.revenue < secondLastYearData.revenue * 0.9) { // If 2024 revenue is < 90% of 2023
         anomalyDisplay.innerHTML += `<p><strong>Anomaly Detected:</strong> Revenue in ${lastYearData.year} (${lastYearData.revenue.toLocaleString()}) shows a significant drop compared to ${secondLastYearData.year} (${secondLastYearData.revenue.toLocaleString()}).</p>`;
         anomaliesFound = true;
    }
    // Add more anomaly checks if needed (e.g., based on ratio, standard deviation)

    if (anomaliesFound) {
        anomalyDisplay.classList.remove('d-none');
    }

    // --- 2. Trend Prediction (Linear Regression) ---

    // Simple Linear Regression Function
    function linearRegression(x, y) {
        const n = x.length;
        if (n < 2) return null; // Need at least two points

        let sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
        for (let i = 0; i < n; i++) {
            sumX += x[i];
            sumY += y[i];
            sumXY += x[i] * y[i];
            sumX2 += x[i] * x[i];
        }

        const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
        const intercept = (sumY - slope * sumX) / n;

        // Calculate residuals and standard deviation of residuals for CI estimate
        let sumSqResiduals = 0;
        const predictions = [];
        for (let i = 0; i < n; i++) {
            const predictedY = slope * x[i] + intercept;
            predictions.push(predictedY);
            sumSqResiduals += Math.pow(y[i] - predictedY, 2);
        }
         // Standard Error of the Estimate (simplified view of prediction error)
        const stdErrEstimate = n > 2 ? Math.sqrt(sumSqResiduals / (n - 2)) : Math.sqrt(sumSqResiduals / n) ; // Use n-2 degrees of freedom if n>2

        return { slope, intercept, stdErrEstimate, predictions };
    }

    // Prepare data for regression (use indices 0, 1, 2... for x)
    // OPTION: Exclude anomalous data (e.g., 2024 if deemed unreliable)
    const regressionYears = salesData.map((_, index) => index); // Use 0, 1, 2, 3, 4
    const regressionVolumes = volumes; // Use all historical volumes
    const regressionRevenues = revenues; // Use all historical revenues

    // Perform Regression
    const volRegression = linearRegression(regressionYears, regressionVolumes);
    const revRegression = linearRegression(regressionYears, regressionRevenues);

    // Predict for 2025 (index 5)
    const forecastYear = salesData[salesData.length - 1].year + 1; // 2025
    const forecastIndex = regressionYears.length; // 5
    let predictedVolume = null, volLowerCI = null, volUpperCI = null;
    let predictedRevenue = null, revLowerCI = null, revUpperCI = null;

    const forecastDetailsDiv = document.getElementById('forecastDetails');
    let forecastHTML = '';

    if (volRegression) {
        predictedVolume = volRegression.slope * forecastIndex + volRegression.intercept;
        // Simplified Confidence Interval (e.g., +/- 1.96 * StdErr of Estimate - approximate 95%)
        // NOTE: This is a rough estimate and doesn't account for uncertainty in slope/intercept or distance of prediction point.
        const volMarginOfError = 1.96 * volRegression.stdErrEstimate;
        volLowerCI = predictedVolume - volMarginOfError;
        volUpperCI = predictedVolume + volMarginOfError;
        forecastHTML += `<p><strong>Predicted Sales Volume for ${forecastYear}:</strong> ${Math.round(predictedVolume).toLocaleString()}`;
        forecastHTML += ` (Approx. 95% CI: ${Math.round(volLowerCI).toLocaleString()} - ${Math.round(volUpperCI).toLocaleString()})</p>`;
    } else {
         forecastHTML += `<p><strong>Volume Forecast:</strong> Not enough data for prediction.</p>`;
    }


    if (revRegression) {
        predictedRevenue = revRegression.slope * forecastIndex + revRegression.intercept;
        const revMarginOfError = 1.96 * revRegression.stdErrEstimate;
        revLowerCI = predictedRevenue - revMarginOfError;
        revUpperCI = predictedRevenue + revMarginOfError;
        forecastHTML += `<p><strong>Predicted Sales Revenue for ${forecastYear}:</strong> ${Math.round(predictedRevenue).toLocaleString()}`;
         forecastHTML += ` (Approx. 95% CI: ${Math.round(revLowerCI).toLocaleString()} - ${Math.round(revUpperCI).toLocaleString()})</p>`;
    } else {
         forecastHTML += `<p><strong>Revenue Forecast:</strong> Not enough data for prediction.</p>`;
    }

    forecastDetailsDiv.innerHTML = forecastHTML;

    // Forecast Charts (Show historical + forecast point + regression line)
    const forecastYearsLabels = [...years, forecastYear];

    // Volume Forecast Chart
    if (volRegression) {
        const volForecastCtx = document.getElementById('volumeForecastChart').getContext('2d');
        const volRegressionLine = regressionYears.map(x => volRegression.slope * x + volRegression.intercept);

        new Chart(volForecastCtx, {
             type: 'line',
              {
                 labels: forecastYearsLabels,
                 datasets: [
                     { // Historical Data
                        label: 'Historical Volume',
                         [...volumes, NaN], // Add NaN to skip connecting to forecast
                        borderColor: 'rgb(75, 192, 192)',
                        tension: 0.1
                     },
                     { // Forecast Point
                         label: 'Forecast Volume',
                          [...Array(volumes.length).fill(NaN), predictedVolume], // Only show last point
                         borderColor: 'rgb(75, 192, 192)',
                         backgroundColor: 'rgb(75, 192, 192)',
                         pointStyle: 'rectRot',
                         pointRadius: 6,
                         pointHoverRadius: 8
                     },
                      { // Regression Line (optional)
                        label: 'Trend Line',
                         [...volRegressionLine, predictedVolume], // Extend line to forecast
                        borderColor: 'rgba(75, 192, 192, 0.3)',
                        borderDash: [5, 5], // Dashed line
                        tension: 0.1,
                        pointRadius: 0 // No points on the line itself
                     }
                 ]
             },
             options: {...chartOptions,
                 plugins: { ...chartOptions.plugins, title: { display: true, text: 'Volume: Historical + Forecast' } }
             }
         });
    }

     // Revenue Forecast Chart
    if (revRegression) {
        const revForecastCtx = document.getElementById('revenueForecastChart').getContext('2d');
         const revRegressionLine = regressionYears.map(x => revRegression.slope * x + revRegression.intercept);

        new Chart(revForecastCtx, {
             type: 'line',
              {
                 labels: forecastYearsLabels,
                 datasets: [
                     { // Historical Data
                        label: 'Historical Revenue',
                         [...revenues, NaN],
                        borderColor: 'rgb(255, 99, 132)',
                        tension: 0.1
                     },
                     { // Forecast Point
                         label: 'Forecast Revenue',
                          [...Array(revenues.length).fill(NaN), predictedRevenue],
                         borderColor: 'rgb(255, 99, 132)',
                         backgroundColor: 'rgb(255, 99, 132)',
                         pointStyle: 'rectRot',
                         pointRadius: 6,
                         pointHoverRadius: 8
                     },
                     { // Regression Line (optional)
                        label: 'Trend Line',
                         [...revRegressionLine, predictedRevenue],
                        borderColor: 'rgba(255, 99, 132, 0.3)',
                        borderDash: [5, 5],
                        tension: 0.1,
                        pointRadius: 0
                     }
                 ]
             },
             options: {...chartOptions,
                plugins: { ...chartOptions.plugins, title: { display: true, text: 'Revenue: Historical + Forecast' } }
             }
         });
    }


    // --- 3. Data Validation ---
    const validationResultsList = document.getElementById('validationResults');
    validationResultsList.innerHTML = ''; // Clear previous results
    let validationIssues = 0;

    // Calculate average Price/Sales Ratio (Revenue per Unit Volume)
    const ratios = [];
    let sumRatio = 0;
    salesData.forEach(d => {
        if (d.volume > 0) {
            const ratio = d.revenue / d.volume;
            ratios.push({ year: d.year, ratio: ratio });
            sumRatio += ratio;
        } else {
             ratios.push({ year: d.year, ratio: null }); // Avoid division by zero
        }
    });
    const averageRatio = sumRatio / salesData.filter(d => d.volume > 0).length;

    // Check for significant deviations in the ratio
    const ratioThreshold = 0.25; // Example: flag if ratio deviates by more than 25% from average
    ratios.forEach(r => {
        if (r.ratio !== null) {
            const deviation = Math.abs(r.ratio - averageRatio) / averageRatio;
            if (deviation > ratioThreshold) {
                 const listItem = document.createElement('li');
                 listItem.classList.add('list-group-item', 'list-group-item-warning'); // Use warning color
                 listItem.innerHTML = `<strong>Potential Ratio Anomaly (${r.year}):</strong> Revenue/Volume ratio (${r.ratio.toFixed(2)}) deviates significantly from the average (${averageRatio.toFixed(2)}).
                                        <br><small>Recommendation: Verify ${r.year} sales volume and revenue figures for accuracy.</small>`;
                 validationResultsList.appendChild(listItem);
                 validationIssues++;
            }
         } else if (r.ratio === null && salesData.find(d => d.year === r.year).revenue > 0) {
             // Handle case where volume is 0 but revenue exists
             const listItem = document.createElement('li');
             listItem.classList.add('list-group-item', 'list-group-item-danger'); // Use danger color
             listItem.innerHTML = `<strong>Data Inconsistency (${r.year}):</strong> Sales volume is reported as 0, but revenue (${salesData.find(d => d.year === r.year).revenue.toLocaleString()}) exists.
                                    <br><small>Recommendation: Correct the sales volume or revenue figure for ${r.year}.</small>`;
             validationResultsList.appendChild(listItem);
             validationIssues++;
         }
    });

     // Specific check based on source [3]'s 2024 data potentially being low/odd
    const lastData = salesData[salesData.length - 1];
    const lastRatio = ratios[ratios.length - 1].ratio;
    if(lastData.year === 2024 && lastRatio && lastRatio < averageRatio * (1-ratioThreshold)) {
        // Check if already flagged, add specific note if not.
        // This demonstrates how specific known issues can be highlighted.
         if (!validationResultsList.querySelector(`li:contains("Potential Ratio Anomaly (${lastData.year})")`)) {
            const listItem = document.createElement('li');
            listItem.classList.add('list-group-item', 'list-group-item-info'); // Use info color
            listItem.innerHTML = `<strong>Note (${lastData.year}):</strong> The revenue figure of ${lastData.revenue.toLocaleString()} for ${lastData.year} (Source [3]) results in a revenue/volume ratio (${lastRatio.toFixed(2)}) that appears lower than the historical average (${averageRatio.toFixed(2)}).
                                    <br><small>Recommendation: Double-check the ${lastData.year} data source or context.</small>`;
            validationResultsList.appendChild(listItem);
            validationIssues++;
        }
    }


    if (validationIssues === 0) {
        const listItem = document.createElement('li');
        listItem.classList.add('list-group-item', 'list-group-item-success');
        listItem.textContent = 'No significant data validation issues detected based on current rules.';
        validationResultsList.appendChild(listItem);
    }

}); // End DOMContentLoaded
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Mobile Sales Analysis & Forecast</title>
    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
    <!-- Custom CSS -->
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top">
        <div class="container-fluid">
            <a class="navbar-brand" href="#">�� Sales Analyzer</a>
        </div>
    </nav>

    <div class="container mt-5 pt-4">
        <!-- Interactive Dashboard Section -->
        <section id="dashboard" class="mb-4">
            <h2>Sales Dashboard (2020-2024)</h2>
            <div class="card">
                <div class="card-body">
                    <div class="row">
                        <div class="col-md-6 mb-3 mb-md-0">
                            <h5 class="card-title">Sales Volume Trend</h5>
                            <canvas id="salesVolumeChart"></canvas>
                        </div>
                        <div class="col-md-6">
                            <h5 class="card-title">Sales Revenue Trend</h5>
                            <canvas id="salesRevenueChart"></canvas>
                        </div>
                    </div>
                    <div id="anomalyDisplay" class="alert alert-warning mt-3 d-none" role="alert">
                        <!-- Anomaly messages will appear here -->
                    </div>
                </div>
            </div>
        </section>

        <!-- Trend Prediction Section -->
        <section id="prediction" class="mb-4">
            <h2>Trend Prediction (2025 Forecast)</h2>
            <div class="card">
                <div class="card-body">
                     <div class="row">
                        <div class="col-md-6 mb-3 mb-md-0">
                             <h5 class="card-title">Volume Forecast</h5>
                             <canvas id="volumeForecastChart"></canvas>
                        </div>
                         <div class="col-md-6">
                             <h5 class="card-title">Revenue Forecast</h5>
                             <canvas id="revenueForecastChart"></canvas>
                         </div>
                    </div>
                    <div id="forecastDetails" class="mt-3">
                        <!-- Forecast details will appear here -->
                    </div>
                </div>
            </div>
        </section>

        <!-- Data Validation Section -->
        <section id="validation" class="mb-4">
            <h2>Data Validation</h2>
            <div class="card">
                <div class="card-body">
                    <h5 class="card-title">Potential Data Issues</h5>
                    <ul id="validationResults" class="list-group">
                        <!-- Validation results will appear here -->
                    </ul>
                </div>
            </div>
        </section>
    </div>

    <!-- Bootstrap JS Bundle (includes Popper) -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
    <!-- Chart.js -->
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <!-- Custom JS -->
    <script src="script.js"></script>
</body>
</html>
body {
    padding-top: 70px; /* Adjust based on navbar height */
}

.card {
    margin-bottom: 1.5rem;
}

/* Ensure canvas elements are responsive within their containers */
canvas {
    max-width: 100%;
    height: auto !important; /* Override potential inline styles from Chart.js if needed */
}

/* Style for validation list items */
#validationResults .list-group-item-warning {
    border-left: 5px solid #ffc107; /* Yellow border for warnings */
}
#validationResults .list-group-item-danger {
    border-left: 5px solid #dc3545; /* Red border for critical issues */
}
#validationResults .list-group-item-info {
     border-left: 5px solid #0dcaf0; /* Blue border for info */
}