From cc0164b689317e859d02ad26b964a8e715ba5edc Mon Sep 17 00:00:00 2001 From: bytedream Date: Mon, 20 Apr 2026 10:13:11 +0200 Subject: [PATCH] change average price to median price on the dashboard (#300) * change average price to median price on the dashboard * Use more efficient median calculation Co-authored-by: Christian Kellner * Fix applied suggestion artifacts * Update sql query and js sort function * Group sql statement by id * Revert sort function change --------- Co-authored-by: Christian Kellner --- lib/api/routes/dashboardRouter.js | 4 +-- lib/services/storage/listingsStorage.js | 44 ++++++++++++++++--------- ui/src/views/dashboard/Dashboard.jsx | 8 ++--- 3 files changed, 35 insertions(+), 21 deletions(-) diff --git a/lib/api/routes/dashboardRouter.js b/lib/api/routes/dashboardRouter.js index 57d3126..b72d804 100644 --- a/lib/api/routes/dashboardRouter.js +++ b/lib/api/routes/dashboardRouter.js @@ -37,7 +37,7 @@ dashboardRouter.get('/', async (req, res) => { const totalJobs = jobs.length; const totalListings = jobs.reduce((sum, j) => sum + (j.numberOfFoundListings || 0), 0); const jobIds = jobs.map((j) => j.id); - const { numberOfActiveListings, avgPriceOfListings } = getListingsKpisForJobIds(jobIds); + const { numberOfActiveListings, medianPriceOfListings } = getListingsKpisForJobIds(jobIds); // Build Pie data in a simple shape the frontend can consume directly // Shape: { labels: string[], values: number[] } with values as percentages const providerPieRaw = getProviderDistributionForJobIds(jobIds); @@ -63,7 +63,7 @@ dashboardRouter.get('/', async (req, res) => { totalJobs, totalListings, numberOfActiveListings, - avgPriceOfListings, + medianPriceOfListings, }, pie: providerPie, }; diff --git a/lib/services/storage/listingsStorage.js b/lib/services/storage/listingsStorage.js index 831a3b5..43a51d6 100755 --- a/lib/services/storage/listingsStorage.js +++ b/lib/services/storage/listingsStorage.js @@ -29,33 +29,47 @@ export const getKnownListingHashesForJobAndProvider = (jobId, providerId) => { * Compute KPI aggregates for a given set of job IDs from the listings table. * * - numberOfActiveListings: count of listings where is_active = 1 - * - avgPriceOfListings: average of numeric price, rounded to nearest integer + * - medianPriceOfListings: median of numeric price, rounded to nearest integer * * When no jobIds are provided, returns zeros. * * @param {string[]} jobIds - * @returns {{ numberOfActiveListings: number, avgPriceOfListings: number }} + * @returns {{ numberOfActiveListings: number, medianPriceOfListings: number }} */ export const getListingsKpisForJobIds = (jobIds = []) => { if (!Array.isArray(jobIds) || jobIds.length === 0) { - return { numberOfActiveListings: 0, avgPriceOfListings: 0 }; + return { numberOfActiveListings: 0, medianPriceOfListings: 0 }; } const placeholders = jobIds.map(() => '?').join(','); - const row = - SqliteConnection.query( - `SELECT - SUM(CASE WHEN is_active = 1 THEN 1 ELSE 0 END) AS activeCount, - AVG(price) AS avgPrice - FROM listings - WHERE job_id IN (${placeholders}) - AND manually_deleted = 0`, - jobIds, - )[0] || {}; + const rows = SqliteConnection.query( + `SELECT + SUM(CASE WHEN is_active = 1 THEN 1 ELSE 0 END) OVER() AS active_count, + price + FROM listings + WHERE job_id IN (${placeholders}) + AND manually_deleted = 0 + GROUP BY + id`, + jobIds, + ); + + const activeCount = rows[0]?.active_count ?? 0; + + const prices = rows + .map((r) => r.price) + .filter((p) => p !== null) + .sort((a, b) => a - b); + + let medianPrice = 0; + if (prices.length > 0) { + const mid = Math.floor(prices.length / 2); + medianPrice = prices.length % 2 !== 0 ? prices[mid] : (prices[mid - 1] + prices[mid]) / 2; + } return { - numberOfActiveListings: Number(row.activeCount || 0), - avgPriceOfListings: row?.avgPrice == null ? 0 : Math.round(Number(row.avgPrice)), + numberOfActiveListings: activeCount, + medianPriceOfListings: medianPrice, }; }; diff --git a/ui/src/views/dashboard/Dashboard.jsx b/ui/src/views/dashboard/Dashboard.jsx index de1f51f..c2911a8 100644 --- a/ui/src/views/dashboard/Dashboard.jsx +++ b/ui/src/views/dashboard/Dashboard.jsx @@ -127,18 +127,18 @@ export default function Dashboard() { } - description="Avg. Price of listings" + description="Median Price of listings" />