PRININIT / src /eval /evaluator.ts
Rachit-Tw's picture
Upload 50 files
7d9309d verified
Raw
History Blame Contribute Delete
6.78 kB
/**
* PRIX AI Evaluation System - Evaluation Engine
* Compares test outputs against expected results
*/
import {
TestOutput,
ExpectedResult,
TestScore,
TestResult,
TestCase,
IssueType,
ConfidenceLevel
} from './types'
export interface EvaluationOptions {
strictConfidence?: boolean
strictIssueType?: boolean
allowPartialMatches?: boolean
}
export class Evaluator {
private options: EvaluationOptions
constructor(options: EvaluationOptions = {}) {
this.options = {
strictConfidence: false,
strictIssueType: false,
allowPartialMatches: true,
...options
}
}
/**
* Evaluate a single test output against expected results
*/
evaluate(testCase: TestCase, output: TestOutput): TestResult {
const startTime = Date.now()
const score = this.calculateScore(output, testCase.expected)
return {
testName: testCase.name,
testPath: testCase.path,
category: testCase.category,
passed: score.overall,
score,
output,
expected: testCase.expected,
executionTime: output.executionTime,
timestamp: new Date().toISOString()
}
}
/**
* Calculate detailed score for a test
*/
calculateScore(output: TestOutput, expected: ExpectedResult): TestScore {
// Detection check
const detectionMatch = expected.shouldDetectIssue === output.detectedIssues
// Issue type check (if detection expected)
let typeMatch = true
if (expected.shouldDetectIssue && expected.issueType && output.issueType) {
typeMatch = this.matchIssueType(expected.issueType, output.issueType)
}
// Confidence check
let confidenceMatch = true
if (expected.expectedConfidence && output.confidence) {
confidenceMatch = this.matchConfidence(
expected.expectedConfidence,
output.confidence
)
}
// Auto-PR check
const autoPRMatch = expected.shouldAutoPR === output.autoPRTriggered
// Suggestion count check (if specific count expected)
let suggestionCountMatch = true
if (expected.expectedSuggestions) {
suggestionCountMatch =
expected.expectedSuggestions.length === output.suggestions.length
}
// Calculate overall score
const allChecks = [
detectionMatch,
typeMatch,
confidenceMatch,
autoPRMatch,
suggestionCountMatch
]
// Weight detection more heavily
const overall =
detectionMatch &&
(typeMatch || !this.options.strictIssueType) &&
(confidenceMatch || !this.options.strictConfidence) &&
autoPRMatch
return {
detection: detectionMatch,
classification: typeMatch,
confidence: confidenceMatch,
autoPR: autoPRMatch,
overall,
details: {
detectionMatch,
typeMatch,
confidenceMatch,
autoPRMatch,
suggestionCountMatch
}
}
}
/**
* Match issue types with flexibility
*/
private matchIssueType(expected: IssueType, actual: IssueType): boolean {
if (expected === actual) return true
if (!this.options.strictIssueType) {
// Allow some flexibility in classification
const securityTypes: IssueType[] = ['security', 'bug']
const bugTypes: IssueType[] = ['bug', 'mutation']
if (securityTypes.includes(expected) && securityTypes.includes(actual))
return true
if (bugTypes.includes(expected) && bugTypes.includes(actual)) return true
}
return false
}
/**
* Match confidence levels
*/
private matchConfidence(
expected: ConfidenceLevel,
actual: ConfidenceLevel
): boolean {
if (expected === actual) return true
if (!this.options.strictConfidence) {
// Allow adjacent confidence levels
const levels: ConfidenceLevel[] = ['low', 'medium', 'high']
const expectedIdx = levels.indexOf(expected)
const actualIdx = levels.indexOf(actual)
// Within 1 level is acceptable
return Math.abs(expectedIdx - actualIdx) <= 1
}
return false
}
/**
* Generate detailed evaluation report for a single test
*/
generateReport(result: TestResult): string {
const lines = [
`\n${'='.repeat(60)}`,
`Test: ${result.testName} (${result.category})`,
`${'='.repeat(60)}`,
`Status: ${result.passed ? 'βœ… PASS' : '❌ FAIL'}`,
`Execution Time: ${result.executionTime}ms`,
'',
'--- Expected ---',
` Detect Issue: ${result.expected.shouldDetectIssue}`,
` Issue Type: ${result.expected.issueType || 'any'}`,
` Confidence: ${result.expected.expectedConfidence || 'any'}`,
` Auto-PR: ${result.expected.shouldAutoPR}`,
'',
'--- Actual ---',
` Detected: ${result.output.detectedIssues}`,
` Type: ${result.output.issueType || 'none'}`,
` Confidence: ${result.output.confidence || 'none'}`,
` Auto-PR: ${result.output.autoPRTriggered}`,
` Suggestions: ${result.output.suggestions.length}`,
` Comments: ${result.output.comments.length}`,
'',
'--- Score Breakdown ---',
` Detection: ${result.score.detection ? 'βœ…' : '❌'}`,
` Classification: ${result.score.classification ? 'βœ…' : '❌'}`,
` Confidence: ${result.score.confidence ? 'βœ…' : '❌'}`,
` Auto-PR: ${result.score.autoPR ? 'βœ…' : '❌'}`,
'',
'--- Errors ---',
...(result.output.errors.length > 0
? result.output.errors.map(e => ` ❌ ${e}`)
: [' None'])
]
return lines.join('\n')
}
/**
* Compare two test results for regression detection
*/
compareResults(
baseline: TestResult,
current: TestResult
): {regressed: boolean; changes: string[]} {
const changes: string[] = []
if (baseline.passed && !current.passed) {
changes.push(`Test ${current.testName} started failing`)
}
if (!baseline.passed && current.passed) {
changes.push(`Test ${current.testName} now passing (improvement)`)
}
if (baseline.score.overall && !current.score.overall) {
changes.push(`Overall score degraded for ${current.testName}`)
}
return {
regressed: changes.some(
c => c.includes('failing') || c.includes('degraded')
),
changes
}
}
}
/**
* Utility function for quick evaluation
*/
export function evaluate(
output: TestOutput,
expected: ExpectedResult,
options?: EvaluationOptions
): TestScore {
const evaluator = new Evaluator(options)
return evaluator.calculateScore(output, expected)
}