UNPKG

hubot-pollen

Version:

Retrieves the latest from the Pollen.com API

244 lines (216 loc) 8.57 kB
// Description: // Pollen tools for Ollama integration // // Configuration: // HUBOT_POLLEN_OLLAMA_ENABLED Set to 'true' to register pollen tools with hubot-ollama module.exports = (robot, { getOpenMeteoGeocode, getOpenMeteoForecast, getPollenForecastData, summarizeOpenMeteoPollen, buildOpenMeteoLocationLabel, extractFallbackZip, formatIndexLabel, }, _registryForTest = null) => { /** * Register tools for Ollama integration if enabled */ if (process.env.HUBOT_POLLEN_OLLAMA_ENABLED !== 'true') { robot.logger.debug('Pollen Ollama tools disabled (set HUBOT_POLLEN_OLLAMA_ENABLED=true to enable)'); return; } robot.logger.info('Registering Pollen tools with Ollama'); try { let registry; if (_registryForTest) { registry = _registryForTest; } else { // Resolve the registry once using explicit search paths for linked installs const path = require('path'); const registryPath = require.resolve('hubot-ollama/src/tool-registry', { paths: [ __dirname, path.resolve(__dirname, '../../node_modules'), path.resolve(__dirname, '../../../node_modules'), process.cwd(), ], }); registry = require(registryPath); } robot.logger.info('Tool registry loaded successfully'); /** * Helper: build a Pollen.com result object from a raw forecast response */ const buildPollenComResult = (forecast) => { const period = forecast.Location && forecast.Location.periods && forecast.Location.periods[1]; if (!period || period.Index == null) { return { source: 'pollen.com', location: forecast.Location ? forecast.Location.DisplayLocation : 'Unknown', error: 'No forecast available', }; } const triggers = period.Triggers ? Object.values(period.Triggers).filter((t) => t && t.Name).map((t) => t.Name) : []; return { source: 'pollen.com', location: forecast.Location.DisplayLocation, zip: forecast.Location.ZIP, index: period.Index, level: formatIndexLabel(period.Index), triggers, }; }; /** * Tool: get_pollen_forecast * Geocodes a location string then queries Open-Meteo for pollen data, * falling back to Pollen.com when a US ZIP is available. */ const pollenForecastTool = { description: 'Get the current pollen forecast for a location. Returns pollen intensity, peak concentration, and active pollen types. Uses Open-Meteo as the primary source with automatic fallback to Pollen.com for US ZIP codes when Open-Meteo has no data.', parameters: { type: 'object', required: ['location'], properties: { location: { type: 'string', description: 'Location to get pollen forecast for. Can be a city name, city/state, city/country, or US ZIP code (e.g., "Nashville, TN", "London", "37203").', }, }, }, handler: async ({ location }) => new Promise((resolve, reject) => { const query = `${location}`.trim(); const fallbackZipFromQuery = extractFallbackZip(query, null); getOpenMeteoGeocode(query, (geocodeErr, geoLocation) => { if (geocodeErr) { robot.logger.debug(`Geocoding failed for "${query}":`, geocodeErr.message); if (fallbackZipFromQuery) { getPollenForecastData(fallbackZipFromQuery, (pollenErr, forecast) => { if (pollenErr) { reject(new Error(`Could not retrieve pollen data: ${pollenErr.message}`)); return; } resolve(buildPollenComResult(forecast)); }); return; } reject(new Error(`Could not find location: ${location}`)); return; } const fallbackZip = extractFallbackZip(query, geoLocation); getOpenMeteoForecast(geoLocation, (forecastErr, forecast) => { if (!forecastErr) { const summary = summarizeOpenMeteoPollen(forecast); if (summary) { robot.logger.debug('Open-Meteo pollen result:', summary); resolve({ source: 'open-meteo', location: buildOpenMeteoLocationLabel(geoLocation), coordinates: { latitude: geoLocation.latitude, longitude: geoLocation.longitude, }, intensity: summary.intensity, peak_grains_per_m3: summary.topPeak, pollen_types: summary.entries.map((e) => ({ type: e.label, peak: e.peak })), }); return; } } if (fallbackZip) { robot.logger.debug('Open-Meteo had no usable pollen data, falling back to Pollen.com', { fallbackZip }); getPollenForecastData(fallbackZip, (pollenErr, pollenForecast) => { if (pollenErr) { reject(new Error(`Could not retrieve pollen data: ${pollenErr.message}`)); return; } resolve(buildPollenComResult(pollenForecast)); }); return; } if (forecastErr) { reject(new Error(`Could not retrieve pollen forecast: ${forecastErr.message}`)); return; } // Open-Meteo geocoded but returned no usable pollen and no ZIP fallback available resolve({ source: 'open-meteo', location: buildOpenMeteoLocationLabel(geoLocation), coordinates: { latitude: geoLocation.latitude, longitude: geoLocation.longitude, }, intensity: 'None', peak_grains_per_m3: 0, pollen_types: [], }); }); }); }), }; registry.registerTool('get_pollen_forecast', pollenForecastTool); robot.logger.info('Registered get_pollen_forecast tool'); /** * Tool: get_pollen_by_coordinates * Queries Open-Meteo directly for pollen data at given coordinates. */ const pollenByCoordinatesTool = { description: 'Get the current pollen forecast for specific geographic coordinates using Open-Meteo. Returns pollen intensity, peak concentration, and active pollen types. Useful when you already have latitude and longitude.', parameters: { type: 'object', required: ['latitude', 'longitude'], properties: { latitude: { type: 'number', description: 'Latitude coordinate', }, longitude: { type: 'number', description: 'Longitude coordinate', }, }, }, handler: async ({ latitude, longitude }) => new Promise((resolve, reject) => { const coords = { latitude, longitude }; getOpenMeteoForecast(coords, (err, forecast) => { if (err) { reject(new Error(`Could not retrieve pollen forecast: ${err.message}`)); return; } const summary = summarizeOpenMeteoPollen(forecast); if (!summary) { resolve({ source: 'open-meteo', coordinates: { latitude, longitude }, intensity: 'None', peak_grains_per_m3: 0, pollen_types: [], }); return; } robot.logger.debug('Open-Meteo coordinate pollen result:', summary); resolve({ source: 'open-meteo', coordinates: { latitude, longitude }, intensity: summary.intensity, peak_grains_per_m3: summary.topPeak, pollen_types: summary.entries.map((e) => ({ type: e.label, peak: e.peak })), }); }); }), }; registry.registerTool('get_pollen_by_coordinates', pollenByCoordinatesTool); robot.logger.info('Registered get_pollen_by_coordinates tool'); robot.logger.info('Pollen tools registered with Ollama'); } catch (err) { // hubot-ollama not installed, silently ignore if (err.code !== 'MODULE_NOT_FOUND') { robot.logger.error(`Failed to register Pollen tools with Ollama: ${err.message}`, err); } else { robot.logger.debug('hubot-ollama not installed, skipping Ollama tool registration'); } } };