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/: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) => {
|
vendorRouter.get("/:id", async (req: Request, res: Response, next: NextFunction) => {
|
||||||
if (req.params.id === "market-share" || req.params.id === "intelligence") return next();
|
if (req.params.id === "market-share" || req.params.id === "intelligence") return next();
|
||||||
try {
|
try {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user