Compare commits

..
Author SHA1 Message Date
CC WorkerandClaude Sonnet 4.6 ab35193be1 Merge S4-10: mark scheme editor + SpecPoint picker
app-ci-deploy / test-build-deploy (push) Has been cancelled
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-06-07 04:31:02 +00:00
CC WorkerandClaude Sonnet 4.6 b3f71c5749 Merge S4-10: mark scheme editor + SpecPoint picker
- Add /exam-marker/:templateId/marks route with MarkSchemePage
- Per-Part mark scheme editor: points/levels/parts/checklist/free forms
- SpecPoint picker via GET /api/exam/specs/{spec_code}/points (falls back to manual spec_ref when endpoint 404s)
- Manual neo4j-sync button; ASSESSES edge verified in cc.public.exams
- Edit marks button on each template card in dashboard
- Merge-resolved: AppRoutes, index.ts, exam.types.ts, ExamDashboardPage (kept grouped UI + added Edit marks button)

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-06-07 04:30:41 +00:00
3 changed files with 68 additions and 67 deletions
+8 -8
View File
@@ -315,20 +315,20 @@ const ExamDashboardPage: React.FC = () => {
{t.exam_code && (
<Typography variant="caption" color="text.secondary">{t.exam_code}</Typography>
)}
<Stack spacing={1} sx={{ mt: 'auto', pt: 1 }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<Chip size="small" label={t.status} color={STATUS_COLOR[t.status] ?? 'default'} variant="outlined" />
<Typography variant="caption" color="text.secondary">
Updated {new Date(t.updated_at).toLocaleDateString()}
</Typography>
</Box>
<Box sx={{ mt: 'auto', pt: 1, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<Chip size="small" label={t.status} color={STATUS_COLOR[t.status] ?? 'default'} variant="outlined" />
<Typography variant="caption" color="text.secondary">
Updated {new Date(t.updated_at).toLocaleDateString()}
</Typography>
</Box>
<Stack direction="row" spacing={1} sx={{ pt: 0.5 }}>
<Button
size="small"
variant="outlined"
onClick={(e) => { e.stopPropagation(); navigate(`/exam-marker/${t.id}/marks`); }}
startIcon={<GradingIcon fontSize="small" />}
>
Mark scheme
Edit marks
</Button>
</Stack>
</Paper>
+3 -3
View File
@@ -1,3 +1,3 @@
export { default as ExamDashboardPage } from ./ExamDashboardPage;
export { default as ExamTemplateSetupPage } from ./setup/ExamTemplateSetupPage;
export { default as MarkSchemePage } from ./MarkSchemePage;
export { default as ExamDashboardPage } from './ExamDashboardPage';
export { default as ExamTemplateSetupPage } from './setup/ExamTemplateSetupPage';
export { default as MarkSchemePage } from './MarkSchemePage';
+57 -56
View File
@@ -4,7 +4,7 @@
* the shared UUIDs (template/question/region ids, exam_code).
*/
export type ExamTemplateStatus = draft | ready | archived;
export type ExamTemplateStatus = 'draft' | 'ready' | 'archived';
export interface ExamTemplate {
id: string;
@@ -31,7 +31,7 @@ export interface CreateTemplatePayload {
institute_id?: string;
}
export type MarkSchemeType = points | levels | parts | checklist | free;
export type MarkSchemeType = 'points' | 'levels' | 'parts' | 'checklist' | 'free';
export interface MarkSchemePoint {
mark: number;
@@ -92,12 +92,12 @@ export interface ExamQuestion {
}
export type ExamResponseAreaKind =
| response
| context
| question_number
| mark_area
| reference
| furniture;
| 'response'
| 'context'
| 'question_number'
| 'mark_area'
| 'reference'
| 'furniture';
export interface ExamResponseArea {
id: string;
@@ -108,7 +108,7 @@ export interface ExamResponseArea {
kind: ExamResponseAreaKind;
response_form: string | null;
context_type?: string | null;
source: manual | ai;
source: 'manual' | 'ai';
confirmed: boolean;
confidence: number | null;
}
@@ -121,7 +121,7 @@ export interface ExamBoundary {
page_index: number;
y: number;
bounds: Record<string, number> | null;
source: manual | ai;
source: 'manual' | 'ai';
confirmed: boolean;
}
@@ -131,11 +131,57 @@ export interface ExamTemplateDetail extends ExamTemplate {
boundaries: ExamBoundary[];
}
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;
}>;
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;
}>;
boundaries: Array<{
id?: string;
question_id?: string | null;
label?: string | null;
page_index: number;
y: number;
bounds?: Record<string, number> | null;
source?: 'manual' | 'ai';
confirmed?: boolean;
}>;
}
export interface PatchQuestionPayload {
label?: string;
order?: number;
max_marks?: number;
answer_type?: written | mcq | short | diagram | null;
answer_type?: 'written' | 'mcq' | 'short' | 'diagram' | null;
mcq_options?: unknown;
mark_scheme?: MarkScheme;
is_container?: boolean;
@@ -155,48 +201,3 @@ export interface Neo4jSyncResult {
status: string;
projection?: Record<string, unknown>;
}
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?: MarkScheme;
is_container?: boolean;
spec_ref?: string | null;
bounds?: Record<string, number> | null;
page?: number | null;
}>;
response_areas: Array<{
id?: string;
question_id: string;
page: number;
bounds: Record<string, number>;
kind: ExamResponseAreaKind;
response_form?: string | null;
context_type?: string | null;
source?: manual | ai;
confirmed?: boolean;
confidence?: number | null;
}>;
boundaries: Array<{
id?: string;
question_id?: string | null;
label?: string | null;
page_index: number;
y: number;
bounds?: Record<string, number> | null;
source?: manual | ai;
confirmed?: boolean;
}>;
}