feat(vendors): add /reliability endpoint (per-vendor freshness/frequency/coverage scores from real data)

This commit is contained in:
Rene Fichtmueller 2026-06-04 21:08:08 +00:00
parent f067c0999d
commit c6e79e9967

View File

@ -114,6 +114,39 @@ vendorRouter.post("/", async (req: Request, res: Response) => {
});
// GET /api/vendors/:id — Get single vendor with full stats
// GET /api/vendors/reliability — per-vendor data-reliability scores (freshness/frequency/coverage)
vendorRouter.get("/reliability", async (_req: Request, res: Response) => {
try {
const result = await pool.query(`
SELECT v.id::text AS vendor_id, v.name AS vendor_name,
COUNT(DISTINCT t.id)::int AS sku_count,
COUNT(po.transceiver_id)::int AS obs_count,
MAX(po.time) AS last_obs
FROM vendors v
JOIN transceivers t ON t.vendor_id = v.id
LEFT JOIN price_observations po ON po.transceiver_id = t.id
GROUP BY v.id, v.name
HAVING COUNT(DISTINCT t.id) > 0`);
const rows = result.rows as Array<{ vendor_id: string; vendor_name: string; sku_count: number; obs_count: number; last_obs: string | null }>;
const maxSku = Math.max(1, ...rows.map((r) => r.sku_count));
const maxObs = Math.max(1, ...rows.map((r) => r.obs_count));
const now = Date.now();
const vendors = rows.map((r) => {
const daysStale = r.last_obs ? Math.max(0, (now - new Date(r.last_obs).getTime()) / 86400000) : 999;
const freshness_score = Math.max(0, Math.min(100, Math.round(100 - daysStale * 3)));
const frequency_score = Math.round(100 * Math.min(1, Math.log10(r.obs_count + 1) / Math.log10(maxObs + 1)));
const coverage_score = Math.round(100 * Math.min(1, r.sku_count / maxSku));
const reliability_score = Math.round(0.4 * freshness_score + 0.3 * frequency_score + 0.3 * coverage_score);
return { vendor_id: r.vendor_id, vendor_name: r.vendor_name, sku_count: r.sku_count, obs_count: r.obs_count, last_obs: r.last_obs, freshness_score, frequency_score, coverage_score, reliability_score };
});
vendors.sort((a, b) => b.reliability_score - a.reliability_score);
res.json({ success: true, count: vendors.length, vendors });
} catch (err) {
console.error("vendor reliability error:", err);
res.status(500).json({ success: false, error: "Failed to compute vendor reliability" });
}
});
vendorRouter.get("/:id", async (req: Request, res: Response, next: NextFunction) => {
if (req.params.id === "market-share" || req.params.id === "intelligence") return next();
try {