UNPKG

pms-analysis-reports-mcp-server

Version:

PMS analysis reports server handling maintenance reports, equipment analysis, compliance tracking, and performance metrics with ERP access for data extraction

333 lines 12.8 kB
import { config } from './config.js'; import { logger } from './logger.js'; import { getCompanyImoNumbers, shouldBypassImoFiltering, isValidImoForCompany } from './imoUtils.js'; /** * IMO field names that should be checked for filtering */ const IMO_FIELD_NAMES = [ 'imo', 'vesselImo', 'imoNumber', 'IMO', 'vessel_imo', 'imo_number', 'vesselIMO' ]; /** * Check if an object contains any IMO field * @param obj - Object to check * @returns Object containing IMO field name and value, or null if none found */ function findImoField(obj) { if (!obj || typeof obj !== 'object') { return null; } for (const fieldName of IMO_FIELD_NAMES) { if (obj.hasOwnProperty(fieldName) && obj[fieldName] !== null && obj[fieldName] !== undefined) { return { fieldName, value: obj[fieldName] }; } } return null; } /** * Filter an array by removing items with unauthorized IMO numbers * @param array - Array to filter * @param stats - Statistics object to update * @returns Filtered array and updated statistics */ function filterArrayByImo(array, stats) { logger.debug(`Starting array filtering`, { arrayLength: array.length }); const filteredArray = array.filter(item => { stats.itemsProcessed++; // Check if this item has an IMO field at any level const hasUnauthorizedImo = checkForUnauthorizedImo(item); if (hasUnauthorizedImo.found) { stats.itemsFiltered++; stats.unauthorizedImos.push(hasUnauthorizedImo.location); logger.debug(`Filtered item with unauthorized IMO: ${hasUnauthorizedImo.location}`); return false; } return true; }); logger.debug(`Array filtering complete`, { originalLength: array.length, filteredLength: filteredArray.length, itemsFiltered: stats.itemsFiltered }); return { filtered: filteredArray, stats }; } /** * Recursively check for unauthorized IMO numbers in an object * @param obj - Object to check * @returns Object with found flag and location string */ function checkForUnauthorizedImo(obj) { if (!obj || typeof obj !== 'object') { return { found: false, location: '' }; } // Check direct IMO fields const imoField = findImoField(obj); if (imoField && !isValidImoForCompany(imoField.value)) { return { found: true, location: `${imoField.fieldName}: ${imoField.value}` }; } // Recursively check nested objects and arrays for (const key in obj) { if (obj.hasOwnProperty(key)) { const value = obj[key]; if (Array.isArray(value)) { // Check each item in the array for (let i = 0; i < value.length; i++) { const result = checkForUnauthorizedImo(value[i]); if (result.found) { return { found: true, location: `${key}[${i}].${result.location}` }; } } } else if (typeof value === 'object' && value !== null) { // Recursively check nested objects const result = checkForUnauthorizedImo(value); if (result.found) { return { found: true, location: `${key}.${result.location}` }; } } } } return { found: false, location: '' }; } /** * Recursively filter response content for unauthorized IMO numbers * @param content - Content to filter * @param stats - Statistics object to update * @returns Filtered content and updated statistics */ function filterResponseContent(content, stats) { if (content === null || content === undefined) { return { filtered: content, stats }; } // Handle arrays if (Array.isArray(content)) { const result = filterArrayByImo(content, stats); return { filtered: result.filtered, stats: result.stats }; } // Handle objects if (typeof content === 'object') { const filtered = { ...content }; // Check if this object itself has an IMO field that should be filtered const imoField = findImoField(content); if (imoField && !isValidImoForCompany(imoField.value)) { stats.itemsProcessed++; stats.itemsFiltered++; stats.unauthorizedImos.push(`${imoField.fieldName}: ${imoField.value}`); logger.debug(`Filtered object with unauthorized IMO: ${imoField.fieldName} = ${imoField.value}`); return { filtered: null, stats }; } // Recursively filter object properties for (const key in content) { if (content.hasOwnProperty(key)) { const result = filterResponseContent(content[key], stats); filtered[key] = result.filtered; stats = result.stats; } } return { filtered, stats }; } // For primitive values, return as-is return { filtered: content, stats }; } /** * Main function to filter tool responses by company IMO numbers * @param response - The tool response to filter * @returns Filtered response */ export async function filterResponseByCompanyImos(response) { const startTime = Date.now(); const companyName = config.companyName; // Skip filtering for admin companies if (!companyName || shouldBypassImoFiltering(companyName)) { logger.debug(`Skipping IMO filtering for admin company: ${companyName}`); return response; } // Skip filtering if no company IMO numbers are configured const companyImos = getCompanyImoNumbers(); if (companyImos.length === 0) { logger.warn('No company IMO numbers configured. Skipping IMO filtering.'); return response; } try { const filteredResponse = []; const stats = { itemsProcessed: 0, itemsFiltered: 0, unauthorizedImos: [], processingTimeMs: 0 }; // Process each response item for (const item of response) { if (item.type === 'text') { try { // Try to parse as JSON const parsedContent = JSON.parse(item.text); logger.debug(`Processing response item for filtering`, { companyName, companyImos: companyImos.slice(0, 5), // Log first 5 IMOs hasHits: parsedContent.hits ? parsedContent.hits.length : 0 }); const originalHitsCount = parsedContent.hits ? parsedContent.hits.length : 0; const result = filterResponseContent(parsedContent, stats); const filteredHitsCount = result.filtered && result.filtered.hits ? result.filtered.hits.length : 0; // Check if all hits were filtered out due to unauthorized IMO if (originalHitsCount > 0 && filteredHitsCount === 0 && stats.itemsFiltered > 0) { // Return informative message instead of empty response const unauthorizedImos = [...new Set(stats.unauthorizedImos.map(imo => imo.split(': ')[1]))]; const errorMessage = `The queried vessel${unauthorizedImos.length > 1 ? 's are' : ' is'} not under ${companyName}. ` + `IMO number${unauthorizedImos.length > 1 ? 's' : ''} ${unauthorizedImos.join(', ')} ${unauthorizedImos.length > 1 ? 'are' : 'is'} not associated with this company.`; filteredResponse.push({ ...item, text: errorMessage }); } else if (result.filtered !== null && result.filtered !== undefined) { // Include the item if it has content after filtering filteredResponse.push({ ...item, text: JSON.stringify(result.filtered, null, 2) }); } } catch (parseError) { // If it's not JSON, pass through as-is logger.debug(`Non-JSON response item passed through`, { parseError: parseError instanceof Error ? parseError.message : String(parseError) }); filteredResponse.push(item); } } else { // For non-text items, pass through as-is filteredResponse.push(item); } } stats.processingTimeMs = Date.now() - startTime; // Log filtering statistics if (stats.itemsFiltered > 0) { logger.warn(`Filtered ${stats.itemsFiltered} unauthorized items from response`, { companyName, itemsProcessed: stats.itemsProcessed, itemsFiltered: stats.itemsFiltered, unauthorizedImos: stats.unauthorizedImos.slice(0, 10), // Log first 10 processingTimeMs: stats.processingTimeMs }); } else { logger.debug(`IMO filtering completed: ${stats.itemsProcessed} items processed, none filtered`, { companyName, processingTimeMs: stats.processingTimeMs }); } return filteredResponse; } catch (error) { logger.error('Error during IMO response filtering:', error); // Return original response if filtering fails return response; } } /** * Filter a single response item by IMO numbers * @param item - Single response item to filter * @returns Filtered item or null if should be filtered out */ export function filterSingleResponseItem(item) { const companyName = config.companyName; // Skip filtering for admin companies if (!companyName || shouldBypassImoFiltering(companyName)) { return item; } // Skip filtering if no company IMO numbers are configured const companyImos = getCompanyImoNumbers(); if (companyImos.length === 0) { return item; } const stats = { itemsProcessed: 0, itemsFiltered: 0, unauthorizedImos: [], processingTimeMs: 0 }; const result = filterResponseContent(item, stats); return result.filtered; } /** * Check if a response contains any unauthorized IMO numbers * @param response - Response to check * @returns True if response contains unauthorized IMO numbers */ export function hasUnauthorizedImos(response) { const companyName = config.companyName; // Skip check for admin companies if (!companyName || shouldBypassImoFiltering(companyName)) { return false; } const companyImos = getCompanyImoNumbers(); if (companyImos.length === 0) { return false; } for (const item of response) { if (item.type === 'text') { try { const parsedContent = JSON.parse(item.text); const stats = { itemsProcessed: 0, itemsFiltered: 0, unauthorizedImos: [], processingTimeMs: 0 }; filterResponseContent(parsedContent, stats); if (stats.itemsFiltered > 0) { return true; } } catch (parseError) { // Ignore parse errors for this check } } } return false; } /** * Get detailed filtering statistics for a response * @param response - Response to analyze * @returns Detailed statistics about IMO filtering */ export function getFilteringStats(response) { const companyName = config.companyName; const startTime = Date.now(); const stats = { itemsProcessed: 0, itemsFiltered: 0, unauthorizedImos: [], processingTimeMs: 0 }; // Skip analysis for admin companies if (!companyName || shouldBypassImoFiltering(companyName)) { stats.processingTimeMs = Date.now() - startTime; return stats; } const companyImos = getCompanyImoNumbers(); if (companyImos.length === 0) { stats.processingTimeMs = Date.now() - startTime; return stats; } for (const item of response) { if (item.type === 'text') { try { const parsedContent = JSON.parse(item.text); filterResponseContent(parsedContent, stats); } catch (parseError) { // Ignore parse errors for statistics } } } stats.processingTimeMs = Date.now() - startTime; return stats; } //# sourceMappingURL=responseFilter.js.map