Skip to content

Commit 3924e43

Browse files
AryanBVclaude
andcommitted
feat: Phase 7.4.3 - Semantic classifier with material-aware search
Major improvements to HS code classification accuracy: Backend - New Services: - Add semantic-classifier.service.ts for embedding-based classification - Add query-term-analyzer.service.ts for intelligent query parsing - Add reranker.service.ts for function-over-material ranking - Add input-specificity.service.ts for query complexity analysis - Add attribute-classifier.service.ts for attribute-based classification Backend - Query Analysis: - Parse queries into product, variety, processing, material, packaging terms - Use fullQueryWithoutPackaging to retain material terms (cotton, silicone) - Exclude only packaging terms (1kg, bags, boxes) from search - Add LED, LCD, bluetooth, wifi and other electronic variety terms Backend - Classification Improvements: - Implement function-over-material reranking (apparel > fabric) - Add raw material code penalty (3910 silicone oil deprioritized) - Add dominant chapter detection for direct classification - Fix coffee classification (0901 not 0902 tea) - Fix cotton t-shirts classification (6109 direct, not questions) - Fix LED light bulbs classification (8539 direct, not questions) Frontend - New Wizard UI: - Add comprehensive wizard component system - Add history page with filtering and stats - Add classification tree visualization - Add loading states with educational facts - Add error handling with suggestions Testing: - Add comprehensive validation test suite (64 test cases) - Achieve 67% chapter accuracy, 0% error rate - Textiles category improved from 33% to 67% - Metals category improved from 60% to 100% 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent f8d6fbb commit 3924e43

70 files changed

Lines changed: 34971 additions & 695 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

backend/src/data/chapter-triggers.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,28 @@
662662
"keywords": ["footwear", "shoes", "boots", "sandals", "sneakers", "slippers"],
663663
"forceChapter": "64",
664664
"reason": "Footwear is always Ch.64 regardless of material"
665+
},
666+
{
667+
"keywords": [
668+
"LED light", "LED bulb", "LED lamp", "LED tube", "LED strip", "LED panel",
669+
"light bulb", "light bulbs", "electric lamp", "electric lamps",
670+
"discharge lamp", "fluorescent lamp", "halogen lamp", "incandescent lamp",
671+
"CFL bulb", "compact fluorescent", "filament lamp", "filament bulb",
672+
"household lamp", "household bulb", "lighting bulb", "lighting lamp"
673+
],
674+
"forceChapter": "85",
675+
"reason": "Electric lamps and light bulbs are Ch.85 (electrical equipment), specifically 8539 for lamps"
676+
},
677+
{
678+
"keywords": [
679+
"luminaire", "luminaires", "lighting fixture", "lighting fixtures",
680+
"ceiling light", "pendant light", "wall light", "chandelier",
681+
"spotlight", "spotlights", "floodlight", "floodlights",
682+
"table lamp", "floor lamp", "desk lamp", "reading lamp",
683+
"street light", "street lamp", "garden light", "outdoor light"
684+
],
685+
"forceChapter": "94",
686+
"reason": "Luminaires and lighting fixtures (complete units) are Ch.94 (9405), not just the bulb"
665687
}
666688
]
667689
},

backend/src/data/elimination-rules.json

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,5 +225,51 @@
225225
"exclude": ["flavored", "flavoured", "with flavor", "with flavour"],
226226
"description": "When user specifies unflavored, exclude flavored codes"
227227
}
228+
},
229+
"lampTypeExclusions": {
230+
"led": {
231+
"include": ["led", "led light", "led bulb", "led lamp", "led tube", "led panel", "led strip", "light-emitting diode", "light emitting diode"],
232+
"exclude": ["filament", "tungsten", "halogen", "incandescent", "discharge", "fluorescent", "arc lamp", "arc-lamp", "sealed beam", "infrared", "infra-red", "ultraviolet", "mercury", "sodium", "neon"],
233+
"description": "When user specifies LED, exclude other lamp types (filament, discharge, UV/IR)"
234+
},
235+
"filament": {
236+
"include": ["filament", "filament lamp", "filament bulb", "incandescent", "tungsten", "halogen", "halogen lamp", "halogen bulb"],
237+
"exclude": ["led", "light-emitting diode", "discharge", "fluorescent", "arc", "mercury", "sodium", "neon"],
238+
"description": "When user specifies filament/halogen/incandescent, exclude LED and discharge lamps"
239+
},
240+
"halogen": {
241+
"include": ["halogen", "halogen lamp", "halogen bulb", "tungsten halogen"],
242+
"exclude": ["led", "light-emitting diode", "discharge", "fluorescent", "arc", "standard filament"],
243+
"description": "When user specifies halogen, exclude LED and other lamp types"
244+
},
245+
"discharge": {
246+
"include": ["discharge", "discharge lamp", "fluorescent", "cfl", "compact fluorescent", "mercury", "sodium", "metal halide", "arc lamp", "arc-lamp"],
247+
"exclude": ["led", "light-emitting diode", "filament", "tungsten", "halogen", "incandescent"],
248+
"description": "When user specifies discharge/fluorescent, exclude LED and filament lamps"
249+
},
250+
"fluorescent": {
251+
"include": ["fluorescent", "fluorescent lamp", "fluorescent tube", "cfl", "compact fluorescent"],
252+
"exclude": ["led", "light-emitting diode", "filament", "tungsten", "halogen", "incandescent"],
253+
"description": "When user specifies fluorescent, exclude LED and filament lamps"
254+
},
255+
"uv": {
256+
"include": ["uv", "uv lamp", "ultraviolet", "ultra-violet", "uv light", "blacklight", "black light"],
257+
"exclude": ["led", "light-emitting diode", "filament", "halogen", "visible light", "household"],
258+
"description": "When user specifies UV, exclude visible light lamp types"
259+
},
260+
"infrared": {
261+
"include": ["infrared", "infra-red", "ir lamp", "heat lamp", "ir light"],
262+
"exclude": ["led", "light-emitting diode", "filament", "visible light", "household", "ultraviolet"],
263+
"description": "When user specifies infrared/IR, exclude visible light lamp types"
264+
}
265+
},
266+
"semanticAliases": {
267+
"description": "Semantic aliases for keyword matching - maps user terms to description terms",
268+
"led": ["led", "light-emitting diode", "light emitting diode", "led light", "led lamp"],
269+
"filament": ["filament", "tungsten", "incandescent"],
270+
"halogen": ["halogen", "tungsten halogen"],
271+
"discharge": ["discharge", "fluorescent", "mercury vapor", "sodium vapor"],
272+
"uv": ["ultraviolet", "ultra-violet", "uv"],
273+
"infrared": ["infrared", "infra-red", "ir"]
228274
}
229275
}

