soroban-abacus-flashcards/apps/web/docs/DAILY_PRACTICE_SYSTEM.md

59 KiB
Raw Permalink Blame History

Daily Practice System

Architecture and implementation plan for the structured daily practice system.

Overview

This document outlines the plan to implement a structured daily practice system following the traditional Japanese soroban teaching methodology. The curriculum follows the order used in physical abacus workbooks.

Book's Teaching Order

The workbooks teach in this specific order:

Level 1: No Regrouping (Single Column Operations)

Operations that don't require carrying/borrowing across columns.

Addition (+1 through +9) For each number, practice in this order:

  1. Without friends of 5: Direct bead movements only
    • e.g., 2 + 1 = 3 (just move earth beads)
  2. With friends of 5: Using the 5-complement technique
    • e.g., 3 + 4 = 7 → needs +5, -1

Subtraction (-9 through -1) For each number, practice in this order:

  1. Without friends of 5: Direct bead movements only
    • e.g., 7 - 2 = 5 (just remove earth beads)
  2. With friends of 5: Using the 5-complement technique
    • e.g., 6 - 4 = 2 → needs -5, +1

Level 2: Addition with Regrouping (Friends of 10)

Addition that requires carrying to the next column.

Addition (+1 through +9) For each number:

  1. Without friends of 5: Pure 10-complement
    • e.g., 5 + 7 = 12 → needs -3, +10 (no 5-bead manipulation in ones)
  2. With friends of 5: Combined 10-complement and 5-complement
    • e.g., 9 + 6 = 15 → needs +10, -5, +1

Level 3: Subtraction with Regrouping (Friends of 10)

Subtraction that requires borrowing from the next column.

Subtraction (-9 through -1) For each number:

  1. Without friends of 5: Pure 10-complement
    • e.g., 12 - 7 = 5 → needs +3, -10
  2. With friends of 5: Combined 10-complement and 5-complement
    • e.g., 15 - 6 = 9 → needs -10, +5, -1

Problem Format

  • Multi-term sequences (3-7 terms): 12 + 45 + 23 + 67 + 8 = ?
  • Double-digit from the start: The books don't start with single-digit only
  • Visualization starts Level 1: Once mechanics are familiar, practice without abacus visible

Existing Infrastructure

What We Have

Component Location Can Leverage
Problem generator src/utils/problemGenerator.ts Core logic exists
Skill analysis analyzeColumnAddition() Pattern to follow
SkillSet types src/types/tutorial.ts Has 5/10 complements
Practice player src/components/tutorial/PracticeProblemPlayer.tsx UI exists
Constraint system allowedSkills, targetSkills, forbiddenSkills Ready to use

What We Need to Add

Feature Description File(s) to Modify Status
Subtraction skill analysis analyzeColumnSubtraction() src/utils/problemGenerator.ts Done
Subtraction in SkillSet Add subtraction-specific skills src/types/tutorial.ts Done
Curriculum definitions Level 1/2/3 PracticeStep configs New: src/curriculum/ Pending
Visualization mode Hide abacus option PracticeProblemPlayer.tsx Pending
Adaptive mastery Continue until N consecutive correct New logic Pending
Progress persistence Track technique mastery Database/localStorage Pending
Student profiles Extend players with curriculum progress New DB tables Done
Student selection UI Pick student before practice src/components/practice/ Done

Student Progress Architecture

Design Decision: Players ARE Students

Rather than creating a separate "student" concept, we extend the existing arcade player system:

  • Players already have identity - name, emoji, color (kid-friendly!)
  • Players are scoped to users - supports multiple kids per parent account
  • Active/inactive concept - can select which student is practicing
  • Guest-friendly - works without auth via guestId
  • Stats infrastructure exists - player_stats with per-game JSON breakdown

This means a child's avatar in arcade games is the same avatar they use for practice - unified experience.

Database Schema Extension

┌─────────────────────────────────────────────────────────────────┐
│                        EXISTING                                  │
├─────────────────────────────────────────────────────────────────┤
│  players                    │  player_stats                     │
│  ├── id                     │  ├── playerId (PK, FK)            │
│  ├── userId (FK)            │  ├── gamesPlayed                  │
│  ├── name                   │  ├── gameStats (JSON)             │
│  ├── emoji                  │  └── ...                          │
│  ├── color                  │                                   │
│  └── isActive               │                                   │
├─────────────────────────────────────────────────────────────────┤
│                        NEW TABLES                                │
├─────────────────────────────────────────────────────────────────┤
│  player_curriculum          │  player_skill_mastery             │
│  ├── playerId (PK, FK)      │  ├── id (PK)                      │
│  ├── currentLevel (1,2,3)   │  ├── playerId (FK)                │
│  ├── currentPhaseId         │  ├── skillId                      │
│  ├── worksheetPreset        │  ├── attempts                     │
│  ├── visualizationMode      │  ├── correct                      │
│  └── updatedAt              │  ├── consecutiveCorrect           │
│                             │  ├── masteryLevel                 │
│  practice_sessions          │  └── lastPracticedAt              │
│  ├── id (PK)                │                                   │
│  ├── playerId (FK)          │  UNIQUE(playerId, skillId)        │
│  ├── phaseId                │                                   │
│  ├── problemsAttempted      │                                   │
│  ├── problemsCorrect        │                                   │
│  ├── skillsUsed (JSON)      │                                   │
│  └── completedAt            │                                   │
└─────────────────────────────────────────────────────────────────┘

