Broken shit

This commit is contained in:
Max Kowalski
2025-06-26 21:00:19 +02:00
parent ab2c75b736
commit 653d70022b
49 changed files with 2826 additions and 2102 deletions

View File

@ -0,0 +1,64 @@
// Check current database status
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function checkDatabaseStatus() {
try {
console.log('📊 Checking database status...\n');
// Count total sessions
const totalSessions = await prisma.session.count();
console.log(`📈 Total sessions: ${totalSessions}`);
// Count processed vs unprocessed
const processedSessions = await prisma.session.count({
where: { processed: true }
});
const unprocessedSessions = await prisma.session.count({
where: { processed: false }
});
console.log(`✅ Processed sessions: ${processedSessions}`);
console.log(`⏳ Unprocessed sessions: ${unprocessedSessions}`);
// Count valid vs invalid data
const validSessions = await prisma.session.count({
where: { validData: true }
});
const invalidSessions = await prisma.session.count({
where: { validData: false }
});
console.log(`🎯 Valid data sessions: ${validSessions}`);
console.log(`❌ Invalid data sessions: ${invalidSessions}`);
// Count sessions with messages
const sessionsWithMessages = await prisma.session.count({
where: {
messages: {
some: {}
}
}
});
console.log(`💬 Sessions with messages: ${sessionsWithMessages}`);
// Count companies
const totalCompanies = await prisma.company.count();
console.log(`🏢 Total companies: ${totalCompanies}`);
if (totalSessions === 0) {
console.log('\n💡 No sessions found. Run CSV refresh to import data:');
console.log(' curl -X POST http://localhost:3000/api/admin/refresh-sessions');
}
} catch (error) {
console.error('❌ Error checking database status:', error);
} finally {
await prisma.$disconnect();
}
}
// Run the script
checkDatabaseStatus();

View File

@ -0,0 +1,69 @@
// Check why questions aren't being extracted properly
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function checkQuestionsIssue() {
console.log('🔍 INVESTIGATING QUESTIONS EXTRACTION ISSUE\n');
// Find a session with questions stored
const sessionWithQuestions = await prisma.session.findFirst({
where: {
processed: true,
questions: { not: null }
},
include: { messages: true }
});
if (sessionWithQuestions) {
console.log('📋 SAMPLE SESSION WITH QUESTIONS:');
console.log('Session ID:', sessionWithQuestions.id);
console.log('Questions stored:', sessionWithQuestions.questions);
console.log('Summary:', sessionWithQuestions.summary);
console.log('Messages count:', sessionWithQuestions.messages.length);
console.log('\n💬 FIRST FEW MESSAGES:');
sessionWithQuestions.messages.slice(0, 8).forEach((msg, i) => {
console.log(` ${i+1}. [${msg.role}]: ${msg.content.substring(0, 150)}...`);
});
}
// Check sessions marked as invalid data
const invalidSessions = await prisma.session.count({
where: {
processed: true,
questions: '[]' // Empty questions array
}
});
console.log(`\n⚠️ SESSIONS WITH EMPTY QUESTIONS: ${invalidSessions}`);
// Find a session with empty questions to analyze
const emptyQuestionSession = await prisma.session.findFirst({
where: {
processed: true,
questions: '[]'
},
include: { messages: true }
});
if (emptyQuestionSession) {
console.log('\n❌ SAMPLE SESSION WITH EMPTY QUESTIONS:');
console.log('Session ID:', emptyQuestionSession.id);
console.log('Questions stored:', emptyQuestionSession.questions);
console.log('Summary:', emptyQuestionSession.summary);
console.log('Messages count:', emptyQuestionSession.messages.length);
console.log('\n💬 MESSAGES FROM EMPTY QUESTION SESSION:');
emptyQuestionSession.messages.slice(0, 8).forEach((msg, i) => {
console.log(` ${i+1}. [${msg.role}]: ${msg.content.substring(0, 150)}...`);
});
}
console.log('\n🤖 CURRENT OPENAI MODEL: gpt-4-turbo');
console.log('🎯 PROMPT INSTRUCTION: "Max 5 user questions in English"');
await prisma.$disconnect();
}
checkQuestionsIssue();

View File

