Spaces:
Sleeping
Sleeping
| """ | |
| AI Backend Simplified - без spacy, совместим с Python 3.14 | |
| Использует: transformers, scikit-learn, nltk, re, numpy | |
| """ | |
| import re | |
| import datetime | |
| import numpy as np | |
| from typing import List, Dict, Optional, Tuple, Any | |
| from dataclasses import dataclass | |
| from enum import Enum | |
| import hashlib | |
| import json | |
| from collections import Counter | |
| import warnings | |
| # ML Libraries (с обработкой ошибок импорта) | |
| try: | |
| import numpy as np | |
| from sklearn.feature_extraction.text import TfidfVectorizer | |
| from sklearn.metrics.pairwise import cosine_similarity | |
| import joblib | |
| import pandas as pd | |
| ML_AVAILABLE = True | |
| except ImportError: | |
| ML_AVAILABLE = False | |
| warnings.warn("scikit-learn not available, using fallback methods") | |
| try: | |
| import torch | |
| from transformers import ( | |
| pipeline, | |
| AutoTokenizer, | |
| AutoModelForSequenceClassification, | |
| AutoModelForSeq2SeqLM | |
| ) | |
| from sentence_transformers import SentenceTransformer | |
| TRANSFORMERS_AVAILABLE = True | |
| except ImportError: | |
| TRANSFORMERS_AVAILABLE = False | |
| warnings.warn("transformers not available") | |
| try: | |
| import nltk | |
| from nltk.corpus import stopwords | |
| from nltk.tokenize import word_tokenize | |
| from nltk.stem import SnowballStemmer | |
| NLTK_AVAILABLE = True | |
| except ImportError: | |
| NLTK_AVAILABLE = False | |
| warnings.warn("nltk not available") | |
| # Конфигурация | |
| class MLConfig: | |
| """Конфигурация ML моделей""" | |
| plagiarism_threshold: float = 0.85 | |
| min_text_length: int = 30 | |
| embedding_model: str = "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2" | |
| classification_model: str = "cointegrated/rubert-tiny-cased-nli-threeway" | |
| paraphraser_model: str = "IlyaGusev/rut5_base_headline_gen_telegram" | |
| use_gpu: bool = torch.cuda.is_available() if 'torch' in globals() else False | |
| cache_dir: str = "./ml_cache" | |
| batch_size: int = 16 | |
| language: str = "russian" | |
| class Specialization(Enum): | |
| """Поддерживаемые специальности""" | |
| IT = "it" | |
| MEDICINE = "medicine" | |
| CONSTRUCTION = "construction" | |
| DESIGN = "design" | |
| MARKETING = "marketing" | |
| LAW = "law" | |
| FINANCE = "finance" | |
| EDUCATION = "education" | |
| ENGINEERING = "engineering" | |
| SCIENCE = "science" | |
| class TextAnalysis: | |
| """Результат анализа текста""" | |
| trust_score: float | |
| is_original: bool | |
| plagiarism_sources: List[str] | |
| readability_score: float | |
| keyword_matches: Dict[str, float] | |
| sentiment: float # -1 to 1 | |
| complexity: float # 0 to 1 | |
| class PracticeAIBackendSimple: | |
| """ | |
| Упрощенный бэкенд без spacy, совместимый с Python 3.14 | |
| """ | |
| def __init__(self, config: Optional[MLConfig] = None): | |
| self.config = config or MLConfig() | |
| self.knowledge_base = self._load_knowledge_base() | |
| self.models = {} | |
| self.vectorizer = None | |
| self.embeddings_cache = {} | |
| # Инициализация ML моделей | |
| self._initialize_models() | |
| # Загрузка стоп-слов и других ресурсов | |
| self._load_nlp_resources() | |
| # Кэш для быстрого доступа | |
| self.text_cache = {} | |
| def _initialize_models(self): | |
| """Инициализация ML моделей""" | |
| if TRANSFORMERS_AVAILABLE: | |
| try: | |
| # Модель для эмбеддингов (векторизации текста) - используем более легкую модель | |
| print("Загрузка модели для эмбеддингов...") | |
| self.models['embedding'] = SentenceTransformer( | |
| 'paraphrase-multilingual-MiniLM-L12-v2', | |
| device='cuda' if self.config.use_gpu and torch.cuda.is_available() else 'cpu' | |
| ) | |
| # Модель для классификации специальностей | |
| print("Загрузка модели классификации...") | |
| try: | |
| self.models['classifier'] = pipeline( | |
| "zero-shot-classification", | |
| model="facebook/bart-large-mnli", | |
| device=0 if self.config.use_gpu and torch.cuda.is_available() else -1 | |
| ) | |
| except: | |
| # Fallback модель | |
| self.models['classifier'] = None | |
| # Модель для перефразирования (улучшения текста) | |
| print("Загрузка модели перефразирования...") | |
| try: | |
| self.models['paraphraser'] = pipeline( | |
| "text2text-generation", | |
| model="google/flan-t5-small", | |
| device=0 if self.config.use_gpu and torch.cuda.is_available() else -1 | |
| ) | |
| except: | |
| self.models['paraphraser'] = None | |
| print("ML модели успешно загружены") | |
| except Exception as e: | |
| print(f"Ошибка загрузки моделей: {e}") | |
| self.models = {} | |
| if ML_AVAILABLE: | |
| # Инициализация TF-IDF векторизатора | |
| self.vectorizer = TfidfVectorizer( | |
| max_features=2000, | |
| ngram_range=(1, 2), | |
| stop_words=self._get_stop_words() if self._get_stop_words() else None | |
| ) | |
| def _load_nlp_resources(self): | |
| """Загрузка NLP ресурсов""" | |
| self.stop_words = self._get_stop_words() | |
| # Инициализация стеммера для русского языка | |
| self.stemmer = None | |
| if NLTK_AVAILABLE: | |
| try: | |
| # Загружаем необходимые ресурсы nltk | |
| nltk.download('punkt', quiet=True) | |
| nltk.download('stopwords', quiet=True) | |
| self.stemmer = SnowballStemmer("russian") | |
| except: | |
| self.stemmer = None | |
| # Простой токенизатор на случай отсутствия nltk | |
| self.simple_tokenizer = lambda text: re.findall(r'\b\w+\b', text.lower()) | |
| def _get_stop_words(self) -> List[str]: | |
| """Получение стоп-слов""" | |
| stop_words = [] | |
| if NLTK_AVAILABLE: | |
| try: | |
| stop_words = stopwords.words('russian') | |
| except: | |
| pass | |
| # Дополнительные стоп-слова | |
| additional_stop_words = [ | |
| 'это', 'вот', 'ну', 'так', 'как', 'что', 'который', | |
| 'весь', 'свой', 'наш', 'ваш', 'их', 'мой', 'твой', | |
| 'тот', 'сам', 'очень', 'более', 'менее', 'просто' | |
| ] | |
| stop_words.extend(additional_stop_words) | |
| return list(set(stop_words)) # Удаляем дубликаты | |
| def _load_knowledge_base(self) -> Dict: | |
| """Загрузка расширенной базы знаний""" | |
| return { | |
| 'it': { | |
| 'tech': ['Python', 'Django', 'React', 'Docker', 'PostgreSQL', | |
| 'FastAPI', 'Git', 'Kubernetes', 'AWS', 'TensorFlow', | |
| 'PyTorch', 'Redis', 'MongoDB', 'Node.js', 'TypeScript', | |
| 'JavaScript', 'HTML', 'CSS', 'SQL', 'NoSQL', 'Linux'], | |
| 'tasks': [ | |
| 'разработал API с использованием REST/GraphQL', | |
| 'оптимизировал SQL запросы и добавил индексы', | |
| 'настроил CI/CD пайплайн с автоматическим тестированием', | |
| 'реализовал микросервисную архитектуру', | |
| 'провел рефакторинг legacy кода', | |
| 'настроил мониторинг и алертинг', | |
| 'разработал систему кэширования', | |
| 'внедрил практики DevOps', | |
| 'создал пользовательский интерфейс', | |
| 'написал unit-тесты и интеграционные тесты' | |
| ], | |
| 'results': [ | |
| 'увеличил производительность на 40%', | |
| 'снизил время отклика API с 200мс до 50мс', | |
| 'уменьшил потребление памяти на 25%', | |
| 'повысил тестовое покрытие до 85%', | |
| 'автоматизировал 80% рутинных операций', | |
| 'улучшил пользовательский опыт', | |
| 'сократил время загрузки страницы', | |
| 'повысил безопасность приложения' | |
| ], | |
| 'keywords': ['код', 'программа', 'алгоритм', 'база данных', | |
| 'сервер', 'клиент', 'интерфейс', 'фреймворк', | |
| 'разработка', 'тестирование', 'деплой', 'оптимизация'] | |
| }, | |
| 'medicine': { | |
| 'tech': ['ЭКГ', 'УЗИ', 'КТ', 'МРТ', 'лабораторная диагностика', | |
| 'эндоскопия', 'реанимационное оборудование'], | |
| 'tasks': [ | |
| 'провел осмотр пациента', | |
| 'выполнил диагностическую процедуру', | |
| 'назначил курс лечения', | |
| 'провел оперативное вмешательство', | |
| 'анализировал результаты исследований' | |
| ], | |
| 'results': [ | |
| 'установил точный диагноз', | |
| 'улучшил состояние пациента', | |
| 'провел успешную операцию', | |
| 'подобрал эффективное лечение', | |
| 'предотвратил осложнения' | |
| ], | |
| 'keywords': ['пациент', 'диагноз', 'лечение', 'болезнь', 'симптом', | |
| 'анализ', 'исследование', 'процедура', 'операция'] | |
| }, | |
| 'design': { | |
| 'tech': ['Figma', 'Adobe Photoshop', 'Illustrator', 'Sketch', | |
| 'InDesign', 'After Effects', 'Blender'], | |
| 'tasks': [ | |
| 'создал дизайн макета', | |
| 'разработал логотип и айдентику', | |
| 'подготовил UI/UX дизайн', | |
| 'создал иллюстрации и графику', | |
| 'разработал анимацию' | |
| ], | |
| 'results': [ | |
| 'улучшил визуальное восприятие', | |
| 'повысил удобство использования', | |
| 'создал запоминающийся бренд', | |
| 'увеличил конверсию', | |
| 'улучшил пользовательский опыт' | |
| ], | |
| 'keywords': ['дизайн', 'макет', 'интерфейс', 'графика', 'цвет', | |
| 'композиция', 'типографика', 'брендинг'] | |
| }, | |
| 'marketing': { | |
| 'tech': ['Google Analytics', 'Яндекс.Метрика', 'CRM', | |
| 'SMM инструменты', 'SEO инструменты'], | |
| 'tasks': [ | |
| 'провел анализ рынка', | |
| 'разработал маркетинговую стратегию', | |
| 'создал контент-план', | |
| 'запустил рекламную кампанию', | |
| 'анализировал эффективность каналов' | |
| ], | |
| 'results': [ | |
| 'увеличил трафик на 40%', | |
| 'повысил конверсию', | |
| 'снизил стоимость привлечения клиента', | |
| 'увеличил узнаваемость бренда', | |
| 'привлек новых клиентов' | |
| ], | |
| 'keywords': ['маркетинг', 'реклама', 'аналитика', 'конверсия', | |
| 'трафик', 'стратегия', 'кампания', 'таргетинг'] | |
| } | |
| } | |
| def analyze_honesty(self, text: str, history: List[str]) -> Dict: | |
| """ | |
| Анализ честности текста | |
| """ | |
| if not text or len(text) < self.config.min_text_length: | |
| return { | |
| 'trust_score': 0, | |
| 'status': 'too_short', | |
| 'is_original': False, | |
| 'plagiarism_sources': [], | |
| 'readability': 0, | |
| 'sentiment': 0 | |
| } | |
| # Нормализация текста | |
| clean_text = self._normalize_text(text) | |
| # Проверка на плагиат | |
| plagiarism_results = self._detect_plagiarism(clean_text, history) | |
| # Анализ читабельности | |
| readability = self._calculate_readability(text) | |
| # Анализ тональности | |
| sentiment = self._analyze_sentiment(text) | |
| # Итоговая оценка доверия | |
| trust_score = self._calculate_trust_score_simple( | |
| plagiarism_results['similarity'], | |
| readability, | |
| sentiment | |
| ) | |
| return { | |
| 'trust_score': round(trust_score, 2), | |
| 'is_original': plagiarism_results['similarity'] < self.config.plagiarism_threshold, | |
| 'plagiarism_sources': plagiarism_results['sources'][:3], | |
| 'readability': readability, | |
| 'sentiment': sentiment, | |
| 'status': 'original' if plagiarism_results['similarity'] < self.config.plagiarism_threshold else 'suspicious' | |
| } | |
| def _detect_plagiarism(self, text: str, history: List[str]) -> Dict: | |
| """ | |
| Обнаружение плагиата | |
| """ | |
| if not text or not history: | |
| return {'similarity': 0.0, 'sources': []} | |
| # Используем эмбеддинги если доступны | |
| if TRANSFORMERS_AVAILABLE and 'embedding' in self.models: | |
| try: | |
| return self._detect_plagiarism_with_embeddings(text, history) | |
| except: | |
| pass | |
| # Fallback на простой метод | |
| return self._detect_plagiarism_simple(text, history) | |
| def _detect_plagiarism_with_embeddings(self, text: str, history: List[str]) -> Dict: | |
| """Детекция плагиата с использованием эмбеддингов""" | |
| text_embedding = self.models['embedding'].encode([text], convert_to_tensor=True) | |
| max_similarity = 0.0 | |
| sources = [] | |
| for i, entry in enumerate(history[:50]): # Ограничиваем для производительности | |
| if len(entry) < 30: | |
| continue | |
| # Кэширование | |
| entry_hash = hashlib.md5(entry.encode()).hexdigest()[:16] | |
| if entry_hash in self.embeddings_cache: | |
| entry_embedding = self.embeddings_cache[entry_hash] | |
| else: | |
| entry_embedding = self.models['embedding'].encode([entry], convert_to_tensor=True) | |
| self.embeddings_cache[entry_hash] = entry_embedding | |
| # Расчет схожести | |
| similarity = cosine_similarity( | |
| text_embedding.cpu().numpy(), | |
| entry_embedding.cpu().numpy() | |
| )[0][0] | |
| if similarity > max_similarity: | |
| max_similarity = similarity | |
| if similarity > 0.7: | |
| sources.append(f"Источник #{i+1}: {similarity:.2f}") | |
| return { | |
| 'similarity': float(max_similarity), | |
| 'sources': sources | |
| } | |
| def _detect_plagiarism_simple(self, text: str, history: List[str]) -> Dict: | |
| """Простая детекция плагиата""" | |
| # Токенизация | |
| text_tokens = set(self._tokenize(text)) | |
| if not text_tokens: | |
| return {'similarity': 0.0, 'sources': []} | |
| max_similarity = 0.0 | |
| sources = [] | |
| for i, entry in enumerate(history): | |
| if len(entry) < 30: | |
| continue | |
| entry_tokens = set(self._tokenize(entry)) | |
| if not entry_tokens: | |
| continue | |
| # Схожесть Жаккара | |
| intersection = len(text_tokens.intersection(entry_tokens)) | |
| union = len(text_tokens.union(entry_tokens)) | |
| similarity = intersection / union if union > 0 else 0 | |
| if similarity > max_similarity: | |
| max_similarity = similarity | |
| if similarity > 0.5: | |
| sources.append(f"Запись #{i+1}: {similarity:.2f}") | |
| return { | |
| 'similarity': float(max_similarity), | |
| 'sources': sources[:5] | |
| } | |
| def improve_text(self, text: str, specialization: str = 'it') -> str: | |
| """ | |
| Улучшение текста | |
| """ | |
| if not text or len(text) < 10: | |
| return text | |
| # Используем ML модель если доступна | |
| if TRANSFORMERS_AVAILABLE and self.models.get('paraphraser'): | |
| try: | |
| # Подготовка промпта | |
| prompt = f"Улучши текст, сделай его более профессиональным: {text}" | |
| # Генерация | |
| result = self.models['paraphraser']( | |
| prompt, | |
| max_length=200, | |
| min_length=30, | |
| do_sample=True, | |
| temperature=0.7, | |
| top_p=0.9 | |
| ) | |
| if result and len(result) > 0: | |
| improved = result[0]['generated_text'] | |
| # Удаляем промпт если он остался | |
| improved = improved.replace(prompt, "").strip() | |
| return self._postprocess_text(improved, specialization) | |
| except Exception as e: | |
| print(f"Ошибка улучшения текста ML: {e}") | |
| # Rule-based улучшение | |
| return self._improve_text_rule_based(text, specialization) | |
| def _improve_text_rule_based(self, text: str, specialization: str) -> str: | |
| """Rule-based улучшение текста""" | |
| spec_data = self.knowledge_base.get(specialization, self.knowledge_base['it']) | |
| # Разбиваем на предложения | |
| sentences = re.split(r'[.!?]+', text) | |
| sentences = [s.strip() for s in sentences if s.strip()] | |
| if not sentences: | |
| return text | |
| improved_sentences = [] | |
| for sentence in sentences: | |
| if not sentence: | |
| continue | |
| # Капитализация | |
| sentence = sentence[0].upper() + sentence[1:] if sentence else "" | |
| # Проверяем, содержит ли предложение профессиональные термины | |
| has_tech = any(tech.lower() in sentence.lower() for tech in spec_data['tech']) | |
| has_task = any(task.lower() in sentence.lower() for task in spec_data['tasks']) | |
| # Добавляем профессиональные термины если их нет | |
| if not has_tech and len(sentence.split()) < 10: | |
| tech = np.random.choice(spec_data['tech']) | |
| sentence = f"{sentence}. Использовал {tech}." | |
| if not has_task and len(sentence.split()) < 8: | |
| task = np.random.choice(spec_data['tasks']) | |
| result = np.random.choice(spec_data['results']) | |
| sentence = f"{sentence}. {task}, что позволило {result}." | |
| improved_sentences.append(sentence) | |
| # Собираем обратно | |
| improved = '. '.join(improved_sentences) | |
| # Добавляем точку в конце если нет | |
| if improved and not improved.endswith(('.', '!', '?')): | |
| improved += '.' | |
| return improved | |
| def detect_specialization(self, text: str) -> Tuple[str, Dict[str, float]]: | |
| """ | |
| Определение специальности по тексту | |
| """ | |
| if not text or len(text) < 20: | |
| return 'it', {'it': 1.0} | |
| # Используем ML классификатор если доступен | |
| if TRANSFORMERS_AVAILABLE and self.models.get('classifier'): | |
| try: | |
| candidate_labels = list(self.knowledge_base.keys()) | |
| result = self.models['classifier']( | |
| text, | |
| candidate_labels=candidate_labels, | |
| multi_label=False | |
| ) | |
| scores = {} | |
| for label, score in zip(result['labels'][:3], result['scores'][:3]): | |
| scores[label] = float(score) | |
| return result['labels'][0], scores | |
| except: | |
| pass | |
| # Rule-based классификация | |
| return self._detect_specialization_rule_based(text) | |
| def _detect_specialization_rule_based(self, text: str) -> Tuple[str, Dict[str, float]]: | |
| """Rule-based определение специальности""" | |
| text_lower = text.lower() | |
| scores = {} | |
| for spec, data in self.knowledge_base.items(): | |
| score = 0 | |
| # Ключевые слова | |
| for keyword in data.get('keywords', []): | |
| if keyword.lower() in text_lower: | |
| score += 2 | |
| # Технологии | |
| for tech in data.get('tech', []): | |
| if tech.lower() in text_lower: | |
| score += 1.5 | |
| # Задачи | |
| for task in data.get('tasks', []): | |
| if task.lower() in text_lower: | |
| score += 1.2 | |
| scores[spec] = score | |
| if not scores or max(scores.values()) == 0: | |
| return 'it', {'it': 1.0} | |
| # Нормализация | |
| max_score = max(scores.values()) | |
| normalized_scores = {k: v/max_score for k, v in scores.items()} | |
| # Выбираем лучшую | |
| best_spec = max(scores.items(), key=lambda x: x[1])[0] | |
| return best_spec, normalized_scores | |
| def calculate_reputation(self, entries: List[Dict]) -> Dict[str, Any]: | |
| """ | |
| Расчет репутации пользователя | |
| """ | |
| if not entries: | |
| return { | |
| 'score': 0, | |
| 'level': 'beginner', | |
| 'breakdown': {}, | |
| 'metrics': { | |
| 'total_entries': 0, | |
| 'approved_entries': 0, | |
| 'approval_rate': 0 | |
| } | |
| } | |
| # Фильтруем одобренные записи | |
| approved_entries = [e for e in entries if e.get('status') == 'approved'] | |
| # Базовые метрики | |
| total_entries = len(entries) | |
| approved_count = len(approved_entries) | |
| approval_rate = approved_count / total_entries if total_entries > 0 else 0 | |
| # Расчет баллов | |
| score = 0 | |
| breakdown = { | |
| 'activity': 0, | |
| 'quality': 0, | |
| 'consistency': 0, | |
| 'diversity': 0, | |
| 'proofs': 0 | |
| } | |
| # 1. Активность (до 30 баллов) | |
| activity_score = min(30, approved_count * 3) | |
| breakdown['activity'] = activity_score | |
| score += activity_score | |
| # 2. Качество (до 25 баллов) | |
| if approved_entries: | |
| total_length = sum(len(e.get('description', '')) for e in approved_entries) | |
| avg_length = total_length / approved_count | |
| if avg_length > 400: | |
| quality_score = 25 | |
| elif avg_length > 250: | |
| quality_score = 20 | |
| elif avg_length > 150: | |
| quality_score = 15 | |
| elif avg_length > 80: | |
| quality_score = 10 | |
| else: | |
| quality_score = 5 | |
| breakdown['quality'] = quality_score | |
| score += quality_score | |
| # 3. Доказательства (до 20 баллов) | |
| proofs_score = 0 | |
| for entry in approved_entries: | |
| if entry.get('image'): | |
| proofs_score += 2 | |
| if entry.get('code_snippet'): | |
| proofs_score += 3 | |
| if entry.get('location'): | |
| proofs_score += 1 | |
| proofs_score = min(20, proofs_score) | |
| breakdown['proofs'] = proofs_score | |
| score += proofs_score | |
| # 4. Разнообразие (до 15 баллов) | |
| if approved_entries and ML_AVAILABLE: | |
| try: | |
| texts = [e.get('description', '') for e in approved_entries] | |
| unique_words = set() | |
| for text in texts: | |
| tokens = self._tokenize(text) | |
| unique_words.update(tokens) | |
| diversity_score = min(15, len(unique_words) // 10) | |
| breakdown['diversity'] = diversity_score | |
| score += diversity_score | |
| except: | |
| pass | |
| # 5. Консистентность (до 10 баллов) | |
| if approved_count >= 5: | |
| # Проверяем, есть ли записи за последнюю неделю | |
| recent_entries = 0 | |
| week_ago = datetime.datetime.now() - datetime.timedelta(days=7) | |
| for entry in approved_entries: | |
| if 'date' in entry: | |
| try: | |
| entry_date = datetime.datetime.fromisoformat(entry['date'].replace('Z', '+00:00')) | |
| if entry_date > week_ago: | |
| recent_entries += 1 | |
| except: | |
| pass | |
| if recent_entries >= 3: | |
| consistency_score = 10 | |
| elif recent_entries >= 1: | |
| consistency_score = 5 | |
| else: | |
| consistency_score = 0 | |
| breakdown['consistency'] = consistency_score | |
| score += consistency_score | |
| # Ограничиваем 0-100 | |
| score = min(100, max(0, score)) | |
| # Определяем уровень | |
| if score >= 85: | |
| level = 'expert' | |
| elif score >= 70: | |
| level = 'advanced' | |
| elif score >= 50: | |
| level = 'intermediate' | |
| elif score >= 25: | |
| level = 'beginner' | |
| else: | |
| level = 'newbie' | |
| return { | |
| 'score': int(score), | |
| 'level': level, | |
| 'breakdown': breakdown, | |
| 'metrics': { | |
| 'total_entries': total_entries, | |
| 'approved_entries': approved_count, | |
| 'approval_rate': round(approval_rate * 100, 1) | |
| } | |
| } | |
| def generate_thought(self, specialization: str = 'it', context: Optional[str] = None) -> str: | |
| """ | |
| Генерация текста записи | |
| """ | |
| spec_data = self.knowledge_base.get(specialization, self.knowledge_base['it']) | |
| templates = [ | |
| "В рамках практики {task}. Применение {tech} позволило {result}.", | |
| "Основное внимание уделил {task}. Использовал {tech}, что привело к {result}.", | |
| "Сегодня работал над {task}. С помощью {tech} удалось {result}.", | |
| "Задачи дня включали {task}. Применение {tech} способствовало {result}.", | |
| "Сфокусировался на {task}. Технология {tech} помогла {result}." | |
| ] | |
| template = np.random.choice(templates) | |
| task = np.random.choice(spec_data['tasks']) | |
| tech = np.random.choice(spec_data['tech']) | |
| result = np.random.choice(spec_data['results']) | |
| thought = template.format(task=task, tech=tech, result=result) | |
| # Добавляем контекст если есть | |
| if context: | |
| # Находим общие слова с контекстом | |
| context_words = set(self._tokenize(context.lower())) | |
| thought_words = set(self._tokenize(thought.lower())) | |
| common_words = context_words.intersection(thought_words) | |
| if common_words: | |
| thought = f"Продолжая работу, {thought.lower()}" | |
| return self._correct_text(thought) | |
| def analyze_skill_gap(self, user_texts: List[str], target_specialization: str) -> Dict: | |
| """ | |
| Анализ пробелов в навыках | |
| """ | |
| spec_data = self.knowledge_base.get(target_specialization, self.knowledge_base['it']) | |
| # Объединяем все тексты | |
| all_text = ' '.join(user_texts).lower() | |
| # Анализируем упомянутые навыки | |
| mentioned_skills = {} | |
| for tech in spec_data['tech']: | |
| tech_lower = tech.lower() | |
| if tech_lower in all_text: | |
| mentioned_skills[tech] = all_text.count(tech_lower) | |
| # Находим отсутствующие навыки | |
| missing_skills = [tech for tech in spec_data['tech'] if tech.lower() not in all_text] | |
| # Анализируем задачи | |
| mentioned_tasks = [task for task in spec_data['tasks'] if task.lower() in all_text] | |
| missing_tasks = [task for task in spec_data['tasks'] if task.lower() not in all_text] | |
| # Рекомендации | |
| recommendations = [] | |
| if missing_skills: | |
| top_missing = missing_skills[:3] | |
| recommendations.append(f"Рекомендуется изучить: {', '.join(top_missing)}") | |
| if missing_tasks: | |
| top_tasks = missing_tasks[:2] | |
| recommendations.append(f"Попрактиковаться в: {', '.join(top_tasks)}") | |
| if len(mentioned_skills) < 3: | |
| recommendations.append("Расширьте спектр используемых технологий") | |
| # Покрытие навыков | |
| coverage = len(mentioned_skills) / len(spec_data['tech']) * 100 if spec_data['tech'] else 0 | |
| return { | |
| 'skill_coverage': round(coverage, 1), | |
| 'mentioned_skills': mentioned_skills, | |
| 'missing_skills': missing_skills[:5], | |
| 'mentioned_tasks': mentioned_tasks, | |
| 'missing_tasks': missing_tasks[:3], | |
| 'recommendations': recommendations, | |
| 'proficiency_level': self._calculate_proficiency_level(coverage) | |
| } | |
| def _calculate_proficiency_level(self, coverage: float) -> str: | |
| """Определение уровня владения""" | |
| if coverage >= 80: | |
| return "эксперт" | |
| elif coverage >= 60: | |
| return "продвинутый" | |
| elif coverage >= 40: | |
| return "средний" | |
| elif coverage >= 20: | |
| return "начинающий" | |
| else: | |
| return "новичок" | |
| # Вспомогательные методы | |
| def _normalize_text(self, text: str) -> str: | |
| """Нормализация текста""" | |
| if not text: | |
| return "" | |
| # Приведение к нижнему регистру | |
| text = text.lower() | |
| # Удаление специальных символов | |
| text = re.sub(r'[^\w\s]', ' ', text) | |
| # Удаление лишних пробелов | |
| text = re.sub(r'\s+', ' ', text).strip() | |
| # Удаление стоп-слов | |
| if self.stop_words: | |
| words = text.split() | |
| words = [w for w in words if w not in self.stop_words and len(w) > 2] | |
| text = ' '.join(words) | |
| return text | |
| def _tokenize(self, text: str) -> List[str]: | |
| """Токенизация текста""" | |
| if NLTK_AVAILABLE: | |
| try: | |
| return word_tokenize(text.lower()) | |
| except: | |
| pass | |
| # Простая токенизация | |
| return re.findall(r'\b\w+\b', text.lower()) | |
| def _calculate_readability(self, text: str) -> float: | |
| """Расчет читабельности""" | |
| if not text: | |
| return 0.0 | |
| sentences = re.split(r'[.!?]+', text) | |
| sentences = [s.strip() for s in sentences if s.strip()] | |
| if not sentences: | |
| return 0.0 | |
| words = text.split() | |
| avg_sentence_length = len(words) / len(sentences) | |
| # Простая формула читабельности | |
| if avg_sentence_length < 10: | |
| readability = 0.9 | |
| elif avg_sentence_length < 15: | |
| readability = 0.7 | |
| elif avg_sentence_length < 20: | |
| readability = 0.5 | |
| elif avg_sentence_length < 25: | |
| readability = 0.3 | |
| else: | |
| readability = 0.1 | |
| return round(readability, 2) | |
| def _analyze_sentiment(self, text: str) -> float: | |
| """Анализ тональности""" | |
| if not text: | |
| return 0.0 | |
| positive_words = [ | |
| 'успех', 'успешно', 'улучшил', 'повысил', 'оптимизировал', 'решил', | |
| 'добился', 'освоил', 'внедрил', 'создал', 'разработал', 'завершил', | |
| 'достиг', 'получил', 'научился', 'понял', 'изучил' | |
| ] | |
| negative_words = [ | |
| 'проблема', 'ошибка', 'сложность', 'неудача', 'исправил', 'столкнулся', | |
| 'трудность', 'баг', 'дефект', 'недочет', 'неполадка', 'сбой', 'затруднение' | |
| ] | |
| text_lower = text.lower() | |
| positive_count = sum(1 for word in positive_words if word in text_lower) | |
| negative_count = sum(1 for word in negative_words if word in text_lower) | |
| total = positive_count + negative_count | |
| if total == 0: | |
| return 0.0 | |
| sentiment = (positive_count - negative_count) / total | |
| return round(sentiment, 2) | |
| def _calculate_trust_score_simple(self, plagiarism: float, readability: float, sentiment: float) -> float: | |
| """Расчет оценки доверия""" | |
| # Веса | |
| plagiarism_weight = 0.5 | |
| readability_weight = 0.3 | |
| sentiment_weight = 0.2 | |
| # Оценка плагиата (меньше плагиата - лучше) | |
| plagiarism_score = (1 - plagiarism) * 100 | |
| # Оценка читабельности | |
| readability_score = readability * 100 | |
| # Оценка тональности (абсолютное значение) | |
| sentiment_score = abs(sentiment) * 100 | |
| # Итоговая оценка | |
| trust_score = ( | |
| plagiarism_score * plagiarism_weight + | |
| readability_score * readability_weight + | |
| sentiment_score * sentiment_weight | |
| ) | |
| return round(max(0, min(100, trust_score)), 2) | |
| def _postprocess_text(self, text: str, specialization: str) -> str: | |
| """Постобработка текста""" | |
| if not text: | |
| return "" | |
| # Удаление повторяющихся слов | |
| words = text.split() | |
| cleaned_words = [] | |
| prev_word = "" | |
| for word in words: | |
| if word.lower() != prev_word.lower(): | |
| cleaned_words.append(word) | |
| prev_word = word | |
| text = ' '.join(cleaned_words) | |
| # Исправление очевидных ошибок | |
| corrections = { | |
| 'под руководством руководством': 'под руководством', | |
| 'в процессе процессе': 'в процессе', | |
| 'с использованием использования': 'с использованием', | |
| ' ,': ',', | |
| ' .': '.', | |
| ' ;': ';', | |
| ' ': ' ' | |
| } | |
| for wrong, correct in corrections.items(): | |
| text = text.replace(wrong, correct) | |
| # Капитализация первого слова | |
| if text: | |
| text = text[0].upper() + text[1:] | |
| # Добавление точки в конце если нужно | |
| if text and not text.endswith(('.', '!', '?')): | |
| text += '.' | |
| return text.strip() | |
| def _correct_text(self, text: str) -> str: | |
| """Коррекция текста""" | |
| return self._postprocess_text(text, 'it') | |
| # Совместимость с оригинальным интерфейсом | |
| def _normalize(self, text: str) -> set: | |
| """Совместимый метод нормализации""" | |
| return set(self._tokenize(text)) | |
| def _calculate_jaccard_similarity(self, set1: set, set2: set) -> float: | |
| """Совместимый метод расчета схожести Жаккара""" | |
| intersection = len(set1.intersection(set2)) | |
| union = len(set1.union(set2)) | |
| return intersection / union if union > 0 else 0.0 | |
| # FastAPI приложение | |
| def create_app(): | |
| """Создание FastAPI приложения""" | |
| try: | |
| from fastapi import FastAPI, HTTPException | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from pydantic import BaseModel | |
| from typing import List, Optional, Dict, Any | |
| app = FastAPI(title="Practice AI Backend Simple", version="1.0") | |
| # Инициализация бэкенда | |
| backend = PracticeAIBackendSimple() | |
| # CORS | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| # Модели запросов | |
| class TextRequest(BaseModel): | |
| text: str | |
| specialization: Optional[str] = "it" | |
| class HonestyRequest(BaseModel): | |
| text: str | |
| history: List[str] | |
| class ReputationRequest(BaseModel): | |
| entries: List[Dict[str, Any]] | |
| class SkillGapRequest(BaseModel): | |
| texts: List[str] | |
| target_specialization: str | |
| # Эндпоинты | |
| async def root(): | |
| return { | |
| "service": "Practice AI Backend", | |
| "version": "1.0", | |
| "status": "running", | |
| "ml_available": ML_AVAILABLE, | |
| "transformers_available": TRANSFORMERS_AVAILABLE | |
| } | |
| async def analyze_honesty(request: HonestyRequest): | |
| try: | |
| result = backend.analyze_honesty(request.text, request.history) | |
| return result | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| async def improve_text(request: TextRequest): | |
| try: | |
| improved = backend.improve_text(request.text, request.specialization) | |
| return {"improved_text": improved} | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| async def calculate_reputation(request: ReputationRequest): | |
| try: | |
| result = backend.calculate_reputation(request.entries) | |
| return result | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| async def detect_specialization(request: TextRequest): | |
| try: | |
| spec, scores = backend.detect_specialization(request.text) | |
| return { | |
| "specialization": spec, | |
| "confidence_scores": scores | |
| } | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| async def analyze_skillgap(request: SkillGapRequest): | |
| try: | |
| result = backend.analyze_skill_gap( | |
| request.texts, | |
| request.target_specialization | |
| ) | |
| return result | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| async def generate_thought(request: TextRequest): | |
| try: | |
| thought = backend.generate_thought(request.specialization, request.text) | |
| return {"generated_text": thought} | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| async def health_check(): | |
| return { | |
| "status": "healthy", | |
| "ml_available": ML_AVAILABLE, | |
| "transformers_available": TRANSFORMERS_AVAILABLE, | |
| "nltk_available": NLTK_AVAILABLE | |
| } | |
| return app | |
| except ImportError: | |
| print("FastAPI не установлен. Используйте: pip install fastapi uvicorn") | |
| return None | |
| # CLI интерфейс | |
| def main(): | |
| """Основная функция CLI""" | |
| import argparse | |
| import sys | |
| parser = argparse.ArgumentParser(description="Practice AI Backend") | |
| parser.add_argument("--mode", choices=["api", "cli"], default="cli", help="Режим работы") | |
| parser.add_argument("--host", default="0.0.0.0", help="Хост для API") | |
| parser.add_argument("--port", type=int, default=8000, help="Порт для API") | |
| args = parser.parse_args() | |
| if args.mode == "api": | |
| app = create_app() | |
| if app: | |
| import uvicorn | |
| print(f"Запуск API сервера на http://{args.host}:{args.port}") | |
| uvicorn.run(app, host=args.host, port=args.port) | |
| else: | |
| print("Не удалось запустить API сервер") | |
| sys.exit(1) | |
| else: | |
| # CLI режим | |
| backend = PracticeAIBackendSimple() | |
| print("=== Practice AI Backend CLI ===") | |
| print("Доступные команды:") | |
| print(" 1. Анализ честности текста") | |
| print(" 2. Улучшение текста") | |
| print(" 3. Определение специальности") | |
| print(" 4. Расчет репутации") | |
| print(" 5. Генерация текста") | |
| print(" 6. Анализ пробелов в навыках") | |
| print(" 0. Выход") | |
| while True: | |
| try: | |
| choice = input("\nВыберите команду (0-6): ").strip() | |
| if choice == "0": | |
| print("Выход...") | |
| break | |
| elif choice == "1": | |
| text = input("Введите текст для анализа: ") | |
| history = input("Введите историю (через ;; ): ").split(";;") | |
| result = backend.analyze_honesty(text, history) | |
| print(f"Результат анализа:") | |
| print(f" Оценка доверия: {result['trust_score']}") | |
| print(f" Оригинальность: {result['is_original']}") | |
| print(f" Читабельность: {result['readability']}") | |
| print(f" Тональность: {result['sentiment']}") | |
| elif choice == "2": | |
| text = input("Введите текст для улучшения: ") | |
| spec = input("Специальность (по умолчанию it): ") or "it" | |
| improved = backend.improve_text(text, spec) | |
| print(f"Улучшенный текст: {improved}") | |
| elif choice == "3": | |
| text = input("Введите текст: ") | |
| spec, scores = backend.detect_specialization(text) | |
| print(f"Определенная специальность: {spec}") | |
| print(f"Оценки уверенности: {scores}") | |
| elif choice == "4": | |
| print("Введите записи в формате JSON (пустая строка для завершения):") | |
| entries = [] | |
| while True: | |
| line = input().strip() | |
| if not line: | |
| break | |
| try: | |
| entry = json.loads(line) | |
| entries.append(entry) | |
| except: | |
| print("Ошибка парсинга JSON") | |
| result = backend.calculate_reputation(entries) | |
| print(f"Репутация: {result['score']} ({result['level']})") | |
| print(f"Детализация: {result['breakdown']}") | |
| elif choice == "5": | |
| spec = input("Специальность (по умолчанию it): ") or "it" | |
| context = input("Контекст (опционально): ") | |
| thought = backend.generate_thought(spec, context if context else None) | |
| print(f"Сгенерированный текст: {thought}") | |
| elif choice == "6": | |
| print("Введите тексты для анализа (пустая строка для завершения):") | |
| texts = [] | |
| while True: | |
| text = input().strip() | |
| if not text: | |
| break | |
| texts.append(text) | |
| target_spec = input("Целевая специальность (по умолчанию it): ") or "it" | |
| result = backend.analyze_skill_gap(texts, target_spec) | |
| print(f"Покрытие навыков: {result['skill_coverage']}%") | |
| print(f"Уровень: {result['proficiency_level']}") | |
| print("Рекомендации:") | |
| for rec in result['recommendations']: | |
| print(f" - {rec}") | |
| else: | |
| print("Неверный выбор") | |
| except KeyboardInterrupt: | |
| print("\nВыход...") | |
| break | |
| except Exception as e: | |
| print(f"Ошибка: {e}") | |
| if __name__ == "__main__": | |
| main() |