Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/main/check_packs/pack_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
["empty_task_page_check"],
["water_in_the_text_check"],
["report_task_tracker"],
["anti_plagiarism_check"],
["report_was_were_check"],
]

Expand Down
75 changes: 75 additions & 0 deletions app/main/checks/report_checks/anti_plagiarism_check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import re
from ..base_check import BaseReportCriterion, answer


class AntiPlagiarismCheck(BaseReportCriterion):
label = "Проверка на заимствования"
_description = ''
id = 'anti_plagiarism_check'

def __init__(self, file_info, originality_threshold=70):
super().__init__(file_info)
self.chapters = []
self.originality_threshold = originality_threshold

def late_init(self):
self.chapters = self.file.make_chapters(self.file_type['report_type'])

def check(self):
if self.file.page_counter() < 4:
return answer(False, "В отчете недостаточно страниц. Нечего проверять.")
self.late_init()
text = ""
user_result_str = ""
for chapter in self.chapters:
if 'список использованных источников' in chapter['text'].lower():
break
for child in chapter['child']:
text += " " + child['text'] # заменить на нужную для метода шинглов структуру
result = self.run_antiplagiarism(text)
originality_percent = result["originality"]
borrowed_fragments = result["fragments"]
if originality_percent < self.originality_threshold:
user_result_str += f"Обнаружены заимствования в тексте отчета. Уникальность работы составляет {originality_percent}%.<br>"
admin_result_str = user_result_str
admin_result_str += f"Ниже приведены фраменты, содержащие заимстования.<br><br>"
for i, fragment in enumerate(borrowed_fragments, start=1):
source_check_id = str(fragment.get('source', ''))
admin_result_str += (f"Заимствованный фрагмент №{i} находится на странице {fragment['page_in_doc']}. "
f"Источник: {fragment['source']}, страница {fragment['page_in_source']}."
f"Совпадение {fragment['percent']}%<br>"
f"Текст фрагмента:<br>"
f"{fragment['text']}<br>"
f"Подробнее: <a href=\"#\" target=\"_blank\" "
f"onclick=\"const antiPath = window.location.pathname.replace('/results/', '/anti_plagiarism/');"
f"window.open(`${{antiPath}}/{source_check_id}`, '_blank'); return false;\">"
f"перейти к сравнению</a><br><br>"
)
return answer(False, user_result_str, admin_result_str)
return answer(True, f"Уникальность текста {originality_percent}%. Проверка пройдена.")

def run_antiplagiarism(self, text):
# вызов реального алгоритма антиплагиата
originality = 65
fragments = [
{
"text": "пример заимствованного текста",
"source": "doc_id",
"percent": 90,
"page_in_doc": 5,
"page_in_source": 8

},
{
"text": "еще один заимствованный фрагмент",
"source": "doc_id2",
"percent": 70,
"page_in_doc": 7,
"page_in_source": 9
}
]

return {
"originality": originality,
"fragments": fragments
}
50 changes: 50 additions & 0 deletions app/routes/anti_plagiarism.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import bson
from bson import ObjectId

from flask import Blueprint, render_template, url_for
from flask_login import current_user, login_required

from app.db import db_methods
from app.root_logger import get_root_logger

anti_plagiarism = Blueprint('anti_plagiarism', __name__, template_folder='templates', static_folder='static')
logger = get_root_logger('web')


@anti_plagiarism.route("/<string:_id>/<string:source_check_id>", methods=["GET"])
@login_required
def anti_plagiarism_page(_id, source_check_id):
try:
oid = ObjectId(_id)
except bson.errors.InvalidId:
logger.error('_id exception:', exc_info=True)
return render_template("./404.html")

check = db_methods.get_check(oid)
if check is None:
logger.info("Запрошенная проверка не найдена: " + _id)
return render_template("./404.html")

try:
source_oid = ObjectId(_id)
except bson.errors.InvalidId:
logger.error('source_check_id exception:', exc_info=True)
return render_template("./404.html")
source_check = db_methods.get_check(source_oid)
if source_check is None:
logger.info("Запрошенная проверка не найдена: " + source_check_id)
return render_template("./404.html")
if not (current_user.is_admin):
return "У вас нет прав на просмотр", 403

