Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
197 changes: 197 additions & 0 deletions __tests__/detect-programmer.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
const { detectProgrammer, programmingKeywords } = require('../detect-programmer');

describe('detectProgrammer', () => {
test('should detect programmer from programming keywords in Russian', () => {
const messages = [
{ id: 1, text: 'Привет! Я занимаюсь программированием на Python' },
{ id: 2, text: 'Изучаю React и Node.js' },
{ id: 3, text: 'Работаю разработчиком' },
];

const result = detectProgrammer(messages);

expect(result.isProgrammer).toBe(true);
expect(result.confidence).toBeGreaterThan(0);
expect(result.indicators.length).toBeGreaterThan(0);
expect(result.stats.totalMessages).toBe(3);
});

test('should detect programmer from programming keywords in English', () => {
const messages = [
{ id: 1, text: 'I am a software engineer' },
{ id: 2, text: 'Working with JavaScript and TypeScript' },
{ id: 3, text: 'Love coding in Python' },
];

const result = detectProgrammer(messages);

expect(result.isProgrammer).toBe(true);
expect(result.confidence).toBeGreaterThan(0);
expect(result.indicators.length).toBeGreaterThan(0);
});

test('should detect programmer from code patterns', () => {
const messages = [
{ id: 1, text: 'function test() { return true; }' },
{ id: 2, text: 'const myVar = 123;' },
{ id: 3, text: 'let x = await getData();' },
];

const result = detectProgrammer(messages);

expect(result.isProgrammer).toBe(true);
expect(result.confidence).toBeGreaterThan(0);
expect(result.stats.codePatternMatches).toBeGreaterThan(0);
});

test('should detect programmer from technology mentions', () => {
const messages = [
{ id: 1, text: 'Запускаю docker контейнер' },
{ id: 2, text: 'Использую git для версионирования' },
{ id: 3, text: 'Деплою на kubernetes' },
{ id: 4, text: 'Работаю с PostgreSQL базой данных' },
];

const result = detectProgrammer(messages);

expect(result.isProgrammer).toBe(true);
expect(result.confidence).toBeGreaterThan(0);
});

test('should not detect programmer from general conversation', () => {
const messages = [
{ id: 1, text: 'Привет! Как дела?' },
{ id: 2, text: 'Что делаешь сегодня?' },
{ id: 3, text: 'Пойдём гулять?' },
{ id: 4, text: 'Хорошая погода' },
{ id: 5, text: 'Как твои дела?' },
];

const result = detectProgrammer(messages);

expect(result.isProgrammer).toBe(false);
expect(result.confidence).toBe(0);
expect(result.indicators.length).toBe(0);
});

test('should handle empty messages array', () => {
const messages = [];

const result = detectProgrammer(messages);

expect(result.isProgrammer).toBe(false);
expect(result.confidence).toBe(0);
expect(result.indicators.length).toBe(0);
});

test('should handle null messages', () => {
const result = detectProgrammer(null);

expect(result.isProgrammer).toBe(false);
expect(result.confidence).toBe(0);
});

test('should handle messages without text', () => {
const messages = [
{ id: 1 },
{ id: 2, text: null },
{ id: 3, text: '' },
];

const result = detectProgrammer(messages);

expect(result.isProgrammer).toBe(false);
});

test('should detect programmer with mixed programming and casual messages', () => {
const messages = [
{ id: 1, text: 'Привет!' },
{ id: 2, text: 'Работаю над API на Node.js' },
{ id: 3, text: 'Как дела?' },
{ id: 4, text: 'Изучаю алгоритмы' },
{ id: 5, text: 'Спасибо!' },
];

const result = detectProgrammer(messages);

expect(result.isProgrammer).toBe(true);
expect(result.stats.totalMessages).toBe(5);
expect(result.stats.keywordMatches).toBeGreaterThan(0);
});

test('should detect programmer from framework mentions', () => {
const messages = [
{ id: 1, text: 'Использую React для фронтенда' },
{ id: 2, text: 'Django отличный фреймворк' },
{ id: 3, text: 'Express.js для сервера' },
];

const result = detectProgrammer(messages);

expect(result.isProgrammer).toBe(true);
expect(result.confidence).toBeGreaterThan(0);
});

test('should detect programmer from GitHub/Git mentions', () => {
const messages = [
{ id: 1, text: 'Залил коммит на GitHub' },
{ id: 2, text: 'Сделал пулреквест' },
{ id: 3, text: 'Смержил ветку' },
];

const result = detectProgrammer(messages);

expect(result.isProgrammer).toBe(true);
});

test('should have correct confidence calculation', () => {
const messages = [
{ id: 1, text: 'Я программист' },
{ id: 2, text: 'Пишу код на JavaScript' },
{ id: 3, text: 'function test() {}' },
];

const result = detectProgrammer(messages);

expect(result.isProgrammer).toBe(true);
expect(result.confidence).toBeGreaterThanOrEqual(0);
expect(result.confidence).toBeLessThanOrEqual(100);
expect(result.stats.matchRatio).toBeGreaterThan(0);
});

test('should limit indicators to top 10', () => {
const messages = [];
for (let i = 0; i < 20; i++) {
messages.push({ id: i, text: 'I am a programmer working with JavaScript' });
}

const result = detectProgrammer(messages);

expect(result.isProgrammer).toBe(true);
expect(result.indicators.length).toBeLessThanOrEqual(10);
});

test('should detect from LeetCode/HackerRank mentions', () => {
const messages = [
{ id: 1, text: 'Решаю задачи на LeetCode' },
{ id: 2, text: 'Прошёл тест на HackerRank' },
];

const result = detectProgrammer(messages);

expect(result.isProgrammer).toBe(true);
});

test('should be case insensitive', () => {
const messages = [
{ id: 1, text: 'JAVASCRIPT' },
{ id: 2, text: 'PyThOn' },
{ id: 3, text: 'ReAcT' },
];

const result = detectProgrammer(messages);

expect(result.isProgrammer).toBe(true);
expect(result.stats.keywordMatches).toBeGreaterThan(0);
});
});
121 changes: 121 additions & 0 deletions detect-programmer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// Programming-related keywords and patterns to detect programmers
const programmingKeywords = [
// Programming languages
'javascript', 'python', 'java', 'c++', 'c#', 'php', 'ruby', 'go', 'rust', 'swift',
'kotlin', 'typescript', 'scala', 'perl', 'haskell', 'dart', 'elixir', 'clojure',

// Technologies and frameworks
'react', 'angular', 'vue', 'node', 'django', 'flask', 'spring', 'laravel',
'express', 'fastapi', 'rails', 'asp.net', 'nextjs', 'nuxt', 'svelte',

// Development concepts
'api', 'backend', 'frontend', 'fullstack', 'database', 'sql', 'nosql',
'mongodb', 'postgresql', 'mysql', 'redis', 'docker', 'kubernetes',
'git', 'github', 'gitlab', 'bitbucket', 'ci/cd', 'devops',
'algorithm', 'data structure', 'leetcode', 'hackerrank',

// Programming terms (Russian)
'программирование', 'программист', 'разработчик', 'разработка',
'код', 'кодинг', 'баг', 'дебаг', 'репозиторий', 'коммит',
'пулреквест', 'деплой', 'бэкенд', 'фронтенд', 'фулстек',

// Programming terms (English)
'programming', 'programmer', 'developer', 'development', 'coding',
'code', 'bug', 'debug', 'repository', 'commit', 'pull request',
'deploy', 'deployment', 'software', 'engineer', 'engineering',

// Common development phrases
'npm install', 'pip install', 'yarn add', 'import ', 'require(',
'function ', 'const ', 'let ', 'var ', 'class ', 'async ', 'await ',
];

