Skip to main content
Glama
storage.ts5.3 kB
import fs from 'fs'; import path from 'path'; import { HistoricalQuote, QuoteDocument, QuoteEvaluationResult } from './types'; const DATA_DIR = path.join(__dirname, '..', 'data'); const QUOTES_FILE = path.join(DATA_DIR, 'quotes.json'); const EVALUATIONS_FILE = path.join(DATA_DIR, 'evaluations.json'); /** * Initialize data directory and files */ export function initializeStorage(): void { if (!fs.existsSync(DATA_DIR)) { fs.mkdirSync(DATA_DIR, { recursive: true }); } if (!fs.existsSync(QUOTES_FILE)) { fs.writeFileSync(QUOTES_FILE, JSON.stringify([], null, 2)); } if (!fs.existsSync(EVALUATIONS_FILE)) { fs.writeFileSync(EVALUATIONS_FILE, JSON.stringify([], null, 2)); } } /** * Load historical quotes from storage */ export function loadHistoricalQuotes(): HistoricalQuote[] { try { const data = fs.readFileSync(QUOTES_FILE, 'utf-8'); return JSON.parse(data); } catch (error) { console.error('Error loading historical quotes:', error); return []; } } /** * Save a new historical quote */ export function saveHistoricalQuote(quote: HistoricalQuote): void { const quotes = loadHistoricalQuotes(); // Check if quote already exists (by ID) const existingIndex = quotes.findIndex(q => q.id === quote.id); if (existingIndex >= 0) { // Update existing quotes[existingIndex] = quote; } else { // Add new quotes.push(quote); } fs.writeFileSync(QUOTES_FILE, JSON.stringify(quotes, null, 2)); } /** * Convert a quote document to historical quote */ export function convertToHistoricalQuote( doc: QuoteDocument, approved: boolean, approvedBy?: string ): HistoricalQuote { const totalCost = doc.lines.reduce((sum, line) => sum + line.total, 0); const qty = doc.lines.find(l => l.qty > 0)?.qty || 1; const costPerUnit = totalCost / qty; // Extract lead days from terms const leadMatch = /(\d+)\s*(?:business\s*)?days?/i.exec(doc.terms); const leadDays = leadMatch ? parseInt(leadMatch[1]) : 14; // Try to extract material and processes from description const firstLine = doc.lines[0]?.desc || ''; const material = extractMaterial(firstLine); const processes = extractProcesses(doc.lines); return { id: doc.quoteId, quoteDate: doc.createdAt, customerName: doc.customerName, normalized: { material: material.toLowerCase(), processes: processes.map(p => p.toLowerCase()), qtyRange: getQtyRange(qty), tolerances: extractTolerances(doc.lines), finish: extractFinish(firstLine), }, costPerUnit, totalCost, leadDays, notes: doc.confidence === 'low' ? 'Low confidence estimate' : undefined, approved, approvedBy, sentDate: doc.status === 'sent' ? new Date().toISOString() : undefined, }; } /** * Load past evaluations */ export function loadEvaluations(): QuoteEvaluationResult[] { try { const data = fs.readFileSync(EVALUATIONS_FILE, 'utf-8'); return JSON.parse(data); } catch (error) { console.error('Error loading evaluations:', error); return []; } } /** * Save an evaluation result */ export function saveEvaluation(evaluation: QuoteEvaluationResult): void { const evaluations = loadEvaluations(); evaluations.push(evaluation); // Keep only last 100 evaluations if (evaluations.length > 100) { evaluations.shift(); } fs.writeFileSync(EVALUATIONS_FILE, JSON.stringify(evaluations, null, 2)); } /** * Get evaluation by idempotency key */ export function getEvaluation(idempotencyKey: string): QuoteEvaluationResult | null { const evaluations = loadEvaluations(); return evaluations.find(e => e.idempotencyKey === idempotencyKey) || null; } // Helper functions function extractMaterial(text: string): string { const materialPatterns = [ /6061[-\s]?T6/i, /6061/i, /304[-\s]?SS/i, /304/i, /316[-\s]?SS/i, /stainless\s*steel/i, /aluminum/i, /steel/i, /titanium/i, /brass/i, ]; for (const pattern of materialPatterns) { const match = pattern.exec(text); if (match) return match[0]; } return 'unknown'; } function extractProcesses(lines: any[]): string[] { const processes: string[] = []; const processLine = lines.find(l => l.desc.includes('Processes:')); if (processLine) { const match = /Processes:\s*(.+)/.exec(processLine.desc); if (match) { return match[1].split(',').map(p => p.trim()); } } return processes; } function extractTolerances(lines: any[]): string { const tolLine = lines.find(l => l.desc.includes('Tolerances:')); if (tolLine) { const match = /Tolerances:\s*(.+)/.exec(tolLine.desc); if (match) return match[1]; } return ''; } function extractFinish(text: string): string { const finishPatterns = [ /anodize/i, /powder coat/i, /paint/i, /polish/i, /passivate/i, /plating/i, ]; for (const pattern of finishPatterns) { const match = pattern.exec(text); if (match) return match[0]; } return ''; } function getQtyRange(qty: number): [number, number] { if (qty <= 10) return [1, 10]; if (qty <= 50) return [11, 50]; if (qty <= 100) return [51, 100]; if (qty <= 500) return [101, 500]; if (qty <= 1000) return [501, 1000]; return [1001, 10000]; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/r-long/mcp-quoting-system'

If you have feedback or need assistance with the MCP directory API, please join our Discord server