diff --git a/app/main/check_packs/pack_config.py b/app/main/check_packs/pack_config.py index 91e08134..471949ae 100644 --- a/app/main/check_packs/pack_config.py +++ b/app/main/check_packs/pack_config.py @@ -50,6 +50,7 @@ ["empty_task_page_check"], ["water_in_the_text_check"], ["report_task_tracker"], + ["references_in_chapter_check"], ] DEFAULT_TYPE = 'pres' diff --git a/app/main/checks/report_checks/__init__.py b/app/main/checks/report_checks/__init__.py index 30f22617..faa3275e 100644 --- a/app/main/checks/report_checks/__init__.py +++ b/app/main/checks/report_checks/__init__.py @@ -34,3 +34,4 @@ from .task_tracker import ReportTaskTracker from .paragraphs_count_check import ReportParagraphsCountCheck from .template_name import ReportTemplateNameCheck +from .lit_ref_in_spec_chapter import LitRefInChapter diff --git a/app/main/checks/report_checks/banned_words_check.py b/app/main/checks/report_checks/banned_words_check.py index 351b403e..58c67cd3 100644 --- a/app/main/checks/report_checks/banned_words_check.py +++ b/app/main/checks/report_checks/banned_words_check.py @@ -5,12 +5,13 @@ class ReportBannedWordsCheck(BaseReportCriterion): label = "Проверка наличия запретных слов в тексте отчёта" - description = 'Запрещено упоминание слова "мы"' + description = 'Запрещено упоминание определенных "опасных" слов' id = 'banned_words_check' def __init__(self, file_info, headers_map=None): super().__init__(file_info) self.words = [] + self.warned_words = [] self.min_count = 0 self.max_count = 0 if headers_map: @@ -21,12 +22,14 @@ def __init__(self, file_info, headers_map=None): def late_init(self): self.headers_main = self.file.get_main_headers(self.file_type['report_type']) if self.headers_main in StyleCheckSettings.CONFIGS.get(self.config): - self.words = [morph.normal_forms(word)[0] for word in StyleCheckSettings.CONFIGS.get(self.config)[self.headers_main]['banned_words']] + self.words = {morph.normal_forms(word)[0] for word in StyleCheckSettings.CONFIGS.get(self.config)[self.headers_main]['banned_words']} + self.warned_words = {morph.normal_forms(word)[0] for word in StyleCheckSettings.CONFIGS.get(self.config)[self.headers_main]['warned_words']} self.min_count = StyleCheckSettings.CONFIGS.get(self.config)[self.headers_main]['min_count_for_banned_words_check'] self.max_count = StyleCheckSettings.CONFIGS.get(self.config)[self.headers_main]['max_count_for_banned_words_check'] else: if 'any_header' in StyleCheckSettings.CONFIGS.get(self.config): - self.words = [morph.normal_forms(word)[0] for word in StyleCheckSettings.CONFIGS.get(self.config)['any_header']['banned_words']] + self.words = {morph.normal_forms(word)[0] for word in StyleCheckSettings.CONFIGS.get(self.config)['any_header']['banned_words']} + self.warned_words = {morph.normal_forms(word)[0] for word in StyleCheckSettings.CONFIGS.get(self.config)['any_header']['warned_words']} self.min_count = StyleCheckSettings.CONFIGS.get(self.config)['any_header']['min_count_for_banned_words_check'] self.max_count = StyleCheckSettings.CONFIGS.get(self.config)['any_header']['max_count_for_banned_words_check'] @@ -34,29 +37,35 @@ def check(self): if self.file.page_counter() < 4: return answer(False, "В отчете недостаточно страниц. Нечего проверять.") self.late_init() - detected_lines = {} result_str = f'Запрещенные слова: {"; ".join(self.words)}
' - count = 0 + banned_counter = {'words': self.words, 'detected_lines': {}, 'count': 0} + warned_counter = {'words': self.warned_words,'detected_lines': {}, 'count': 0} for k, v in self.file.pdf_file.get_text_on_page().items(): lines_on_page = re.split(r'\n', v) for index, line in enumerate(lines_on_page): - words_on_line = re.split(r'[^\w-]+', line) - words_on_line = [morph.normal_forms(word)[0] for word in words_on_line] - count_banned_words = set(words_on_line).intersection(self.words) - if count_banned_words: - count += len(count_banned_words) - if k not in detected_lines.keys(): - detected_lines[k] = [] - detected_lines[k].append(f'Строка {index + 1}: {line} [{"; ".join(count_banned_words)}]') - if len(detected_lines): + words_on_line = {morph.normal_forms(word)[0] for word in re.split(r'[^\w-]+', line)} + for counter in (banned_counter, warned_counter): + count_banned_words = words_on_line.intersection(counter['words']) + if count_banned_words: + counter['count'] += len(count_banned_words) + if k not in counter['detected_lines'].keys(): + counter['detected_lines'][k] = [] + counter['detected_lines'][k].append(f'Строка {index + 1}: {line} [{"; ".join(count_banned_words)}]') + if len(banned_counter['detected_lines']): result_str += 'Обнаружены запретные слова!

