import type { B2BHit, B2BTopic, ImportanceLabel, DecisionLabel } from './types' /** * Process email content into a B2BHit (Manual Import for Testing). * Pure function — no state mutations. */ export function processEmailToHit( emailContent: string, tenantId: string, defaultTopicId: string, emailSubject?: string ): B2BHit { // Parse email content to extract useful information const lines = emailContent.split('\n').filter(l => l.trim()) const title = emailSubject || lines[0]?.slice(0, 100) || 'Manuell eingefuegter Alert' // Try to extract URLs from content const urlRegex = /(https?:\/\/[^\s<>"]+)/g const urls = emailContent.match(urlRegex) || [] const firstUrl = urls[0] || 'https://example.com/manual-import' // Extract snippet (first meaningful paragraph) const snippet = lines.slice(0, 3).join(' ').slice(0, 300) || emailContent.slice(0, 300) // Simulate AI analysis - look for procurement signals const procurementSignals = ['ausschreibung', 'tender', 'vergabe', 'beschaffung', 'auftrag', 'angebot', 'submission', 'procurement', 'rfp', 'rfq', 'bid'] const productSignals = ['parking', 'parkschein', 'ladesäule', 'ev charging', 'ladestation', 'tankstelle', 'fuel', 'bezahlterminal', 'payment'] const buyerSignals = ['stadt', 'kommune', 'gemeinde', 'city', 'municipality', 'council', 'stadtwerke', 'öffentlich', 'public'] const negativeSignals = ['stellenangebot', 'job', 'karriere', 'news', 'blog', 'press release'] const lowerContent = emailContent.toLowerCase() const foundProcurement = procurementSignals.filter(s => lowerContent.includes(s)) const foundProducts = productSignals.filter(s => lowerContent.includes(s)) const foundBuyers = buyerSignals.filter(s => lowerContent.includes(s)) const foundNegatives = negativeSignals.filter(s => lowerContent.includes(s)) // Calculate importance score (0-100) let score = 30 // base score score += foundProcurement.length * 15 score += foundProducts.length * 10 score += foundBuyers.length * 12 score -= foundNegatives.length * 20 score = Math.max(0, Math.min(100, score)) // Determine importance label let importanceLabel: ImportanceLabel = 'INFO' if (score >= 80) importanceLabel = 'KRITISCH' else if (score >= 65) importanceLabel = 'DRINGEND' else if (score >= 50) importanceLabel = 'WICHTIG' else if (score >= 30) importanceLabel = 'PRUEFEN' // Determine decision label let decisionLabel: DecisionLabel = 'irrelevant' let decisionConfidence = 0.5 if (foundNegatives.length > 1) { decisionLabel = 'irrelevant' decisionConfidence = 0.8 } else if (foundProcurement.length >= 2 && foundProducts.length >= 1) { decisionLabel = 'relevant' decisionConfidence = 0.85 } else if (foundProcurement.length >= 1 || foundProducts.length >= 1) { decisionLabel = 'needs_review' decisionConfidence = 0.6 } // Try to guess buyer and country let buyerGuess: string | undefined let countryGuess: string | undefined const countryPatterns = [ { pattern: /deutschland|germany|german/i, country: 'DE' }, { pattern: /österreich|austria|austrian/i, country: 'AT' }, { pattern: /schweiz|switzerland|swiss/i, country: 'CH' }, { pattern: /frankreich|france|french/i, country: 'FR' }, { pattern: /niederlande|netherlands|dutch/i, country: 'NL' }, ] for (const { pattern, country } of countryPatterns) { if (pattern.test(emailContent)) { countryGuess = country break } } // Extract potential buyer name (look for "Stadt X" or "Kommune Y") const buyerMatch = emailContent.match(/(?:stadt|kommune|gemeinde|city of|municipality of)\s+([A-Za-zäöüß]+)/i) if (buyerMatch) { buyerGuess = buyerMatch[0] } // Try to find deadline let deadlineGuess: string | undefined const dateMatch = emailContent.match(/(\d{1,2})[.\/](\d{1,2})[.\/](\d{2,4})/) if (dateMatch) { const day = parseInt(dateMatch[1]) const month = parseInt(dateMatch[2]) const year = dateMatch[3].length === 2 ? 2000 + parseInt(dateMatch[3]) : parseInt(dateMatch[3]) const date = new Date(year, month - 1, day) if (date > new Date()) { deadlineGuess = date.toISOString() } } // Create new hit const newHit: B2BHit = { id: `manual_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, tenantId, topicId: defaultTopicId, sourceType: 'email', sourceRef: 'manual_import', originalUrl: firstUrl, canonicalUrl: firstUrl, title, snippet, fullText: emailContent, foundAt: new Date(), language: 'de', countryGuess, buyerGuess, deadlineGuess, importanceScore: score, importanceLabel, decisionLabel, decisionConfidence, decisionTrace: { rulesTriggered: [ ...(foundProcurement.length > 0 ? ['procurement_signal_detected'] : []), ...(foundProducts.length > 0 ? ['product_match_found'] : []), ...(foundBuyers.length > 0 ? ['public_buyer_signal'] : []), ...(foundNegatives.length > 0 ? ['negative_signal_detected'] : []), ], llmUsed: true, llmConfidence: decisionConfidence, signals: { procurementSignalsFound: foundProcurement, publicBuyerSignalsFound: foundBuyers, productSignalsFound: foundProducts, negativesFound: foundNegatives } }, isRead: false, createdAt: new Date() } return newHit }