@project-sunbird/collection-editor-react 1.0.11 → 1.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,28 +1,17 @@
1
1
  import { INode } from '../types/editor';
2
- export interface HierarchyLeafLike {
3
- objectType?: string;
4
- mimeType?: string;
5
- children?: HierarchyLeafLike[];
6
- }
2
+ export declare const EVALUATION_COURSE_CATEGORY = "Evaluation Course";
7
3
  /**
8
- * True iff the course has at least one leaf and every leaf is a QuML
9
- * QuestionSet — legacy ECML assessment resources
10
- * (application/vnd.ekstep.ecml-archive) do NOT qualify even though they're
11
- * historically labelled "assessment" content. An empty course (no leaves at
12
- * all) is not an assessment course.
13
- */
14
- export declare function isAssessmentCourse(course: HierarchyLeafLike): boolean;
15
- export interface AssessmentCourseInfo {
16
- qualifies: boolean;
17
- /** The course's full metadata from its own hierarchy read (children stripped).
18
- * Needed because composite-search results only carry the LP framework's
19
- * skill field — a course tagged under a different framework (e.g. USF)
20
- * would otherwise land in the tree without its framework or skill tags. */
21
- meta: Record<string, unknown>;
22
- }
23
- export declare function getAssessmentCourseInfo(courseId: string): Promise<AssessmentCourseInfo>;
24
- export declare function checkAssessmentCourse(courseId: string): Promise<boolean>;
25
- export declare function clearAssessmentCourseCache(): void;
4
+ * A course qualifies for the Prior/Outcome Assessment slots and a Level
5
+ * Exam only by being explicitly authored under the Evaluation Course
6
+ * primaryCategory (whose own profile restricts its content to Question
7
+ * Sets/Course Assessment — see evaluationCourseProfile) — never by
8
+ * inspecting its content shape. This is a synchronous, no-network check:
9
+ * every course node (from a search result or a loaded hierarchy) already
10
+ * carries its own primaryCategory.
11
+ */
12
+ export declare function isEvaluationCourse(course: {
13
+ primaryCategory?: string;
14
+ } | undefined): boolean;
26
15
  /**
27
16
  * Repairs a freshly-loaded LP tree (useEditorInit, after readHierarchy).
28
17
  * The hierarchy read undoes two editor-local invariants:
@@ -31,13 +20,19 @@ export declare function clearAssessmentCourseCache(): void;
31
20
  * leaves, so re-flatten them;
32
21
  * - the isAssessmentCourse flag is local-only (stripped from saves), so
33
22
  * without it no Level is recognized as a pre/post slot and "Skills
34
- * covered"/slot rules read the path as having no assessments. Recompute
35
- * it from the course's expanded subtree when the read included one, else
36
- * (single-course first/last Levels only) via a checkAssessmentCourse read.
23
+ * covered"/slot rules read the path as having no assessments. Recomputed
24
+ * from the course's OWN primaryCategory — always present on the loaded
25
+ * node, no network read needed.
37
26
  */
38
- export declare function normalizeLearningPathTree(root: INode): Promise<INode>;
27
+ export declare function normalizeLearningPathTree(root: INode): INode;
39
28
  export type LevelRole = 'pre' | 'post' | 'levelAssessment' | 'content';
40
29
  export declare function isAssessmentLevel(level: INode | undefined): boolean;
30
+ /** The at-most-one Level Exam course linked directly to a content Level, if
31
+ * any — canAddCourseToLevel already caps this at one. Unlike
32
+ * isAssessmentLevel, doesn't require it to be the Level's ONLY child (a
33
+ * Level Exam course sits alongside however many regular courses that
34
+ * Level also has). */
35
+ export declare function getLevelExamCourse(level: INode | undefined): INode | undefined;
41
36
  /**
42
37
  * getLevelRole(levelIndex, levelCount, hasAssessmentCourse) → role.
43
38
  * Level[0] wrapping an assessment course is Prior/diagnostic ("pre");
@@ -65,6 +60,20 @@ export declare function isAssessmentSlotFilled(levels: INode[], slot: 'pre' | 'p
65
60
  * For callers (outside this module) that hold the level object rather
66
61
  * than its index. */
67
62
  export declare function isPrePostSlot(levels: INode[], level: INode | undefined): boolean;
