feat(procurement): availability why-attribution + per-SKU price momentum (1.6T: 2 suppliers, +13.7% same-SKU, 0% stock)

This commit is contained in:
Rene Fichtmueller 2026-06-11 10:22:30 +00:00
parent c6b5bcf844
commit 3cbfabbb38

View File

@ -819,6 +819,24 @@ procurementRouter.get("/availability", async (_req: Request, res: Response) => {
) s ON true
WHERE t.speed_gbps IN (100,200,400,800,1600)
GROUP BY t.speed_gbps
),
pm AS (
-- per-SKU paired price momentum (30d vs prior 30d), bias-free: only SKUs in
-- both windows; aggregate the median per-SKU pct delta per speed tier.
SELECT speed_gbps,
ROUND(PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY (med_now - med_prior) / NULLIF(med_prior,0) * 100)::numeric, 1) AS price_delta_pct
FROM (
SELECT t.speed_gbps, t.id,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY po.price) FILTER (WHERE po.time >= NOW() - INTERVAL '30 days') AS med_now,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY po.price) FILTER (WHERE po.time >= NOW() - INTERVAL '60 days' AND po.time < NOW() - INTERVAL '30 days') AS med_prior,
COUNT(*) FILTER (WHERE po.time >= NOW() - INTERVAL '30 days') AS n_now,
COUNT(*) FILTER (WHERE po.time >= NOW() - INTERVAL '60 days' AND po.time < NOW() - INTERVAL '30 days') AS n_prior
FROM price_observations po JOIN transceivers t ON t.id = po.transceiver_id
WHERE t.speed_gbps IN (100,200,400,800,1600) AND po.price > 0 AND COALESCE(po.is_anomalous,false) = false
GROUP BY t.speed_gbps, t.id
) per
WHERE med_now IS NOT NULL AND med_prior IS NOT NULL AND med_prior > 0 AND n_now >= 2 AND n_prior >= 2
GROUP BY speed_gbps
)
SELECT
sk.speed_gbps,
@ -828,14 +846,16 @@ procurementRouter.get("/availability", async (_req: Request, res: Response) => {
COALESCE(st.skus_in_stock,0)::int AS skus_in_stock,
COALESCE(st.skus_with_stock,0)::int AS skus_with_stock,
CASE WHEN COALESCE(st.skus_with_stock,0) > 0
THEN ROUND(100.0 * st.skus_in_stock / st.skus_with_stock)::int ELSE NULL END AS in_stock_pct
THEN ROUND(100.0 * st.skus_in_stock / st.skus_with_stock)::int ELSE NULL END AS in_stock_pct,
pm.price_delta_pct
FROM sk
LEFT JOIN sup ON sup.speed_gbps = sk.speed_gbps
LEFT JOIN st ON st.speed_gbps = sk.speed_gbps
LEFT JOIN pm ON pm.speed_gbps = sk.speed_gbps
ORDER BY sk.speed_gbps DESC
`);
type Row = { speed_gbps: string; sku_count: number; suppliers: number; suppliers_prior: number; skus_in_stock: number; skus_with_stock: number; in_stock_pct: number | null };
type Row = { speed_gbps: string; sku_count: number; suppliers: number; suppliers_prior: number; skus_in_stock: number; skus_with_stock: number; in_stock_pct: number | null; price_delta_pct: string | null };
const tiers = (result.rows as Row[]).map((r) => {
const sup = r.suppliers ?? 0;
const inStockPct = r.in_stock_pct ?? null;
@ -849,17 +869,33 @@ procurementRouter.get("/availability", async (_req: Request, res: Response) => {
const trend = r.suppliers_prior > 0
? (sup < r.suppliers_prior ? "tightening" : sup > r.suppliers_prior ? "loosening" : "stable")
: "stable";
const priceDelta = r.price_delta_pct != null ? parseFloat(r.price_delta_pct) : null;
const priceTrend = priceDelta === null ? "unknown" : priceDelta > 3 ? "rising" : priceDelta < -3 ? "falling" : "stable";
const drivers: string[] = [`${sup} active suppliers`];
if (trend === "tightening") drivers.push(`supplier base shrank ${r.suppliers_prior}${sup} vs prior 45d`);
else if (trend === "loosening") drivers.push(`supplier base grew ${r.suppliers_prior}${sup} vs prior 45d`);
if (inStockPct !== null) drivers.push(`${inStockPct}% of tracked SKUs in stock`);
drivers.push(`${r.sku_count} SKUs`);
if (trend === "tightening") drivers.push(`supplier base shrinking (${r.suppliers_prior}${sup})`);
if (priceDelta !== null) drivers.push(`price ${priceDelta >= 0 ? "+" : ""}${priceDelta}% (30d, same-SKU median)`);
drivers.push(`${r.sku_count} SKUs catalogued`);
// Plain-language WHY, built only from real per-speed signals
const reasons: string[] = [];
if (availability === "scarce") reasons.push(sup <= 3 ? `only ${sup} supplier(s) offer it` : `near-zero stock coverage`);
else if (availability === "constrained") reasons.push(`limited supplier base (${sup})`);
if (trend === "tightening") reasons.push(`supplier base is shrinking`);
if (priceTrend === "rising") reasons.push(`prices rising ${priceDelta}% on the same parts`);
if (priceTrend === "falling") reasons.push(`prices easing ${priceDelta}%`);
if (inStockPct !== null && inStockPct < 50) reasons.push(`${inStockPct}% in-stock coverage`);
const why = reasons.length ? reasons.join("; ") : `broad supply: ${sup} suppliers, ${inStockPct ?? "n/a"}% in stock, stable pricing`;
return {
speed_gbps: parseFloat(r.speed_gbps),
sku_count: r.sku_count,
suppliers: sup,
in_stock_pct: inStockPct,
price_delta_pct: priceDelta,
price_trend: priceTrend,
availability,
trend,
why,
drivers,
};
});