Major release with significant performance improvements and new processing strategy. ## Core Changes - Implemented simple_full_document processing strategy (default) - Full document → LLM approach: 1-2 passes, ~5-6 minutes processing time - Achieved 100% completeness with 2 API calls (down from 5+) - Removed redundant Document AI passes for faster processing ## Financial Data Extraction - Enhanced deterministic financial table parser - Improved FY3/FY2/FY1/LTM identification from varying CIM formats - Automatic merging of parser results with LLM extraction ## Code Quality & Infrastructure - Cleaned up debug logging (removed emoji markers from production code) - Fixed Firebase Secrets configuration (using modern defineSecret approach) - Updated OpenAI API key - Resolved deployment conflicts (secrets vs environment variables) - Added .env files to Firebase ignore list ## Deployment - Firebase Functions v2 deployment successful - All 7 required secrets verified and configured - Function URL: https://api-y56ccs6wva-uc.a.run.app ## Performance Improvements - Processing time: ~5-6 minutes (down from 23+ minutes) - API calls: 1-2 (down from 5+) - Completeness: 100% achievable - LLM Model: claude-3-7-sonnet-latest ## Breaking Changes - Default processing strategy changed to 'simple_full_document' - RAG processor available as alternative strategy 'document_ai_agentic_rag' ## Files Changed - 36 files changed, 5642 insertions(+), 4451 deletions(-) - Removed deprecated documentation files - Cleaned up unused services and models This release represents a major refactoring focused on speed, accuracy, and maintainability.
450 lines
18 KiB
TypeScript
450 lines
18 KiB
TypeScript
import dotenv from 'dotenv';
|
|
import Joi from 'joi';
|
|
import * as functions from 'firebase-functions';
|
|
|
|
// Load environment variables from .env file (for local development)
|
|
dotenv.config();
|
|
|
|
// Use process.env directly - Firebase Functions v2 supports environment variables
|
|
// For production, set environment variables using:
|
|
// - firebase functions:secrets:set for sensitive data (recommended)
|
|
// - defineString() and defineSecret() in function definitions (automatically available in process.env)
|
|
// - .env files for local development
|
|
// MIGRATION NOTE: functions.config() is deprecated and will be removed Dec 31, 2025
|
|
// We keep it as a fallback for backward compatibility during migration
|
|
let env = { ...process.env };
|
|
|
|
// MIGRATION: Firebase Functions v1 uses functions.config(), v2 uses process.env with defineString()/defineSecret()
|
|
// When using defineString() and defineSecret() in function definitions, values are automatically
|
|
// available in process.env. This fallback is only for backward compatibility during migration.
|
|
try {
|
|
const functionsConfig = functions.config();
|
|
if (functionsConfig && Object.keys(functionsConfig).length > 0) {
|
|
console.log('[CONFIG DEBUG] functions.config() fallback available (migration in progress)');
|
|
// Merge functions.config() values into env (process.env takes precedence - this is correct)
|
|
let fallbackCount = 0;
|
|
Object.keys(functionsConfig).forEach(key => {
|
|
if (typeof functionsConfig[key] === 'object' && functionsConfig[key] !== null) {
|
|
// Handle nested config like functions.config().llm.provider
|
|
Object.keys(functionsConfig[key]).forEach(subKey => {
|
|
const envKey = `${key.toUpperCase()}_${subKey.toUpperCase()}`;
|
|
if (!env[envKey]) {
|
|
env[envKey] = String(functionsConfig[key][subKey]);
|
|
fallbackCount++;
|
|
}
|
|
});
|
|
} else {
|
|
// Handle flat config
|
|
const envKey = key.toUpperCase();
|
|
if (!env[envKey]) {
|
|
env[envKey] = String(functionsConfig[key]);
|
|
fallbackCount++;
|
|
}
|
|
}
|
|
});
|
|
if (fallbackCount > 0) {
|
|
console.log(`[CONFIG DEBUG] Using functions.config() fallback for ${fallbackCount} values (migration in progress)`);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
// functions.config() might not be available in v2, that's okay
|
|
console.log('[CONFIG DEBUG] functions.config() not available (this is normal for v2 with defineString/defineSecret)');
|
|
}
|
|
|
|
// Environment validation schema
|
|
const envSchema = Joi.object({
|
|
NODE_ENV: Joi.string().valid('development', 'production', 'test').default('development'),
|
|
PORT: Joi.number().default(5000),
|
|
|
|
// Firebase Configuration (Required for file storage and auth)
|
|
FB_PROJECT_ID: Joi.string().when('NODE_ENV', {
|
|
is: 'production',
|
|
then: Joi.string().required(),
|
|
otherwise: Joi.string().optional()
|
|
}),
|
|
FB_STORAGE_BUCKET: Joi.string().when('NODE_ENV', {
|
|
is: 'production',
|
|
then: Joi.string().required(),
|
|
otherwise: Joi.string().optional()
|
|
}),
|
|
FB_API_KEY: Joi.string().optional(),
|
|
FB_AUTH_DOMAIN: Joi.string().optional(),
|
|
|
|
// Supabase Configuration (Required for cloud-only architecture)
|
|
SUPABASE_URL: Joi.string().when('NODE_ENV', {
|
|
is: 'production',
|
|
then: Joi.string().required(),
|
|
otherwise: Joi.string().optional()
|
|
}),
|
|
SUPABASE_ANON_KEY: Joi.string().when('NODE_ENV', {
|
|
is: 'production',
|
|
then: Joi.string().required(),
|
|
otherwise: Joi.string().optional()
|
|
}),
|
|
SUPABASE_SERVICE_KEY: Joi.string().when('NODE_ENV', {
|
|
is: 'production',
|
|
then: Joi.string().required(),
|
|
otherwise: Joi.string().optional()
|
|
}),
|
|
|
|
// Google Cloud Configuration (Required)
|
|
GCLOUD_PROJECT_ID: Joi.string().required(),
|
|
DOCUMENT_AI_LOCATION: Joi.string().default('us'),
|
|
DOCUMENT_AI_PROCESSOR_ID: Joi.string().required(),
|
|
GCS_BUCKET_NAME: Joi.string().required(),
|
|
DOCUMENT_AI_OUTPUT_BUCKET_NAME: Joi.string().required(),
|
|
GOOGLE_APPLICATION_CREDENTIALS: Joi.string().default('./serviceAccountKey.json'),
|
|
|
|
// Vector Database Configuration
|
|
VECTOR_PROVIDER: Joi.string().valid('supabase', 'pinecone').default('supabase'),
|
|
|
|
// Pinecone Configuration (optional, only if using Pinecone)
|
|
PINECONE_API_KEY: Joi.string().when('VECTOR_PROVIDER', {
|
|
is: 'pinecone',
|
|
then: Joi.string().required(),
|
|
otherwise: Joi.string().allow('').optional()
|
|
}),
|
|
PINECONE_INDEX: Joi.string().when('VECTOR_PROVIDER', {
|
|
is: 'pinecone',
|
|
then: Joi.string().required(),
|
|
otherwise: Joi.string().allow('').optional()
|
|
}),
|
|
|
|
// JWT - Optional for Firebase Auth
|
|
JWT_SECRET: Joi.string().default('default-jwt-secret-change-in-production'),
|
|
JWT_EXPIRES_IN: Joi.string().default('1h'),
|
|
JWT_REFRESH_SECRET: Joi.string().default('default-refresh-secret-change-in-production'),
|
|
JWT_REFRESH_EXPIRES_IN: Joi.string().default('7d'),
|
|
|
|
// File Upload Configuration (Cloud-only)
|
|
MAX_FILE_SIZE: Joi.number().default(104857600), // 100MB
|
|
ALLOWED_FILE_TYPES: Joi.string().default('application/pdf'),
|
|
|
|
// LLM
|
|
LLM_PROVIDER: Joi.string().valid('openai', 'anthropic', 'openrouter').default('openai'),
|
|
OPENAI_API_KEY: Joi.string().when('LLM_PROVIDER', {
|
|
is: 'openai',
|
|
then: Joi.string().required(),
|
|
otherwise: Joi.string().allow('').optional()
|
|
}),
|
|
ANTHROPIC_API_KEY: Joi.string().when('LLM_PROVIDER', {
|
|
is: ['anthropic', 'openrouter'],
|
|
then: Joi.string().required(),
|
|
otherwise: Joi.string().allow('').optional()
|
|
}),
|
|
OPENROUTER_API_KEY: Joi.string().when('LLM_PROVIDER', {
|
|
is: 'openrouter',
|
|
then: Joi.string().optional(), // Optional if using BYOK
|
|
otherwise: Joi.string().allow('').optional()
|
|
}),
|
|
LLM_MODEL: Joi.string().default('gpt-4'),
|
|
LLM_MAX_TOKENS: Joi.number().default(3500),
|
|
LLM_TEMPERATURE: Joi.number().min(0).max(2).default(0.1),
|
|
LLM_PROMPT_BUFFER: Joi.number().default(500),
|
|
|
|
// Security
|
|
BCRYPT_ROUNDS: Joi.number().default(12),
|
|
RATE_LIMIT_WINDOW_MS: Joi.number().default(900000), // 15 minutes
|
|
RATE_LIMIT_MAX_REQUESTS: Joi.number().default(100),
|
|
|
|
// Logging
|
|
LOG_LEVEL: Joi.string().valid('error', 'warn', 'info', 'debug').default('info'),
|
|
LOG_FILE: Joi.string().default('logs/app.log'),
|
|
|
|
// Processing Strategy
|
|
PROCESSING_STRATEGY: Joi.string().valid('document_ai_agentic_rag').default('document_ai_agentic_rag'),
|
|
|
|
// Agentic RAG Configuration
|
|
AGENTIC_RAG_ENABLED: Joi.boolean().default(false),
|
|
AGENTIC_RAG_MAX_AGENTS: Joi.number().default(6),
|
|
AGENTIC_RAG_PARALLEL_PROCESSING: Joi.boolean().default(true),
|
|
AGENTIC_RAG_VALIDATION_STRICT: Joi.boolean().default(true),
|
|
AGENTIC_RAG_RETRY_ATTEMPTS: Joi.number().default(3),
|
|
AGENTIC_RAG_TIMEOUT_PER_AGENT: Joi.number().default(60000),
|
|
|
|
// Agent-Specific Configuration
|
|
AGENT_DOCUMENT_UNDERSTANDING_ENABLED: Joi.boolean().default(true),
|
|
AGENT_FINANCIAL_ANALYSIS_ENABLED: Joi.boolean().default(true),
|
|
AGENT_MARKET_ANALYSIS_ENABLED: Joi.boolean().default(true),
|
|
AGENT_INVESTMENT_THESIS_ENABLED: Joi.boolean().default(true),
|
|
AGENT_SYNTHESIS_ENABLED: Joi.boolean().default(true),
|
|
AGENT_VALIDATION_ENABLED: Joi.boolean().default(true),
|
|
|
|
// Quality Control
|
|
AGENTIC_RAG_QUALITY_THRESHOLD: Joi.number().min(0).max(1).default(0.8),
|
|
AGENTIC_RAG_COMPLETENESS_THRESHOLD: Joi.number().min(0).max(1).default(0.9),
|
|
AGENTIC_RAG_CONSISTENCY_CHECK: Joi.boolean().default(true),
|
|
|
|
// Monitoring and Logging
|
|
AGENTIC_RAG_DETAILED_LOGGING: Joi.boolean().default(true),
|
|
AGENTIC_RAG_PERFORMANCE_TRACKING: Joi.boolean().default(true),
|
|
AGENTIC_RAG_ERROR_REPORTING: Joi.boolean().default(true),
|
|
}).unknown();
|
|
|
|
// Validate environment variables
|
|
// Use the merged env object (process.env + functions.config() fallback)
|
|
const { error, value: envVars } = envSchema.validate(env);
|
|
|
|
// Enhanced error handling for serverless environments
|
|
if (error) {
|
|
const isProduction = process.env.NODE_ENV === 'production';
|
|
const isCriticalError = error.details.some(detail =>
|
|
detail.path.includes('SUPABASE_URL') ||
|
|
detail.path.includes('FB_PROJECT_ID') ||
|
|
detail.path.includes('ANTHROPIC_API_KEY') ||
|
|
detail.path.includes('GCLOUD_PROJECT_ID')
|
|
);
|
|
|
|
if (isProduction && isCriticalError) {
|
|
console.error(`[Config Validation Error] Critical configuration missing in production:`, error.message);
|
|
// In production, we still log but don't crash immediately to allow for runtime injection
|
|
console.error('Application may not function correctly without these variables');
|
|
} else {
|
|
console.warn(`[Config Validation Warning] ${error.message}`);
|
|
}
|
|
}
|
|
|
|
// Runtime configuration validation function
|
|
export const validateRuntimeConfig = (): { isValid: boolean; errors: string[] } => {
|
|
const errors: string[] = [];
|
|
|
|
// Check critical Firebase configuration
|
|
if (!config.firebase.projectId) {
|
|
errors.push('Firebase Project ID is missing');
|
|
}
|
|
|
|
// Check critical Supabase configuration
|
|
if (!config.supabase.url) {
|
|
errors.push('Supabase URL is missing');
|
|
}
|
|
|
|
// Check LLM configuration
|
|
if (config.llm.provider === 'anthropic' && !config.llm.anthropicApiKey) {
|
|
errors.push('Anthropic API key is missing but provider is set to anthropic');
|
|
}
|
|
|
|
if (config.llm.provider === 'openai' && !config.llm.openaiApiKey) {
|
|
errors.push('OpenAI API key is missing but provider is set to openai');
|
|
}
|
|
|
|
// Check Google Cloud configuration
|
|
if (!config.googleCloud.projectId) {
|
|
errors.push('Google Cloud Project ID is missing');
|
|
}
|
|
|
|
return {
|
|
isValid: errors.length === 0,
|
|
errors
|
|
};
|
|
};
|
|
|
|
// Export validated configuration
|
|
export const config = {
|
|
env: envVars.NODE_ENV,
|
|
nodeEnv: envVars.NODE_ENV,
|
|
port: envVars.PORT,
|
|
frontendUrl: process.env['FRONTEND_URL'] || 'http://localhost:3000',
|
|
|
|
// Firebase Configuration
|
|
firebase: {
|
|
projectId: envVars.FB_PROJECT_ID,
|
|
storageBucket: envVars.FB_STORAGE_BUCKET,
|
|
apiKey: envVars.FB_API_KEY,
|
|
authDomain: envVars.FB_AUTH_DOMAIN,
|
|
},
|
|
|
|
supabase: {
|
|
url: envVars.SUPABASE_URL,
|
|
// CRITICAL: Read directly from process.env for Firebase Secrets (defineSecret values)
|
|
anonKey: process.env['SUPABASE_ANON_KEY'] || envVars.SUPABASE_ANON_KEY,
|
|
serviceKey: process.env['SUPABASE_SERVICE_KEY'] || envVars.SUPABASE_SERVICE_KEY,
|
|
},
|
|
|
|
// Google Cloud Configuration
|
|
googleCloud: {
|
|
projectId: envVars.GCLOUD_PROJECT_ID,
|
|
documentAiLocation: envVars.DOCUMENT_AI_LOCATION,
|
|
documentAiProcessorId: envVars.DOCUMENT_AI_PROCESSOR_ID,
|
|
gcsBucketName: envVars.GCS_BUCKET_NAME,
|
|
documentAiOutputBucketName: envVars.DOCUMENT_AI_OUTPUT_BUCKET_NAME,
|
|
applicationCredentials: envVars.GOOGLE_APPLICATION_CREDENTIALS,
|
|
},
|
|
|
|
jwt: {
|
|
secret: envVars.JWT_SECRET,
|
|
expiresIn: envVars.JWT_EXPIRES_IN,
|
|
refreshSecret: envVars.JWT_REFRESH_SECRET,
|
|
refreshExpiresIn: envVars.JWT_REFRESH_EXPIRES_IN,
|
|
},
|
|
|
|
upload: {
|
|
maxFileSize: envVars.MAX_FILE_SIZE,
|
|
allowedFileTypes: (envVars.ALLOWED_FILE_TYPES || 'application/pdf').split(','),
|
|
// Cloud-only: No local upload directory needed
|
|
uploadDir: '/tmp/uploads', // Temporary directory for file processing
|
|
},
|
|
|
|
llm: {
|
|
// CRITICAL: Read LLM_PROVIDER with explicit logging
|
|
provider: (() => {
|
|
const provider = envVars['LLM_PROVIDER'] || process.env['LLM_PROVIDER'] || 'anthropic';
|
|
console.log('[CONFIG DEBUG] LLM Provider resolution:', {
|
|
fromEnvVars: envVars['LLM_PROVIDER'],
|
|
fromProcessEnv: process.env['LLM_PROVIDER'],
|
|
finalProvider: provider
|
|
});
|
|
return provider;
|
|
})(),
|
|
|
|
// Anthropic Configuration (Primary)
|
|
// CRITICAL: Read directly from process.env for Firebase Secrets (defineSecret values)
|
|
// Firebase Secrets are available in process.env but may not be in envVars during module load
|
|
anthropicApiKey: process.env['ANTHROPIC_API_KEY'] || envVars['ANTHROPIC_API_KEY'],
|
|
|
|
// OpenAI Configuration (Fallback)
|
|
openaiApiKey: process.env['OPENAI_API_KEY'] || envVars['OPENAI_API_KEY'],
|
|
|
|
// OpenRouter Configuration (Rate limit workaround)
|
|
openrouterApiKey: process.env['OPENROUTER_API_KEY'] || envVars['OPENROUTER_API_KEY'],
|
|
openrouterUseBYOK: envVars['OPENROUTER_USE_BYOK'] === 'true', // Use BYOK (Bring Your Own Key)
|
|
|
|
// Model Selection - Using latest Claude 4.5 models (Sept 2025)
|
|
// Claude Sonnet 4.5 is recommended for best balance of intelligence, speed, and cost
|
|
// Supports structured outputs for guaranteed JSON schema compliance
|
|
model: envVars['LLM_MODEL'] || 'claude-3-7-sonnet-latest', // Primary model (Claude 3.7 Sonnet latest)
|
|
fastModel: envVars['LLM_FAST_MODEL'] || 'claude-3-5-haiku-latest', // Fast model (Claude 3.5 Haiku latest)
|
|
fallbackModel: envVars['LLM_FALLBACK_MODEL'] || 'gpt-4o', // Fallback for creativity
|
|
|
|
// Task-specific model selection
|
|
financialModel: envVars['LLM_FINANCIAL_MODEL'] || 'claude-sonnet-4-5-20250929', // Best for financial analysis
|
|
creativeModel: envVars['LLM_CREATIVE_MODEL'] || 'gpt-4o', // Best for creative content
|
|
reasoningModel: envVars['LLM_REASONING_MODEL'] || 'claude-opus-4-1-20250805', // Best for complex reasoning (Opus 4.1)
|
|
|
|
// Token Limits - Optimized for CIM documents with hierarchical processing
|
|
maxTokens: parseInt(envVars['LLM_MAX_TOKENS'] || '16000'), // Output tokens (Claude Sonnet 4.5 supports up to 16,384)
|
|
maxInputTokens: parseInt(envVars['LLM_MAX_INPUT_TOKENS'] || '200000'), // Input tokens (increased for larger context)
|
|
chunkSize: parseInt(envVars['LLM_CHUNK_SIZE'] || '15000'), // Chunk size for section analysis (increased from 4000)
|
|
promptBuffer: parseInt(envVars['LLM_PROMPT_BUFFER'] || '1000'), // Buffer for prompt tokens (increased)
|
|
|
|
// Processing Configuration
|
|
temperature: parseFloat(envVars['LLM_TEMPERATURE'] || '0.1'), // Low temperature for consistent output
|
|
timeoutMs: parseInt(envVars['LLM_TIMEOUT_MS'] || '180000'), // 3 minutes timeout (increased for complex analysis)
|
|
|
|
// Cost Optimization
|
|
enableCostOptimization: envVars['LLM_ENABLE_COST_OPTIMIZATION'] === 'true',
|
|
maxCostPerDocument: parseFloat(envVars['LLM_MAX_COST_PER_DOCUMENT'] || '3.00'), // Max $3 per document (increased for better quality)
|
|
useFastModelForSimpleTasks: envVars['LLM_USE_FAST_MODEL_FOR_SIMPLE_TASKS'] === 'true',
|
|
|
|
// Hybrid approach settings
|
|
enableHybridApproach: envVars['LLM_ENABLE_HYBRID_APPROACH'] === 'true',
|
|
useClaudeForFinancial: envVars['LLM_USE_CLAUDE_FOR_FINANCIAL'] === 'true',
|
|
useGPTForCreative: envVars['LLM_USE_GPT_FOR_CREATIVE'] === 'true',
|
|
},
|
|
|
|
security: {
|
|
bcryptRounds: envVars.BCRYPT_ROUNDS,
|
|
rateLimit: {
|
|
windowMs: envVars.RATE_LIMIT_WINDOW_MS,
|
|
maxRequests: envVars.RATE_LIMIT_MAX_REQUESTS,
|
|
},
|
|
},
|
|
|
|
logging: {
|
|
level: envVars.LOG_LEVEL,
|
|
file: envVars.LOG_FILE,
|
|
},
|
|
|
|
// Processing Strategy
|
|
processingStrategy: envVars['PROCESSING_STRATEGY'] || 'agentic_rag', // 'chunking' | 'rag' | 'agentic_rag'
|
|
enableRAGProcessing: envVars['ENABLE_RAG_PROCESSING'] === 'true',
|
|
enableProcessingComparison: envVars['ENABLE_PROCESSING_COMPARISON'] === 'true',
|
|
|
|
// Agentic RAG Configuration
|
|
agenticRag: {
|
|
enabled: envVars.AGENTIC_RAG_ENABLED,
|
|
maxAgents: parseInt(envVars.AGENTIC_RAG_MAX_AGENTS || '6'),
|
|
parallelProcessing: envVars.AGENTIC_RAG_PARALLEL_PROCESSING,
|
|
validationStrict: envVars.AGENTIC_RAG_VALIDATION_STRICT,
|
|
retryAttempts: parseInt(envVars.AGENTIC_RAG_RETRY_ATTEMPTS || '3'),
|
|
timeoutPerAgent: parseInt(envVars.AGENTIC_RAG_TIMEOUT_PER_AGENT || '60000'),
|
|
},
|
|
|
|
// Agent-Specific Configuration
|
|
agentSpecific: {
|
|
documentUnderstandingEnabled: envVars['AGENT_DOCUMENT_UNDERSTANDING_ENABLED'] === 'true',
|
|
financialAnalysisEnabled: envVars['AGENT_FINANCIAL_ANALYSIS_ENABLED'] === 'true',
|
|
marketAnalysisEnabled: envVars['AGENT_MARKET_ANALYSIS_ENABLED'] === 'true',
|
|
investmentThesisEnabled: envVars['AGENT_INVESTMENT_THESIS_ENABLED'] === 'true',
|
|
synthesisEnabled: envVars['AGENT_SYNTHESIS_ENABLED'] === 'true',
|
|
validationEnabled: envVars['AGENT_VALIDATION_ENABLED'] === 'true',
|
|
},
|
|
|
|
// Quality Control
|
|
qualityControl: {
|
|
qualityThreshold: parseFloat(envVars['AGENTIC_RAG_QUALITY_THRESHOLD'] || '0.8'),
|
|
completenessThreshold: parseFloat(envVars['AGENTIC_RAG_COMPLETENESS_THRESHOLD'] || '0.9'),
|
|
consistencyCheck: envVars['AGENTIC_RAG_CONSISTENCY_CHECK'] === 'true',
|
|
},
|
|
|
|
// Monitoring and Logging
|
|
monitoringAndLogging: {
|
|
detailedLogging: envVars['AGENTIC_RAG_DETAILED_LOGGING'] === 'true',
|
|
performanceTracking: envVars['AGENTIC_RAG_PERFORMANCE_TRACKING'] === 'true',
|
|
errorReporting: envVars['AGENTIC_RAG_ERROR_REPORTING'] === 'true',
|
|
},
|
|
|
|
// Vector Database Configuration (Cloud-only)
|
|
vector: {
|
|
provider: envVars['VECTOR_PROVIDER'] || 'supabase', // 'pinecone' | 'supabase'
|
|
|
|
// Pinecone Configuration (if used)
|
|
pineconeApiKey: envVars['PINECONE_API_KEY'],
|
|
pineconeIndex: envVars['PINECONE_INDEX'],
|
|
},
|
|
|
|
// Legacy database configuration (for compatibility - using Supabase)
|
|
database: {
|
|
url: envVars.SUPABASE_URL,
|
|
host: 'db.supabase.co',
|
|
port: 5432,
|
|
name: 'postgres',
|
|
user: 'postgres',
|
|
password: envVars.SUPABASE_SERVICE_KEY,
|
|
},
|
|
};
|
|
|
|
// Configuration health check function
|
|
export const getConfigHealth = () => {
|
|
const runtimeValidation = validateRuntimeConfig();
|
|
|
|
return {
|
|
timestamp: new Date().toISOString(),
|
|
environment: config.nodeEnv,
|
|
configurationValid: runtimeValidation.isValid,
|
|
errors: runtimeValidation.errors,
|
|
services: {
|
|
firebase: {
|
|
configured: !!config.firebase.projectId && !!config.firebase.storageBucket,
|
|
projectId: config.firebase.projectId ? 'configured' : 'missing',
|
|
storageBucket: config.firebase.storageBucket ? 'configured' : 'missing'
|
|
},
|
|
supabase: {
|
|
configured: !!config.supabase.url && !!config.supabase.serviceKey,
|
|
url: config.supabase.url ? 'configured' : 'missing',
|
|
serviceKey: config.supabase.serviceKey ? 'configured' : 'missing'
|
|
},
|
|
googleCloud: {
|
|
configured: !!config.googleCloud.projectId && !!config.googleCloud.documentAiProcessorId,
|
|
projectId: config.googleCloud.projectId ? 'configured' : 'missing',
|
|
documentAiProcessorId: config.googleCloud.documentAiProcessorId ? 'configured' : 'missing'
|
|
},
|
|
llm: {
|
|
configured: config.llm.provider === 'anthropic' ? !!config.llm.anthropicApiKey : !!config.llm.openaiApiKey,
|
|
provider: config.llm.provider,
|
|
apiKey: (config.llm.provider === 'anthropic' ? config.llm.anthropicApiKey : config.llm.openaiApiKey) ? 'configured' : 'missing'
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
export default config; |