From 2f4b1f6671ac7cd0b5676c4f08ee97560723bd3f Mon Sep 17 00:00:00 2001 From: shats Date: Thu, 5 Mar 2026 10:55:35 +0700 Subject: [PATCH] feat(route-optimizer): refactor bandwidth scoring and enhance speed display in UI for improved clarity --- backend/routes/routeOptimizerRoutes.js | 60 +++++++++++++++++++++----- frontend/src/RouteOptimizerPage.jsx | 21 +++++++-- 2 files changed, 68 insertions(+), 13 deletions(-) diff --git a/backend/routes/routeOptimizerRoutes.js b/backend/routes/routeOptimizerRoutes.js index 2a26b6f..515f744 100644 --- a/backend/routes/routeOptimizerRoutes.js +++ b/backend/routes/routeOptimizerRoutes.js @@ -77,7 +77,7 @@ function latencyScore(pingMs, aiSettings) { return clamp(normalized, 0, 1); } -function bandwidthScore(speedMbps, aiSettings) { +function normalizeBandwidthValue(speedMbps, aiSettings) { if (typeof speedMbps !== 'number' || speedMbps <= 0) return aiSettings.noSpeedScore; // log-scale to avoid dominance by very high channels const normalized = Math.log10(1 + speedMbps) / Math.log10(1001); @@ -96,11 +96,29 @@ function freshnessScore(cacheUpdatedAt, aiSettings) { return aiSettings.staleScore; } -function buildSegmentScore({ pingMs, speedMbps, cacheUpdatedAt, aiSettings }) { +function bandwidthScore({ speedDownloadMbps, speedUploadMbps, aiSettings }) { + // DL/UL considered separately with equal 50/50 contribution. + const dl = normalizeBandwidthValue(speedDownloadMbps, aiSettings); + const ul = normalizeBandwidthValue(speedUploadMbps, aiSettings); + return clamp((dl + ul) / 2, 0, 1); +} + +function buildSegmentScore({ + pingMs, + speedMbps, + speedDownloadMbps, + speedUploadMbps, + cacheUpdatedAt, + aiSettings, +}) { const l = latencyScore(pingMs, aiSettings); - const b = bandwidthScore(speedMbps, aiSettings); + const b = bandwidthScore({ speedDownloadMbps, speedUploadMbps, aiSettings }); const f = freshnessScore(cacheUpdatedAt, aiSettings); - const metricPresence = (typeof pingMs === 'number' ? 1 : 0) + (typeof speedMbps === 'number' ? 1 : 0); + const hasAnySpeed = + typeof speedMbps === 'number' || + typeof speedDownloadMbps === 'number' || + typeof speedUploadMbps === 'number'; + const metricPresence = (typeof pingMs === 'number' ? 1 : 0) + (hasAnySpeed ? 1 : 0); const confidence = metricPresence === 2 ? 1 : metricPresence === 1 ? 0.65 : 0.35; const score = l * aiSettings.latencyWeight + @@ -329,10 +347,24 @@ function buildInterfaceCandidates({ servers, tunnelInterfaces, pingMap, speedMap const speedEntry = asObject(speedMap[skey]); const downBps = toNumber(speedEntry.tcpDownloadBps); const upBps = toNumber(speedEntry.tcpUploadBps); - const speedMbps = downBps != null || upBps != null - ? Math.max(downBps || 0, upBps || 0) / 1e6 - : null; - const scoring = buildSegmentScore({ pingMs, speedMbps, cacheUpdatedAt, aiSettings }); + const speedDownloadMbps = downBps != null ? downBps / 1e6 : null; + const speedUploadMbps = upBps != null ? upBps / 1e6 : null; + const speedMbps = + speedDownloadMbps != null && speedUploadMbps != null + ? (speedDownloadMbps + speedUploadMbps) / 2 + : speedDownloadMbps != null + ? speedDownloadMbps + : speedUploadMbps != null + ? speedUploadMbps + : null; + const scoring = buildSegmentScore({ + pingMs, + speedMbps, + speedDownloadMbps, + speedUploadMbps, + cacheUpdatedAt, + aiSettings, + }); const base = { interfaceName: iface.name || null, @@ -340,8 +372,8 @@ function buildInterfaceCandidates({ servers, tunnelInterfaces, pingMap, speedMap remoteIp: iface.remoteIp || null, pingMs, speedMbps: speedMbps != null ? Number(speedMbps.toFixed(2)) : null, - speedDownloadMbps: downBps != null ? Number((downBps / 1e6).toFixed(2)) : null, - speedUploadMbps: upBps != null ? Number((upBps / 1e6).toFixed(2)) : null, + speedDownloadMbps: speedDownloadMbps != null ? Number(speedDownloadMbps.toFixed(2)) : null, + speedUploadMbps: speedUploadMbps != null ? Number(speedUploadMbps.toFixed(2)) : null, score: Number(scoring.score.toFixed(4)), confidence: Number(scoring.confidence.toFixed(4)), }; @@ -639,6 +671,8 @@ function buildCommunityOptimization({ probabilityOptimal: currentCandidate.probabilityOptimal, pingMs: currentCandidate.pingMs, speedMbps: currentCandidate.speedMbps, + speedDownloadMbps: currentCandidate.speedDownloadMbps, + speedUploadMbps: currentCandidate.speedUploadMbps, } : null, recommended: recommendedCandidate ? { gateway: recommendedCandidate.recursiveGateway || recommendedCandidate.gatewayIpForJumphost || null, @@ -650,6 +684,8 @@ function buildCommunityOptimization({ probabilityOptimal: recommendedCandidate.probabilityOptimal, pingMs: recommendedCandidate.pingMs, speedMbps: recommendedCandidate.speedMbps, + speedDownloadMbps: recommendedCandidate.speedDownloadMbps, + speedUploadMbps: recommendedCandidate.speedUploadMbps, } : null, pinnedGateway: pinned?.gateway || null, pinnedBySettings: Boolean(pinned), @@ -721,6 +757,8 @@ async function getRouteOptimizer(req, res) { interfaceName: hj.interfaceName, pingMs: hj.pingMs, speedMbps: hj.speedMbps, + speedDownloadMbps: hj.speedDownloadMbps, + speedUploadMbps: hj.speedUploadMbps, score: hj.score, confidence: hj.confidence, }, @@ -728,6 +766,8 @@ async function getRouteOptimizer(req, res) { interfaceName: jhExit.interfaceName, pingMs: jhExit.pingMs, speedMbps: jhExit.speedMbps, + speedDownloadMbps: jhExit.speedDownloadMbps, + speedUploadMbps: jhExit.speedUploadMbps, score: jhExit.score, confidence: jhExit.confidence, }, diff --git a/frontend/src/RouteOptimizerPage.jsx b/frontend/src/RouteOptimizerPage.jsx index c53015b..d927d5a 100644 --- a/frontend/src/RouteOptimizerPage.jsx +++ b/frontend/src/RouteOptimizerPage.jsx @@ -22,6 +22,13 @@ function fmtProb(v) { return typeof v === 'number' ? `${v}%` : '—'; } +function fmtSpeed(dl, ul, fallback) { + if (typeof dl === 'number' || typeof ul === 'number') { + return `DL: ${dl ?? '—'} Mbps, UL: ${ul ?? '—'} Mbps`; + } + return `speed: ${fallback ?? '—'} Mbps`; +} + const OPT_TABLE_STYLE = { tableLayout: 'fixed', width: '100%' }; const OPT_COLS = ['24%', '20%', '22%', '18%', '16%']; @@ -167,7 +174,7 @@ export default function RouteOptimizerPage() { {c.jumphost?.label || c.jumphost?.dns || c.jumphost?.ip || 'jumphost'} {c.interfaceName || '—'} - ping: {c.pingMs ?? '—'} ms, speed: {c.speedMbps ?? '—'} Mbps + ping: {c.pingMs ?? '—'} ms, {fmtSpeed(c.speedDownloadMbps, c.speedUploadMbps, c.speedMbps)} {c.score} / {c.confidence} @@ -229,10 +236,18 @@ export default function RouteOptimizerPage() { {r.exit?.label} - ping: {r.homeToJumphost?.pingMs ?? '—'} ms, speed: {r.homeToJumphost?.speedMbps ?? '—'} Mbps + ping: {r.homeToJumphost?.pingMs ?? '—'} ms, {fmtSpeed( + r.homeToJumphost?.speedDownloadMbps, + r.homeToJumphost?.speedUploadMbps, + r.homeToJumphost?.speedMbps + )} - ping: {r.jumphostToExit?.pingMs ?? '—'} ms, speed: {r.jumphostToExit?.speedMbps ?? '—'} Mbps + ping: {r.jumphostToExit?.pingMs ?? '—'} ms, {fmtSpeed( + r.jumphostToExit?.speedDownloadMbps, + r.jumphostToExit?.speedUploadMbps, + r.jumphostToExit?.speedMbps + )} {r.score} / {r.confidence ?? '—'}