import fs from 'fs/promises'; import path from 'path'; import { fileURLToPath } from 'url'; export interface TwinQuestion { question: string; correct_answer: string; incorrect_answers: string[]; createdAt: string; } export interface ChatMessage { role: 'user' | 'assistant'; content: string; createdAt: string; } export interface TestQuestion { question: string; correct_answer: string; incorrect_answers: string[]; user_answer: string | null; confidence: string | null; is_correct: boolean; explanation: string | null; conversation: ChatMessage[]; children: TwinQuestion[]; } export interface TestRecord { id: string; createdAt: string; student: string; school: string; subject: string; chapter: string; difficulty: string; score: { total: number; correct: number; percent: number; overconfident: number; lucky: number; solid: number; }; feedback: { summary: string; detail: string; recommendation: string } | null; questions: TestQuestion[]; } const DATA_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), 'data'); const FILE = path.join(DATA_DIR, 'history.json'); let records: TestRecord[] = []; let writeQueue: Promise = Promise.resolve(); export async function load() { try { records = JSON.parse(await fs.readFile(FILE, 'utf8')); } catch { records = []; } for (const record of records) { for (const question of record.questions) { question.conversation ??= []; question.children ??= []; } } } function persist() { writeQueue = writeQueue.then(async () => { await fs.mkdir(DATA_DIR, { recursive: true }); await fs.writeFile(FILE, JSON.stringify(records, null, 2), 'utf8'); }); return writeQueue; } export function listSummaries() { return records .map((r) => ({ id: r.id, createdAt: r.createdAt, student: r.student, school: r.school, subject: r.subject, chapter: r.chapter, difficulty: r.difficulty, score: r.score, twinCount: r.questions.reduce((n, q) => n + q.children.length, 0), explainedCount: r.questions.filter((q) => q.explanation).length, askedCount: r.questions.reduce( (n, q) => n + q.conversation.filter((m) => m.role === 'user').length, 0 ), })) .sort((a, b) => b.createdAt.localeCompare(a.createdAt)); } export function get(id: string) { return records.find((r) => r.id === id) ?? null; } export async function save(record: TestRecord) { records.push(record); await persist(); return record; } async function updateQuestion( testId: string, index: number, mutate: (question: TestQuestion) => void ) { const record = get(testId); const question = record?.questions[index]; if (!question) return null; mutate(question); await persist(); return question; } export const setExplanation = (testId: string, index: number, explanation: string) => updateQuestion(testId, index, (q) => { q.explanation = explanation; }); export const addChild = (testId: string, index: number, twin: TwinQuestion) => updateQuestion(testId, index, (q) => { q.children.push(twin); }); export const addMessages = (testId: string, index: number, messages: ChatMessage[]) => updateQuestion(testId, index, (q) => { q.conversation.push(...messages); }); export async function setFeedback(testId: string, feedback: TestRecord['feedback']) { const record = get(testId); if (!record) return null; record.feedback = feedback; await persist(); return record; }