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

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,17 +1,28 @@
1
1
  import { INode } from '../types/editor';
2
- export declare const EVALUATION_COURSE_CATEGORY = "Evaluation Course";
2
+ export interface HierarchyLeafLike {
3
+ objectType?: string;
4
+ mimeType?: string;
5
+ children?: HierarchyLeafLike[];
6
+ }
3
7
  /**
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;
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;
15
26
  /**
16
27
  * Repairs a freshly-loaded LP tree (useEditorInit, after readHierarchy).
17
28
  * The hierarchy read undoes two editor-local invariants:
@@ -20,19 +31,13 @@ export declare function isEvaluationCourse(course: {
20
31
  * leaves, so re-flatten them;
21
32
  * - the isAssessmentCourse flag is local-only (stripped from saves), so
22
33
  * without it no Level is recognized as a pre/post slot and "Skills
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.
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.
26
37
  */
27
- export declare function normalizeLearningPathTree(root: INode): INode;
38
+ export declare function normalizeLearningPathTree(root: INode): Promise<INode>;
28
39
  export type LevelRole = 'pre' | 'post' | 'levelAssessment' | 'content';
29
40
  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;
36
41
  /**
37
42
  * getLevelRole(levelIndex, levelCount, hasAssessmentCourse) → role.
38
43
  * Level[0] wrapping an assessment course is Prior/diagnostic ("pre");
@@ -60,20 +65,6 @@ export declare function isAssessmentSlotFilled(levels: INode[], slot: 'pre' | 'p
60
65
  * For callers (outside this module) that hold the level object rather
61
66
  * than its index. */
62
67
  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;
77
68
  /**
78
69
  * Which pre/post assessment slot (if any) is open to receive a newly-linked
79
70
  * assessment course, given root's current Level children. Pre is checked
@@ -89,13 +80,6 @@ export declare function resolveOpenAssessmentSlot(levels: INode[]): 'pre' | 'pos
89
80
  * content Levels between them. Simulates the same splice-based move
90
81
  * `reorderInParent` performs and checks the pinned Levels didn't shift,
91
82
  * 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.
99
83
  */
100
84
  export declare function canReorderLevel(levels: INode[], fromIndex: number, toIndex: number): boolean;
101
85
  /**
@@ -122,15 +106,6 @@ export declare function canAddCourseToLevel(level: INode | undefined, incomingIs
122
106
  */
123
107
  export declare function getExplicitCurriculum(root: INode | undefined, treeCache: Record<string, Record<string, unknown>>): string | undefined;
124
108
  export declare function hasExplicitCurriculum(root: INode | undefined, treeCache: Record<string, Record<string, unknown>>): boolean;
125
- /**
126
- * "Path shape" (root summary, Phase 4): level count excludes the pre/post
127
- * assessment slots (design: "Neither counts as a level"); course count
128
- * includes every linked course, assessment courses included.
129
- */
130
- export declare function computePathShape(root: INode | undefined): {
131
- levelCount: number;
132
- courseCount: number;
133
- };
134
109
  /**
135
110
  * "Skills covered" (root summary, Phase 4): the union of skills tagged
136
111
  * across the Prior Assessment, each Level's *selected* skills, and the
@@ -140,6 +115,15 @@ export declare function computePathShape(root: INode | undefined): {
140
115
  * never the reserved Sunbird `competencies` field, whose platform schema
141
116
  * expects competency-ontology objects, not plain framework-term strings.
142
117
  */
118
+ /**
119
+ * "Path shape" (root summary, Phase 4): level count excludes the pre/post
120
+ * assessment slots (design: "Neither counts as a level"); course count
121
+ * includes every linked course, assessment courses included.
122
+ */
123
+ export declare function computePathShape(root: INode | undefined): {
124
+ levelCount: number;
125
+ courseCount: number;
126
+ };
143
127
  export declare function computeSkillsCovered(root: INode | undefined, skillCategoryCode: string | undefined): string[];
144
128
  /**
145
129
  * The skills-covered union that should be persisted onto the LP root's OWN
@@ -193,12 +177,10 @@ export declare const REQUIRES_PRIOR_POLICIES: Set<string>;
193
177
  */
194
178
  export declare function validateLearningPathStructure(root: INode | undefined, skillCategoryCode: string | undefined, skillScope: string[], treeCache?: Record<string, Record<string, unknown>>): LpValidationIssue[];
195
179
  /**
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.
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.
203
185
  */
204
186
  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.14",
3
+ "version": "1.0.15",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -1,9 +0,0 @@
1
- import { default as React } from 'react';
2
- import { INode } from '../../types/editor';
3
- interface LevelCourseItemProps {
4
- item: INode;
5
- onRemove: (id: string) => void;
6
- isEditable: boolean;
7
- }
8
- export declare const LevelCourseItem: React.FC<LevelCourseItemProps>;
9
- export {};
@@ -1,7 +0,0 @@
1
- import { default as React } from 'react';
2
- interface LevelExamItemProps {
3
- levelId: string | null | undefined;
4
- isEditable: boolean;
5
- }
6
- export declare const LevelExamItem: React.FC<LevelExamItemProps>;
7
- export {};
@@ -1,18 +0,0 @@
1
- import { INode } from '../types/editor';
2
- export interface UseLevelExamResult {
3
- course: INode | undefined;
4
- filled: boolean;
5
- courseName: string | undefined;
6
- isActiveTarget: boolean;
7
- armExam: () => void;
8
- deleteExam: () => void;
9
- }
10
- /**
11
- * Level Exam course for a single content Level — the optional, at-most-one
12
- * course (canAddCourseToLevel already caps it) whose leaves are purely a
13
- * question set, used to determine the learner's outcome for THIS Level
14
- * specifically (distinct from the Prior/Outcome Assessments, which gate the
15
- * whole path). Mirrors useAssessmentSlots.ts's shape, scoped to one Level
16
- * (by id) instead of root's pinned pre/post positions.
17
- */
18
- export declare function useLevelExam(levelId: string | null | undefined): UseLevelExamResult;