feat(vendors): add /reliability endpoint (per-vendor freshness/frequency/coverage scores from real data)
This commit is contained in:
parent
f067c0999d
commit
c6e79e9967
@ -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 {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user