// Regular expressions for code patterns
const codePatterns = [
/\bfunction\s+\w+\s*\(/i,
/\bconst\s+\w+\s*=/i,
/\blet\s+\w+\s*=/i,
/\bvar\s+\w+\s*=/i,
/\bimport\s+.*\s+from\s+/i,
/\brequire\s*\(/i,
/\bclass\s+\w+/i,
/\basync\s+function/i,
/=>/, // Arrow functions
/\{\s*\.\.\.\w+\s*\}/, // Spread operator
/\w+\.\w+\(.*\)/, // Method calls
/\/\/ .+/, // Single-line comments
/\/\*.+\*\//, // Multi-line comments
/```[\s\S]*```/, // Code blocks in markdown
];

/**
* Detects if a person is a programmer based on their message history
* @param {Array} messages - Array of message objects from VK API
* @returns {Object} - { isProgrammer: boolean, confidence: number, indicators: Array }
*/
function detectProgrammer(messages) {
if (!messages || messages.length === 0) {
return { isProgrammer: false, confidence: 0, indicators: [] };
}

const indicators = [];
let keywordMatches = 0;
let codePatternMatches = 0;

for (const message of messages) {
const text = message.text?.toLowerCase() || '';

// Skip very short messages
if (text.length < 3) {
continue;
}

// Check for programming keywords
for (const keyword of programmingKeywords) {
if (text.includes(keyword.toLowerCase())) {
keywordMatches++;
indicators.push({ type: 'keyword', value: keyword, messageId: message.id });
break; // Count only once per message
}
}

// Check for code patterns
for (const pattern of codePatterns) {
if (pattern.test(message.text || '')) {
codePatternMatches++;
indicators.push({ type: 'pattern', value: pattern.toString(), messageId: message.id });
break; // Count only once per message
}
}
}

// Calculate confidence based on matches
const totalMatches = keywordMatches + (codePatternMatches * 2); // Code patterns weighted more
const messagesChecked = messages.length;
const matchRatio = totalMatches / messagesChecked;

// Determine if programmer
// If at least 3 matches or 5% of messages contain programming content
const isProgrammer = totalMatches >= 3 || matchRatio >= 0.05;

// Confidence score (0-100)
const confidence = Math.min(100, Math.round(matchRatio * 1000 + totalMatches * 10));

return {
isProgrammer,
confidence,
indicators: indicators.slice(0, 10), // Return top 10 indicators
stats: {
totalMessages: messagesChecked,
keywordMatches,
codePatternMatches,
totalMatches,
matchRatio: Math.round(matchRatio * 10000) / 100, // Percentage
}
};
}

module.exports = {
detectProgrammer,
programmingKeywords,
codePatterns,
};
Loading