All files / server/services/tutor/curriculum inline-focus-topic.ts

84.21% Statements 16/19
60% Branches 9/15
100% Functions 6/6
82.35% Lines 14/17

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      21x                 30x           60x                               87x                       60x                 30x       30x 30x 30x 390x 390x           30x 30x 30x    
import type { ExampleFocusArea } from "../../course-content/embedded-tutor-annotation";
import { validateCurriculumTopic, type CurriculumTopic } from "./curriculum-schema";
 
export const INLINE_FOCUS_TOPIC_PREFIX = "inline-";
 
/**
 * Builds the Topic for the focus areas a teacher wrote into an Example's annotation. Every focus area
 * is one Concept with its questions; a focus area is mastered once every one of its questions was
 * answered successfully, and progression uses fixed defaults. The questions carry
 * no fact requirements: the teacher authored them for exactly this sketch.
 */
export function buildInlineFocusTopic(exampleId: string, focus: readonly ExampleFocusArea[]): CurriculumTopic {
  const topic: CurriculumTopic = {
    schemaVersion: 1,
    id: `${INLINE_FOCUS_TOPIC_PREFIX}${toSafeId(exampleId)}`,
    title: "Fokus dieses Beispiels",
    locale: "de-DE",
    activation: { any: [{ fact: "serial-call", values: ["print", "write"] }] },
    concepts: focus.map((area) => ({
      id: area.id,
      title: area.title,
      objective: area.objective,
      prerequisites: [],
      difficulty: { entry: [1, 100], transfer: [1, 100] },
      misconceptions: [],
      indicators: [{ id: `understands-${area.id}`.slice(0, 64), description: area.objective }],
      mastery: {
        minimumSuccessfulProbes: area.questions.length,
        successRatingAtLeast: 3,
        requiredIndicators: [`understands-${area.id}`.slice(0, 64)],
        minimumDistinctQuestionKinds: 1,
        recentWeakAnswersAllowed: 0,
      },
    })),
    questions: focus.flatMap((area) => area.questions.map((question, index) => ({
      id: `${area.id}-q${index + 1}`.slice(0, 64),
      concept: area.id,
      indicator: `understands-${area.id}`.slice(0, 64),
      kind: question.kind,
      difficulty: [1, 100] as [number, number],
      requires: [],
      text: question.text,
    }))),
    scaffolds: [],
    progression: {
      entryConcepts: [focus[0]?.id ?? ""],
      preferredOrder: focus.map(({ id }) => id),
      onRating: {
        "1-2": "remediate",
        "3": "clarify-same-indicator",
        "4": "probe-missing-indicator",
        "5": "evaluate-mastery-and-advance",
      },
    },
  };
  return validateCurriculumTopic(topic);
}
 
function toSafeId(value: string): string {
  const parts: string[] = [];
  let current = "";
  for (const character of value.toLowerCase()) {
    if ((character >= "a" && character <= "z") || (character >= "0" && character <= "9") || character === "-") {
      current += character;
    } else Eif (current !== "") {
      parts.push(current);
      current = "";
    }
  }
  Eif (current !== "") parts.push(current);
  const cleaned = parts.join("-").replaceAll("--", "-");
  return (cleaned === "" ? "example" : cleaned).slice(0, 64 - INLINE_FOCUS_TOPIC_PREFIX.length);
}