Rene Fichtmueller e9fcda2811 feat: wire finder.ts + switch-docs + Ollama LLM tools to MCP server
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.
2026-04-18 00:21:58 +02:00

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) },
});
}
});