// 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(/([\s\S]*?<\/sel>)/g); for (const part of parts) { if (part.startsWith('')) { 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, }; }