← back to Norma
agents/onboard-agent/lib/foundation-matcher.js
70 lines
/**
* Foundation Matcher
*
* Matches discovered nonprofit organizations with foundations
* based on overlapping focus areas and geographic alignment.
*/
/**
* Find foundations that match an organization's issue areas and location.
*
* @param {Object} pool - pg Pool instance (or query function container)
* @param {string[]} orgIssues - Issue areas for the org (from NTEE mapping)
* @param {string} orgState - State abbreviation of the org (e.g. 'CA', 'NY')
* @returns {Promise<Array<{ foundation_id: string, foundation_name: string, overlap_areas: string[], geo_match: boolean, score: number }>>}
*/
async function matchFoundations(pool, orgIssues, orgState) {
// Query active foundations, ordered by annual giving
const { rows } = await pool.query(`
SELECT id, name, focus_areas, geographic_focus, annual_giving
FROM foundations
WHERE 1=1
ORDER BY annual_giving DESC NULLS LAST
LIMIT 100
`);
const matches = [];
for (const f of rows) {
const fAreas = Array.isArray(f.focus_areas) ? f.focus_areas : [];
if (fAreas.length === 0) continue;
// Find overlapping focus areas
const overlap = fAreas.filter(a =>
orgIssues.some(i =>
a.toLowerCase().includes(i) || i.includes(a.toLowerCase())
)
);
if (overlap.length === 0) continue;
// Check geographic match
const geoFocus = Array.isArray(f.geographic_focus) ? f.geographic_focus : [];
const geoMatch = geoFocus.length > 0
? (geoFocus.some(g =>
g.toLowerCase().includes((orgState || '').toLowerCase()) ||
g.toLowerCase().includes('national') ||
g.toLowerCase() === 'all'
))
: false;
// Score: 60% focus area overlap, 40% geographic alignment
const overlapScore = (overlap.length / Math.max(fAreas.length, 1)) * 60;
const geoScore = geoMatch ? 40 : 0;
matches.push({
foundation_id: f.id,
foundation_name: f.name,
overlap_areas: overlap,
geo_match: geoMatch,
annual_giving: f.annual_giving,
score: overlapScore + geoScore,
});
}
// Return top 10 sorted by score descending
return matches.sort((a, b) => b.score - a.score).slice(0, 10);
}
module.exports = { matchFoundations };