'Digital text' button opens the digital-replica markdown (stem text, parts, marks, answer-space placeholders, spec refs) in a dialog with download. A 'Marks N/M' chip surfaces the extraction cover-total reconciliation (green=ok / amber=under-read) from extraction_meta.audit. examRepository.getDigitalText + DigitalTextResponse type. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
409 lines
10 KiB
TypeScript
409 lines
10 KiB
TypeScript
/**
|
|
* Exam-marker types — mirror the FastAPI /api/exam contract (see api routers/exam/schemas.py).
|
|
* Supabase is source of truth for this operational data; the graph (cc.public.exams) joins by
|
|
* the shared UUIDs (template/question/region ids, exam_code).
|
|
*/
|
|
|
|
export type ExamTemplateStatus = 'draft' | 'ready' | 'archived';
|
|
|
|
export interface ExamTemplate {
|
|
id: string;
|
|
title: string;
|
|
subject: string | null;
|
|
exam_id: string | null;
|
|
exam_code: string | null;
|
|
source_file_id: string | null;
|
|
page_count: number | null;
|
|
institute_id: string;
|
|
teacher_id: string;
|
|
status: ExamTemplateStatus;
|
|
created_at: string;
|
|
updated_at: string;
|
|
}
|
|
|
|
export interface CreateTemplatePayload {
|
|
title: string;
|
|
subject?: string;
|
|
exam_id?: string;
|
|
exam_code?: string;
|
|
source_file_id?: string;
|
|
page_count?: number;
|
|
institute_id?: string;
|
|
}
|
|
|
|
export type MarkSchemeType = 'points' | 'levels' | 'parts' | 'checklist' | 'free';
|
|
|
|
export interface MarkSchemePoint {
|
|
mark: number;
|
|
text: string;
|
|
}
|
|
|
|
export interface MarkSchemeLevel {
|
|
level: string;
|
|
min: number;
|
|
max: number;
|
|
descriptor: string;
|
|
}
|
|
|
|
export interface MarkSchemePart {
|
|
label: string;
|
|
marks: number;
|
|
guidance: string;
|
|
}
|
|
|
|
export interface MarkSchemeChecklistItem {
|
|
text: string;
|
|
marks: number;
|
|
}
|
|
|
|
export interface MarkScheme {
|
|
type?: MarkSchemeType;
|
|
points?: MarkSchemePoint[];
|
|
levels?: MarkSchemeLevel[];
|
|
parts?: MarkSchemePart[];
|
|
checklist?: MarkSchemeChecklistItem[];
|
|
text?: string;
|
|
notes?: string;
|
|
[key: string]: unknown;
|
|
}
|
|
|
|
export interface UpdateTemplateMetaPayload {
|
|
title?: string;
|
|
subject?: string | null;
|
|
page_count?: number | null;
|
|
status?: ExamTemplateStatus;
|
|
}
|
|
|
|
/** Canvas children (used from S4-9 onward; defined here so the seam is complete). */
|
|
export type ExamTemplateSource = 'manual' | 'ai';
|
|
|
|
export interface ExamQuestion {
|
|
id: string;
|
|
template_id: string;
|
|
parent_id: string | null;
|
|
label: string;
|
|
order: number;
|
|
max_marks: number;
|
|
answer_type: string | null;
|
|
mcq_options: unknown | null;
|
|
mark_scheme: MarkScheme;
|
|
is_container: boolean;
|
|
spec_ref: string | null;
|
|
bounds?: Record<string, number> | null;
|
|
page?: number | null;
|
|
source: ExamTemplateSource;
|
|
confirmed: boolean;
|
|
confidence: number | null;
|
|
derivation: string | null;
|
|
}
|
|
|
|
export type ExamResponseAreaKind =
|
|
| 'response'
|
|
| 'context'
|
|
| 'question_number'
|
|
| 'mark_area'
|
|
| 'reference'
|
|
| 'furniture';
|
|
|
|
export type ExamMarkSubtype = 'part_marks' | 'question_total' | 'grader_box';
|
|
|
|
export interface ExamResponseArea {
|
|
id: string;
|
|
question_id: string;
|
|
template_id: string;
|
|
page: number;
|
|
bounds: Record<string, number>;
|
|
kind: ExamResponseAreaKind;
|
|
response_form: string | null;
|
|
context_type?: string | null;
|
|
source: ExamTemplateSource;
|
|
confirmed: boolean;
|
|
confidence: number | null;
|
|
mark_subtype?: ExamMarkSubtype | null;
|
|
derivation?: string | null;
|
|
}
|
|
|
|
export interface ExamBoundary {
|
|
id: string;
|
|
template_id: string;
|
|
question_id: string | null;
|
|
label: string | null;
|
|
page_index: number;
|
|
y: number;
|
|
bounds: Record<string, number> | null;
|
|
source: ExamTemplateSource;
|
|
confirmed: boolean;
|
|
confidence: number | null;
|
|
derivation: string | null;
|
|
}
|
|
|
|
export interface ExamTemplateLayout {
|
|
id: string;
|
|
template_id: string;
|
|
page_index: number;
|
|
role: string | null;
|
|
margin_left: number | null;
|
|
margin_right: number | null;
|
|
margin_top: number | null;
|
|
margin_bottom: number | null;
|
|
margins_enabled: boolean;
|
|
source: ExamTemplateSource;
|
|
confirmed: boolean;
|
|
confidence: number | null;
|
|
derivation: string | null;
|
|
meta: Record<string, unknown>;
|
|
created_at?: string;
|
|
updated_at?: string;
|
|
}
|
|
|
|
export interface ExamTemplateDetail extends ExamTemplate {
|
|
questions: ExamQuestion[];
|
|
response_areas: ExamResponseArea[];
|
|
boundaries: ExamBoundary[];
|
|
layout: ExamTemplateLayout[];
|
|
}
|
|
|
|
|
|
export interface TemplateReplacePayload {
|
|
meta?: {
|
|
title?: string;
|
|
subject?: string;
|
|
page_count?: number;
|
|
status?: ExamTemplateStatus;
|
|
};
|
|
questions: Array<{
|
|
id?: string;
|
|
parent_id?: string | null;
|
|
label: string;
|
|
order?: number;
|
|
max_marks?: number;
|
|
answer_type?: 'written' | 'mcq' | 'short' | 'diagram' | null;
|
|
mcq_options?: unknown | null;
|
|
mark_scheme?: Record<string, unknown>;
|
|
is_container?: boolean;
|
|
spec_ref?: string | null;
|
|
bounds?: Record<string, number> | null;
|
|
page?: number | null;
|
|
source?: ExamTemplateSource;
|
|
confirmed?: boolean;
|
|
confidence?: number | null;
|
|
derivation?: string | null;
|
|
}>;
|
|
response_areas: Array<{
|
|
id?: string;
|
|
question_id: string;
|
|
page: number;
|
|
bounds: Record<string, number>;
|
|
kind: ExamResponseArea['kind'];
|
|
response_form?: string | null;
|
|
context_type?: string | null;
|
|
source?: 'manual' | 'ai';
|
|
confirmed?: boolean;
|
|
confidence?: number | null;
|
|
mark_subtype?: ExamMarkSubtype | null;
|
|
derivation?: string | null;
|
|
}>;
|
|
boundaries: Array<{
|
|
id?: string;
|
|
question_id?: string | null;
|
|
label?: string | null;
|
|
page_index: number;
|
|
y: number;
|
|
bounds?: Record<string, number> | null;
|
|
source?: ExamTemplateSource;
|
|
confirmed?: boolean;
|
|
confidence?: number | null;
|
|
derivation?: string | null;
|
|
}>;
|
|
layout?: Array<{
|
|
id?: string;
|
|
page_index: number;
|
|
role?: string | null;
|
|
margin_left?: number | null;
|
|
margin_right?: number | null;
|
|
margin_top?: number | null;
|
|
margin_bottom?: number | null;
|
|
margins_enabled?: boolean;
|
|
source?: ExamTemplateSource;
|
|
confirmed?: boolean;
|
|
confidence?: number | null;
|
|
derivation?: string | null;
|
|
meta?: Record<string, unknown>;
|
|
}>;
|
|
}
|
|
|
|
export interface PatchQuestionPayload {
|
|
label?: string;
|
|
order?: number;
|
|
max_marks?: number;
|
|
answer_type?: 'written' | 'mcq' | 'short' | 'diagram' | null;
|
|
mcq_options?: unknown;
|
|
mark_scheme?: MarkScheme;
|
|
is_container?: boolean;
|
|
spec_ref?: string | null;
|
|
}
|
|
|
|
export interface SpecPoint {
|
|
uid?: string;
|
|
uuid_string?: string;
|
|
ref: string;
|
|
description: string;
|
|
spec_code: string;
|
|
exam_board_code?: string;
|
|
}
|
|
|
|
export interface Neo4jSyncResult {
|
|
status: string;
|
|
projection?: Record<string, unknown>;
|
|
}
|
|
|
|
export interface AutoMapAcceptedResponse {
|
|
status: 'accepted';
|
|
job_id: string;
|
|
}
|
|
|
|
export interface AutoMapJobStatus {
|
|
job_id: string;
|
|
status: 'queued' | 'running' | 'completed' | 'failed' | string;
|
|
template_id: string;
|
|
updated_at?: number;
|
|
counts?: Record<string, number>;
|
|
error?: string;
|
|
template?: ExamTemplateDetail;
|
|
}
|
|
|
|
export type AutoMapResponse = ExamTemplateDetail | AutoMapAcceptedResponse;
|
|
|
|
export interface MarkingBatch {
|
|
id: string;
|
|
template_id: string;
|
|
class_id: string | null;
|
|
institute_id: string;
|
|
teacher_id: string;
|
|
title: string | null;
|
|
status: 'open' | 'closed' | 'archived' | string;
|
|
created_at: string;
|
|
updated_at?: string;
|
|
submission_count?: number;
|
|
}
|
|
|
|
export interface StudentSubmission {
|
|
id: string;
|
|
batch_id: string;
|
|
student_id: string | null;
|
|
student_name: string | null;
|
|
status: 'absent' | 'unmatched' | 'matched' | 'marking' | 'complete' | string;
|
|
storage_path?: string | null;
|
|
mark_entry_count?: number;
|
|
}
|
|
|
|
export interface BatchQueueResponse {
|
|
batch: MarkingBatch;
|
|
submissions: StudentSubmission[];
|
|
progress: {
|
|
total: number;
|
|
absent: number;
|
|
complete: number;
|
|
in_progress: number;
|
|
};
|
|
}
|
|
|
|
export interface ExamResultRow {
|
|
submission_id: string;
|
|
student_id: string | null;
|
|
student_name: string | null;
|
|
status: string | null;
|
|
marks: Record<string, number | null | undefined>;
|
|
total: number | null;
|
|
}
|
|
|
|
export interface BatchResultsResponse {
|
|
batch: MarkingBatch;
|
|
questions: Array<Pick<ExamQuestion, 'id' | 'label' | 'max_marks' | 'order'>>;
|
|
results: ExamResultRow[];
|
|
}
|
|
|
|
export interface CreateBatchPayload {
|
|
template_id: string;
|
|
class_id?: string;
|
|
title?: string;
|
|
}
|
|
|
|
export interface MarkUpsertPayload {
|
|
submission_id: string;
|
|
question_id: string;
|
|
awarded_marks: number;
|
|
mark_scheme_detail?: Record<string, unknown>;
|
|
annotation_shape_ids?: unknown;
|
|
comment?: string;
|
|
confirmed?: boolean;
|
|
}
|
|
|
|
// ── Mode-3 question bank + custom-paper assembly ──────────────────────────────
|
|
export interface BankQuestion {
|
|
id: string;
|
|
template_id: string;
|
|
label: string | null;
|
|
max_marks: number | null;
|
|
answer_type: string | null;
|
|
spec_ref: string | null;
|
|
bounds: Record<string, number> | null;
|
|
page: number | null;
|
|
paper: { id: string | null; title: string | null; subject: string | null; exam_code: string | null };
|
|
}
|
|
|
|
export interface BankResponse {
|
|
questions: BankQuestion[];
|
|
n: number;
|
|
facets: { spec_ref: Record<string, number>; subject: Record<string, number> };
|
|
}
|
|
|
|
export interface CreateCustomPaperPayload {
|
|
title: string;
|
|
subject?: string;
|
|
question_ids: string[];
|
|
}
|
|
|
|
export interface CreateCustomPaperResult {
|
|
id: string;
|
|
title: string;
|
|
subject: string | null;
|
|
n_questions: number;
|
|
n_response_areas: number;
|
|
}
|
|
|
|
// ── Exam-bank corpus coverage (state of the collected bank) ───────────────────
|
|
export type CorpusDocType = 'QP' | 'MS' | 'ER';
|
|
export interface CorpusPaper {
|
|
paper_code: string | null;
|
|
session: string | null;
|
|
tier: string | null;
|
|
docs: Partial<Record<CorpusDocType, boolean>>;
|
|
exam_codes: Partial<Record<CorpusDocType, string>>;
|
|
}
|
|
export interface CorpusSpec {
|
|
spec_code: string;
|
|
subject: string;
|
|
level: string;
|
|
board: string;
|
|
first_teach: string | null;
|
|
n_papers: number;
|
|
counts: Record<CorpusDocType, number>;
|
|
papers: CorpusPaper[];
|
|
}
|
|
export interface CorpusBoard { board: string; n_specs: number; specs: CorpusSpec[] }
|
|
export interface CorpusResponse {
|
|
totals: { specs: number; papers: number; sessions: number; QP: number; MS: number; ER: number };
|
|
boards: CorpusBoard[];
|
|
}
|
|
|
|
// ── Digital-replica markdown (P4) ─────────────────────────────────────────────
|
|
export interface DigitalTextResponse {
|
|
slug: string;
|
|
title: string;
|
|
n_questions: number;
|
|
total_marks: number;
|
|
markdown: string;
|
|
questions: { label: string; marks: number | null; markdown: string }[];
|
|
}
|