Spaces:
Sleeping
Sleeping
| /** | |
| * test_all_bugfixes.js | |
| * | |
| * Comprehensive checkup for all 17 bug fixes from the LexGuard deep scan. | |
| * Runs without DB connection for unit tests, marks DB tests as skipped if no connection. | |
| */ | |
| const path = require('path'); | |
| const ROOT = path.join(__dirname, '..'); | |
| let passed = 0; | |
| let failed = 0; | |
| let skipped = 0; | |
| function pass(name) { | |
| console.log(` ✅ PASS: ${name}`); | |
| passed++; | |
| } | |
| function fail(name, reason) { | |
| console.log(` ❌ FAIL: ${name}`); | |
| console.log(` Reason: ${reason}`); | |
| failed++; | |
| } | |
| function skip(name, reason) { | |
| console.log(` ⏭️ SKIP: ${name} (${reason})`); | |
| skipped++; | |
| } | |
| function section(title) { | |
| console.log(`\n${'═'.repeat(60)}`); | |
| console.log(` ${title}`); | |
| console.log(`${'═'.repeat(60)}`); | |
| } | |
| // ───────────────────────────────────────────────────────────── | |
| // MODULE LOADING TESTS | |
| // ───────────────────────────────────────────────────────────── | |
| section('1. MODULE LOADING — All edited services must require() cleanly'); | |
| const modulesToTest = [ | |
| 'src/services/agent9CrossRefAuditor.js', | |
| 'src/services/agent10DeterministicAuditor.js', | |
| 'src/services/citationVerifier.js', | |
| 'src/services/crossClauseContradictionEngine.js', | |
| 'src/services/graphRagService.js', | |
| 'src/services/aiClient.js', | |
| 'src/services/lawRetrieverService.js', | |
| 'src/services/agent2RiskAnalyst.js', | |
| 'src/services/agentPreFlight.js', | |
| 'src/utils/semanticChunker.js', | |
| 'src/services/deterministicFactExtractor.js', | |
| ]; | |
| for (const mod of modulesToTest) { | |
| try { | |
| require(path.join(ROOT, mod)); | |
| pass(`require('${mod.split('/').pop()}')`); | |
| } catch (e) { | |
| fail(`require('${mod.split('/').pop()}')`, e.message.split('\n')[0]); | |
| } | |
| } | |
| // ───────────────────────────────────────────────────────────── | |
| // BUG 1 — Agent 9 import fix | |
| // ───────────────────────────────────────────────────────────── | |
| section('2. BUG 1 — Agent 9: correct named import of callLLM'); | |
| try { | |
| const agent9Source = require('fs').readFileSync( | |
| path.join(ROOT, 'src/services/agent9CrossRefAuditor.js'), 'utf8' | |
| ); | |
| const hasWrongImport = /const callLLM = require\('\.\/aiClient'\)/.test(agent9Source); | |
| const hasCorrectImport = /const \{ callLLM \} = require\('\.\/aiClient'\)/.test(agent9Source); | |
| if (hasWrongImport) { | |
| fail('Agent 9 import', 'Still using incorrect non-destructured import'); | |
| } else if (hasCorrectImport) { | |
| pass('Agent 9 uses { callLLM } destructured import'); | |
| } else { | |
| fail('Agent 9 import', 'Could not find callLLM import at all'); | |
| } | |
| } catch (e) { | |
| fail('Agent 9 import check', e.message); | |
| } | |
| // ───────────────────────────────────────────────────────────── | |
| // BUG 2 — Agent 10: llmResult.parsed removed | |
| // ───────────────────────────────────────────────────────────── | |
| section('3. BUG 2 — Agent 10: llmResult.parsed dead code removed'); | |
| try { | |
| const agent10Source = require('fs').readFileSync( | |
| path.join(ROOT, 'src/services/agent10DeterministicAuditor.js'), 'utf8' | |
| ); | |
| const hasDeadCode = /llmResult\.parsed\s*\|\|\s*llmResult/.test(agent10Source); | |
| const hasCorrectCode = /const data = llmResult;/.test(agent10Source); | |
| if (hasDeadCode) { | |
| fail('Agent 10 llmResult.parsed', 'Dead code still present: llmResult.parsed || llmResult'); | |
| } else if (hasCorrectCode) { | |
| pass('Agent 10: const data = llmResult (no .parsed dead code)'); | |
| } else { | |
| fail('Agent 10 llmResult.parsed', 'Could not verify correction'); | |
| } | |
| } catch (e) { | |
| fail('Agent 10 source check', e.message); | |
| } | |
| // ───────────────────────────────────────────────────────────── | |
| // BUG 3 — Citation Verifier: dead stats loop removed | |
| // ───────────────────────────────────────────────────────────── | |
| section('4. BUG 3 — Citation Verifier: verifyCitations.lastStats dead code removed'); | |
| try { | |
| const cvSource = require('fs').readFileSync( | |
| path.join(ROOT, 'src/services/citationVerifier.js'), 'utf8' | |
| ); | |
| const hasDeadCode = /verifyCitations\.lastStats/.test(cvSource); | |
| if (hasDeadCode) { | |
| fail('Citation Verifier stats', 'verifyCitations.lastStats dead code still present'); | |
| } else { | |
| pass('Citation Verifier: dead stats loop removed'); | |
| } | |
| } catch (e) { | |
| fail('Citation Verifier source check', e.message); | |
| } | |
| // ───────────────────────────────────────────────────────────── | |
| // BUG 4 — Cross-Clause Contradiction Engine: Rule 1b logic | |
| // ───────────────────────────────────────────────────────────── | |
| section('5. BUG 4 — Contradiction Engine: Rule 1b gap fix'); | |
| try { | |
| const ccSource = require('fs').readFileSync( | |
| path.join(ROOT, 'src/services/crossClauseContradictionEngine.js'), 'utf8' | |
| ); | |
| // Check Rule 1b no longer requires BOTH to be zero | |
| const hasOldLogic = /totalFeeFacts\.length === 0 && milestoneFacts\.length === 0/.test(ccSource); | |
| const hasNewLogic = /rule1Fired/.test(ccSource); | |
| if (hasOldLogic && !hasNewLogic) { | |
| fail('Rule 1b gap', 'Old AND logic still present, rule1Fired variable not found'); | |
| } else if (hasNewLogic) { | |
| pass('Rule 1b: uses rule1Fired guard (not dual-empty AND logic)'); | |
| } else { | |
| fail('Rule 1b gap', 'Could not verify fix'); | |
| } | |
| // Check triple-AND exclusion | |
| const hasTrippleGuard = /f\.raw === totalFeeFacts\[0\]\.raw/.test(ccSource); | |
| if (hasTrippleGuard) { | |
| pass('Rule 1 exclusion: triple-AND guard (clauseIndex + value + raw)'); | |
| } else { | |
| fail('Rule 1 exclusion', 'Triple-AND guard not found'); | |
| } | |
| } catch (e) { | |
| fail('Contradiction engine source check', e.message); | |
| } | |
| // ───────────────────────────────────────────────────────────── | |
| // BUG 5+6 — Agent 4: live risk_level re-fetch + note protection | |
| // ───────────────────────────────────────────────────────────── | |
| section('6. BUG 5+6 — Agent 4: live risk_level + note preservation'); | |
| try { | |
| const a4Source = require('fs').readFileSync( | |
| path.join(ROOT, 'src/services/agent4ComplianceChecker.js'), 'utf8' | |
| ); | |
| const hasLiveFetch = /liveClause = await Clause\.findById/.test(a4Source); | |
| const hasNoteGuard = /hasExistingNote/.test(a4Source); | |
| const hasRiskPriority = /RISK_PRIORITY/.test(a4Source); | |
| if (hasLiveFetch) { | |
| pass('Agent 4: re-fetches live clause per iteration'); | |
| } else { | |
| fail('Agent 4 live fetch', 'liveClause re-fetch not found'); | |
| } | |
| if (hasNoteGuard) { | |
| pass('Agent 4: hasExistingNote guard prevents overwriting Agent 10 notes'); | |
| } else { | |
| fail('Agent 4 note guard', 'hasExistingNote guard not found'); | |
| } | |
| if (hasRiskPriority) { | |
| pass('Agent 4: RISK_PRIORITY prevents downgrading escalated risk levels'); | |
| } else { | |
| fail('Agent 4 risk priority', 'RISK_PRIORITY guard not found'); | |
| } | |
| } catch (e) { | |
| fail('Agent 4 source check', e.message); | |
| } | |
| // ───────────────────────────────────────────────────────────── | |
| // BUG 7 — GraphRAG sentinel string | |
| // ───────────────────────────────────────────────────────────── | |
| section('7. BUG 7 — GraphRAG: sentinel string filtered'); | |
| try { | |
| const grSource = require('fs').readFileSync( | |
| path.join(ROOT, 'src/services/graphRagService.js'), 'utf8' | |
| ); | |
| const hasSentinelCheck = /No specific statutory framework mapped/.test(grSource); | |
| if (hasSentinelCheck) { | |
| pass('GraphRAG: sentinel string "No specific statutory framework mapped." is filtered'); | |
| } else { | |
| fail('GraphRAG sentinel', 'Sentinel string check not found'); | |
| } | |
| } catch (e) { | |
| fail('GraphRAG source check', e.message); | |
| } | |
| // ───────────────────────────────────────────────────────────── | |
| // BUG 8 — agentLimit module-level | |
| // ───────────────────────────────────────────────────────────── | |
| section('8. BUG 8 — jobQueueService: agentLimit at module level'); | |
| try { | |
| const jqSource = require('fs').readFileSync( | |
| path.join(ROOT, 'src/services/jobQueueService.js'), 'utf8' | |
| ); | |
| // Check that agentLimit is NOT inside processContractJob | |
| const lines = jqSource.split('\n'); | |
| let inFunction = false; | |
| let functionDepth = 0; | |
| let agentLimitInFunction = false; | |
| let agentLimitAtModule = false; | |
| for (let i = 0; i < lines.length; i++) { | |
| const line = lines[i]; | |
| if (line.includes('async function processContractJob') || line.includes('processContractJob = async')) { | |
| inFunction = true; | |
| functionDepth = 0; | |
| } | |
| if (inFunction) { | |
| functionDepth += (line.match(/\{/g) || []).length; | |
| functionDepth -= (line.match(/\}/g) || []).length; | |
| if (functionDepth <= 0 && i > 0) inFunction = false; | |
| } | |
| if (line.includes('const agentLimit') && line.includes('limitFn')) { | |
| if (inFunction) { | |
| agentLimitInFunction = true; | |
| } else { | |
| agentLimitAtModule = true; | |
| } | |
| } | |
| } | |
| if (agentLimitInFunction) { | |
| fail('agentLimit scope', 'agentLimit is still defined inside processContractJob'); | |
| } else if (agentLimitAtModule) { | |
| pass('agentLimit is at module level (shared across all concurrent jobs)'); | |
| } else { | |
| // Could be that it's there but we couldn't detect it with simple parsing | |
| // Just check the source string | |
| const moduleDecl = jqSource.indexOf('const agentLimit'); | |
| const funcDecl = jqSource.indexOf('async function processContractJob'); | |
| if (moduleDecl > 0 && funcDecl > 0 && moduleDecl < funcDecl) { | |
| pass('agentLimit declared before processContractJob function (module level)'); | |
| } else { | |
| skip('agentLimit scope', 'Could not statically determine scope, check manually'); | |
| } | |
| } | |
| } catch (e) { | |
| fail('jobQueueService source check', e.message); | |
| } | |
| // ───────────────────────────────────────────────────────────── | |
| // ISSUE 9 — Semantic Chunker: precise period regex | |
| // ───────────────────────────────────────────────────────────── | |
| section('9. ISSUE 9 — SemanticChunker: precise period regex'); | |
| try { | |
| const chunker = require(path.join(ROOT, 'src/utils/semanticChunker.js')); | |
| // Test 1: Should still fix concatenated sentences | |
| const test1 = chunker.normalizeText('Total Contract Value: INR 50,00,000.Milestone 1: INR 10,00,000'); | |
| const fixed = test1.includes('50,00,000. Milestone'); | |
| if (fixed) { | |
| pass('Chunker: "50,00,000.Milestone" → "50,00,000. Milestone" (sentence join fixed)'); | |
| } else { | |
| fail('Chunker sentence fix', `Got: "${test1}"`); | |
| } | |
| // Test 2: Should NOT break abbreviations like U.S.A. | |
| const test2 = chunker.normalizeText('Governed by U.S.A. law.'); | |
| const abbreviationBroken = test2.includes('U. S. A.'); | |
| if (abbreviationBroken) { | |
| fail('Chunker abbreviation protection', `"U.S.A." was broken into "${test2}"`); | |
| } else { | |
| pass('Chunker: "U.S.A." abbreviation NOT broken by period regex'); | |
| } | |
| // Test 3: Should NOT break "Pvt.Ltd." | |
| const test3 = chunker.normalizeText('Company: TechCorp Pvt.Ltd. is the employer.'); | |
| if (test3.includes('Pvt. Ltd.') || !test3.includes('PvtLtd')) { | |
| pass('Chunker: "Pvt.Ltd." handling is acceptable'); | |
| } | |
| } catch (e) { | |
| fail('SemanticChunker test', e.message); | |
| } | |
| // ───────────────────────────────────────────────────────────── | |
| // ISSUE 10 — INR regex: crore-level amounts | |
| // ───────────────────────────────────────────────────────────── | |
| section('10. ISSUE 10 — DeterministicFactExtractor: crore-level INR amounts'); | |
| try { | |
| const { extractFactTable } = require(path.join(ROOT, 'src/services/deterministicFactExtractor.js')); | |
| // Test standard lakh amount | |
| const result1 = extractFactTable([{ segmentIndex: 0, rawText: 'Total Contract Value: INR 50,00,000', clause_type: 'total_fee', risk_level: 'low' }]); | |
| const currency1 = result1.facts.filter(f => f.type === 'currency'); | |
| if (currency1.some(f => f.value === 5000000)) { | |
| pass('INR parser: INR 50,00,000 → 5,000,000 ✓'); | |
| } else { | |
| fail('INR parser lakh', `Expected 5000000, got: ${JSON.stringify(currency1.map(f => f.value))}`); | |
| } | |
| // Test crore-level amount (new fix) | |
| const result2 = extractFactTable([{ segmentIndex: 0, rawText: 'Total Contract Value: INR 1,00,00,000 (One Crore)', clause_type: 'total_fee', risk_level: 'low' }]); | |
| const currency2 = result2.facts.filter(f => f.type === 'currency'); | |
| if (currency2.some(f => f.value === 10000000)) { | |
| pass('INR parser: INR 1,00,00,000 → 10,000,000 (crore) ✓'); | |
| } else { | |
| fail('INR parser crore', `Expected 10000000, got: ${JSON.stringify(currency2.map(f => f.value))}`); | |
| } | |
| // Test milestone sum mismatch scenario (Clause 3 trap) | |
| const clauses = [ | |
| { segmentIndex: 0, rawText: 'Total Contract Value: INR 50,00,000.', clause_type: 'total_fee', risk_level: 'low' }, | |
| { segmentIndex: 1, rawText: 'Milestone 1: INR 10,00,000', clause_type: 'milestone_fee', risk_level: 'low' }, | |
| { segmentIndex: 2, rawText: 'Milestone 2: INR 15,00,000', clause_type: 'milestone_fee', risk_level: 'low' }, | |
| { segmentIndex: 3, rawText: 'Milestone 3: INR 30,00,000', clause_type: 'milestone_fee', risk_level: 'low' }, | |
| ]; | |
| const allFacts = extractFactTable(clauses); | |
| const totalFact = allFacts.facts.find(f => f.type === 'currency' && f.clauseIndex === 0); | |
| const milestoneFacts = allFacts.facts.filter(f => f.type === 'currency' && f.clauseIndex > 0); | |
| const total = totalFact?.value; | |
| const sum = milestoneFacts.reduce((a, f) => a + f.value, 0); | |
| if (total > 0 && sum > 0) { | |
| if (total !== sum) { | |
| pass(`INR Math trap test: Total=${total/100000}L, Milestones Sum=${sum/100000}L → MISMATCH detectable (${sum > total ? 'milestones exceed total' : 'milestones below total'})!`); | |
| } else { | |
| pass(`INR Math trap test: All amounts parsed. Total=${total}, Sum=${sum}`); | |
| } | |
| } else { | |
| fail('INR Math trap test', `Could not parse: Total=${total}, Sum=${sum}`); | |
| } | |
| } catch (e) { | |
| fail('DeterministicFactExtractor test', e.message); | |
| } | |
| // ───────────────────────────────────────────────────────────── | |
| // ISSUE 12 — aiClient auto-chunker merge shape | |
| // ───────────────────────────────────────────────────────────── | |
| section('11. ISSUE 12 — aiClient: auto-chunker intelligent merge'); | |
| try { | |
| const aiClientSource = require('fs').readFileSync( | |
| path.join(ROOT, 'src/services/aiClient.js'), 'utf8' | |
| ); | |
| const hasSmartMerge = /hasResults1.*hasResults2/.test(aiClientSource.replace(/\n/g, ' ')); | |
| const hasResultsMerge = aiClientSource.includes('combinedResults = [...(res1?.results'); | |
| if (hasSmartMerge) { | |
| pass('aiClient: smart merge detects response shape before combining'); | |
| } else { | |
| fail('aiClient smart merge', 'hasResults1/hasResults2 shape detection not found'); | |
| } | |
| } catch (e) { | |
| fail('aiClient source check', e.message); | |
| } | |
| // ───────────────────────────────────────────────────────────── | |
| // ISSUE 13 — lawRetrieverService: empty domain guard | |
| // ───────────────────────────────────────────────────────────── | |
| section('12. ISSUE 13 — lawRetrieverService: empty domain fallback'); | |
| try { | |
| const lrSource = require('fs').readFileSync( | |
| path.join(ROOT, 'src/services/lawRetrieverService.js'), 'utf8' | |
| ); | |
| const hasFallback = /activeDomains\.length === 0/.test(lrSource); | |
| if (hasFallback) { | |
| pass('lawRetrieverService: empty domains → fallback to general_contract_law'); | |
| } else { | |
| fail('Empty domain guard', 'activeDomains.length === 0 guard not found'); | |
| } | |
| } catch (e) { | |
| fail('lawRetrieverService source check', e.message); | |
| } | |
| // ───────────────────────────────────────────────────────────── | |
| // ISSUE 14 — agent2RiskAnalyst: indemnification safe harbor | |
| // ───────────────────────────────────────────────────────────── | |
| section('13. ISSUE 14 — agent2RiskAnalyst: gross negligence blocks safe harbor'); | |
| try { | |
| const a2Source = require('fs').readFileSync( | |
| path.join(ROOT, 'src/services/agent2RiskAnalyst.js'), 'utf8' | |
| ); | |
| const hasGrossNegGuard = /!rawText\.includes\('gross negligence'\)/.test(a2Source) || | |
| /!rawText\.includes\("gross negligence"\)/.test(a2Source); | |
| if (hasGrossNegGuard) { | |
| pass('agent2: gross negligence blocks mutual indemnification safe harbor'); | |
| } else { | |
| fail('Indemnification safe harbor', 'gross negligence guard not found'); | |
| } | |
| } catch (e) { | |
| fail('agent2RiskAnalyst source check', e.message); | |
| } | |
| // ───────────────────────────────────────────────────────────── | |
| // ISSUE 15 — agentPreFlight: generalized prompt | |
| // ───────────────────────────────────────────────────────────── | |
| section('14. ISSUE 15 — agentPreFlight: generalized contract prompt'); | |
| try { | |
| const pfSource = require('fs').readFileSync( | |
| path.join(ROOT, 'src/services/agentPreFlight.js'), 'utf8' | |
| ); | |
| const hasEmploymentBias = /attached employment contract/.test(pfSource); | |
| const hasGenericPrompt = /attached legal contract/.test(pfSource); | |
| if (hasEmploymentBias) { | |
| fail('Pre-flight prompt', 'Still says "employment contract" in prompt'); | |
| } else if (hasGenericPrompt) { | |
| pass('Pre-flight: generalized to "legal contract" — works for all contract types'); | |
| } else { | |
| fail('Pre-flight prompt', 'Could not detect prompt text'); | |
| } | |
| } catch (e) { | |
| fail('agentPreFlight source check', e.message); | |
| } | |
| // ───────────────────────────────────────────────────────────── | |
| // ISSUE 16 — Agent 9: chunking for large contracts | |
| // ───────────────────────────────────────────────────────────── | |
| section('15. ISSUE 16 — agent9CrossRefAuditor: chunked analysis for large contracts'); | |
| try { | |
| const a9Source = require('fs').readFileSync( | |
| path.join(ROOT, 'src/services/agent9CrossRefAuditor.js'), 'utf8' | |
| ); | |
| const hasChunkSize = /CHUNK_SIZE = 15/.test(a9Source); | |
| const hasDedup = /seen = new Set/.test(a9Source); | |
| const hasWindowLoop = /for \(let i = 0; i < clauseMarkers\.length/.test(a9Source); | |
| if (hasChunkSize && hasDedup && hasWindowLoop) { | |
| pass('Agent 9: chunked windowed analysis with dedup for large contracts'); | |
| } else { | |
| const missing = []; | |
| if (!hasChunkSize) missing.push('CHUNK_SIZE'); | |
| if (!hasDedup) missing.push('dedup Set'); | |
| if (!hasWindowLoop) missing.push('window loop'); | |
| fail('Agent 9 chunking', `Missing: ${missing.join(', ')}`); | |
| } | |
| } catch (e) { | |
| fail('agent9 source check', e.message); | |
| } | |
| // ───────────────────────────────────────────────────────────── | |
| // CLASSIFIER SERVICE: predatory trap detector functions | |
| // ───────────────────────────────────────────────────────────── | |
| section('16. Classifier Service: detectPredatoryTraps function'); | |
| try { | |
| const { detectPredatoryTraps } = require(path.join(ROOT, 'src/services/classifierService.js')); | |
| // Test 1: Non-compete after termination → should detect | |
| const result1 = detectPredatoryTraps('Employee shall not work for competitors after termination for 12 months following their date of separation in a similar line of business.'); | |
| if (result1.some(t => t.severity === 'critical')) { | |
| pass('Classifier: post-employment non-compete detected as critical'); | |
| } else { | |
| fail('Classifier trap detection', `Expected critical trap, got: ${JSON.stringify(result1)}`); | |
| } | |
| // Test 2: Normal text → should not trigger | |
| const result2 = detectPredatoryTraps('Employee will receive a salary of INR 50,000 per month.'); | |
| if (result2.length === 0) { | |
| pass('Classifier: normal salary clause produces no false positives'); | |
| } else { | |
| fail('Classifier false positive', `Triggered on normal text: ${JSON.stringify(result2)}`); | |
| } | |
| } catch (e) { | |
| fail('Classifier test', e.message); | |
| } | |
| // ───────────────────────────────────────────────────────────── | |
| // CROSS-CLAUSE CONTRADICTION ENGINE: math logic | |
| // ───────────────────────────────────────────────────────────── | |
| section('17. Cross-Clause Contradiction Engine: fee sum mismatch logic'); | |
| try { | |
| const { detectContradictions } = require(path.join(ROOT, 'src/services/crossClauseContradictionEngine.js')); | |
| const { extractFactTable } = require(path.join(ROOT, 'src/services/deterministicFactExtractor.js')); | |
| // Simulate the Clause 3 math trap: Total=50L, Milestones=10L+15L+30L=55L | |
| const clauses = [ | |
| { _id: 'c1', segmentIndex: 0, rawText: 'Total Contract Value: INR 50,00,000.', clause_type: 'total_fee', risk_level: 'low', risk_score: 2 }, | |
| { _id: 'c2', segmentIndex: 1, rawText: 'Milestone 1: INR 10,00,000 upon completion of Phase 1.', clause_type: 'milestone_fee', risk_level: 'low', risk_score: 2 }, | |
| { _id: 'c3', segmentIndex: 2, rawText: 'Milestone 2: INR 15,00,000 upon delivery of Phase 2.', clause_type: 'milestone_fee', risk_level: 'low', risk_score: 2 }, | |
| { _id: 'c4', segmentIndex: 3, rawText: 'Milestone 3: INR 30,00,000 upon final acceptance.', clause_type: 'milestone_fee', risk_level: 'low', risk_score: 2 }, | |
| ]; | |
| // Test 1: Milestone Mismatch | |
| const factTable = extractFactTable(clauses); | |
| const result = detectContradictions(factTable, clauses); | |
| const findings = result?.findings || result || []; | |
| const feeIssues = findings.filter(f => f.rule === 'FEE_SUM_MISMATCH'); | |
| if (feeIssues.length > 0) { | |
| pass(`Math trap detected: FEE_SUM_MISMATCH → ${feeIssues.length} finding(s) (${feeIssues[0].severity || 'high'} severity)`); | |
| } else { | |
| fail('Fee sum mismatch', 'No contradiction detected for 50L total vs 55L milestones'); | |
| } | |
| // Test 2: Correct Mixed Periodic Milestones (Should NOT find contradiction) | |
| // Total: 50L (50,00,000) | |
| // Milestone 1 (one-time): 10L (10,00,000) | |
| // Milestone 2 (monthly): 2L (2,00,000) -> 24L | |
| // Milestone 3 (quarterly): 4L (4,00,000) -> 16L | |
| // Total sum: 10L + 24L + 16L = 50L | |
| const periodicClauses = [ | |
| { _id: 'pc1', segmentIndex: 0, rawText: 'Total contract value is INR 50,00,000.', clause_type: 'total_fee', risk_level: 'low' }, | |
| { _id: 'pc2', segmentIndex: 1, rawText: 'Initial payment: INR 10,00,000 upon signing.', clause_type: 'milestone_fee', risk_level: 'low' }, | |
| { _id: 'pc3', segmentIndex: 2, rawText: 'Monthly retainer of INR 2,00,000.', clause_type: 'milestone_fee', risk_level: 'low' }, | |
| { _id: 'pc4', segmentIndex: 3, rawText: 'Quarterly compliance fee of INR 4,00,000.', clause_type: 'milestone_fee', risk_level: 'low' }, | |
| ]; | |
| const periodicFactTable = extractFactTable(periodicClauses); | |
| const periodicResult = detectContradictions(periodicFactTable, periodicClauses); | |
| const periodicFindings = periodicResult?.findings || periodicResult || []; | |
| const periodicFeeIssues = periodicFindings.filter(f => f.rule === 'FEE_SUM_MISMATCH'); | |
| if (periodicFeeIssues.length === 0) { | |
| pass('Contradiction Engine periodic accumulation: 10L + 2L*12 + 4L*4 = 50L matched correctly ✓'); | |
| } else { | |
| fail('Contradiction Engine periodic accumulation', `Reported mismatch when sum should match. Findings: ${JSON.stringify(periodicFeeIssues)}`); | |
| } | |
| } catch (e) { | |
| fail('Cross-clause contradiction engine test', e.message); | |
| } | |
| // ───────────────────────────────────────────────────────────── | |
| // FINAL SUMMARY | |
| // ───────────────────────────────────────────────────────────── | |
| console.log(`\n${'═'.repeat(60)}`); | |
| console.log(` FULL CHECKUP RESULTS`); | |
| console.log(`${'═'.repeat(60)}`); | |
| console.log(` ✅ Passed: ${passed}`); | |
| console.log(` ❌ Failed: ${failed}`); | |
| console.log(` ⏭️ Skipped: ${skipped}`); | |
| console.log(`${'═'.repeat(60)}`); | |
| if (failed === 0) { | |
| console.log(`\n 🎉 ALL CHECKS PASSED! System is healthy.\n`); | |
| process.exit(0); | |
| } else { | |
| console.log(`\n 🚨 ${failed} check(s) FAILED. Review errors above.\n`); | |
| process.exit(1); | |
| } | |