File size: 14,992 Bytes
172ee17
 
 
 
 
 
 
53fe915
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172ee17
5071500
172ee17
53fe915
172ee17
 
 
 
 
 
 
 
 
 
 
 
 
 
53fe915
172ee17
 
 
 
 
53fe915
172ee17
53fe915
 
172ee17
53fe915
 
5071500
172ee17
53fe915
172ee17
 
 
 
 
 
 
 
53fe915
172ee17
 
 
 
53fe915
 
172ee17
53fe915
 
 
 
 
172ee17
 
 
53fe915
 
172ee17
 
 
53fe915
172ee17
 
 
 
 
 
53fe915
172ee17
 
 
53fe915
 
 
 
 
 
 
 
 
 
172ee17
 
53fe915
172ee17
 
 
 
 
 
 
53fe915
 
 
 
 
 
 
 
 
 
172ee17
 
53fe915
172ee17
 
 
 
 
 
 
 
 
 
53fe915
172ee17
 
 
 
 
 
 
 
 
 
53fe915
172ee17
 
 
 
 
 
 
 
 
 
53fe915
172ee17
 
 
 
 
53fe915
172ee17
53fe915
172ee17
 
 
 
 
53fe915
172ee17
 
 
 
53fe915
172ee17
 
 
 
 
 
 
53fe915
172ee17
 
 
 
 
 
 
 
 
 
 
 
53fe915
172ee17
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53fe915
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172ee17
 
 
 
53fe915
172ee17
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53fe915
 
172ee17
 
 
53fe915
 
 
172ee17
 
 
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
import re
import hashlib
from typing import List, Dict

