Skip to content

Commit 7da4c0d

Browse files
committed
feat(api): make CappedTubeRequirement work
1 parent 5cbfaf6 commit 7da4c0d

File tree

2 files changed

+130
-33
lines changed

2 files changed

+130
-33
lines changed

api/src/quest/domain/models/Success.js

+24-20
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import uniqBy from 'lodash/uniqBy.js';
2+
13
import { KnowledgeElement } from '../../../shared/domain/models/index.js';
24

35
export class Success {
@@ -28,26 +30,28 @@ export class Success {
2830
* @param {Array<{tubeId: string, level: number}>} cappedTubes
2931
* @returns {number}
3032
*/
31-
// Pour cette implémentation, ne tient pas compte des versions d'acquis obtenus dans les campagnes
32-
// potentiellement effectuées dans le cadre de la quête, mais seulement du profil de l'utilisateur
33-
// pour que ça marche efficacement, ajouter une condition d'éligibilité qui impose d'être allé au bout
34-
// de la participation, on s'assure ainsi qu'il a bien un KE pour chaque acquis des campagnes et qu'il
35-
// n'obtienne pas l'attestation sans avoir effectivement participé
36-
37-
// L'autre solution serait de baser les cappedTubes sur l'ensemble des lots d'acquis des campagnes
38-
// concernées peut-être ?
39-
/*getMasteryPercentageForCappedTubes(cappedTubes) {
40-
if (!cappedTubes?.length) {
41-
return 0;
33+
getMasteryPercentageForCappedTubes(cappedTubes) {
34+
const uniqCampaignSkills = uniqBy(this.campaignSkills, 'id');
35+
const sortedKEByDateDesc = this.knowledgeElements.sort((keA, keB) => keB.createdAt - keA.createdAt);
36+
let total = 0;
37+
let validated = 0;
38+
for (const cappedTube of cappedTubes) {
39+
const skillsInTubeWithinMaxDifficulty = uniqCampaignSkills.filter(
40+
({ tubeId, difficulty }) => tubeId === cappedTube.tubeId && difficulty <= cappedTube.level,
41+
);
42+
const skillsByDifficulty = Object.groupBy(skillsInTubeWithinMaxDifficulty, ({ difficulty }) => difficulty);
43+
for (const skills of Object.values(skillsByDifficulty)) {
44+
++total;
45+
const skillIds = skills.map(({ id }) => id);
46+
const ke = sortedKEByDateDesc.find(({ skillId }) => skillIds.includes(skillId));
47+
if (ke?.status === KnowledgeElement.StatusType.VALIDATED) {
48+
++validated;
49+
}
50+
}
4251
}
4352

44-
let sumTotal = 0;
45-
let sumValidated = 0;
46-
for (const { tubeId, level } of cappedTubes) {
47-
sumTotal += level; // ceci est très cavalier, je présuppose que le référentiel n'a aucun trou \o/
48-
const dataForTubeId = this.#dataByTubeId[tubeId] ?? [];
49-
sumValidated += dataForTubeId.filter(({ isValidated, difficulty }) => isValidated && difficulty <= level).length;
50-
}
51-
return Math.round((sumValidated * 100) / sumTotal);
52-
}*/
53+
if (total === 0) return 0;
54+
55+
return (validated / total) * 100;
56+
}
5357
}

api/tests/quest/unit/domain/models/Requirement_test.js

+106-13
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { Success } from '../../../../../src/quest/domain/models/Success.js';
1313
import { KnowledgeElement } from '../../../../../src/shared/domain/models/index.js';
1414
import { expect } from '../../../../test-helper.js';
1515

