261 lines
8.3 KiB
TypeScript
261 lines
8.3 KiB
TypeScript
// utils/transformPlagiatResponse.ts
|
|
|
|
interface HighlightSegment {
|
|
text: string;
|
|
plagiarized: boolean;
|
|
}
|
|
|
|
interface SemanticMetrics {
|
|
totalWords: number;
|
|
uniqueWords: number;
|
|
lexicalUniqueness: number;
|
|
avgWordLength: number;
|
|
geoAvgWordLength: number;
|
|
minWordLength: number;
|
|
maxWordLength: number;
|
|
sentences: number;
|
|
avgWordsPerSentence: number;
|
|
polysyllabicWords: number;
|
|
polysyllabicPercent: number;
|
|
totalChars: number;
|
|
charsNoSpaces: number;
|
|
vowels: number;
|
|
consonants: number;
|
|
punctuation: number;
|
|
uppercase: number;
|
|
lowercase: number;
|
|
digits: number;
|
|
capsLockWords: number;
|
|
stopWords: number;
|
|
stopWordsPercent: number;
|
|
junkWords: number;
|
|
junkPercent: number;
|
|
maxConsecutiveRepeats: number;
|
|
top5Words: string;
|
|
spamRatio: number;
|
|
hasHtml: boolean;
|
|
hasEmail: boolean;
|
|
hasUrl: boolean;
|
|
hasDate: boolean;
|
|
hasPhone: boolean;
|
|
startsEqualsEnd: boolean;
|
|
isPalindrome: boolean;
|
|
hasQuestion: boolean;
|
|
hasExclamation: boolean;
|
|
paragraphs: number;
|
|
lines: number;
|
|
latinPercent: number;
|
|
cyrillicPercent: number;
|
|
longWords16plus: number;
|
|
}
|
|
|
|
interface Certificate {
|
|
verificationCode: string;
|
|
issuerName: string;
|
|
issuedAt: string;
|
|
expiresAt: string;
|
|
downloadUrl: string;
|
|
}
|
|
|
|
interface Source {
|
|
url: string;
|
|
matchPercentage: number;
|
|
module: string;
|
|
}
|
|
|
|
interface PlagiatData {
|
|
name: string;
|
|
initials: string;
|
|
email: string;
|
|
location: string;
|
|
fileName: string;
|
|
checkedAt: string;
|
|
humanPercent: number;
|
|
aiPercent: number;
|
|
plagiarismPercent: number;
|
|
originalityPercent: number;
|
|
citationPercent: number;
|
|
highlightedText: HighlightSegment[];
|
|
sources: Source[];
|
|
semantic: SemanticMetrics;
|
|
certificate: Certificate | null;
|
|
}
|
|
|
|
interface ApiRes {
|
|
ai: number;
|
|
hash: string;
|
|
text: string;
|
|
citation: number;
|
|
plagiarism: number;
|
|
originality: number;
|
|
}
|
|
|
|
interface ApiAnalyzeText {
|
|
Цифр: number;
|
|
Гласных: number;
|
|
'URL найден': string;
|
|
'Стоп-слов': number;
|
|
'ТОП-5 слов': string;
|
|
'Email найден': string;
|
|
Палиндром: string;
|
|
Согласных: number;
|
|
'Слов в CAPSLOCK': number;
|
|
'Без пробелов': number;
|
|
'Дата найдена': string;
|
|
'Доля мусора (%)': number;
|
|
'Начало = Конец': string;
|
|
'Строчных букв': number;
|
|
'Заглавных букв': number;
|
|
'Символов всего': number;
|
|
'Телефон найден': string;
|
|
'Доля латиницы (%)': number;
|
|
'Мин. длина слова': number;
|
|
'Уникальных слов': number;
|
|
'Доля стоп-слов (%)': number;
|
|
'Наличие HTML-тегов': string;
|
|
'Доля кириллицы (%)': number;
|
|
'Количество строк': number;
|
|
'Макс. длина слова': number;
|
|
'Знаков препинания': number;
|
|
'Мусорные слова (шт)': number;
|
|
'Средняя длина слов': number;
|
|
'Количество абзацев': number;
|
|
'Вопросительный знак': string;
|
|
'Восклицательный знак': string;
|
|
'Заспамленность (max/total)': number;
|
|
'Общее количество слов': number;
|
|
'Слов длиной 16+ символов': number;
|
|
'Количество предложений': number;
|
|
'Полисиллабических слов': number;
|
|
'Макс. подряд повторов слова': number;
|
|
'Лексическая уникальность (%)': number;
|
|
'Доля полисиллабических слов (%)': number;
|
|
'Геометрическая средняя длина слов': number;
|
|
'Среднее количество слов в предложении': number;
|
|
}
|
|
|
|
interface ApiResultJson {
|
|
ok: boolean;
|
|
res: ApiRes;
|
|
error: string;
|
|
success: string;
|
|
text_res: string;
|
|
analyze_text: ApiAnalyzeText;
|
|
}
|
|
|
|
interface ApiResult {
|
|
id: number;
|
|
document: number;
|
|
result_json: ApiResultJson;
|
|
created_at: string;
|
|
updated_at: string;
|
|
}
|
|
|
|
interface ApiDocument {
|
|
id: number;
|
|
title: string;
|
|
file: string;
|
|
certificate: boolean;
|
|
text: string;
|
|
created_at: string;
|
|
updated_at: string;
|
|
results: ApiResult[];
|
|
}
|
|
|
|
// ─── Parsers ──────────────────────────────────────────────────────────────────
|
|
|
|
function parseHighlightedText(textRes: string): HighlightSegment[] {
|
|
const segments: HighlightSegment[] = [];
|
|
const parts = textRes.split(/(<sel>[\s\S]*?<\/sel>)/g);
|
|
|
|
for (const part of parts) {
|
|
if (part.startsWith('<sel>')) {
|
|
segments.push({ text: part.replace(/<\/?sel>/g, ''), plagiarized: true });
|
|
} else if (part) {
|
|
segments.push({ text: part, plagiarized: false });
|
|
}
|
|
}
|
|
|
|
return segments;
|
|
}
|
|
|
|
function parseAnalyzeText(a: ApiAnalyzeText): SemanticMetrics {
|
|
const bool = (v: string): boolean => v === 'Да';
|
|
|
|
return {
|
|
totalWords: a['Общее количество слов'],
|
|
uniqueWords: a['Уникальных слов'],
|
|
lexicalUniqueness: a['Лексическая уникальность (%)'],
|
|
avgWordLength: a['Средняя длина слов'],
|
|
geoAvgWordLength: a['Геометрическая средняя длина слов'],
|
|
minWordLength: a['Мин. длина слова'],
|
|
maxWordLength: a['Макс. длина слова'],
|
|
sentences: a['Количество предложений'],
|
|
avgWordsPerSentence: a['Среднее количество слов в предложении'],
|
|
polysyllabicWords: a['Полисиллабических слов'],
|
|
polysyllabicPercent: a['Доля полисиллабических слов (%)'],
|
|
totalChars: a['Символов всего'],
|
|
charsNoSpaces: a['Без пробелов'],
|
|
vowels: a['Гласных'],
|
|
consonants: a['Согласных'],
|
|
punctuation: a['Знаков препинания'],
|
|
uppercase: a['Заглавных букв'],
|
|
lowercase: a['Строчных букв'],
|
|
digits: a['Цифр'],
|
|
capsLockWords: a['Слов в CAPSLOCK'],
|
|
stopWords: a['Стоп-слов'],
|
|
stopWordsPercent: a['Доля стоп-слов (%)'],
|
|
junkWords: a['Мусорные слова (шт)'],
|
|
junkPercent: a['Доля мусора (%)'],
|
|
maxConsecutiveRepeats: a['Макс. подряд повторов слова'],
|
|
top5Words: a['ТОП-5 слов'],
|
|
spamRatio: a['Заспамленность (max/total)'],
|
|
hasHtml: bool(a['Наличие HTML-тегов']),
|
|
hasEmail: bool(a['Email найден']),
|
|
hasUrl: bool(a['URL найден']),
|
|
hasDate: bool(a['Дата найдена']),
|
|
hasPhone: bool(a['Телефон найден']),
|
|
startsEqualsEnd: bool(a['Начало = Конец']),
|
|
isPalindrome: bool(a['Палиндром']),
|
|
hasQuestion: bool(a['Вопросительный знак']),
|
|
hasExclamation: bool(a['Восклицательный знак']),
|
|
paragraphs: a['Количество абзацев'],
|
|
lines: a['Количество строк'],
|
|
latinPercent: a['Доля латиницы (%)'],
|
|
cyrillicPercent: a['Доля кириллицы (%)'],
|
|
longWords16plus: a['Слов длиной 16+ символов'],
|
|
};
|
|
}
|
|
|
|
// ─── Main transformer ─────────────────────────────────────────────────────────
|
|
|
|
export function transformPlagiatResponse(apiDoc: ApiDocument): PlagiatData {
|
|
const result = apiDoc.results[0];
|
|
const res = result.result_json.res;
|
|
const analyze = result.result_json.analyze_text;
|
|
|
|
const nameParts = apiDoc.text.trim().split(/\s+/);
|
|
const initials = nameParts
|
|
.slice(0, 2)
|
|
.map((n) => n[0]?.toUpperCase() ?? '')
|
|
.join('');
|
|
|
|
return {
|
|
name: apiDoc.text,
|
|
initials,
|
|
email: '',
|
|
location: '',
|
|
fileName: apiDoc.file.split('/').pop() ?? apiDoc.file,
|
|
checkedAt: apiDoc.created_at.slice(0, 10),
|
|
humanPercent: 100 - res.ai,
|
|
aiPercent: res.ai,
|
|
plagiarismPercent: res.plagiarism,
|
|
originalityPercent: res.originality,
|
|
citationPercent: res.citation,
|
|
highlightedText: parseHighlightedText(result.result_json.text_res),
|
|
sources: [],
|
|
semantic: parseAnalyzeText(analyze),
|
|
certificate: null,
|
|
};
|
|
}
|