/
dbccx
/
VestnikChecker
Обзор
Документация
Войти
/
dbccx
/
VestnikChecker
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
core/llm_postprocessor.py
198 строк
9 KB
t i a
Первый коммит: система проверки научных статей VestnikChecker
29 май 2026, 02:53
29 май 2026, 02:53
dc428ec
Код
Авторство
О чём код?
import re from models.check_result import Violation class LLMPostProcessor: def __init__(self, parsed_doc): self.doc = parsed_doc def filter_violations(self, violations): filtered = [] for v in violations: # Используем description вместо remark desc_lower = v.description.lower() # Пропускаем проверку, если это не критичное замечание if self._is_false_positive(v, desc_lower): continue # Корректируем severity, если оно завышено corrected_severity = self._correct_severity(v, desc_lower) if corrected_severity != v.severity: v.severity = corrected_severity filtered.append(v) return filtered def _is_false_positive(self, v, desc_lower): # 1. Проверка про ключевые слова if any(kw in desc_lower for kw in ["ключевые слова", "keywords", "key words"]): if self.doc.keywords and len(self.doc.keywords) >= 3: return True # Ключевые слова есть, игнорируем # 2. Проверка про аннотацию (объем) if "аннотац" in desc_lower or "abstract" in desc_lower: word_count = self._count_words(self.doc.abstract) # Если аннотация > 120 слов, считаем замечание ложным if word_count >= 120: return True # 3. Проверка про структуру разделов if "раздел" in desc_lower or "section" in desc_lower: # Если есть хотя бы 2 раздела, считаем структуру приемлемой if self.doc.sections and len(self.doc.sections) >= 2: # Особые случаи, когда действительно чего-то не хватает missing_critical = self._check_missing_critical_sections(desc_lower) if not missing_critical: return True # 4. Проверка про список литературы if "литератур" in desc_lower or "reference" in desc_lower or "bibliograph" in desc_lower: ref_count = self._count_references(self.doc.bibliography_text) # Если ссылок >= 3, замечание об "отсутствии литературы" — ложное if ref_count >= 3 and ("отсутств" in desc_lower or "нет" in desc_lower): return True # 5. Проверка про УДК if "удк" in desc_lower or "udc" in desc_lower: # Если УДК указан и не пустой, считаем достаточным if self.doc.udk and len(self.doc.udk) > 3: return True # 6. Проверка про английскую часть if "англ" in desc_lower or "english" in desc_lower or "translation" in desc_lower: # Если есть переводная часть (даже короткая) if self.doc.translation_part and len(self.doc.translation_part) > 200: return True # 7. Проверка про скрытый текст (white-on-white) if "скрыт" in desc_lower or "white" in desc_lower: # Проверяем реальное наличие скрытого текста if not self._has_hidden_text(self.doc.full_text): return True # 8. Проверка про объем аннотации (конкретные числа) if "150 слов" in desc_lower or "150 слов" in desc_lower: word_count = self._count_words(self.doc.abstract) if word_count >= 130: # 130-149 слов - допустимо return True return False def _correct_severity(self, v, desc_lower): # Аннотация 120-149 слов: critical/high → low if "аннотац" in desc_lower or "abstract" in desc_lower: word_count = self._count_words(self.doc.abstract) if 120 <= word_count < 150: if v.severity in ["critical", "high"]: return "low" # Неполное соответствие УДК: high → low if "удк" in desc_lower and not self._is_udc_completely_wrong(): if v.severity in ["critical", "high"]: return "low" # Небольшие проблемы с литературой (3-4 ссылки): high → medium if "литератур" in desc_lower: ref_count = self._count_references(self.doc.bibliography_text) if 3 <= ref_count <= 4: if v.severity == "high": return "medium" # Неявно сформулированная цель: high → low if "цель" in desc_lower and "не указан" in desc_lower: return "low" # Отсутствие сравнения с аналогами: high → medium if "сравнен" in desc_lower and "аналог" in desc_lower: return "medium" return v.severity def _count_words(self, text): if not text: return 0 # Убираем лишние пробелы и разбиваем words = re.findall(r'\b\w+\b', text) return len(words) def _count_references(self, bibliography_text): if not bibliography_text: return 0 # Ищем пронумерованные ссылки: 1., 2., [1], [2] и т.д. patterns = [ r'(?:^|\n)\s*\d+\.', # 1. r'(?:^|\n)\s*\[\d+\]', # [1] r'(?:^|\n)\s*\(\d+\)', # (1) ] total = 0 for pattern in patterns: total += len(re.findall(pattern, bibliography_text)) return max(total, 0) def _check_missing_critical_sections(self, desc_lower): """Проверяет, действительно ли отсутствуют критически важные разделы.""" # Разделы, которые действительно обязательны critical_sections = ["введение", "introduction", "заключение", "conclusion"] # Получаем заголовки разделов из документа section_titles = [s.get("title", "").lower() for s in self.doc.sections] missing = [] for critical in critical_sections: if not any(critical in title for title in section_titles): missing.append(critical) # Если отсутствуют хотя бы 2 критических раздела — замечание обосновано return len(missing) >= 2 def _is_udc_completely_wrong(self): """Проверяет, полностью ли не соответствует УДК теме статьи.""" # Если УДК пустой или очень короткий — считаем, что не соответствует if not self.doc.udk or len(self.doc.udk) < 3: return True # Тематические классы УДК для вычислительной математики и информатики relevant_classes = ["004", "519.6", "519.7", "004.8", "004.9", "004.4", "004.2"] # Проверяем, есть ли хоть один релевантный класс udk_parts = re.findall(r'\d+(?:\.\d+)*', self.doc.udk) for part in udk_parts: for relevant in relevant_classes: if part.startswith(relevant) or relevant.startswith(part): return False # Есть соответствие return True # Полностью не соответствует def _has_hidden_text(self, full_text): """Проверяет наличие скрытого текста (white-on-white, tiny font, etc).""" if not full_text: return False # Признаки скрытого текста в PDF (через текстовый слой) hidden_patterns = [ r'color:\s*white', r'color:\s*#fff', r'color:\s*#ffffff', r'font-size:\s*0', r'font-size:\s*0\.\d+', r'opacity:\s*0', r'visibility:\s*hidden', ] text_lower = full_text.lower() for pattern in hidden_patterns: if re.search(pattern, text_lower): return True # Проверка на длинные строки из непонятных символов в начале документа lines = full_text.split('\n')[:20] for line in lines: # Если строка содержит много странных символов и очень длинная if len(line) > 100 and re.match(r'^[\x00-\x1F\x7F-\xFF\s]+$', line): return True return False