' - for k, v in detected_lines.items(): - result_str += f'Страница №{k}:
{"
".join(detected_lines[k])}

' + for k, v in banned_counter['detected_lines'].items(): + result_str += f'Страница №{k}:
{"
".join(banned_counter['detected_lines'][k])}

' else: result_str = 'Пройдена!' + + if len(warned_counter['detected_lines']): + result_str += f'

Обнаружены потенциально опасные слова (не влияют на результат проверки)!
Обратите внимание, что их использование возможно только в подтвержденных случаях: {"; ".join(self.warned_words)}

' + for k, v in warned_counter['detected_lines'].items(): + result_str += f'Страница №{k}:
{"
".join(warned_counter['detected_lines'][k])}

' + result_score = 1 - if count > self.min_count: - if count <= self.max_count: + if banned_counter['count'] > self.min_count: + if banned_counter['count'] <= self.max_count: result_score = 0.5 else: result_score = 0 diff --git a/app/main/checks/report_checks/lit_ref_in_spec_chapter.py b/app/main/checks/report_checks/lit_ref_in_spec_chapter.py new file mode 100644 index 00000000..d31aa4e6 --- /dev/null +++ b/app/main/checks/report_checks/lit_ref_in_spec_chapter.py @@ -0,0 +1,96 @@ +import re +from .style_check_settings import StyleCheckSettings +from ..base_check import BaseReportCriterion, answer + + +class LitRefInChapter(BaseReportCriterion): + label = "Проверка количества ссылок на источники в определенном разделе" + description = '' + id = 'references_in_chapter_check' + + def __init__(self, file_info, min_ref_value=0.5, max_ref_value=1, headers_map=None): + super().__init__(file_info) + self.chapters_for_lit_ref = {} + self.lit_ref_count = {} + self.min_ref_value = min_ref_value + self.max_ref_value = max_ref_value + if headers_map: + self.config = headers_map + else: + self.config = 'VKR_HEADERS' if (self.file_type['report_type'] == 'VKR') else 'LR_HEADERS' + + def late_init(self): + self.chapters = self.file.make_chapters(self.file_type['report_type']) + self.headers_main = self.file.get_main_headers(self.file_type['report_type']) + if self.headers_main in StyleCheckSettings.CONFIGS.get(self.config): + self.chapters_for_lit_ref = StyleCheckSettings.CONFIGS.get(self.config)[self.headers_main][ + 'chapters_for_lit_ref'] + else: + if 'any_header' in StyleCheckSettings.CONFIGS.get(self.config): + self.chapters_for_lit_ref = StyleCheckSettings.CONFIGS.get(self.config)['any_header'][ + 'chapters_for_lit_ref'] + + def check(self): + if self.file.page_counter() < 4: + return answer(False, "В отчете недостаточно страниц. Нечего проверять.") + self.late_init() + if not self.chapters_for_lit_ref: + return answer(True, 'Для загруженной работы данная проверка не предусмотрена.') + result = [] + result_str = f'Пройдена!' + currant_head = '' + chapter_for_check = 0 + ref_in_annotation = False + for chapter in self.chapters: + header = chapter["text"].lower() + if currant_head: + self.lit_ref_count[currant_head].append(chapter['number']) + if currant_head in self.chapters_for_lit_ref: + chapter_for_check += 1 + ref_count = len(self.search_references(self.lit_ref_count[currant_head][0], + self.lit_ref_count[currant_head][1])) + if ref_count > self.chapters_for_lit_ref[currant_head][1] or ref_count < \ + self.chapters_for_lit_ref[currant_head][0]: + result.append(f'«{currant_head[0].upper() + currant_head[1:]}» : {ref_count}') + if currant_head == 'аннотация' or currant_head == 'annotation': + ref_in_annotation = True + self.lit_ref_count[header] = [chapter['number'], ] + currant_head = header + if result: + if chapter_for_check > 0: + ref_value = round((chapter_for_check - len(result)) / chapter_for_check, 2) + else: + ref_value = 1.0 + result_str = (f'Доля соответствия количества ссылок необходимому в требуемых разделах равна {ref_value}' + f'
Количество ссылок на источники не удовлетворяет допустимому в следующих разделах:
{"
".join(res for res in result)}' + f'
Допустимые пороги количества ссылок:
' + f'{"
".join(f"«{chapter.capitalize()}»: от {limit[0]} до {limit[1]}" for chapter, limit in self.chapters_for_lit_ref.items())}') + result_str += 'В аннотации не должно быть ссылок на литературу.' if ref_in_annotation else '' + if ref_value >= self.max_ref_value and not ref_in_annotation: + return answer(1, f'Пройдена!') + elif ref_value >= self.min_ref_value and not ref_in_annotation: + return answer(ref_value, f'Частично пройдена! {result_str}') + else: + return answer(0, f'Не пройдена! {result_str}') + elif ref_in_annotation: + return answer(0, 'В аннотации не должно быть ссылок на литературу.') + else: + return answer(1, result_str) + + def search_references(self, start_par, end_par): + array_of_references = [] + for i in range(start_par, end_par): + if isinstance(self.file.paragraphs[i], str): + detected_references = re.findall(r'\[[\d \-,]+\]', self.file.paragraphs[i]) + else: + detected_references = re.findall(r'\[[\d \-,]+\]', self.file.paragraphs[i].paragraph_text) + if detected_references: + for reference in detected_references: + for one_part in re.split(r'[\[\],]', reference): + if re.match(r'\d+[ \-]+\d+', one_part): + start, end = re.split(r'[ -]+', one_part) + for k in range(int(start), int(end) + 1): + array_of_references.append((k)) + elif one_part != '': + array_of_references.append(int(one_part)) + return array_of_references diff --git a/app/main/checks/report_checks/main_character_check.py b/app/main/checks/report_checks/main_character_check.py index c596e999..47914a6f 100644 --- a/app/main/checks/report_checks/main_character_check.py +++ b/app/main/checks/report_checks/main_character_check.py @@ -82,12 +82,6 @@ def extract_table_contents(self, table): contents.append("|".join(row_text)) return contents - def calculate_find_value(self, table, index): - count = int((len(table) - index - 2) / 2) - if count >= 0: - return count - return 0 - def check_table(self, check_list, table, table_num): for item in check_list: for i, line in enumerate(table): @@ -105,10 +99,7 @@ def check_table(self, check_list, table, table_num): continue elif item["key"] in ["Зав. кафедрой", "Консультант"] and item["found_key"] > 0: - if item["key"] == "Консультант": - if item["found_key"] == 1: - item["find"] += self.calculate_find_value(table, i) for value in item["value"]: - if re.search(value, line): + if "Руководитель" not in line and re.search(value, line): # исключаем из поиска строки с рукодителем item["found_value"] += 1 item["logs"] += f"'{item['key']}': значение компоненты '{value}' найдено в строке '{line}' в таблице №{table_num}
" diff --git a/app/main/checks/report_checks/style_check_settings.py b/app/main/checks/report_checks/style_check_settings.py index 7c3a61b5..791e8c2e 100644 --- a/app/main/checks/report_checks/style_check_settings.py +++ b/app/main/checks/report_checks/style_check_settings.py @@ -10,14 +10,15 @@ class StyleCheckSettings: HEADER_REGEX = "^\\D+.+$" HEADER_1_REGEX = "^()([\\w\\s]+)$" HEADER_2_REGEX = "^()([\\w\\s]+)\\.$" - STD_BANNED_WORDS = ['мы', 'моя', 'мои', 'моё', 'наш', 'наши', + STD_BANNED_WORDS = ('мы', 'моя', 'мои', 'моё', 'наш', 'наши', 'аттач', 'билдить', 'бинарник', 'валидный', 'дебаг', 'деплоить', 'десктопное', 'железо', - 'исходники', 'картинка', 'консольное', 'конфиг', 'кусок', 'либа', 'лог', 'мануал', 'машина', + 'исходники', 'картинка', 'консольное', 'конфиг', 'кусок', 'либа', 'лог', 'мануал', 'отнаследованный', 'парсинг', 'пост', 'распаковать', 'сбоит', 'скачать', 'склонировать', 'скрипт', 'тестить', 'тул', 'тула', 'тулза', 'фиксить', 'флажок', 'флаг', 'юзкейс', 'продакт', 'продакшн', - 'прод', 'фидбек', 'дедлайн', 'дэдлайн', 'оптимально', 'оптимальный', 'надежный', 'интуитивный', + 'прод', 'фидбек', 'дедлайн', 'дэдлайн', 'оптимально', 'надежный', 'интуитивный', 'хороший', 'плохой', 'идеальный', 'быстро', 'медленно', 'какой-нибудь', 'некоторый', 'почти' - ] # TODO: list of "warning" words + ) + STD_WARNED_WORDS = ('машина', 'оптимальный') # TODO: list of "warning" words STD_MIN_LIT_REF = 1 STD_MAX_LIT_REF = 1000 #just in case for future edit HEADER_1_STYLE = { @@ -98,9 +99,11 @@ class StyleCheckSettings: "style": HEADER_1_STYLE, "docx_style": ["heading 1"], "headers": ["Исходный код программы"], + "chapters_for_lit_ref": {}, "unify_regex": APPENDIX_UNIFY_REGEX, "regex": APPENDIX_REGEX, "banned_words": STD_BANNED_WORDS, + "warned_words": STD_WARNED_WORDS, 'min_count_for_banned_words_check': 3, 'max_count_for_banned_words_check': 6, 'min_ref_for_literature_references_check': STD_MIN_LIT_REF, @@ -111,6 +114,7 @@ class StyleCheckSettings: "style": HEADER_2_STYLE, "docx_style": ["heading 2"], "headers": ["Цель работы", "Выполнение работы", "Выводы"], + "chapters_for_lit_ref": {}, "unify_regex": None, "regex": HEADER_1_REGEX, } @@ -122,9 +126,12 @@ class StyleCheckSettings: "style": HEADER_1_STYLE, "docx_style": ["heading 2"], "headers": ["ВВЕДЕНИЕ", "ЗАКЛЮЧЕНИЕ", "СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ"], + "chapters_for_lit_ref":{}, + # { 'введение': [0, 1], 'определения, обозначения и сокращения': [2, 5], 'заключение': [1, 5] }, "unify_regex": None, "regex": HEADER_REGEX, "banned_words": STD_BANNED_WORDS, + "warned_words": STD_WARNED_WORDS, 'min_count_for_banned_words_check': 3, 'max_count_for_banned_words_check': 6, 'min_ref_for_literature_references_check': STD_MIN_LIT_REF, @@ -149,7 +156,8 @@ class StyleCheckSettings: "ПЛАН РАБОТЫ НА ВЕСЕННИЙ СЕМЕСТР", "ОТЗЫВ РУКОВОДИТЕЛЯ", "СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ"], "unify_regex": None, "regex": HEADER_REGEX, - "banned_words": STD_BANNED_WORDS + ['доработать', 'доработка', 'переписать', 'рефакторинг', 'исправление'] + "banned_words": STD_BANNED_WORDS + ('доработать', 'доработка', 'переписать', 'рефакторинг', 'исправление'), + "warned_words": STD_WARNED_WORDS }, } @@ -162,10 +170,11 @@ class StyleCheckSettings: "ПЛАН РАБОТЫ НА ОСЕННИЙ СЕМЕСТР", "СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ"], "unify_regex": None, "regex": HEADER_REGEX, - "banned_words": STD_BANNED_WORDS + ['доработать', 'доработка', 'переписать', 'рефакторинг', 'исправление'] + "banned_words": STD_BANNED_WORDS + ('доработать', 'доработка', 'переписать', 'рефакторинг', 'исправление'), + "warned_words": STD_WARNED_WORDS }, } - + NIR3_CONFIG = { 'any_header': { @@ -175,7 +184,8 @@ class StyleCheckSettings: "ПЛАН РАБОТЫ НА ВЕСЕННИЙ СЕМЕСТР", "ОТЗЫВ РУКОВОДИТЕЛЯ", "СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ"], "unify_regex": None, "regex": HEADER_REGEX, - "banned_words": STD_BANNED_WORDS + ['доработать', 'доработка', 'переписать', 'рефакторинг', 'исправление'] + "banned_words": STD_BANNED_WORDS + ('доработать', 'доработка', 'переписать', 'рефакторинг', 'исправление'), + "warned_words": STD_WARNED_WORDS }, } @@ -189,10 +199,13 @@ class StyleCheckSettings: "Методы обоснования", "Статья", ], + "chapters_for_lit_ref": {}, "header_for_report_section_component": "Поставленная цель и задачи", + "headers_for_lit_count": [], "unify_regex": None, "regex": HEADER_REGEX, "banned_words": STD_BANNED_WORDS, + "warned_words": STD_WARNED_WORDS, 'min_count_for_banned_words_check': 3, 'max_count_for_banned_words_check': 6, }, @@ -204,10 +217,13 @@ class StyleCheckSettings: "Характеристика выводов", "Статья", ], + "chapters_for_lit_ref": {}, "header_for_report_section_component": "", + "headers_for_lit_count": [], "unify_regex": None, "regex": HEADER_REGEX, "banned_words": STD_BANNED_WORDS, + "warned_words": STD_WARNED_WORDS, 'min_count_for_banned_words_check': 3, 'max_count_for_banned_words_check': 6, } @@ -230,6 +246,7 @@ class StyleCheckSettings: "unify_regex": None, "regex": HEADER_REGEX, "banned_words": STD_BANNED_WORDS, + "warned_words": STD_WARNED_WORDS, 'min_ref_for_literature_references_check': 1, 'mах_ref_for_literature_references_check': 1000, #just for future possible edit 'min_count_for_banned_words_check': 2, @@ -241,14 +258,15 @@ class StyleCheckSettings: "docx_style": ["heading 2"], "headers": ["Принцип отбора аналогов", "Критерии сравнения аналогов", - "Таблица сравнения аналогов", "Выводы по итогам сравнения", "Выбор метода решения", "Список использованных источников" ], + "chapters_for_lit_ref": {}, "unify_regex": None, "regex": HEADER_REGEX, "banned_words": STD_BANNED_WORDS, + "warned_words": STD_WARNED_WORDS, 'min_ref_for_literature_references_check': 3, 'mах_ref_for_literature_references_check': 1000, #just for future possible edit 'min_count_for_banned_words_check': 2, @@ -265,9 +283,11 @@ class StyleCheckSettings: "Заключение", "Список использованных источников" ], + "chapters_for_lit_ref": {}, "unify_regex": None, "regex": HEADER_REGEX, "banned_words": STD_BANNED_WORDS, + "warned_words": STD_WARNED_WORDS, 'min_ref_for_literature_references_check': 5, 'mах_ref_for_literature_references_check': 1000, #just for future possible edit 'min_count_for_banned_words_check': 2, diff --git a/app/routes/results.py b/app/routes/results.py index 91cc5b7f..95f4f508 100644 --- a/app/routes/results.py +++ b/app/routes/results.py @@ -3,6 +3,7 @@ from time import time from flask import Blueprint, Response, render_template +from flask_login import current_user, login_required from wsgiref.handlers import format_date_time as format_date from app.db import db_methods @@ -16,6 +17,7 @@ @results_bp.route("/", methods=["GET"]) +@login_required def results_main(_id): try: oid = ObjectId(_id) @@ -24,11 +26,15 @@ def results_main(_id): return render_template("./404.html") check = db_methods.get_check(oid) if check is not None: - # show processing time for user - avg_process_time = None if check.is_ended else db_methods.get_average_processing_time() - return render_template("./results.html", navi_upload=True, results=check, - columns=TABLE_COLUMNS, avg_process_time=avg_process_time, - stats=format_check(check.pack())) + # show check only for author or admin + if current_user.is_admin or current_user.username == check.user: + # show processing time for user + avg_process_time = None if check.is_ended else db_methods.get_average_processing_time() + return render_template("./results.html", navi_upload=True, results=check, + columns=TABLE_COLUMNS, avg_process_time=avg_process_time, + stats=format_check(check.pack())) + else: + return "У вас нет прав на просмотр результатов чужих проверок", 403 else: logger.info("Запрошенная проверка не найдена: " + _id) return render_template("./404.html") diff --git a/app/templates/404.html b/app/templates/404.html index bf8140dd..ad648890 100644 --- a/app/templates/404.html +++ b/app/templates/404.html @@ -4,7 +4,7 @@ {% block main %}
- Страница не найдена! +

Запрашиваемый ресурс не найден

diff --git a/assets/styles/404.css b/assets/styles/404.css index c833cc62..384902ba 100644 --- a/assets/styles/404.css +++ b/assets/styles/404.css @@ -1,3 +1,3 @@ #middle-container { - background-color: black; + background-color: rgb(255, 255, 255); } diff --git a/tests/test_recheck.py b/tests/test_recheck.py new file mode 100644 index 00000000..4fc8135c --- /dev/null +++ b/tests/test_recheck.py @@ -0,0 +1,36 @@ +import time +from basic_selenium_test import BasicSeleniumTest +from selenium.webdriver.common.by import By +from selenium.webdriver.support.wait import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC + + +class RecheckTestSelenium(BasicSeleniumTest): + + def test_recheck_file(self): + check_id = self.open_statistic() + if check_id: + URL = self.get_url(f"/recheck/{check_id}") + self.get_driver().get(URL) + obj = WebDriverWait(self.driver, 10).until( + EC.presence_of_element_located((By.ID, "results_title")) + ) + if "Производится проверка файла" in obj.text: + start_time = time.time() + max_time = 240 + while (time.time() - start_time) < max_time: + time.sleep(10) + try: + obj = WebDriverWait(self.driver, 10).until( + EC.presence_of_element_located((By.ID, "results_table")) + ) + if obj is not None: + self.assertNotEquals(obj, None) + return + except: + continue + self.fail("Result of check is not found") + else: + self.fail("No checking status after /recheck") + else: + self.skipTest("No check in system for testing recheck")