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
JavaScript
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