Spaces:
Running
Running
Update services/difyService.ts
Browse files- services/difyService.ts +220 -114
services/difyService.ts
CHANGED
@@ -362,6 +362,193 @@ const extractOverallPass = (text: string): boolean => {
|
|
362 |
return false;
|
363 |
};
|
364 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
365 |
/**
|
366 |
* Parses the new, structured QA report format.
|
367 |
* @param qaText The raw `qa_gaurd` string from the API.
|
@@ -375,7 +562,8 @@ const parseNewQaReport = (qaText: string): { detailedQaReport: DetailedQaReport,
|
|
375 |
meta: { ...defaultSection },
|
376 |
h1: { ...defaultSection },
|
377 |
copy: { ...defaultSection },
|
378 |
-
overall: { grade: 'N/A', pass: false, primaryIssue: 'Parsing failed' }
|
|
|
379 |
};
|
380 |
|
381 |
const cleanedQaText = cleanResponseText(qaText);
|
@@ -385,7 +573,7 @@ const parseNewQaReport = (qaText: string): { detailedQaReport: DetailedQaReport,
|
|
385 |
|
386 |
// Check if it's the new markdown format (starts with ##) or contains **TITLE** style sections
|
387 |
if (cleanedQaText.startsWith('##') || cleanedQaText.includes('**TITLE**') || cleanedQaText.includes('### **') || cleanedQaText.includes('### TITLE')) {
|
388 |
-
console.log('Using markdown parser for QA report');
|
389 |
console.log('QA text starts with:', cleanedQaText.substring(0, 200));
|
390 |
|
391 |
// Handle different section header formats
|
@@ -413,6 +601,7 @@ const parseNewQaReport = (qaText: string): { detailedQaReport: DetailedQaReport,
|
|
413 |
|
414 |
const parsedData: Partial<DetailedQaReport> = {};
|
415 |
let correctedCopyFromSeparateSection = '';
|
|
|
416 |
|
417 |
// Special handling for single-section format like "## GRADE REPORT"
|
418 |
if (sections.length === 1 && (sections[0].includes('GRADE REPORT') || sections[0].includes('QUALITY ASSURANCE'))) {
|
@@ -429,125 +618,37 @@ const parseNewQaReport = (qaText: string): { detailedQaReport: DetailedQaReport,
|
|
429 |
header = header.replace(/^#+\s*/, '').replace(/\*\*/g, '').replace(/[:\-–]+$/, '').trim();
|
430 |
console.log('Processing header:', header);
|
431 |
|
|
|
432 |
if (header.includes('title')) {
|
433 |
-
console.log('Parsing title section');
|
434 |
-
parsedData.title =
|
435 |
} else if (header.includes('meta')) {
|
436 |
-
console.log('Parsing meta section');
|
437 |
-
parsedData.meta =
|
438 |
} else if (header.includes('h1')) {
|
439 |
-
console.log('Parsing h1 section');
|
440 |
-
parsedData.h1 =
|
441 |
} else if (header.includes('copy') && !header.includes('corrected')) {
|
442 |
-
console.log('Parsing copy section');
|
443 |
-
parsedData.copy =
|
444 |
} else if (header.includes('corrected') && header.includes('copy')) {
|
445 |
console.log('Capturing separate CORRECTED COPY section');
|
446 |
correctedCopyFromSeparateSection = lines.slice(1).join('\n').trim();
|
447 |
} else if (header.includes('overall') || header.includes('assessment') || header.includes('pipeline')) {
|
448 |
-
console.log('Parsing overall section');
|
449 |
console.log('Overall section text:', sectionBlock.substring(0, 300));
|
450 |
|
451 |
-
//
|
452 |
-
|
453 |
-
|
454 |
-
|
455 |
-
|
456 |
-
|
457 |
-
|
458 |
-
|
459 |
-
|
460 |
-
|
461 |
-
|
462 |
-
/(?:•|-|\*)\s*\*\*(?:Final|Total|Overall)?\s*Grade\*\*:?:\s*(.*)/i, // Generic grade
|
463 |
-
/(?:•|-|\*)\s*(?:Final|Total|Overall)?\s*Grade:?:\s*(.*)/i, // No bold
|
464 |
-
/(?:Final|Total|Overall)\s*Grade:?:\s*(\d+\/\d+|\d+)/im, // Anywhere in text
|
465 |
-
/(\d+\/\d+)\s*(?:final|total|overall)?/im // Number first
|
466 |
-
];
|
467 |
-
|
468 |
-
for (const pattern of overallGradePatterns) {
|
469 |
-
gradeMatch = sectionBlock.match(pattern);
|
470 |
-
if (gradeMatch) {
|
471 |
-
grade = gradeMatch[1].trim();
|
472 |
-
break;
|
473 |
-
}
|
474 |
-
}
|
475 |
-
console.log('Overall grade match:', gradeMatch, 'Final grade:', grade);
|
476 |
-
|
477 |
-
// ROBUST OVERALL PASS EXTRACTION - handles multiple formats
|
478 |
-
let pass = false;
|
479 |
-
let passMatch = null;
|
480 |
-
|
481 |
-
const overallPassPatterns = [
|
482 |
-
// Exact format variations found in logs
|
483 |
-
/-\s*\*\*Overall Pass:\*\*\s*(.*)/i, // - **Overall Pass:** true
|
484 |
-
/•\s*\*\*Overall Pass:\*\*\s*(.*)/i, // • **Overall Pass:** true
|
485 |
-
/\*\s*\*\*Overall Pass:\*\*\s*(.*)/i, // * **Overall Pass:** true
|
486 |
-
/•\s*\*\*All Sections Pass:\*\*\s*(.*)/i, // • **All Sections Pass:** true
|
487 |
-
/-\s*\*\*All Sections Pass:\*\*\s*(.*)/i, // - **All Sections Pass:** true
|
488 |
-
/\*\s*\*\*All Sections Pass:\*\*\s*(.*)/i, // * **All Sections Pass:** true
|
489 |
-
/•\s*\*\*Final Pass:\*\*\s*(.*)/i, // • **Final Pass:** true
|
490 |
-
/-\s*\*\*Final Pass:\*\*\s*(.*)/i, // - **Final Pass:** true
|
491 |
-
/\*\s*\*\*Final Pass:\*\*\s*(.*)/i, // * **Final Pass:** true
|
492 |
-
|
493 |
-
// Generic patterns with flexible formatting
|
494 |
-
/(?:•|-|\*)\s*\*\*(?:Overall\s+|All\s+Sections\s+|Final\s+)?Pass\*\*:?:\s*(.*)/i,
|
495 |
-
/(?:•|-|\*)\s*(?:Overall\s+|All\s+Sections\s+|Final\s+)?Pass:?:\s*(.*)/i,
|
496 |
-
|
497 |
-
// Anywhere in text patterns
|
498 |
-
/Pass:?:\s*(true|false|✅|❌|TRUE|FALSE)/im,
|
499 |
-
/Overall\s*Pass:?:\s*(true|false|✅|❌|TRUE|FALSE)/im,
|
500 |
-
/All\s*Sections\s*Pass:?:\s*(true|false|✅|❌|TRUE|FALSE)/im,
|
501 |
-
/(true|false|✅|❌|TRUE|FALSE)\s*(?:overall|pass)/im,
|
502 |
-
|
503 |
-
// Handle capitalized boolean values
|
504 |
-
/Pass:?:\s*(True|False|TRUE|FALSE)/im,
|
505 |
-
/Overall\s*Pass:?:\s*(True|False|TRUE|FALSE)/im
|
506 |
-
];
|
507 |
-
|
508 |
-
for (const pattern of overallPassPatterns) {
|
509 |
-
passMatch = sectionBlock.match(pattern);
|
510 |
-
if (passMatch) {
|
511 |
-
const passValue = passMatch[1].toLowerCase().trim();
|
512 |
-
pass = passValue.includes('true') ||
|
513 |
-
passValue.includes('✅') ||
|
514 |
-
passValue === 'yes' ||
|
515 |
-
passValue === 'passed' ||
|
516 |
-
passValue === 'pass';
|
517 |
-
break;
|
518 |
-
}
|
519 |
-
}
|
520 |
-
console.log('Overall pass match:', passMatch, 'Final pass:', pass);
|
521 |
-
|
522 |
-
// Look for various primary issue formats
|
523 |
-
const explanationMatch = sectionBlock.match(/\*\*Overall Pass\*\*:\s*[^()]*\(([^)]+)\)/);
|
524 |
-
const statusMatch = sectionBlock.match(/(?:•|-|\*)\s*\*\*(?:Pipeline\s+)?Status\*\*?:?\s*([\s\S]*?)(?=\n(?:•|-|\*)\s*\*\*|$)/);
|
525 |
-
const primaryIssueMatch = sectionBlock.match(/(?:•|-|\*)\s*\*\*(?:Primary\s+)?Issue\*\*?:?\s*([\s\S]*?)(?=\n(?:•|-|\*)\s*\*\*|$)/) ||
|
526 |
-
sectionBlock.match(/(?:•|-|\*)\s*(?:Primary\s+)?Issue:?:\s*([\s\S]*?)(?=\n(?:•|-|\*)\s*\*\*|$)/);
|
527 |
-
const errorsMatch = sectionBlock.match(/(?:•|-|\*)\s*\*\*Total\s+Errors?\*\*?:?\s*([\s\S]*?)(?=\n(?:•|-|\*)\s*\*\*|$)/);
|
528 |
-
const totalSectionsMatch = sectionBlock.match(/Total\s*Sections\s*Passing:?:\s*([^\n]+)/i);
|
529 |
-
|
530 |
-
let primaryIssue = 'All sections passed successfully.';
|
531 |
-
if (explanationMatch) {
|
532 |
-
primaryIssue = explanationMatch[1].trim();
|
533 |
-
} else if (statusMatch) {
|
534 |
-
primaryIssue = statusMatch[1].trim();
|
535 |
-
} else if (primaryIssueMatch) {
|
536 |
-
primaryIssue = primaryIssueMatch[1].trim();
|
537 |
-
} else if (errorsMatch) {
|
538 |
-
const errorText = errorsMatch[1].trim();
|
539 |
-
if (errorText !== '[]' && errorText !== '') {
|
540 |
-
primaryIssue = `Errors found: ${errorText}`;
|
541 |
-
}
|
542 |
-
}
|
543 |
-
if (totalSectionsMatch) {
|
544 |
-
primaryIssue = `${primaryIssue} | Total Sections Passing: ${totalSectionsMatch[1].trim()}`;
|
545 |
-
}
|
546 |
-
|
547 |
-
console.log('Primary issue extraction - explanation:', explanationMatch, 'status:', statusMatch, 'issue:', primaryIssueMatch, 'Final issue:', primaryIssue);
|
548 |
-
|
549 |
-
console.log('Setting overall data - grade:', grade, 'pass:', pass, 'primaryIssue:', primaryIssue);
|
550 |
-
parsedData.overall = { grade, pass, primaryIssue };
|
551 |
}
|
552 |
});
|
553 |
|
@@ -556,7 +657,9 @@ const parseNewQaReport = (qaText: string): { detailedQaReport: DetailedQaReport,
|
|
556 |
meta: parsedData.meta || { ...defaultSection, errors: ['Meta section not found'] },
|
557 |
h1: parsedData.h1 || { ...defaultSection, errors: ['H1 section not found'] },
|
558 |
copy: parsedData.copy || { ...defaultSection, errors: ['Copy section not found'] },
|
559 |
-
overall: parsedData.overall || { grade: 'N/A', pass: false, primaryIssue: 'Overall section not found' }
|
|
|
|
|
560 |
};
|
561 |
|
562 |
// If we saw a separate CORRECTED COPY section, populate copy.corrected with it when useful
|
@@ -592,7 +695,8 @@ const parseNewQaReport = (qaText: string): { detailedQaReport: DetailedQaReport,
|
|
592 |
parsedData.overall = {
|
593 |
grade,
|
594 |
pass,
|
595 |
-
primaryIssue: pass ? 'All sections passed successfully.' : 'Overall assessment failed.'
|
|
|
596 |
};
|
597 |
|
598 |
console.log('Found inline overall results - grade:', grade, 'pass:', pass);
|
@@ -621,7 +725,8 @@ const parseNewQaReport = (qaText: string): { detailedQaReport: DetailedQaReport,
|
|
621 |
parsedData.overall = {
|
622 |
grade: averageGrade,
|
623 |
pass: allSectionsPassed,
|
624 |
-
primaryIssue: allSectionsPassed ? 'All sections passed successfully.' : 'One or more sections failed.'
|
|
|
625 |
};
|
626 |
|
627 |
console.log('Calculated overall from individual sections - pass:', allSectionsPassed, 'grade:', averageGrade);
|
@@ -633,6 +738,7 @@ const parseNewQaReport = (qaText: string): { detailedQaReport: DetailedQaReport,
|
|
633 |
|
634 |
console.log('Final parsed QA data:', finalReport.overall);
|
635 |
console.log('Setting overallPass:', finalReport.overall.pass, 'overallGrade:', finalReport.overall.grade);
|
|
|
636 |
|
637 |
return {
|
638 |
detailedQaReport: finalReport,
|
|
|
362 |
return false;
|
363 |
};
|
364 |
|
365 |
+
/**
|
366 |
+
* Enhanced section parsing that captures ALL QA Guard content
|
367 |
+
*/
|
368 |
+
const parseEnhancedSection = (sectionBlock: string): QaSectionResult => {
|
369 |
+
const baseSection = parseSection(sectionBlock);
|
370 |
+
|
371 |
+
// Extract detailed assessment content
|
372 |
+
const detailedAssessmentMatch = sectionBlock.match(/(?:•|-|\*)\s*\*\*(?:Detailed\s+)?Assessment\*\*?:?\s*([\s\S]*?)(?=\n(?:•|-|\*)\s*\*\*|$)/i);
|
373 |
+
const explanationsMatch = sectionBlock.match(/(?:•|-|\*)\s*\*\*(?:Explanation|Reasoning)\*\*?:?\s*([\s\S]*?)(?=\n(?:•|-|\*)\s*\*\*|$)/i);
|
374 |
+
|
375 |
+
// Extract key strengths
|
376 |
+
const keyStrengthsMatch = sectionBlock.match(/(?:•|-|\*)\s*\*\*Key\s+Strengths\*\*?:?\s*([\s\S]*?)(?=\n(?:•|-|\*)\s*\*\*|$)/i);
|
377 |
+
const strengthsList = keyStrengthsMatch ?
|
378 |
+
keyStrengthsMatch[1].split('\n')
|
379 |
+
.map(line => line.replace(/^[-•*]\s*/, '').trim())
|
380 |
+
.filter(line => line.length > 0) : undefined;
|
381 |
+
|
382 |
+
// Extract recommendations
|
383 |
+
const recommendationsMatch = sectionBlock.match(/(?:•|-|\*)\s*\*\*(?:Recommendations?|Suggestions?)\*\*?:?\s*([\s\S]*?)(?=\n(?:•|-|\*)\s*\*\*|$)/i);
|
384 |
+
const recommendationsList = recommendationsMatch ?
|
385 |
+
recommendationsMatch[1].split('\n')
|
386 |
+
.map(line => line.replace(/^[-•*]\s*/, '').trim())
|
387 |
+
.filter(line => line.length > 0) : undefined;
|
388 |
+
|
389 |
+
return {
|
390 |
+
...baseSection,
|
391 |
+
detailedAssessment: detailedAssessmentMatch ? detailedAssessmentMatch[1].trim() : undefined,
|
392 |
+
explanations: explanationsMatch ? explanationsMatch[1].trim() : undefined,
|
393 |
+
keyStrengths: strengthsList,
|
394 |
+
recommendations: recommendationsList,
|
395 |
+
rawContent: sectionBlock
|
396 |
+
};
|
397 |
+
};
|
398 |
+
|
399 |
+
/**
|
400 |
+
* Enhanced overall section parsing that captures ALL QA Guard content
|
401 |
+
*/
|
402 |
+
const parseEnhancedOverallSection = (sectionBlock: string): { grade: string; pass: boolean; primaryIssue: string; detailedAssessment?: string; keyStrengths?: string[]; recommendations?: string[]; explanations?: string; rawContent?: string } => {
|
403 |
+
// ROBUST OVERALL GRADE EXTRACTION - handles multiple formats
|
404 |
+
let grade = 'N/A';
|
405 |
+
let gradeMatch = null;
|
406 |
+
|
407 |
+
const overallGradePatterns = [
|
408 |
+
/-\s*\*\*Final Grade:\*\*\s*(.*)/, // - **Final Grade:** 100/100
|
409 |
+
/•\s*\*\*Final Grade:\*\*\s*(.*)/, // • **Final Grade:** 100/100
|
410 |
+
/\*\s*\*\*Final Grade:\*\*\s*(.*)/, // * **Final Grade:** 100/100
|
411 |
+
/-\s*\*\*Total Grade:\*\*\s*(.*)/, // - **Total Grade:** 100/100
|
412 |
+
/•\s*\*\*Total Grade:\*\*\s*(.*)/, // • **Total Grade:** 100/100
|
413 |
+
/\*\s*\*\*Total Grade:\*\*\s*(.*)/, // * **Total Grade:** 100/100
|
414 |
+
/(?:•|-|\*)\s*\*\*(?:Final|Total|Overall)?\s*Grade\*\*:?:\s*(.*)/i, // Generic grade
|
415 |
+
/(?:•|-|\*)\s*(?:Final|Total|Overall)?\s*Grade:?:\s*(.*)/i, // No bold
|
416 |
+
/(?:Final|Total|Overall)\s*Grade:?:\s*(\d+\/\d+|\d+)/im, // Anywhere in text
|
417 |
+
/(\d+\/\d+)\s*(?:final|total|overall)?/im // Number first
|
418 |
+
];
|
419 |
+
|
420 |
+
for (const pattern of overallGradePatterns) {
|
421 |
+
gradeMatch = sectionBlock.match(pattern);
|
422 |
+
if (gradeMatch) {
|
423 |
+
grade = gradeMatch[1].trim();
|
424 |
+
break;
|
425 |
+
}
|
426 |
+
}
|
427 |
+
console.log('Overall grade match:', gradeMatch, 'Final grade:', grade);
|
428 |
+
|
429 |
+
// ROBUST OVERALL PASS EXTRACTION - handles multiple formats
|
430 |
+
let pass = false;
|
431 |
+
let passMatch = null;
|
432 |
+
|
433 |
+
const overallPassPatterns = [
|
434 |
+
// Exact format variations found in logs
|
435 |
+
/-\s*\*\*Overall Pass:\*\*\s*(.*)/i, // - **Overall Pass:** true
|
436 |
+
/•\s*\*\*Overall Pass:\*\*\s*(.*)/i, // • **Overall Pass:** true
|
437 |
+
/\*\s*\*\*Overall Pass:\*\*\s*(.*)/i, // * **Overall Pass:** true
|
438 |
+
/•\s*\*\*All Sections Pass:\*\*\s*(.*)/i, // • **All Sections Pass:** true
|
439 |
+
/-\s*\*\*All Sections Pass:\*\*\s*(.*)/i, // - **All Sections Pass:** true
|
440 |
+
/\*\s*\*\*All Sections Pass:\*\*\s*(.*)/i, // * **All Sections Pass:** true
|
441 |
+
/•\s*\*\*Final Pass:\*\*\s*(.*)/i, // • **Final Pass:** true
|
442 |
+
/-\s*\*\*Final Pass:\*\*\s*(.*)/i, // - **Final Pass:** true
|
443 |
+
/\*\s*\*\*Final Pass:\*\*\s*(.*)/i, // * **Final Pass:** true
|
444 |
+
|
445 |
+
// Generic patterns with flexible formatting
|
446 |
+
/(?:•|-|\*)\s*\*\*(?:Overall\s+|All\s+Sections\s+|Final\s+)?Pass\*\*:?:\s*(.*)/i,
|
447 |
+
/(?:•|-|\*)\s*(?:Overall\s+|All\s+Sections\s+|Final\s+)?Pass:?:\s*(.*)/i,
|
448 |
+
|
449 |
+
// Anywhere in text patterns
|
450 |
+
/Pass:?:\s*(true|false|✅|❌|TRUE|FALSE)/im,
|
451 |
+
/Overall\s*Pass:?:\s*(true|false|✅|❌|TRUE|FALSE)/im,
|
452 |
+
/All\s*Sections\s*Pass:?:\s*(true|false|✅|❌|TRUE|FALSE)/im,
|
453 |
+
/(true|false|✅|❌|TRUE|FALSE)\s*(?:overall|pass)/im,
|
454 |
+
|
455 |
+
// Handle capitalized boolean values
|
456 |
+
/Pass:?:\s*(True|False|TRUE|FALSE)/im,
|
457 |
+
/Overall\s*Pass:?:\s*(True|False|TRUE|FALSE)/im
|
458 |
+
];
|
459 |
+
|
460 |
+
for (const pattern of overallPassPatterns) {
|
461 |
+
passMatch = sectionBlock.match(pattern);
|
462 |
+
if (passMatch) {
|
463 |
+
const passValue = passMatch[1].toLowerCase().trim();
|
464 |
+
pass = passValue.includes('true') ||
|
465 |
+
passValue.includes('✅') ||
|
466 |
+
passValue === 'yes' ||
|
467 |
+
passValue === 'passed' ||
|
468 |
+
passValue === 'pass';
|
469 |
+
break;
|
470 |
+
}
|
471 |
+
}
|
472 |
+
console.log('Overall pass match:', passMatch, 'Final pass:', pass);
|
473 |
+
|
474 |
+
// Look for various primary issue formats
|
475 |
+
const explanationMatch = sectionBlock.match(/\*\*Overall Pass\*\*:\s*[^()]*\(([^)]+)\)/);
|
476 |
+
const statusMatch = sectionBlock.match(/(?:•|-|\*)\s*\*\*(?:Pipeline\s+)?Status\*\*?:?\s*([\s\S]*?)(?=\n(?:•|-|\*)\s*\*\*|$)/);
|
477 |
+
const primaryIssueMatch = sectionBlock.match(/(?:•|-|\*)\s*\*\*(?:Primary\s+)?Issue\*\*?:?\s*([\s\S]*?)(?=\n(?:•|-|\*)\s*\*\*|$)/) ||
|
478 |
+
sectionBlock.match(/(?:•|-|\*)\s*(?:Primary\s+)?Issue:?:\s*([\s\S]*?)(?=\n(?:•|-|\*)\s*\*\*|$)/);
|
479 |
+
const errorsMatch = sectionBlock.match(/(?:•|-|\*)\s*\*\*Total\s+Errors?\*\*?:?\s*([\s\S]*?)(?=\n(?:•|-|\*)\s*\*\*|$)/);
|
480 |
+
const totalSectionsMatch = sectionBlock.match(/Total\s*Sections\s*Passing:?:\s*([^\n]+)/i);
|
481 |
+
|
482 |
+
let primaryIssue = 'All sections passed successfully.';
|
483 |
+
if (explanationMatch) {
|
484 |
+
primaryIssue = explanationMatch[1].trim();
|
485 |
+
} else if (statusMatch) {
|
486 |
+
primaryIssue = statusMatch[1].trim();
|
487 |
+
} else if (primaryIssueMatch) {
|
488 |
+
primaryIssue = primaryIssueMatch[1].trim();
|
489 |
+
} else if (errorsMatch) {
|
490 |
+
const errorText = errorsMatch[1].trim();
|
491 |
+
if (errorText !== '[]' && errorText !== '') {
|
492 |
+
primaryIssue = `Errors found: ${errorText}`;
|
493 |
+
}
|
494 |
+
}
|
495 |
+
if (totalSectionsMatch) {
|
496 |
+
primaryIssue = `${primaryIssue} | Total Sections Passing: ${totalSectionsMatch[1].trim()}`;
|
497 |
+
}
|
498 |
+
|
499 |
+
console.log('Primary issue extraction - explanation:', explanationMatch, 'status:', statusMatch, 'issue:', primaryIssueMatch, 'Final issue:', primaryIssue);
|
500 |
+
|
501 |
+
// Extract detailed assessment content
|
502 |
+
const detailedAssessmentMatch = sectionBlock.match(/(?:•|-|\*)\s*\*\*(?:Detailed\s+)?Assessment\*\*?:?\s*([\s\S]*?)(?=\n(?:•|-|\*)\s*\*\*|$)/i);
|
503 |
+
const explanationsMatch = sectionBlock.match(/(?:•|-|\*)\s*\*\*(?:Explanation|Reasoning)\*\*?:?\s*([\s\S]*?)(?=\n(?:•|-|\*)\s*\*\*|$)/i);
|
504 |
+
|
505 |
+
// Extract key strengths
|
506 |
+
const keyStrengthsMatch = sectionBlock.match(/(?:•|-|\*)\s*\*\*Key\s+Strengths\*\*?:?\s*([\s\S]*?)(?=\n(?:•|-|\*)\s*\*\*|$)/i);
|
507 |
+
const strengthsList = keyStrengthsMatch ?
|
508 |
+
keyStrengthsMatch[1].split('\n')
|
509 |
+
.map(line => line.replace(/^[-•*]\s*/, '').trim())
|
510 |
+
.filter(line => line.length > 0) : undefined;
|
511 |
+
|
512 |
+
// Extract recommendations
|
513 |
+
const recommendationsMatch = sectionBlock.match(/(?:•|-|\*)\s*\*\*(?:Recommendations?|Suggestions?)\*\*?:?\s*([\s\S]*?)(?=\n(?:•|-|\*)\s*\*\*|$)/i);
|
514 |
+
const recommendationsList = recommendationsMatch ?
|
515 |
+
recommendationsMatch[1].split('\n')
|
516 |
+
.map(line => line.replace(/^[-•*]\s*/, '').trim())
|
517 |
+
.filter(line => line.length > 0) : undefined;
|
518 |
+
|
519 |
+
console.log('Setting overall data - grade:', grade, 'pass:', pass, 'primaryIssue:', primaryIssue);
|
520 |
+
|
521 |
+
return {
|
522 |
+
grade,
|
523 |
+
pass,
|
524 |
+
primaryIssue,
|
525 |
+
detailedAssessment: detailedAssessmentMatch ? detailedAssessmentMatch[1].trim() : undefined,
|
526 |
+
explanations: explanationsMatch ? explanationsMatch[1].trim() : undefined,
|
527 |
+
keyStrengths: strengthsList,
|
528 |
+
recommendations: recommendationsList,
|
529 |
+
rawContent: sectionBlock
|
530 |
+
};
|
531 |
+
};
|
532 |
+
|
533 |
+
/**
|
534 |
+
* Determine the type of an additional section based on its content
|
535 |
+
*/
|
536 |
+
const determineSectionType = (sectionBlock: string): 'assessment' | 'strengths' | 'recommendations' | 'explanations' | 'other' => {
|
537 |
+
const lowerContent = sectionBlock.toLowerCase();
|
538 |
+
|
539 |
+
if (lowerContent.includes('strength') || lowerContent.includes('positive') || lowerContent.includes('excellent')) {
|
540 |
+
return 'strengths';
|
541 |
+
} else if (lowerContent.includes('recommend') || lowerContent.includes('suggest') || lowerContent.includes('improve')) {
|
542 |
+
return 'recommendations';
|
543 |
+
} else if (lowerContent.includes('explain') || lowerContent.includes('reason') || lowerContent.includes('why')) {
|
544 |
+
return 'explanations';
|
545 |
+
} else if (lowerContent.includes('assess') || lowerContent.includes('evaluate') || lowerContent.includes('analysis')) {
|
546 |
+
return 'assessment';
|
547 |
+
} else {
|
548 |
+
return 'other';
|
549 |
+
}
|
550 |
+
};
|
551 |
+
|
552 |
/**
|
553 |
* Parses the new, structured QA report format.
|
554 |
* @param qaText The raw `qa_gaurd` string from the API.
|
|
|
562 |
meta: { ...defaultSection },
|
563 |
h1: { ...defaultSection },
|
564 |
copy: { ...defaultSection },
|
565 |
+
overall: { grade: 'N/A', pass: false, primaryIssue: 'Parsing failed' },
|
566 |
+
completeRawReport: qaText // Always preserve the complete raw report
|
567 |
};
|
568 |
|
569 |
const cleanedQaText = cleanResponseText(qaText);
|
|
|
573 |
|
574 |
// Check if it's the new markdown format (starts with ##) or contains **TITLE** style sections
|
575 |
if (cleanedQaText.startsWith('##') || cleanedQaText.includes('**TITLE**') || cleanedQaText.includes('### **') || cleanedQaText.includes('### TITLE')) {
|
576 |
+
console.log('Using enhanced markdown parser for QA report');
|
577 |
console.log('QA text starts with:', cleanedQaText.substring(0, 200));
|
578 |
|
579 |
// Handle different section header formats
|
|
|
601 |
|
602 |
const parsedData: Partial<DetailedQaReport> = {};
|
603 |
let correctedCopyFromSeparateSection = '';
|
604 |
+
const additionalSections: { [sectionName: string]: { content: string; type: 'assessment' | 'strengths' | 'recommendations' | 'explanations' | 'other'; } } = {};
|
605 |
|
606 |
// Special handling for single-section format like "## GRADE REPORT"
|
607 |
if (sections.length === 1 && (sections[0].includes('GRADE REPORT') || sections[0].includes('QUALITY ASSURANCE'))) {
|
|
|
618 |
header = header.replace(/^#+\s*/, '').replace(/\*\*/g, '').replace(/[:\-–]+$/, '').trim();
|
619 |
console.log('Processing header:', header);
|
620 |
|
621 |
+
// Enhanced section parsing to capture ALL content
|
622 |
if (header.includes('title')) {
|
623 |
+
console.log('Parsing title section with enhanced content capture');
|
624 |
+
parsedData.title = parseEnhancedSection(sectionBlock);
|
625 |
} else if (header.includes('meta')) {
|
626 |
+
console.log('Parsing meta section with enhanced content capture');
|
627 |
+
parsedData.meta = parseEnhancedSection(sectionBlock);
|
628 |
} else if (header.includes('h1')) {
|
629 |
+
console.log('Parsing h1 section with enhanced content capture');
|
630 |
+
parsedData.h1 = parseEnhancedSection(sectionBlock);
|
631 |
} else if (header.includes('copy') && !header.includes('corrected')) {
|
632 |
+
console.log('Parsing copy section with enhanced content capture');
|
633 |
+
parsedData.copy = parseEnhancedSection(sectionBlock);
|
634 |
} else if (header.includes('corrected') && header.includes('copy')) {
|
635 |
console.log('Capturing separate CORRECTED COPY section');
|
636 |
correctedCopyFromSeparateSection = lines.slice(1).join('\n').trim();
|
637 |
} else if (header.includes('overall') || header.includes('assessment') || header.includes('pipeline')) {
|
638 |
+
console.log('Parsing overall section with enhanced content capture');
|
639 |
console.log('Overall section text:', sectionBlock.substring(0, 300));
|
640 |
|
641 |
+
// Enhanced overall section parsing to capture ALL content
|
642 |
+
const enhancedOverall = parseEnhancedOverallSection(sectionBlock);
|
643 |
+
parsedData.overall = enhancedOverall;
|
644 |
+
} else {
|
645 |
+
// Capture any additional sections that don't match standard patterns
|
646 |
+
console.log('Capturing additional section:', header);
|
647 |
+
const sectionType = determineSectionType(sectionBlock);
|
648 |
+
additionalSections[header] = {
|
649 |
+
content: sectionBlock,
|
650 |
+
type: sectionType
|
651 |
+
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
652 |
}
|
653 |
});
|
654 |
|
|
|
657 |
meta: parsedData.meta || { ...defaultSection, errors: ['Meta section not found'] },
|
658 |
h1: parsedData.h1 || { ...defaultSection, errors: ['H1 section not found'] },
|
659 |
copy: parsedData.copy || { ...defaultSection, errors: ['Copy section not found'] },
|
660 |
+
overall: parsedData.overall || { grade: 'N/A', pass: false, primaryIssue: 'Overall section not found' },
|
661 |
+
additionalSections: Object.keys(additionalSections).length > 0 ? additionalSections : undefined,
|
662 |
+
completeRawReport: qaText
|
663 |
};
|
664 |
|
665 |
// If we saw a separate CORRECTED COPY section, populate copy.corrected with it when useful
|
|
|
695 |
parsedData.overall = {
|
696 |
grade,
|
697 |
pass,
|
698 |
+
primaryIssue: pass ? 'All sections passed successfully.' : 'Overall assessment failed.',
|
699 |
+
rawContent: cleanedQaText
|
700 |
};
|
701 |
|
702 |
console.log('Found inline overall results - grade:', grade, 'pass:', pass);
|
|
|
725 |
parsedData.overall = {
|
726 |
grade: averageGrade,
|
727 |
pass: allSectionsPassed,
|
728 |
+
primaryIssue: allSectionsPassed ? 'All sections passed successfully.' : 'One or more sections failed.',
|
729 |
+
rawContent: cleanedQaText
|
730 |
};
|
731 |
|
732 |
console.log('Calculated overall from individual sections - pass:', allSectionsPassed, 'grade:', averageGrade);
|
|
|
738 |
|
739 |
console.log('Final parsed QA data:', finalReport.overall);
|
740 |
console.log('Setting overallPass:', finalReport.overall.pass, 'overallGrade:', finalReport.overall.grade);
|
741 |
+
console.log('Additional sections captured:', Object.keys(additionalSections));
|
742 |
|
743 |
return {
|
744 |
detailedQaReport: finalReport,
|