← back to Professional Directory
agents/dedupe-agent/server.js
62 lines
#!/usr/bin/env node
/**
* Dedupe agent — HTTP control plane for entity-resolution passes.
*
* GET /health — { ok, agent, candidates }
* POST /run/link-locations — runs scripts/link_locations_to_orgs.js
*
* Future: NPI-based dedupe, fuzzy name dedupe, organization clustering.
*/
const fs = require('node:fs');
const path = require('path');
const { spawn } = require('node:child_process');
require('dotenv').config({ path: path.resolve(__dirname, '../../.env') });
const express = require('express');
const { pool, query } = require('../shared/db');
const PORT = Number(process.env.PORT_DEDUPE || process.env.PORT || 9873);
const ROOT = path.resolve(__dirname, '../..');
const SCRIPT_LINK = path.join(ROOT, 'scripts/link_locations_to_orgs.js');
const app = express();
app.use(express.json());
// LAN/tailnet auth gate (skip /health for watchdog probes)
const BASIC_AUTH = process.env.BASIC_AUTH || 'admin:DWSecure2024!';
const AUTH_HEADER = 'Basic ' + Buffer.from(BASIC_AUTH).toString('base64');
app.use((req, res, next) => {
if (req.path === '/health' || req.path === '/healthz') return next();
if (req.get('authorization') === AUTH_HEADER) return next();
res.set('WWW-Authenticate', 'Basic realm="mac2-pm2"');
res.status(401).send('auth required');
});
app.get('/health', async (_req, res) => {
try {
const r = await query(`
SELECT
(SELECT COUNT(*) FROM professional_locations) AS prof_locations_total,
(SELECT COUNT(*) FROM professional_locations WHERE organization_id IS NOT NULL) AS linked,
(SELECT COUNT(*) FROM professional_locations WHERE organization_id IS NULL) AS unlinked
`, []);
res.json({ ok: true, agent: 'pd-dedupe', ...r.rows[0] });
} catch (e) {
res.status(500).json({ ok: false, error: e.message });
}
});
app.post('/run/link-locations', (_req, res) => {
if (!fs.existsSync(SCRIPT_LINK)) {
return res.status(500).json({ error: `missing script: ${SCRIPT_LINK}` });
}
const child = spawn('node', [SCRIPT_LINK], {
cwd: ROOT, env: process.env, detached: true, stdio: 'ignore',
});
child.unref();
res.json({ ok: true, started: 'link-locations', pid: child.pid });
});
app.listen(PORT, '0.0.0.0', () => {
console.log(`[pd-dedupe] listening on http://127.0.0.1:${PORT}`);
});