fragments = []

student_pdf_url = url_for("get_pdf.get_pdf_main", _id=check.conv_pdf_fs_id)
source_pdf_url = url_for("get_pdf.get_pdf_main", _id=source_check.conv_pdf_fs_id)

return render_template(
"anti_plagiarism.html",
fragments=fragments,
student_pdf_url=student_pdf_url,
source_pdf_url=source_pdf_url,
)
12 changes: 12 additions & 0 deletions app/routes/results.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@
logger = get_root_logger('web')


def _prepare_verdicts_for_view(check, is_admin):
if not isinstance(check.enabled_checks, list):
return check

for criterion_info in check.enabled_checks:
verdict = criterion_info.get('verdict')
if isinstance(verdict, (list, tuple)) and len(verdict) > 1:
criterion_info['verdict'] = [verdict[1] if is_admin else verdict[0]]
return check


@results_bp.route("/<string:_id>", methods=["GET"])
@login_required
def results_main(_id):
Expand All @@ -30,6 +41,7 @@ def results_main(_id):
if current_user.is_admin or current_user.username == check.user or check.user == "api_access_token":
# show processing time for user
avg_process_time = None if check.is_ended else db_methods.get_average_processing_time()
check = _prepare_verdicts_for_view(check, current_user.is_admin)
return render_template("./results.html", navi_upload=True, results=check,
columns=TABLE_COLUMNS, avg_process_time=avg_process_time,
stats=format_check(check.pack()))
Expand Down
2 changes: 2 additions & 0 deletions app/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
from routes.version import version
from routes.capacity import capacity
from routes.profile import profile
from routes.anti_plagiarism import anti_plagiarism

from server_consts import UPLOAD_FOLDER

Expand Down Expand Up @@ -87,6 +88,7 @@
app.register_blueprint(version, url_prefix='/version')
app.register_blueprint(capacity, url_prefix='/capacity')
app.register_blueprint(profile, url_prefix='/profile')
app.register_blueprint(anti_plagiarism, url_prefix='/anti_plagiarism')

app.logger.addHandler(get_logging_stdout_handler())
app.logger.propagate = False
Expand Down
49 changes: 49 additions & 0 deletions app/templates/anti_plagiarism.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{% extends "root.html" %}

{% block title %}Сравнение документов{% endblock %}

{% block main %}
<div class="holder" id="anti_plagiarism_holder">
<h3 id="results_title" class="texteous ins" style="text-align: center;">
Проверка на заимствования
</h3>

<div class="pdf_compare_container">
<div class="pdf_block">
<h5 class="title">Документ студента</h5>
<div class="pdf_controls">
<button class="btn" id="student-prev-page"><i class="bi bi-arrow-left"></i></button>
<button class="btn" id="student-next-page"><i class="bi bi-arrow-right"></i></button>
<span class="page-info">
Страница <span id="student-page-num"></span> из <span id="student-page-count"></span>
</span>
<a href="{{ student_pdf_url }}" id="student-pdf-download" class="hidden"></a>
</div>
<div class="pdf_canvas_frame" id="student-canvas-frame">
<canvas id="student-canvas" style="direction:ltr;"></canvas>
</div>
</div>

<div class="pdf_block">
<h5 class="title">Документ для сравнения</h5>
<div class="pdf_controls">
<button class="btn" id="source-prev-page"><i class="bi bi-arrow-left"></i></button>
<button class="btn" id="source-next-page"><i class="bi bi-arrow-right"></i></button>
<span class="page-info">
Страница <span id="source-page-num"></span> из <span id="source-page-count"></span>
</span>
<a href="{{ source_pdf_url }}" id="source-pdf-download" class="hidden"></a>
</div>
<div class="pdf_canvas_frame" id="source-canvas-frame">
<canvas id="source-canvas" style="direction:ltr;"></canvas>
</div>
</div>
</div>
</div>
{% endblock %}
{% block script %}
<script>
const fragments = {{ fragments|tojson|safe }};
</script>
<script src="{{ url_for('static', filename='anti_plagiarism.js') }}"></script>
{% endblock script %}
123 changes: 123 additions & 0 deletions assets/scripts/anti_plagiarism.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import '../styles/anti_plagiarism.css';
import * as pdfjsLib from 'pdfjs-dist';
import pdfjsWorker from "pdfjs-dist/build/pdf.worker.entry";

