fix(procurement): exclude speed=0 signals + consistent speed formatting
Reconciles Stock/Health working-tree WIP uncommitted since 2026-07-04: - supply-squeeze: exclude speed_gbps=0 / unclassified speed_tier=0 from price-momentum and AI-cluster-demand signals - dashboard: render transceiver speed via fmtSpd() (1600 -> 1.6T) in reorder-top, switch-compat, supply-squeeze, dead-stock-revival tables
This commit is contained in:
parent
479a0dbe56
commit
41e98ca5df
@ -938,6 +938,7 @@ procurementRouter.get("/supply-squeeze", async (_req: Request, res: Response) =>
|
|||||||
AND t.part_number NOT ILIKE '%AOC%'
|
AND t.part_number NOT ILIKE '%AOC%'
|
||||||
AND t.part_number NOT ILIKE '%-DAC-%'
|
AND t.part_number NOT ILIKE '%-DAC-%'
|
||||||
AND (t.standard_name IS NULL OR (t.standard_name NOT ILIKE '%Switch%' AND t.standard_name NOT ILIKE '%InfiniBand%'))
|
AND (t.standard_name IS NULL OR (t.standard_name NOT ILIKE '%Switch%' AND t.standard_name NOT ILIKE '%InfiniBand%'))
|
||||||
|
AND t.speed_gbps > 0
|
||||||
GROUP BY t.id, t.speed_gbps, t.form_factor
|
GROUP BY t.id, t.speed_gbps, t.form_factor
|
||||||
)
|
)
|
||||||
SELECT
|
SELECT
|
||||||
@ -955,21 +956,23 @@ procurementRouter.get("/supply-squeeze", async (_req: Request, res: Response) =>
|
|||||||
GROUP BY speed_gbps, form_factor
|
GROUP BY speed_gbps, form_factor
|
||||||
HAVING COUNT(*) >= 3
|
HAVING COUNT(*) >= 3
|
||||||
`),
|
`),
|
||||||
// AI cluster demand by speed tier
|
// AI cluster demand by speed tier (speed_tier = 0 excluded — unclassified announcements)
|
||||||
pool.query(`
|
pool.query(`
|
||||||
SELECT
|
SELECT speed_tier, total_tx, cluster_count FROM (
|
||||||
CASE
|
SELECT
|
||||||
WHEN COALESCE(network_speed, title, summary, '') ILIKE '%800G%' THEN 800
|
CASE
|
||||||
WHEN COALESCE(network_speed, title, summary, '') ILIKE '%400G%' THEN 400
|
WHEN COALESCE(network_speed, title, summary, '') ILIKE '%800G%' THEN 800
|
||||||
WHEN COALESCE(network_speed, title, summary, '') ILIKE '%100G%' THEN 100
|
WHEN COALESCE(network_speed, title, summary, '') ILIKE '%400G%' THEN 400
|
||||||
ELSE 0
|
WHEN COALESCE(network_speed, title, summary, '') ILIKE '%100G%' THEN 100
|
||||||
END AS speed_tier,
|
ELSE 0
|
||||||
COALESCE(SUM(estimated_transceivers),0)::int AS total_tx,
|
END AS speed_tier,
|
||||||
COUNT(*)::int AS cluster_count
|
COALESCE(SUM(estimated_transceivers),0)::int AS total_tx,
|
||||||
FROM ai_cluster_announcements
|
COUNT(*)::int AS cluster_count
|
||||||
WHERE announced_date >= NOW() - INTERVAL '90 days'
|
FROM ai_cluster_announcements
|
||||||
GROUP BY 1
|
WHERE announced_date >= NOW() - INTERVAL '90 days'
|
||||||
HAVING COALESCE(SUM(estimated_transceivers),0) > 0
|
GROUP BY 1
|
||||||
|
HAVING COALESCE(SUM(estimated_transceivers),0) > 0
|
||||||
|
) t WHERE speed_tier > 0
|
||||||
`),
|
`),
|
||||||
// Hype phase per technology
|
// Hype phase per technology
|
||||||
pool.query(`
|
pool.query(`
|
||||||
@ -1047,7 +1050,7 @@ procurementRouter.get("/supply-squeeze", async (_req: Request, res: Response) =>
|
|||||||
activeSignals, severity, reasons,
|
activeSignals, severity, reasons,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.filter((r) => r.activeSignals >= 1)
|
.filter((r) => r.activeSignals >= 1 && parseFloat(String(r.speed_gbps)) > 0)
|
||||||
.sort((a, b) => b.activeSignals - a.activeSignals || b.price_momentum_pct - a.price_momentum_pct);
|
.sort((a, b) => b.activeSignals - a.activeSignals || b.price_momentum_pct - a.price_momentum_pct);
|
||||||
|
|
||||||
res.json({ success: true, signals, criticalCount: signals.filter(s => s.severity === "critical").length });
|
res.json({ success: true, signals, criticalCount: signals.filter(s => s.severity === "critical").length });
|
||||||
|
|||||||
@ -7990,7 +7990,7 @@ async function loadReorderTop() {
|
|||||||
+ '<td style="padding:0.45rem 0.5rem;font-weight:700;color:var(--text-bright);font-size:0.78rem">'+esc(r.vendor_name||'')+'</td>'
|
+ '<td style="padding:0.45rem 0.5rem;font-weight:700;color:var(--text-bright);font-size:0.78rem">'+esc(r.vendor_name||'')+'</td>'
|
||||||
+ '<td style="padding:0.45rem 0.5rem;font-family:monospace;font-size:0.72rem;color:var(--text-dim)">'+esc(r.part_number||'')+'</td>'
|
+ '<td style="padding:0.45rem 0.5rem;font-family:monospace;font-size:0.72rem;color:var(--text-dim)">'+esc(r.part_number||'')+'</td>'
|
||||||
+ '<td style="padding:0.45rem 0.5rem;text-align:center"><span style="background:rgba(99,102,241,0.15);color:#818cf8;border-radius:4px;padding:2px 6px;font-size:0.7rem">'+esc(r.form_factor||'')+'</span></td>'
|
+ '<td style="padding:0.45rem 0.5rem;text-align:center"><span style="background:rgba(99,102,241,0.15);color:#818cf8;border-radius:4px;padding:2px 6px;font-size:0.7rem">'+esc(r.form_factor||'')+'</span></td>'
|
||||||
+ '<td style="padding:0.45rem 0.5rem;text-align:right;color:var(--blue);font-weight:700;font-size:0.78rem">'+esc(String(r.speed_gbps||''))+'G</td>'
|
+ '<td style="padding:0.45rem 0.5rem;text-align:right;color:var(--blue);font-weight:700;font-size:0.78rem">'+fmtSpd(r.speed_gbps)+'</td>'
|
||||||
+ '<td style="padding:0.45rem 0.5rem;text-align:center;font-weight:700;color:'+strColor+';font-family:monospace">'+str+'%</td>'
|
+ '<td style="padding:0.45rem 0.5rem;text-align:center;font-weight:700;color:'+strColor+';font-family:monospace">'+str+'%</td>'
|
||||||
+ '<td style="padding:0.45rem 0.5rem;font-size:0.7rem">'+pt+' Preis · '+st+' Stock</td>'
|
+ '<td style="padding:0.45rem 0.5rem;font-size:0.7rem">'+pt+' Preis · '+st+' Stock</td>'
|
||||||
+ '<td style="padding:0.45rem 0.5rem;font-size:0.7rem;color:var(--text-dim);max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="'+esc(reasons)+'">'+esc(reasons.substring(0,80))+'</td>'
|
+ '<td style="padding:0.45rem 0.5rem;font-size:0.7rem;color:var(--text-dim);max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="'+esc(reasons)+'">'+esc(reasons.substring(0,80))+'</td>'
|
||||||
@ -8106,7 +8106,7 @@ async function loadSwitchCompat() {
|
|||||||
+ '<td style="padding:0.4rem 0.5rem;font-weight:600;font-size:0.75rem;color:var(--text-bright)">'+esc(t.vendor_name)+'</td>'
|
+ '<td style="padding:0.4rem 0.5rem;font-weight:600;font-size:0.75rem;color:var(--text-bright)">'+esc(t.vendor_name)+'</td>'
|
||||||
+ '<td style="padding:0.4rem 0.5rem;font-family:monospace;font-size:0.7rem;color:var(--text-dim)">'+esc(t.part_number||'')+'</td>'
|
+ '<td style="padding:0.4rem 0.5rem;font-family:monospace;font-size:0.7rem;color:var(--text-dim)">'+esc(t.part_number||'')+'</td>'
|
||||||
+ '<td style="padding:0.4rem 0.5rem;text-align:center"><span style="background:rgba(99,102,241,0.15);color:#818cf8;border-radius:4px;padding:1px 5px;font-size:0.68rem">'+esc(t.form_factor)+'</span></td>'
|
+ '<td style="padding:0.4rem 0.5rem;text-align:center"><span style="background:rgba(99,102,241,0.15);color:#818cf8;border-radius:4px;padding:1px 5px;font-size:0.68rem">'+esc(t.form_factor)+'</span></td>'
|
||||||
+ '<td style="padding:0.4rem 0.5rem;text-align:right;color:var(--blue);font-weight:700;font-size:0.72rem">'+esc(String(t.speed_gbps))+'G</td>'
|
+ '<td style="padding:0.4rem 0.5rem;text-align:right;color:var(--blue);font-weight:700;font-size:0.72rem">'+fmtSpd(t.speed_gbps)+'</td>'
|
||||||
+ '<td style="padding:0.4rem 0.5rem;text-align:right;color:var(--green);font-size:0.72rem">'+esc(priceStr)+'</td>'
|
+ '<td style="padding:0.4rem 0.5rem;text-align:right;color:var(--green);font-size:0.72rem">'+esc(priceStr)+'</td>'
|
||||||
+ '<td style="padding:0.4rem 0.5rem;text-align:center;font-size:0.68rem;color:var(--text-dim)">'+esc(t.verification_method||'')+'</td>'
|
+ '<td style="padding:0.4rem 0.5rem;text-align:center;font-size:0.68rem;color:var(--text-dim)">'+esc(t.verification_method||'')+'</td>'
|
||||||
+ '</tr>';
|
+ '</tr>';
|
||||||
@ -8194,7 +8194,7 @@ async function loadSupplySqueeze() {
|
|||||||
var mom = s.price_momentum_pct !== 0 ? (s.price_momentum_pct > 0 ? '+' : '') + s.price_momentum_pct + '%' : '—';
|
var mom = s.price_momentum_pct !== 0 ? (s.price_momentum_pct > 0 ? '+' : '') + s.price_momentum_pct + '%' : '—';
|
||||||
var momColor = s.price_momentum_pct > 10 ? '#ef4444' : s.price_momentum_pct > 0 ? '#f59e0b' : '#22c55e';
|
var momColor = s.price_momentum_pct > 10 ? '#ef4444' : s.price_momentum_pct > 0 ? '#f59e0b' : '#22c55e';
|
||||||
return '<tr style="border-bottom:1px solid var(--border)">'
|
return '<tr style="border-bottom:1px solid var(--border)">'
|
||||||
+ '<td style="padding:0.45rem 0.5rem;font-weight:700;font-size:0.8rem;color:'+col+'">'+icon+' '+esc(String(s.speed_gbps))+'G</td>'
|
+ '<td style="padding:0.45rem 0.5rem;font-weight:700;font-size:0.8rem;color:'+col+'">'+icon+' '+fmtSpd(s.speed_gbps)+'</td>'
|
||||||
+ '<td style="padding:0.45rem 0.5rem"><span style="background:rgba(99,102,241,0.15);color:#818cf8;border-radius:4px;padding:2px 6px;font-size:0.7rem">'+esc(s.form_factor||'—')+'</span></td>'
|
+ '<td style="padding:0.45rem 0.5rem"><span style="background:rgba(99,102,241,0.15);color:#818cf8;border-radius:4px;padding:2px 6px;font-size:0.7rem">'+esc(s.form_factor||'—')+'</span></td>'
|
||||||
+ '<td style="padding:0.45rem 0.5rem;text-align:right;font-weight:700;color:'+momColor+';font-family:monospace">'+esc(mom)+'</td>'
|
+ '<td style="padding:0.45rem 0.5rem;text-align:right;font-weight:700;color:'+momColor+';font-family:monospace">'+esc(mom)+'</td>'
|
||||||
+ '<td style="padding:0.45rem 0.5rem;font-size:0.7rem;color:var(--text-dim)">'+esc((s.hype_phase||'—').replace(/_/g,' '))+'</td>'
|
+ '<td style="padding:0.45rem 0.5rem;font-size:0.7rem;color:var(--text-dim)">'+esc((s.hype_phase||'—').replace(/_/g,' '))+'</td>'
|
||||||
@ -8246,7 +8246,7 @@ async function loadDeadStockRevival() {
|
|||||||
+ '<td style="padding:0.45rem 0.5rem;font-weight:700;font-size:0.75rem;color:var(--text-bright)">'+esc(r.vendor_name||'')+'</td>'
|
+ '<td style="padding:0.45rem 0.5rem;font-weight:700;font-size:0.75rem;color:var(--text-bright)">'+esc(r.vendor_name||'')+'</td>'
|
||||||
+ '<td style="padding:0.45rem 0.5rem;font-family:monospace;font-size:0.68rem;color:var(--text-dim)">'+esc(r.part_number||'')+'</td>'
|
+ '<td style="padding:0.45rem 0.5rem;font-family:monospace;font-size:0.68rem;color:var(--text-dim)">'+esc(r.part_number||'')+'</td>'
|
||||||
+ '<td style="padding:0.45rem 0.5rem;text-align:center"><span style="background:rgba(99,102,241,0.15);color:#818cf8;border-radius:4px;padding:2px 5px;font-size:0.68rem">'+esc(r.form_factor)+'</span></td>'
|
+ '<td style="padding:0.45rem 0.5rem;text-align:center"><span style="background:rgba(99,102,241,0.15);color:#818cf8;border-radius:4px;padding:2px 5px;font-size:0.68rem">'+esc(r.form_factor)+'</span></td>'
|
||||||
+ '<td style="padding:0.45rem 0.5rem;text-align:right;color:var(--blue);font-weight:700;font-size:0.72rem">'+esc(String(r.speed_gbps))+'G</td>'
|
+ '<td style="padding:0.45rem 0.5rem;text-align:right;color:var(--blue);font-weight:700;font-size:0.72rem">'+fmtSpd(r.speed_gbps)+'</td>'
|
||||||
+ '<td style="padding:0.45rem 0.5rem;font-size:0.72rem">'+icon+' '+esc((r.hype_phase||'').replace(/_/g,' '))+'</td>'
|
+ '<td style="padding:0.45rem 0.5rem;font-size:0.72rem">'+icon+' '+esc((r.hype_phase||'').replace(/_/g,' '))+'</td>'
|
||||||
+ '<td style="padding:0.45rem 0.5rem;text-align:right;font-weight:700;color:'+scoreColor+';font-family:monospace">'+Math.round(r.hype_score)+'</td>'
|
+ '<td style="padding:0.45rem 0.5rem;text-align:right;font-weight:700;color:'+scoreColor+';font-family:monospace">'+Math.round(r.hype_score)+'</td>'
|
||||||
+ '<td style="padding:0.45rem 0.5rem;text-align:right;font-size:0.72rem;color:'+(parseFloat(trend)>0?'#22c55e':'#64748b')+'">'+esc(trend)+'</td>'
|
+ '<td style="padding:0.45rem 0.5rem;text-align:right;font-size:0.72rem;color:'+(parseFloat(trend)>0?'#22c55e':'#64748b')+'">'+esc(trend)+'</td>'
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user