Skip to content

Commit 54016c5

Browse files
committed
feat(api) add CappedTubeRequirement
1 parent da7e85e commit 54016c5

File tree

3 files changed

+319
-21
lines changed

3 files changed

+319
-21
lines changed

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

+45
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export const COMPARISONS = {
88
export const TYPES = {
99
COMPOSE: 'compose',
1010
SKILL_PROFILE: 'skillProfile',
11+
CAPPED_TUBES_PROFILE: 'cappedTubesProfile',
1112
OBJECT: {
1213
ORGANIZATION_LEARNER: 'organizationLearner',
1314
ORGANIZATION: 'organization',
@@ -167,6 +168,48 @@ export class SkillProfileRequirement extends BaseRequirement {
167168
}
168169
}
169170

171+
export class CappedTubesProfileRequirement extends BaseRequirement {
172+
#cappedTubes;
173+
#threshold;
174+
175+
constructor({ data }) {
176+
super({ requirement_type: TYPES.CAPPED_TUBES_PROFILE, comparison: null });
177+
this.#cappedTubes = data.cappedTubes;
178+
this.#threshold = data.threshold;
179+
}
180+
181+
/**
182+
* @returns {Object}
183+
*/
184+
get data() {
185+
return {
186+
cappedTubes: Object.freeze(this.#cappedTubes),
187+
threshold: this.#threshold,
188+
};
189+
}
190+
191+
/**
192+
* @param {Eligibility|Success} dataInput
193+
* @returns {Boolean}
194+
*/
195+
isFulfilled(dataInput) {
196+
const masteryPercentage = dataInput.getMasteryPercentageForCappedTubes(this.#cappedTubes);
197+
return masteryPercentage >= this.#threshold;
198+
}
199+
200+
/**
201+
* @returns {Object}
202+
*/
203+
toDTO() {
204+
const superDto = super.toDTO();
205+
delete superDto.comparison;
206+
return {
207+
...superDto,
208+
data: this.data,
209+
};
210+
}
211+
}
212+
170213
/**
171214
* @param {Object} params
172215
* @param {string} params.requirement_type
@@ -182,6 +225,8 @@ export function buildRequirement({ requirement_type, data, comparison }) {
182225
return new ObjectRequirement({ requirement_type, data, comparison });
183226
} else if (requirement_type === TYPES.SKILL_PROFILE) {
184227
return new SkillProfileRequirement({ data });
228+
} else if (requirement_type === TYPES.CAPPED_TUBES_PROFILE) {
229+
return new CappedTubesProfileRequirement({ data });
185230
}
186231
throw new Error(`Unknown requirement_type "${requirement_type}"`);
187232
}

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
}

0 commit comments

Comments
 (0)