@ -1,8 +1,8 @@
// Script to check what's in the transcript files
// Usage: node scripts/check-transcript-content.js
import { PrismaClient } from '@prisma/client';
import fetch from 'node-fetch';
import { PrismaClient } from "@prisma/client";
import fetch from "node-fetch";
const prisma = new PrismaClient();
@ -11,10 +11,7 @@ async function checkTranscriptContent() {
// Get a few sessions without messages
const sessions = await prisma.session.findMany({
where: {
AND: [
{ fullTranscriptUrl: { not: null } },
{ messages: { none: {} } },
]
AND: [{ fullTranscriptUrl: { not: null } }, { messages: { none: {} } }],
},
include: { company: true },
take: 3,
@ -25,9 +22,13 @@ async function checkTranscriptContent() {
console.log(` URL: ${session.fullTranscriptUrl}`);
try {
const authHeader = session.company.csvUsername && session.company.csvPassword
? "Basic " + Buffer.from(`${session.company.csvUsername}:${session.company.csvPassword}`).toString("base64")
: undefined;
const authHeader =
session.company.csvUsername && session.company.csvPassword
? "Basic " +
Buffer.from(
`${session.company.csvUsername}:${session.company.csvPassword}`
).toString("base64")
: undefined;
const response = await fetch(session.fullTranscriptUrl, {
headers: authHeader ? { Authorization: authHeader } : {},
@ -47,24 +48,26 @@ async function checkTranscriptContent() {
} else if (content.length < 100) {
console.log(` 📝 Full content: "${content}"`);
} else {
console.log(` 📝 First 200 chars: "${content.substring(0, 200)}..."`);
console.log(
` 📝 First 200 chars: "${content.substring(0, 200)}..."`
);
}
// Check if it matches our expected format
const lines = content.split('\n').filter(line => line.trim());
const formatMatches = lines.filter(line =>
const lines = content.split("\n").filter((line) => line.trim());
const formatMatches = lines.filter((line) =>
line.match(/^\[([^\]]+)\]\s*([^:]+):\s*(.+)$/)
);
console.log(` 🔍 Lines total: ${lines.length}, Format matches: ${formatMatches.length}`);
console.log(
` 🔍 Lines total: ${lines.length}, Format matches: ${formatMatches.length}`
);
} catch (error) {
console.log(` ❌ Error: ${error.message}`);
}
}
} catch (error) {
console.error('❌ Error:', error);
console.error("❌ Error:", error);
} finally {
await prisma.$disconnect();
}

View File

@ -0,0 +1,34 @@
// Check sessions for transcript URLs
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
async function checkTranscriptUrls() {
const sessions = await prisma.session.findMany({
where: {
messages: { none: {} },
},
select: {
id: true,
fullTranscriptUrl: true,
}
});
const withUrl = sessions.filter(s => s.fullTranscriptUrl);
const withoutUrl = sessions.filter(s => !s.fullTranscriptUrl);
console.log(`\n📊 Transcript URL Status for Sessions without Messages:`);
console.log(`✅ Sessions with transcript URL: ${withUrl.length}`);
console.log(`❌ Sessions without transcript URL: ${withoutUrl.length}`);
if (withUrl.length > 0) {
console.log(`\n🔍 Sample URLs:`);
withUrl.slice(0, 3).forEach(s => {
console.log(` ${s.id}: ${s.fullTranscriptUrl}`);
});
}
await prisma.$disconnect();
}
checkTranscriptUrls();

View File

@ -0,0 +1,144 @@
// Complete processing workflow - Fetches transcripts AND processes everything
import { PrismaClient } from '@prisma/client';
import { processUnprocessedSessions } from '../lib/processingScheduler.ts';
import { exec } from 'child_process';
import { promisify } from 'util';
const execAsync = promisify(exec);
const prisma = new PrismaClient();
async function completeProcessingWorkflow() {
try {
console.log('🚀 COMPLETE PROCESSING WORKFLOW STARTED\n');
// Step 1: Check initial status
console.log('📊 STEP 1: Initial Status Check');
console.log('=' .repeat(50));
await checkStatus();
// Step 2: Fetch missing transcripts
console.log('\n📥 STEP 2: Fetching Missing Transcripts');
console.log('=' .repeat(50));
const sessionsWithoutMessages = await prisma.session.count({
where: {
messages: { none: {} },
fullTranscriptUrl: { not: null }
}
});
if (sessionsWithoutMessages > 0) {
console.log(`🔍 Found ${sessionsWithoutMessages} sessions needing transcript fetch`);
console.log('📥 Fetching transcripts...\n');
try {
const { stdout } = await execAsync('node scripts/fetch-and-parse-transcripts.js');
console.log(stdout);
} catch (error) {
console.error('❌ Error fetching transcripts:', error);
}
} else {
console.log('✅ All sessions with transcript URLs already have messages');
}
// Step 3: Process ALL unprocessed sessions
console.log('\n🤖 STEP 3: AI Processing (Complete Batch Processing)');
console.log('=' .repeat(50));
const unprocessedWithMessages = await prisma.session.count({
where: {
processed: false,
messages: { some: {} }
}
});
if (unprocessedWithMessages > 0) {
console.log(`🔄 Found ${unprocessedWithMessages} unprocessed sessions with messages`);
console.log('🤖 Starting complete batch processing...\n');
const result = await processUnprocessedSessions(10, 3);
console.log('\n🎉 AI Processing Results:');
console.log(` ✅ Successfully processed: ${result.totalProcessed}`);
console.log(` ❌ Failed to process: ${result.totalFailed}`);
console.log(` ⏱️ Total time: ${result.totalTime.toFixed(2)}s`);
} else {
console.log('✅ No unprocessed sessions with messages found');
}
// Step 4: Continue fetching more transcripts if available
console.log('\n🔄 STEP 4: Checking for More Transcripts');
console.log('=' .repeat(50));
const remainingWithoutMessages = await prisma.session.count({
where: {
messages: { none: {} },
fullTranscriptUrl: { not: null }
}
});
if (remainingWithoutMessages > 0) {
console.log(`🔍 Found ${remainingWithoutMessages} more sessions needing transcripts`);
console.log('📥 Fetching additional transcripts...\n');
try {
const { stdout } = await execAsync('node scripts/fetch-and-parse-transcripts.js');
console.log(stdout);
// Process the newly fetched sessions
const newUnprocessed = await prisma.session.count({
where: {
processed: false,
messages: { some: {} }
}
});
if (newUnprocessed > 0) {
console.log(`\n🤖 Processing ${newUnprocessed} newly fetched sessions...\n`);
const result = await processUnprocessedSessions(10, 3);
console.log(`✅ Additional processing: ${result.totalProcessed} processed, ${result.totalFailed} failed`);
}
} catch (error) {
console.error('❌ Error fetching additional transcripts:', error);
}
} else {
console.log('✅ No more sessions need transcript fetching');
}
// Step 5: Final status
console.log('\n📊 STEP 5: Final Status');
console.log('=' .repeat(50));
await checkStatus();
console.log('\n🎯 WORKFLOW COMPLETE!');
console.log('✅ All available sessions have been processed');
console.log('✅ System ready for new data');
} catch (error) {
console.error('❌ Error in complete workflow:', error);
} finally {
await prisma.$disconnect();
}
}
async function checkStatus() {
const totalSessions = await prisma.session.count();
const processedSessions = await prisma.session.count({ where: { processed: true } });
const unprocessedSessions = await prisma.session.count({ where: { processed: false } });
const sessionsWithMessages = await prisma.session.count({
where: { messages: { some: {} } }
});
const sessionsWithoutMessages = await prisma.session.count({
where: { messages: { none: {} } }
});
console.log(`📈 Total sessions: ${totalSessions}`);
console.log(`✅ Processed sessions: ${processedSessions}`);
console.log(`⏳ Unprocessed sessions: ${unprocessedSessions}`);
console.log(`💬 Sessions with messages: ${sessionsWithMessages}`);
console.log(`📄 Sessions without messages: ${sessionsWithoutMessages}`);
}
// Run the complete workflow
completeProcessingWorkflow();

View File

@ -0,0 +1,99 @@
// Complete workflow demonstration - Shows the full automated processing system
import { PrismaClient } from '@prisma/client';
import { processUnprocessedSessions } from '../lib/processingScheduler.ts';
const prisma = new PrismaClient();
async function demonstrateCompleteWorkflow() {
try {
console.log('🚀 COMPLETE AUTOMATED WORKFLOW DEMONSTRATION\n');
// Step 1: Check initial status
console.log('📊 STEP 1: Initial Database Status');
console.log('=' .repeat(50));
await checkDatabaseStatus();
// Step 2: Fetch any missing transcripts
console.log('\n📥 STEP 2: Fetching Missing Transcripts');
console.log('=' .repeat(50));
const sessionsWithoutMessages = await prisma.session.count({
where: {
messages: { none: {} },
fullTranscriptUrl: { not: null }
}
});
if (sessionsWithoutMessages > 0) {
console.log(`Found ${sessionsWithoutMessages} sessions without messages but with transcript URLs`);
console.log('💡 Run: node scripts/fetch-and-parse-transcripts.js');
} else {
console.log('✅ All sessions with transcript URLs already have messages');
}
// Step 3: Process all unprocessed sessions
console.log('\n🤖 STEP 3: Complete AI Processing (All Unprocessed Sessions)');
console.log('=' .repeat(50));
const unprocessedCount = await prisma.session.count({
where: {
processed: false,
messages: { some: {} }
}
});
if (unprocessedCount > 0) {
console.log(`Found ${unprocessedCount} unprocessed sessions with messages`);
console.log('🔄 Starting complete batch processing...\n');
const result = await processUnprocessedSessions(10, 3);
console.log('\n🎉 Processing Results:');
console.log(` ✅ Successfully processed: ${result.totalProcessed}`);
console.log(` ❌ Failed to process: ${result.totalFailed}`);
console.log(` ⏱️ Total time: ${result.totalTime.toFixed(2)}s`);
} else {
console.log('✅ No unprocessed sessions found - all caught up!');
}
// Step 4: Final status
console.log('\n📊 STEP 4: Final Database Status');
console.log('=' .repeat(50));
await checkDatabaseStatus();
// Step 5: System summary
console.log('\n🎯 STEP 5: Automated System Summary');
console.log('=' .repeat(50));
console.log('✅ HOURLY SCHEDULER: Processes new unprocessed sessions automatically');
console.log('✅ DASHBOARD REFRESH: Triggers processing when refresh button is pressed');
console.log('✅ BATCH PROCESSING: Processes ALL unprocessed sessions until completion');
console.log('✅ QUALITY VALIDATION: Filters out low-quality sessions automatically');
console.log('✅ COMPLETE AUTOMATION: No manual intervention needed for ongoing operations');
console.log('\n🚀 SYSTEM READY FOR PRODUCTION!');
} catch (error) {
console.error('❌ Error in workflow demonstration:', error);
} finally {
await prisma.$disconnect();
}
}
async function checkDatabaseStatus() {
const totalSessions = await prisma.session.count();
const processedSessions = await prisma.session.count({ where: { processed: true } });
const unprocessedSessions = await prisma.session.count({ where: { processed: false } });
const sessionsWithMessages = await prisma.session.count({
where: { messages: { some: {} } }
});
const companies = await prisma.company.count();
console.log(`📈 Total sessions: ${totalSessions}`);
console.log(`✅ Processed sessions: ${processedSessions}`);
console.log(`⏳ Unprocessed sessions: ${unprocessedSessions}`);
console.log(`💬 Sessions with messages: ${sessionsWithMessages}`);
console.log(`🏢 Total companies: ${companies}`);
}
// Run the demonstration
demonstrateCompleteWorkflow();

View File

@ -0,0 +1,53 @@
import { PrismaClient } from '@prisma/client';
import bcrypt from 'bcryptjs';
const prisma = new PrismaClient();
async function createAdminUser() {
try {
// Check if user exists
const existingUser = await prisma.user.findUnique({
where: { email: 'admin@example.com' }
});
if (existingUser) {
console.log('✅ User already exists:', existingUser.email);
console.log('Password hash:', existingUser.password);
return;
}
// First, ensure we have a company
let company = await prisma.company.findFirst();
if (!company) {
company = await prisma.company.create({
data: {
name: 'Demo Company',
csvUrl: 'https://example.com/demo.csv',
}
});
console.log('✅ Created demo company:', company.name);
}
// Create user
const hashedPassword = await bcrypt.hash('admin123', 10);
const user = await prisma.user.create({
data: {
email: 'admin@example.com',
password: hashedPassword,
role: 'admin',
companyId: company.id,
}
});
console.log('✅ User created successfully:', user.email);
console.log('Password hash:', user.password);
console.log('Role:', user.role);
console.log('Company:', company.name);
} catch (error) {
console.error('❌ Error creating user:', error);
} finally {
await prisma.$disconnect();
}
}
createAdminUser();

View File

@ -1,8 +1,8 @@
// Script to fetch transcripts and parse them into messages
// Usage: node scripts/fetch-and-parse-transcripts.js
import { PrismaClient } from '@prisma/client';
import fetch from 'node-fetch';
import { PrismaClient } from "@prisma/client";
import fetch from "node-fetch";
const prisma = new PrismaClient();
@ -11,9 +11,10 @@ const prisma = new PrismaClient();
*/
async function fetchTranscriptContent(url, username, password) {
try {
const authHeader = username && password
? "Basic " + Buffer.from(`${username}:${password}`).toString("base64")
: undefined;
const authHeader =
username && password
? "Basic " + Buffer.from(`${username}:${password}`).toString("base64")
: undefined;
const response = await fetch(url, {
headers: authHeader ? { Authorization: authHeader } : {},
@ -21,7 +22,9 @@ async function fetchTranscriptContent(url, username, password) {
});
if (!response.ok) {
console.log(`❌ Failed to fetch ${url}: ${response.status} ${response.statusText}`);
console.log(
`❌ Failed to fetch ${url}: ${response.status} ${response.statusText}`
);
return null;
}
return await response.text();
@ -35,11 +38,11 @@ async function fetchTranscriptContent(url, username, password) {
* Parses transcript content into messages
*/
function parseTranscriptToMessages(transcript, sessionId) {
if (!transcript || transcript.trim() === '') {
if (!transcript || transcript.trim() === "") {
return [];
}
const lines = transcript.split('\n').filter(line => line.trim());
const lines = transcript.split("\n").filter((line) => line.trim());
const messages = [];
let messageOrder = 0;
let currentTimestamp = new Date();
@ -52,7 +55,9 @@ function parseTranscriptToMessages(transcript, sessionId) {
const [, timestamp, role, content] = timestampMatch;
// Parse timestamp (DD-MM-YYYY HH:MM:SS)
const dateMatch = timestamp.match(/^(\d{1,2})-(\d{1,2})-(\d{4}) (\d{1,2}):(\d{1,2}):(\d{1,2})$/);
const dateMatch = timestamp.match(
/^(\d{1,2})-(\d{1,2})-(\d{4}) (\d{1,2}):(\d{1,2}):(\d{1,2})$/
);
let parsedTimestamp = new Date();
if (dateMatch) {
@ -104,7 +109,7 @@ function parseTranscriptToMessages(transcript, sessionId) {
*/
async function fetchAndParseTranscripts() {
try {
console.log('🔍 Finding sessions without messages...\n');
console.log("🔍 Finding sessions without messages...\n");
// Get sessions that have fullTranscriptUrl but no messages
const sessionsWithoutMessages = await prisma.session.findMany({
@ -112,7 +117,7 @@ async function fetchAndParseTranscripts() {
AND: [
{ fullTranscriptUrl: { not: null } },
{ messages: { none: {} } }, // No messages
]
],
},
include: {
company: true,
@ -121,11 +126,15 @@ async function fetchAndParseTranscripts() {
});
if (sessionsWithoutMessages.length === 0) {
console.log('✅ All sessions with transcript URLs already have messages!');
console.log(
"✅ All sessions with transcript URLs already have messages!"
);
return;
}
console.log(`📥 Found ${sessionsWithoutMessages.length} sessions to process\n`);
console.log(
`📥 Found ${sessionsWithoutMessages.length} sessions to process\n`
);
let successCount = 0;
let errorCount = 0;
@ -148,7 +157,10 @@ async function fetchAndParseTranscripts() {
}
// Parse transcript into messages
const messages = parseTranscriptToMessages(transcriptContent, session.id);
const messages = parseTranscriptToMessages(
transcriptContent,
session.id
);
if (messages.length === 0) {
console.log(` ⚠️ No messages found in transcript`);
@ -163,7 +175,6 @@ async function fetchAndParseTranscripts() {
console.log(` ✅ Added ${messages.length} messages`);
successCount++;
} catch (error) {
console.log(` ❌ Error: ${error.message}`);
errorCount++;
@ -173,10 +184,11 @@ async function fetchAndParseTranscripts() {
console.log(`\n📊 Results:`);
console.log(` ✅ Successfully processed: ${successCount} sessions`);
console.log(` ❌ Failed to process: ${errorCount} sessions`);
console.log(`\n💡 Now you can run the processing scheduler to analyze these sessions!`);
console.log(
`\n💡 Now you can run the processing scheduler to analyze these sessions!`
);
} catch (error) {
console.error('❌ Error:', error);
console.error("❌ Error:", error);
} finally {
await prisma.$disconnect();
}

View File

@ -1,37 +1,39 @@
// Simple script to test the manual processing trigger
// Usage: node scripts/manual-trigger-test.js
import fetch from 'node-fetch';
import fetch from "node-fetch";
async function testManualTrigger() {
try {
console.log('Testing manual processing trigger...');
console.log("Testing manual processing trigger...");
const response = await fetch('http://localhost:3000/api/admin/trigger-processing', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
// Note: In a real scenario, you'd need to include authentication cookies
// For testing, you might need to login first and copy the session cookie
},
body: JSON.stringify({
batchSize: 5, // Process max 5 sessions
maxConcurrency: 3 // Use 3 concurrent workers
})
});
const response = await fetch(
"http://localhost:3000/api/admin/trigger-processing",
{
method: "POST",
headers: {
"Content-Type": "application/json",
// Note: In a real scenario, you'd need to include authentication cookies
// For testing, you might need to login first and copy the session cookie
},
body: JSON.stringify({
batchSize: 5, // Process max 5 sessions
maxConcurrency: 3, // Use 3 concurrent workers
}),
}
);
const result = await response.json();
if (response.ok) {
console.log('✅ Manual trigger successful:');
console.log("✅ Manual trigger successful:");
console.log(JSON.stringify(result, null, 2));
} else {
console.log('❌ Manual trigger failed:');
console.log("❌ Manual trigger failed:");
console.log(JSON.stringify(result, null, 2));
}
} catch (error) {
console.error('❌ Error testing manual trigger:', error.message);
console.error("❌ Error testing manual trigger:", error.message);
}
}

View File

@ -10,16 +10,18 @@ import { dirname, join } from "path";
// Load environment variables from .env.local
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const envPath = join(__dirname, '..', '.env.local');
const envPath = join(__dirname, "..", ".env.local");
try {
const envFile = readFileSync(envPath, 'utf8');
const envVars = envFile.split('\n').filter(line => line.trim() && !line.startsWith('#'));
const envFile = readFileSync(envPath, "utf8");
const envVars = envFile
.split("\n")
.filter((line) => line.trim() && !line.startsWith("#"));
envVars.forEach(line => {
const [key, ...valueParts] = line.split('=');
envVars.forEach((line) => {
const [key, ...valueParts] = line.split("=");
if (key && valueParts.length > 0) {
const value = valueParts.join('=').trim();
const value = valueParts.join("=").trim();
if (!process.env[key.trim()]) {
process.env[key.trim()] = value;
}
@ -65,11 +67,8 @@ async function triggerProcessingScheduler() {
AND: [
{ messages: { some: {} } },
{
OR: [
{ processed: false },
{ processed: null }
]
}
OR: [{ processed: false }, { processed: null }],
},
],
},
select: {
@ -129,10 +128,7 @@ async function showProcessingStatus() {
});
const unprocessedSessions = await prisma.session.count({
where: {
OR: [
{ processed: false },
{ processed: null }
]
OR: [{ processed: false }, { processed: null }],
},
});
const withMessages = await prisma.session.count({
@ -147,11 +143,8 @@ async function showProcessingStatus() {
AND: [
{ messages: { some: {} } },
{
OR: [
{ processed: false },
{ processed: null }
]
}
OR: [{ processed: false }, { processed: null }],
},
],
},
});
@ -170,11 +163,8 @@ async function showProcessingStatus() {
AND: [
{ messages: { some: {} } },
{
OR: [
{ processed: false },
{ processed: null }
]
}
OR: [{ processed: false }, { processed: null }],
},
],
},
select: {

View File

@ -1,283 +0,0 @@
// Script to manually process unprocessed sessions with OpenAI
import { PrismaClient } from "@prisma/client";
import fetch from "node-fetch";
const prisma = new PrismaClient();
const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
const OPENAI_API_URL = "https://api.openai.com/v1/chat/completions";
/**
* Processes a session transcript using OpenAI API
* @param {string} sessionId The session ID
* @param {string} transcript The transcript content to process
* @returns {Promise<Object>} Processed data from OpenAI
*/
async function processTranscriptWithOpenAI(sessionId, transcript) {
if (!OPENAI_API_KEY) {
throw new Error("OPENAI_API_KEY environment variable is not set");
}
// Create a system message with instructions
const systemMessage = `
You are an AI assistant tasked with analyzing chat transcripts.
Extract the following information from the transcript:
1. The primary language used by the user (ISO 639-1 code)
2. Number of messages sent by the user
3. Overall sentiment (positive, neutral, or negative)
4. Whether the conversation was escalated
5. Whether HR contact was mentioned or provided
6. The best-fitting category for the conversation from this list:
- Schedule & Hours
- Leave & Vacation
- Sick Leave & Recovery
- Salary & Compensation
- Contract & Hours
- Onboarding
- Offboarding
- Workwear & Staff Pass
- Team & Contacts
- Personal Questions
- Access & Login
- Social questions
- Unrecognized / Other
7. Up to 5 paraphrased questions asked by the user (in English)
8. A brief summary of the conversation (10-300 characters)
Return the data in JSON format matching this schema:
{
"language": "ISO 639-1 code",
"messages_sent": number,
"sentiment": "positive|neutral|negative",
"escalated": boolean,
"forwarded_hr": boolean,
"category": "one of the categories listed above",
"questions": ["question 1", "question 2", ...],
"summary": "brief summary",
"session_id": "${sessionId}"
}
`;
try {
const response = await fetch(OPENAI_API_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${OPENAI_API_KEY}`,
},
body: JSON.stringify({
model: "gpt-4-turbo",
messages: [
{
role: "system",
content: systemMessage,
},
{
role: "user",
content: transcript,
},
],
temperature: 0.3, // Lower temperature for more consistent results
response_format: { type: "json_object" },
}),
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`OpenAI API error: ${response.status} - ${errorText}`);
}
const data = await response.json();
const processedData = JSON.parse(data.choices[0].message.content);
// Validate the response against our expected schema
validateOpenAIResponse(processedData);
return processedData;
} catch (error) {
console.error(`Error processing transcript with OpenAI:`, error);
throw error;
}
}
/**
* Validates the OpenAI response against our expected schema
* @param {Object} data The data to validate
*/
function validateOpenAIResponse(data) {
// Check required fields
const requiredFields = [
"language",
"messages_sent",
"sentiment",
"escalated",
"forwarded_hr",
"category",
"questions",
"summary",
"session_id",
];
for (const field of requiredFields) {
if (!(field in data)) {
throw new Error(`Missing required field: ${field}`);
}
}
// Validate field types
if (typeof data.language !== "string" || !/^[a-z]{2}$/.test(data.language)) {
throw new Error(
"Invalid language format. Expected ISO 639-1 code (e.g., 'en')"
);
}
if (typeof data.messages_sent !== "number" || data.messages_sent < 0) {
throw new Error("Invalid messages_sent. Expected non-negative number");
}
if (!["positive", "neutral", "negative"].includes(data.sentiment)) {
throw new Error(
"Invalid sentiment. Expected 'positive', 'neutral', or 'negative'"
);
}
if (typeof data.escalated !== "boolean") {
throw new Error("Invalid escalated. Expected boolean");
}
if (typeof data.forwarded_hr !== "boolean") {
throw new Error("Invalid forwarded_hr. Expected boolean");
}
const validCategories = [
"Schedule & Hours",
"Leave & Vacation",
"Sick Leave & Recovery",
"Salary & Compensation",
"Contract & Hours",
"Onboarding",
"Offboarding",
"Workwear & Staff Pass",
"Team & Contacts",
"Personal Questions",
"Access & Login",
"Social questions",
"Unrecognized / Other",
];
if (!validCategories.includes(data.category)) {
throw new Error(
`Invalid category. Expected one of: ${validCategories.join(", ")}`
);
}
if (!Array.isArray(data.questions)) {
throw new Error("Invalid questions. Expected array of strings");
}
if (
typeof data.summary !== "string" ||
data.summary.length < 10 ||
data.summary.length > 300
) {
throw new Error(
"Invalid summary. Expected string between 10-300 characters"
);
}
if (typeof data.session_id !== "string") {
throw new Error("Invalid session_id. Expected string");
}
}
/**
* Main function to process unprocessed sessions
*/
async function processUnprocessedSessions() {
console.log("Starting to process unprocessed sessions...");
// Find sessions that have transcript content but haven't been processed
const sessionsToProcess = await prisma.session.findMany({
where: {
AND: [
{ transcriptContent: { not: null } },
{ transcriptContent: { not: "" } },
{ processed: { not: true } }, // Either false or null
],
},
select: {
id: true,
transcriptContent: true,
},
});
if (sessionsToProcess.length === 0) {
console.log("No sessions found requiring processing.");
return;
}
console.log(`Found ${sessionsToProcess.length} sessions to process.`);
let successCount = 0;
let errorCount = 0;
for (const session of sessionsToProcess) {
if (!session.transcriptContent) {
// Should not happen due to query, but good for type safety
console.warn(
`Session ${session.id} has no transcript content, skipping.`
);
continue;
}
console.log(`Processing transcript for session ${session.id}...`);
try {
const processedData = await processTranscriptWithOpenAI(
session.id,
session.transcriptContent
);
// Map sentiment string to float value for compatibility with existing data
const sentimentMap = {
positive: 0.8,
neutral: 0.0,
negative: -0.8,
};
// Update the session with processed data
await prisma.session.update({
where: { id: session.id },
data: {
language: processedData.language,
messagesSent: processedData.messages_sent,
sentiment: sentimentMap[processedData.sentiment] || 0,
sentimentCategory: processedData.sentiment,
escalated: processedData.escalated,
forwardedHr: processedData.forwarded_hr,
category: processedData.category,
questions: JSON.stringify(processedData.questions),
summary: processedData.summary,
processed: true,
},
});
console.log(`Successfully processed session ${session.id}.`);
successCount++;
} catch (error) {
console.error(`Error processing session ${session.id}:`, error);
errorCount++;
}
}
console.log("Session processing complete.");
console.log(`Successfully processed: ${successCount} sessions.`);
console.log(`Failed to process: ${errorCount} sessions.`);
}
// Run the main function
processUnprocessedSessions()
.catch((e) => {
console.error("An error occurred during the script execution:", e);
process.exitCode = 1;
})
.finally(async () => {
await prisma.$disconnect();
});

View File

@ -0,0 +1,48 @@
// Reset all sessions to processed: false for reprocessing with new instructions
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function resetProcessedStatus() {
try {
console.log('🔄 Resetting processed status for all sessions...');
// Get count of currently processed sessions
const processedCount = await prisma.session.count({
where: { processed: true }
});
console.log(`📊 Found ${processedCount} processed sessions to reset`);
if (processedCount === 0) {
console.log('✅ No sessions need to be reset');
return;
}
// Reset all sessions to processed: false
const result = await prisma.session.updateMany({
where: { processed: true },
data: {
processed: false,
// Also reset AI-generated fields so they get fresh analysis
sentimentCategory: null,
category: null,
questions: null,
summary: null,
validData: true // Reset to default
}
});
console.log(`✅ Successfully reset ${result.count} sessions to processed: false`);
console.log('🤖 These sessions will be reprocessed with the new OpenAI instructions');
console.log('🎯 Quality validation will now mark invalid data appropriately');
} catch (error) {
console.error('❌ Error resetting processed status:', error);
} finally {
await prisma.$disconnect();
}
}
// Run the script
resetProcessedStatus();

View File

@ -0,0 +1,83 @@
// Test script to demonstrate the automated processing system
import { PrismaClient } from '@prisma/client';
import { processUnprocessedSessions, startProcessingScheduler } from '../lib/processingScheduler.ts';
const prisma = new PrismaClient();
async function testAutomation() {
console.log('🧪 TESTING AUTOMATED PROCESSING SYSTEM\n');
// Step 1: Show current status
console.log('📊 STEP 1: Current Database Status');
console.log('=' .repeat(50));
await showStatus();
// Step 2: Test the automated function
console.log('\n🤖 STEP 2: Testing Automated Processing Function');
console.log('=' .repeat(50));
console.log('This is the SAME function that runs automatically every hour...\n');
try {
// This is the EXACT same function that runs automatically every hour
const result = await processUnprocessedSessions(5, 2); // Smaller batch for demo
console.log('\n✅ AUTOMATION TEST RESULTS:');
console.log(` 📊 Sessions processed: ${result.totalProcessed}`);
console.log(` ❌ Sessions failed: ${result.totalFailed}`);
console.log(` ⏱️ Processing time: ${result.totalTime.toFixed(2)}s`);
if (result.totalProcessed === 0 && result.totalFailed === 0) {
console.log('\n🎉 PERFECT! No unprocessed sessions found.');
console.log('✅ This means the automation is working - everything is already processed!');
}
} catch (error) {
console.error('❌ Error testing automation:', error);
}
// Step 3: Show what the scheduler does
console.log('\n⏰ STEP 3: Automated Scheduler Information');
console.log('=' .repeat(50));
console.log('🔄 HOURLY AUTOMATION:');
console.log(' • Runs every hour: cron.schedule("0 * * * *")');
console.log(' • Checks: WHERE processed = false AND messages: { some: {} }');
console.log(' • Processes: ALL unprocessed sessions through OpenAI');
console.log(' • Continues: Until NO unprocessed sessions remain');
console.log(' • Quality: Validates and filters low-quality sessions');
console.log('\n🚀 DASHBOARD INTEGRATION:');
console.log(' • Refresh button triggers: triggerCompleteWorkflow()');
console.log(' • Fetches transcripts: For sessions without messages');
console.log(' • Processes everything: Until all sessions are analyzed');
console.log('\n🎯 PRODUCTION STATUS:');
console.log(' ✅ System is FULLY AUTOMATED');
console.log(' ✅ No manual intervention needed');
console.log(' ✅ Processes new data automatically');
console.log(' ✅ Quality validation included');
await prisma.$disconnect();
}
async function showStatus() {
const totalSessions = await prisma.session.count();
const processedSessions = await prisma.session.count({ where: { processed: true } });
const unprocessedSessions = await prisma.session.count({ where: { processed: false } });
const sessionsWithMessages = await prisma.session.count({
where: { messages: { some: {} } }
});
console.log(`📈 Total sessions: ${totalSessions}`);
console.log(`✅ Processed sessions: ${processedSessions}`);
console.log(`⏳ Unprocessed sessions: ${unprocessedSessions}`);
console.log(`💬 Sessions with messages: ${sessionsWithMessages}`);
if (processedSessions === sessionsWithMessages && unprocessedSessions === 0) {
console.log('\n🎉 AUTOMATION WORKING PERFECTLY!');
console.log('✅ All sessions with messages have been processed');
console.log('✅ No unprocessed sessions remaining');
}
}
// Run the test
testAutomation();

View File

@ -0,0 +1,47 @@
// Test the improved prompt on a few sessions
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function testImprovedPrompt() {
console.log('🧪 TESTING IMPROVED QUESTION EXTRACTION PROMPT\n');
// Reset a few sessions to test the new prompt
console.log('📝 Resetting 5 sessions to test improved prompt...');
const sessionsToReprocess = await prisma.session.findMany({
where: {
processed: true,
questions: '[]' // Sessions with empty questions
},
take: 5
});
if (sessionsToReprocess.length > 0) {
// Reset these sessions to unprocessed
await prisma.session.updateMany({
where: {
id: { in: sessionsToReprocess.map(s => s.id) }
},
data: {
processed: false,
questions: null,
summary: null
}
});
console.log(`✅ Reset ${sessionsToReprocess.length} sessions for reprocessing`);
console.log('Session IDs:', sessionsToReprocess.map(s => s.id));
console.log('\n🚀 Now run this command to test the improved prompt:');
console.log('npx tsx scripts/trigger-processing-direct.js');
console.log('\nThen check the results with:');
console.log('npx tsx scripts/check-questions-issue.js');
} else {
console.log('❌ No sessions with empty questions found to reprocess');
}
await prisma.$disconnect();
}
testImprovedPrompt();

View File

@ -1,36 +1,39 @@
// Script to check processing status and trigger processing
// Usage: node scripts/test-processing-status.js
import { PrismaClient } from '@prisma/client';
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
async function checkProcessingStatus() {
try {
console.log('🔍 Checking processing status...\n');
console.log("🔍 Checking processing status...\n");
// Get processing status
const totalSessions = await prisma.session.count();
const processedSessions = await prisma.session.count({
where: { processed: true }
where: { processed: true },
});
const unprocessedSessions = await prisma.session.count({
where: { processed: false }
where: { processed: false },
});
const sessionsWithMessages = await prisma.session.count({
where: {
processed: false,
messages: { some: {} }
}
messages: { some: {} },
},
});
console.log('📊 Processing Status:');
console.log("📊 Processing Status:");
console.log(` Total sessions: ${totalSessions}`);
console.log(` ✅ Processed: ${processedSessions}`);
console.log(` ⏳ Unprocessed: ${unprocessedSessions}`);
console.log(` 📝 Unprocessed with messages: ${sessionsWithMessages}`);
const processedPercentage = ((processedSessions / totalSessions) * 100).toFixed(1);
const processedPercentage = (
(processedSessions / totalSessions) *
100
).toFixed(1);
console.log(` 📈 Processing progress: ${processedPercentage}%\n`);
// Check recent processing activity
@ -38,35 +41,40 @@ async function checkProcessingStatus() {
where: {
processed: true,
createdAt: {
gte: new Date(Date.now() - 60 * 60 * 1000) // Last hour
}
gte: new Date(Date.now() - 60 * 60 * 1000), // Last hour
},
},
orderBy: { createdAt: 'desc' },
orderBy: { createdAt: "desc" },
take: 5,
select: {
id: true,
createdAt: true,
category: true,
sentiment: true
}
sentiment: true,
},
});
if (recentlyProcessed.length > 0) {
console.log('🕒 Recently processed sessions:');
recentlyProcessed.forEach(session => {
const timeAgo = Math.round((Date.now() - session.createdAt.getTime()) / 1000 / 60);
console.log(`${session.id.substring(0, 8)}... (${timeAgo}m ago) - ${session.category || 'No category'}`);
console.log("🕒 Recently processed sessions:");
recentlyProcessed.forEach((session) => {
const timeAgo = Math.round(
(Date.now() - session.createdAt.getTime()) / 1000 / 60
);
console.log(
`${session.id.substring(0, 8)}... (${timeAgo}m ago) - ${session.category || "No category"}`
);
});
} else {
console.log('🕒 No sessions processed in the last hour');
console.log("🕒 No sessions processed in the last hour");
}
console.log('\n✨ Processing system is working correctly!');
console.log('💡 The parallel processing successfully processed sessions.');
console.log('🎯 For manual triggers, you need to be logged in as an admin user.');
console.log("\n✨ Processing system is working correctly!");
console.log("💡 The parallel processing successfully processed sessions.");
console.log(
"🎯 For manual triggers, you need to be logged in as an admin user."
);
} catch (error) {
console.error('❌ Error checking status:', error);
console.error("❌ Error checking status:", error);
} finally {
await prisma.$disconnect();
}

View File

@ -0,0 +1,57 @@
// Trigger CSV refresh for all companies
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function triggerCsvRefresh() {
try {
console.log('🔄 Triggering CSV refresh for all companies...\n');
// Get all companies
const companies = await prisma.company.findMany();
if (companies.length === 0) {
console.log('❌ No companies found. Run seed script first.');
return;
}
console.log(`🏢 Found ${companies.length} companies:`);
for (const company of companies) {
console.log(`📊 Company: ${company.name} (ID: ${company.id})`);
console.log(`📥 CSV URL: ${company.csvUrl}`);
try {
const response = await fetch('http://localhost:3000/api/admin/refresh-sessions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
companyId: company.id
})
});
const result = await response.json();
if (response.ok) {
console.log(`✅ Successfully imported ${result.imported} sessions for ${company.name}`);
} else {
console.log(`❌ Error for ${company.name}: ${result.error}`);
}
} catch (error) {
console.log(`❌ Failed to refresh ${company.name}: ${error.message}`);
}
console.log(''); // Empty line for readability
}
} catch (error) {
console.error('❌ Error triggering CSV refresh:', error);
} finally {
await prisma.$disconnect();
}
}
// Run the script
triggerCsvRefresh();

View File

@ -1,20 +1,21 @@
// Direct trigger for processing scheduler (bypasses authentication)
// Usage: node scripts/trigger-processing-direct.js
import { processUnprocessedSessions } from '../lib/processingScheduler.js';
// Direct processing trigger without authentication
import { processUnprocessedSessions } from '../lib/processingScheduler.ts';
async function triggerProcessing() {
try {
console.log('🚀 Manually triggering processing scheduler...\n');
console.log('🤖 Starting complete batch processing of all unprocessed sessions...\n');
// Process with custom parameters
await processUnprocessedSessions(50, 3); // Process 50 sessions with 3 concurrent workers
// Process all unprocessed sessions in batches until completion
const result = await processUnprocessedSessions(10, 3);
console.log('\n✅ Processing trigger completed!');
console.log('\n🎉 Complete processing finished!');
console.log(`📊 Final results: ${result.totalProcessed} processed, ${result.totalFailed} failed`);
console.log(`⏱️ Total time: ${result.totalTime.toFixed(2)}s`);
} catch (error) {
console.error('❌ Error triggering processing:', error);
console.error('❌ Error during processing:', error);
}
}
// Run the script
triggerProcessing();