$(function(){
if ($("#student-pdf-download").length === 0 || $("#source-pdf-download").length === 0) {
return;
}

pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsWorker;

function initPdfViewer(canvasId, canvasFrameId, prevBtnId, nextBtnId, pageNumId, pageCountId, pdfUrl) {
let pdfDoc = null;
let pageNum = 1;
let pageIsRendering = false;
let pageNumIsPending = null;

const canvas = document.getElementById(canvasId);
const canvasFrame = document.getElementById(canvasFrameId);
const ctx = canvas.getContext('2d');

const renderPage = num => {
pageIsRendering = true;

pdfDoc.getPage(num).then(page => {
const baseViewport = page.getViewport({scale: 1});
const targetWidth = canvasFrame.clientWidth || baseViewport.width;
const scale = targetWidth / baseViewport.width;
const viewport = page.getViewport({scale: scale});

canvas.height = viewport.height;
canvas.width = viewport.width;

const renderCtx = {
canvasContext: ctx,
viewport
};

page.render(renderCtx).promise.then(() => {
pageIsRendering = false;

if (pageNumIsPending !== null) {
renderPage(pageNumIsPending);
pageNumIsPending = null;
}
});

document.getElementById(pageNumId).textContent = num;
});
};

const queueRenderPage = num => {
if (pageIsRendering) {
pageNumIsPending = num;
} else {
renderPage(num);
}
};

const showPrevPage = () => {
if (pageNum <= 1) {
return;
}
pageNum--;
queueRenderPage(pageNum);
};

const showNextPage = () => {
if (pageNum >= pdfDoc.numPages) {
return;
}
pageNum++;
queueRenderPage(pageNum);
};

const handleResize = () => {
if (!pdfDoc) {
return;
}
queueRenderPage(pageNum);
};

pdfjsLib
.getDocument(pdfUrl)
.promise.then(pdfDoc_ => {
pdfDoc = pdfDoc_;
document.getElementById(pageCountId).textContent = pdfDoc.numPages;
renderPage(pageNum);
});

$('#' + prevBtnId).click(showPrevPage);
$('#' + nextBtnId).click(showNextPage);
window.addEventListener('resize', handleResize);

if (window.ResizeObserver) {
const resizeObserver = new ResizeObserver(handleResize);
resizeObserver.observe(canvasFrame);
}
}

const studentPdfUrl = $("#student-pdf-download").attr('href');
const sourcePdfUrl = $("#source-pdf-download").attr('href');

initPdfViewer(
'student-canvas',
'student-canvas-frame',
'student-prev-page',
'student-next-page',
'student-page-num',
'student-page-count',
studentPdfUrl
);

initPdfViewer(
'source-canvas',
'source-canvas-frame',
'source-prev-page',
'source-next-page',
'source-page-num',
'source-page-count',
sourcePdfUrl
);
});
48 changes: 48 additions & 0 deletions assets/styles/anti_plagiarism.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#anti_plagiarism_holder {
overflow: auto;
}

#results_title {
margin-top: 1rem;
}

.pdf_compare_container {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 2rem;
margin-bottom: 1rem;
}

.pdf_block {
width: 600px;
max-width: 100%;
}

.pdf_block .title {
margin-bottom: 1rem;
text-align: center;
}

.pdf_controls {
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
margin-bottom: 0.5rem;
}

.pdf_canvas_frame {
width: 100%;
aspect-ratio: 210 / 297;
overflow: hidden;
background: var(--bright-color);
border: 1px solid var(--border-color);
}

canvas {
display: block;
width: 100%;
height: auto;
background: var(--bright-color);
}
Loading
Loading