meezan / src /core /knowledgeGaps.ts
agenthinkmesh's picture
Upload project files
de07704 verified
Raw History Blame Contribute Delete
8.02 kB
// ==========================================
// 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<string, KnowledgeGap>;
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'
);