← back to Norma
agents/datasource-agent/server.js
128 lines
/**
* Norma Data Source Agent — Port 9807
*
* Discovers data APIs, scrapes newspaper front pages, collects non-profit
* statements, and rates all sources for advocacy relevance.
*
* Skills: discover (discover-apis), post (scrape-frontpages),
* reply (collect-statements), monitor (rate-sources), report
*/
const { createAgentServer } = require('../shared/agent-base');
// Import skills
const discover = require('./skills/discover-apis');
const post = require('./skills/scrape-frontpages');
const reply = require('./skills/collect-statements');
const monitor = require('./skills/rate-sources');
const report = require('./skills/report');
const { scrapePetitions } = require('./skills/scrape-petitions');
const PORT = 9807;
const AGENT_NAME = 'datasource-agent';
const { app, start, scheduler } = createAgentServer({
name: AGENT_NAME,
port: PORT,
skills: {
discover, // discover-apis
post, // scrape-frontpages
reply, // collect-statements
monitor, // rate-sources
report, // report
},
cronJobs: [
{
name: 'discover-apis',
schedule: '0 6,14,22 * * *', // 3x/day at 6am, 2pm, 10pm
fn: async () => {
console.log(`[${AGENT_NAME}] Cron: discovering APIs...`);
try {
const result = await discover({ score: true });
console.log(`[${AGENT_NAME}] Cron discover-apis complete: ${result.discovered} discovered, ${result.scored} scored`);
} catch (err) {
console.error(`[${AGENT_NAME}] Cron discover-apis error:`, err.message);
}
},
},
{
name: 'scrape-frontpages',
schedule: '0 7,12,18 * * *', // 3x/day at 7am, 12pm, 6pm
fn: async () => {
console.log(`[${AGENT_NAME}] Cron: scraping front pages...`);
try {
const result = await post({ analyze: true });
console.log(`[${AGENT_NAME}] Cron scrape-frontpages complete: ${result.scraped} scraped, ${result.relevant} relevant`);
} catch (err) {
console.error(`[${AGENT_NAME}] Cron scrape-frontpages error:`, err.message);
}
},
},
{
name: 'collect-statements',
schedule: '30 */4 * * *', // every 4 hours at :30
fn: async () => {
console.log(`[${AGENT_NAME}] Cron: collecting statements...`);
try {
const result = await reply({ analyze: true });
console.log(`[${AGENT_NAME}] Cron collect-statements complete: ${result.orgs_checked} orgs, ${result.statements_found} statements`);
} catch (err) {
console.error(`[${AGENT_NAME}] Cron collect-statements error:`, err.message);
}
},
},
{
name: 'rate-sources',
schedule: '0 */2 * * *', // every 2 hours
fn: async () => {
console.log(`[${AGENT_NAME}] Cron: rating sources...`);
try {
const result = await monitor({});
console.log(`[${AGENT_NAME}] Cron rate-sources complete: ${result.total_rated} rated`);
} catch (err) {
console.error(`[${AGENT_NAME}] Cron rate-sources error:`, err.message);
}
},
},
{
name: 'scrape-petitions',
schedule: '15 8,16 * * *', // 2x/day at 8:15am and 4:15pm
fn: async () => {
console.log(`[${AGENT_NAME}] Cron: scraping petitions...`);
try {
const result = await scrapePetitions({ score: true });
console.log(`[${AGENT_NAME}] Cron scrape-petitions complete: ${result.total_found} found, ${result.total_upserted} upserted`);
} catch (err) {
console.error(`[${AGENT_NAME}] Cron scrape-petitions error:`, err.message);
}
},
},
],
});
// ──────────────────────────────────────
// Custom skill route: scrape-petitions
// The agent-base registers a catch-all 404 and error handler at the end
// of the router stack. We pop those, add our custom route, then re-add them
// so that our route is matched before the catch-all.
// ──────────────────────────────────────
const stack = app._router.stack;
const errorHandler = stack.pop(); // error handler (last)
const notFoundHandler = stack.pop(); // 404 catch-all (second to last)
app.post('/api/skill/scrape-petitions', async (req, res) => {
try {
const result = await scrapePetitions(req.body || {});
res.json({ success: true, skill: 'scrape-petitions', result });
} catch (err) {
console.error(`[${AGENT_NAME}] Skill "scrape-petitions" error:`, err.message);
res.status(500).json({ success: false, skill: 'scrape-petitions', error: err.message });
}
});
// Re-add 404 and error handlers at the end
if (notFoundHandler) stack.push(notFoundHandler);
if (errorHandler) stack.push(errorHandler);
start();