File size: 5,214 Bytes
eeb307d
6a25ae8
 
 
 
 
 
 
 
 
 
 
eeb307d
 
 
 
6a25ae8
 
 
 
eeb307d
 
73e67e0
 
 
6a25ae8
f86ee93
6a25ae8
eeb307d
 
 
 
 
 
 
6a25ae8
eeb307d
6a25ae8
 
eeb307d
 
 
 
6a25ae8
eeb307d
 
 
 
 
 
6a25ae8
eeb307d
6323066
66b7865
73e67e0
04cb628
6a25ae8
ae54989
f86ee93
 
524fa3e
6a25ae8
f86ee93
6a25ae8
505c8e4
 
 
 
 
 
 
 
 
f86ee93
 
eeb307d
f86ee93
 
6a25ae8
eeb307d
f86ee93
 
 
6a25ae8
eeb307d
f86ee93
04cb628
6a25ae8
f86ee93
 
ed54f93
6a25ae8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73e67e0
04cb628
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
/** engine/validator.js V94.1 - Syntax Fixed */
const fs = require('fs');
const path = require('path');

let productsDB = {};
try {
    productsDB = JSON.parse(fs.readFileSync(path.join(__dirname, '../db/products.json'), 'utf8'));
} catch (e) {
    console.error("Failed loading products.json in validator");
}
const VALID_PRODUCTS = new Set(Object.keys(productsDB));

const PRODUCT_KEYWORDS_MAP = {
    'ืคื•ืกื˜ืจ': 'poster', 'poster': 'poster',
    'ืคืœื™ื™ืจ': 'flyer', 'flyer': 'flyer',
    'ื—ื•ื‘ืจ': 'booklet', 'ืกืคืจ': 'booklet', 'ืงื˜ืœื•ื’': 'booklet', 'booklet': 'booklet',
    'ืžื“ื‘ืง': 'sticker', 'sticker': 'sticker',
    'ืกืงื•ื“ื™ืงืก': 'scodix', 'scodix': 'scodix', 'ืคื•ื™ืœ': 'scodix',
    'ืืœื•ืงื•ื‘ื•ื ื“': 'alucobond', 'alucobond': 'alucobond', 'ืฉืœื˜': 'alucobond',
    'ื’ืœื™ืœ': 'roll_stickers', 'roll_stickers': 'roll_stickers'
};

