← back to Norma
agents/onboard-agent/skills/report.js
126 lines
/**
* OnBoard Report Skill
*
* Provides discovery statistics and recent match data.
* Returns counts by status, mind_map_links by type, and recent top matches.
*/
const { query } = require('../../shared/db');
const { logAction } = require('../../shared/audit-logger');
const AGENT = 'onboard-agent';
const PLATFORM = 'internal';
/**
* @param {Object} body - Request body
* @param {number} [body.hours_back=24] - Hours back for recent matches
* @param {number} [body.top_n=10] - Number of top matches to return
* @returns {Promise<Object>}
*/
module.exports = async function report(body = {}) {
const hoursBack = body.hours_back || 24;
const topN = body.top_n || 10;
// 1. Count onboard_discoveries by status
const { rows: statusCounts } = await query(
`SELECT status, COUNT(*)::int AS count
FROM onboard_discoveries
GROUP BY status
ORDER BY count DESC`
);
const discoveries = {};
for (const row of statusCounts) {
discoveries[row.status] = row.count;
}
// 2. Count mind_map_links by link_type (for links created by this agent)
const { rows: linkCounts } = await query(
`SELECT link_type, COUNT(*)::int AS count
FROM mind_map_links
WHERE auto_generated = true
AND metadata->>'discovered_by' = 'onboard-agent'
GROUP BY link_type
ORDER BY count DESC`
);
const links = {};
for (const row of linkCounts) {
links[row.link_type] = row.count;
}
// 3. Total mind_map_links (all types)
const { rows: totalLinks } = await query(
`SELECT COUNT(*)::int AS count FROM mind_map_links
WHERE auto_generated = true
AND metadata->>'discovered_by' = 'onboard-agent'`
);
// 4. Recent top matches (last N hours)
const { rows: recentMatches } = await query(
`SELECT d.id, d.match_score, d.trigger_label, d.status,
d.recommended_actions, d.created_at,
o.name AS org_name, o.city, o.state, o.ntee_code
FROM onboard_discoveries d
JOIN organizations o ON d.org_id = o.id
WHERE d.created_at > NOW() - INTERVAL '1 hour' * $1
ORDER BY d.match_score DESC
LIMIT $2`,
[hoursBack, topN]
);
// 5. Total orgs discovered by this agent
const { rows: orgCount } = await query(
`SELECT COUNT(*)::int AS count FROM organizations
WHERE source = 'onboard-agent'`
);
// 6. Agent action summary (last 24h)
const { rows: actionSummary } = await query(
`SELECT action_type, status, COUNT(*)::int AS count,
MAX(created_at) AS last_at
FROM sdcc_agent_actions
WHERE agent = $1
AND created_at > NOW() - INTERVAL '24 hours'
GROUP BY action_type, status
ORDER BY count DESC`,
[AGENT]
);
// Log the report action
await logAction({
agent: AGENT,
actionType: 'report',
platform: PLATFORM,
status: 'success',
content: `Report generated (${hoursBack}h window)`,
responseData: {
discovery_count: Object.values(discoveries).reduce((a, b) => a + b, 0),
link_count: totalLinks[0]?.count || 0,
recent_match_count: recentMatches.length,
},
});
return {
discoveries,
total_discoveries: Object.values(discoveries).reduce((a, b) => a + b, 0),
orgs_discovered: orgCount[0]?.count || 0,
links,
total_links: totalLinks[0]?.count || 0,
recent_matches: recentMatches.map(m => ({
id: m.id,
org_name: m.org_name,
location: `${m.city || '?'}, ${m.state || '?'}`,
ntee_code: m.ntee_code,
match_score: m.match_score,
trigger_topic: m.trigger_label,
status: m.status,
recommended_actions: m.recommended_actions,
discovered_at: m.created_at,
})),
agent_actions_24h: actionSummary,
report_window_hours: hoursBack,
generated_at: new Date().toISOString(),
};
};