@sunbird-cb/cbp-ai 0.1.119 → 0.1.120
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.
- package/esm2022/lib/components/gap-analysis-recommended-course/gap-analysis-recommended-course.component.mjs +56 -22
- package/esm2022/lib/components/generate-course-recommendation/generate-course-recommendation.component.mjs +43 -2
- package/esm2022/lib/components/role-mapping-generation/role-mapping-generation.component.mjs +41 -35
- package/fesm2022/sunbird-cb-cbp-ai.mjs +135 -54
- package/fesm2022/sunbird-cb-cbp-ai.mjs.map +1 -1
- package/lib/components/gap-analysis-recommended-course/gap-analysis-recommended-course.component.d.ts +2 -0
- package/lib/components/gap-analysis-recommended-course/gap-analysis-recommended-course.component.d.ts.map +1 -1
- package/lib/components/generate-course-recommendation/generate-course-recommendation.component.d.ts +1 -0
- package/lib/components/generate-course-recommendation/generate-course-recommendation.component.d.ts.map +1 -1
- package/lib/components/role-mapping-generation/role-mapping-generation.component.d.ts +1 -1
- package/lib/components/role-mapping-generation/role-mapping-generation.component.d.ts.map +1 -1
- package/package.json +1 -1
- package/sunbird-cb-cbp-ai-0.1.120.tgz +0 -0
- package/sunbird-cb-cbp-ai-0.1.119.tgz +0 -0
|
@@ -3822,6 +3822,16 @@ class GapAnalysisRecommendedCourseComponent {
|
|
|
3822
3822
|
'competencySubtheme'
|
|
3823
3823
|
]);
|
|
3824
3824
|
}
|
|
3825
|
+
getProficiencyLevel(competency) {
|
|
3826
|
+
return this.getValue(competency, [
|
|
3827
|
+
'proficiency_level',
|
|
3828
|
+
'proficiencyLevel',
|
|
3829
|
+
'requiredProficiency',
|
|
3830
|
+
'required_proficiency',
|
|
3831
|
+
'currentProficiency',
|
|
3832
|
+
'current_proficiency'
|
|
3833
|
+
]);
|
|
3834
|
+
}
|
|
3825
3835
|
/**
|
|
3826
3836
|
* IMPORTANT:
|
|
3827
3837
|
* course_level is the authoritative STO level of a course.
|
|
@@ -3915,53 +3925,76 @@ class GapAnalysisRecommendedCourseComponent {
|
|
|
3915
3925
|
const requiredArea = this.normalize(this.getCompetencyArea(requiredCompetency));
|
|
3916
3926
|
const requiredTheme = this.normalize(this.getCompetencyTheme(requiredCompetency));
|
|
3917
3927
|
const requiredSubtheme = this.normalize(this.getCompetencySubtheme(requiredCompetency));
|
|
3928
|
+
const requiredProficiencyLevel = this.normalize(this.getProficiencyLevel(requiredCompetency));
|
|
3918
3929
|
if (!requiredTheme || !requiredSubtheme) {
|
|
3919
3930
|
return;
|
|
3920
3931
|
}
|
|
3932
|
+
const courseProficiencyLevel = this.normalize(this.getProficiencyLevel(courseCompetency));
|
|
3921
3933
|
/*
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
* the matching keys.
|
|
3926
|
-
*/
|
|
3934
|
+
* Match:
|
|
3935
|
+
* Area + Theme + Subtheme + Proficiency Level
|
|
3936
|
+
*/
|
|
3927
3937
|
const areaMatches = !courseArea ||
|
|
3928
3938
|
!requiredArea ||
|
|
3929
3939
|
courseArea === requiredArea;
|
|
3930
3940
|
const themeMatches = courseTheme === requiredTheme;
|
|
3931
3941
|
const subthemeMatches = courseSubtheme === requiredSubtheme;
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3942
|
+
const proficiencyLevelMatches = courseProficiencyLevel === requiredProficiencyLevel;
|
|
3943
|
+
/*
|
|
3944
|
+
* All 4 parameters must match.
|
|
3945
|
+
*
|
|
3946
|
+
* If all 4 match:
|
|
3947
|
+
* NOT part of gap
|
|
3948
|
+
*
|
|
3949
|
+
* If any one does not match:
|
|
3950
|
+
* PART of gap
|
|
3951
|
+
*/
|
|
3952
|
+
const allFourMatch = areaMatches &&
|
|
3953
|
+
themeMatches &&
|
|
3954
|
+
subthemeMatches &&
|
|
3955
|
+
proficiencyLevelMatches;
|
|
3935
3956
|
const requiredLevel = this.getRequiredLevel(requiredCompetency);
|
|
3936
3957
|
const currentLevel = this.getCurrentLevel(requiredCompetency);
|
|
3937
3958
|
const requiredRank = this.getLevelRank(requiredLevel);
|
|
3938
3959
|
const courseRank = this.getLevelRank(courseLevel);
|
|
3939
3960
|
let status;
|
|
3940
3961
|
let reason;
|
|
3941
|
-
if (
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
else if (courseRank >= requiredRank) {
|
|
3962
|
+
if (allFourMatch) {
|
|
3963
|
+
/*
|
|
3964
|
+
* All four competency attributes match.
|
|
3965
|
+
* Therefore this competency is NOT part of the gap.
|
|
3966
|
+
*/
|
|
3947
3967
|
status = 'FULL';
|
|
3948
3968
|
reason =
|
|
3949
|
-
|
|
3950
|
-
}
|
|
3951
|
-
else if (courseRank === requiredRank - 1) {
|
|
3952
|
-
status = 'PARTIAL';
|
|
3953
|
-
reason =
|
|
3954
|
-
`Course covers the ${courseLevel} level, which is one level below the required ${requiredLevel} level.`;
|
|
3969
|
+
'Competency Area, Theme, Subtheme and Proficiency Level all match. This competency is not part of the gap.';
|
|
3955
3970
|
}
|
|
3956
3971
|
else {
|
|
3972
|
+
/*
|
|
3973
|
+
* At least one of the four attributes does not match.
|
|
3974
|
+
* Therefore this competency is part of the gap.
|
|
3975
|
+
*/
|
|
3976
|
+
const mismatches = [];
|
|
3977
|
+
if (!areaMatches) {
|
|
3978
|
+
mismatches.push('Competency Area');
|
|
3979
|
+
}
|
|
3980
|
+
if (!themeMatches) {
|
|
3981
|
+
mismatches.push('Theme');
|
|
3982
|
+
}
|
|
3983
|
+
if (!subthemeMatches) {
|
|
3984
|
+
mismatches.push('Subtheme');
|
|
3985
|
+
}
|
|
3986
|
+
if (!proficiencyLevelMatches) {
|
|
3987
|
+
mismatches.push('Proficiency Level');
|
|
3988
|
+
}
|
|
3957
3989
|
status = 'NO_MATCH';
|
|
3958
3990
|
reason =
|
|
3959
|
-
|
|
3991
|
+
`${mismatches.join(', ')} ${mismatches.length === 1 ? 'does' : 'do'} not match. This competency is part of the gap.`;
|
|
3960
3992
|
}
|
|
3961
3993
|
results.push({
|
|
3962
3994
|
area: this.getCompetencyArea(requiredCompetency),
|
|
3963
3995
|
theme: this.getCompetencyTheme(requiredCompetency),
|
|
3964
3996
|
subtheme: this.getCompetencySubtheme(requiredCompetency),
|
|
3997
|
+
proficiencyLevel: this.getProficiencyLevel(requiredCompetency),
|
|
3965
3998
|
currentLevel,
|
|
3966
3999
|
requiredLevel,
|
|
3967
4000
|
courseLevel,
|
|
@@ -3979,6 +4012,7 @@ class GapAnalysisRecommendedCourseComponent {
|
|
|
3979
4012
|
this.normalize(result.area),
|
|
3980
4013
|
this.normalize(result.theme),
|
|
3981
4014
|
this.normalize(result.subtheme),
|
|
4015
|
+
this.normalize(result.proficiencyLevel),
|
|
3982
4016
|
this.normalize(result.requiredLevel),
|
|
3983
4017
|
this.normalize(result.courseLevel)
|
|
3984
4018
|
].join('|');
|
|
@@ -4123,6 +4157,7 @@ class GenerateCourseRecommendationComponent {
|
|
|
4123
4157
|
// "recommendations already exist" pause, which previously caused the progress
|
|
4124
4158
|
// display to jump to 100% and then reset back to 0%).
|
|
4125
4159
|
this.progressTimerId = null;
|
|
4160
|
+
this.immediateFailureHandled = false;
|
|
4126
4161
|
this.searchText = '';
|
|
4127
4162
|
this.filterdCourses = [];
|
|
4128
4163
|
this.originalData = [];
|
|
@@ -5877,6 +5912,7 @@ class GenerateCourseRecommendationComponent {
|
|
|
5877
5912
|
this.progressPercentage = 30;
|
|
5878
5913
|
this.startProgressiveLoading();
|
|
5879
5914
|
this.firstApiResponse = null;
|
|
5915
|
+
this.immediateFailureHandled = false;
|
|
5880
5916
|
concat(of(null), // immediate call
|
|
5881
5917
|
interval(5000) // poll every 5 sec
|
|
5882
5918
|
)
|
|
@@ -5887,18 +5923,42 @@ class GenerateCourseRecommendationComponent {
|
|
|
5887
5923
|
this.existingRecommendationFound = true;
|
|
5888
5924
|
}
|
|
5889
5925
|
console.log('First recommendation response:', this.firstApiResponse);
|
|
5926
|
+
if (response?.status === 'FAILED') {
|
|
5927
|
+
// takeWhile still forwards this same value to the subscriber below; tell it to skip
|
|
5928
|
+
this.immediateFailureHandled = true;
|
|
5929
|
+
this.stopProgressiveLoading();
|
|
5930
|
+
this.sharedService.deleteCourseRecommendations(role_mapping_id).pipe(catchError((error) => {
|
|
5931
|
+
// A 404 just means there were no course recommendations to delete yet;
|
|
5932
|
+
// either way, don't let this block the automatic retry below.
|
|
5933
|
+
if (error?.status !== 404) {
|
|
5934
|
+
console.error(`Failed to delete course recommendations for role mapping ${role_mapping_id}`, error);
|
|
5935
|
+
}
|
|
5936
|
+
return of(null);
|
|
5937
|
+
})).subscribe(() => {
|
|
5938
|
+
this.loading = false;
|
|
5939
|
+
this.firstApiResponse = null;
|
|
5940
|
+
// regenerate
|
|
5941
|
+
this.getRecommendedCourseWithProgress(role_mapping_id)
|
|
5942
|
+
.then(resolve)
|
|
5943
|
+
.catch(reject);
|
|
5944
|
+
});
|
|
5945
|
+
}
|
|
5890
5946
|
}
|
|
5891
5947
|
}), takeWhile((response) => {
|
|
5892
5948
|
// stop polling if existing recommendations found
|
|
5893
5949
|
if (this.firstApiResponse?.is_existing) {
|
|
5894
5950
|
return false;
|
|
5895
5951
|
}
|
|
5896
|
-
return response?.status !== 'COMPLETED';
|
|
5952
|
+
return response?.status !== 'COMPLETED' && response?.status !== 'FAILED';
|
|
5897
5953
|
}, true), finalize(() => {
|
|
5898
5954
|
this.loading = false;
|
|
5899
5955
|
}))
|
|
5900
5956
|
.subscribe({
|
|
5901
5957
|
next: (response) => {
|
|
5958
|
+
if (this.immediateFailureHandled) {
|
|
5959
|
+
this.immediateFailureHandled = false; // already handled in tap() above; avoid double-handling this same emission
|
|
5960
|
+
return;
|
|
5961
|
+
}
|
|
5902
5962
|
// ===========================
|
|
5903
5963
|
// EXISTING RECOMMENDATIONS
|
|
5904
5964
|
// ===========================
|
|
@@ -5966,6 +6026,21 @@ class GenerateCourseRecommendationComponent {
|
|
|
5966
6026
|
});
|
|
5967
6027
|
resolve(response);
|
|
5968
6028
|
}
|
|
6029
|
+
// ===========================
|
|
6030
|
+
// FAILED (after being in progress)
|
|
6031
|
+
// ===========================
|
|
6032
|
+
else if (response?.status === 'FAILED') {
|
|
6033
|
+
this.stopProgressiveLoading();
|
|
6034
|
+
this.loading = false;
|
|
6035
|
+
this.currentProcessingStage = '';
|
|
6036
|
+
this.progressPercentage = 0;
|
|
6037
|
+
this.firstApiResponse = null;
|
|
6038
|
+
this.snackBar.open('Generation failed please try again later', 'X', {
|
|
6039
|
+
duration: 3000,
|
|
6040
|
+
panelClass: ['snackbar-error']
|
|
6041
|
+
});
|
|
6042
|
+
reject(response);
|
|
6043
|
+
}
|
|
5969
6044
|
},
|
|
5970
6045
|
error: (error) => {
|
|
5971
6046
|
this.stopProgressiveLoading();
|
|
@@ -11680,6 +11755,7 @@ class RoleMappingGenerationComponent {
|
|
|
11680
11755
|
this.workAllocationOrderDocumentMissing = false;
|
|
11681
11756
|
this.showWorkAllocationOrderDocumentMissing = false;
|
|
11682
11757
|
this.destroy$ = new Subject();
|
|
11758
|
+
this.immediateFailureHandled = false;
|
|
11683
11759
|
this.onDepartmentScroll = (event) => {
|
|
11684
11760
|
const panel = event.target;
|
|
11685
11761
|
if (panel.scrollTop + panel.clientHeight >= panel.scrollHeight - 60) {
|
|
@@ -12206,6 +12282,7 @@ class RoleMappingGenerationComponent {
|
|
|
12206
12282
|
}
|
|
12207
12283
|
generateFinalRoleMapping() {
|
|
12208
12284
|
this.apiLoading = true;
|
|
12285
|
+
this.immediateFailureHandled = false;
|
|
12209
12286
|
if (!this.roleMappingForm.valid) {
|
|
12210
12287
|
this.roleMappingForm.markAllAsTouched();
|
|
12211
12288
|
return;
|
|
@@ -12270,6 +12347,7 @@ class RoleMappingGenerationComponent {
|
|
|
12270
12347
|
if (data && data?.status === 'FAILED') {
|
|
12271
12348
|
this.apiLoading = false;
|
|
12272
12349
|
this.loading = false;
|
|
12350
|
+
this.immediateFailureHandled = true; // takeWhile still forwards this same value to subscribe(); tell it to skip
|
|
12273
12351
|
this.destroy$.next(); // 🛑 stop polling
|
|
12274
12352
|
this.regenerateRoleMappingAfterFailedAndDelete();
|
|
12275
12353
|
return;
|
|
@@ -12282,6 +12360,10 @@ class RoleMappingGenerationComponent {
|
|
|
12282
12360
|
data?.status !== 'FAILED', true // emit the final COMPLETED/FAILED response
|
|
12283
12361
|
))
|
|
12284
12362
|
.subscribe(data => {
|
|
12363
|
+
if (this.immediateFailureHandled) {
|
|
12364
|
+
this.immediateFailureHandled = false; // already handled in tap() above; avoid double-handling this same emission
|
|
12365
|
+
return;
|
|
12366
|
+
}
|
|
12285
12367
|
console.log('role mapping data--', data);
|
|
12286
12368
|
if (this.firstApiResponse?.is_existing) {
|
|
12287
12369
|
this.showWorkAllocationOrderDocumentMissing = false;
|
|
@@ -12369,7 +12451,10 @@ class RoleMappingGenerationComponent {
|
|
|
12369
12451
|
this.loading = false;
|
|
12370
12452
|
this.apiLoading = false;
|
|
12371
12453
|
this.destroy$.next(); // 🛑 stop polling
|
|
12372
|
-
this.
|
|
12454
|
+
this.snackBar.open('Generation failed please try again later', 'X', {
|
|
12455
|
+
duration: 3000,
|
|
12456
|
+
panelClass: ['snackbar-error']
|
|
12457
|
+
});
|
|
12373
12458
|
return;
|
|
12374
12459
|
}
|
|
12375
12460
|
});
|
|
@@ -12626,45 +12711,41 @@ class RoleMappingGenerationComponent {
|
|
|
12626
12711
|
goToUploadDocument() {
|
|
12627
12712
|
this.router.navigate(['/ai/upload-documents']);
|
|
12628
12713
|
}
|
|
12629
|
-
showRoleMappingFailedPopup() {
|
|
12630
|
-
console.log('role mapping failed, try to regenerate role mapping?');
|
|
12631
|
-
// alert('role mapping failed, try to regenerate role mapping?')
|
|
12632
|
-
const dialogRef = this.dialog.open(DeleteRoleMappingPopupComponent, {
|
|
12633
|
-
width: '400px',
|
|
12634
|
-
data: { documents: this.documents, from: 'roleMappingFailed', },
|
|
12635
|
-
panelClass: 'view-cbp-plan-popup',
|
|
12636
|
-
minHeight: '300px',
|
|
12637
|
-
maxHeight: '80vh',
|
|
12638
|
-
disableClose: true // Optional: prevent closing with outside click
|
|
12639
|
-
});
|
|
12640
|
-
dialogRef.afterClosed().subscribe(result => {
|
|
12641
|
-
if (result === 'saved') {
|
|
12642
|
-
console.log('Changes saved!');
|
|
12643
|
-
this.regenerateRoleMappingAfterFailedAndDelete();
|
|
12644
|
-
this.loading = true;
|
|
12645
|
-
}
|
|
12646
|
-
else {
|
|
12647
|
-
this.loading = false;
|
|
12648
|
-
}
|
|
12649
|
-
});
|
|
12650
|
-
}
|
|
12651
12714
|
regenerateRoleMappingAfterFailedAndDelete() {
|
|
12652
|
-
this.
|
|
12653
|
-
|
|
12654
|
-
|
|
12655
|
-
|
|
12656
|
-
|
|
12657
|
-
|
|
12658
|
-
|
|
12715
|
+
const stateCenterId = this.roleMappingForm.value.ministry;
|
|
12716
|
+
const departmentId = this.roleMappingForm.value.departments;
|
|
12717
|
+
this.sharedService.getRoleMappingByStateCenterAndDepartment(stateCenterId, departmentId).subscribe({
|
|
12718
|
+
next: (existingRoleMappings) => {
|
|
12719
|
+
const roleMappingIds = (Array.isArray(existingRoleMappings) ? existingRoleMappings : [])
|
|
12720
|
+
.map((roleMapping) => roleMapping?.id)
|
|
12721
|
+
.filter(Boolean);
|
|
12722
|
+
if (!roleMappingIds.length) {
|
|
12723
|
+
this.loading = false;
|
|
12724
|
+
this.firstApiResponse = null;
|
|
12725
|
+
this.generateFinalRoleMapping();
|
|
12726
|
+
return;
|
|
12727
|
+
}
|
|
12728
|
+
forkJoin(roleMappingIds.map((id) => this.sharedService.deleteCourseRecommendations(id).pipe(catchError((error) => {
|
|
12729
|
+
// A 404 just means this designation had no course recommendations yet.
|
|
12730
|
+
// Either way, one designation's delete failing shouldn't block the
|
|
12731
|
+
// regenerate retry for the rest.
|
|
12732
|
+
if (error?.status !== 404) {
|
|
12733
|
+
console.error(`Failed to delete course recommendations for role mapping ${id}`, error);
|
|
12734
|
+
}
|
|
12735
|
+
return of(null);
|
|
12736
|
+
})))).subscribe(() => {
|
|
12737
|
+
this.loading = false;
|
|
12738
|
+
this.firstApiResponse = null;
|
|
12739
|
+
this.generateFinalRoleMapping();
|
|
12740
|
+
});
|
|
12659
12741
|
},
|
|
12660
12742
|
error: (error) => {
|
|
12661
12743
|
this.snackBar.open(error?.error?.detail, 'X', {
|
|
12662
12744
|
duration: 3000,
|
|
12663
12745
|
panelClass: ['snackbar-error']
|
|
12664
12746
|
});
|
|
12665
|
-
this.firstApiResponse = [];
|
|
12666
12747
|
this.loading = false;
|
|
12667
|
-
|
|
12748
|
+
this.firstApiResponse = null;
|
|
12668
12749
|
}
|
|
12669
12750
|
});
|
|
12670
12751
|
}
|