Fix mock tests

Former-commit-id: 43b5fc03fd
This commit is contained in:
Tour
2025-12-07 11:08:59 +01:00
parent 89969b8234
commit fb31915b39
8 changed files with 322 additions and 94 deletions

View File

@@ -6,7 +6,6 @@
<title>Auctiora - Intelligence Dashboard</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/plotly.js/3.0.3/plotly.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<style>
:root {
@@ -757,6 +756,12 @@ function showToast(message, type = 'info', duration = 4000) {
// Initialize dashboard
document.addEventListener('DOMContentLoaded', function() {
// Check for missing CDN dependencies
if (typeof Plotly === 'undefined') {
console.warn('Plotly.js not loaded - charts will be disabled');
addLog('Running in offline mode - charts disabled', 'warning');
}
addLog('Dashboard v2.1 initialized with predictive analytics');
startUptimeCounter();
fetchAllData();
@@ -1029,7 +1034,13 @@ function updateCountryChart(data) {
font: { size: 12 }
};
Plotly.newPlot('country-chart', [chartData], layout, {responsive: true});
// Check if Plotly is available before using it
if (typeof Plotly !== 'undefined') {
Plotly.newPlot('country-chart', [chartData], layout, {responsive: true});
} else {
console.warn('Plotly not loaded - chart rendering skipped');
document.getElementById('country-chart').innerHTML = '<div class="text-gray-500 text-sm p-4">Chart library not available (offline mode)</div>';
}
// Update summary stats
document.getElementById('top-country').textContent = countries[0] || '--';
@@ -1061,7 +1072,13 @@ function updateCategoryChart(data) {
font: { size: 12 }
};
Plotly.newPlot('category-chart', [chartData], layout, {responsive: true});
// Check if Plotly is available before using it
if (typeof Plotly !== 'undefined') {
Plotly.newPlot('category-chart', [chartData], layout, {responsive: true});
} else {
console.warn('Plotly not loaded - chart rendering skipped');
document.getElementById('category-chart').innerHTML = '<div class="text-gray-500 text-sm p-4">Chart library not available (offline mode)</div>';
}
// Update summary stats
document.getElementById('top-category').textContent = categories[0] || '--';
@@ -1105,7 +1122,13 @@ function updateTrendChart(data) {
font: { size: 12 }
};
Plotly.newPlot('trend-chart', [trace1, trace2], layout, {responsive: true});
// Check if Plotly is available before using it
if (typeof Plotly !== 'undefined') {
Plotly.newPlot('trend-chart', [trace1, trace2], layout, {responsive: true});
} else {
console.warn('Plotly not loaded - chart rendering skipped');
document.getElementById('trend-chart').innerHTML = '<div class="text-gray-500 text-sm p-4">Chart library not available (offline mode)</div>';
}
}
// Update insights

View File

@@ -10,7 +10,6 @@
<title>Auctiora - Valuation Analytics</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/plotly.js/3.0.3/plotly.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<style>
:root {
@@ -639,6 +638,93 @@ async function recalculate() {
showLoadingState(false);
}
// Helper function: Calculate undervaluation score
// Formula: U_score = (FMV - P_current)/FMV · σ_market · (1 + B_velocity/10) · ln(1 + W_watch/W_bid)
function calculateUndervaluationScore(params, fmv) {
if (fmv <= 0) return 0.0;
const priceGap = (fmv - params.currentBid) / fmv;
const velocityFactor = 1 + params.bidVelocity / 10.0;
const watchRatio = Math.log(1 + params.watchCount / Math.max(params.bidCount, 1));
const uScore = priceGap * params.marketVolatility * velocityFactor * watchRatio;
return Math.max(0.0, Math.round(uScore * 1000) / 1000);
}
// Helper function: Predict final price
// Formula: P̂_final = FMV · (1 + ε_bid + ε_time + ε_comp)
function calculateFinalPrice(params, fmv) {
// Bid momentum error: ε_bid = tanh(φ_1 · Λ_b - φ_2 · P_current/FMV)
const epsilonBid = Math.tanh(0.15 * params.bidVelocity - 0.10 * (params.currentBid / fmv));
// Time pressure error: ε_time = ψ · exp(-t_close/30)
const epsilonTime = 0.20 * Math.exp(-params.minutesUntilClose / 30.0);
// Competition error: ε_comp = ρ · ln(1 + W_watch/50)
const epsilonComp = 0.08 * Math.log(1 + params.watchCount / 50.0);
const predictedPrice = fmv * (1 + epsilonBid + epsilonTime + epsilonComp);
// 95% confidence interval: ± 1.96 · σ_residual
const residualStdDev = fmv * 0.08;
const ciLower = predictedPrice - 1.96 * residualStdDev;
const ciUpper = predictedPrice + 1.96 * residualStdDev;
return {
predictedPrice: Math.round(predictedPrice * 100) / 100,
confidenceIntervalLower: Math.round(ciLower * 100) / 100,
confidenceIntervalUpper: Math.round(ciUpper * 100) / 100,
components: {
bidMomentum: Math.round(epsilonBid * 1000) / 1000,
timePressure: Math.round(epsilonTime * 1000) / 1000,
competition: Math.round(epsilonComp * 1000) / 1000
}
};
}
// Helper function: Generate bidding strategy
function generateBiddingStrategy(params, fmv, prediction) {
const strategy = {
competitionLevel: 'MEDIUM',
recommendedTiming: 'FINAL_10_MINUTES',
recommendedTimingText: 'FINAL 10 MINUTES',
maxBid: 0,
analysis: '',
riskFactors: []
};
// Determine competition level
if (params.bidVelocity > 5.0) {
strategy.competitionLevel = 'HIGH';
strategy.recommendedTiming = 'FINAL_30_SECONDS';
strategy.recommendedTimingText = 'FINAL 30 SECONDS';
strategy.maxBid = prediction.predictedPrice + 50;
strategy.riskFactors = ['Bidding war likely', 'Sniping detected'];
} else if (params.minutesUntilClose < 10) {
strategy.competitionLevel = 'EXTREME';
strategy.recommendedTiming = 'FINAL_10_SECONDS';
strategy.recommendedTimingText = 'FINAL 10 SECONDS';
strategy.maxBid = prediction.predictedPrice * 1.02;
strategy.riskFactors = ['Last-minute sniping', 'Price volatility'];
} else {
const undervaluationScore = calculateUndervaluationScore(params, fmv.value);
if (undervaluationScore > 0.25) {
strategy.maxBid = fmv.value * 1.05;
strategy.analysis = 'Significant undervaluation detected. Consider aggressive bidding.';
} else {
strategy.maxBid = fmv.value * 1.03;
}
strategy.riskFactors = ['Standard competition level'];
}
// Generate analysis
strategy.analysis = `Bid velocity is ${params.bidVelocity.toFixed(1)} bids/min with ${params.watchCount} watchers. ${strategy.competitionLevel} competition detected. Predicted final: €${prediction.predictedPrice.toFixed(2)}.`;
strategy.maxBid = Math.round(strategy.maxBid * 100) / 100;
return strategy;
}
// Fallback mock calculation (original logic)
async function calculateMockFallback(params) {
addLog('Using fallback calculation...', 'info');
@@ -654,9 +740,9 @@ async function calculateMockFallback(params) {
let weightSum = 0;
comparables.forEach(comp => {
const wc = Math.exp(-CONSTANTS.lambda_c * Math.abs(params.c_target - comp.condition));
const wt = Math.exp(-CONSTANTS.lambda_t * Math.abs(params.t_target - comp.year));
const wp = 1 + CONSTANTS.delta_p * (params.n_docs > 0 ? 1 : 0 - comp.provenance);
const wc = Math.exp(-CONSTANTS.lambda_c * Math.abs(params.conditionScore - comp.condition));
const wt = Math.exp(-CONSTANTS.lambda_t * Math.abs(params.manufacturingYear - comp.year));
const wp = 1 + CONSTANTS.delta_p * (params.provenanceDocs > 0 ? 1 : 0 - comp.provenance);
const wh = 1 / (1 + Math.exp(-CONSTANTS.kh * (comp.days_ago - 45)));
const weight = wc * wt * wp * wh;
@@ -664,12 +750,12 @@ async function calculateMockFallback(params) {
weightSum += weight;
});
let fmv = weightSum > 0 ? weightedSum / weightSum : (params.est_min + params.est_max) / 2;
const mCond = Math.exp(CONSTANTS.alpha_c * Math.sqrt(params.c_target) - CONSTANTS.beta_c);
let fmv = weightSum > 0 ? weightedSum / weightSum : (params.estimatedMin + params.estimatedMax) / 2;
const mCond = Math.exp(CONSTANTS.alpha_c * Math.sqrt(params.conditionScore) - CONSTANTS.beta_c);
fmv *= mCond;
if (params.n_docs > 0) {
const provPremium = CONSTANTS.delta_p + 0.05 * Math.log(1 + params.n_docs);
if (params.provenanceDocs > 0) {
const provPremium = CONSTANTS.delta_p + 0.05 * Math.log(1 + params.provenanceDocs);
fmv *= (1 + provPremium);
}
@@ -678,7 +764,7 @@ async function calculateMockFallback(params) {
fairMarketValue: {
value: Math.round(fmv * 100) / 100,
conditionMultiplier: Math.round(mCond * 1000) / 1000,
provenancePremium: params.n_docs > 0 ? CONSTANTS.delta_p + 0.05 * Math.log(1 + params.n_docs) : 0,
provenancePremium: params.provenanceDocs > 0 ? CONSTANTS.delta_p + 0.05 * Math.log(1 + params.provenanceDocs) : 0,
comparablesUsed: comparables.length,
confidence: 0.85,
weightedComparables: comparables.map(comp => ({
@@ -686,9 +772,9 @@ async function calculateMockFallback(params) {
finalPrice: comp.price,
totalWeight: 0.25,
components: {
conditionWeight: Math.round(Math.exp(-CONSTANTS.lambda_c * Math.abs(params.c_target - comp.condition)) * 1000) / 1000,
timeWeight: Math.round(Math.exp(-CONSTANTS.lambda_t * Math.abs(params.t_target - comp.year)) * 1000) / 1000,
provenanceWeight: Math.round((1 + CONSTANTS.delta_p * (params.n_docs > 0 ? 1 : 0 - comp.provenance)) * 1000) / 1000,
conditionWeight: Math.round(Math.exp(-CONSTANTS.lambda_c * Math.abs(params.conditionScore - comp.condition)) * 1000) / 1000,
timeWeight: Math.round(Math.exp(-CONSTANTS.lambda_t * Math.abs(params.manufacturingYear - comp.year)) * 1000) / 1000,
provenanceWeight: Math.round((1 + CONSTANTS.delta_p * (params.provenanceDocs > 0 ? 1 : 0 - comp.provenance)) * 1000) / 1000,
historicalWeight: Math.round((1 / (1 + Math.exp(-CONSTANTS.kh * (comp.days_ago - 45)))) * 1000) / 1000
}
}))
@@ -803,47 +889,57 @@ function generateSensitivityCharts() {
const conditionRange = Array.from({length: 21}, (_, i) => i * 0.5);
const conditionValues = conditionRange.map(c => {
const params = getInputParams();
params.c_target = c;
params.conditionScore = c;
return calculateMockFMV(params); // Use simplified mock for chart
});
Plotly.newPlot('condition-chart', [{
x: conditionRange,
y: conditionValues,
type: 'scatter',
mode: 'lines+markers',
line: { color: '#3b82f6', width: 3 },
marker: { size: 6 },
name: 'FMV vs Condition'
}], {
xaxis: { title: 'Condition Score (C)' },
yaxis: { title: 'FMV (€)' },
margin: { t: 20, b: 40, l: 60, r: 20 },
font: { size: 12 }
}, {responsive: true});
// Check if Plotly is available before using it
if (typeof Plotly !== 'undefined') {
Plotly.newPlot('condition-chart', [{
x: conditionRange,
y: conditionValues,
type: 'scatter',
mode: 'lines+markers',
line: { color: '#3b82f6', width: 3 },
marker: { size: 6 },
name: 'FMV vs Condition'
}], {
xaxis: { title: 'Condition Score (C)' },
yaxis: { title: 'FMV (€)' },
margin: { t: 20, b: 40, l: 60, r: 20 },
font: { size: 12 }
}, {responsive: true});
} else {
document.getElementById('condition-chart').innerHTML = '<div class="text-gray-500 text-sm p-4">Chart library not available (offline mode)</div>';
}
// Time sensitivity chart
const timeRange = Array.from({length: 20}, (_, i) => i * 5);
const timeValues = timeRange.map(t => {
const params = getInputParams();
params.t_close = t;
params.minutesUntilClose = t;
return calculateMockFinalPrice(params, calculateMockFMV(params));
});
Plotly.newPlot('time-chart', [{
x: timeRange,
y: timeValues,
type: 'scatter',
mode: 'lines+markers',
line: { color: '#10b981', width: 3 },
marker: { size: 6 },
name: 'Predicted Final vs Time'
}], {
xaxis: { title: 'Time to Close (minutes)' },
yaxis: { title: 'Predicted Final Price (€)' },
margin: { t: 20, b: 40, l: 60, r: 20 },
font: { size: 12 }
}, {responsive: true});
// Check if Plotly is available before using it
if (typeof Plotly !== 'undefined') {
Plotly.newPlot('time-chart', [{
x: timeRange,
y: timeValues,
type: 'scatter',
mode: 'lines+markers',
line: { color: '#10b981', width: 3 },
marker: { size: 6 },
name: 'Predicted Final vs Time'
}], {
xaxis: { title: 'Time to Close (minutes)' },
yaxis: { title: 'Predicted Final Price (€)' },
margin: { t: 20, b: 40, l: 60, r: 20 },
font: { size: 12 }
}, {responsive: true});
} else {
document.getElementById('time-chart').innerHTML = '<div class="text-gray-500 text-sm p-4">Chart library not available (offline mode)</div>';
}
}
// Simplified mock FMV for chart generation
@@ -857,22 +953,22 @@ function calculateMockFMV(params) {
let weightSum = 0;
comparables.forEach(comp => {
const wc = Math.exp(-CONSTANTS.lambda_c * Math.abs(params.c_target - comp.condition));
const wt = Math.exp(-CONSTANTS.lambda_t * Math.abs(params.t_target - comp.year));
const wc = Math.exp(-CONSTANTS.lambda_c * Math.abs(params.conditionScore - comp.condition));
const wt = Math.exp(-CONSTANTS.lambda_t * Math.abs(params.manufacturingYear - comp.year));
const weight = wc * wt;
weightedSum += comp.price * weight;
weightSum += weight;
});
let fmv = weightSum > 0 ? weightedSum / weightSum : 8000;
const mCond = Math.exp(CONSTANTS.alpha_c * Math.sqrt(params.c_target) - CONSTANTS.beta_c);
const mCond = Math.exp(CONSTANTS.alpha_c * Math.sqrt(params.conditionScore) - CONSTANTS.beta_c);
return fmv * mCond;
}
// Simplified mock final price for chart generation
function calculateMockFinalPrice(params, fmv) {
const epsilon_bid = Math.tanh(CONSTANTS.phi_1 * params.lambda_b);
const epsilon_time = CONSTANTS.psi * Math.exp(-params.t_close / 30);
const epsilon_bid = Math.tanh(CONSTANTS.phi_1 * params.bidVelocity);
const epsilon_time = CONSTANTS.psi * Math.exp(-params.minutesUntilClose / 30);
return fmv * (1 + epsilon_bid + epsilon_time);
}