diff --git a/app/main/checks/presentation_checks/excessive_decimal_places.py b/app/main/checks/presentation_checks/excessive_decimal_places.py
new file mode 100644
index 00000000..87bb769b
--- /dev/null
+++ b/app/main/checks/presentation_checks/excessive_decimal_places.py
@@ -0,0 +1,49 @@
+import re
+from ..base_check import BasePresCriterion, answer
+from utils.decimal_checker import DecimalPlacesChecker, DocumentType
+
+
+class PresExcessiveDecimalPlacesCheck(BasePresCriterion):
+ description = 'Проверка на избыточное количество десятичных знаков в числах'
+ label = "Проверка на избыточное количество десятичных знаков"
+ id = 'pres_excessive_decimal_places_check'
+
+ def __init__(self, file_info, max_decimal_places=2, max_number_of_violations=1):
+ super().__init__(file_info)
+ self.max_decimal_places = max_decimal_places
+ self.max_number_of_violations = max_number_of_violations
+ self.checker = DecimalPlacesChecker(max_decimal_places, DocumentType.PRESENTATION)
+
+ def check(self):
+
+ detected_slides = {}
+ total_violations = 0
+
+ for slide_num, slide_text in enumerate(self.file.get_text_from_slides()):
+ lines = re.split(r'\n', slide_text)
+
+ slide_violations = self.checker.find_violations_in_lines(lines)
+
+ for line_idx, number_str, decimal_places, line in slide_violations:
+ total_violations += 1
+
+ if slide_num not in detected_slides:
+ detected_slides[slide_num] = []
+
+ violation_msg = self.checker.format_violation_message(
+ line_idx, line, number_str, decimal_places
+ )
+ detected_slides[slide_num].append(violation_msg)
+
+ if total_violations > self.max_number_of_violations:
+ result_str = self.checker.format_failure_message(
+ total_violations,
+ detected_slides,
+ format_page_link_fn=self.format_page_link
+ )
+ result_score = 0
+ else:
+ result_str = self.checker.format_success_message()
+ result_score = 1
+
+ return answer(result_score, result_str)
diff --git a/app/main/checks/report_checks/excessive_decimal_places_check.py b/app/main/checks/report_checks/excessive_decimal_places_check.py
new file mode 100644
index 00000000..796f2952
--- /dev/null
+++ b/app/main/checks/report_checks/excessive_decimal_places_check.py
@@ -0,0 +1,51 @@
+import re
+from ..base_check import BaseReportCriterion, answer
+from utils.decimal_checker import DecimalPlacesChecker
+
+
+class ReportExcessiveDecimalPlacesCheck(BaseReportCriterion):
+ label = "Проверка на избыточное количество десятичных знаков"
+ description = 'Проверка на избыточное количество десятичных знаков в числах'
+ id = 'excessive_decimal_places_check'
+
+ def __init__(self, file_info, max_decimal_places=2, max_number_of_violations=2):
+ super().__init__(file_info)
+ self.max_decimal_places = max_decimal_places
+ self.max_number_of_violations = max_number_of_violations
+ self.checker = DecimalPlacesChecker(max_decimal_places)
+
+ def check(self):
+ if self.file.page_counter() < 4:
+ return answer(False, "В отчете недостаточно страниц. Нечего проверять.")
+
+ detected_pages = {}
+ total_violations = 0
+
+ for page_num, page_text in self.file.pdf_file.get_text_on_page().items():
+ lines = re.split(r'\n', page_text)
+
+ page_violations = self.checker.find_violations_in_lines(lines)
+
+ for line_idx, number_str, decimal_places, line in page_violations:
+ total_violations += 1
+
+ if page_num not in detected_pages:
+ detected_pages[page_num] = []
+
+ violation_msg = self.checker.format_violation_message(
+ line_idx, line, number_str, decimal_places
+ )
+ detected_pages[page_num].append(violation_msg)
+
+ if total_violations > self.max_number_of_violations:
+ result_str = self.checker.format_failure_message(
+ total_violations,
+ detected_pages,
+ format_page_link_fn=self.format_page_link
+ )
+ result_score = 0
+ else:
+ result_str = self.checker.format_success_message()
+ result_score = 1.0
+
+ return answer(result_score, result_str)
diff --git a/app/utils/decimal_checker.py b/app/utils/decimal_checker.py
new file mode 100644
index 00000000..47d9dd13
--- /dev/null
+++ b/app/utils/decimal_checker.py
@@ -0,0 +1,126 @@
+import re
+from enum import Enum
+
+class DocumentType(Enum):
+ REPORT = 'report'
+ PRESENTATION = 'pres'
+
+class DecimalPlacesChecker:
+ """
+ Класс для проверки чисел на избыточное количество десятичных знаков.
+ Игнорирует IP-адреса, версии ПО и другие составные числа.
+
+ Проверяет:
+ - Обычные числа с десятичными знаками (например, -3.14159)
+ Не проверяет:
+ - IP-адреса (например, 192.168.1.1)
+ - Версии ПО (например, 1.2.3.4)
+ - Другие составные числа
+ """
+
+ DECIMAL_PATTERN = r'(? 0 else ' '
+
+ if char_before == '.':
+ return False
+
+ if end_pos < len(text):
+ char_after = text[end_pos]
+ if char_after == '.' and end_pos + 1 < len(text) and text[end_pos + 1].isdigit():
+ return False
+
+ return True
+
+ def count_decimal_places(self, number_str):
+ normalized = number_str.replace(',', '.')
+
+ if '.' in normalized:
+ decimal_part = normalized.split('.')[1]
+ return len(decimal_part)
+
+ return 0
+
+ def has_excessive_decimals(self, number_str):
+ return self.count_decimal_places(number_str) > self.max_decimal_places
+
+ def find_violations_in_text(self, text):
+ violations = []
+ matches = re.finditer(self.DECIMAL_PATTERN, text)
+
+ for match in matches:
+ if not self.is_valid_number(match, text):
+ continue
+
+ number_str = match.group()
+ decimal_places = self.count_decimal_places(number_str)
+
+ if decimal_places > self.max_decimal_places:
+ violations.append((number_str, decimal_places, match))
+
+ return violations
+
+ def find_violations_in_lines(self, lines):
+ violations = []
+
+ for line_idx, line in enumerate(lines):
+ line_violations = self.find_violations_in_text(line)
+
+ for number_str, decimal_places, match in line_violations:
+ violations.append((line_idx, number_str, decimal_places, line))
+
+ return violations
+
+ def highlight_number(self, line, number_str):
+ return line.replace(number_str, f'{number_str}', 1)
+
+ def format_violation_message(self, line_idx, line, number_str, decimal_places):
+ highlighted_line = self.highlight_number(line, number_str)
+ return (
+ f'Строка {line_idx + 1}: {highlighted_line} '
+ f'(найдено {decimal_places} знаков после запятой, '
+ f'максимум: {self.max_decimal_places})'
+ )
+
+ def format_success_message(self):
+ if self.type == DocumentType.REPORT:
+ document_type = "документе"
+ elif self.type == DocumentType.PRESENTATION:
+ document_type = "презентации"
+ return (
+ f'Проверка пройдена! Все числа в {document_type} имеют допустимое количество '
+ f'десятичных знаков (не более {self.max_decimal_places}).'
+ )
+
+ def format_failure_message(self, total_violations,
+ violations_by_location,
+ format_page_link_fn=None):
+ if self.type == DocumentType.REPORT:
+ location_label = "Страница"
+ elif self.type == DocumentType.PRESENTATION:
+ location_label = "Слайд"
+ result_str = (
+ f'Найдены числа с избыточным количеством десятичных знаков!
'
+ f'Максимально допустимое количество знаков после запятой: {self.max_decimal_places}
'
+ f'Всего нарушений: {total_violations}
'
+ )
+
+ for location_num, violations in violations_by_location.items():
+ if format_page_link_fn:
+ location_str = f'{location_label} {format_page_link_fn([location_num])}'
+ else:
+ location_str = f'{location_label} №{location_num}'
+
+ result_str += f'{location_str}:
'
+ result_str += '
'.join(violations)
+ result_str += '
'
+
+ return result_str