import { Module } from "."; export type Exam = ReadingExam | ListeningExam | WritingExam | SpeakingExam | LevelExam; export type Variant = "full" | "partial"; export type InstructorGender = "male" | "female" | "varied"; export type Difficulty = BasicDifficulty | CEFRLevels; // Left easy, medium and hard to support older exam versions export type BasicDifficulty = "easy" | "medium" | "hard"; export type CEFRLevels = "A1" | "A2" | "B1" | "B2" | "C1" | "C2"; export const ACCESSTYPE = ["public", "private", "confidential"] as const; export type AccessType = typeof ACCESSTYPE[number]; export interface ExamBase { id: string; module: Module; minTimer: number; isDiagnostic: boolean; variant?: Variant; difficulty?: Difficulty | Difficulty[]; owners?: string[]; entities?: string[] shuffle?: boolean; createdBy?: string; // option as it has been added later createdAt?: string; // option as it has been added later access: AccessType; label?: string; requiresApproval?: boolean; } export interface ReadingExam extends ExamBase { module: "reading"; parts: ReadingPart[]; type: "academic" | "general"; } export interface Section { intro?: string; category?: string; sectionId?: number; } export interface ReadingPart extends Section { text: { title: string; content: string; }; exercises: Exercise[]; } export interface LevelExam extends ExamBase { module: "level"; parts: LevelPart[]; } export interface LevelPart extends Section { // to support old exams that have reading passage mc on context context?: string; exercises: Exercise[]; audio?: { source: string; repeatableTimes: number; // *The amount of times the user is allowed to repeat the audio, 0 for unlimited }; script?: Script; text?: { title: string; content: string; }; } export interface ListeningExam extends ExamBase { parts: ListeningPart[]; module: "listening"; instructions?: string; } export type Message = { name: string; gender: string; text: string; voice?: string; }; export type Script = Message[] | string; export interface ListeningPart extends Section { audio?: { source: string; repeatableTimes: number; // *The amount of times the user is allowed to repeat the audio, 0 for unlimited }; script?: Script; exercises: Exercise[]; } export interface UserSolution { id?: string; solutions: any[]; module?: Module; exam?: string; type: string; score: { correct: number; total: number; missing: number; }; exercise: string; isDisabled?: boolean; shuffleMaps?: ShuffleMap[]; isPractice?: boolean } export interface WritingExam extends ExamBase { module: "writing"; enableNavigation?: boolean; exercises: WritingExercise[]; type?: "academic" | "general"; } interface WordCounter { type: "min" | "max"; limit: number; } export interface SpeakingExam extends ExamBase { module: "speaking"; exercises: (SpeakingExercise | InteractiveSpeakingExercise)[]; instructorGender: InstructorGender; } export type Exercise = | FillBlanksExercise | TrueFalseExercise | MatchSentencesExercise | MultipleChoiceExercise | WriteBlanksExercise | WritingExercise | SpeakingExercise | InteractiveSpeakingExercise; export interface Evaluation { comment: string; overall: number; task_response: { [key: string]: number | { grade: number; comment: string } }; misspelled_pairs?: { correction: string | null; misspelled: string }[]; } type InteractivePerfectAnswerKey = `perfect_answer_${number}`; type InteractiveTranscriptKey = `transcript_${number}`; type InteractiveFixedTextKey = `fixed_text_${number}`; type InteractivePerfectAnswerType = { [key in InteractivePerfectAnswerKey]: { answer: string } }; type InteractiveTranscriptType = { [key in InteractiveTranscriptKey]?: string }; type InteractiveFixedTextType = { [key in InteractiveFixedTextKey]?: string }; interface InteractiveSpeakingEvaluation extends Evaluation, InteractivePerfectAnswerType, InteractiveTranscriptType, InteractiveFixedTextType { } interface SpeakingEvaluation extends CommonEvaluation { perfect_answer_1?: string; transcript_1?: string; fixed_text_1?: string; } interface CommonEvaluation extends Evaluation { perfect_answer?: string; fixed_text?: string; } export interface WritingExercise extends Section { id: string; type: "writing"; prefix: string; //* The information about the task, like the amount of time they should spend on it suffix: string; prompt: string; //* The context given to the user containing what they should write about wordCounter: WordCounter; //* The minimum or maximum amount of words that should be written attachment?: { url: string; description: string; }; //* The url for an image to work as an attachment to show the user userSolutions: { id: string; solution: string; evaluation?: WritingEvaluation; }[]; topic?: string; variant?: string; isPractice?: boolean difficulty?: Difficulty } export interface AIDetectionAttributes { predicted_class: "ai" | "mixed" | "human"; confidence_category: "high" | "medium" | "low"; class_probabilities: { ai: number; human: number; mixed: number; }; sentences: { sentence: string; highlight_sentence_for_ai: boolean; }[]; } export interface WritingEvaluation extends CommonEvaluation { ai_detection?: AIDetectionAttributes; } export interface SpeakingExercise extends Section { id: string; type: "speaking"; title: string; text: string; prompts: string[]; suffix?: string; video_url: string; userSolutions: { id: string; solution: string; evaluation?: SpeakingEvaluation; }[]; topic?: string; isPractice?: boolean difficulty?: Difficulty } export interface InteractiveSpeakingExercise extends Section { id: string; type: "interactiveSpeaking"; title: string; first_title?: string; second_title?: string; text: string; prompts: { text: string; video_url: string }[]; userSolutions: { id: string; solution: { questionIndex: number; question: string; answer: string }[]; evaluation?: InteractiveSpeakingEvaluation; }[]; topic?: string; first_topic?: string; second_topic?: string; variant?: "initial" | "final"; isPractice?: boolean difficulty?: Difficulty } export interface FillBlanksMCOption { id: string; options: { A: string; B: string; C: string; D: string; }; } export interface FillBlanksExercise { prompt: string; // *EXAMPLE: "Complete the summary below. Click a blank to select the corresponding word for it." type: "fillBlanks"; id: string; words: (string | { letter: string; word: string } | FillBlanksMCOption)[]; // *EXAMPLE: ["preserve", "unaware"] text: string; // *EXAMPLE: "They tried to {{1}} burning" allowRepetition?: boolean; solutions: { id: string; // *EXAMPLE: "1" solution: string; // *EXAMPLE: "preserve" }[]; userSolutions: { id: string; // *EXAMPLE: "1" solution: string; // *EXAMPLE: "preserve" }[]; variant?: string; isPractice?: boolean difficulty?: Difficulty } export interface TrueFalseExercise { type: "trueFalse"; id: string; prompt: string; // *EXAMPLE: "Select the appropriate option." questions: TrueFalseQuestion[]; userSolutions: { id: string; solution: "true" | "false" | "not_given" }[]; isPractice?: boolean difficulty?: Difficulty } export interface TrueFalseQuestion { id: string; // *EXAMPLE: "1" prompt: string; // *EXAMPLE: "What does her briefcase look like?" solution: "true" | "false" | "not_given" | undefined; // *EXAMPLE: "True" } export interface WriteBlanksExercise { prompt: string; // *EXAMPLE: "Complete the notes below by writing NO MORE THAN THREE WORDS in the spaces provided." maxWords: number; // *EXAMPLE: 3 - The maximum amount of words allowed per blank, 0 for unlimited type: "writeBlanks"; id: string; text: string; // *EXAMPLE: "The Government plans to give ${{14}}" solutions: { id: string; // *EXAMPLE: "14" solution: string[]; // *EXAMPLE: ["Prescott"] - All possible solutions (case sensitive) }[]; userSolutions: { id: string; solution: string; }[]; variant?: string; isPractice?: boolean difficulty?: Difficulty } export interface MatchSentencesExercise { type: "matchSentences"; id: string; prompt: string; userSolutions: { question: string; option: string }[]; sentences: MatchSentenceExerciseSentence[]; allowRepetition: boolean; options: MatchSentenceExerciseOption[]; variant?: string; isPractice?: boolean difficulty?: Difficulty } export interface MatchSentenceExerciseSentence { id: string; sentence: string; solution: string; } export interface MatchSentenceExerciseOption { id: string; sentence: string; } export interface MultipleChoiceExercise { type: "multipleChoice"; id: string; prompt: string; // *EXAMPLE: "Select the appropriate option." questions: MultipleChoiceQuestion[]; userSolutions: { question: string; option: string }[]; mcVariant?: string; passage?: { title: string; content: string; } isPractice?: boolean difficulty?: Difficulty } export interface MultipleChoiceQuestion { variant: "image" | "text"; id: string; // *EXAMPLE: "1" prompt: string; // *EXAMPLE: "What does her briefcase look like?" solution: string; // *EXAMPLE: "A" options: { id: string; // *EXAMPLE: "A" src?: string; // *EXAMPLE: "https://i.imgur.com/rEbrSqA.png" (only used if the variant is "image") text?: string; // *EXAMPLE: "wallet, pens and novel" (only used if the variant is "text") }[]; shuffleMap?: Record; } export interface ShuffleMap { questionID: string; map: { [key: string]: string; }; } export interface Shuffles { exerciseID: string; shuffles: ShuffleMap[]; } export type ModuleExam = LevelExam | ReadingExam | ListeningExam | WritingExam | SpeakingExam; export type PartExam = ReadingExam | ListeningExam | LevelExam; export type ExerciseOnlyExam = SpeakingExam | WritingExam;