16-
describe('Quest | Unit | Domain | Models | Requirement ', function () {
16+
describe.only('Quest | Unit | Domain | Models | Requirement ', function () {
1717
describe('Factory - buildRequirement', function () {
1818
context('when requirement_type is compose', function () {
1919
it('should build a ComposedRequirement', function () {
@@ -664,18 +664,46 @@ describe('Quest | Unit | Domain | Models | Requirement ', function () {
664664
beforeEach(function () {
665665
successWith60MasteryPercentage = new Success({
666666
knowledgeElements: [
667-
{ status: KnowledgeElement.StatusType.VALIDATED, skillId: 'skill1tubeA' },
668-
{ status: KnowledgeElement.StatusType.INVALIDATED, skillId: 'skill2tubeA' },
669-
{ status: KnowledgeElement.StatusType.VALIDATED, skillId: 'skill3tubeA' },
670-
{ status: KnowledgeElement.StatusType.VALIDATED, skillId: 'skill4tubeA' },
671-
{ status: KnowledgeElement.StatusType.VALIDATED, skillId: 'skill1tubeB' },
672-
{ status: KnowledgeElement.StatusType.VALIDATED, skillId: 'skill2tubeB' },
673-
{ status: KnowledgeElement.StatusType.INVALIDATED, skillId: 'skill3tubeB' },
674-
{ status: KnowledgeElement.StatusType.VALIDATED, skillId: 'skillTubeC' },
675-
{ status: KnowledgeElement.StatusType.VALIDATED, skillId: 'skillTubeD' },
667+
{
668+
status: KnowledgeElement.StatusType.VALIDATED,
669+
skillId: 'skill1tubeA_V1',
670+
createdAt: new Date('2025-03-17'),
671+
},
672+
{
673+
status: KnowledgeElement.StatusType.INVALIDATED,
674+
skillId: 'skill2tubeA',
675+
createdAt: new Date('2025-03-17'),
676+
},
677+
{
678+
status: KnowledgeElement.StatusType.VALIDATED,
679+
skillId: 'skill3tubeA',
680+
createdAt: new Date('2025-03-17'),
681+
},
682+
{
683+
status: KnowledgeElement.StatusType.VALIDATED,
684+
skillId: 'skill4tubeA',
685+
createdAt: new Date('2025-03-17'),
686+
},
687+
{
688+
status: KnowledgeElement.StatusType.VALIDATED,
689+
skillId: 'skill1tubeB',
690+
createdAt: new Date('2025-03-17'),
691+
},
692+
{
693+
status: KnowledgeElement.StatusType.VALIDATED,
694+
skillId: 'skill2tubeB',
695+
createdAt: new Date('2025-03-17'),
696+
},
697+
{
698+
status: KnowledgeElement.StatusType.INVALIDATED,
699+
skillId: 'skill3tubeB',
700+
createdAt: new Date('2025-03-17'),
701+
},
702+
{ status: KnowledgeElement.StatusType.VALIDATED, skillId: 'skillTubeC', createdAt: new Date('2025-03-17') },
703+
{ status: KnowledgeElement.StatusType.VALIDATED, skillId: 'skillTubeD', createdAt: new Date('2025-03-17') },
676704
],
677-
skills: [
678-
{ id: 'skill1tubeA', tubeId: 'tubeA', difficulty: 1 },
705+
campaignSkills: [
706+
{ id: 'skill1tubeA_V1', tubeId: 'tubeA', difficulty: 1 },
679707
{ id: 'skill2tubeA', tubeId: 'tubeA', difficulty: 2 },
680708
{ id: 'skill3tubeA', tubeId: 'tubeA', difficulty: 3 },
681709
{ id: 'skill4tubeA', tubeId: 'tubeA', difficulty: 4 },
@@ -687,6 +715,71 @@ describe('Quest | Unit | Domain | Models | Requirement ', function () {
687715
],
688716
});
689717
});
718+
context(
719+
'when two skills exist for a tube and a difficulty compute fulfillement taking in account only most recent knowledge element',
720+
function () {
721+
it('return true', function () {
722+
// given
723+
const success = new Success({
724+
knowledgeElements: [
725+
...successWith60MasteryPercentage.knowledgeElements,
726+
{
727+
status: KnowledgeElement.StatusType.INVALIDATED,
728+
skillId: 'skill1tubeA_V2',
729+
createdAt: new Date('2024-06-10'),
730+
},
731+
],
732+
campaignSkills: [
733+
...successWith60MasteryPercentage.campaignSkills,
734+
{ id: 'skill1tubeA_V2', tubeId: 'tubeA', difficulty: 1 },
735+
],
736+
});
737+
const requirement = new CappedTubesProfileRequirement({
738+
data: {
739+
cappedTubes,
740+
threshold: 60,
741+
},
742+
});
743+
744+
// when
745+
const isFulfilled = requirement.isFulfilled(success);
746+
747+
// then
748+
expect(isFulfilled).to.be.true;
749+
});
750+
751+
it('return false', function () {
752+
// given
753+
const success = new Success({
754+
knowledgeElements: [
755+
...successWith60MasteryPercentage.knowledgeElements,
756+
{
757+
status: KnowledgeElement.StatusType.INVALIDATED,
758+
skillId: 'skill1tubeA_V2',
759+
createdAt: new Date('2025-12-24'),
760+
},
761+
],
762+
campaignSkills: [
763+
...successWith60MasteryPercentage.campaignSkills,
764+
{ id: 'skill1tubeA_V2', tubeId: 'tubeA', difficulty: 1 },
765+
],
766+
});
767+
const requirement = new CappedTubesProfileRequirement({
768+
data: {
769+
cappedTubes,
770+
threshold: 60,
771+
},
772+
});
773+
774+
// when
775+
const isFulfilled = requirement.isFulfilled(success);
776+
777+
// then
778+
expect(isFulfilled).to.be.false;
779+
});
780+
},
781+
);
782+
690783
context("when dataInput's masteryPercentage is below threshold", function () {
691784
it('should return false', function () {
692785
// given
@@ -711,7 +804,7 @@ describe('Quest | Unit | Domain | Models | Requirement ', function () {
711804
const requirement = new CappedTubesProfileRequirement({
712805
data: {
713806
cappedTubes,
714-
threshold: 50,
807+
threshold: 60,
715808
},
716809
});
717810

0 commit comments

Comments
 (0)