// ========================================== // MEEZAN - ميزان Knowledge Gap Tracker // Identifies what Meezan doesn't know well // and prioritizes what to learn next // ========================================== import * as fs from 'fs'; import * as path from 'path'; import { KnowledgeGap } from '../types'; // Topic patterns to detect knowledge domains const TOPIC_PATTERNS: Array<{ topic: string; topicAr: string; category: KnowledgeGap['category']; patterns: string[]; }> = [ // Fiqh topics { topic: 'Inheritance (Faraidh)', topicAr: 'الميراث والفرائض', category: 'fiqh', patterns: ['ميراث', 'فرائض', 'وصية', 'inheritance', 'faraidh', 'تركة'] }, { topic: 'Islamic Marriage (Nikah)', topicAr: 'النكاح والزواج', category: 'fiqh', patterns: ['نكاح', 'زواج', 'مهر', 'طلاق', 'marriage', 'nikah', 'divorce', 'khul'] }, { topic: 'Halal Food Details', topicAr: 'تفاصيل الأطعمة الحلال', category: 'fiqh', patterns: ['ذبح', 'تذكية', 'مستفسر', 'slaughter', 'zabiha', 'gelatin', 'rennet'] }, { topic: 'Business Ethics in Islam', topicAr: 'أخلاقيات التجارة الإسلامية', category: 'fiqh', patterns: ['غش', 'تدليس', 'عقد', 'بيع', 'شراء', 'fraud', 'contract', 'gharar'] }, // Islamic Finance { topic: 'Sukuk Details', topicAr: 'تفاصيل الصكوك', category: 'finance', patterns: ['صكوك', 'sukuk', 'islamic bond', 'صك'] }, { topic: 'Islamic Insurance (Takaful)', topicAr: 'التأمين الإسلامي', category: 'finance', patterns: ['تكافل', 'تأمين', 'takaful', 'insurance'] }, { topic: 'Cryptocurrency in Islam', topicAr: 'العملات الرقمية في الإسلام', category: 'finance', patterns: ['بيتكوين', 'كريبتو', 'عملة رقمية', 'bitcoin', 'crypto', 'nft', 'defi', 'web3'] }, { topic: 'Kuwait Stock Exchange', topicAr: 'بورصة الكويت', category: 'finance', patterns: ['بورصة الكويت', 'boursa kuwait', 'كويت للأسهم', 'kuwaitstock', 'tadawul'] }, // Kuwait Government { topic: 'Kuwait Visa Process', topicAr: 'إجراءات التأشيرة الكويتية', category: 'government', patterns: ['تأشيرة', 'visa', 'iqama', 'إقامة', 'residence permit', 'work permit'] }, { topic: 'Kuwait Labor Law', topicAr: 'قانون العمل الكويتي', category: 'government', patterns: ['عمالة', 'labor', 'employment', 'عقد عمل', 'راتب', 'فصل', 'resignation'] }, { topic: 'Kuwait VAT / Tax', topicAr: 'الضرائب الكويتية', category: 'government', patterns: ['ضريبة', 'vat', 'tax', 'زكاة الشركات', 'corporate tax'] }, // Culture { topic: 'Ramadan Practices', topicAr: 'أحكام رمضان', category: 'culture', patterns: ['رمضان', 'صيام', 'إفطار', 'سحور', 'fasting', 'ramadan', 'iftar', 'suhoor'] }, { topic: 'Hajj and Umrah', topicAr: 'الحج والعمرة', category: 'culture', patterns: ['حج', 'عمرة', 'مكة', 'hajj', 'umrah', 'mecca', 'ihram'] }, { topic: 'GCC Cultural Etiquette', topicAr: 'الآداب الخليجية', category: 'culture', patterns: ['عادات', 'تقاليد', 'آداب', 'culture', 'etiquette', 'customs', 'خليج'] }, ]; export class KnowledgeGapTracker { private dbPath: string; private gaps: Map; constructor(dbPath: string = './data/knowledge_gaps.json') { this.dbPath = dbPath; this.gaps = new Map(); this.load(); } /** * Analyze a conversation exchange and detect potential knowledge gaps */ analyzeExchange( userMessage: string, assistantResponse: string, wasConfused: boolean, wasNegative: boolean ): KnowledgeGap[] { const detectedGaps: KnowledgeGap[] = []; if (!wasConfused && !wasNegative) return detectedGaps; const combined = (userMessage + ' ' + assistantResponse).toLowerCase(); for (const topicDef of TOPIC_PATTERNS) { const matches = topicDef.patterns.filter(p => combined.includes(p.toLowerCase())); if (matches.length > 0) { const gapId = `gap_${topicDef.topic.replace(/\s+/g, '_').toLowerCase()}`; const existing = this.gaps.get(gapId); if (existing) { // Increment frequency existing.frequency++; existing.lastSeen = new Date().toISOString(); if (!existing.exampleQuestions.includes(userMessage)) { existing.exampleQuestions = [...existing.exampleQuestions.slice(-4), userMessage]; } // Upgrade priority if frequent enough if (existing.frequency >= 10 && existing.priority !== 'high') { existing.priority = 'high'; } else if (existing.frequency >= 5 && existing.priority === 'low') { existing.priority = 'medium'; } this.gaps.set(gapId, existing); detectedGaps.push(existing); } else { // New gap detected const newGap: KnowledgeGap = { id: gapId, topic: topicDef.topic, topicAr: topicDef.topicAr, category: topicDef.category, frequency: 1, firstSeen: new Date().toISOString(), lastSeen: new Date().toISOString(), exampleQuestions: [userMessage], status: 'identified', priority: 'low' }; this.gaps.set(gapId, newGap); detectedGaps.push(newGap); } } } if (detectedGaps.length > 0) this.save(); return detectedGaps; } /** * Get top priority knowledge gaps to address */ getTopGaps(limit: number = 5): KnowledgeGap[] { const allGaps = Array.from(this.gaps.values()) .filter(g => g.status !== 'resolved'); // Sort by priority then frequency const priorityScore = (g: KnowledgeGap) => { const p = g.priority === 'high' ? 3 : g.priority === 'medium' ? 2 : 1; return p * 100 + g.frequency; }; return allGaps .sort((a, b) => priorityScore(b) - priorityScore(a)) .slice(0, limit); } /** * Mark a gap as resolved with a resolution note */ resolveGap(gapId: string, resolution: string): void { const gap = this.gaps.get(gapId); if (gap) { gap.status = 'resolved'; gap.resolution = resolution; this.gaps.set(gapId, gap); this.save(); } } /** * Get summary stats for the learning dashboard */ getStats(): { total: number; high: number; medium: number; low: number; resolved: number; topTopics: string[]; } { const all = Array.from(this.gaps.values()); return { total: all.length, high: all.filter(g => g.priority === 'high' && g.status !== 'resolved').length, medium: all.filter(g => g.priority === 'medium' && g.status !== 'resolved').length, low: all.filter(g => g.priority === 'low' && g.status !== 'resolved').length, resolved: all.filter(g => g.status === 'resolved').length, topTopics: this.getTopGaps(3).map(g => g.topicAr) }; } getAllGaps(): KnowledgeGap[] { return Array.from(this.gaps.values()); } private load(): void { const dir = path.dirname(this.dbPath); if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); if (fs.existsSync(this.dbPath)) { try { const data = JSON.parse(fs.readFileSync(this.dbPath, 'utf-8')); for (const gap of data) { this.gaps.set(gap.id, gap); } } catch { /* start fresh */ } } } private save(): void { const data = Array.from(this.gaps.values()); fs.writeFileSync(this.dbPath, JSON.stringify(data, null, 2), 'utf-8'); } } export const knowledgeGapTracker = new KnowledgeGapTracker( process.env.DB_PATH?.replace('memory.json', 'knowledge_gaps.json') || './data/knowledge_gaps.json' );