All files / server/services/tutor/evaluation anchor-course-content.ts

100% Statements 27/27
100% Branches 9/9
100% Functions 6/6
100% Lines 27/27

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228          7x                 7x     457x                                                                                                                                                                     457x       159x                                                                                           457x                         457x           7x               7x                 234x               457x     234x   64x 64x 64x 64x 64x 64x 64x             159x 159x 159x 159x 159x 159x 159x 159x                
import type { TutorCapability } from "../../course-content/course-content-loader";
import { curriculumTopicSchema, validateCurriculumTopic, type CurriculumTopic } from "../curriculum/curriculum-schema";
import { createTutorProgressionState, type TutorProgressionState } from "../curriculum/progression-state";
import type { TutorPlanningContentContext } from "../tutor-planning";
 
export const ANCHOR_COURSE_CONTENT_FIXTURE_IDS = [
  "variables",
  "progression-learn",
  "progression-deepen",
  "progression-expand",
] as const;
 
export type AnchorCourseContentFixtureId = typeof ANCHOR_COURSE_CONTENT_FIXTURE_IDS[number];
 
const REVISION = "2".repeat(40);
 
function variableTopic(schemaVersion: 1 | 2 = 1): CurriculumTopic {
  const raw = {
    schemaVersion,
    id: "variables-and-serial",
    title: "Variablen und Serial-Ausgabe",
    locale: "de-DE",
    activation: { any: [{ fact: "serial-call", values: ["print"] }] },
    concepts: [{
      id: "variable-values",
      title: "Variablenwerte",
      objective: "Den Zusammenhang zwischen einem deklarierten Integerwert und seiner Verwendung erklären.",
      prerequisites: [],
      difficulty: { entry: [1, 50], transfer: [20, 80] },
      misconceptions: [],
      indicators: [{ id: "relates-value-to-use", description: "Ordnet einen Integerwert seiner Verwendung im Sketch zu." }],
      mastery: {
        minimumSuccessfulProbes: 1,
        successRatingAtLeast: 3,
        requiredIndicators: ["relates-value-to-use"],
        minimumDistinctQuestionKinds: 1,
        recentWeakAnswersAllowed: 0,
      },
    }],
    questions: [
      {
        id: "variable-value-recall",
        concept: "variable-values",
        indicator: "relates-value-to-use",
        kind: "recall",
        difficulty: [1, 50],
        requires: [{ fact: "type-used", values: ["int"] }],
        text: "Welche Rolle spielt der Integer-Datentyp im aktuellen Sketch?",
      },
      {
        id: "serial-output-prediction",
        concept: "variable-values",
        indicator: "relates-value-to-use",
        kind: "prediction",
        difficulty: [20, 70],
        requires: [{ fact: "serial-call", values: ["print"] }],
        text: "Welche Ausgabe erzeugt Serial.println im aktuellen Sketch?",
      },
      {
        id: "variable-output-transfer",
        concept: "variable-values",
        indicator: "relates-value-to-use",
        kind: "transfer",
        difficulty: [30, 90],
        requires: [{ fact: "type-used", values: ["int"] }, { fact: "serial-call", values: ["print"] }],
        text: "Wie würdest du die Veränderung von counter an der seriellen Ausgabe überprüfen?",
      },
      // Post-mastery content of the DEEPEN/EXPAND fixtures. Its premise (a stored int value that is
      // printed via Serial) holds on both anchor sketches.
      ...(schemaVersion === 2 ? [{
        id: "variable-output-application",
        concept: "variable-values",
        indicator: "relates-value-to-use",
        kind: "application",
        difficulty: [20, 90],
        requires: [{ fact: "type-used", values: ["int"] }, { fact: "serial-call", values: ["print"] }],
        text: "Wie hängt der gespeicherte Wert von counter mit der seriellen Ausgabe im aktuellen Sketch zusammen?",
      }] : []),
    ],
    scaffolds: [],
    progression: {
      entryConcepts: ["variable-values"],
      preferredOrder: ["variable-values"],
      onRating: {
        "1-2": "remediate",
        "3": "clarify-same-indicator",
        "4": "probe-missing-indicator",
        "5": "evaluate-mastery-and-advance",
      },
    },
    ...(schemaVersion === 2 ? {
      deepening: {
        minimumSuccessfulProbes: 1,
        successRatingAtLeast: 4,
        requiredQuestionKinds: ["transfer"],
        recentWeakAnswersAllowed: 0,
      },
      extensions: [{ topic: "serial-output", objective: "Eine weitere serielle Beobachtung am Sketch ableiten." }],
    } : {}),
  };
  return validateCurriculumTopic(curriculumTopicSchema.parse(raw));
}
 