function validateLLMResult(llmResult, userText, session) {
    let result = { ...llmResult };
    const text = userText.toLowerCase();

    if (!result.mapped_params) result.mapped_params = {};

    // Product Detection Override
    const detectedProductsSet = new Set();
    Object.keys(PRODUCT_KEYWORDS_MAP).forEach(keyword => {
        if (text.includes(keyword)) {
            detectedProductsSet.add(PRODUCT_KEYWORDS_MAP[keyword]);
        }
    });

    const detectedProducts = Array.from(detectedProductsSet);
    const hasNumber = /\d/.test(text);

    if (detectedProducts.length > 0 && hasNumber) {
        if (result.intent !== 'quote' || !result.product) {
            console.log(`๐Ÿ›ก๏ธ [VALIDATOR] Force-Switching to QUOTE. Found: ${detectedProducts.join(', ')}`);  // โœ… ืชื•ืงืŸ!
            result.intent = 'quote';

            if (!result.product) {
                result.product = detectedProducts[0];
            }
        }
        result.allDetectedProducts = detectedProducts;
    }

    // ืฉืืจ ื”ืงื•ื“ ื ืฉืืจ ืื•ืชื• ื“ื‘ืจ...
    const updateKeywords = ["ืชืฉื ื”", "ืชื—ืœื™ืฃ", "ืขื“ื›ืŸ", "ื›ืžื•ืช", "ืœ-", "ืœืžืขืœื”", "ืœืžื˜ื”", "ื‘ืžืงื•ื", "update", "qty", "change", "replace", "fix"];
    if (updateKeywords.some(k => text.includes(k))) {
        result.intent = 'update';
    }

    const sizeMatch = text.match(/(\d+\.\d+|\d+)\s*(?:x|X|\*|ืขืœ)\s*(\d+\.\d+|\d+)/);
    if (sizeMatch) {
        result.mapped_params.size = `${sizeMatch[1]}x${sizeMatch[2]}`;
        console.log(`๐Ÿ›ก๏ธ [VALIDATOR] Detected Size with Decimals: ${result.mapped_params.size}`);
        if (result.intent === 'chat') result.intent = 'update';
    }

    // --- PHASE 1.3 UAT Hotfix: Alucobond vs Office Intercept ---
    if (text.includes('ืืœื•ืงื•ื‘ื•ื ื“') || text.includes('alucobond')) {
        if (result.product === 'office') {
            console.log(`\x1b[33m๐Ÿ›ก๏ธ [X-RAY VALIDATOR] Intercepted LLM hallucination: Swapped 'office' for 'alucobond'\x1b[0m`);
            result.product = 'alucobond';
            result.intent = 'quote';
        }
    }

    if (text.includes('ื›ืจื•ืžื•')) {
        if (text.includes('300')) result.mapped_params.paper_type = 'chromo_300';
        else result.mapped_params.paper_type = 'chromo_130';
        if (result.intent === 'chat') result.intent = 'update';
    }

    if (text.includes('ืžื˜')) {
        result.mapped_params.paper_type = 'matte_350';
        if (result.intent === 'chat') result.intent = 'update';
    }

    if (text.includes("ื‘ืœื™") || text.includes("ืœืœื")) {
        if (result.intent === 'chat') result.intent = 'update';
    }

    if (/^\d+$/.test(text.trim()) && result.intent === 'chat') {
        result.intent = 'update';
    }

    // --- PHASE 1.3 Anti-Hallucination: Confidence Gate ---
    if (result.confidence !== undefined && result.confidence < 0.85) {
        console.log(`\x1b[31m๐Ÿ›ก๏ธ [X-RAY VALIDATOR] Confidence Gate Blocked: ${result.confidence} is below 0.85 threshold.\x1b[0m`);
        result.intent = 'chat';
        result.product = null;
        result.answer_text = "ืื ื™ ืœื ืœื’ืžืจื™ ื‘ื˜ื•ื— ื‘ื›ื•ื•ื ืชืš, ืืคืฉืจ ืœื ืกื— ืฉื•ื‘?";
    }

    // --- PHASE 1.3 Anti-Hallucination: Product Whitelist ---
    if (result.product && !VALID_PRODUCTS.has(result.product)) {
        console.log(`\x1b[31m๐Ÿ›ก๏ธ [X-RAY VALIDATOR] Product Whitelist Blocked: '${result.product}' does not exist in DB.\x1b[0m`);
        result.intent = 'chat';
        result.product = null;
        result.answer_text = "ืœืฆืขืจื™ ืื™ืŸ ืœื ื• ืืช ื”ืžื•ืฆืจ ื”ื–ื” ื›ืจื’ืข ื‘ืžืขืจื›ืช. ืœื—ื–ื•ืจ ืœืชืคืจื™ื˜?";
    }

    // --- PHASE 1.3 Anti-Hallucination: Quantity Clamp ---
    if (result.mapped_params && result.mapped_params.qty !== undefined) {
        const rawQty = parseInt(result.mapped_params.qty);
        if (!isNaN(rawQty)) {
            // ื”ื’ื‘ืœืช ืžื™ื ื™ืžื•ื ื•ืžืงืกื™ืžื•ื ืื’ืจืกื™ื‘ื™ืช (1 ืขื“ 100,000)
            const clampedQty = Math.max(1, Math.min(rawQty, 100000));
            if (rawQty !== clampedQty) {
                console.log(`\x1b[33m๐Ÿ›ก๏ธ [X-RAY VALIDATOR] Quantity Clamped from ${rawQty} to ${clampedQty}.\x1b[0m`);
            }
            result.mapped_params.qty = clampedQty;
        }
    }

    return result;
}

module.exports = { validateLLMResult };