MCP Server (packages/mcp-server/src/index.ts): - Register registerSwitchDocTools (switch-docs.ts) — switch documentation lookup - Register finderTools dynamically (finder.ts) — find_flexoptix_for_switch, get_competitor_alerts - Add analyze_market_with_llm tool: qwen2.5:14b via Ollama, enriched with live hype cycle + pricing + news - Add generate_blog_post tool: fo-blog-v5 (fine-tuned) with qwen2.5:14b fallback, enriched with live pricing data - OLLAMA_BASE_URL env var (default: https://ollama.fichtmueller.org) Also includes scraper improvements (ascentoptics, atgbics, gbics, skylane, ebay-enricher), API route updates (blog, blog-sll, health, hot-topics, transceivers, queries), and dashboard hot-topics refresh.
75 lines
3.0 KiB
TypeScript
75 lines
3.0 KiB
TypeScript
import { Router, Request, Response } from "express";
|
|
import { getDbStats } from "../db/queries";
|
|
import { pool } from "../db/client";
|
|
|
|
export const healthRouter = Router();
|
|
|
|
// GET /api/health — Health check with DB stats
|
|
healthRouter.get("/", async (_req: Request, res: Response) => {
|
|
try {
|
|
const start = Date.now();
|
|
const stats = await getDbStats();
|
|
const latencyMs = Date.now() - start;
|
|
|
|
// Verification stats
|
|
const verStats = await pool.query(`
|
|
SELECT
|
|
COUNT(*) FILTER (WHERE price_verified) AS price_verified,
|
|
COUNT(*) FILTER (WHERE image_verified) AS image_verified,
|
|
COUNT(*) FILTER (WHERE details_verified) AS details_verified,
|
|
COUNT(*) FILTER (WHERE fully_verified) AS fully_verified,
|
|
COUNT(*) AS total
|
|
FROM transceivers
|
|
`).catch(() => ({ rows: [{}] }));
|
|
const v = verStats.rows[0] || {};
|
|
|
|
// Stock observations stats
|
|
const stockStats = await pool.query(`
|
|
SELECT
|
|
COUNT(*) AS total_observations,
|
|
COUNT(DISTINCT transceiver_id) AS transceivers_with_stock,
|
|
COUNT(DISTINCT source_vendor_id) AS vendors_with_stock,
|
|
SUM(warehouse_de_qty) FILTER (WHERE warehouse_de_qty > 0) AS total_de_qty,
|
|
SUM(warehouse_global_qty) FILTER (WHERE warehouse_global_qty > 0) AS total_global_qty,
|
|
MAX(time) AS last_observation_at
|
|
FROM stock_observations
|
|
`).catch(() => ({ rows: [{}] }));
|
|
const s = stockStats.rows[0] || {};
|
|
|
|
res.json({
|
|
success: true,
|
|
status: "healthy",
|
|
version: "0.3.0",
|
|
uptime: process.uptime(),
|
|
database: {
|
|
connected: true,
|
|
latency_ms: latencyMs,
|
|
stats,
|
|
},
|
|
verification: {
|
|
price_verified: Number(v.price_verified || 0),
|
|
image_verified: Number(v.image_verified || 0),
|
|
details_verified: Number(v.details_verified || 0),
|
|
fully_verified: Number(v.fully_verified || 0),
|
|
total: Number(v.total || 0),
|
|
price_coverage_pct: v.total ? Math.round(Number(v.price_verified) / Number(v.total) * 100) : 0,
|
|
fully_verified_pct: v.total ? Math.round(Number(v.fully_verified) / Number(v.total) * 100) : 0,
|
|
},
|
|
stock: {
|
|
total_observations: Number(s.total_observations || 0),
|
|
transceivers_with_stock: Number(s.transceivers_with_stock || 0),
|
|
vendors_with_stock: Number(s.vendors_with_stock || 0),
|
|
total_de_qty: Number(s.total_de_qty || 0),
|
|
total_global_qty: Number(s.total_global_qty || 0),
|
|
last_observation_at: s.last_observation_at ?? null,
|
|
},
|
|
});
|
|
} catch (err) {
|
|
res.status(503).json({
|
|
success: false,
|
|
status: "unhealthy",
|
|
database: { connected: false, error: String(err) },
|
|
});
|
|
}
|
|
});
|