class DataNormalizer:
    def __init__(self):
        self.tag_keywords = {
            'ml': ['машинное обучение', 'machine learning', 'ml', 'алгоритм', 'модель', 'классификация', 'регрессия'],
            'dl': ['глубокое обучение', 'deep learning', 'нейронная сеть', 'cnn', 'rnn', 'transformer', 'нейросеть'],
            'nlp': ['nlp', 'обработка естественного языка', 'natural language', 'текст', 'язык', 'токенизация'],
            'cv': ['компьютерное зрение', 'computer vision', 'cv', 'изображение', 'видео', 'детекция', 'сегментация'],
            'math': ['математика', 'математический', 'алгебра', 'геометрия', 'анализ', 'линейная алгебра', 'статистика'],
            'stats': ['статистика', 'вероятность', 'статистический', 'probability', 'теория вероятностей'],
            'product': ['продукт', 'product', 'разработка продукта', 'продуктовая', 'аналитика'],
            'business': ['бизнес', 'business', 'менеджмент', 'управление', 'экономика', 'маркетинг'],
            'pm': ['project management', 'управление проектами', 'pm', 'проект', 'agile', 'scrum'],
            'systems': ['система', 'system', 'архитектура', 'инфраструктура', 'разработка'],
            'data': ['данные', 'data', 'анализ данных', 'big data', 'база данных', 'sql', 'nosql'],
            'research': ['исследование', 'research', 'наука', 'научный', 'диссертация', 'магистерская'],
            'python': ['python', 'питон', 'программирование'],
            'java': ['java', 'джава', 'программирование'],
            'sql': ['sql', 'база данных', 'database'],
            'git': ['git', 'версионирование', 'контроль версий'],
            'docker': ['docker', 'контейнеризация', 'контейнер'],
            'aws': ['aws', 'amazon', 'облако', 'cloud'],
            'tensorflow': ['tensorflow', 'tf', 'фреймворк'],
            'pytorch': ['pytorch', 'torch', 'фреймворк'],
            'scikit-learn': ['scikit-learn', 'sklearn', 'библиотека']
        }
    
    def normalize_courses(self, courses: List[Dict]) -> List[Dict]:
        """Нормализует список курсов"""
        normalized_courses = []
        seen_hashes = set()
        
        for course in courses:
            normalized = self._normalize_course(course)
            if normalized:
                course_hash = self._calculate_course_hash(normalized)
                if course_hash not in seen_hashes:
                    seen_hashes.add(course_hash)
                    normalized_courses.append(normalized)
        
        return normalized_courses
    
    def _normalize_course(self, course: Dict) -> Dict:
        """Нормализует отдельный курс"""
        if not course.get('name'):
            return None
        
        normalized = course.copy()
        
        # Нормализация названия
        normalized['name'] = self._normalize_name(course['name'])
        
        # Генерация короткого описания
        normalized['short_desc'] = self._generate_short_desc(course)
        
        # Генерация тегов
        normalized['tags'] = self._generate_tags(course)
        
        # Нормализация числовых полей
        normalized['semester'] = self._normalize_semester(course.get('semester', 1))
        normalized['credits'] = self._normalize_credits(course.get('credits', 0))
        normalized['hours'] = self._normalize_hours(course.get('hours', 0))
        normalized['type'] = self._normalize_type(course.get('type', 'required'))
        
        return normalized
    
    def _normalize_name(self, name: str) -> str:
        """Нормализует название курса"""
        if not name:
            return ''
        
        name = str(name).strip()
        
        # Удаляем лишние пробелы и символы
        name = re.sub(r'\s+', ' ', name)
        name = name.replace('"', '').replace('"', '').replace('«', '').replace('»', '')
        
        # Убираем лишние скобки и символы
        name = re.sub(r'^\s*[\(\)\[\]\-\s]+', '', name)
        name = re.sub(r'[\(\)\[\]\-\s]+\s*$', '', name)
        
        return name
    
    def _generate_short_desc(self, course: Dict) -> str:
        """Генерирует короткое описание курса"""
        name = course.get('name', '')
        desc = course.get('description', '')
        
        # Если есть описание, используем его
        if desc:
            desc = str(desc).strip()
            if len(desc) > 220:
                desc = desc[:220] + '...'
            return desc
        
        # Если название длинное, используем его как описание
        if name and len(name) > 50:
            return name[:220]
        
        # Генерируем базовое описание
        program_id = course.get('program_id', '')
        semester = course.get('semester', 1)
        
        if program_id == 'ai':
            return f'Курс программы "Искусственный интеллект" ({semester} семестр)'
        elif program_id == 'ai_product':
            return f'Курс программы "AI Product Management" ({semester} семестр)'
        else:
            return f'Курс из учебного плана программы ({semester} семестр)'
    
    def _generate_tags(self, course: Dict) -> List[str]:
        """Генерирует теги для курса"""
        text = f"{course.get('name', '')} {course.get('short_desc', '')}".lower()
        tags = []
        
        for tag, keywords in self.tag_keywords.items():
            if any(keyword in text for keyword in keywords):
                tags.append(tag)
        
        # Добавляем теги на основе программы
        program_id = course.get('program_id', '')
        if program_id == 'ai':
            if 'ml' not in tags:
                tags.append('ml')
        elif program_id == 'ai_product':
            if 'product' not in tags:
                tags.append('product')
        
        return list(set(tags))  # Убираем дубликаты
    
    def _normalize_semester(self, semester) -> int:
        """Нормализует номер семестра"""
        try:
            semester = int(semester)
            if 1 <= semester <= 4:
                return semester
        except (ValueError, TypeError):
            pass
        
        return 1
    
    def _normalize_credits(self, credits) -> int:
        """Нормализует количество кредитов"""
        try:
            credits = int(credits)
            if credits >= 0:
                return credits
        except (ValueError, TypeError):
            pass
        
        return 0
    
    def _normalize_hours(self, hours) -> int:
        """Нормализует количество часов"""
        try:
            hours = int(hours)
            if hours >= 0:
                return hours
        except (ValueError, TypeError):
            pass
        
        return 0
    
    def _normalize_type(self, course_type: str) -> str:
        """Нормализует тип курса"""
        if not course_type:
            return 'required'
        
        type_lower = str(course_type).lower()
        
        if any(word in type_lower for word in ['обязательная', 'required', 'обяз', 'базовая']):
            return 'required'
        elif any(word in type_lower for word in ['по выбору', 'elective', 'выбор', 'электив', 'факультатив']):
            return 'elective'
        
        return 'required'
    
    def _calculate_course_hash(self, course: Dict) -> str:
        """Вычисляет хэш курса для дедупликации"""
        text = f"{course.get('name', '')}{course.get('program_id', '')}{course.get('semester', '')}"
        return hashlib.md5(text.encode()).hexdigest()
    
    def merge_courses(self, courses_list: List[List[Dict]]) -> List[Dict]:
        """Объединяет несколько списков курсов"""
        all_courses = []
        for courses in courses_list:
            all_courses.extend(courses)
        
        return self.normalize_courses(all_courses)
    
    def validate_course(self, course: Dict) -> bool:
        """Проверяет валидность курса"""
        required_fields = ['name', 'program_id', 'semester']
        
        for field in required_fields:
            if not course.get(field):
                return False
        
        if len(course.get('name', '')) < 3:
            return False
        
        return True
    
    def get_statistics(self, courses: List[Dict]) -> Dict:
        """Получает статистику по курсам"""
        stats = {
            'total_courses': len(courses),
            'by_program': {},
            'by_semester': {},
            'by_type': {},
            'by_tags': {}
        }
        
        for course in courses:
            program_id = course.get('program_id', 'unknown')
            semester = course.get('semester', 1)
            course_type = course.get('type', 'required')
            tags = course.get('tags', [])
            
            stats['by_program'][program_id] = stats['by_program'].get(program_id, 0) + 1
            stats['by_semester'][semester] = stats['by_semester'].get(semester, 0) + 1
            stats['by_type'][course_type] = stats['by_type'].get(course_type, 0) + 1
            
            for tag in tags:
                stats['by_tags'][tag] = stats['by_tags'].get(tag, 0) + 1
        
        return stats
    
    def enrich_courses(self, courses: List[Dict]) -> List[Dict]:
        """Обогащает курсы дополнительной информацией"""
        for course in courses:
            # Добавляем сложность курса
            course['difficulty'] = self._calculate_difficulty(course)
            
            # Добавляем рекомендуемый опыт
            course['recommended_experience'] = self._calculate_recommended_experience(course)
            
            # Добавляем категорию
            course['category'] = self._determine_category(course)
        
        return courses
    
    def _calculate_difficulty(self, course: Dict) -> str:
        """Вычисляет сложность курса"""
        name = course.get('name', '').lower()
        credits = course.get('credits', 0)
        semester = course.get('semester', 1)
        
        # По ключевым словам
        if any(word in name for word in ['продвинутый', 'advanced', 'углубленный']):
            return 'advanced'
        elif any(word in name for word in ['базовый', 'basic', 'введение', 'вводный']):
            return 'beginner'
        
        # По кредитам и семестру
        if credits >= 6 or semester >= 3:
            return 'intermediate'
        elif credits <= 3 and semester <= 2:
            return 'beginner'
        else:
            return 'intermediate'
    
    def _calculate_recommended_experience(self, course: Dict) -> Dict:
        """Вычисляет рекомендуемый опыт для курса"""
        difficulty = course.get('difficulty', 'intermediate')
        tags = course.get('tags', [])
        
        experience = {
            'programming': 1,
            'math': 1,
            'ml': 0
        }
        
        if difficulty == 'advanced':
            experience['programming'] = 4
            experience['math'] = 3
        elif difficulty == 'intermediate':
            experience['programming'] = 2
            experience['math'] = 2
        else:  # beginner
            experience['programming'] = 1
            experience['math'] = 1
        
        # Корректировка по тегам
        if 'ml' in tags or 'dl' in tags:
            experience['ml'] = max(experience['ml'], 1)
        if 'math' in tags or 'stats' in tags:
            experience['math'] = max(experience['math'], 2)
        if 'python' in tags or 'java' in tags:
            experience['programming'] = max(experience['programming'], 2)
        
        return experience
    
    def _determine_category(self, course: Dict) -> str:
        """Определяет категорию курса"""
        tags = course.get('tags', [])
        name = course.get('name', '').lower()
        
        if any(tag in tags for tag in ['ml', 'dl', 'nlp', 'cv']):
            return 'ai_core'
        elif any(tag in tags for tag in ['product', 'business', 'pm']):
            return 'product_management'
        elif any(tag in tags for tag in ['math', 'stats']):
            return 'mathematics'
        elif any(tag in tags for tag in ['systems', 'data']):
            return 'systems_data'
        elif 'research' in tags or 'диссертация' in name:
            return 'research'
        else:
            return 'general'

def main():
    normalizer = DataNormalizer()
    
    # Тестовые курсы
    test_courses = [
        {
            'id': 'test_1',
            'program_id': 'ai',
            'name': 'Машинное обучение',
            'semester': 1,
            'credits': 6,
            'type': 'required'
        },
        {
            'id': 'test_2',
            'program_id': 'ai_product',
            'name': 'Глубокое обучение',
            'semester': 2,
            'credits': 4,
            'type': 'elective'
        }
    ]
    
    normalized = normalizer.normalize_courses(test_courses)
    enriched = normalizer.enrich_courses(normalized)
    stats = normalizer.get_statistics(enriched)
    
    print(f'Нормализовано курсов: {len(normalized)}')
    print(f'Статистика: {stats}')
    
    for course in enriched:
        print(f"- {course['name']}: {course['tags']} (сложность: {course['difficulty']})")

if __name__ == '__main__':
    main()