Data Models

// player_curriculum - Overall curriculum position for a player
interface PlayerCurriculum {
  playerId: string; // FK to players, PRIMARY KEY
  currentLevel: 1 | 2 | 3; // Which level they're on
  currentPhaseId: string; // e.g., "L1.add.+3.withFive"
  worksheetPreset: string; // Saved worksheet difficulty profile
  visualizationMode: boolean; // Practice without visible abacus
  updatedAt: Date;
}

// player_skill_mastery - Per-skill progress tracking
interface PlayerSkillMastery {
  id: string;
  playerId: string; // FK to players
  skillId: string; // e.g., "fiveComplements.4=5-1"
  attempts: number; // Total attempts using this skill
  correct: number; // Successful uses
  consecutiveCorrect: number; // Current streak (resets on error)
  masteryLevel: "learning" | "practicing" | "mastered";
  lastPracticedAt: Date;
  // UNIQUE constraint on (playerId, skillId)
}

// practice_sessions - Historical session data
interface PracticeSession {
  id: string;
  playerId: string;
  phaseId: string; // Which curriculum phase
  problemsAttempted: number;
  problemsCorrect: number;
  averageTimeMs: number;
  skillsUsed: string[]; // Skills exercised this session
  startedAt: Date;
  completedAt: Date;
}

Mastery Logic

const MASTERY_CONFIG = {
  consecutiveForMastery: 5, // 5 correct in a row = mastered
  minimumAttempts: 10, // Need at least 10 attempts
  accuracyThreshold: 0.85, // 85% accuracy for practicing → mastered
};

function updateMasteryLevel(skill: PlayerSkillMastery): MasteryLevel {
  if (
    skill.consecutiveCorrect >= MASTERY_CONFIG.consecutiveForMastery &&
    skill.attempts >= MASTERY_CONFIG.minimumAttempts &&
    skill.correct / skill.attempts >= MASTERY_CONFIG.accuracyThreshold
  ) {
    return "mastered";
  }
  if (skill.attempts >= 5) {
    return "practicing";
  }
  return "learning";
}

UX Flow

┌─────────────────────────────────────────────────────────────────┐
│ PARENT/TEACHER VIEW                                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  "Who is practicing today?"                                      │
│                                                                  │
│  ┌──────┐  ┌──────┐  ┌──────┐  ┌──────────┐                     │
│  │  😊  │  │  🦊  │  │  🚀  │  │        │                     │
│  │ Sonia│  │Carlos│  │ Maya │  │ Add New  │                     │
│  │ Lv.2 │  │ Lv.1 │  │ Lv.1 │  │ Student  │                     │
│  └──────┘  └──────┘  └──────┘  └──────────┘                     │
│                                                                  │
│  [Select student, hand computer to child]                        │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────────┐
│ STUDENT VIEW (after selection)                                   │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  Hi Sonia! 😊                                                    │
│                                                                  │
│  ┌─────────────────────────────────────────┐                    │
│  │  Level 2: Addition with Regrouping      │                    │
│  │  Current: +6 with Friends of 5          │                    │
│  │  ████████████░░░░░░░░  60% mastered     │                    │
│  │                                          │                    │
│  │  [Continue Practice]   [View Progress]  │                    │
│  └─────────────────────────────────────────┘                    │
│                                                                  │
│  [Generate Worksheet for My Level]                               │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

Worksheet Integration

When generating worksheets:

  1. No student selected: Manual difficulty selection (current behavior)
  2. Student selected:
    • Pre-populate settings based on their curriculum position
    • "Generate for Sonia's level" button
    • Still allow manual override

Practice Session Planning

Overview

