#!/usr/bin/env ts-node /** * Monitor Document Processing Script * * Usage: * npx ts-node src/scripts/monitor-document-processing.ts * * This script provides real-time monitoring of document processing steps * and detailed audit information. */ import { getSupabaseServiceClient } from '../config/supabase'; import { logger } from '../utils/logger'; interface ProcessingStep { step: string; status: 'completed' | 'in_progress' | 'failed' | 'pending'; details: any; timestamp?: string; } async function monitorDocument(documentId: string, intervalSeconds: number = 5) { const supabase = getSupabaseServiceClient(); console.log(`\nπŸ” Monitoring Document: ${documentId}`); console.log(`πŸ“Š Refresh interval: ${intervalSeconds} seconds\n`); console.log('Press Ctrl+C to stop monitoring\n'); console.log('='.repeat(80)); let previousStatus: string | null = null; let checkCount = 0; const monitorInterval = setInterval(async () => { checkCount++; const timestamp = new Date().toISOString(); try { // Get document status const { data: document, error: docError } = await supabase .from('documents') .select('*') .eq('id', documentId) .single(); if (docError || !document) { console.log(`\n❌ [${timestamp}] Document not found`); clearInterval(monitorInterval); return; } // Get latest job const { data: jobs } = await supabase .from('processing_jobs') .select('*') .eq('document_id', documentId) .order('created_at', { ascending: false }) .limit(1); const latestJob = jobs?.[0]; // Get chunks const { count: chunkCount } = await supabase .from('document_chunks') .select('*', { count: 'exact', head: true }) .eq('document_id', documentId); const { count: embeddingCount } = await supabase .from('document_chunks') .select('*', { count: 'exact', head: true }) .eq('document_id', documentId) .not('embedding', 'is', null); // Get review const { data: review } = await supabase .from('cim_reviews') .select('id') .eq('document_id', documentId) .single(); // Status change detection const statusChanged = previousStatus !== document.status; if (statusChanged || checkCount === 1) { console.log(`\nπŸ“‹ [${new Date().toLocaleTimeString()}] Status Update #${checkCount}`); console.log('─'.repeat(80)); } // Display current status const statusIcon = document.status === 'completed' ? 'βœ…' : document.status === 'failed' ? '❌' : document.status === 'processing_llm' ? 'πŸ€–' : '⏳'; console.log(`${statusIcon} Document Status: ${document.status}`); if (latestJob) { const jobIcon = latestJob.status === 'completed' ? 'βœ…' : latestJob.status === 'failed' ? '❌' : latestJob.status === 'processing' ? 'πŸ”„' : '⏸️'; console.log(`${jobIcon} Job Status: ${latestJob.status} (Attempt ${latestJob.attempts}/${latestJob.max_attempts})`); if (latestJob.started_at) { const elapsed = Math.round((Date.now() - new Date(latestJob.started_at).getTime()) / 1000); console.log(` ⏱️ Processing Time: ${elapsed}s (${Math.round(elapsed/60)}m)`); } if (latestJob.error) { console.log(` ⚠️ Error: ${latestJob.error.substring(0, 100)}${latestJob.error.length > 100 ? '...' : ''}`); } } // Processing steps console.log('\nπŸ“Š Processing Steps:'); const steps: ProcessingStep[] = [ { step: '1. Document Upload', status: document.upload_status === 'completed' ? 'completed' : 'pending', details: {}, timestamp: document.created_at, }, { step: '2. Text Extraction', status: document.processing_status ? 'completed' : 'pending', details: {}, }, { step: '3. Document Chunking', status: (chunkCount || 0) > 0 ? 'completed' : 'pending', details: { chunks: chunkCount || 0 }, }, { step: '4. Vector Embeddings', status: (embeddingCount || 0) === (chunkCount || 0) && (chunkCount || 0) > 0 ? 'completed' : (embeddingCount || 0) > 0 ? 'in_progress' : 'pending', details: { embeddings: embeddingCount || 0, chunks: chunkCount || 0, progress: chunkCount ? `${Math.round(((embeddingCount || 0) / chunkCount) * 100)}%` : '0%', }, }, { step: '5. LLM Analysis', status: latestJob ? latestJob.status === 'completed' ? 'completed' : latestJob.status === 'failed' ? 'failed' : 'in_progress' : 'pending', details: { strategy: latestJob?.options?.strategy || 'unknown', }, }, { step: '6. CIM Review', status: review ? 'completed' : document.analysis_data ? 'completed' : 'pending', details: {}, }, ]; steps.forEach((step, index) => { const icon = step.status === 'completed' ? 'βœ…' : step.status === 'failed' ? '❌' : step.status === 'in_progress' ? 'πŸ”„' : '⏸️'; const detailsStr = Object.keys(step.details).length > 0 ? ` (${Object.entries(step.details).map(([k, v]) => `${k}: ${v}`).join(', ')})` : ''; console.log(` ${icon} ${step.step}${detailsStr}`); }); // Completion check if (document.status === 'completed' || document.status === 'failed') { console.log('\n' + '='.repeat(80)); console.log(`\n${document.status === 'completed' ? 'βœ…' : '❌'} Processing ${document.status}!`); if (document.status === 'completed') { console.log(`πŸ“„ Review ID: ${review?.id || 'N/A'}`); console.log(`πŸ“ Has Summary: ${document.generated_summary ? 'Yes' : 'No'}`); } clearInterval(monitorInterval); process.exit(0); } previousStatus = document.status; console.log('\n' + '─'.repeat(80)); } catch (error) { console.error(`\n❌ Error monitoring document:`, error); clearInterval(monitorInterval); process.exit(1); } }, intervalSeconds * 1000); // Initial check const initialCheck = async () => { try { const { data: document } = await supabase .from('documents') .select('status, file_path') .eq('id', documentId) .single(); if (document) { console.log(`πŸ“„ File: ${document.file_path?.split('/').pop() || 'Unknown'}`); console.log(`πŸ“Š Initial Status: ${document.status}\n`); } } catch (error) { console.error('Error in initial check:', error); } }; await initialCheck(); } // Main execution const documentId = process.argv[2]; const interval = parseInt(process.argv[3]) || 5; if (!documentId) { console.error('Usage: npx ts-node src/scripts/monitor-document-processing.ts [intervalSeconds]'); console.error('\nExample:'); console.error(' npx ts-node src/scripts/monitor-document-processing.ts 5b5a1ab6-ba51-4a... 5'); process.exit(1); } monitorDocument(documentId, interval).catch((error) => { console.error('Fatal error:', error); process.exit(1); });