UNPKG

purchase-mcp-server

Version:
1,660 lines 77.3 kB
import Typesense from "typesense";
import { MongoClient } from "mongodb";
import { updateTypesenseFilterWithCompanyImos, isValidVesselImoForCompany, isValidFleetImoForCompany, filterImoListByCompany } from "../utils/company-filtering.js";
export async function getTypesenseClient() {
    // Validate required environment variables
    const host = process.env.TYPESENSE_HOST;
    const port = process.env.TYPESENSE_PORT;
    const protocol = process.env.TYPESENSE_PROTOCOL;
    const apiKey = process.env.TYPESENSE_API_KEY;
    if (!host || !port || !protocol || !apiKey) {
        throw new Error('Missing required Typesense environment variables');
    }
    // Initialize Typesense client from scratch
    const typesenseConfig = {
        nodes: [
            {
                host,
                port: Number(port),
                protocol
            }
        ],
        apiKey,
        connectionTimeoutSeconds: 10,
        // retryIntervalSeconds: 1.0,
        // numRetries: 3,
        // healthcheckIntervalSeconds: 30,
        // logLevel: 'debug' as 'debug'
    };
    return new Typesense.Client(typesenseConfig);
}
export async function getComponentData(componentId, vesselComponentsDbName, vesselComponentsMongoUri, collectionName = 'vesselinfocomponents') {
    const match = componentId.match(/^(\d+)_(\d+)_(\d+)$/);
    if (!match) {
        return `⚠️ Invalid component_id format: ${componentId}`;
    }
    if (!vesselComponentsDbName || !vesselComponentsMongoUri || !collectionName) {
        throw new Error('Database name, MongoDB URI, and collection name are required');
    }
    const [, componentNumber, questionNumber, imo] = match;
    const componentNo = `${componentNumber}_${questionNumber}_${imo}`;
    const mongoClient = new MongoClient(vesselComponentsMongoUri);
    await mongoClient.connect();
    try {
        const db = mongoClient.db(vesselComponentsDbName);
        const collection = db.collection(collectionName);
        const doc = await collection.findOne({ componentNo });
        if (!doc) {
            return `⚠️ No component found for ID: ${componentId}`;
        }
        if (!doc.data) {
            return "No data found in the table component";
        }
        if (!doc.data.headers || !Array.isArray(doc.data.headers)) {
            return "No headers found in the table component";
        }
        if (!doc.data.body || !Array.isArray(doc.data.body)) {
            return "No body data found in the table component";
        }
        // Extract headers excluding lineitem
        const headers = doc.data.headers
            .filter((h) => h && h.name !== "lineitem")
            .map((h) => h.name);
        const rows = doc.data.body;
        // Build markdown table
        let md = "| " + headers.join(" | ") + " |\n";
        md += "| " + headers.map(() => "---").join(" | ") + " |\n";
        for (const row of rows) {
            const formattedRow = row
                .filter((cell) => cell && !cell.lineitem) // Exclude lineitem and null cells
                .map((cell) => {
                if (cell && cell.value && cell.link) {
                    return `[${cell.value}](${cell.link})`;
                }
                else if (cell && cell.status && cell.color) {
                    return cell.status;
                }
                return cell ? String(cell) : '';
            });
            md += "| " + formattedRow.join(" | ") + " |\n";
        }
        return md;
    }
    catch (error) {
        // logger.error('Error getting component data:', error);
        throw new Error(`Error getting component data: ${error.message}`);
    }
    finally {
        await mongoClient.close();
    }
}
export async function addComponentData(answer, imo, vesselComponentsDbName, vesselComponentsMongoUri) {
    const pattern = /httpsdev\.syia\.ai\/chat\/ag-grid-table\?component=(\d+_\d+)/g;
    const matches = Array.from(answer.matchAll(pattern));
    // logger.info(`addComponentData called with IMO: ${imo}, matches found: ${matches.length}`);
    // logger.info(`Answer contains ag-grid URL: ${answer.includes('ag-grid-table')}`);
    let result = answer;
    for (const match of matches) {
        const component = match[1];
        // logger.info(`Processing component: ${component}, full match: ${match[0]}`);
        try {
            const replacement = await getComponentData(`${component}_${imo}`, vesselComponentsDbName, vesselComponentsMongoUri, 'vesselinfocomponents');
            // logger.info(`Component data retrieved, length: ${replacement.length}`);
            result = result.replace(match[0], replacement);
        }
        catch (error) {
            // logger.error('Error replacing component data:', error);
        }
    }
    // logger.info(`addComponentData returning result, length: ${result.length}`);
    return result;
}
export async function getVesselQnASnapshot(imo, questionNo) {
    try {
        const raw_snapshotUrl = process.env.SNAPSHOT_URL;
        // API endpoint
        const snapshotUrl = `${raw_snapshotUrl}/${imo}/${questionNo}`;
        const raw_jwtToken = process.env.JWT_TOKEN;
        // Authentication token
        const jwtToken = `Bearer ${raw_jwtToken}`;
        // Headers for the request
        const headers = {
            "Authorization": jwtToken
        };
        // logger.info(`Fetching vessel QnA snapshot for IMO: ${imo}, Question: ${questionNo}`);
        const response = await fetch(snapshotUrl, {
            method: 'GET',
            headers
        });
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        const data = await response.json();
        // Return resultData if it exists, otherwise return the full response
        if (data && typeof data === 'object' && "resultData" in data) {
            return data.resultData;
        }
        return data;
    }
    catch (error) {
        // logger.error(`Error fetching vessel QnA snapshot for IMO ${imo}, Question ${questionNo}:`, error);
        return null;
    }
}
export async function fetchQADetails(imo, qaId, vesselInfoDbName, vesselInfoMongoUri, collectionName = 'vesselinfos') {
    const mongoClient = new MongoClient(vesselInfoMongoUri);
    await mongoClient.connect();
    try {
        const db = mongoClient.db(vesselInfoDbName);
        const collection = db.collection(collectionName);
        const query = {
            'imo': parseInt(imo),
            'questionNo': qaId
        };
        const projection = {
            '_id': 0,
            'imo': 1,
            'vesselName': 1,
            'refreshDate': 1,
            'answer': 1
        };
        const mongoResult = await collection.findOne(query, { projection });
        let res = mongoResult ? {
            imo: mongoResult.imo,
            vesselName: mongoResult.vesselName,
            refreshDate: mongoResult.refreshDate,
            answer: mongoResult.answer
        } : {
            imo: parseInt(imo),
            vesselName: null,
            refreshDate: null,
            answer: null
        };
        // Format refresh date if it exists
        if (res.refreshDate && new Date(res.refreshDate).toString() !== 'Invalid Date') {
            res.refreshDate = new Date(res.refreshDate).toLocaleDateString('en-US', {
                day: 'numeric',
                month: 'short',
                year: 'numeric'
            });
        }
        // Process answer with component data if it exists
        if (res.answer) {
            const vesselComponentsDbName = process.env.vesselComponentsDbName || vesselInfoDbName;
            const vesselComponentsMongoUri = process.env.vesselComponentsMongoUri || vesselInfoMongoUri;
            res.answer = await addComponentData(res.answer, imo, vesselComponentsDbName, vesselComponentsMongoUri);
        }
        // Get vessel QnA snapshot link
        try {
            res.Artifactlink = await getVesselQnASnapshot(imo, qaId.toString());
        }
        catch (error) {
            res.Artifactlink = null;
        }
        return res;
    }
    catch (error) {
        // logger.error('Error fetching QA details:', error);
        throw new Error(`Error fetching QA details: ${error.message}`);
    }
    finally {
        await mongoClient.close();
    }
}
export async function fetchQADetailsAndCreateResponse(imo, questionNo, functionName, linkHeader, vesselInfoDbName, vesselInfoMongoUri, collectionName = 'vesselinfos') {
    if (!imo) {
        throw new Error("IMO is required");
    }
    // Debug logging removed - use console.error for stderr if needed
    // console.error("Mongo URI from fetchQADetailsAndCreateResponse: ", vesselInfoMongoUri);
    // console.error("Mongo DB Name from fetchQADetailsAndCreateResponse: ", vesselInfoDbName);
    try {
        // Fetch QA details
        const result = await fetchQADetails(imo, questionNo, vesselInfoDbName, vesselInfoMongoUri, collectionName);
        const link = result.Artifactlink;
        const vesselName = result.vesselName;
        // Insert data link to MongoDB
        // await insertDataLinkToMongoDB(link, linkHeader, sessionId, imo, vesselName, vesselInfoDbName, vesselInfoMongoUri);
        // Get artifact data
        const artifactData = await getArtifact(functionName, link);
        // Create content responses with processed answer
        const artifactLinkText = link ? `\n\nArtifact Link: ${link}` : "";
        const content = {
            type: "text",
            text: `${result.answer || "No data available"}${artifactLinkText}`
        };
        const artifact = {
            type: "text",
            text: JSON.stringify(artifactData, null, 2)
        };
        return {
            content: [content, artifact]
        };
    }
    catch (error) {
        // logger.error(`Error in ${functionName}:`, error);
        throw new Error(`Error in ${functionName}: ${error.message}`);
    }
}
export async function getArtifact(toolName, link) {
    try {
        const timestamp = Math.floor(Date.now() / 1000);
        const artifact = {
            id: `msg_browser_${Math.random().toString(36).substring(2, 8)}`,
            parentTaskId: `task_${toolName}_${Math.random().toString(36).substring(2, 8)}`,
            timestamp,
            agent: {
                id: "agent_siya_browser",
                name: "SIYA",
                type: "qna"
            },
            messageType: "action",
            action: {
                tool: "browser",
                operation: "browsing",
                params: {
                    url: link,
                    pageTitle: `Tool response for ${toolName}`,
                    visual: {
                        icon: "browser",
                        color: "#2D8CFF"
                    },
                    stream: {
                        type: "vnc",
                        streamId: "stream_browser_1",
                        target: "browser"
                    }
                }
            },
            content: `Viewed page: ${toolName}`,
            artifacts: [
                {
                    id: `artifact_webpage_${Date.now()}_${Math.floor(Math.random() * 1000)}`,
                    type: "browser_view",
                    content: {
                        url: link,
                        title: toolName,
                        screenshot: "",
                        textContent: `Observed output of cmd \`${toolName}\` executed:`,
                        extractedInfo: {}
                    },
                    metadata: {
                        domainName: "example.com",
                        visitTimestamp: Date.now(),
                        category: "web_page"
                    }
                }
            ],
            status: "completed"
        };
        return artifact;
    }
    catch (error) {
        // logger.error('Error getting artifact:', error);
        throw new Error(`Error getting artifact: ${error.message}`);
    }
}
export async function getVesselImoListFromFleet(fleetImo) {
    const dbName = process.env.GROUP_DETAILS_DB_NAME;
    const mongoUri = process.env.GROUP_DETAILS_MONGO_URI;
    const collectionName = "common_group_details";
    if (!dbName || !mongoUri || !collectionName) {
        throw new Error('Database name, MongoDB URI, and collection name are required for fleet operations');
    }
    const mongoClient = new MongoClient(mongoUri);
    await mongoClient.connect();
    try {
        const db = mongoClient.db(dbName);
        const collection = db.collection(collectionName);
        const fleetDoc = await collection.findOne({ imo: fleetImo });
        if (fleetDoc && fleetDoc.imoList && Array.isArray(fleetDoc.imoList)) {
            return fleetDoc.imoList;
        }
        return [];
    }
    catch (error) {
        // logger.error(`Error fetching vessel IMO list for fleet ${fleetImo}:`, error);
        throw error;
    }
    finally {
        await mongoClient.close();
    }
}
export function convertUnixDates(document) {
    // logger.debug(`Starting Unix date conversion for document with ${Object.keys(document).length} fields`);
    const result = { ...document };
    const dateFields = [
        'purchaseRequisitionDate',
        'purchaseOrderIssuedDate',
        'orderReadinessDate',
        'date',
        'poDate',
        'expenseDate',
        "inspectionTargetDate",
        "reportDate",
        "closingDate",
        "targetDate",
        "nextDueDate",
        "extendedDate"
    ];
    // logger.debug(`Checking ${dateFields.length} potential date fields for Unix timestamp conversion`);
    let convertedCount = 0;
    for (const field of dateFields) {
        const value = result[field];
        if (typeof value === "number" && Number.isFinite(value)) {
            const originalValue = value;
            result[field] = new Date(value * 1000).toISOString();
            // logger.debug(`Converted field '${field}' from Unix timestamp ${originalValue} to ISO date: ${result[field]}`);
            convertedCount++;
        }
    }
    // logger.debug(`Unix date conversion completed - ${convertedCount} fields converted out of ${dateFields.length} checked fields`);
    return result;
}
export async function getDataLink(data) {
    try {
        const snapshotUrl = process.env.SNAPSHOT_URL || "";
        const jwtToken = process.env.JWT_TOKEN || "";
        const headers = {
            "Content-Type": "application/json",
            "Authorization": `Bearer ${jwtToken}`
        };
        const payload = {
            data
        };
        // Log URL and token (complete values)
        // logger.info(`Making request to URL: ${url}`);
        // logger.info(`Using JWT token: ${raw_jwtToken}`);
        const response = await fetch(snapshotUrl, {
            method: 'POST',
            headers,
            body: JSON.stringify(payload)
        });
        // Log response status and headers
        // logger.info(`Response status: ${response.status} ${response.statusText}`);
        // logger.info(`Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
        if (!response.ok) {
            // logger.error(`HTTP error! status: ${response.status}, statusText: ${response.statusText}`);
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        const result = await response.json();
        // Log complete response data
        // logger.info(`Complete response data: ${JSON.stringify(result)}`);
        if (result.status === "OK") {
            // logger.info(`Successfully got data link: ${result.resultData}`);
            return result.resultData;
        }
        else {
            // logger.error(`Failed to get data link: Invalid response status - ${result.status}`);
            throw new Error('Failed to get data link: Invalid response status');
        }
    }
    catch (error) {
        // logger.error('Error getting data link:', error);
        throw new Error(`Error getting data link: ${error.message}`);
    }
}
export async function processTypesenseResults(searchResult, toolName, title, linkHeader, artifactTitle, dbName, mongoUri) {
    // logger.info(`Starting processTypesenseResults for tool: ${toolName}, session: ${session_id}`);
    try {
        // Log input validation
        // logger.info(`Validating search results for ${toolName} - hits count: ${searchResult?.hits?.length || 0}`);
        if (!searchResult || !searchResult.hits || searchResult.hits.length === 0) {
            // logger.warn(`No search results found for ${toolName}`);
            return {
                content: [{
                        type: "text",
                        text: "No records found for the specified criteria."
                    }]
            };
        }
        // logger.info(`Processing ${searchResult.hits.length} hits for ${toolName}`);
        // Process search results into the standard format
        const hits = searchResult.hits || [];
        // logger.info(`Starting document processing for ${hits.length} hits in ${toolName}`);
        const documents = await Promise.all(hits.map(async (hit, index) => {
            if (!hit.document) {
                // logger.warn(`Hit ${index} is missing document property in ${toolName}`);
                return {};
            }
            // Create a shallow copy of the document
            const document = { ...hit.document };
            // Remove embedding field to reduce response size
            if (document.embedding) {
                // logger.debug(`Removing embedding field from document ${index} in ${toolName}`);
                delete document.embedding;
            }
            // Convert Unix timestamps to readable dates
            // logger.debug(`Converting Unix dates for document ${index} in ${toolName}`);
            return await convertUnixDates(document);
        }));
        // logger.info(`Successfully processed ${documents.length} documents for ${toolName}`);
        // Get data link
        // logger.info(`Generating data link for ${toolName}`);
        const dataLink = await getDataLink(documents);
        // logger.info(`Data link generated successfully for ${toolName}: ${dataLink.substring(0, 50)}...`);
        // Get vessel name and IMO from hits
        let vesselName = null;
        let imo = null;
        // logger.info(`Extracting vessel information from search results for ${toolName}`);
        try {
            vesselName = searchResult.hits[0]?.document?.vesselName;
            imo = searchResult.hits[0]?.document?.imo;
            // logger.info(`Vessel info extracted for ${toolName} - Name: ${vesselName}, IMO: ${imo}`);
        }
        catch (error) {
            // logger.warn(`Could not get vessel name or IMO from hits in ${toolName}:`, error);
        }
        // Insert the data link to mongodb collection
        // logger.info(`Inserting data link to MongoDB for ${toolName}, session: ${session_id}`);
        //   if (dbName && mongoUri) {
        //       // await insertDataLinkToMongoDB(dataLink, linkHeader, session_id, imo || "", vesselName || "", dbName, mongoUri);
        //   }
        // logger.info(`Data link successfully inserted to MongoDB for ${toolName}`);
        // Format results in the standard structure
        // logger.info(`Formatting results for ${toolName}`);
        const formattedResults = {
            found: searchResult.found || 0,
            out_of: searchResult.out_of || 0,
            page: searchResult.page || 1,
            hits: documents,
            artifactLink: dataLink
        };
        // logger.info(`Results formatted successfully for ${toolName} - found: ${formattedResults.found}, out_of: ${formattedResults.out_of}`);
        // Get artifact data
        // logger.info(`Retrieving artifact data for ${toolName}`);
        const artifactData = await getArtifact(toolName, dataLink);
        // logger.info(`Artifact data retrieved successfully for ${toolName}`);
        // Create content response
        // logger.info(`Creating content response for ${toolName}`);
        const content_ = {
            type: "text",
            text: JSON.stringify(formattedResults, null, 2),
            title,
            format: "json"
        };
        // Create artifact response
        // logger.info(`Creating artifact response for ${toolName}`);
        const artifact = {
            type: "text",
            text: JSON.stringify(artifactData, null, 2),
            title: artifactTitle || title,
            format: "json"
        };
        // logger.info(`processTypesenseResults completed successfully for ${toolName}`);
        return {
            content: [content_, artifact]
        };
    }
    catch (error) {
        // logger.error(`Error processing Typesense results for ${toolName}:`, error);
        return {
            content: [{
                    type: "text",
                    text: `Error processing results: ${error.message}`,
                    title: "Error",
                    format: "json"
                }]
        };
    }
}
export async function exportDataForImoListGeneric(collectionName, imoList, startDate, endDate, dateField, excludeFields = "", timestampFields = []) {
    try {
        // Filter IMO list to only include company IMOs
        const filteredImoList = await filterImoListByCompany(imoList);
        if (filteredImoList.length === 0) {
            return []; // Return empty array if no valid IMOs
        }
        const client = await getTypesenseClient();
        const collection = client.collections(collectionName);
        const dateToTs = (dateStr) => {
            return Math.floor(new Date(dateStr).getTime() / 1000);
        };
        const filterParts = [`imo:[${filteredImoList.join(',')}]`];
        if (startDate && dateField) {
            filterParts.push(`${dateField}:>=${dateToTs(startDate)}`);
        }
        if (endDate && dateField) {
            filterParts.push(`${dateField}:<=${dateToTs(endDate)}`);
        }
        const filterBy = filterParts.join(" && ");
        const query = {
            filter_by: filterBy,
            exclude_fields: excludeFields
        };
        const exportResult = await collection.documents().export(query);
        let exportData;
        if (typeof exportResult === 'string') {
            exportData = exportResult;
        }
        else if (exportResult && typeof exportResult === 'object' && 'buffer' in exportResult) {
            exportData = new TextDecoder().decode(exportResult);
        }
        else {
            exportData = String(exportResult);
        }
        const documents = exportData
            .split('\n')
            .filter(line => line.trim())
            .map(line => JSON.parse(line));
        // Convert UNIX timestamps to readable date strings
        for (const doc of documents) {
            for (const field of timestampFields) {
                if (field in doc && typeof doc[field] === 'number') {
                    try {
                        doc[field] = new Date(doc[field] * 1000).toISOString().replace('T', ' ').substring(0, 19);
                    }
                    catch (err) {
                        // Leave original value on error
                    }
                }
            }
        }
        return documents;
    }
    catch (error) {
        // logger.error(`Error exporting data from '${collectionName}' collection:`, error);
        return [];
    }
}
export async function exportPurchasesForImoList(imoList, startDate, endDate) {
    return exportDataForImoListGeneric('purchase', imoList, startDate, endDate, 'purchaseRequisitionDate', "embedding", [
        'purchaseRequisitionDate',
        'purchaseOrderIssuedDate',
        'orderReadinessDate'
    ]);
}
export async function exportBudgetsForImoList(imoList, startDate, endDate) {
    return exportDataForImoListGeneric('budget', imoList, startDate, endDate, 'date', "embedding", ['date']);
}
export async function exportExpensesForImoList(imoList, startDate, endDate) {
    return exportDataForImoListGeneric('expense', imoList, startDate, endDate, 'expenseDate', "embedding,vesselId,fleetId,docId,fleetManagerId,technicalSuperintendentId,ownerId", [
        'expenseDate',
        'poDate'
    ]);
}
export async function universal_purchase_search(arguments_) {
    const collection = "purchase";
    // Validate that only allowed properties are present
    // const providedProperties = Object.keys(arguments_);
    // const invalidProperties = providedProperties.filter(prop => !UniversalPurchaseSearchKeys.includes(prop as any));
    // if (invalidProperties.length > 0) {
    //     throw new Error(`Invalid properties provided: ${invalidProperties.join(', ')}. Only the following properties are allowed: ${UniversalPurchaseSearchKeys.join(', ')}`);
    // }
    // Extract new schema parameters
    const args = arguments_;
    const query_by = args.query_by;
    const query_text = (args.q || "").trim() || "*";
    // Validate that query_by is provided when q is specified
    if (args.q && args.q.trim() !== "*" && !query_by) {
        throw new Error("query_by parameter is required when using full-text search with q parameter. Only the following properties are allowed: query_by, q, filter_by, sort_by, page, per_page. Please check the tool schema for universal_purchase_search for the allowed properties.");
    }
    const filter_by = args.filter_by || "";
    const sort_by = args.sort_by || "relevance";
    const page = args.page || 1;
    const per_page = args.per_page || 50;
    try {
        let filterBy = filter_by;
        // Convert date fields from yyyy-mm-dd to Unix timestamps in filter_by
        if (filterBy) {
            const dateFields = ["date", "purchaseRequisitionDate", "purchaseOrderIssuedDate", "orderReadinessDate"];
            for (const dateField of dateFields) {
                // Handle date range operations (>=, <=, >, <, =)
                const dateRegexPatterns = [
                    { pattern: new RegExp(`${dateField}:>=(\\d{4}-\\d{2}-\\d{2})`, 'g'), operator: ':>=' },
                    { pattern: new RegExp(`${dateField}:<=(\\d{4}-\\d{2}-\\d{2})`, 'g'), operator: ':<=' },
                    { pattern: new RegExp(`${dateField}:>(\\d{4}-\\d{2}-\\d{2})`, 'g'), operator: ':>' },
                    { pattern: new RegExp(`${dateField}:<(\\d{4}-\\d{2}-\\d{2})`, 'g'), operator: ':<' },
                    { pattern: new RegExp(`${dateField}:(\\d{4}-\\d{2}-\\d{2})`, 'g'), operator: ':' }
                ];
                for (const { pattern, operator } of dateRegexPatterns) {
                    filterBy = filterBy.replace(pattern, (match, dateStr) => {
                        try {
                            const timestamp = Math.floor(new Date(dateStr + 'T00:00:00.000Z').getTime() / 1000);
                            return `${dateField}${operator}${timestamp}`;
                        }
                        catch (error) {
                            return match; // Keep original if conversion fails
                        }
                    });
                }
            }
        }
        // Apply company IMO restrictions
        filterBy = await updateTypesenseFilterWithCompanyImos(filterBy || "");
        // Set up all available fields for inclusion (based on schema)
        const includeFields = "imo,vesselName,purchaseRequisitionDate,prDescription," +
            "purchaseRequisitionStatus,poValue,purchaseOrderIssuedDate,vendorOrSupplierName," +
            "accountCode,invoiceStatus,invoiceValue,prStatusColor,orderReadinessDate," +
            "orderReadinessColor,purchaseRequisitionNumber,purchaseRequisitionLink," +
            "purchaseOrderNumber,purchaseOrderLink,prType,prTypeColor,invoiceStatusColor," +
            "scanID,scanIDLink,orderPriority,currencyCode,purchaseOrderStatus,poCreatedBy," +
            "orderType,invoiceAmount,invoiceApproverName,qtcNo,qtcLink,forwarderName," +
            "forwarderRemarks,warehouseLocation,cargoType,weight,purchaseOrderAmount," +
            "purchaseRequisitionType,directPo,poInvoiceDiscrepency,purchaseRequisitionSummary," +
            "poStatusColor,purchaseOrderStage,purchaseRequisitionDescription," +
            "isNewPurchaseRequisition,isPurchaseRequisitionNotOrdered," +
            "isPurchaseRequisitionSupplied,isPurchaseOrderReady,purchaseOverviewUpdate";
        const excludeFields = "embedding,vesselId,docId,fleetManagerId,technicalSuperintendentId,ownerId";
        const query = {
            q: query_text,
            query_by: query_by,
            include_fields: includeFields,
            exclude_fields: excludeFields,
            page: page,
            per_page: per_page
        };
        if (filterBy) {
            query.filter_by = filterBy;
        }
        // Handle sorting
        if (sort_by && sort_by !== "relevance") {
            query.sort_by = sort_by;
        }
        // logger.debug(`[Typesense Query] ${JSON.stringify(query)}`);
        const typesenseClient = await getTypesenseClient();
        const results = await typesenseClient.collections(collection).documents().search(query);
        // Debug: Log the structure of the search results
        // logger.info(`UniversalPurchaseSearchHandler - Results structure: ${JSON.stringify(Object.keys(results))}`);
        if (results.hits && results.hits.length > 0) {
            const sampleHit = results.hits[0];
            // logger.info(`UniversalPurchaseSearchHandler - Sample hit structure: ${JSON.stringify(Object.keys(sampleHit))}`);
        }
        if (!results || !results.hits || results.hits.length === 0) {
            return {
                content: [{
                        type: "text",
                        text: `No purchase records found.`
                    }]
            };
        }
        // Post-filter: Remove any results with forbidden IMOs (additional security layer)
        // Even though we filter at query level, we also filter results to ensure no forbidden IMOs slip through
        const originalHitsCount = results.hits.length;
        const filteredHits = [];
        const forbiddenImos = [];
        for (const hit of results.hits) {
            const document = hit.document;
            if (document && document.imo !== undefined && document.imo !== null) {
                const imo = String(document.imo);
                const isValid = await isValidVesselImoForCompany(imo);
                if (isValid) {
                    filteredHits.push(hit);
                }
                else {
                    forbiddenImos.push(imo);
                }
            }
            else {
                // If no IMO field, include it (shouldn't happen, but safe fallback)
                filteredHits.push(hit);
            }
        }
        // Update results with filtered hits
        results.hits = filteredHits;
        results.found = filteredHits.length;
        if (filteredHits.length === 0) {
            return {
                content: [{
                        type: "text",
                        text: `No purchase records found after filtering by company IMOs.`
                    }]
            };
        }
        // Format results using the utility function
        let title = `Universal Purchase Search Results for '${query_text}'`;
        const linkHeader = `Universal purchase search result for query: '${query_text}' in fields: ${query_by}`;
        return await processTypesenseResults(results, "universal_purchase_search", title, linkHeader);
    }
    catch (error) {
        // logger.error('Error performing universal purchase search:', error);
        throw new Error(`Error performing search: ${error.message}`);
    }
}
export async function universal_budget_search(arguments_) {
    const collection = "budget";
    // Validate that only allowed properties are present
    // const providedProperties = Object.keys(arguments_);
    // const invalidProperties = providedProperties.filter(prop => !UniversalBudgetSearchKeys.includes(prop as any));
    // if (invalidProperties.length > 0) {
    //     throw new Error(`Invalid properties provided: ${invalidProperties.join(', ')}. Only the following properties are allowed: ${UniversalBudgetSearchKeys.join(', ')}`);
    // }
    // Extract new schema parameters
    const args = arguments_;
    const query_by = args.query_by;
    const query_text = (args.q || "").trim() || "*";
    // Validate that query_by is provided when q is specified
    if (args.q && args.q.trim() !== "*" && !query_by) {
        throw new Error("query_by parameter is required when using full-text search with q parameter. Only the following properties are allowed: query_by, q, filter_by, sort_by, page, per_page. Please check the tool schema for universal_budget_search for the allowed properties.");
    }
    const filter_by = args.filter_by || "";
    const sort_by = args.sort_by || "relevance";
    const page = args.page || 1;
    const per_page = args.per_page || 50;
    try {
        let filterBy = filter_by;
        // Convert date fields from yyyy-mm-dd to Unix timestamps in filter_by
        if (filterBy) {
            const dateFields = ["date"];
            for (const dateField of dateFields) {
                // Handle date range operations (>=, <=, >, <, =)
                const dateRegexPatterns = [
                    { pattern: new RegExp(`${dateField}:>=(\\d{4}-\\d{2}-\\d{2})`, 'g'), operator: ':>=' },
                    { pattern: new RegExp(`${dateField}:<=(\\d{4}-\\d{2}-\\d{2})`, 'g'), operator: ':<=' },
                    { pattern: new RegExp(`${dateField}:>(\\d{4}-\\d{2}-\\d{2})`, 'g'), operator: ':>' },
                    { pattern: new RegExp(`${dateField}:<(\\d{4}-\\d{2}-\\d{2})`, 'g'), operator: ':<' },
                    { pattern: new RegExp(`${dateField}:(\\d{4}-\\d{2}-\\d{2})`, 'g'), operator: ':' }
                ];
                for (const { pattern, operator } of dateRegexPatterns) {
                    filterBy = filterBy.replace(pattern, (match, dateStr) => {
                        try {
                            const timestamp = Math.floor(new Date(dateStr + 'T00:00:00.000Z').getTime() / 1000);
                            return `${dateField}${operator}${timestamp}`;
                        }
                        catch (error) {
                            // logger.warn(`Failed to convert date ${dateStr} for field ${dateField}:`, error);
                            return match; // Keep original if conversion fails
                        }
                    });
                }
            }
        }
        // Apply company IMO restrictions
        filterBy = await updateTypesenseFilterWithCompanyImos(filterBy || "");
        // Set up all available fields for inclusion (based on schema)
        const includeFields = "imo,vesselName,category,group,period,budgetAmount,expenseAmount,date";
        const excludeFields = "embedding";
        const query = {
            q: query_text,
            query_by: query_by || "vesselName",
            include_fields: includeFields,
            exclude_fields: excludeFields,
            page: page,
            per_page: per_page
        };
        if (filterBy) {
            query.filter_by = filterBy;
        }
        // Handle sorting
        if (sort_by && sort_by !== "relevance") {
            query.sort_by = sort_by;
        }
        // logger.debug(`[Typesense Query] ${JSON.stringify(query)}`);
        const typesenseClient = await getTypesenseClient();
        const results = await typesenseClient.collections(collection).documents().search(query);
        if (!results || !results.hits || results.hits.length === 0) {
            return {
                content: [{
                        type: "text",
                        text: `No budget records found for the specified criteria.`
                    }]
            };
        }
        // Post-filter: Remove any results with forbidden IMOs (additional security layer)
        // Even though we filter at query level, we also filter results to ensure no forbidden IMOs slip through
        const filteredHits = [];
        const forbiddenImos = [];
        for (const hit of results.hits) {
            const document = hit.document;
            if (document && document.imo !== undefined && document.imo !== null) {
                const imo = String(document.imo);
                const isValid = await isValidVesselImoForCompany(imo);
                if (isValid) {
                    filteredHits.push(hit);
                }
                else {
                    forbiddenImos.push(imo);
                }
            }
            else {
                // If no IMO field, include it (shouldn't happen, but safe fallback)
                filteredHits.push(hit);
            }
        }
        // Update results with filtered hits
        results.hits = filteredHits;
        results.found = filteredHits.length;
        if (filteredHits.length === 0) {
            return {
                content: [{
                        type: "text",
                        text: `No budget records found after filtering by company IMOs.`
                    }]
            };
        }
        // Format results using the utility function
        let title = `Universal Budget Search Results for '${query_text}'`;
        const linkHeader = `Universal budget search result for query: '${query_text}' in fields: ${query_by}`;
        return await processTypesenseResults(results, "universal_budget_search", title, linkHeader);
    }
    catch (error) {
        // logger.error('Error performing universal budget search:', error);
        throw new Error(`Error performing search: ${error.message}`);
    }
}
export async function universal_expense_search(arguments_) {
    const collection = "expense";
    // Validate that only allowed properties are present
    // const providedProperties = Object.keys(arguments_);
    // const invalidProperties = providedProperties.filter(prop => !UniversalExpenseSearchKeys.includes(prop as any));
    // if (invalidProperties.length > 0) {
    //     throw new Error(`Invalid properties provided: ${invalidProperties.join(', ')}. Only the following properties are allowed: ${UniversalExpenseSearchKeys.join(', ')}`);
    // }
    // Extract new schema parameters
    const args = arguments_;
    const query_by = args.query_by;
    const query_text = (args.q || "").trim() || "*";
    // Validate that query_by is provided when q is specified
    if (args.q && args.q.trim() !== "*" && !query_by) {
        throw new Error("query_by parameter is required when using full-text search with q parameter. Only the following properties are allowed: query_by, q, filter_by, sort_by, page, per_page. Please check the tool schema for universal_expense_search for the allowed properties.");
    }
    const filter_by = args.filter_by || "";
    const sort_by = args.sort_by || "relevance";
    const page = args.page || 1;
    const per_page = args.per_page || 50;
    try {
        let filterBy = filter_by;
        // Convert date fields from yyyy-mm-dd to Unix timestamps in filter_by
        if (filterBy) {
            const dateFields = ["expenseDate", "poDate"];
            for (const dateField of dateFields) {
                // Handle date range operations (>=, <=, >, <, =)
                const dateRegexPatterns = [
                    { pattern: new RegExp(`${dateField}:>=(\\d{4}-\\d{2}-\\d{2})`, 'g'), operator: ':>=' },
                    { pattern: new RegExp(`${dateField}:<=(\\d{4}-\\d{2}-\\d{2})`, 'g'), operator: ':<=' },
                    { pattern: new RegExp(`${dateField}:>(\\d{4}-\\d{2}-\\d{2})`, 'g'), operator: ':>' },
                    { pattern: new RegExp(`${dateField}:<(\\d{4}-\\d{2}-\\d{2})`, 'g'), operator: ':<' },
                    { pattern: new RegExp(`${dateField}:(\\d{4}-\\d{2}-\\d{2})`, 'g'), operator: ':' }
                ];
                for (const { pattern, operator } of dateRegexPatterns) {
                    filterBy = filterBy.replace(pattern, (match, dateStr) => {
                        try {
                            const timestamp = Math.floor(new Date(dateStr + 'T00:00:00.000Z').getTime() / 1000);
                            return `${dateField}${operator}${timestamp}`;
                        }
                        catch (error) {
                            // logger.warn(`Failed to convert date ${dateStr} for field ${dateField}:`, error);
                            return match; // Keep original if conversion fails
                        }
                    });
                }
            }
        }
        // Apply company IMO restrictions
        filterBy = await updateTypesenseFilterWithCompanyImos(filterBy || "");
        // Set up all available fields for inclusion (based on schema)
        const includeFields = "imo,vesselName,group,category,accountNo,accountDescription,vendor,expenseDate,expenseAmount,poAmount,expenseCategory";
        const excludeFields = "embedding";
        const query = {
            q: query_text,
            query_by: query_by || "vesselName",
            include_fields: includeFields,
            exclude_fields: excludeFields,
            page: page,
            per_page: per_page
        };
        if (filterBy) {
            query.filter_by = filterBy;
        }
        // Handle sorting
        if (sort_by && sort_by !== "relevance") {
            query.sort_by = sort_by;
        }
        // logger.debug(`[Typesense Query] ${JSON.stringify(query)}`);
        const typesenseClient = await getTypesenseClient();
        const results = await typesenseClient.collections(collection).documents().search(query);
        if (!results || !results.hits || results.hits.length === 0) {
            return {
                content: [{
                        type: "text",
                        text: `No expense records found for the specified criteria.`
                    }]
            };
        }
        // Post-filter: Remove any results with forbidden IMOs (additional security layer)
        // Even though we filter at query level, we also filter results to ensure no forbidden IMOs slip through
        const filteredHits = [];
        const forbiddenImos = [];
        for (const hit of results.hits) {
            const document = hit.document;
            if (document && document.imo !== undefined && document.imo !== null) {
                const imo = String(document.imo);
                const isValid = await isValidVesselImoForCompany(imo);
                if (isValid) {
                    filteredHits.push(hit);
                }
                else {
                    forbiddenImos.push(imo);
                }
            }
            else {
                // If no IMO field, include it (shouldn't happen, but safe fallback)
                filteredHits.push(hit);
            }
        }
        // Update results with filtered hits
        results.hits = filteredHits;
        results.found = filteredHits.length;
        if (filteredHits.length === 0) {
            return {
                content: [{
                        type: "text",
                        text: `No expense records found after filtering by company IMOs.`
                    }]
            };
        }
        // Format results using the utility function
        let title = `Universal Expense Search Results for '${query_text}'`;
        const linkHeader = `Universal expense search result for query: '${query_text}' in fields: ${query_by}`;
        return await processTypesenseResults(results, "universal_expense_search", title, linkHeader);
    }
    catch (error) {
        // logger.error('Error performing universal expense search:', error);
        throw new Error(`Error performing search: ${error.message}`);
    }
}
export async function get_complete_vessel_purchase_requisition_data(arguments_) {
    // Validate that only allowed properties are present
    //   const providedProperties = Object.keys(arguments_);
    //   const invalidProperties = providedProperties.filter(prop => !GetCompleteVesselPurchaseRequisitionDataKeys.includes(prop as any));
    //   if (invalidProperties.length > 0) {
    //       throw new Error(`Invalid properties provided: ${invalidProperties.join(', ')}. Only the following properties are allowed: ${GetCompleteVesselPurchaseRequisitionDataKeys.join(', ')}`);
    //   }
    const collection = "purchase";
    const imo = arguments_.imo;
    const start_date = arguments_.start_date;
    const end_date = arguments_.end_date;
    if (!imo) {
        throw new Error("IMO number is required");
    }
    try {
        // Validate vessel IMO belongs to company
        const isValid = await isValidVesselImoForCompany(imo);
        if (!isValid) {
            return {
                content: [{
                        type: "text",
                        text: `Access denied: Vessel IMO ${imo} does not belong to your company.`,
                        title: "Access Denied"
                    }]
            };
        }
        // Compose filter_by string from inputs
        const filterParts = [`imo:${imo}`];
        if (start_date) {
            const startTs = Math.floor(new Date(start_date).getTime() / 1000);
            filterParts.push(`purchaseRequisitionDate:>=${startTs}`);
        }
        if (end_date) {
            const endTs = Math.floor(new Date(end_date).getTime() / 1000);
            filterParts.push(`purchaseRequisitionDate:<=${endTs}`);
        }
        const filterBy = filterParts.join(" && ");
        // Build query for export
        const query = {
            filter_by: filterBy
        };
        // logger.debug(`[Typesense Query] ${JSON.stringify(query)}`);
        // Use export method to get all documents
        const typesenseClient = await getTypesenseClient();
        const exportResult = await typesenseClient.collections(collection).documents().export(query);
        // Debug the export result
        // logger.debug(`Export result type: ${typeof exportResult}`);
        // logger.debug(`Export result length: ${exportResult?.length || 'undefined'}`);
        // logger.debug(`First 200 chars of export result: ${exportResult?.substring(0, 200)}`);
        // Parse the export result (it's typically JSONL format)
        const lines = exportResult.split('\n').filter((line) => line.trim());
        // logger.debug(`Number of lines after split: ${lines.length}`);
        // logger.debug(`First line: ${lines[0]}`);
        const documents = lines.map((line, index) => {
            try {
                const doc = JSON.parse(line);
                // Remove embedding field and convert dates
                delete doc.embedding;
                const convertedDoc = convertUnixDates(doc);
                // if (index === 0) {
                //     // logger.debug(`First converted document keys: ${Object.keys(convertedDoc).join(', ')}`);
                // }
                return convertedDoc;
            }
            catch (error) {
                // logger.error(`Error parsing line ${index}: ${line}`);
                return {};
            }
        });
        if (documents.length === 0) {
            return {
                content: [{
                        type: "text",
                        text: `No purchase requisitions found for vessel with IMO ${imo}.`,
                    }]
            };
        }
        const vesselName = documents[0]?.vesselName || null;
        const title = `Exported purchase requisitions for IMO ${imo}`;
        const artifactTitle = `Purchase requisition export artifact for IMO ${imo}`;
        const linkHeader = `Purchase requisition export for IMO ${imo}`;
        // Process results for export tool
        return {
            content: [{
                    type: "text",
                    text: JSON.stringify({
                        summary: {
                            imo: imo,
                            vesselName: vesselName,
                            recordCount: documents.length,
                            exportType: "purchase_requisition_data"
                        },
                        data: documents
                    }, null, 2),
                    title: title,
                    format: "json"
                }]
        };
    }
    catch (error) {
        //   logger.error(`Error executing purchase requisition export: ${error}`);
        throw new Error(`Error exporting purchase requisitions: ${error.message}`);
    }
}
export async function get_complete_vessel_expense_data(arguments_) {
    // Validate that only allowed properties are present
    //   const providedProperties = Object.keys(arguments_);
    //   const invalidProperties = providedProperties.filter(prop => !GetCompleteVesselExpenseDataKeys.includes(prop as any));
    //   if (invalidProperties.length > 0) {
    //       throw new Error(`Invalid properties provided: ${invalidProperties.join(', ')}. Only the following properties are allowed: ${GetCompleteVesselExpenseDataKeys.join(', ')}`);
    //   }
    const collection = "expense";
    const imo = arguments_.imo;
    const start_date = arguments_.start_date;
    const end_date = arguments_.end_date;
    const excludeFieldsStr = "_id,docId,fleetId,vesselId,fleetManagerId,technicalSuperintendentId";
    if (!imo) {
        throw new Error("imo is required. Only the following properties are allowed: imo, start_date, end_date. Please check the tool schema for get_complete_vessel_expense_data for the allowed properties.");
    }
    try {
        // Validate vessel IMO belongs to company
        const isValid = await isValidVesselImoForCompany(imo);
        if (!isValid) {
            return {
                content: [{
                        type: "text",
                        text: `Access denied: Vessel IMO ${imo} does not belong to your company.`,
                        title: "Access Denied"
                    }]
            };
        }
        // Build filter_by string from inputs
        const filterParts = [`imo:${imo}`];
        if (start_date) {
            const startTs = Math.floor(new Date(start_date).getTime() / 1000);
            filterParts.push(`expenseDate:>=${startTs}`);
        }
        if (end_date) {
            const endTs = Math.floor(new Date(end_date).getTime() / 1000);
            filterParts.push(`expenseDate:<=${endTs}`);
        }
        const filterBy = filterParts.join(" && ");
        // Build query for export
        const query = {
            filter_by: filterBy,
            exclude_fields: excludeFieldsStr
        };
        // logger.debug(`[Typesense Query] ${JSON.stringify(query)}`);
        // Use export method to get all documents
        const typesenseClient = await getTypesenseClient();
        const exportResult = await typesenseClient.collections(collection).documents().export(query);
        // Debug the export result
        // logger.debug(`Export result type: ${typeof exportResult}`);
        // logger.debug(`Export result length: ${exportResult?.length || 'undefined'}`);
        // logger.debug(`First 200 chars of export result: ${exportResult?.substring(0, 200)}`);
        // Parse the export result (it's typically JSONL format)
        const lines = exportResult.split('\n').filter((line) => line.trim());
        // logger.debug(`Number of lines after split: ${lines.length}`);
        // logger.debug(`First line: ${lines[0]}`);
        const documents = lines.map((line, index) => {
            try {
                const doc = JSON.parse(line);
                // Remove embedding field and convert dates
                delete doc.embedding;
                const convertedDoc = convertUnixDates(doc);
                if (index === 0) {
                    // logger.debug(`First converted document keys: ${Object.keys(convertedDoc).join(', ')}`);
                }
                return convertedDoc;
            }
            catch (error) {
                // logger.error(`Error parsing line ${index}: ${line}`);
                return {};
            }
        });
        if (documents.length === 0) {
            return {
                content: [{
                        type: "text",
                        text: `No expense records found for vessel with IMO ${imo}.`,
                        title: "No Expense Records Found",
                        format: "json"
                    }]
            };
        }
        const vesselName = documents[0]?.vesselName || null;
        const title = `Exported expense records for IMO ${imo}`;
        // Process results for export tool
        return {
            content: [{
                    type: "text",
                    text: JSON.stringify({
                        summary: {
                            imo: imo,
                            vesselName: vesselName,
                            recordCount: documents.length,
                            exportType: "complete_expense_data"
                        },
                        data: documents
                    }, null, 2),
                    title: title,
                    format: "json"
                }]
        };
    }
    catch (error) {
        //   logger.error(`Error executing expense export: ${error}`);
        throw new Error(`Error exporting expense records: ${error.message}`);
    }
}
export async function get_complete_vessel_budget_data(arguments_) {
    // Validate that only allowed properties are present
    //   const providedProperties = Object.keys(arguments_);
    //   const invalidProperties = providedProperties.filter(prop => !GetCompleteVesselBudgetDataKeys.includes(prop as any));
    //   if (invalidProperties.length > 0) {
    //       throw new Error(`Invalid properties provided: ${invalidProperties.join(', ')}. Only the following properties are allowed: ${GetCompleteVesselBudgetDataKeys.join(', ')}`);
    //   }
    const collection = "budget";
    const imo = arguments_.imo;
    const start_date = arguments_.start_date;
    const end_date = arguments_.end_date;
    const excludeFieldsStr = "_id,docId,fleetId,vesselId,fleetManagerId,technicalSuperintendentId";
    if (!imo) {
        throw new Error("imo is required. Only the following properties are allowed: imo, start_date, end_date. Please check the tool schema for get_complete_vessel_budget_data for the allowed properties.");
    }
    try {
        // Validate vessel IMO belongs to company
        const isValid = await isValidVesselImoForCompany(imo);
        if (!isValid) {
            return {
                content: [{
                        type: "text",
                        text: `Access denied: Vessel IMO ${imo} does not belong to your company.`,
                        title: "Access Denied"
                    }]
            };
        }
        // Build filter_by string from inputs
        const filterParts = [`imo:${imo}`];
        if (start_date) {
            const startTs = Math.floor(new Date(start_date).getTime() / 1000);
            filterParts.push(`date:>=${startTs}`);
        }
        if (end_date) {
            const endTs = Math.floor(new Date(end_date).getTime() / 1000);
            filterParts.push(`date:<=${endTs}`);
        }
        const filterBy = filterParts.join(" && ");
        // Build query for export
        const query = {
            filter_by: filterBy,
            exclude_fields: excludeFieldsStr
        };
        // logger.debug(`[Typesense Query] ${JSON.stringify(query)}`);
        // Use export method to get all documents
        const typesenseClient = await getTypesenseClient();
        const exportResult = await typesenseClient.collections(collection).documents().export(query);
        // Debug the export result
        // logger.debug(`Export result type: ${typeof exportResult}`);
        // logger.debug(`Export result length: ${exportResult?.length || 'undefined'}`);
        // logger.debug(`First 200 chars of export result: ${exportResult?.substring(0, 200)}`);
        // Parse the export result (it's typically JSONL format)
        const lines = exportResult.split('\n').filter((line) => line.trim());
        // logger.debug(`Number of lines after split: ${lines.length}`);
        // logger.debug(`First line: ${lines[0]}`);
        const documents = lines.map((line, index) => {
            try {
                const doc = JSON.parse(line);
                // Remove embedding field 
                delete doc.embedding;
                // if (index === 0) {
                //     logger.debug(`Original document keys before conversion: ${Object.keys(doc).join(', ')}`);
                // }
                // Convert Unix timestamps to ISO strings
                const convertedDoc = convertUnixDates(doc);
                if (index === 0) {
                    // logger.debug(`Final document keys: ${Object.keys(convertedDoc).join(', ')}`);
                }
                return convertedDoc;
            }
            catch (error) {
                // logger.error(`Error parsing line ${index}: ${line}`);
                return {};
            }
        });
        if (documents.length === 0) {
            return {
                content: [{
                        type: "text",
                        text: `No budget records found for vessel with IMO ${imo}.`,
                        title: "No Budget Records Found",
                        format: "json"
                    }]
            };
        }
        const vesselName = documents[0]?.vesselName || null;
        const title = `Exported budget records for IMO ${imo}`;
        // Process results for export tool
        return {
            content: [{
                    type: "text",
                    text: JSON.stringify({
                        summary: {
                            imo: imo,
                            vesselName: vesselName,
                            recordCount: documents.length,
                            exportType: "complete_budget_data"
                        },
                        data: documents
                    }, null, 2),
                    title: title,
                    format: "json"
                }]
        };
    }
    catch (error) {
        //   logger.error(`Error executing budget export: ${error}`);
        throw new Error(`Error exporting budget records: ${error.message}`);
    }
}
export async function get_complete_fleet_purchases(arguments_) {
    // logger.info("exportFleetPurchases called", arguments_);
    const fleetImo = arguments_.fleet_imo;
    const startDate = arguments_.start_date;
    const endDate = arguments_.end_date;
    if (!fleetImo) {
        throw new Error("fleet_imo is required. Only the following properties are allowed: fleet_imo, start_date, end_date. Please check the tool schema for get_complete_fleet_purchases for the allowed properties.");
    }
    try {
        // Validate fleet IMO belongs to company
        const isValidFleetImo = await isValidFleetImoForCompany(fleetImo);
        if (!isValidFleetImo) {
            return {
                content: [{
                        type: "text",
                        text: `Access denied: Fleet IMO ${fleetImo} does not belong to your company.`,
                        title: "Access Denied"
                    }]
            };
        }
        // Step 1: Get vessel IMO list from fleet
        // logger.info(`Step 1: Getting vessel IMO list for fleet IMO ${fleetImo}`);
        const vesselImoList = await getVesselImoListFromFleet(Number(fleetImo));
        if (!vesselImoList || vesselImoList.length === 0) {
            return {
                content: [{
                        type: "text",
                        text: `No vessels found for fleet IMO: ${fleetImo}`,
                        title: "No Vessels Found"
                    }]
            };
        }
        // Filter IMO list to only include company IMOs
        const filteredImoList = await filterImoListByCompany(vesselImoList);
        if (filteredImoList.length === 0) {
            return {
                content: [{
                        type: "text",
                        text: `No vessels found for fleet IMO ${fleetImo} that belong to your company.`,
                        title: "No Accessible Vessels Found"
                    }]
            };
        }
        // logger.info(`Found ${filteredImoList.length} vessels in fleet IMO ${fleetImo} (filtered from ${vesselImoList.length} total)`);
        // Step 2: Export purchases for all vessels in the fleet
        // logger.info(`Step 2: Exporting purchases for ${filteredImoList.length} vessels`);
        const purchaseDocuments = await exportPurchasesForImoList(filteredImoList, startDate, endDate);
        if (!purchaseDocuments || purchaseDocuments.length === 0) {
            return {
                content: [{
                        type: "text",
                        text: `No purchase records found for fleet IMO ${fleetImo} with the specified criteria`,
                        title: "No Purchase Records Found"
                    }]
            };
        }
        // logger.info(`Found ${purchaseDocuments.length} purchase records for fleet IMO ${fleetImo}`);
        // Step 3: Process documents and convert Unix dates
        // logger.info(`Step 3: Processing ${purchaseDocuments.length} records for JSON export`);
        const processedDocuments = purchaseDocuments.map(doc => {
            // Remove embedding field and convert dates
            delete doc.embedding;
            return convertUnixDates(doc);
        });
        const fleetName = purchaseDocuments.length > 0 ? purchaseDocuments[0].fleetName : null;
        const title = `Exported purchase records for fleet IMO ${fleetImo}`;
        // Process results for export tool
        return {
            content: [{
                    type: "text",
                    text: JSON.stringify({
                        summary: {
                            fleetImo: fleetImo,
                            fleetName: fleetName,
                            recordCount: processedDocuments.length,
                            exportType: "fleet_purchase_data",
                            vesselCount: vesselImoList.length
                        },
                        data: processedDocuments
                    }, null, 2),
                    title: title,
                    format: "json"
                }]
        };
    }
    catch (error) {
        // logger.error(`Error exporting fleet purchases for IMO ${fleetImo}: ${error}`);
        throw new Error(`Error exporting fleet purchases: ${String(error)}`);
    }
}
export async function get_complete_fleet_budgets(arguments_) {
    // logger.info("exportFleetBudgets called", arguments_);
    const fleetImo = arguments_.fleet_imo;
    const startDate = arguments_.start_date;
    const endDate = arguments_.end_date;
    if (!fleetImo) {
        throw new Error("fleet_imo is required. Only the following properties are allowed: fleet_imo, start_date, end_date. Please check the tool schema for get_complete_fleet_budgets for the allowed properties.");
    }
    try {
        // Step 1: Get vessel IMO list from fleet
        // logger.info(`Step 1: Getting vessel IMO list for fleet IMO ${fleetImo}`);
        const vesselImoList = await getVesselImoListFromFleet(Number(fleetImo));
        if (!vesselImoList || vesselImoList.length === 0) {
            return {
                content: [{
                        type: "text",
                        text: `No vessels found for fleet IMO: ${fleetImo}`,
                        title: "No Vessels Found"
                    }]
            };
        }
        // Filter IMO list to only include company IMOs
        const filteredImoList = await filterImoListByCompany(vesselImoList);
        if (filteredImoList.length === 0) {
            return {
                content: [{
                        type: "text",
                        text: `No vessels found for fleet IMO ${fleetImo} that belong to your company.`,
                        title: "No Accessible Vessels Found"
                    }]
            };
        }
        // logger.info(`Found ${filteredImoList.length} vessels in fleet IMO ${fleetImo} (filtered from ${vesselImoList.length} total)`);
        // Step 2: Export budgets for all vessels in the fleet
        // logger.info(`Step 2: Exporting budgets for ${filteredImoList.length} vessels`);
        const budgetDocuments = await exportBudgetsForImoList(filteredImoList, startDate, endDate);
        if (!budgetDocuments || budgetDocuments.length === 0) {
            return {
                content: [{
                        type: "text",
                        text: `No budget records found for fleet IMO ${fleetImo} with the specified criteria`,
                        title: "No Budget Records Found"
                    }]
            };
        }
        // logger.info(`Found ${budgetDocuments.length} budget records for fleet IMO ${fleetImo}`);
        // Step 3: Process documents and convert Unix dates
        // logger.info(`Step 3: Processing ${budgetDocuments.length} records for JSON export`);
        const processedDocuments = budgetDocuments.map(doc => {
            // Remove embedding field and convert dates
            delete doc.embedding;
            return convertUnixDates(doc);
        });
        const fleetName = budgetDocuments.length > 0 ? budgetDocuments[0].fleetName : null;
        const title = `Exported budget records for fleet IMO ${fleetImo}`;
        // Process results for export tool
        return {
            content: [{
                    type: "text",
                    text: JSON.stringify({
                        summary: {
                            fleetImo: fleetImo,
                            fleetName: fleetName,
                            recordCount: processedDocuments.length,
                            exportType: "fleet_budget_data",
                            vesselCount: vesselImoList.length
                        },
                        data: processedDocuments
                    }, null, 2),
                    title: title,
                    format: "json"
                }]
        };
    }
    catch (error) {
        // logger.error(`Error exporting fleet budgets for IMO ${fleetImo}: ${error}`);
        throw new Error(`Error exporting fleet budgets: ${String(error)}`);
    }
}
export async function get_complete_fleet_expenses(arguments_) {
    // logger.info("exportFleetExpenses called", arguments_);
    const fleetImo = arguments_.fleet_imo;
    const startDate = arguments_.start_date;
    const endDate = arguments_.end_date;
    if (!fleetImo) {
        throw new Error("fleet_imo is required. Only the following properties are allowed: fleet_imo, start_date, end_date. Please check the tool schema for get_complete_fleet_expenses for the allowed properties.");
    }
    try {
        // Step 1: Get vessel IMO list from fleet
        // logger.info(`Step 1: Getting vessel IMO list for fleet IMO ${fleetImo}`);
        const vesselImoList = await getVesselImoListFromFleet(Number(fleetImo));
        if (!vesselImoList || vesselImoList.length === 0) {
            return {
                content: [{
                        type: "text",
                        text: `No vessels found for fleet IMO: ${fleetImo}`,
                        title: "No Vessels Found"
                    }]
            };
        }
        // Filter IMO list to only include company IMOs
        const filteredImoList = await filterImoListByCompany(vesselImoList);
        if (filteredImoList.length === 0) {
            return {
                content: [{
                        type: "text",
                        text: `No vessels found for fleet IMO ${fleetImo} that belong to your company.`,
                        title: "No Accessible Vessels Found"
                    }]
            };
        }
        // logger.info(`Found ${filteredImoList.length} vessels in fleet IMO ${fleetImo} (filtered from ${vesselImoList.length} total)`);
        // Step 2: Export expenses for all vessels in the fleet
        // logger.info(`Step 2: Exporting expenses for ${filteredImoList.length} vessels`);
        const expenseDocuments = await exportExpensesForImoList(filteredImoList, startDate, endDate);
        if (!expenseDocuments || expenseDocuments.length === 0) {
            return {
                content: [{
                        type: "text",
                        text: `No expense records found for fleet IMO ${fleetImo} with the specified criteria`,
                        title: "No Expense Records Found"
                    }]
            };
        }
        // logger.info(`Found ${expenseDocuments.length} expense records for fleet IMO ${fleetImo}`);
        // Step 3: Process documents and convert Unix dates
        // logger.info(`Step 3: Processing ${expenseDocuments.length} records for JSON export`);
        const processedDocuments = expenseDocuments.map(doc => {
            // Remove embedding field and convert dates
            delete doc.embedding;
            return convertUnixDates(doc);
        });
        const fleetName = expenseDocuments.length > 0 ? expenseDocuments[0].fleetName : null;
        const title = `Exported expense records for fleet IMO ${fleetImo}`;
        // Process results for export tool
        return {
            content: [{
                    type: "text",
                    text: JSON.stringify({
                        summary: {
                            fleetImo: fleetImo,
                            fleetName: fleetName,
                            recordCount: processedDocuments.length,
                            exportType: "fleet_expense_data",
                            vesselCount: vesselImoList.length
                        },
                        data: processedDocuments
                    }, null, 2),
                    title: title,
                    format: "json"
                }]
        };
    }
    catch (error) {
        // logger.error(`Error exporting fleet expenses for IMO ${fleetImo}: ${error}`);
        throw new Error(`Error exporting fleet expenses: ${String(error)}`);
    }
}
// To Do - Add validations for the argument
export async function fleet_procurement_and_budget_information(args) {
    // logger.info("fleet_procurement_and_budget_information called", args);
    try {
        const imo = args.imo;
        const questionNo = args.question_no;
        if (!imo) {
            throw new Error("imo is required. Only the following properties are allowed: imo, question_no. Please check the tool schema for fleet_procurement_and_budget_information for the allowed properties.");
        }
        if (!questionNo) {
            throw new Error("Question number is required");
        }
        // Validate fleet IMO belongs to company (this tool uses fleet IMO)
        const isValidFleetImo = await isValidFleetImoForCompany(imo);
        if (!isValidFleetImo) {
            return {
                content: [{
                        type: "text",
                        text: `Access denied: Fleet IMO ${imo} does not belong to your company.`,
                        title: "Access Denied"
                    }]
            };
        }
        // Validate question number against allowed values
        const allowedQuestions = [11, 121, 257];
        if (!allowedQuestions.includes(questionNo)) {
            return {
                content: [{
                        type: "text",
                        text: `Invalid question number: ${questionNo}. Allowed values are: ${allowedQuestions.join(', ')}`
                    }]
            };
        }
        // logger.info(`Fetching fleet management information for questionNo: ${questionNo}, fleet IMO: ${imo}`);
        // Initialize database connection
        const qaDbName = process.env.QA_DB_NAME || 'syia-etl-dev';
        const qaMongoUri = process.env.QA_MONGO_URI || '';
        // Use the helper function directly
        const result = await fetchQADetailsAndCreateResponse(imo, questionNo, `fleet_procurement_and_budget_information_q${questionNo}`, `fleet_procurement_and_budget_information_q${questionNo}`, qaDbName, qaMongoUri, 'vesselinfos');
        return result;
    }
    catch (error) {
        // logger.error("Error in getFleetManagementInformation", { error });
        throw new Error(`Error retrieving fleet management information: ${error instanceof Error ? error.message : String(error)}`);
    }
}
export async function get_vessel_purchase_order_information(args) {
    // logger.info("getVesselPurchaseOrderInformation called", args);
    // Initialize database connection (moved outside try block for error handling)
    const qaDbName = process.env.QA_DB_NAME || 'syia-etl-dev';
    const qaMongoUri = process.env.QA_MONGO_URI || '';
    try {
        const imo = args.imo;
        const questionNo = args.question_no;
        if (!imo) {
            throw new Error("imo is required. Only the following properties are allowed: imo, question_no. Please check the tool schema for get_vessel_purchase_order_information for the allowed properties.");
        }
        if (!questionNo) {
            throw new Error("Question number is required");
        }
        // Validate vessel IMO belongs to company
        const isValid = await isValidVesselImoForCompany(imo);
        if (!isValid) {
            return {
                content: [{
                        type: "text",
                        text: `Access denied: Vessel IMO ${imo} does not belong to your company.`,
                        title: "Access Denied"
                    }]
            };
        }
        // Validate question number against allowed values
        const allowedQuestions = [122, 123];
        if (!allowedQuestions.includes(questionNo)) {
            return {
                content: [{
                        type: "text",
                        text: `Invalid question number: ${questionNo}. Allowed values are: ${allowedQuestions.join(', ')}`
                    }]
            };
        }
        // logger.info(`Fetching vessel purchase order information for questionNo: ${questionNo}, vessel IMO: ${imo}`);
        // Use the helper function directly
        const result = await fetchQADetailsAndCreateResponse(imo, questionNo, `get_vessel_purchase_order_information_q${questionNo}`, `vessel_purchase_order_q${questionNo}`, qaDbName, qaMongoUri, 'vesselinfos');
        return result;
    }
    catch (error) {
        // logger.error("Error in getVesselPurchaseOrderInformation", { error });
        throw new Error(`Error retrieving vessel purchase order information: URI=${qaMongoUri} DB=${qaDbName} - ${error instanceof Error ? error.message : String(error)}`);
    }
}
export async function get_vessel_budget_expense_information(args) {
    // logger.info("getVesselBudgetExpenseInformation called", args);
    try {
        const imo = args.imo;
        const questionNo = args.question_no;
        if (!imo) {
            throw new Error("imo is required. Only the following properties are allowed: imo, question_no. Please check the tool schema for get_vessel_budget_expense_information for the allowed properties.");
        }
        if (!questionNo) {
            throw new Error("Question number is required");
        }
        // Validate vessel IMO belongs to company
        const isValid = await isValidVesselImoForCompany(imo);
        if (!isValid) {
            return {
                content: [{
                        type: "text",
                        text: `Access denied: Vessel IMO ${imo} does not belong to your company.`,
                        title: "Access Denied"
                    }]
            };
        }
        // Validate question number against allowed values
        const allowedQuestions = [9, 12, 13, 14, 15, 16, 17];
        if (!allowedQuestions.includes(questionNo)) {
            return {
                content: [{
                        type: "text",
                        text: `Invalid question number: ${questionNo}. Allowed values are: ${allowedQuestions.join(', ')}`
                    }]
            };
        }
        // logger.info(`Fetching vessel budget expense information for questionNo: ${questionNo}, vessel IMO: ${imo}`);
        // Initialize database connection
        const qaDbName = process.env.QA_DB_NAME || 'syia-etl-dev';
        const qaMongoUri = process.env.QA_MONGO_URI || '';
        // Use the helper function directly
        const result = await fetchQADetailsAndCreateResponse(imo, questionNo, `get_vessel_budget_expense_information_q${questionNo}`, `vessel_budget_expense_q${questionNo}`, qaDbName, qaMongoUri, 'vesselinfos');
        return result;
    }
    catch (error) {
        // logger.error("Error in getVesselBudgetExpenseInformation", { error });
        throw new Error(`Error retrieving vessel budget expense information: ${error instanceof Error ? error.message : String(error)}`);
    }
}