A "session plan" is the system's recommendation for what a student should practice, generated based on:

  • Available time (specified by teacher)
  • Student's current curriculum position
  • Skill mastery levels (what needs work vs. what's mastered)
  • Spaced repetition needs (when were skills last practiced)

The teacher drives the process: select student → specify time → review plan → start session.

Session Flow

┌─────────────────────────────────────────────────────────────────┐
│  1. SETUP (Teacher)                                             │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐         │
│  │ Select      │ →  │ Set Time    │ →  │ Generate    │         │
│  │ Student     │    │ Available   │    │ Plan        │         │
│  └─────────────┘    └─────────────┘    └─────────────┘         │
├─────────────────────────────────────────────────────────────────┤
│  2. REVIEW PLAN (Teacher + Student)                             │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │  "Today's Practice for Emma"                    [Config]  │  │
│  │                                                    ⚙️     │  │
│  │  Time: ~15 minutes (20 problems)                         │  │
│  │                                                          │  │
│  │  Focus: Adding +3 using five-complement (12 problems)    │  │
│  │  Review: +1, +2 direct addition (6 problems)             │  │
│  │  Challenge: Mixed +1 to +3 (2 problems)                  │  │
│  │                                                          │  │
│  │  [Adjust Plan]              [Let's Go! ✓]                │  │
│  └──────────────────────────────────────────────────────────┘  │
├─────────────────────────────────────────────────────────────────┤
│  3. ACTIVE SESSION (Student, Teacher monitors)                  │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │  Problem 7 of 20                    ⏱️ 8:32 remaining    │  │
│  │  ████████░░░░░░░░░░░░ 35%                      [Config]  │  │
│  │                                                    ⚙️     │  │
│  │  Session Health: 🟢 On Track                             │  │
│  │  Accuracy: 85% (6/7)  |  Avg: 42s/problem                │  │
│  │                                                          │  │
│  │  [Teacher: Pause] [Teacher: Adjust] [Teacher: End Early] │  │
│  └──────────────────────────────────────────────────────────┘  │
├─────────────────────────────────────────────────────────────────┤
│  4. SESSION SUMMARY                                             │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │  Great job, Emma! 🎉                                     │  │
│  │                                                          │  │
│  │  Completed: 18/20 problems in 14 minutes                 │  │
│  │  Accuracy: 83%                                           │  │
│  │                                                          │  │
│  │  Mastery Progress:                                       │  │
│  │  +3 five-comp: Learning → Practicing ⬆️                  │  │
│  │  +1 direct: Mastered ✓ (reviewed)                        │  │
│  │                                                          │  │
│  │  [Done] [Generate Worksheet] [Start Another Session]     │  │
│  └──────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘

Configuration Inspector (Debug/Fine-tune Mode)

Both the Plan Review and Active Session screens include a "Config" button (⚙️) that opens a panel showing the exact configuration being applied. This is useful for:

  • Teachers: Understanding why certain problems appear
  • Developers: Debugging the plan generation algorithm
  • Fine-tuning: Adjusting parameters before starting
┌──────────────────────────────────────────────────────────────────┐
│  Session Configuration                                    [×]    │
├──────────────────────────────────────────────────────────────────┤
│  PLAN PARAMETERS                                                 │
│  ├── targetDuration: 15 minutes                                  │
│  ├── estimatedProblems: 20                                       │
│  ├── avgTimePerProblem: 45s (based on student history)           │
│  └── planGeneratedAt: 2024-01-15T10:30:00Z                       │
│                                                                  │
│  DISTRIBUTION                                                    │
│  ├── focus: 60% (12 problems)                                    │
│  │   └── targetSkills: ["fiveComplements.3=5-2"]                 │
│  ├── reinforce: 20% (4 problems)                                 │
│  │   └── targetSkills: ["basic.directAddition"]                  │
│  ├── review: 15% (3 problems)                                    │
│  │   └── targetSkills: ["fiveComplements.1=5-4"]                 │
│  └── challenge: 5% (1 problem)                                   │
│      └── targetSkills: ["mixed"]                                 │
│                                                                  │
│  PROBLEM CONSTRAINTS (Current Slot)                              │
│  ├── slotIndex: 7                                                │
│  ├── purpose: "focus"                                            │
│  ├── allowedSkills: { fiveComplements: { "3=5-2": true } }      │
│  ├── forbiddenSkills: { tenComplements: true }                   │
│  ├── digitRange: { min: 1, max: 2 }                              │
│  └── termCount: { min: 3, max: 5 }                               │
│                                                                  │
│  STUDENT STATE SNAPSHOT                                          │
│  ├── currentPhase: "L1.add.+3.five"                              │
│  ├── phaseProgress: 4/10 skills at 'practicing' or better        │
│  └── skillMastery:                                               │
│      ├── fiveComplements.3=5-2: { attempts: 23, accuracy: 74% }  │
│      ├── fiveComplements.2=5-3: { attempts: 18, accuracy: 89% }  │
│      └── basic.directAddition: { attempts: 45, accuracy: 96% }   │
│                                                                  │
│  [Copy to Clipboard]  [Export JSON]                              │
└──────────────────────────────────────────────────────────────────┘

Session Health Indicators

Real-time metrics visible to the teacher during the active session:

Indicator 🟢 Good 🟡 Warning 🔴 Struggling
Accuracy >80% 60-80% <60%
Pace On track or ahead 10-30% behind >30% behind
Streak 3+ consecutive correct Mixed results 3+ consecutive wrong
Engagement <60s per problem 60-90s per problem >90s or long pauses

Overall session health is the worst of the four indicators.

Teacher Adjustments Mid-Session

When the session isn't going well, the teacher can:

Adjustment Effect When to Use
Reduce Difficulty Switch remaining slots to easier problems Accuracy < 60%, frustration visible
Enable Scaffolding Turn on visualization mode (show abacus) Conceptual confusion
Narrow Focus Drop review/challenge, focus only on current skill Overwhelmed by variety
Take a Break Pause timer, allow discussion Long pauses, emotional state
Extend Session Add more problems Going well, student wants more
End Gracefully Complete current problem, show summary Time constraint, fatigue

All adjustments are logged in SessionPlan.adjustments[] for later analysis.

Data Model

interface SessionPlan {
  id: string;
  playerId: string;

  // Setup parameters
  targetDurationMinutes: number;
  estimatedProblemCount: number;
  avgTimePerProblemSeconds: number; // Calculated from student history

  // Problem slots (generated upfront, can be modified)
  slots: ProblemSlot[];

  // Human-readable summary for plan review screen
  summary: SessionSummary;

  // State machine
  status: "draft" | "approved" | "in_progress" | "completed" | "abandoned";

  // Timestamps
  createdAt: Date;
  approvedAt?: Date; // When teacher/student clicked "Let's Go"
  startedAt?: Date; // When first problem displayed
  completedAt?: Date;

  // Live tracking
  currentSlotIndex: number;
  sessionHealth: SessionHealth;
  adjustments: SessionAdjustment[];

  // Results (filled in as session progresses)
  results: SlotResult[];
}

interface ProblemSlot {
  index: number;
  purpose: "focus" | "reinforce" | "review" | "challenge";

  // Constraints passed to problem generator
  constraints: {
    allowedSkills?: Partial<SkillSet>;
    targetSkills?: Partial<SkillSet>;
    forbiddenSkills?: Partial<SkillSet>;
    digitRange?: { min: number; max: number };
    termCount?: { min: number; max: number };
    operator?: "addition" | "subtraction" | "mixed";
  };

  // Generated problem (filled when slot is reached)
  problem?: GeneratedProblem;
}

interface SessionSummary {
  focusDescription: string; // "Adding +3 using five-complement"
  focusCount: number;
  reviewSkills: string[]; // Human-readable skill names
  reviewCount: number;
  challengeCount: number;
  estimatedMinutes: number;
}

interface SessionHealth {
  overall: "good" | "warning" | "struggling";
  accuracy: number; // 0-1
  pacePercent: number; // 100 = on track, <100 = behind
  currentStreak: number; // Positive = correct streak, negative = wrong streak
  avgResponseTimeMs: number;
}

interface SessionAdjustment {
  timestamp: Date;
  type:
    | "difficulty_reduced"
    | "scaffolding_enabled"
    | "focus_narrowed"
    | "paused"
    | "resumed"
    | "extended"
    | "ended_early";
  reason?: string; // Optional teacher note
  previousHealth: SessionHealth;
}

interface SlotResult {
  slotIndex: number;
  problem: GeneratedProblem;
  studentAnswer: number;
  isCorrect: boolean;
  responseTimeMs: number;
  skillsExercised: string[]; // Which skills this problem tested
  timestamp: Date;
}

Plan Generation Algorithm

interface PlanGenerationConfig {
  // Distribution weights (should sum to 1.0)
  focusWeight: number; // Default: 0.60
  reinforceWeight: number; // Default: 0.20
  reviewWeight: number; // Default: 0.15
  challengeWeight: number; // Default: 0.05

  // Timing
  defaultSecondsPerProblem: number; // Default: 45

  // Spaced repetition
  reviewIntervalDays: {
    mastered: number; // Default: 7 (review mastered skills weekly)
    practicing: number; // Default: 3 (review practicing skills every 3 days)
  };
}

function generateSessionPlan(
  playerId: string,
  durationMinutes: number,
  config: PlanGenerationConfig = DEFAULT_CONFIG,
): SessionPlan {
  // 1. Load student state
  const curriculum = await getPlayerCurriculum(playerId);
  const skillMastery = await getAllSkillMastery(playerId);
  const recentSessions = await getRecentSessions(playerId, 10);

  // 2. Calculate personalized timing
  const avgTime =
    calculateAvgTimePerProblem(recentSessions) ??
    config.defaultSecondsPerProblem;
  const problemCount = Math.floor((durationMinutes * 60) / avgTime);

  // 3. Categorize skills by need
  const currentPhaseSkills = getSkillsForPhase(curriculum.currentPhaseId);
  const struggling = skillMastery.filter(
    (s) =>
      currentPhaseSkills.includes(s.skillId) && s.correct / s.attempts < 0.7,
  );
  const needsReview = skillMastery.filter(
    (s) =>
      s.masteryLevel === "mastered" &&
      daysSince(s.lastPracticedAt) > config.reviewIntervalDays.mastered,
  );

  // 4. Calculate slot distribution
  const focusCount = Math.round(problemCount * config.focusWeight);
  const reinforceCount = Math.round(problemCount * config.reinforceWeight);
  const reviewCount = Math.round(problemCount * config.reviewWeight);
  const challengeCount =
    problemCount - focusCount - reinforceCount - reviewCount;

  // 5. Build slots with constraints
  const slots: ProblemSlot[] = [];

  // Focus slots: current phase, primary skill
  for (let i = 0; i < focusCount; i++) {
    slots.push({
      index: slots.length,
      purpose: "focus",
      constraints: buildConstraintsForPhase(curriculum.currentPhaseId),
    });
  }

  // Reinforce slots: struggling skills get extra practice
  for (let i = 0; i < reinforceCount; i++) {
    const skill = struggling[i % struggling.length];
    slots.push({
      index: slots.length,
      purpose: "reinforce",
      constraints: buildConstraintsForSkill(skill?.skillId),
    });
  }

  // Review slots: spaced repetition of mastered skills
  for (let i = 0; i < reviewCount; i++) {
    const skill = needsReview[i % needsReview.length];
    slots.push({
      index: slots.length,
      purpose: "review",
      constraints: buildConstraintsForSkill(skill?.skillId),
    });
  }

  // Challenge slots: slightly harder or mixed
  for (let i = 0; i < challengeCount; i++) {
    slots.push({
      index: slots.length,
      purpose: "challenge",
      constraints: buildChallengeConstraints(curriculum),
    });
  }

  // 6. Shuffle to interleave purposes (but keep some focus problems together)
  const shuffledSlots = intelligentShuffle(slots);

  // 7. Build summary
  const summary = buildHumanReadableSummary(shuffledSlots, curriculum);

  return {
    id: generateId(),
    playerId,
    targetDurationMinutes: durationMinutes,
    estimatedProblemCount: problemCount,
    avgTimePerProblemSeconds: avgTime,
    slots: shuffledSlots,
    summary,
    status: "draft",
    createdAt: new Date(),
    currentSlotIndex: 0,
    sessionHealth: {
      overall: "good",
      accuracy: 1,
      pacePercent: 100,
      currentStreak: 0,
      avgResponseTimeMs: 0,
    },
    adjustments: [],
    results: [],
  };
}

Database Schema Addition

-- Add to existing schema
CREATE TABLE session_plans (
  id TEXT PRIMARY KEY,
  player_id TEXT NOT NULL REFERENCES players(id) ON DELETE CASCADE,

  -- Setup
  target_duration_minutes INTEGER NOT NULL,
  estimated_problem_count INTEGER NOT NULL,
  avg_time_per_problem_seconds INTEGER NOT NULL,

  -- Slots and summary stored as JSON
  slots TEXT NOT NULL,           -- JSON array of ProblemSlot
  summary TEXT NOT NULL,         -- JSON SessionSummary

  -- State
  status TEXT NOT NULL DEFAULT 'draft',
  current_slot_index INTEGER NOT NULL DEFAULT 0,
  session_health TEXT,           -- JSON SessionHealth
  adjustments TEXT,              -- JSON array of SessionAdjustment
  results TEXT,                  -- JSON array of SlotResult

  -- Timestamps
  created_at INTEGER NOT NULL,
  approved_at INTEGER,
  started_at INTEGER,
  completed_at INTEGER
);

CREATE INDEX idx_session_plans_player ON session_plans(player_id);
CREATE INDEX idx_session_plans_status ON session_plans(status);

API Endpoints

POST   /api/curriculum/{playerId}/sessions/plan
       Body: { durationMinutes: number, config?: PlanGenerationConfig }
       Returns: SessionPlan (status: 'draft')

GET    /api/curriculum/{playerId}/sessions/plan/{planId}
       Returns: SessionPlan with full slot details

PATCH  /api/curriculum/{playerId}/sessions/plan/{planId}
       Body: { status: 'approved' } or adjustment data
       Returns: Updated SessionPlan

POST   /api/curriculum/{playerId}/sessions/plan/{planId}/slot/{index}/result
       Body: { studentAnswer: number, responseTimeMs: number }
       Returns: { isCorrect, updatedHealth, nextSlot? }

POST   /api/curriculum/{playerId}/sessions/plan/{planId}/adjust
       Body: { type: AdjustmentType, reason?: string }
       Returns: Updated SessionPlan with modified remaining slots

Practice Experience

Overview

The practice experience is the actual problem-solving interface where the student works through their session plan. The computer/phone serves as the primary proctoring device - displaying problems, collecting answers, and tracking progress.

Design Principles

  1. One problem at a time - Clean, focused display without distraction
  2. Physical abacus preferred - On-screen abacus is a "last resort"
  3. Device-appropriate input - Native keyboard on desktop, simplified keypad on phone
  4. Visualization mode - Encourages mental math by hiding abacus aids
  5. Skill-appropriate problems - Never ask for skills not yet learned

Hardware Recommendations

┌─────────────────────────────────────────────────────────────────┐
│  RECOMMENDED SETUP                                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│    ┌─────────────┐       ┌───────────────────────┐             │
│    │   SCREEN    │       │   PHYSICAL ABACUS     │             │
│    │  (problems) │       │   (3D printed STL)    │             │
│    │             │       │                       │             │
│    │   45 + 23   │       │  ☐ ☐ ☐ ☐ ☐ ☐ ☐ ☐ ☐   │             │
│    │             │       │  ───────────────────  │             │
│    │   [  68  ]  │       │  ● ● ● ● ○ ○ ○ ○ ○   │             │
│    │             │       │  ● ● ● ● ○ ○ ○ ○ ○   │             │
│    └─────────────┘       │  ● ● ● ● ○ ○ ○ ○ ○   │             │
│                          │  ● ● ● ● ○ ○ ○ ○ ○   │             │
│                          └───────────────────────┘             │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Problem Formats

The curriculum uses two distinct problem formats:

1. Vertical (Columnar) Format - Primary

This is the main format from the workbooks. Numbers are stacked vertically:

  • Plus sign omitted - Addition is implicit
  • Minus sign shown - Only subtraction is marked
  • Answer box at bottom - Student fills in the result
┌─────────────────────────────────────────────────────────────────┐
│  VERTICAL FORMAT (Primary - Parts 1 & 2)                        │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  Problem 7 of 20                           ⏱️ 8:32 remaining   │
│  ████████░░░░░░░░░░░░ 35%                                      │
│                                                                 │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │                                                          │  │
│  │                         88                               │  │
│  │                         61                               │  │
│  │                         33                               │  │
│  │                       - 55                               │  │
│  │                         47                               │  │
│  │                       - 28                               │  │
│  │                      ──────                              │  │
│  │                    ┌───────┐                             │  │
│  │                    │  146  │  ← Student input            │  │
│  │                    └───────┘                             │  │
│  │                                                          │  │
│  │                      [Submit]                            │  │
│  │                                                          │  │
│  └──────────────────────────────────────────────────────────┘  │
│                                                                 │
│  [Need Help?]                              [Show Abacus]        │
│                                            (last resort)        │
└─────────────────────────────────────────────────────────────────┘

2. Linear Format - Mental Math (Part 3)

After visualization practice, students progress to linear problems - sequences presented as a math sentence for mental calculation:

┌─────────────────────────────────────────────────────────────────┐
│  LINEAR FORMAT (Part 3 - Mental Math)                           │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  Problem 4 of 10                    🧠 Mental Math Practice     │
│  ████████████░░░░░░░░ 40%                                      │
│                                                                 │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │                                                          │  │
│  │           88 + 61 + 33 - 55 + 47 - 28 = ?                │  │
│  │                                                          │  │
│  │                    ┌───────────┐                         │  │
│  │                    │    146    │                         │  │
│  │                    └───────────┘                         │  │
│  │                                                          │  │
│  │                      [Submit]                            │  │
│  │                                                          │  │
│  └──────────────────────────────────────────────────────────┘  │
│                                                                 │
│  💭 "Visualize the beads as you work through each number"      │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Daily Practice Structure

Based on the workbook format, a typical daily practice session has three parts:

Part Format Abacus Purpose
Part 1: Skill Building Vertical Physical abacus Build muscle memory, learn techniques
Part 2: Visualization Vertical Hidden/mental Internalize bead movements mentally
Part 3: Mental Math Linear None Pure mental calculation, no visual aid

Input Methods

Device Primary Input Implementation
Desktop/Laptop Native keyboard <input type="number"> with auto-focus
Tablet with keyboard Native keyboard Same as desktop
Phone/Touch tablet Virtual keypad react-simple-keyboard numeric layout

Phone Keypad Implementation

Reference existing implementations:

  • Know Your World: src/arcade-games/know-your-world/components/SimpleLetterKeyboard.tsx
    • Uses react-simple-keyboard v3.8.139
    • Configured for letter input in learning mode
  • Memory Quiz: src/arcade-games/memory-quiz/components/InputPhase.tsx
    • Custom numeric keypad implementation
    • Device detection logic for keyboard vs touch
// Simplified numeric keypad for practice
const numericLayout = {
  default: ["7 8 9", "4 5 6", "1 2 3", "{bksp} 0 {enter}"],
};

// Use device detection from memory quiz
const useDeviceType = () => {
  // Returns 'desktop' | 'tablet' | 'phone'
  // Based on screen size and touch capability
};

Abacus Access

The on-screen abacus is available as a last resort, not a primary tool:

┌─────────────────────────────────────────────────────────────────┐
│  ABACUS ACCESS (when clicked)                                   │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │  💡 Tip: Using a physical abacus helps build muscle      │  │
│  │     memory! We have a 3D-printable model available.      │  │
│  │                                                          │  │
│  │     [Download STL]    [Show On-Screen Abacus Anyway]     │  │
│  └──────────────────────────────────────────────────────────┘  │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

If the student insists, show the AbacusReact component from @soroban/abacus-react.

Tracking: Log when students use the on-screen abacus to identify those who may need a physical one.

Visualization Mode

When visualizationMode: true in the student's curriculum settings:

┌─────────────────────────────────────────────────────────────────┐
│  VISUALIZATION MODE                                             │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  Problem 7 of 20                    🧠 Visualization Practice   │
│                                                                 │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │                                                          │  │
│  │              45 + 23 + 12 + 8 = ?                        │  │
│  │                                                          │  │
│  │         💭 "Picture the beads in your mind"              │  │
│  │                                                          │  │
│  │                    ┌───────────┐                         │  │
│  │                    │           │                         │  │
│  │                    └───────────┘                         │  │
│  │                                                          │  │
│  └──────────────────────────────────────────────────────────┘  │
│                                                                 │
│  [Having Trouble?]          ← Opens hints, NOT the abacus      │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Visualization mode behaviors:

  • Hide "Show Abacus" button entirely
  • Add gentle reminder: "Picture the beads in your mind"
  • If student struggles (2+ wrong in a row):
    • Offer guided visualization hints
    • Suggest stepping back to physical abacus practice
    • Do NOT automatically show abacus
  • Track accuracy separately for visualization vs. abacus-assisted

Skill Constraint Enforcement

CRITICAL: Never present problems requiring skills the student hasn't learned yet.

The problem generator (src/utils/problemGenerator.ts) already supports:

  • allowedSkills - Skills the problem MUST use
  • targetSkills - Skills we're trying to practice
  • forbiddenSkills - Skills the problem must NOT use
// For a Level 1 student who has only learned +1, +2, +3 direct addition:
const constraints = {
  forbiddenSkills: {
    fiveComplements: true, // No five-complement techniques
    tenComplements: true, // No ten-complement techniques
    tenComplementsSub: true, // No subtraction borrowing
    fiveComplementsSub: true, // No subtraction with fives
  },
  allowedSkills: {
    basic: { directAddition: true },
  },
};

Audit checklist for problem generation:

  1. analyzeRequiredSkills() accurately categorizes all techniques needed
  2. problemMatchesSkills() correctly validates against constraints
  3. Create curriculum phase → constraints mapping
  4. Validate no "skill leak" (problems requiring unlearned techniques)

Existing Components to Leverage

Component Location Purpose
PracticeProblemPlayer src/components/tutorial/PracticeProblemPlayer.tsx Existing practice UI (abacus-based input)
SimpleLetterKeyboard src/arcade-games/know-your-world/components/SimpleLetterKeyboard.tsx react-simple-keyboard integration
InputPhase src/arcade-games/memory-quiz/components/InputPhase.tsx Custom numeric keypad + device detection
problemGenerator src/utils/problemGenerator.ts Skill-constrained problem generation
AbacusReact @soroban/abacus-react On-screen abacus (last resort)

Data Model Extensions

interface PracticeAnswer {
  slotIndex: number;
  studentAnswer: number;
  isCorrect: boolean;
  responseTimeMs: number;
  inputMethod: "keyboard" | "virtual_keypad" | "touch";
  usedOnScreenAbacus: boolean; // Track abacus usage
  visualizationMode: boolean; // Was this in visualization mode?
}

// For identifying students who may need a physical abacus
interface StudentAbacusUsage {
  onScreenAbacusUsed: number; // Count of problems using on-screen
  totalProblems: number;
  usageRate: number; // Percentage
  suggestPhysicalAbacus: boolean; // true if usage rate > 30%
}

Mobile Responsiveness

┌────────────────────┐
│ PHONE - VERTICAL   │
├────────────────────┤
│                    │
│   Problem 7/20     │
│   ████░░░░░ 35%    │
│                    │
│         88         │
│         61         │
│         33         │
│       - 55         │
│         47         │
│       - 28         │
│       ──────       │
│    ┌────────┐      │
│    │  146   │      │
│    └────────┘      │
│                    │
│  ┌──┬──┬──┐       │
│  │ 7│ 8│ 9│       │
│  ├──┼──┼──┤       │
│  │ 4│ 5│ 6│       │
│  ├──┼──┼──┤       │
│  │ 1│ 2│ 3│       │
│  ├──┼──┼──┤       │
│  │ ⌫│ 0│ ⏎│       │
│  └──┴──┴──┘       │
│                    │
└────────────────────┘
┌────────────────────┐
│ PHONE - LINEAR     │
│ (Mental Math)      │
├────────────────────┤
│                    │
│   Problem 4/10     │
│  🧠 Mental Math    │
│                    │
│  88 + 61 + 33      │
│  - 55 + 47 - 28    │
│      = ?           │
│                    │
│    ┌────────┐      │
│    │  146   │      │
│    └────────┘      │
│                    │
│  ┌──┬──┬──┐       │
│  │ 7│ 8│ 9│       │
│  ├──┼──┼──┤       │
│  │ 4│ 5│ 6│       │
│  ├──┼──┼──┤       │
│  │ 1│ 2│ 3│       │
│  ├──┼──┼──┤       │
│  │ ⌫│ 0│ ⏎│       │
│  └──┴──┴──┘       │
│                    │
└────────────────────┘

Implementation Phases

Phase 0: Student Progress Infrastructure COMPLETE

Goal: Create database tables and basic UI for tracking student progress through the curriculum.

Tasks:

  1. Create player_curriculum table schema - src/db/schema/player-curriculum.ts
  2. Create player_skill_mastery table schema - src/db/schema/player-skill-mastery.ts
  3. Create practice_sessions table schema - src/db/schema/practice-sessions.ts
  4. Migrations run automatically on app start
  5. Create student selection UI component - src/components/practice/StudentSelector.tsx
  6. Create progress dashboard component - src/components/practice/ProgressDashboard.tsx
  7. Create API routes for curriculum progress CRUD - src/app/api/curriculum/[playerId]/
  8. Create src/lib/curriculum/progress-manager.ts
  9. Create src/hooks/usePlayerCurriculum.ts
  10. Create /practice page - src/app/practice/page.tsx

Files Created:

  • src/db/schema/player-curriculum.ts - Curriculum position tracking
  • src/db/schema/player-skill-mastery.ts - Per-skill mastery tracking with MASTERY_CONFIG and calculateMasteryLevel()
  • src/db/schema/practice-sessions.ts - Practice session history
  • src/components/practice/StudentSelector.tsx - Student selection UI
  • src/components/practice/ProgressDashboard.tsx - Progress display and actions
  • src/components/practice/index.ts - Component exports
  • src/lib/curriculum/progress-manager.ts - CRUD operations for curriculum data
  • src/hooks/usePlayerCurriculum.ts - Client-side curriculum state management
  • src/app/api/curriculum/[playerId]/route.ts - GET/PATCH curriculum
  • src/app/api/curriculum/[playerId]/advance/route.ts - POST advance phase
  • src/app/api/curriculum/[playerId]/skills/route.ts - POST record skill attempt
  • src/app/api/curriculum/[playerId]/skills/batch/route.ts - POST batch record
  • src/app/api/curriculum/[playerId]/sessions/route.ts - POST start session
  • src/app/api/curriculum/[playerId]/sessions/[sessionId]/complete/route.ts - POST complete
  • src/app/practice/page.tsx - Practice entry point page

Phase 1: Problem Generator Extension COMPLETE

Goal: Enable the problem generator to handle subtraction and properly categorize "with/without friends of 5".

Tasks:

  1. Add analyzeColumnSubtraction() function - src/utils/problemGenerator.ts:148
  2. Add subtraction skills to SkillSet type - src/types/tutorial.ts:36
    • fiveComplementsSub: -4=-5+1, -3=-5+2, -2=-5+3, -1=-5+4
    • tenComplementsSub: -9=+1-10, -8=+2-10, ... -1=+9-10
    • basic.directSubtraction, basic.heavenBeadSubtraction, basic.simpleCombinationsSub
  3. Add analyzeSubtractionStepSkills() function - src/utils/problemGenerator.ts:225
  4. Refactor problemMatchesSkills() and findValidNextTerm() to support new skill categories
  5. Test with Storybook stories (pending)
  6. Add generateSubtractionSequence() for mixed operation problems (pending)

Phase 2: Curriculum Definitions

Goal: Define the Level 1/2/3 structure as data that drives practice.

Tasks:

  1. Create curriculum data structure:

    interface CurriculumLevel {
      id: string;
      name: string;
      description: string;
      phases: CurriculumPhase[];
    }
    
    interface CurriculumPhase {
      targetNumber: number; // +1, +2, ... +9 or -9, -8, ... -1
      operation: "addition" | "subtraction";
      useFiveComplement: boolean;
      usesTenComplement: boolean;
      practiceStep: PracticeStep; // Existing type
    }
    
  2. Define all phases for Level 1, 2, 3

  3. Create helper to convert curriculum phase to PracticeStep constraints

Phase 3: Daily Practice Mode NEXT UP

Goal: A /practice page that guides students through the curriculum with intelligent session planning.

Tasks:

  1. Create /app/practice/page.tsx - Basic structure done
  2. Track current position in curriculum - Database schema done
  3. Create session plan generator (src/lib/curriculum/session-planner.ts)
  4. Create session_plans database table
  5. Create Plan Review screen component
  6. Create Active Session screen component
  7. Create Configuration Inspector component
  8. Create Session Summary screen component
  9. Implement session health tracking and indicators
  10. Implement teacher adjustment controls
  11. Visualization toggle (show/hide abacus)
  12. API routes for session plan CRUD

Sub-phases:

Phase 3a: Session Plan Generation

  • Create SessionPlan type definitions
  • Implement generateSessionPlan() algorithm
  • Create session_plans table schema
  • API: POST /api/curriculum/{playerId}/sessions/plan

Phase 3b: Plan Review UI

  • Plan summary display
  • Configuration inspector (debug panel)
  • "Adjust Plan" controls
  • "Let's Go" approval flow

Phase 3c: Active Session UI (Practice Experience)

  • One-problem-at-a-time display with progress bar
  • Timer and pace tracking
  • Device-appropriate input:
    • Desktop: native keyboard with auto-focus
    • Phone: react-simple-keyboard numeric keypad (reference: SimpleLetterKeyboard.tsx)
    • Device detection logic (reference: InputPhase.tsx)
  • On-screen abacus access (last resort):
    • Prompt suggesting physical abacus first
    • Track on-screen abacus usage
    • AbacusReact from @soroban/abacus-react
  • Visualization mode:
    • Hide abacus button entirely
    • "Picture the beads" reminder
    • Guided hints for struggling students
  • Session health indicators (accuracy, pace, streak, engagement)
  • Teacher controls (pause, adjust, end early)
  • Configuration inspector (current slot details)

Phase 3d: Session Completion

  • Summary display with results
  • Mastery level changes
  • Skill update and persistence
  • Next steps (worksheet, another session, done)

Phase 4: Worksheet Integration

Goal: Generate printable worksheets targeting specific techniques.

Tasks:

  1. Add "technique mode" to worksheet config
  2. Allow selecting specific curriculum phase for worksheet
  3. Generate problems using same constraints as online practice

Technical Details

Skill Analysis Logic

Current addition analysis (from analyzeColumnAddition):

  • Checks if adding termDigit to currentDigit requires:
    • Direct addition (result ≤ 4)
    • Heaven bead (involves 5)
    • Five complement (needs +5-n)
    • Ten complement (needs -n+10)

Subtraction analysis (to implement):

  • Check if subtracting termDigit from currentDigit requires:
    • Direct subtraction (have enough earth beads)
    • Heaven bead removal (have 5-bead to remove)
    • Five complement (needs -5+n)
    • Ten complement (needs +n-10)

"With/Without Friends of 5" Implementation

Use forbiddenSkills to exclude five-complement techniques:

// Level 1, +3, WITHOUT friends of 5
const practiceStep: PracticeStep = {
  allowedSkills: { basic: { directAddition: true, heavenBead: true } },
  targetSkills: {
    /* target +3 specifically */
  },
  forbiddenSkills: {
    fiveComplements: {
      "3=5-2": true,
      "2=5-3": true,
      "1=5-4": true,
      "4=5-1": true,
    },
  },
};

// Level 1, +3, WITH friends of 5
const practiceStep: PracticeStep = {
  allowedSkills: {
    basic: { directAddition: true, heavenBead: true },
    fiveComplements: { "2=5-3": true },
  },
  targetSkills: { fiveComplements: { "2=5-3": true } }, // Specifically target +3 via +5-2
};

Assessment Data to Track

  • Per technique:

    • Total attempts
    • Correct count
    • Consecutive correct streak
    • Average time per problem
    • Error patterns (which complement pairs are weak)
  • Per session:

    • Date/time
    • Problems completed
    • Accuracy
    • Techniques practiced

Questions Resolved

Question Answer
Problem format? Multi-term sequences (3-7 terms), like the books
Single-digit first? No, double-digit from the start
Visualization mode? No abacus visible - that's the point of mental math
Adaptive mastery? Yes, continue until demonstrated proficiency

Sources