Skip to content

Commit bb45ff8

Browse files
AryanBVclaude
andcommitted
Phase 3: Eliminative reasoning - modifier-based code elimination
Implements eliminative reasoning so user-provided modifiers automatically eliminate irrelevant HS codes, reducing questions and improving accuracy. New Files: - elimination.service.ts: Core elimination logic with rule-based filtering - test-elimination.ts: Test script verifying elimination behavior Key Features: - Variety exclusion: "Arabica" excludes Robusta codes, and vice versa - Processing state exclusion: "instant" restricts to Ch.21, excludes roasted/raw - Grade exclusion: "Grade A" excludes Grade B/C codes - Form exclusion: "beans" excludes powder/ground/instant forms - Decaffeination/flavoring exclusion rules - Material exclusion: "brake pads for cars" forces Ch.87, excludes Ch.69 Integration Points: - llm-navigator.service.ts: Filters children after keyword filtering - llm-conversational-classifier.service.ts: Tracks accumulated modifiers - multi-candidate-search.service.ts: Applies elimination to search results Test Results: - "Arabica coffee beans" -> Excludes all Robusta codes (PASS) - "Robusta coffee" -> Excludes all Arabica codes (PASS) - "instant coffee" -> Only Ch.21 codes (PASS) - "brake pads" -> Still works correctly (PASS) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 05c6c68 commit bb45ff8

5 files changed

Lines changed: 1046 additions & 13 deletions

