Skip to main content
Glama
pdf-analysis.service.ts11.2 kB
/** * PDF Analysis Service * Handles parsing and analysis of bioimpedance PDF documents */ import { PDFParse } from 'pdf-parse'; import * as fs from 'fs/promises'; import { BioimpedanceData } from '../types/index.js'; import { logger } from '../utils/logger.js'; import { OpenAIService } from './openai.service.js'; export class PDFAnalysisService { constructor(private readonly openAIService?: OpenAIService) {} /** * Parse bioimpedance PDF and extract data */ async analyzeBioimpedancePDF(filePath: string): Promise<BioimpedanceData> { try { logger.info('Starting PDF analysis', { filePath }); // Read the PDF file const dataBuffer = await fs.readFile(filePath); // Create parser and extract text const pdfParser = new PDFParse({ data: dataBuffer }); const textResult = await pdfParser.getText(); logger.debug('PDF parsed successfully', { pages: textResult.pages.length, textLength: textResult.text.length }); // Extract bioimpedance data from text const extractedData = this.extractBioimpedanceData(textResult.text); // Generate analysis and recommendations const insights = await this.enhanceWithAI(extractedData); const { analysis, recommendations } = insights; const result: BioimpedanceData = { ...extractedData, analysis, recommendations }; logger.info('PDF analysis completed successfully'); return result; } catch (error) { logger.error('Failed to analyze PDF', error); throw new Error(`PDF analysis failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Extract bioimpedance data from PDF text */ private extractBioimpedanceData(text: string): Omit<BioimpedanceData, 'analysis' | 'recommendations'> { const data: Omit<BioimpedanceData, 'analysis' | 'recommendations'> = {}; // Extract patient name const nameMatch = text.match(/(?:paciente|patient|nome|name)[:\s]+([^\n]+)/i); if (nameMatch) { data.patientName = nameMatch[1].trim(); } // Extract date const dateMatch = text.match(/(?:data|date)[:\s]+(\d{1,2}[\/\-]\d{1,2}[\/\-]\d{2,4})/i); if (dateMatch) { data.date = dateMatch[1]; } // Extract weight (kg) const weightMatch = text.match(/(?:peso|weight)[:\s]+(\d+(?:\.\d+)?)\s*(?:kg)?/i); if (weightMatch) { data.weight = parseFloat(weightMatch[1]); } // Extract height (cm) const heightMatch = text.match(/(?:altura|height)[:\s]+(\d+(?:\.\d+)?)\s*(?:cm)?/i); if (heightMatch) { data.height = parseFloat(heightMatch[1]); } // Extract body fat percentage const bodyFatMatch = text.match(/(?:gordura corporal|body fat|%\s*gordura)[:\s]+(\d+(?:\.\d+)?)\s*%?/i); if (bodyFatMatch) { data.bodyFatPercentage = parseFloat(bodyFatMatch[1]); } // Extract muscle mass percentage const muscleMassMatch = text.match(/(?:massa muscular|muscle mass|%\s*m[uú]sculo)[:\s]+(\d+(?:\.\d+)?)\s*%?/i); if (muscleMassMatch) { data.muscleMassPercentage = parseFloat(muscleMassMatch[1]); } // Extract bone mass (kg) const boneMassMatch = text.match(/(?:massa [óo]ssea|bone mass)[:\s]+(\d+(?:\.\d+)?)\s*(?:kg)?/i); if (boneMassMatch) { data.boneMass = parseFloat(boneMassMatch[1]); } // Extract body water percentage const bodyWaterMatch = text.match(/(?:[áa]gua corporal|body water|%\s*[áa]gua)[:\s]+(\d+(?:\.\d+)?)\s*%?/i); if (bodyWaterMatch) { data.bodyWaterPercentage = parseFloat(bodyWaterMatch[1]); } // Extract visceral fat const visceralFatMatch = text.match(/(?:gordura visceral|visceral fat)[:\s]+(\d+(?:\.\d+)?)/i); if (visceralFatMatch) { data.visceralFat = parseFloat(visceralFatMatch[1]); } // Extract BMR (Basal Metabolic Rate) const bmrMatch = text.match(/(?:taxa metab[óo]lica basal|bmr|tmb)[:\s]+(\d+(?:\.\d+)?)\s*(?:kcal)?/i); if (bmrMatch) { data.bmr = parseFloat(bmrMatch[1]); } // Calculate or extract BMI const bmiMatch = text.match(/(?:imc|bmi)[:\s]+(\d+(?:\.\d+)?)/i); if (bmiMatch) { data.bmi = parseFloat(bmiMatch[1]); } else if (data.weight && data.height) { // Calculate BMI if not found: BMI = weight(kg) / (height(m))^2 const heightInMeters = data.height / 100; data.bmi = parseFloat((data.weight / (heightInMeters * heightInMeters)).toFixed(2)); } logger.debug('Extracted bioimpedance data', data); return data; } /** * Generate analysis based on extracted data */ private generateAnalysis(data: Omit<BioimpedanceData, 'analysis' | 'recommendations'>): string { const analyses: string[] = []; // BMI analysis if (data.bmi) { let bmiCategory = ''; if (data.bmi < 18.5) { bmiCategory = 'abaixo do peso (underweight)'; } else if (data.bmi < 25) { bmiCategory = 'peso normal (normal weight)'; } else if (data.bmi < 30) { bmiCategory = 'sobrepeso (overweight)'; } else { bmiCategory = 'obesidade (obesity)'; } analyses.push(`IMC (BMI) de ${data.bmi} indica ${bmiCategory}.`); } // Body fat analysis if (data.bodyFatPercentage) { if (data.bodyFatPercentage > 25) { analyses.push(`Percentual de gordura corporal (${data.bodyFatPercentage}%) está acima do ideal.`); } else if (data.bodyFatPercentage < 10) { analyses.push(`Percentual de gordura corporal (${data.bodyFatPercentage}%) está muito baixo.`); } else { analyses.push(`Percentual de gordura corporal (${data.bodyFatPercentage}%) está em nível saudável.`); } } // Muscle mass analysis if (data.muscleMassPercentage) { if (data.muscleMassPercentage > 40) { analyses.push(`Excelente massa muscular (${data.muscleMassPercentage}%).`); } else if (data.muscleMassPercentage < 30) { analyses.push(`Massa muscular (${data.muscleMassPercentage}%) abaixo do recomendado.`); } else { analyses.push(`Massa muscular (${data.muscleMassPercentage}%) em nível adequado.`); } } // Visceral fat analysis if (data.visceralFat) { if (data.visceralFat > 12) { analyses.push(`Gordura visceral (${data.visceralFat}) em nível elevado - requer atenção.`); } else if (data.visceralFat < 9) { analyses.push(`Gordura visceral (${data.visceralFat}) em nível saudável.`); } else { analyses.push(`Gordura visceral (${data.visceralFat}) em nível limítrofe.`); } } // Body water analysis if (data.bodyWaterPercentage) { if (data.bodyWaterPercentage < 50) { analyses.push(`Nível de hidratação (${data.bodyWaterPercentage}%) pode estar baixo.`); } else { analyses.push(`Nível de hidratação (${data.bodyWaterPercentage}%) adequado.`); } } return analyses.length > 0 ? analyses.join(' ') : 'Análise incompleta - dados insuficientes no documento.'; } /** * Generate recommendations based on extracted data */ private generateRecommendations(data: Omit<BioimpedanceData, 'analysis' | 'recommendations'>): string[] { const recommendations: string[] = []; // BMI-based recommendations if (data.bmi) { if (data.bmi < 18.5) { recommendations.push('Considere aumentar a ingestão calórica com alimentos nutritivos'); recommendations.push('Consulte um nutricionista para um plano alimentar adequado'); } else if (data.bmi >= 25) { recommendations.push('Considere um programa de perda de peso supervisionado'); recommendations.push('Aumente atividade física gradualmente'); } } // Body fat recommendations if (data.bodyFatPercentage && data.bodyFatPercentage > 25) { recommendations.push('Exercícios aeróbicos regulares (3-5x por semana)'); recommendations.push('Reduza consumo de alimentos processados e açúcares'); } // Muscle mass recommendations if (data.muscleMassPercentage && data.muscleMassPercentage < 30) { recommendations.push('Inicie treinamento de força/resistência'); recommendations.push('Aumente ingestão de proteínas (1.6-2.2g/kg de peso)'); } // Visceral fat recommendations if (data.visceralFat && data.visceralFat > 12) { recommendations.push('Reduza consumo de gorduras saturadas e trans'); recommendations.push('Aumente consumo de fibras e alimentos integrais'); recommendations.push('Considere avaliação médica para riscos cardiovasculares'); } // Hydration recommendations if (data.bodyWaterPercentage && data.bodyWaterPercentage < 50) { recommendations.push('Aumente ingestão de água (mínimo 2L por dia)'); recommendations.push('Monitore cor da urina como indicador de hidratação'); } // General recommendations if (recommendations.length === 0) { recommendations.push('Mantenha hábitos saudáveis de alimentação e exercício'); recommendations.push('Realize avaliações periódicas de bioimpedância'); } else { recommendations.push('Consulte profissionais de saúde para orientação personalizada'); recommendations.push('Realize nova avaliação em 3-6 meses para acompanhamento'); } return recommendations; } /** * Parse PDF from base64 string */ async analyzeBioimpedancePDFFromBase64(base64Data: string): Promise<BioimpedanceData> { try { logger.info('Starting PDF analysis from base64'); // Decode base64 to buffer const buffer = Buffer.from(base64Data, 'base64'); // Create parser and extract text const pdfParser = new PDFParse({ data: buffer }); const textResult = await pdfParser.getText(); logger.debug('PDF parsed successfully', { pages: textResult.pages.length, textLength: textResult.text.length }); // Extract bioimpedance data from text const extractedData = this.extractBioimpedanceData(textResult.text); // Generate analysis and recommendations const insights = await this.enhanceWithAI(extractedData); const { analysis, recommendations } = insights; const result: BioimpedanceData = { ...extractedData, analysis, recommendations }; logger.info('PDF analysis completed successfully'); return result; } catch (error) { logger.error('Failed to analyze PDF from base64', error); throw new Error(`PDF analysis failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } private async enhanceWithAI( data: Omit<BioimpedanceData, 'analysis' | 'recommendations'> ): Promise<{ analysis: string; recommendations: string[] }> { const fallback = { analysis: this.generateAnalysis(data), recommendations: this.generateRecommendations(data) }; if (!this.openAIService?.isConfigured()) { return fallback; } return this.openAIService.generateBioimpedanceInsights(data, fallback); } }

Implementation Reference

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/osmarsant/fitslot-mcp'

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