feat: add expert diagnosis review workflow

This commit is contained in:
Asep Haryana Saputra
2026-05-22 16:23:40 +00:00
parent afa2e4999b
commit 59204b0fde
2 changed files with 91 additions and 0 deletions
+2
View File
@@ -7,6 +7,7 @@ import { classificationRoutes } from './routes/classifications';
import { diagnosisRoutes } from './routes/diagnoses';
import { dashboardRoutes } from './routes/dashboard';
import { authRoutes } from './routes/auth';
import { expertRoutes } from './routes/expert';
assertRequiredEnv();
@@ -17,6 +18,7 @@ const app = new Elysia()
.use(diseaseRoutes)
.use(classificationRoutes)
.use(diagnosisRoutes)
.use(expertRoutes)
.use(dashboardRoutes)
.listen(env.port);
+89
View File
@@ -0,0 +1,89 @@
import { Elysia } from 'elysia';
import type { ReviewDiagnosisRequest } from '@zeavis/shared';
import { isDiseaseSlug } from '@zeavis/shared';
import { desc, eq } from 'drizzle-orm';
import { createDbClient } from '../db/client';
import { diagnoses, expertReviews } from '../db/schema';
import { badRequest, forbidden, notFound, serviceUnavailable, unauthorized } from '../lib/http-errors';
import { getCurrentUser } from '../lib/auth';
import { loadDiagnosisRecord } from './diagnoses';
function isDiagnosisRecordOrNull(record: unknown): record is ReturnType<typeof loadDiagnosisRecord> extends Promise<infer T> ? T : never {
return record !== null;
}
export const expertRoutes = new Elysia({ prefix: '/api/v1/expert' })
.get('/reviews', async ({ request }) => {
const user = await getCurrentUser(request.headers.get('cookie'));
if (!user) return unauthorized('Authentication required');
if (user.role !== 'expert') return forbidden('Expert role required');
try {
const db = createDbClient();
const rows = await db
.select({ id: diagnoses.id })
.from(diagnoses)
.where(eq(diagnoses.status, 'needs_review'))
.orderBy(desc(diagnoses.createdAt))
.limit(50);
const records = await Promise.all(rows.map((row) => loadDiagnosisRecord(row.id, null, true)));
return records.filter(isDiagnosisRecordOrNull);
} catch (error) {
return serviceUnavailable('Database unavailable');
}
})
.post('/reviews/:diagnosisId', async ({ params, body, request }) => {
const user = await getCurrentUser(request.headers.get('cookie'));
if (!user) return unauthorized('Authentication required');
if (user.role !== 'expert') return forbidden('Expert role required');
const req = body as Partial<ReviewDiagnosisRequest> | undefined;
if (!req || (req.verdict !== 'verified' && req.verdict !== 'corrected')) {
return badRequest('Verdict must be verified or corrected');
}
const notes = typeof req.notes === 'string' ? req.notes.trim() : '';
if (!notes) return badRequest('Review notes are required');
if (req.verdict === 'corrected') {
if (!req.correctedDiseaseSlug || !isDiseaseSlug(req.correctedDiseaseSlug)) {
return badRequest('Corrected disease slug is required for corrected reviews');
}
}
try {
const db = createDbClient();
const existing = await db
.select()
.from(diagnoses)
.where(eq(diagnoses.id, params.diagnosisId))
.limit(1);
const diagnosis = existing[0];
if (!diagnosis) return notFound('Diagnosis not found');
const correctedSlug = req.verdict === 'corrected' ? req.correctedDiseaseSlug : null;
await db.insert(expertReviews).values({
diagnosisId: diagnosis.id,
expertId: user.id,
verdict: req.verdict,
correctedDiseaseSlug: correctedSlug,
notes,
});
await db
.update(diagnoses)
.set({
status: req.verdict === 'corrected' ? 'expert_corrected' : 'expert_verified',
predictedDiseaseSlug: req.verdict === 'corrected' ? correctedSlug : diagnosis.predictedDiseaseSlug,
updatedAt: new Date(),
})
.where(eq(diagnoses.id, diagnosis.id));
return await loadDiagnosisRecord(diagnosis.id, null, true);
} catch (error) {
return serviceUnavailable('Database unavailable');
}
});