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
1,166 lines • 150 kB
JavaScript
import { logger } from "../utils/logger.js";
import { getDatabase, getELTDatabase, getEngineDataDatabase } from "../utils/mongodb.js";
import { searchDocuments, typesenseClient } from "../utils/typesense.js";
import { getArtifact, getDataLink, insertDataLinkToMongodb } from '../utils/helpers.js';
import { parseDocumentLink } from '../utils/documentParser.js';
import { getMongoClient } from '../utils/mongodb.js';
import { ObjectId } from 'mongodb';
import markdown from '../utils/markdown.js';
const { markdownToHtmlLink } = markdown;
import { pushToTypesense } from '../utils/typesense.js';
import { config } from "../utils/config.js";
// MongoDB database name
const MONGODB_DATABASE = config.secondaryDbName;
// Engine names mapping
const ENGINE_NAMES_MAPPING = {
"AE1": "Auxillary Engine 1",
"AE2": "Auxillary Engine 2",
"AE3": "Auxillary Engine 3",
"AE4": "Auxillary Engine 4",
"AE5": "Auxillary Engine 5",
"ME": "Main Engine"
};
// Helper functions
function getDocumentsFromHits(hits) {
return (hits || []).map((hit) => hit.document);
}
function stringToPromise(value) {
return Promise.resolve(value ?? null);
}
async function getDataLinkHelper(documents) {
return getDataLink(documents);
}
function getDataLinkWithDefault(dataLink) {
return dataLink ?? "no_link";
}
async function insertDataLinkToMongodbHelper(dataLink, linkHeader, sessionId, imo, vesselName) {
if (!dataLink)
return;
await insertDataLinkToMongodb(dataLink, linkHeader, sessionId, String(imo), vesselName ?? undefined);
}
function getArtifactHelper(toolName, url) {
return getArtifact(toolName, url ?? "no_link");
}
async function handleReportLink(reportLink) {
if (!reportLink)
return null;
try {
return await Promise.resolve(parseDocumentLink(reportLink));
}
catch (error) {
return null;
}
}
function convertPmsDates(doc) {
// Convert date strings to Date objects for PMS collection
if (!doc)
return doc;
const dateFields = ['jobDueDate', 'jobDoneDate', 'defermentRequestedDate', 'createdDate', 'lastModified'];
const convertedDoc = { ...doc };
dateFields.forEach(field => {
if (convertedDoc[field] && typeof convertedDoc[field] === 'string') {
convertedDoc[field] = new Date(convertedDoc[field]);
}
});
return convertedDoc;
}
function convertLubeOilDates(doc) {
// Convert Unix timestamps to readable dates for fuel oil data
if (!doc)
return doc;
const timestampFields = ['bunkerDate', 'testDate', 'sampleDate', 'createdDate', 'lastModified'];
const convertedDoc = { ...doc };
timestampFields.forEach(field => {
if (convertedDoc[field] && typeof convertedDoc[field] === 'number') {
// Convert Unix timestamp to ISO string
convertedDoc[field] = new Date(convertedDoc[field] * 1000).toISOString();
}
});
return convertedDoc;
}
// Typesense tool implementations
export async function lubeReportTableQuery(args) {
logger.info("Executing lube_report_table_query", { args });
try {
const collection = args.collection;
const query = args.query || {};
if (!collection) {
throw new Error("Missing required parameter: 'collection'.");
}
// Validate required query fields
const requiredQueryFields = ["q", "query_by"];
for (const field of requiredQueryFields) {
if (!query[field] || !query[field]) {
throw new Error(`Missing required query field: '${field}'.`);
}
}
logger.debug(`Querying Typesense collection '${collection}' with:`, query);
// Use the existing typesense client to search
const results = await typesenseClient.collections(collection).documents().search(query);
const hits = results.hits || [];
const formattedHits = [];
for (const hit of hits) {
let formattedDoc = hit.document || {};
if (collection === "pms") {
formattedDoc = convertPmsDates(formattedDoc);
}
formattedHits.push(formattedDoc);
}
const formattedResults = {
found: results.found || 0,
out_of: results.out_of || 0,
page: results.page || 1,
hits: formattedHits
};
// Generate link and artifact data
const link = await getDataLinkHelper(formattedHits) || '';
const artifactData = getArtifactHelper("typesense_query", link);
const content = {
type: "text",
text: JSON.stringify(formattedResults, null, 2),
title: `Search results for ${collection}`,
format: "json"
};
const artifact = {
type: "text",
text: JSON.stringify(artifactData, null, 2),
title: `search results for ${collection}`,
format: "json"
};
return [content, artifact];
}
catch (error) {
logger.error("Error in lubeReportTableQuery", { error });
throw new Error(`Typesense query failed: ${error instanceof Error ? error.message : String(error)}`);
}
}
export async function smartFuelOilTableSearch(args) {
logger.info("Executing universal_fuel_analysis_search", { args });
try {
const collection = "fuel_oil_data";
const sessionId = args.session_id || "testing";
const queryText = args.query || "*";
const filters = args.filters || {};
const sortBy = args.sort_by || "relevance";
const sortOrder = args.sort_order || "desc";
const maxResults = args.max_results || 10;
// Compose filter_by string from filters
const filterParts = [];
if (filters) {
for (const [key, value] of Object.entries(filters)) {
if (key === "bunkerDate_range" && typeof value === 'object' && value !== null) {
const dateRange = value;
const startDate = dateRange.start_date;
const endDate = dateRange.end_date;
if (startDate) {
// Convert to Unix timestamp if it's not already
if (typeof startDate === 'string') {
try {
const dtObj = new Date(startDate.replace('Z', '+00:00'));
const startTs = Math.floor(dtObj.getTime() / 1000);
filterParts.push(`bunkerDate:>=${startTs}`);
}
catch (error) {
logger.warn(`Invalid start_date format: ${startDate}`);
}
}
else {
filterParts.push(`bunkerDate:>=${startDate}`);
}
}
if (endDate) {
// Convert to Unix timestamp if it's not already
if (typeof endDate === 'string') {
try {
const dtObj = new Date(endDate.replace('Z', '+00:00'));
const endTs = Math.floor(dtObj.getTime() / 1000);
filterParts.push(`bunkerDate:<=${endTs}`);
}
catch (error) {
logger.warn(`Invalid end_date format: ${endDate}`);
}
}
else {
filterParts.push(`bunkerDate:<=${endDate}`);
}
}
}
else if (key === "sulfur_range" && typeof value === 'object' && value !== null) {
const sulfurRange = value;
const minSulfur = sulfurRange.min_sulfur;
const maxSulfur = sulfurRange.max_sulfur;
if (minSulfur !== null && minSulfur !== undefined) {
filterParts.push(`sulfur:>=${minSulfur}`);
}
if (maxSulfur !== null && maxSulfur !== undefined) {
filterParts.push(`sulfur:<=${maxSulfur}`);
}
}
else if (key === "density_range" && typeof value === 'object' && value !== null) {
const densityRange = value;
const minDensity = densityRange.min_density;
const maxDensity = densityRange.max_density;
if (minDensity !== null && minDensity !== undefined) {
filterParts.push(`density:>=${minDensity}`);
}
if (maxDensity !== null && maxDensity !== undefined) {
filterParts.push(`density:<=${maxDensity}`);
}
}
else if (value !== null && value !== undefined) {
filterParts.push(`${key}:=${value}`);
}
}
}
const filterBy = filterParts.length > 0 ? filterParts.join(" && ") : "";
// Prepare query parameters
const queryBy = "vesselName,bunkerPort,fuelType,supplier,testLab,rating,samplingMethod,samplingPoint,barge,complianceComments";
// Exclude fields to reduce response size
const excludeFields = "vesselId,docId,fleetId,fleetManagerId,technicalSuperintendentId,_id,ownerId";
const query = {
q: queryText,
query_by: queryBy,
per_page: maxResults,
exclude_fields: excludeFields
};
if (filterBy) {
query.filter_by = filterBy;
}
// Apply sorting if not relevance
if (sortBy !== "relevance") {
const sortDirection = sortOrder === "asc" ? "" : ":desc";
query.sort_by = `${sortBy}${sortDirection}`;
}
logger.debug(`Querying Typesense collection '${collection}' with:`, query);
// Execute the search
const results = await typesenseClient.collections(collection).documents().search(query);
// Process results
const hits = results.hits || [];
const filteredHits = [];
for (const hit of hits) {
const documentData = convertPmsDates(hit.document || {});
filteredHits.push({
id: documentData.id,
score: hit.text_match || 0,
document: documentData
});
}
// Format the results
const formattedResults = {
found: results.found || 0,
out_of: results.out_of || 0,
page: results.page || 1,
hits: filteredHits
};
const content = {
type: "text",
text: JSON.stringify(formattedResults, null, 2),
title: `Fuel oil analysis search results for '${queryText}'`,
format: "json"
};
// Generate artifact for visualization if there are results
let artifactData = null;
if (filteredHits.length > 0) {
const documents = hits.map((hit) => hit.document);
const link = await getDataLinkHelper(documents);
artifactData = getArtifactHelper("universal_fuel_analysis_search", link || '');
const artifact = {
type: "text",
text: JSON.stringify(artifactData, null, 2),
title: `Fuel oil analysis search results for ${collection}`,
format: "json"
};
return [content, artifact];
}
return [content];
}
catch (error) {
logger.error("Error in smartFuelOilTableSearch", { error });
const errorMessage = `Error searching fuel oil analysis data: ${error instanceof Error ? error.message : String(error)}`;
return [
{
type: "error",
content: errorMessage,
title: "Error searching fuel oil analysis data"
}
];
}
}
export async function smartPmsSearch(args) {
logger.info("Executing universal_pms_maintenance_search", { args });
try {
const collection = "pms";
const sessionId = args.session_id || "testing";
const queryText = args.query || "*";
const filters = args.filters || {};
const sortBy = args.sort_by || "relevance";
const sortOrder = args.sort_order || "asc";
const maxResults = args.max_results || 10;
// Check for IMO in filters and validate it before processing
if (filters && filters.imo) {
const { validateImoNumber } = await import('../utils/imoUtils.js');
const { config } = await import('../utils/config.js');
const validation = validateImoNumber(filters.imo, config.companyName);
if (!validation.isValid) {
logger.warn(`Invalid IMO number in universal_pms_maintenance_search: ${filters.imo}`);
return [{
type: "text",
text: validation.errorMessage || "Invalid IMO number",
title: "IMO Validation Error"
}];
}
}
// Compose filter_by string from filters
const filterParts = [];
if (filters) {
for (const [key, value] of Object.entries(filters)) {
if (value === null || value === undefined) {
continue;
}
if (key.endsWith("_range") && typeof value === 'object' && value !== null) {
if (key === "remainingHrsNextOverhaul_range") {
const hoursRange = value;
const minHours = hoursRange.min_hours;
const maxHours = hoursRange.max_hours;
if (minHours !== null && minHours !== undefined) {
filterParts.push(`remainingHrsNextOverhaul:>=${minHours}`);
}
if (maxHours !== null && maxHours !== undefined) {
filterParts.push(`remainingHrsNextOverhaul:<=${maxHours}`);
}
}
else if (key === "remainingDaysNextOverhaul_range") {
const daysRange = value;
const minDays = daysRange.min_days;
const maxDays = daysRange.max_days;
if (minDays !== null && minDays !== undefined) {
filterParts.push(`remainingDaysNextOverhaul:>=${minDays}`);
}
if (maxDays !== null && maxDays !== undefined) {
filterParts.push(`remainingDaysNextOverhaul:<=${maxDays}`);
}
}
else if (key === "jobDueDate_range" || key === "jobDoneDate_range" || key === "defermentRequestedDate_range") {
const dateRange = value;
const startDate = dateRange.start_date;
const endDate = dateRange.end_date;
const fieldBase = key.replace("_range", "");
if (startDate) {
try {
const startTs = Math.floor(new Date(startDate).getTime() / 1000);
filterParts.push(`${fieldBase}:>=${startTs}`);
}
catch (error) {
logger.warn(`Invalid start_date format for ${key}: ${startDate}`);
}
}
if (endDate) {
try {
const endTs = Math.floor(new Date(endDate).getTime() / 1000);
filterParts.push(`${fieldBase}:<=${endTs}`);
}
catch (error) {
logger.warn(`Invalid end_date format for ${key}: ${endDate}`);
}
}
}
}
else if (value !== null && value !== undefined) {
if (typeof value === 'string') {
// Remove quotes from JSON stringified value to match Python behavior
const cleanValue = JSON.stringify(value).replace(/^"|"$/g, '');
filterParts.push(`${key}:=${cleanValue}`);
}
else {
filterParts.push(`${key}:=${value}`);
}
}
}
}
const filterBy = filterParts.length > 0 ? filterParts.join(" && ") : "";
// Prepare query parameters
const queryBy = "jobTitle,component,vesselName";
const excludeFields = "embedding,_id,vesselId,docId,fleetId,fleetManagerId,technicalSuperintendentId";
const query = {
q: queryText,
query_by: queryBy,
per_page: maxResults,
exclude_fields: excludeFields
};
if (filterBy) {
query.filter_by = filterBy;
}
if (sortBy !== "relevance") {
const sortDirection = sortOrder === "asc" ? "" : ":desc";
query.sort_by = `${sortBy}${sortDirection}`;
}
logger.debug(`Querying Typesense collection '${collection}' with:`, query);
// Execute the search
const results = await typesenseClient.collections(collection).documents().search(query);
// Process results
const hits = results.hits || [];
const filteredHits = [];
for (const hit of hits) {
const documentData = convertPmsDates(hit.document || {});
filteredHits.push({
id: documentData.id,
score: hit.text_match || 0,
document: documentData
});
}
// Format the results
const formattedResults = {
found: results.found || 0,
out_of: results.out_of || 0,
page: results.page || 1,
hits: filteredHits
};
// Create the main content
const content = {
type: "text",
text: JSON.stringify(formattedResults, null, 2),
title: `PMS jobs search results for '${queryText}'`,
format: "json"
};
// Generate artifact for visualization if there are results
let artifactData = null;
if (filteredHits.length > 0) {
const documents = hits.map((hit) => hit.document);
const link = await getDataLinkHelper(documents);
artifactData = getArtifactHelper("universal_pms_maintenance_search", link || '');
const artifact = {
type: "text",
text: JSON.stringify(artifactData, null, 2),
title: `PMS jobs search results for ${collection}`,
format: "json"
};
return [content, artifact];
}
return [content];
}
catch (error) {
logger.error("Error in smartPmsSearch", { error });
const errorMessage = `Error searching PMS jobs data: ${error instanceof Error ? error.message : String(error)}`;
return [
{
type: "error",
content: errorMessage,
title: "Error searching PMS jobs data"
}
];
}
}
export async function listPmsJobsByCategoryAndStatus(args) {
logger.info("Executing list_pms_jobs_by_category_and_status", { args });
try {
const imo = args.imo;
const jobCategory = args.jobCategory;
const jobStatus = args.jobStatus;
const perPage = args.per_page || 250;
const sessionId = args.session_id || "testing";
if (!imo || !jobCategory || !jobStatus) {
throw new Error("IMO number, job category, and job status are required");
}
const collection = "pms";
const includeFields = "imo,vesselName,jobTitle,component,jobDueDate,jobDoneDate,jobCode,defermentRequestedDate,jobCategory,scheduledOverhaulInterval,currentCounter,defermentApplied,jobStatus,currentStatus,remainingHrsNextOverhaul,remainingDaysNextOverhaul";
const query = {
q: "*",
filter_by: `imo:${imo} && jobCategory:${jobCategory} && jobStatus:${jobStatus}`,
per_page: perPage,
include_fields: includeFields
};
// Execute the search
logger.info(`Searching for ${jobCategory} PMS jobs with status ${jobStatus} for vessel ${imo}`);
const results = await typesenseClient.collections(collection).documents().search(query);
const hits = results.hits || [];
const filteredHits = [];
for (const hit of hits) {
const documentData = convertPmsDates(hit.document || {});
filteredHits.push({
id: documentData.id,
score: hit.text_match || 0,
document: documentData
});
}
// Get documents for data link
const documents = getDocumentsFromHits(hits);
// Get vessel name from hits
let vesselName = null;
// Get data link
const dataLink = await getDataLinkWithDefault(await getDataLinkHelper(documents));
await insertDataLinkToMongodbHelper(dataLink, 'Maintenance Summary', sessionId || '', imo || '', vesselName || '');
try {
vesselName = hits[0]?.document?.vesselName || null;
}
catch (error) {
vesselName = null;
}
// Format the results
const formattedResults = {
found: results.found || 0,
out_of: results.out_of || 0,
page: results.page || 1,
hits: filteredHits
};
// Create artifact
const artifactData = getArtifactHelper("list_pms_jobs_by_category_and_status", dataLink || '');
const content = {
type: "text",
text: JSON.stringify(formattedResults, null, 2),
title: `PMS jobs search results for ${collection}`,
format: "json"
};
const artifact = {
type: "text",
text: JSON.stringify(artifactData, null, 2),
title: `PMS jobs search results for ${collection}`,
format: "json"
};
return [content, artifact];
}
catch (error) {
logger.error("Error in listPmsJobsByCategoryAndStatus", { error });
const errorMessage = `Error retrieving PMS jobs: ${error instanceof Error ? error.message : String(error)}`;
return [
{
type: "error",
content: errorMessage,
title: "Error retrieving PMS jobs"
}
];
}
}
export async function listPmsJobsForComponent(args) {
logger.info("Executing list_pms_jobs_for_component", { args });
try {
const imo = args.imo;
const component = args.component;
const perPage = args.per_page || 250;
const sessionId = args.session_id || "testing";
if (!imo || !component) {
throw new Error("IMO number and component name are required");
}
const collection = "pms";
const includeFields = "imo,vesselName,jobTitle,component,jobDueDate,jobDoneDate,jobCode,defermentRequestedDate,jobCategory,scheduledOverhaulInterval,currentCounter,defermentApplied,jobStatus,currentStatus,remainingHrsNextOverhaul,remainingDaysNextOverhaul";
const query = {
q: "*",
filter_by: `imo:${imo} && component:${component}`,
sort_by: "jobDueDate:asc",
per_page: perPage,
include_fields: includeFields
};
// Execute the search
logger.info(`Searching for PMS jobs for component '${component}' on vessel ${imo}`);
const results = await typesenseClient.collections(collection).documents().search(query);
const hits = results.hits || [];
const filteredHits = [];
for (const hit of hits) {
const documentData = convertPmsDates(hit.document || {});
filteredHits.push({
id: documentData.id,
score: hit.text_match || 0,
document: documentData
});
}
// Get documents for data link
const documents = getDocumentsFromHits(hits);
// Get data link
const dataLink = await getDataLinkHelper(documents);
// Get vessel name from hits
let vesselName = null;
try {
vesselName = hits[0]?.document?.vesselName || null;
}
catch (error) {
vesselName = null;
}
// Insert the data link to mongodb collection
const linkHeader = `pms jobs for component ${component}`;
await insertDataLinkToMongodbHelper(dataLink ?? "", linkHeader, sessionId, imo, vesselName ?? undefined);
// Format the results
const formattedResults = {
found: results.found || 0,
out_of: results.out_of || 0,
page: results.page || 1,
hits: filteredHits
};
// Create artifact
const artifactData = getArtifactHelper("list_pms_jobs_for_component", dataLink ?? '');
const content = {
type: "text",
text: JSON.stringify(formattedResults, null, 2),
title: `PMS jobs for component '${component}' on vessel ${imo}`,
format: "json"
};
const artifact = {
type: "text",
text: JSON.stringify(artifactData, null, 2),
title: `PMS jobs for component '${component}' on vessel ${imo}`,
format: "json"
};
return [content, artifact];
}
catch (error) {
logger.error("Error in listPmsJobsForComponent", { error });
const errorMessage = `Error retrieving PMS jobs: ${error instanceof Error ? error.message : String(error)}`;
return {
type: "error",
content: errorMessage,
metadata: {
source: "list_pms_jobs_for_component",
error: errorMessage
}
};
}
}
export async function listPmsJobsForComponentDueWithin(args) {
logger.info("Executing list_pms_jobs_for_component_due_within", { args });
try {
const imo = args.imo;
const component = args.component;
const days = args.days;
const perPage = args.per_page || 250;
const sessionId = args.session_id || "testing";
if (!imo || !component || days === undefined || days === null) {
throw new Error("IMO number, component name, and days are required");
}
// Calculate future date based on days
const futureDate = new Date();
futureDate.setDate(futureDate.getDate() + days);
const futureTs = Math.floor(futureDate.getTime() / 1000);
const collection = "pms";
const includeFields = "imo,vesselName,jobTitle,component,jobDueDate,jobDoneDate,jobCode,defermentRequestedDate,jobCategory,scheduledOverhaulInterval,currentCounter,defermentApplied,jobStatus,currentStatus,remainingHrsNextOverhaul,remainingDaysNextOverhaul";
const query = {
q: "*",
filter_by: `imo:${imo} && component:${component} && jobDueDate:<${futureTs}`,
sort_by: "jobDueDate:asc",
per_page: perPage,
include_fields: includeFields
};
// Execute the search
logger.info(`Searching for PMS jobs for component '${component}' due within ${days} days on vessel ${imo}`);
const results = await typesenseClient.collections(collection).documents().search(query);
const hits = results.hits || [];
const filteredHits = [];
for (const hit of hits) {
const documentData = convertPmsDates(hit.document || {});
filteredHits.push({
id: documentData.id,
score: hit.text_match || 0,
document: documentData
});
}
// Get documents for data link
const documents = getDocumentsFromHits(hits);
// Get data link
const dataLink = await getDataLinkHelper(documents);
// Get vessel name from hits
let vesselName = null;
try {
vesselName = hits[0]?.document?.vesselName || null;
}
catch (error) {
vesselName = null;
}
// Insert the data link to mongodb collection
const linkHeader = `pms jobs for component ${component} due within ${days} days`;
await insertDataLinkToMongodbHelper(dataLink ?? "", linkHeader, sessionId, imo, vesselName ?? undefined);
// Format the results
const formattedResults = {
found: results.found || 0,
out_of: results.out_of || 0,
page: results.page || 1,
hits: filteredHits
};
// Create human-readable date for the title
const futureDateStr = futureDate.toISOString().split('T')[0]; // YYYY-MM-DD format
// Create artifact
const artifactData = getArtifactHelper("list_pms_jobs_for_component_due_within", dataLink ?? "no_link");
const content = {
type: "text",
text: JSON.stringify(formattedResults, null, 2),
title: `PMS jobs for component '${component}' due within ${days} days (by ${futureDateStr}) on vessel ${imo}`,
format: "json"
};
const artifact = {
type: "text",
text: JSON.stringify(artifactData, null, 2),
title: `PMS jobs for component '${component}' due within ${days} days (by ${futureDateStr}) on vessel ${imo}`,
format: "json"
};
return [content, artifact];
}
catch (error) {
logger.error("Error in listPmsJobsForComponentDueWithin", { error });
const errorMessage = `Error retrieving PMS jobs: ${error instanceof Error ? error.message : String(error)}`;
return [
{
type: "error",
content: errorMessage,
title: "Error retrieving PMS jobs"
}
];
}
}
export async function listOverdueLubeOilSamples(args) {
logger.info("Executing list_overdue_lube_oil_samples", { args });
try {
const imo = args.imo;
const sessionId = args.session_id || "testing";
if (!imo) {
throw new Error("IMO number is required");
}
// Build filter_by string
let filterBy = `imo:${imo} && dueStatus:"OVERDUE"`;
// Add optional machinery name filter
if (args.machineryName) {
filterBy += ` && machineryName:${args.machineryName}`;
}
// Execute search
const searchParameters = {
q: "*",
filter_by: filterBy,
sort_by: "nextDue:asc",
per_page: args.per_page || 30,
include_fields: "imo,vesselName,machineryName,reportStatus,sampleDate,nextDue,frequency,dueStatus,testLab,report"
};
const searchResult = await typesenseClient.collections("lube_oil_reports").documents().search(searchParameters);
if (!searchResult || !searchResult.hits) {
return {
type: "text",
content: "No overdue lube oil samples found for the specified criteria.",
metadata: {
source: "list_overdue_lube_oil_samples",
title: "No Overdue Samples Found"
}
};
}
// Process results
const hits = searchResult.hits || [];
const samples = [];
for (const hit of hits) {
let documentData = hit.document || {};
// Convert dates if needed
documentData = convertLubeOilDates(documentData);
samples.push({
imo: documentData.imo,
vesselName: documentData.vesselName,
machineryName: documentData.machineryName,
reportStatus: documentData.reportStatus,
sampleDate: documentData.sampleDate,
nextDue: documentData.nextDue,
frequency: documentData.frequency,
dueStatus: documentData.dueStatus,
testLab: documentData.testLab,
report: documentData.report
});
}
// Get documents for data link
const documents = getDocumentsFromHits(hits);
// Get data link
const dataLink = await getDataLinkHelper(documents);
// Get vessel name from hits
let vesselName = null;
try {
vesselName = hits[0]?.document?.vesselName || null;
}
catch (error) {
vesselName = null;
}
// Insert the data link to mongodb collection
const machineryName = args.machineryName || "all machinery";
const linkHeader = `overdue lube oil samples for ${machineryName}`;
await insertDataLinkToMongodbHelper(dataLink ?? undefined, linkHeader, sessionId, imo, vesselName ?? undefined);
// Format results
const formattedText = JSON.stringify(samples, null, 2);
// Create artifact
const artifactData = getArtifactHelper("list_overdue_lube_oil_samples", dataLink ?? "no_link");
const content = {
type: "text",
text: formattedText,
title: `Overdue Lube Oil Samples`,
format: "json"
};
const artifact = {
type: "text",
text: JSON.stringify(artifactData, null, 2),
title: `Overdue Lube Oil Samples`,
format: "json"
};
return [content, artifact];
}
catch (error) {
logger.error("Error in listOverdueLubeOilSamples", { error });
const errorMessage = `Error retrieving overdue lube oil samples: ${error instanceof Error ? error.message : String(error)}`;
return [
{
type: "error",
content: errorMessage,
title: "Error retrieving overdue lube oil samples"
}
];
}
}
export async function machineryWithWarningLubeOilAnalysis(args) {
logger.info("Executing machinery_with_warning_lube_oil_analysis", { args });
try {
const imo = args.imo;
const sessionId = args.session_id || "testing";
if (!imo) {
throw new Error("IMO number is required");
}
// Build filter_by string
let filterBy = `imo:${imo} && reportStatus:"WARNING"`;
// Add optional machinery name filter
if (args.machineryName) {
filterBy += ` && machineryName:${args.machineryName}`;
}
// Execute search
const searchParameters = {
q: "*",
filter_by: filterBy,
sort_by: "sampleDate:desc",
per_page: args.per_page || 30,
include_fields: "imo,vesselName,machineryName,reportStatus,sampleDate,nextDue,frequency,dueStatus,testLab,report"
};
const searchResult = await typesenseClient.collections("lube_oil_reports").documents().search(searchParameters);
if (!searchResult || !searchResult.hits) {
return {
type: "text",
content: "No warning status oil analysis reports found for the specified criteria.",
metadata: {
source: "machinery_with_warning_lube_oil_analysis",
title: "No Warning Reports Found"
}
};
}
// Process results
const hits = searchResult.hits || [];
const reports = [];
for (const hit of hits) {
let documentData = hit.document || {};
// Convert dates if needed
documentData = convertLubeOilDates(documentData);
reports.push({
imo: documentData.imo,
vesselName: documentData.vesselName,
machineryName: documentData.machineryName,
reportStatus: documentData.reportStatus,
sampleDate: documentData.sampleDate,
nextDue: documentData.nextDue,
frequency: documentData.frequency,
dueStatus: documentData.dueStatus,
testLab: documentData.testLab,
report: documentData.report
});
}
// Get documents for data link
const documents = getDocumentsFromHits(hits);
// Get data link
const dataLink = await getDataLinkHelper(documents);
// Get vessel name from hits
let vesselName = null;
try {
vesselName = hits[0]?.document?.vesselName || null;
}
catch (error) {
vesselName = null;
}
// Insert the data link to mongodb collection
const machineryName = args.machineryName || "all machinery";
const linkHeader = `warning oil analysis reports for ${machineryName}`;
await insertDataLinkToMongodbHelper(dataLink ?? undefined, linkHeader, sessionId, imo, vesselName ?? undefined);
// Format results
const formattedText = JSON.stringify(reports, null, 2);
// Create artifact
const artifactData = getArtifactHelper("machinery_with_warning_lube_oil_analysis", dataLink ?? "no_link");
const content = {
type: "text",
text: formattedText,
title: `Machinery with Warning Oil Analysis`,
format: "json"
};
const artifact = {
type: "text",
text: JSON.stringify(artifactData, null, 2),
title: `Machinery with Warning Oil Analysis`,
format: "json"
};
return [content, artifact];
}
catch (error) {
logger.error("Error in machineryWithWarningLubeOilAnalysis", { error });
const errorMessage = `Error retrieving warning status oil analysis reports: ${error instanceof Error ? error.message : String(error)}`;
return [
{
type: "error",
content: errorMessage,
title: "Error retrieving warning status oil analysis reports"
}
];
}
}
export async function getLatestLubeOilAnalysisForMachinery(args) {
logger.info("Executing get_latest_lube_oil_analysis_for_machinery", { args });
try {
const imo = args.imo;
const machineryName = args.machineryName;
const sessionId = args.session_id || "testing";
if (!imo || !machineryName) {
throw new Error("IMO number and machinery name are required");
}
// Build filter_by string
const filterBy = `imo:${imo} && machineryName:${machineryName}`;
// Execute search
const searchParameters = {
q: "*",
filter_by: filterBy,
sort_by: "sampleDate:desc",
per_page: args.per_page || 10,
include_fields: "imo,vesselName,machineryName,reportStatus,sampleDate,nextDue,frequency,dueStatus,testLab,report"
};
const searchResult = await typesenseClient.collections("lube_oil_reports").documents().search(searchParameters);
if (!searchResult || !searchResult.hits || searchResult.hits.length === 0) {
return {
type: "text",
content: `No oil analysis records found for ${machineryName} on vessel ${imo}.`,
metadata: {
source: "get_latest_lube_oil_analysis_for_machinery",
title: "No Analysis Found"
}
};
}
// Get the latest record
const hits = searchResult.hits;
let document = hits[0].document;
// Convert date fields to human readable format
document = convertLubeOilDates(document);
const result = {
imo: document.imo,
vesselName: document.vesselName,
machineryName: document.machineryName,
reportStatus: document.reportStatus,
sampleDate: document.sampleDate,
nextDue: document.nextDue,
frequency: document.frequency,
dueStatus: document.dueStatus,
testLab: document.testLab,
report: document.report
};
// Get documents for data link
const documents = getDocumentsFromHits(hits);
// Get data link
const dataLink = await getDataLinkHelper(documents);
// Get vessel name from document
const vesselName = document.vesselName || null;
// Insert the data link to mongodb collection
const linkHeader = `latest oil analysis for ${machineryName}`;
await insertDataLinkToMongodbHelper(dataLink ?? undefined, linkHeader, sessionId, imo, vesselName ?? undefined);
// Format results
const formattedText = JSON.stringify(result, null, 2);
// Create artifact
const artifactData = getArtifactHelper("get_latest_oil_analysis_for_machinery", dataLink ?? "no_link");
const content = {
type: "text",
text: formattedText,
title: `Latest Oil Analysis for ${machineryName}`,
format: "json"
};
const artifact = {
type: "text",
text: JSON.stringify(artifactData, null, 2),
title: `Latest Oil Analysis for ${machineryName}`,
format: "json"
};
return [content, artifact];
}
catch (error) {
logger.error("Error in getLatestLubeOilAnalysisForMachinery", { error });
const errorMessage = `Error retrieving latest oil analysis: ${error instanceof Error ? error.message : String(error)}`;
return [
{
type: "error",
content: errorMessage,
title: "Error retrieving latest oil analysis"
}
];
}
}
export async function listLubeOilSamplesByFrequency(args) {
logger.info("Executing list_lube_oil_samples_by_frequency", { args });
try {
const imo = args.imo;
const frequency = args.frequency;
const sessionId = args.session_id || "testing";
if (!imo || !frequency) {
throw new Error("IMO number and frequency are required");
}
// Build filter_by string
const filterBy = `imo:${imo} && frequency:"${frequency}"`;
// Execute search
const searchParameters = {
q: "*",
filter_by: filterBy,
sort_by: "nextDue:asc",
per_page: args.per_page || 30,
include_fields: "imo,vesselName,machineryName,reportStatus,sampleDate,nextDue,frequency,dueStatus,testLab,report"
};
const searchResult = await typesenseClient.collections("lube_oil_reports").documents().search(searchParameters);
if (!searchResult || !searchResult.hits || searchResult.hits.length === 0) {
return {
type: "text",
content: `No ${frequency} frequency lube oil samples found for vessel ${imo}.`,
metadata: {
source: "list_lube_oil_samples_by_frequency",
title: "No Samples Found"
}
};
}
// Process results
const hits = searchResult.hits;
const samples = [];
for (const hit of hits) {
let sampleData = hit.document || {};
// Convert dates if needed
sampleData = convertLubeOilDates(sampleData);
samples.push({
imo: sampleData.imo,
vesselName: sampleData.vesselName,
machineryName: sampleData.machineryName,
reportStatus: sampleData.reportStatus,
sampleDate: sampleData.sampleDate,
nextDue: sampleData.nextDue,
frequency: sampleData.frequency,
dueStatus: sampleData.dueStatus,
testLab: sampleData.testLab,
report: sampleData.report
});
}
// Get documents for data link
const documents = getDocumentsFromHits(hits);
// Get data link
const dataLink = await getDataLinkHelper(documents);
// Get vessel name from hits
let vesselName = null;
try {
vesselName = hits[0]?.document?.vesselName || null;
}
catch (error) {
vesselName = null;
}
// Insert the data link to mongodb collection
const linkHeader = `${frequency} frequency lube oil samples`;
await insertDataLinkToMongodbHelper(dataLink ?? undefined, linkHeader, sessionId, imo, vesselName ?? undefined);
// Format results
const formattedText = JSON.stringify(samples, null, 2);
// Create artifact
const artifactData = getArtifactHelper("list_lube_oil_samples_by_frequency", dataLink ?? "no_link");
const content = {
type: "text",
text: formattedText,
title: `${frequency} Frequency Lube Oil Samples`,
format: "json"
};
const artifact = {
type: "text",
text: JSON.stringify(artifactData, null, 2),
title: `${frequency} Frequency Lube Oil Samples`,
format: "json"
};
return [content, artifact];
}
catch (error) {
logger.error("Error in listLubeOilSamplesByFrequency", { error });
const errorMessage = `Error retrieving lube oil samples: ${error instanceof Error ? error.message : String(error)}`;
return [
{
type: "error",
content: errorMessage,
title: "Error retrieving lube oil samples"
}
];
}
}
export async function fetchLubeOilAnalysisWithLink(args) {
logger.info("Executing fetch_lube_oil_analysis_with_link", { args });
try {
const vesselName = args.vesselName;
const sessionId = args.session_id || "testing";
let imo = args.imo;
if (!vesselName) {
throw new Error("Vessel name is required");
}
// Build filter_by string
let filterBy = `vesselName:${vesselName}`;
// Add optional machinery name filter
if (args.machineryName) {
filterBy += ` && machineryName:${args.machineryName}`;
}
// Execute search
const searchParameters = {
q: "*",
filter_by: filterBy,
sort_by: "sampleDate:desc",
per_page: args.per_page || 10,
include_fields: "imo,vesselName,machineryName,reportStatus,sampleDate,nextDue,frequency,dueStatus,testLab,report"
};
const searchResult = await typesenseClient.collections("lube_oil_reports").documents().search(searchParameters);
if (!searchResult || !searchResult.hits || searchResult.hits.length === 0) {
return {
type: "text",
content: `No oil analysis reports found for vessel ${vesselName}.`,
metadata: {
source: "fetch_lube_oil_analysis_with_link",
title: "No Reports Found"
}
};
}
// Get the latest record
const hits = searchResult.hits;
let latest = hits[0].document;
// Convert date fields to human readable format
latest = convertLubeOilDates(latest);
const reportLink = latest?.report || null;
// const report = await parseDocumentLink({ document_link: reportLink }) : null;
const result = {
machineryName: latest.machineryName,
sampleDate: latest.sampleDate,
// report: report,
reportLink: reportLink
};
// If imo wasn't provided in arguments, get it from the search result
if (!imo) {
imo = latest.imo;
}
// Get documents for data link
const documents = hits.map((hit) => hit.document);
// Get data link
const dataLink = await getDataLinkHelper(documents);
// Get machinery name for the header
const machineryName = args.machineryName || latest.machineryName || "machinery";
// Insert the data link to mongodb collection
const linkHeader = `latest oil analysis report link for ${machineryName}`;
await insertDataLinkToMongodbHelper(dataLink ?? undefined, linkHeader, sessionId, imo, vesselName ?? undefined);
// Format results
const formattedText = JSON.stringify(result, null, 2);
// Create artifact
const artifactData = getArtifactHelper("fetch_lube_oil_analysis_with_link", dataLink ?? "no_link");
const content = {
type: "text",
text: formattedText,
title: `Latest Oil Analysis Report Link`,
format: "json"
};
const artifact = {
type: "text",
text: JSON.stringify(artifactData, null, 2),
title: `Latest Oil Analysis Report Link`,
format: "json"
};
return [content, artifact];
}
catch (error) {
logger.error("Error in fetchLubeOilAnalysisWithLink", { error });
const errorMessage = `Error retrieving latest oil analysis report link: ${error instanceof Error ? error.message : String(error)}`;
return {
type: "erro