63
+ /**
64
+ * True iff `levelNode` sits at index 0 or the last index among root's
65
+ * Levels AND currently has no courses at all — i.e. adding a single
66
+ * QuestionSet-only course here next would leave it indistinguishable from
67
+ * a genuine Prior/Outcome Assessment slot (isAssessmentLevel: exactly one
68
+ * course, and that one flagged isAssessmentCourse — there's no separate
69
+ * marker recording *why* a Level ended up in that shape). A Level anywhere
70
+ * else, or one that already has ≥1 course, never hits this ambiguity — a
71
+ * second course makes children.length !== 1, so isAssessmentLevel can't
72
+ * match regardless of shape (this is also why a Level Exam Course paired
73
+ * with other regular content in the same Level is unaffected: only a
74
+ * Level reduced to just the one QuestionSet-only course is at risk).
75
+ */
76
+ export declare function wouldBecomeAmbiguousSlot(rootLevels: INode[], levelNode: INode): boolean;
68
77
  /**
69
78
  * Which pre/post assessment slot (if any) is open to receive a newly-linked
70
79
  * assessment course, given root's current Level children. Pre is checked
@@ -80,6 +89,13 @@ export declare function resolveOpenAssessmentSlot(levels: INode[]): 'pre' | 'pos
80
89
  * content Levels between them. Simulates the same splice-based move
81
90
  * `reorderInParent` performs and checks the pinned Levels didn't shift,
82
91
  * rather than hand-deriving index-shift arithmetic.
92
+ *
93
+ * Also blocks moving a content Level (one that ISN'T already a pinned pre/
94
+ * post slot) into index 0 or the last index if it would land there already
95
+ * shaped like one (isAssessmentLevel — exactly one course, flagged) — e.g. a
96
+ * content Level whose only remaining course is its Level Exam course. Same
97
+ * ambiguity wouldBecomeAmbiguousSlot guards on add; without this, reordering
98
+ * could produce the identical shape without ever going through that check.
83
99
  */
84
100
  export declare function canReorderLevel(levels: INode[], fromIndex: number, toIndex: number): boolean;
85
101
  /**
@@ -106,15 +122,6 @@ export declare function canAddCourseToLevel(level: INode | undefined, incomingIs
106
122
  */
107
123
  export declare function getExplicitCurriculum(root: INode | undefined, treeCache: Record<string, Record<string, unknown>>): string | undefined;
108
124
  export declare function hasExplicitCurriculum(root: INode | undefined, treeCache: Record<string, Record<string, unknown>>): boolean;
109
- /**
110
- * "Skills covered" (root summary, Phase 4): the union of skills tagged
111
- * across the Prior Assessment, each Level's *selected* skills, and the
112
- * Outcome Assessment — never skills scraped from linked courses' content.
113
- * Both assessment Levels (their course's tags) and content Levels (their
114
- * own selection) read the SAME resolved skillCategoryCode metadata field —
115
- * never the reserved Sunbird `competencies` field, whose platform schema
116
- * expects competency-ontology objects, not plain framework-term strings.
117
- */
118
125
  /**
119
126
  * "Path shape" (root summary, Phase 4): level count excludes the pre/post
120
127
  * assessment slots (design: "Neither counts as a level"); course count
@@ -124,6 +131,15 @@ export declare function computePathShape(root: INode | undefined): {
124
131
  levelCount: number;
125
132
  courseCount: number;
126
133
  };
134
+ /**
135
+ * "Skills covered" (root summary, Phase 4): the union of skills tagged
136
+ * across the Prior Assessment, each Level's *selected* skills, and the
137
+ * Outcome Assessment — never skills scraped from linked courses' content.
138
+ * Both assessment Levels (their course's tags) and content Levels (their
139
+ * own selection) read the SAME resolved skillCategoryCode metadata field —
140
+ * never the reserved Sunbird `competencies` field, whose platform schema
141
+ * expects competency-ontology objects, not plain framework-term strings.
142
+ */
127
143
  export declare function computeSkillsCovered(root: INode | undefined, skillCategoryCode: string | undefined): string[];
128
144
  /**
129
145
  * The skills-covered union that should be persisted onto the LP root's OWN
@@ -177,10 +193,12 @@ export declare const REQUIRES_PRIOR_POLICIES: Set<string>;
177
193
  */
178
194
  export declare function validateLearningPathStructure(root: INode | undefined, skillCategoryCode: string | undefined, skillScope: string[], treeCache?: Record<string, Record<string, unknown>>): LpValidationIssue[];
179
195
  /**
180
- * Re-verifies the pre/post assessment courses are STILL question-set-only at
181
- * publish time (Phase 1 item 6 / Phase 5: "the course may have changed since
182
- * it was linked"). Separate from validateLearningPathStructure because it
183
- * requires a network read; callers should run it alongside the sync checks,
184
- * not instead of them.
196
+ * Re-verifies the pre/post assessment courses are STILL categorized
197
+ * Evaluation Course at publish time — an author could re-tag a linked
198
+ * course's category after it was added to the path. A lightweight
199
+ * collection read (not a hierarchy walk — qualification is a single
200
+ * metadata field now) suffices. Separate from validateLearningPathStructure
201
+ * because it requires a network read; callers should run it alongside the
202
+ * sync checks, not instead of them.
185
203
  */
186
204
  export declare function revalidateAssessmentSlots(root: INode | undefined): Promise<LpValidationIssue[]>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@project-sunbird/collection-editor-react",
3
- "version": "1.0.11",
3
+ "version": "1.0.14",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",