DocDoeAI / src /lib /api /normalizers.ts
asnannp's picture
deploy: sync backend to Space root (learn-lesson HF cache fix)
65d68f5
Raw History Blame Contribute Delete
7.5 kB
import type {
ApiDocument,
DocumentStatus,
ExamModeOutput,
FlashcardResponseItem,
FlashcardsPayload,
NotesOutput,
PreviousPaperAnalysisResponse,
QuizQuestionResponse,
QuizQuestionsPayload,
StudyPathResponse,
VideoPlanV2Response,
VideoRenderJob,
VideoRenderJobStatus,
VideoScenePlanResponse,
} from "@/lib/api/types";
type UnknownRecord = Record<string, unknown>;
function isRecord(value: unknown): value is UnknownRecord {
return Boolean(value && typeof value === "object" && !Array.isArray(value));
}
function readString(value: unknown, fallback = "") {
return typeof value === "string" && value.trim() ? value : fallback;
}
function readStringArray(value: unknown): string[] {
if (!Array.isArray(value)) return [];
return value.map((item) => String(item)).filter((item) => item.trim().length > 0);
}
export function normalizeDocument(value: unknown): ApiDocument {
const record = isRecord(value) ? value : {};
const now = new Date().toISOString();
return {
id: readString(record.id, `doc-local-${Date.now()}`),
user_id: readString(record.user_id, "demo-user-student"),
title: readString(record.title, "Study source"),
file_name: readString(record.file_name, "source.pdf"),
file_type: readString(record.file_type, "PDF"),
subject: typeof record.subject === "string" ? record.subject : null,
chapter: typeof record.chapter === "string" ? record.chapter : null,
syllabus: typeof record.syllabus === "string" ? record.syllabus : null,
status: normalizeDocumentStatus(record.status),
// extracted_text removed — raw OCR text is not returned by the API
extracted_text_length: typeof record.extracted_text_length === "number" ? record.extracted_text_length : null,
chunk_count: typeof record.chunk_count === "number" ? record.chunk_count : 0,
created_at: readString(record.created_at, now),
};
}
export function normalizeDocumentStatus(value: unknown): DocumentStatus {
if (value === "ready" || value === "processing" || value === "failed" || value === "uploaded") {
return value;
}
return "ready";
}
export function normalizeNotes(value: unknown): NotesOutput {
const record = isRecord(value) ? value : {};
// Backend returns exam_keywords; exam_focus is a frontend-side alias
const examKeywords = readStringArray(record.exam_keywords);
return {
title: readString(record.title, "Smart Notes"),
summary: typeof record.summary === "string" ? record.summary : undefined,
student_level_summary: typeof record.student_level_summary === "string" ? record.student_level_summary : undefined,
key_points: readStringArray(record.key_points).length ? readStringArray(record.key_points) : ["Review the main ideas first."],
important_keywords: readStringArray(record.important_keywords),
important_definitions: readStringArray(record.important_definitions),
formulas: readStringArray(record.formulas),
diagrams_to_practice: readStringArray(record.diagrams_to_practice),
memory_tricks: readStringArray(record.memory_tricks),
exam_keywords: examKeywords,
exam_focus: readStringArray(record.exam_focus).length ? readStringArray(record.exam_focus) : examKeywords,
possible_exam_questions: readStringArray(record.possible_exam_questions),
last_minute_revision: readStringArray(record.last_minute_revision),
quick_check: readStringArray(record.quick_check),
source_summary: typeof record.source_summary === "string" ? record.source_summary : undefined,
scoring_keywords: readStringArray(record.scoring_keywords),
common_mistakes: readStringArray(record.common_mistakes),
ten_minute_revision: readStringArray(record.ten_minute_revision),
next_study_action: typeof record.next_study_action === "string" ? record.next_study_action : undefined,
caution_note: typeof record.caution_note === "string" ? record.caution_note : undefined,
};
}
export function normalizeQuiz(value: unknown): QuizQuestionResponse[] {
const payload: QuizQuestionsPayload | unknown = value;
if (Array.isArray(payload)) return payload.filter(isRecord).map(normalizeQuizQuestion);
if (isRecord(payload) && Array.isArray(payload.questions)) {
return payload.questions.filter(isRecord).map(normalizeQuizQuestion);
}
return [];
}
function normalizeQuizQuestion(value: UnknownRecord): QuizQuestionResponse {
return {
question: readString(value.question, "Practice question"),
type: readString(value.type, "MCQ"),
options: readStringArray(value.options),
answer: readString(value.answer, "Review the explanation."),
explanation: typeof value.explanation === "string" ? value.explanation : undefined,
difficulty: readString(value.difficulty, "Practice"),
topic: typeof value.topic === "string" ? value.topic : undefined,
why_this_matters: typeof value.why_this_matters === "string" ? value.why_this_matters : undefined,
common_trap: typeof value.common_trap === "string" ? value.common_trap : undefined,
};
}
export function normalizeFlashcards(value: unknown): FlashcardResponseItem[] {
const payload: FlashcardsPayload | unknown = value;
if (Array.isArray(payload)) return payload.filter(isRecord).map(normalizeFlashcard);
if (isRecord(payload) && Array.isArray(payload.cards)) {
return payload.cards.filter(isRecord).map(normalizeFlashcard);
}
return [];
}
function normalizeFlashcard(value: UnknownRecord): FlashcardResponseItem {
return {
front: readString(value.front, "Key idea"),
back: readString(value.back, "Short explanation"),
hint: typeof value.hint === "string" ? value.hint : undefined,
tag: typeof value.tag === "string" ? value.tag : undefined,
topic: typeof value.topic === "string" ? value.topic : undefined,
memory_hook: typeof value.memory_hook === "string" ? value.memory_hook : undefined,
exam_use: typeof value.exam_use === "string" ? value.exam_use : undefined,
};
}
export function normalizePYQAnalysis(value: unknown): PreviousPaperAnalysisResponse | null {
return isRecord(value) && Array.isArray(value.repeated_topics)
? (value as PreviousPaperAnalysisResponse)
: null;
}
export function normalizeStudyPlan(value: unknown): StudyPathResponse | null {
return isRecord(value) && Array.isArray(value.blocks) ? (value as StudyPathResponse) : null;
}
export function normalizeExamAnswer(value: unknown): ExamModeOutput | null {
return isRecord(value) ? (value as ExamModeOutput) : null;
}
export function normalizeVideoPlan(value: unknown): VideoScenePlanResponse | VideoPlanV2Response | null {
if (!isRecord(value)) return null;
if (Array.isArray(value.scenes) || Array.isArray(value.sections)) {
return value as VideoScenePlanResponse | VideoPlanV2Response;
}
return null;
}
export function normalizeRenderJob(value: unknown): VideoRenderJob | null {
return isRecord(value) && typeof value.id === "string" ? (value as VideoRenderJob) : null;
}
export function normalizeRenderStatus(value: unknown): VideoRenderJobStatus {
if (
value === "ready" ||
value === "failed" ||
value === "queued" ||
value === "running" ||
value === "planning" ||
value === "script_ready" ||
value === "audio_generating" ||
value === "visuals_generating" ||
value === "rendering" ||
value === "uploading" ||
value === "complete"
) {
return value;
}
if (value === "completed") return "complete";
if (value === "processing") return "running";
return "queued";
}