function serialOutputTopic(): CurriculumTopic {
  return validateCurriculumTopic(curriculumTopicSchema.parse({
    schemaVersion: 1,
    id: "serial-output",
    title: "Serielle Ausgabe",
    locale: "de-DE",
    activation: { any: [{ fact: "type-used", values: ["float"] }] },
    concepts: [{
      id: "serial-observation",
      title: "Serielle Beobachtung",
      objective: "Eine serielle Ausgabe als Beobachtung beschreiben.",
      prerequisites: [],
      difficulty: { entry: [1, 60], transfer: [20, 80] },
      misconceptions: [],
      indicators: [{ id: "observes-output", description: "Beschreibt eine serielle Ausgabe." }],
      mastery: {
        minimumSuccessfulProbes: 1,
        successRatingAtLeast: 3,
        requiredIndicators: ["observes-output"],
        minimumDistinctQuestionKinds: 1,
        recentWeakAnswersAllowed: 0,
      },
    }],
    questions: [{
      id: "serial-output-observe",
      concept: "serial-observation",
      indicator: "observes-output",
      kind: "prediction",
      difficulty: [1, 60],
      requires: [{ fact: "type-used", values: ["float"] }],
      text: "Welche serielle Beobachtung erwartest du?",
    }],
    scaffolds: [],
    progression: {
      entryConcepts: ["serial-observation"],
      preferredOrder: ["serial-observation"],
      onRating: {
        "1-2": "remediate",
        "3": "clarify-same-indicator",
        "4": "probe-missing-indicator",
        "5": "evaluate-mastery-and-advance",
      },
    },
  }));
}
 
function tutorCapability(topics: readonly CurriculumTopic[]): TutorCapability {
  return {
    status: "valid",
    manifest: {
      schemaVersion: 2,
      topics: [],
      strategies: [],
    },
    topics,
    strategies: [],
  };
}
 
function baseState(): TutorProgressionState {
  return createTutorProgressionState(REVISION);
}
 
// A state the product can reach: the Topic is mastered through the LEARN answer that
// strategy-learn-strong-answer gives (one successful prediction probe), and EXPAND is reached
// through the DEEPEN transfer probe that the schema-v2 deepening criterion requires.
const LEARN_MASTERY_EVIDENCE = {
  questionId: "serial-output-prediction",
  conceptId: "variable-values",
  indicatorId: "relates-value-to-use",
  kind: "prediction",
  rating: 4,
} as const;
 
const DEEPEN_TRANSFER_EVIDENCE = {
  questionId: "variable-output-transfer",
  conceptId: "variable-values",
  indicatorId: "relates-value-to-use",
  kind: "transfer",
  rating: 4,
} as const;
 
function variablesCourseContent(): TutorPlanningContentContext {
  return {
    revision: REVISION,
    tutor: tutorCapability([variableTopic()]),
    progressionState: baseState(),
  };
}
 
export function createAnchorCourseContent(fixtureId: AnchorCourseContentFixtureId): TutorPlanningContentContext {
  switch (fixtureId) {
    case "variables":
    case "progression-learn":
      return variablesCourseContent();
    case "progression-deepen": {
      const state = baseState();
      state.activeTopicId = "variables-and-serial";
      state.phase = "DEEPEN";
      state.masteredTopicIds = ["variables-and-serial"];
      state.masteryEvidence = { "variables-and-serial": [{ ...LEARN_MASTERY_EVIDENCE }] };
      state.retainedPhases = { "variables-and-serial": "DEEPEN" };
      return {
        revision: REVISION,
        tutor: tutorCapability([variableTopic(2)]),
        progressionState: state,
      };
    }
    case "progression-expand": {
      const state = baseState();
      state.activeTopicId = "variables-and-serial";
      state.phase = "EXPAND";
      state.masteredTopicIds = ["variables-and-serial"];
      state.masteryEvidence = { "variables-and-serial": [{ ...LEARN_MASTERY_EVIDENCE }] };
      state.postMasteryEvidence = { "variables-and-serial": [{ ...DEEPEN_TRANSFER_EVIDENCE }] };
      state.retainedPhases = { "variables-and-serial": "EXPAND" };
      return {
        revision: REVISION,
        tutor: tutorCapability([variableTopic(2), serialOutputTopic()]),
        progressionState: state,
      };
    }
  }
}