backend/src/routes/classify-conversational.routes.ts

Lines changed: 16 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77

88
import { Router, Request, Response } from 'express';
99
import { logger } from '../utils/logger';
10+
// PHASE 7.1: Use Semantic Search as PRIMARY classifier
11+
import { classifyWithSemanticSearch } from '../services/semantic-classifier.service';
12+
// Keep legacy imports for backwards compatibility on other endpoints
1013
import {
11-
classifyWithLLMNavigator,
1214
getConversation,
1315
abandonConversation,
1416
getConversationStats,
@@ -42,29 +44,21 @@ router.post('/', async (req: Request, res: Response): Promise<void> => {
4244
try {
4345
const { productDescription, sessionId, conversationId, answers } = req.body;
4446

45-
// Validation
46-
if (!sessionId) {
47-
res.status(400).json({
48-
success: false,
49-
error: 'Session ID is required',
50-
timestamp: new Date().toISOString()
51-
});
52-
return;
53-
}
54-
47+
// Validation - sessionId optional for semantic classifier
5548
if (!conversationId && !productDescription?.trim()) {
5649
res.status(400).json({
5750
success: false,
51+
responseType: 'error',
5852
error: 'Product description is required for new conversations',
5953
timestamp: new Date().toISOString()
6054
});
6155
return;
6256
}
6357

64-
logger.info(`[CLASSIFY] ${conversationId ? 'Continuing' : 'New'} conversation`);
58+
logger.info(`[CLASSIFY-SEMANTIC] ${conversationId ? 'Continuing' : 'New'} - "${productDescription?.substring(0, 50)}..."`);
6559

66-
// Call the LLM navigator classifier
67-
const result = await classifyWithLLMNavigator({
60+
// PHASE 7.1: Use Semantic Search as PRIMARY classifier
61+
const result = await classifyWithSemanticSearch({
6862
productDescription: productDescription || '',
6963
sessionId,
7064
conversationId,
@@ -73,7 +67,7 @@ router.post('/', async (req: Request, res: Response): Promise<void> => {
7367

7468
// Log performance
7569
const responseTime = Date.now() - startTime;
76-
logger.info(`[CLASSIFY] Response in ${responseTime}ms - type: ${result.responseType}`);
70+
logger.info(`[CLASSIFY-SEMANTIC] Response in ${responseTime}ms - type: ${result.responseType}`);
7771

7872
if (result.success) {
7973
res.status(200).json(result);
@@ -83,43 +77,36 @@ router.post('/', async (req: Request, res: Response): Promise<void> => {
8377

8478
} catch (error) {
8579
const errorMsg = error instanceof Error ? error.message : String(error);
86-
logger.error(`[CLASSIFY] Error: ${errorMsg}`);
80+
logger.error(`[CLASSIFY-SEMANTIC] Error: ${errorMsg}`);
8781

8882
res.status(500).json({
8983
success: false,
84+
responseType: 'error',
9085
error: 'Internal server error during classification',
9186
timestamp: new Date().toISOString()
9287
});
9388
}
9489
});
9590

96-
// Legacy /llm endpoint - redirects to main endpoint for backward compatibility
91+
// Legacy /llm endpoint - now also uses semantic classifier
9792
router.post('/llm', async (req: Request, res: Response): Promise<void> => {
98-
// Just forward to main handler
9993
const startTime = Date.now();
10094

10195
try {
10296
const { productDescription, sessionId, conversationId, answers } = req.body;
10397

104-
if (!sessionId) {
105-
res.status(400).json({
106-
success: false,
107-
error: 'Session ID is required',
108-
timestamp: new Date().toISOString()
109-
});
110-
return;
111-
}
112-
11398
if (!conversationId && !productDescription?.trim()) {
11499
res.status(400).json({
115100
success: false,
101+
responseType: 'error',
116102
error: 'Product description is required for new conversations',
117103
timestamp: new Date().toISOString()
118104
});
119105
return;
120106
}
121107

122-
const result = await classifyWithLLMNavigator({
108+
// PHASE 7.1: Use Semantic Search for /llm endpoint too
109+
const result = await classifyWithSemanticSearch({
123110
productDescription: productDescription || '',
124111
sessionId,
125112
conversationId,
@@ -141,6 +128,7 @@ router.post('/llm', async (req: Request, res: Response): Promise<void> => {
141128

142129
res.status(500).json({
143130
success: false,
131+
responseType: 'error',
144132
error: 'Internal server error during LLM classification',
145133
timestamp: new Date().toISOString()
146134
});

0 commit comments

Comments
 (0)