File tree

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
/**
2+
* Test script for Phase 3: Elimination Service
3+
*
4+
* Tests:
5+
* 1. "Arabica coffee beans" - should NOT show Robusta codes
6+
* 2. "Robusta coffee" - should NOT show Arabica codes
7+
* 3. "instant coffee" - should only show Ch.21 codes
8+
* 4. "brake pads" - should still work (regression test)
9+
*/
10+
11+
import {
12+
filterCandidatesByElimination,
13+
extractModifiersFromText,
14+
extractModifiersFromAnswer,
15+
isQuestionStillRelevant
16+
} from '../src/services/elimination.service';
17+
18+
// Test data - simulated HS code candidates
19+
const coffeeCandidates = [
20+
{ code: '0901.11.11', description: 'Coffee not roasted: Arabica plantation: A Grade' },
21+
{ code: '0901.11.12', description: 'Coffee not roasted: Arabica plantation: B Grade' },
22+
{ code: '0901.11.21', description: 'Coffee not roasted: Arabica cherry: A Grade' },
23+
{ code: '0901.11.31', description: 'Coffee not roasted: Robusta parchment: AB Grade' },
24+
{ code: '0901.11.32', description: 'Coffee not roasted: Robusta parchment: PB Grade' },
25+
{ code: '0901.11.41', description: 'Coffee not roasted: Robusta cherry: AB Grade' },
26+
{ code: '0901.12.00', description: 'Coffee roasted, not decaffeinated' },
27+
{ code: '2101.11.00', description: 'Extracts, essences and concentrates, of coffee' },
28+
{ code: '2101.11.10', description: 'Instant coffee, not flavoured' },
29+
{ code: '2101.11.20', description: 'Instant coffee, flavoured' },
30+
];
31+
32+
const brakePadCandidates = [
33+
{ code: '8708.30.00', description: 'Brakes and servo-brakes; parts thereof' },
34+
{ code: '8708.30.10', description: 'Brake pads for motor vehicles' },
35+
{ code: '6813.81.00', description: 'Brake linings and pads (friction material)' },
36+
{ code: '6913.10.00', description: 'Ceramic articles n.e.s.' },
37+
];
38+
39+
console.log('='.repeat(60));
40+
console.log('PHASE 3: Elimination Service Tests');
41+
console.log('='.repeat(60));
42+
43+
// Test 1: Extract modifiers from text
44+
console.log('\n--- Test 1: Extract Modifiers from Text ---');
45+
const arabicaModifiers = extractModifiersFromText('Arabica coffee beans');
46+
console.log(`"Arabica coffee beans" -> modifiers: [${arabicaModifiers.join(', ')}]`);
47+
48+
const robustaModifiers = extractModifiersFromText('Robusta coffee');
49+
console.log(`"Robusta coffee" -> modifiers: [${robustaModifiers.join(', ')}]`);
50+
51+
const instantModifiers = extractModifiersFromText('instant coffee');
52+
console.log(`"instant coffee" -> modifiers: [${instantModifiers.join(', ')}]`);
53+
54+
const brakePadModifiers = extractModifiersFromText('brake pads for cars');
55+
console.log(`"brake pads for cars" -> modifiers: [${brakePadModifiers.join(', ')}]`);
56+
57+
// Test 2: Filter Arabica coffee (should exclude Robusta)
58+
console.log('\n--- Test 2: Filter "Arabica coffee beans" ---');
59+
const arabicaResult = filterCandidatesByElimination(coffeeCandidates, {
60+
productTypeModifiers: ['arabica', 'beans'],
61+
originalQuery: 'Arabica coffee beans'
62+
});
63+
console.log(`Input: ${coffeeCandidates.length} candidates`);
64+
console.log(`Output: ${arabicaResult.filteredCodes.length} candidates`);
65+
console.log(`Eliminated: ${arabicaResult.eliminatedCount}`);
66+
console.log(`Applied rules: ${arabicaResult.appliedRules.join(', ')}`);
67+
console.log('Remaining codes:');
68+
arabicaResult.filteredCodes.forEach(c => console.log(` - ${c.code}: ${c.description.substring(0, 50)}...`));
69+
70+
// Verify: Should NOT contain Robusta
71+
const hasRobusta = arabicaResult.filteredCodes.some(c =>
72+
c.description.toLowerCase().includes('robusta')
73+
);
74+
console.log(`\nVERIFY: Contains Robusta? ${hasRobusta ? 'FAIL' : 'PASS'}`);
75+
76+
// Test 3: Filter Robusta coffee (should exclude Arabica)
77+
console.log('\n--- Test 3: Filter "Robusta coffee" ---');
78+
const robustaResult = filterCandidatesByElimination(coffeeCandidates, {
79+
productTypeModifiers: ['robusta'],
80+
originalQuery: 'Robusta coffee'
81+
});
82+
console.log(`Input: ${coffeeCandidates.length} candidates`);
83+
console.log(`Output: ${robustaResult.filteredCodes.length} candidates`);
84+
console.log(`Eliminated: ${robustaResult.eliminatedCount}`);
85+
console.log('Remaining codes:');
86+
robustaResult.filteredCodes.forEach(c => console.log(` - ${c.code}: ${c.description.substring(0, 50)}...`));
87+
88+
// Verify: Should NOT contain Arabica
89+
const hasArabica = robustaResult.filteredCodes.some(c =>
90+
c.description.toLowerCase().includes('arabica')
91+
);
92+
console.log(`\nVERIFY: Contains Arabica? ${hasArabica ? 'FAIL' : 'PASS'}`);
93+
94+
// Test 4: Filter instant coffee (should only be Ch.21)
95+
console.log('\n--- Test 4: Filter "instant coffee" ---');
96+
const instantResult = filterCandidatesByElimination(coffeeCandidates, {
97+
productTypeModifiers: ['instant'],
98+
originalQuery: 'instant coffee'
99+
});
100+
console.log(`Input: ${coffeeCandidates.length} candidates`);
101+
console.log(`Output: ${instantResult.filteredCodes.length} candidates`);
102+
console.log(`Eliminated: ${instantResult.eliminatedCount}`);
103+
console.log('Remaining codes:');
104+
instantResult.filteredCodes.forEach(c => console.log(` - ${c.code}: ${c.description.substring(0, 50)}...`));
105+
106+
// Verify: All remaining should be Ch.21
107+
const allCh21 = instantResult.filteredCodes.every(c => c.code.startsWith('21'));
108+
console.log(`\nVERIFY: All Ch.21? ${allCh21 ? 'PASS' : 'FAIL'}`);
109+
110+
// Test 5: Brake pads (should not eliminate relevant codes)
111+
console.log('\n--- Test 5: Filter "brake pads for cars" ---');
112+
const brakePadResult = filterCandidatesByElimination(brakePadCandidates, {
113+
productTypeModifiers: [],
114+
originalQuery: 'brake pads for cars'
115+
});
116+
console.log(`Input: ${brakePadCandidates.length} candidates`);
117+
console.log(`Output: ${brakePadResult.filteredCodes.length} candidates`);
118+
console.log(`Eliminated: ${brakePadResult.eliminatedCount}`);
119+
console.log('Remaining codes:');
120+
brakePadResult.filteredCodes.forEach(c => console.log(` - ${c.code}: ${c.description.substring(0, 50)}...`));
121+
122+
// Verify: Should contain 8708.30 codes
123+
const has8708 = brakePadResult.filteredCodes.some(c => c.code.startsWith('8708'));
124+
console.log(`\nVERIFY: Contains 8708 brake codes? ${has8708 ? 'PASS' : 'FAIL'}`);
125+
126+
// Test 6: Extract modifiers from answer
127+
console.log('\n--- Test 6: Extract Modifiers from Answer ---');
128+
const arabicaAnswer = extractModifiersFromAnswer({
129+
code: '0901.11.11',
130+
description: 'Coffee not roasted: Arabica plantation: A Grade'
131+
});
132+
console.log(`Answer "Arabica plantation A Grade" -> modifiers: [${arabicaAnswer.join(', ')}]`);
133+
134+
const robustaAnswer = extractModifiersFromAnswer({
135+
code: '0901.11.31',
136+
description: 'Coffee not roasted: Robusta parchment: AB Grade'
137+
});
138+
console.log(`Answer "Robusta parchment AB Grade" -> modifiers: [${robustaAnswer.join(', ')}]`);
139+
140+
// Test 7: Is question still relevant
141+
console.log('\n--- Test 7: Is Question Still Relevant ---');
142+
const arabicaOptions = [
143+
{ code: '0901.11.11', description: 'Arabica plantation A Grade' },
144+
{ code: '0901.11.12', description: 'Arabica plantation B Grade' },
145+
{ code: '0901.11.31', description: 'Robusta parchment AB Grade' },
146+
];
147+
const relevanceCheck = isQuestionStillRelevant(arabicaOptions, {
148+
productTypeModifiers: ['arabica'],
149+
originalQuery: 'Arabica coffee'
150+
});
151+
console.log(`With "arabica" modifier:`);
152+
console.log(` Relevant: ${relevanceCheck.relevant}`);
153+
console.log(` Remaining options: ${relevanceCheck.remainingOptions}`);
154+
if (relevanceCheck.autoSelectCode) {
155+
console.log(` Auto-select code: ${relevanceCheck.autoSelectCode}`);
156+
}
157+
158+
// Summary
159+
console.log('\n' + '='.repeat(60));
160+
console.log('SUMMARY');
161+
console.log('='.repeat(60));
162+
console.log(`Test 2 (Arabica excludes Robusta): ${!hasRobusta ? 'PASS' : 'FAIL'}`);
163+
console.log(`Test 3 (Robusta excludes Arabica): ${!hasArabica ? 'PASS' : 'FAIL'}`);
164+
console.log(`Test 4 (Instant -> Ch.21 only): ${allCh21 ? 'PASS' : 'FAIL'}`);
165+
console.log(`Test 5 (Brake pads -> 8708): ${has8708 ? 'PASS' : 'FAIL'}`);
166+
167+
const allPassed = !hasRobusta && !hasArabica && allCh21 && has8708;
168+
console.log(`\nOVERALL: ${allPassed ? 'ALL TESTS PASSED' : 'SOME TESTS FAILED'}`);

0 commit comments

Comments
 (0)