DocDoeAI / src /lib /api /learning-state.ts
asnannp's picture
deploy: sync backend to Space root (learn-lesson HF cache fix)
4ec452e
Raw History Blame Contribute Delete
21.5 kB
import { apiDelete, apiFetch } from "@/lib/api/client";
export type LearningTask = {
id: string;
study_plan_id: string;
daily_plan_id?: string | null;
logical_key?: string | null;
subject_id: string | null;
chapter_id: string | null;
subject: string | null;
chapter: string | null;
task_type: "lesson" | "revision" | "practice" | "test" | "note" | "upload" | "repair";
title: string;
status: "pending" | "completed" | "missed" | "skipped";
scheduled_for: string;
duration_minutes: number;
priority: number;
href: string | null;
mission_id: string | null;
rescheduled_from_id: string | null;
completed_at: string | null;
/** Why DocDoe chose this task — from the adaptive planner, never invented. */
reason?: string | null;
source?: string | null;
repair_item_id?: string | null;
revision_concept_key?: string | null;
reason_codes?: string[];
reason_facts?: Record<string, unknown>;
target?: Record<string, unknown>;
};
export type LearningRevisionItem = {
id: string;
client_item_id: string;
subject_id: string | null;
chapter_id: string | null;
subject: string | null;
chapter: string | null;
mission_id: string | null;
topic_key: string | null;
title: string;
source_kind: string;
source_ref: string | null;
status: "pending" | "due" | "completed" | "dismissed";
due_at: string | null;
completed_at: string | null;
created_at: string;
};
export type TodayPlan = {
id: string;
date: string;
generation: number;
version: string;
status: string;
available_minutes: number;
planned_minutes: number;
completed_minutes: number;
tasks: LearningTask[];
generated_at: string;
regenerated_at: string | null;
};
export type MasteryState =
| "not_started"
| "introduced"
| "developing"
| "needs_repair"
| "secure"
| "revision_due"
| "at_risk";
export type RepairItem = {
id: string;
subject_id: string | null;
chapter_id: string | null;
concept_key: string;
concept_label: string;
mission_id: string | null;
error_category: string;
diagnosis: string;
recommended_activity: string;
activity_prompt: string;
priority: number;
estimated_minutes: number;
status: "open" | "resolved" | "escalated";
support_level: number;
failed_attempts: number;
retry_result: string | null;
mastery_recovered: boolean;
created_at: string;
resolved_at: string | null;
};
export type AcademicTopic = {
topic_key: string;
label: string;
state: MasteryState;
score: number;
confidence: number;
subject: string | null;
chapter: string | null;
next_review_at: string | null;
};
export type AcademicTuitionSession = {
class_session_id: string;
subject: string | null;
chapter: string | null;
chapter_catalog_id: string | null;
mission_id: string | null;
topic: string | null;
current_step_id: string;
current_step_label: string;
progress_percent: number;
updated_at: string;
href: string;
};
export type AcademicCourse = {
roadmap_id: string;
topic: string;
current_module: string | null;
completed_topics: number;
total_topics: number;
progress_percent: number;
updated_at: string;
href: string;
};
export type AcademicChatContext = {
session_id: string;
subject: string | null;
chapter: string | null;
topic: string | null;
source_id: string | null;
source_ref: string | null;
source_label: string | null;
activity_type: string | null;
question_id: string | null;
question_label: string | null;
return_href: string | null;
updated_at: string;
href: string;
};
export type NextAcademicAction = {
type:
| "COMPLETE_SETUP"
| "CONTINUE_LESSON"
| "CORRECT_MISTAKE"
| "PRACTISE_TOPIC"
| "REVISE_TOPIC"
| "START_TODAYS_PLAN"
| "CONTINUE_COURSE"
| "REVIEW_PYQ"
| "ASK_FOLLOWUP"
| "START_NEXT_LESSON";
title: string;
reason: string;
action_label: string;
subject: string | null;
chapter: string | null;
topic: string | null;
href: string;
resume_payload: Record<string, unknown>;
urgency: "low" | "normal" | "high" | "urgent";
estimated_minutes: number;
};
export type LearningOnboardingResponse = {
profile_id: string;
plan_id: string;
available_study_days: number;
first_task_id: string;
first_task_href: string;
tasks: LearningTask[];
today_plan?: TodayPlan | null;
};
export type LearningStateSummary = {
profile: null | {
id: string;
class_level: string;
board: string;
exam_date: string | null;
goal: string;
daily_minutes: number;
preferred_time: string | null;
available_study_days: number;
current_subject_id: string | null;
current_chapter_id: string | null;
current_mission_id: string | null;
onboarding_completed: boolean;
preferences?: Record<string, unknown>;
};
subjects: Array<{
id: string;
name: string;
board: string;
class_level: string;
priority: number;
status: string;
}>;
chapters: Array<{
id: string;
subject_id: string;
catalog_id: string;
title: string;
order_index: number;
importance: number;
estimated_minutes: number;
status: string;
curated: boolean;
}>;
tasks: LearningTask[];
today_plan?: TodayPlan | null;
lesson_progress: Array<{
id: string;
chapter_id: string;
chapter_catalog_id: string;
chapter: string;
subject_id: string;
mission_id: string;
status: "not_started" | "in_progress" | "completed";
progress_percent: number;
current_step: number;
completed_at: string | null;
last_seen_at: string;
}>;
mastery: Array<{
id: string;
subject_id: string | null;
chapter_id: string | null;
topic_key: string;
topic_label: string;
score: number;
confidence: number;
attempts_count: number;
last_result: string | null;
next_review_at: string | null;
state: MasteryState;
consecutive_success: number;
error_categories: Record<string, number>;
last_evidence_at: string | null;
}>;
quiz_attempts: Array<{
id: string;
quiz_id: string | null;
daily_task_id: string | null;
subject_id: string | null;
chapter_id: string | null;
title: string;
subject: string | null;
chapter: string | null;
score: number;
max_score: number;
answers: Array<Record<string, unknown>>;
corrections: Array<Record<string, unknown>>;
missing_keywords: string[];
misconceptions: string[];
completed_at: string;
}>;
resources: Array<{
id: string;
subject_id: string | null;
chapter_id: string | null;
subject: string | null;
chapter: string | null;
source_id: string | null;
resource_type: string;
title: string;
status: string;
storage_url: string | null;
resource_data: Record<string, unknown>;
created_at: string;
}>;
repair_items: RepairItem[];
revision_items?: LearningRevisionItem[];
completed_lessons: number;
completed_quiz_attempts: number;
generated_resources: number;
generated_notes: number;
questions_asked: number;
academic_state: {
class_level: string | null;
board: string | null;
subjects: string[];
exam_date: string | null;
days_to_exam: number | null;
current_subject: string | null;
current_chapter: string | null;
current_topic: string | null;
last_meaningful_activity: null | {
kind: string;
title: string;
detail: string | null;
occurred_at: string;
href: string;
};
unfinished_lesson: AcademicTuitionSession | null;
recent_mistakes: Array<{
repair_id: string;
topic_key: string;
label: string;
error_category: string;
diagnosis: string;
activity_prompt: string;
subject: string | null;
chapter: string | null;
mission_id: string | null;
occurred_at: string;
}>;
weak_topics: AcademicTopic[];
mastered_topics: AcademicTopic[];
revision_due: AcademicTopic[];
active_course: AcademicCourse | null;
active_tuition_session: AcademicTuitionSession | null;
recent_study_chat_context: AcademicChatContext | null;
suggested_notifications: Array<{
id: string;
title: string;
body: string;
kind: "info" | "success" | "warning" | "action";
href: string;
created_at: string;
}>;
};
next_action: NextAcademicAction;
};
export type StudentBrainSkillReadiness = {
skill:
| "concepts"
| "numericals"
| "derivations"
| "pyqs"
| "formulas"
| "graphs"
| "answer_writing"
| "diagrams";
label: string;
score: number;
attempts: number;
correct_attempts: number;
evidence_level: "early" | "building" | "reliable";
last_attempted_at: string;
};
export type StudentBrainMasteryItem = {
topic_key: string;
topic: string;
subject: string | null;
chapter: string | null;
score: number;
confidence: number;
state: string;
attempts: number;
last_result: string | null;
last_evidence_at: string | null;
next_review_at: string | null;
skill_readiness: StudentBrainSkillReadiness[];
};
export type StudentBrainContext = {
schema_version: "student-brain-v1";
generated_at: string;
focus: {
subject: string | null;
chapter: string | null;
topic: string | null;
available_minutes_today: number | null;
};
profile: {
class_level: string | null;
board: string | null;
subjects: string[];
preferred_language: string | null;
learning_preferences: Record<string, string | number | boolean>;
};
exam: {
name: string | null;
date: string | null;
days_remaining: number | null;
syllabus: string | null;
target_marks: number | null;
total_marks: number | null;
};
goal: {
description: string | null;
daily_minutes: number | null;
available_minutes_today: number | null;
};
weakest_topics: StudentBrainMasteryItem[];
retention_due: Array<{
topic_key: string;
topic: string;
subject: string | null;
chapter: string | null;
due_at: string;
state: string;
}>;
recent_mistakes: Array<{
checkpoint_ref: string;
topic_key: string;
topic: string;
subject: string | null;
chapter: string | null;
error_type: string;
error_type_source: "server_classification" | "student_reflection";
internal_error_category: string;
diagnosis: string | null;
question_type: string | null;
difficulty: string | null;
time_spent_seconds: number | null;
skill_tags: string[];
attempt_index: number;
occurred_at: string;
}>;
recent_question_attempts: Array<{
checkpoint_ref: string;
topic_key: string;
topic: string;
subject: string | null;
chapter: string | null;
kind: string;
correct: boolean;
hint_used: boolean;
difficulty: string | null;
time_spent_seconds: number | null;
skill_tags: string[];
attempt_index: number;
occurred_at: string;
}>;
recent_performance: Array<{
assessment_ref: string;
subject: string | null;
chapter: string | null;
score: number;
max_score: number;
accuracy_percent: number;
completed_at: string;
}>;
study_history: Array<{
date: string;
planned_minutes: number;
completed_minutes: number;
completion_percent: number;
subjects: string[];
}>;
pyq_importance: Array<{
topic: string;
subject: string | null;
chapter: string | null;
question_count: number;
total_marks: number;
years: number[];
priority: "high" | "medium" | "normal";
}>;
resources: Array<{
kind: "source" | "generated" | "curated_chapter";
title: string;
resource_type: string;
subject: string | null;
chapter: string | null;
status: string;
}>;
next_action: null | {
task_ref: string;
title: string;
task_type: string;
subject: string | null;
chapter: string | null;
topic: string | null;
chapter_catalog_id: string | null;
mission_id: string | null;
duration_minutes: number;
reason: string | null;
reason_codes: string[];
href: string | null;
};
};
export function saveLearningOnboarding(payload: {
class_level: string;
board: string;
subjects: string[];
/** Omit or null when the student does not know their exam date yet. */
exam_date: string | null;
goal: string;
daily_minutes: number;
preferred_time: string | null;
preferences: Record<string, unknown>;
}): Promise<LearningOnboardingResponse> {
return apiFetch<LearningOnboardingResponse>("/learning-state/onboarding", {
method: "POST",
body: JSON.stringify(payload),
});
}
export function confirmLearningOnboarding(planId: string): Promise<{
profile_id: string;
plan_id: string;
confirmed_at: string;
onboarding_completed: boolean;
}> {
return apiFetch("/learning-state/onboarding/confirm", {
method: "POST",
body: JSON.stringify({ plan_id: planId }),
});
}
export async function getLearningState(): Promise<LearningStateSummary> {
const state = await apiFetch<LearningStateSummary>("/learning-state/me");
// A returning student may open Home before Tuition on a new study day.
// Ensure is account-scoped and idempotent; an existing plan stays unchanged.
if (state.profile?.onboarding_completed && !state.today_plan) {
await ensureTodayPlan();
return apiFetch<LearningStateSummary>("/learning-state/me");
}
return state;
}
export function getStudentBrainContext(): Promise<StudentBrainContext> {
return apiFetch<StudentBrainContext>("/learning-state/brain-context");
}
export function deleteLearningResource(resourceId: string): Promise<void> {
return apiDelete<void>(
`/learning-state/resources/${encodeURIComponent(resourceId)}`,
);
}
export function setLearningTaskStatus(
taskId: string,
status: "pending" | "completed" | "missed" | "skipped",
reason?: string,
): Promise<{
task: LearningTask;
recalculated_tasks: LearningTask[];
message: string;
}> {
return apiFetch(`/learning-state/tasks/${encodeURIComponent(taskId)}/status`, {
method: "PATCH",
body: JSON.stringify({ status, reason: reason ?? null }),
});
}
export type LessonProgressResult = {
progress_id: string;
status: "in_progress" | "completed";
progress_percent: number;
completed_lessons: number;
completed_task_id: string | null;
message: string;
};
export function saveLearningLessonProgress(payload: {
chapter_catalog_id: string;
mission_id: string;
title: string;
daily_task_id?: string | null;
duration_minutes?: number;
current_step?: number;
progress_percent?: number;
status?: "in_progress" | "completed";
}): Promise<LessonProgressResult> {
return apiFetch<LessonProgressResult>("/learning-state/lessons/progress", {
method: "POST",
body: JSON.stringify(payload),
});
}
export type RevisionItemSaveResult = {
item: LearningRevisionItem;
replayed: boolean;
message: string;
};
export function saveLearningRevisionItem(payload: {
client_item_id: string;
title: string;
chapter_catalog_id: string;
mission_id?: string | null;
topic_key?: string | null;
source_kind: "manual_save" | "lesson_recap";
source_ref?: string | null;
due_in_days?: number;
}): Promise<RevisionItemSaveResult> {
return apiFetch<RevisionItemSaveResult>("/learning-state/revision-items", {
method: "POST",
body: JSON.stringify(payload),
});
}
export type AssessmentConsequence = {
attempt_id: string;
mastery_before: number;
mastery_after: number;
revision_task: LearningTask | null;
next_recommended_task_id: string | null;
message: string;
};
export type LearningAssessmentInput = {
client_attempt_id?: string | null;
title: string;
topic_key: string;
topic_label: string;
score: number;
max_score: number;
subject_id?: string | null;
chapter_id?: string | null;
daily_task_id?: string | null;
quiz_id?: string | null;
mission_id?: string | null;
answers?: Array<Record<string, unknown>>;
corrections?: Array<Record<string, unknown>>;
missing_keywords?: string[];
misconceptions?: string[];
mastery_already_recorded?: boolean;
};
export function recordLearningAssessment(
payload: LearningAssessmentInput,
): Promise<AssessmentConsequence> {
return apiFetch<AssessmentConsequence>("/learning-state/assessments", {
method: "POST",
body: JSON.stringify(payload),
});
}
export function adjustLearningPlan(payload: {
target_subject: string;
replace_subject?: string | null;
reason: string;
chapters: string[];
}): Promise<{
tasks: LearningTask[];
unavailable_chapters: string[];
rescheduled_tasks: number;
message: string;
}> {
return apiFetch("/learning-state/plan-adjustments", {
method: "POST",
body: JSON.stringify(payload),
});
}
export type CheckpointConsequence = {
checkpoint_id: string;
correct: boolean;
replayed: boolean;
error_category: string | null;
diagnosis: string | null;
recommended_activity: string | null;
activity_prompt: string | null;
repair_status: "created" | "escalated" | "resolved" | "none";
repair_item: RepairItem | null;
mastery: {
before_score: number;
after_score: number;
before_state: MasteryState;
after_state: MasteryState;
confidence: number;
consecutive_success: number;
next_review_at: string | null;
};
message: string;
};
export type CheckpointKind =
| "recall_correct"
| "checkpoint_mcq"
| "checkpoint_short"
| "checkpoint_numerical"
| "checkpoint_diagram"
| "board_answer"
| "transfer_check"
| "revision_recall";
export function recordLearningCheckpoint(payload: {
client_event_id: string;
concept_key: string;
concept_label: string;
chapter_catalog_id: string;
mission_id: string;
kind: CheckpointKind;
question_id: string;
question_type: "mcq" | "short" | "numerical" | "diagram" | "board";
prompt?: string;
student_answer: string;
correct_answer?: string;
client_correct?: boolean;
attempt_index?: number;
hint_used?: boolean;
expected_keywords?: string[];
time_spent_seconds?: number | null;
difficulty?: "easy" | "medium" | "hard" | null;
skill_tags?: string[];
error_reflection?: {
error_type: "careless_error" | "misread_question" | "time_pressure";
note?: string | null;
} | null;
subject_id?: string | null;
chapter_id?: string | null;
daily_task_id?: string | null;
}): Promise<CheckpointConsequence> {
return apiFetch<CheckpointConsequence>("/learning-state/checkpoints", {
method: "POST",
body: JSON.stringify(payload),
});
}
export type PlanTodayResult = {
plan_id: string;
date: string;
generation: number;
version: string;
status: string;
daily_minutes: number;
planned_minutes: number;
tasks: LearningTask[];
created_task_ids: string[];
cancelled_task_ids: string[];
message: string;
};
export function reconcileTodaysPlan(): Promise<PlanTodayResult> {
return ensureTodayPlan();
}
export function getTodayPlan(): Promise<PlanTodayResult> {
return apiFetch<PlanTodayResult>("/learning-state/today-plan");
}
export function ensureTodayPlan(): Promise<PlanTodayResult> {
return apiFetch<PlanTodayResult>("/learning-state/today-plan/ensure", {
method: "POST",
body: JSON.stringify({}),
});
}
export function replanToday(payload: {
idempotency_key: string;
reason:
| "explicit_replan"
| "daily_minutes_changed"
| "exam_date_changed"
| "content_changed"
| "meaningful_evidence";
}): Promise<PlanTodayResult> {
return apiFetch<PlanTodayResult>("/learning-state/today-plan/replan", {
method: "POST",
body: JSON.stringify(payload),
});
}
export type RevisionRecallQuestion = {
question_id: string;
prompt: string;
question_type: "mcq" | "short" | "numerical" | "diagram" | "board";
options: string[];
basis: "saved_mistake" | "linked_lesson";
reason: string;
failure_count: number;
};
export function getRevisionRecallQuestion(revisionItemId: string): Promise<RevisionRecallQuestion> {
return apiFetch(`/learning-state/revision-items/${encodeURIComponent(revisionItemId)}/question`);
}
export function recordRevisionResult(
revisionItemId: string,
payload: { client_event_id: string; response: string; question_id?: string },
): Promise<{
item: LearningRevisionItem;
evaluation: {
evaluation_version: string;
question_id: string;
correct: boolean;
awarded_marks: number;
max_marks: number;
mistake_categories: string[];
feedback: string;
};
replayed: boolean;
message: string;
}> {
return apiFetch(
`/learning-state/revision-items/${encodeURIComponent(revisionItemId)}/result`,
{ method: "POST", body: JSON.stringify(payload) },
);
}
export function listOpenStudyFollowUps(): Promise<Record<string, unknown>[]> {
return apiFetch("/learning-state/study-followups");
}
export function createStudyFollowUp(payload: {
client_followup_id: string;
chat_session_id: string;
source_message_id?: string | null;
kind: "practice" | "pyq_review" | "clarification" | "source_review";
title: string;
subject?: string | null;
chapter?: string | null;
topic?: string | null;
due_at?: string | null;
estimated_minutes?: number;
source_ref?: string | null;
target?: Record<string, unknown>;
}): Promise<Record<string, unknown>> {
return apiFetch("/learning-state/study-followups", {
method: "POST",
body: JSON.stringify(payload),
});
}