/**
* 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);
}
}