@programisto/edrm-storage 0.3.1 → 1.0.0
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/dist/modules/edrm-storage/examples/usage.example.js +2 -2
- package/dist/modules/edrm-storage/models/file.model.js +1 -0
- package/dist/modules/edrm-storage/services/edrm-storage.service.d.ts +0 -1
- package/dist/modules/edrm-storage/services/edrm-storage.service.js +3 -14
- package/package.json +5 -6
- package/dist/modules/edrm-exams/lib/openai/correctQuestion.txt +0 -9
- package/dist/modules/edrm-exams/lib/openai/createQuestion.txt +0 -6
- package/dist/modules/edrm-exams/lib/openai.d.ts +0 -37
- package/dist/modules/edrm-exams/lib/openai.js +0 -135
- package/dist/modules/edrm-exams/listeners/correct.listener.d.ts +0 -2
- package/dist/modules/edrm-exams/listeners/correct.listener.js +0 -167
- package/dist/modules/edrm-exams/models/candidate.model.d.ts +0 -21
- package/dist/modules/edrm-exams/models/candidate.model.js +0 -75
- package/dist/modules/edrm-exams/models/candidate.models.d.ts +0 -21
- package/dist/modules/edrm-exams/models/candidate.models.js +0 -75
- package/dist/modules/edrm-exams/models/company.model.d.ts +0 -8
- package/dist/modules/edrm-exams/models/company.model.js +0 -34
- package/dist/modules/edrm-exams/models/contact.model.d.ts +0 -14
- package/dist/modules/edrm-exams/models/contact.model.js +0 -60
- package/dist/modules/edrm-exams/models/test-category.models.d.ts +0 -7
- package/dist/modules/edrm-exams/models/test-category.models.js +0 -29
- package/dist/modules/edrm-exams/models/test-job.model.d.ts +0 -7
- package/dist/modules/edrm-exams/models/test-job.model.js +0 -29
- package/dist/modules/edrm-exams/models/test-question.model.d.ts +0 -25
- package/dist/modules/edrm-exams/models/test-question.model.js +0 -70
- package/dist/modules/edrm-exams/models/test-result.model.d.ts +0 -26
- package/dist/modules/edrm-exams/models/test-result.model.js +0 -70
- package/dist/modules/edrm-exams/models/test.model.d.ts +0 -47
- package/dist/modules/edrm-exams/models/test.model.js +0 -133
- package/dist/modules/edrm-exams/models/user.model.d.ts +0 -18
- package/dist/modules/edrm-exams/models/user.model.js +0 -73
- package/dist/modules/edrm-exams/routes/company.router.d.ts +0 -7
- package/dist/modules/edrm-exams/routes/company.router.js +0 -108
- package/dist/modules/edrm-exams/routes/exams-candidate.router.d.ts +0 -7
- package/dist/modules/edrm-exams/routes/exams-candidate.router.js +0 -448
- package/dist/modules/edrm-exams/routes/exams.router.d.ts +0 -8
- package/dist/modules/edrm-exams/routes/exams.router.js +0 -1343
- package/dist/modules/edrm-exams/routes/result.router.d.ts +0 -7
- package/dist/modules/edrm-exams/routes/result.router.js +0 -370
- package/dist/modules/edrm-exams/routes/user.router.d.ts +0 -7
- package/dist/modules/edrm-exams/routes/user.router.js +0 -96
- package/dist/modules/edrm-storage/integration/edrm-storage-integration.d.ts +0 -53
- package/dist/modules/edrm-storage/integration/edrm-storage-integration.js +0 -132
- package/dist/modules/edrm-storage/tests/edrm-storage.service.test.d.ts +0 -1
- package/dist/modules/edrm-storage/tests/edrm-storage.service.test.js +0 -143
- package/dist/modules/edrm-storage/tests/integration.test.d.ts +0 -1
- package/dist/modules/edrm-storage/tests/integration.test.js +0 -141
|
@@ -127,7 +127,7 @@ export const exampleRouterUsage = {
|
|
|
127
127
|
data: result
|
|
128
128
|
});
|
|
129
129
|
}
|
|
130
|
-
catch
|
|
130
|
+
catch {
|
|
131
131
|
res.status(500).json({
|
|
132
132
|
success: false,
|
|
133
133
|
message: 'Erreur lors de l\'initialisation de l\'upload'
|
|
@@ -146,7 +146,7 @@ export const exampleRouterUsage = {
|
|
|
146
146
|
data: result
|
|
147
147
|
});
|
|
148
148
|
}
|
|
149
|
-
catch
|
|
149
|
+
catch {
|
|
150
150
|
res.status(500).json({
|
|
151
151
|
success: false,
|
|
152
152
|
message: 'Erreur lors de la génération de l\'URL de téléchargement'
|
|
@@ -7,6 +7,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
8
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
9
|
};
|
|
10
|
+
/* eslint-disable @typescript-eslint/no-unused-vars, no-use-before-define */
|
|
10
11
|
import { EnduranceSchema, EnduranceModelType } from '@programisto/endurance-core';
|
|
11
12
|
// Enums pour les statuts de fichiers
|
|
12
13
|
export var FileStatus;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { enduranceEmitter, enduranceEventTypes } from '@programisto/endurance-core';
|
|
1
2
|
import FileModel, { FileStatus, FileProvider, FileType } from '../models/file.model.js';
|
|
2
3
|
import { S3StorageProvider } from '../providers/s3-storage.provider.js';
|
|
3
4
|
import crypto from 'crypto';
|
|
@@ -92,8 +93,7 @@ export class EdrmStorageService {
|
|
|
92
93
|
fileRecord.checksum = metadata.etag;
|
|
93
94
|
fileRecord.url = `https://${fileRecord.bucket}.s3.${process.env.AWS_REGION || 'us-east-1'}.amazonaws.com/${fileRecord.key}`;
|
|
94
95
|
await fileRecord.save();
|
|
95
|
-
|
|
96
|
-
this.emitFileStoredEvent(fileRecord);
|
|
96
|
+
enduranceEmitter.emit(enduranceEventTypes.FILE_STORED, fileRecord);
|
|
97
97
|
return fileRecord;
|
|
98
98
|
}
|
|
99
99
|
catch (error) {
|
|
@@ -135,6 +135,7 @@ export class EdrmStorageService {
|
|
|
135
135
|
// Marquer comme supprimé en base
|
|
136
136
|
fileRecord.status = FileStatus.DELETED;
|
|
137
137
|
await fileRecord.save();
|
|
138
|
+
enduranceEmitter.emit(enduranceEventTypes.FILE_DELETED, fileRecord);
|
|
138
139
|
}
|
|
139
140
|
catch (error) {
|
|
140
141
|
throw new Error(`Erreur lors de la suppression: ${error}`);
|
|
@@ -173,16 +174,4 @@ export class EdrmStorageService {
|
|
|
173
174
|
}
|
|
174
175
|
return fileRecord;
|
|
175
176
|
}
|
|
176
|
-
emitFileStoredEvent(fileRecord) {
|
|
177
|
-
// À implémenter selon votre système d'événements
|
|
178
|
-
// Exemple avec un système d'événements simple
|
|
179
|
-
console.log('FileStored event emitted:', {
|
|
180
|
-
fileId: fileRecord._id,
|
|
181
|
-
filename: fileRecord.filename,
|
|
182
|
-
size: fileRecord.size,
|
|
183
|
-
tenantId: fileRecord.tenantId,
|
|
184
|
-
entityName: fileRecord.entityName,
|
|
185
|
-
entityId: fileRecord.entityId
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
177
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@programisto/edrm-storage",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "1.0.0",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -10,13 +10,15 @@
|
|
|
10
10
|
"scripts": {
|
|
11
11
|
"start": "node ./dist/bin/www",
|
|
12
12
|
"dev": "tsc-watch --onSuccess \"node ./dist/bin/www\"",
|
|
13
|
-
"test": "
|
|
13
|
+
"test": "mocha",
|
|
14
14
|
"build": "tsc && npm run copy-files",
|
|
15
15
|
"copy-files": "copyfiles -u 1 'src/**/*.txt' dist",
|
|
16
16
|
"lint": "eslint \"**/*.{ts,tsx}\"",
|
|
17
17
|
"prepare": "husky install"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
+
"@aws-sdk/client-s3": "^3.879.0",
|
|
21
|
+
"@aws-sdk/s3-request-presigner": "^3.879.0",
|
|
20
22
|
"@programisto/endurance-core": "^1.1.6",
|
|
21
23
|
"aws-sdk": "^2.1692.0",
|
|
22
24
|
"debug": "^4.3.7",
|
|
@@ -43,7 +45,6 @@
|
|
|
43
45
|
"@semantic-release/github": "^11.0.3",
|
|
44
46
|
"@semantic-release/npm": "^12.0.1",
|
|
45
47
|
"@semantic-release/release-notes-generator": "^14.0.3",
|
|
46
|
-
"@types/jest": "^30.0.0",
|
|
47
48
|
"@types/node": "^22.16.5",
|
|
48
49
|
"@typescript-eslint/eslint-plugin": "^8.26.0",
|
|
49
50
|
"@typescript-eslint/parser": "^8.26.0",
|
|
@@ -55,12 +56,10 @@
|
|
|
55
56
|
"eslint-plugin-n": "^16.6.2",
|
|
56
57
|
"eslint-plugin-promise": "^6.6.0",
|
|
57
58
|
"husky": "^9.1.7",
|
|
58
|
-
"jest": "^30.1.2",
|
|
59
59
|
"mocha": "^11.0.1",
|
|
60
60
|
"nodemon": "^3.1.4",
|
|
61
61
|
"semantic-release": "^24.2.5",
|
|
62
62
|
"supertest": "^3.0.0",
|
|
63
|
-
"ts-jest": "^29.4.1",
|
|
64
63
|
"ts-node": "^10.9.2",
|
|
65
64
|
"typescript": "^5.8.3"
|
|
66
65
|
},
|
|
@@ -78,4 +77,4 @@
|
|
|
78
77
|
"dist",
|
|
79
78
|
"README.md"
|
|
80
79
|
]
|
|
81
|
-
}
|
|
80
|
+
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
Question : ${instruction}
|
|
2
|
-
Type de question : ${questionType}
|
|
3
|
-
- si MCQ les réponses possibles étaient : ${possibleResponses}
|
|
4
|
-
|
|
5
|
-
Réponse du candidat à corriger :
|
|
6
|
-
--------------------------------
|
|
7
|
-
${response}
|
|
8
|
-
--------------------------------
|
|
9
|
-
Score à donner : De 0 point (tout faux ou réponse vide) à ${maxScore} points (réponse correcte), avec un commentaire de correction
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
interface CreateQuestionParams {
|
|
2
|
-
job: string;
|
|
3
|
-
seniority: string;
|
|
4
|
-
questionType: string;
|
|
5
|
-
category: string;
|
|
6
|
-
expertiseLevel: string;
|
|
7
|
-
otherQuestions: string;
|
|
8
|
-
}
|
|
9
|
-
interface CorrectQuestionParams {
|
|
10
|
-
question: {
|
|
11
|
-
_id: string;
|
|
12
|
-
instruction: string;
|
|
13
|
-
maxScore: number;
|
|
14
|
-
possibleResponses: Array<{
|
|
15
|
-
possibleResponse: string;
|
|
16
|
-
valid: boolean;
|
|
17
|
-
}>;
|
|
18
|
-
questionType: string;
|
|
19
|
-
};
|
|
20
|
-
result: {
|
|
21
|
-
responses: Array<{
|
|
22
|
-
questionId: string;
|
|
23
|
-
response: string;
|
|
24
|
-
}>;
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
interface ContextBuilder {
|
|
28
|
-
createQuestion: (params: CreateQuestionParams) => Promise<Record<string, string>>;
|
|
29
|
-
correctQuestion: (params: CorrectQuestionParams) => Promise<{
|
|
30
|
-
instruction: string;
|
|
31
|
-
response: string;
|
|
32
|
-
maxScore: number;
|
|
33
|
-
}>;
|
|
34
|
-
}
|
|
35
|
-
export declare function generateLiveMessage(messageType: keyof ContextBuilder, params: CreateQuestionParams | CorrectQuestionParams, json?: boolean): Promise<string>;
|
|
36
|
-
export declare function generateLiveMessageAssistant(assistantId: string, messageType: keyof ContextBuilder, params: CreateQuestionParams | CorrectQuestionParams, json?: boolean): Promise<string>;
|
|
37
|
-
export {};
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import OpenAI from 'openai';
|
|
2
|
-
import { fileURLToPath } from 'url';
|
|
3
|
-
import fs from 'fs';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
-
const __dirname = path.dirname(__filename);
|
|
7
|
-
const openai = new OpenAI({
|
|
8
|
-
apiKey: process.env.OPENAI_API_KEY
|
|
9
|
-
});
|
|
10
|
-
const contextBuilder = {
|
|
11
|
-
async createQuestion({ job, seniority, questionType, category, expertiseLevel, otherQuestions }) {
|
|
12
|
-
const context = {
|
|
13
|
-
job,
|
|
14
|
-
seniority,
|
|
15
|
-
questionType,
|
|
16
|
-
category,
|
|
17
|
-
expertiseLevel,
|
|
18
|
-
otherQuestions
|
|
19
|
-
};
|
|
20
|
-
return context;
|
|
21
|
-
},
|
|
22
|
-
async correctQuestion({ question, result }) {
|
|
23
|
-
let response = '';
|
|
24
|
-
for (let i = 0; i < result.responses.length; i++) {
|
|
25
|
-
if (result.responses[i].questionId === question._id) {
|
|
26
|
-
response = result.responses[i].response;
|
|
27
|
-
break;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
const instruction = question.instruction;
|
|
31
|
-
const maxScore = question.maxScore;
|
|
32
|
-
const questionType = question.questionType;
|
|
33
|
-
const possibleResponses = question.possibleResponses.map((response, index) => `réponse ${index + 1} = "${response.possibleResponse}" (${response.valid ? 'correcte' : 'incorrecte'})`).join('\n');
|
|
34
|
-
const context = {
|
|
35
|
-
instruction,
|
|
36
|
-
response,
|
|
37
|
-
maxScore,
|
|
38
|
-
questionType,
|
|
39
|
-
possibleResponses
|
|
40
|
-
};
|
|
41
|
-
return context;
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
export async function generateLiveMessage(messageType, params, json) {
|
|
45
|
-
const MAX_RETRY = 2;
|
|
46
|
-
let retryCount = 0;
|
|
47
|
-
const context = await contextBuilder[messageType](params);
|
|
48
|
-
const text = fs.readFileSync(path.join(__dirname, 'openai', `${messageType}.txt`), 'utf8');
|
|
49
|
-
const message = text.replace(/\${(.*?)}/g, (_, v) => context[v]);
|
|
50
|
-
while (retryCount <= MAX_RETRY) {
|
|
51
|
-
try {
|
|
52
|
-
const openAIParams = {
|
|
53
|
-
model: 'gpt-4-1106-preview',
|
|
54
|
-
temperature: 0.7,
|
|
55
|
-
messages: [{ role: 'system', content: message }]
|
|
56
|
-
};
|
|
57
|
-
if (json) {
|
|
58
|
-
openAIParams.response_format = { type: 'json_object' };
|
|
59
|
-
}
|
|
60
|
-
const result = await openai.chat.completions.create(openAIParams);
|
|
61
|
-
const content = result.choices[0].message.content;
|
|
62
|
-
if (!content) {
|
|
63
|
-
throw new Error('No content in response');
|
|
64
|
-
}
|
|
65
|
-
return removeQuotes(content);
|
|
66
|
-
}
|
|
67
|
-
catch (error) {
|
|
68
|
-
retryCount++;
|
|
69
|
-
console.log(error);
|
|
70
|
-
if (retryCount > MAX_RETRY) {
|
|
71
|
-
return 'Brain freezed, I cannot generate a live message right now.';
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
return 'Brain freezed, I cannot generate a live message right now.';
|
|
76
|
-
}
|
|
77
|
-
export async function generateLiveMessageAssistant(assistantId, messageType, params, json) {
|
|
78
|
-
const MAX_RETRY = 2;
|
|
79
|
-
let retryCount = 0;
|
|
80
|
-
// Construire le contexte pour le message
|
|
81
|
-
const context = await contextBuilder[messageType](params);
|
|
82
|
-
const text = fs.readFileSync(path.join(__dirname, 'openai', `${messageType}.txt`), 'utf8');
|
|
83
|
-
const message = text.replace(/\${(.*?)}/g, (_, v) => context[v]);
|
|
84
|
-
while (retryCount <= MAX_RETRY) {
|
|
85
|
-
try {
|
|
86
|
-
// Créer un thread avec l'assistant
|
|
87
|
-
const thread = await openai.beta.threads.create();
|
|
88
|
-
// Ajouter le message avec le contexte
|
|
89
|
-
await openai.beta.threads.messages.create(thread.id, {
|
|
90
|
-
role: 'user',
|
|
91
|
-
content: message
|
|
92
|
-
});
|
|
93
|
-
// Exécuter l'assistant
|
|
94
|
-
const run = await openai.beta.threads.runs.create(thread.id, {
|
|
95
|
-
assistant_id: assistantId
|
|
96
|
-
});
|
|
97
|
-
// Attendre que l'exécution soit terminée
|
|
98
|
-
let runStatus = await openai.beta.threads.runs.retrieve(thread.id, run.id);
|
|
99
|
-
while (runStatus.status === 'in_progress' || runStatus.status === 'queued') {
|
|
100
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
101
|
-
runStatus = await openai.beta.threads.runs.retrieve(thread.id, run.id);
|
|
102
|
-
}
|
|
103
|
-
if (runStatus.status === 'failed') {
|
|
104
|
-
throw new Error('Assistant execution failed');
|
|
105
|
-
}
|
|
106
|
-
// Récupérer les messages de réponse
|
|
107
|
-
const messages = await openai.beta.threads.messages.list(thread.id);
|
|
108
|
-
const lastMessage = messages.data[0]; // Le premier message est le plus récent
|
|
109
|
-
if (!lastMessage || !lastMessage.content || lastMessage.content.length === 0) {
|
|
110
|
-
throw new Error('No content in response');
|
|
111
|
-
}
|
|
112
|
-
const content = lastMessage.content[0];
|
|
113
|
-
if (content.type === 'text') {
|
|
114
|
-
return removeQuotes(content.text.value);
|
|
115
|
-
}
|
|
116
|
-
else {
|
|
117
|
-
throw new Error('Unexpected content type');
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
catch (error) {
|
|
121
|
-
retryCount++;
|
|
122
|
-
console.log(error);
|
|
123
|
-
if (retryCount > MAX_RETRY) {
|
|
124
|
-
return 'Brain freezed, I cannot generate a live message right now.';
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
return 'Brain freezed, I cannot generate a live message right now.';
|
|
129
|
-
}
|
|
130
|
-
function removeQuotes(str) {
|
|
131
|
-
if (str.startsWith('"') && str.endsWith('"')) {
|
|
132
|
-
return str.substring(1, str.length - 1);
|
|
133
|
-
}
|
|
134
|
-
return str;
|
|
135
|
-
}
|
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
import { enduranceListener, enduranceEventTypes, enduranceEmitter } from '@programisto/endurance-core';
|
|
2
|
-
import TestQuestion from '../models/test-question.model.js';
|
|
3
|
-
import { generateLiveMessageAssistant } from '../lib/openai.js';
|
|
4
|
-
import TestResult, { TestState } from '../models/test-result.model.js';
|
|
5
|
-
import CandidateModel from '../models/candidate.model.js';
|
|
6
|
-
import ContactModel from '../models/contact.model.js';
|
|
7
|
-
import TestModel from '../models/test.model.js';
|
|
8
|
-
async function sendDiscordNotification(message) {
|
|
9
|
-
const discordWebhook = process.env.TEST_CORRECTION_DISCORD_WEBHOOKS;
|
|
10
|
-
if (discordWebhook) {
|
|
11
|
-
try {
|
|
12
|
-
await fetch(discordWebhook, {
|
|
13
|
-
method: 'POST',
|
|
14
|
-
headers: {
|
|
15
|
-
'Content-Type': 'application/json',
|
|
16
|
-
},
|
|
17
|
-
body: JSON.stringify({
|
|
18
|
-
content: message,
|
|
19
|
-
}),
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
catch (error) {
|
|
23
|
-
console.error('Error sending Discord notification:', error);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
async function correctTest(options) {
|
|
28
|
-
if (!options.testId)
|
|
29
|
-
throw new Error('TestId is required');
|
|
30
|
-
if (!options.responses)
|
|
31
|
-
throw new Error('Responses are required');
|
|
32
|
-
if (!options.state)
|
|
33
|
-
throw new Error('State is required');
|
|
34
|
-
try {
|
|
35
|
-
// Récupérer le résultat de test
|
|
36
|
-
const result = await TestResult.findById(options._id);
|
|
37
|
-
if (!result) {
|
|
38
|
-
throw new Error('Test result not found');
|
|
39
|
-
}
|
|
40
|
-
let finalscore = 0;
|
|
41
|
-
let maxScore = 0;
|
|
42
|
-
// Pour chaque réponse enregistrée en base, on cherche la correction correspondante
|
|
43
|
-
for (const dbResponse of result.responses) {
|
|
44
|
-
const correction = options.responses.find(r => r.questionId.toString() === dbResponse.questionId.toString());
|
|
45
|
-
if (!correction)
|
|
46
|
-
continue;
|
|
47
|
-
const question = await TestQuestion.findById(dbResponse.questionId);
|
|
48
|
-
if (!question) {
|
|
49
|
-
console.error('Question not found', { questionId: dbResponse.questionId });
|
|
50
|
-
continue;
|
|
51
|
-
}
|
|
52
|
-
maxScore += question.maxScore;
|
|
53
|
-
const scoreResponse = await generateLiveMessageAssistant(process.env.OPENAI_ASSISTANT_ID_CORRECT_QUESTION || '', 'correctQuestion', {
|
|
54
|
-
question: {
|
|
55
|
-
_id: question._id.toString(),
|
|
56
|
-
instruction: question.instruction,
|
|
57
|
-
possibleResponses: question.possibleResponses,
|
|
58
|
-
questionType: question.questionType,
|
|
59
|
-
maxScore: question.maxScore
|
|
60
|
-
},
|
|
61
|
-
result: {
|
|
62
|
-
responses: [{
|
|
63
|
-
questionId: dbResponse.questionId.toString(),
|
|
64
|
-
response: dbResponse.response
|
|
65
|
-
}]
|
|
66
|
-
}
|
|
67
|
-
}, true);
|
|
68
|
-
console.log('Correction result:', { scoreResponse });
|
|
69
|
-
const parsedResult = JSON.parse(scoreResponse);
|
|
70
|
-
// Valider le score retourné par l'IA
|
|
71
|
-
let validScore = 0;
|
|
72
|
-
if (parsedResult.score !== undefined && parsedResult.score !== null) {
|
|
73
|
-
const score = parseFloat(parsedResult.score.toString());
|
|
74
|
-
if (!isNaN(score) && isFinite(score) && score >= 0) {
|
|
75
|
-
validScore = score;
|
|
76
|
-
}
|
|
77
|
-
else {
|
|
78
|
-
console.warn('Invalid score returned by AI:', parsedResult.score);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
finalscore += validScore;
|
|
82
|
-
dbResponse.score = validScore;
|
|
83
|
-
dbResponse.comment = parsedResult.comment || '';
|
|
84
|
-
}
|
|
85
|
-
// S'assurer que finalscore est un nombre valide
|
|
86
|
-
if (isNaN(finalscore) || !isFinite(finalscore)) {
|
|
87
|
-
console.warn('Invalid finalscore calculated, setting to 0:', finalscore);
|
|
88
|
-
finalscore = 0;
|
|
89
|
-
}
|
|
90
|
-
// S'assurer que maxScore est un nombre valide
|
|
91
|
-
if (isNaN(maxScore) || !isFinite(maxScore)) {
|
|
92
|
-
console.warn('Invalid maxScore calculated, setting to 0:', maxScore);
|
|
93
|
-
maxScore = 0;
|
|
94
|
-
}
|
|
95
|
-
// Mettre à jour le score final et l'état
|
|
96
|
-
result.score = finalscore;
|
|
97
|
-
result.state = TestState.Finish;
|
|
98
|
-
// Forcer la sauvegarde des sous-documents responses
|
|
99
|
-
result.markModified('responses');
|
|
100
|
-
// Calculer le pourcentage de score en évitant la division par zéro
|
|
101
|
-
let scorePercentage = 0;
|
|
102
|
-
if (maxScore > 0) {
|
|
103
|
-
scorePercentage = Math.ceil((finalscore / maxScore) * 100);
|
|
104
|
-
}
|
|
105
|
-
else if (finalscore > 0) {
|
|
106
|
-
// Si maxScore est 0 mais qu'il y a un score, on met 100%
|
|
107
|
-
scorePercentage = 100;
|
|
108
|
-
}
|
|
109
|
-
// S'assurer que le score est un nombre valide
|
|
110
|
-
if (isNaN(scorePercentage) || !isFinite(scorePercentage)) {
|
|
111
|
-
scorePercentage = 0;
|
|
112
|
-
}
|
|
113
|
-
// Sauvegarder les modifications avec findByIdAndUpdate pour éviter les conflits de version
|
|
114
|
-
await TestResult.findByIdAndUpdate(result._id, {
|
|
115
|
-
$set: {
|
|
116
|
-
responses: result.responses,
|
|
117
|
-
score: finalscore,
|
|
118
|
-
state: result.state
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
|
-
const test = await TestModel.findById(result.testId);
|
|
122
|
-
const candidate = await CandidateModel.findById(result.candidateId);
|
|
123
|
-
if (candidate) {
|
|
124
|
-
const contact = await ContactModel.findById(candidate.contact);
|
|
125
|
-
if (contact) {
|
|
126
|
-
const testLink = (process.env.TEST_INVITATION_LINK || '') + contact.email;
|
|
127
|
-
enduranceEmitter.emit(enduranceEventTypes.SEND_EMAIL, {
|
|
128
|
-
template: 'test-result',
|
|
129
|
-
to: contact.email,
|
|
130
|
-
data: {
|
|
131
|
-
firstname: contact.firstname,
|
|
132
|
-
lastname: contact.lastname,
|
|
133
|
-
score: scorePercentage,
|
|
134
|
-
testName: test?.title || '',
|
|
135
|
-
testLink
|
|
136
|
-
}
|
|
137
|
-
});
|
|
138
|
-
// Envoyer une notification Discord
|
|
139
|
-
const discordMessage = `📊 **Nouveau résultat de test**\n` +
|
|
140
|
-
`**Candidat:** ${contact.firstname} ${contact.lastname}\n` +
|
|
141
|
-
`**Test:** ${test?.title || 'Test inconnu'}\n` +
|
|
142
|
-
`**Score:** ${scorePercentage}%\n` +
|
|
143
|
-
`**Score brut:** ${finalscore}/${maxScore}`;
|
|
144
|
-
await sendDiscordNotification(discordMessage);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
catch (err) {
|
|
149
|
-
if (err instanceof Error) {
|
|
150
|
-
console.error(`Error correcting test: ${err.message}`, { err });
|
|
151
|
-
}
|
|
152
|
-
else {
|
|
153
|
-
console.error('Unknown error occurred during test correction', { err });
|
|
154
|
-
}
|
|
155
|
-
throw err; // Propager l'erreur pour la gestion en amont
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
enduranceListener.createListener(enduranceEventTypes.CORRECT_TEST, (args) => {
|
|
159
|
-
if (typeof args === 'object' && args !== null) {
|
|
160
|
-
const options = args;
|
|
161
|
-
correctTest(options);
|
|
162
|
-
}
|
|
163
|
-
else {
|
|
164
|
-
console.error('Invalid data type received in correct listener', { args });
|
|
165
|
-
}
|
|
166
|
-
});
|
|
167
|
-
export default enduranceListener;
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { EnduranceSchema } from '@programisto/endurance-core';
|
|
2
|
-
import { Types } from 'mongoose';
|
|
3
|
-
export declare enum ExperienceLevel {
|
|
4
|
-
JUNIOR = "JUNIOR",
|
|
5
|
-
INTERMEDIATE = "INTERMEDIATE",
|
|
6
|
-
SENIOR = "SENIOR",
|
|
7
|
-
EXPERT = "EXPERT"
|
|
8
|
-
}
|
|
9
|
-
declare class Candidate extends EnduranceSchema {
|
|
10
|
-
contact: Types.ObjectId;
|
|
11
|
-
experienceLevel: string;
|
|
12
|
-
yearsOfExperience: number;
|
|
13
|
-
skills: string[];
|
|
14
|
-
magicLinkToken?: string;
|
|
15
|
-
magicLinkExpiresAt?: Date;
|
|
16
|
-
authToken?: string;
|
|
17
|
-
authTokenExpiresAt?: Date;
|
|
18
|
-
static getModel(): import("@typegoose/typegoose").ReturnModelType<typeof Candidate, import("@typegoose/typegoose/lib/types").BeAnObject>;
|
|
19
|
-
}
|
|
20
|
-
declare const CandidateModel: import("@typegoose/typegoose").ReturnModelType<typeof Candidate, import("@typegoose/typegoose/lib/types").BeAnObject>;
|
|
21
|
-
export default CandidateModel;
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
-
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
-
};
|
|
7
|
-
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
-
};
|
|
10
|
-
import { EnduranceSchema, EnduranceModelType } from '@programisto/endurance-core';
|
|
11
|
-
import { Types } from 'mongoose';
|
|
12
|
-
// Enum pour les niveaux d'expérience
|
|
13
|
-
/* eslint-disable no-unused-vars */
|
|
14
|
-
export var ExperienceLevel;
|
|
15
|
-
(function (ExperienceLevel) {
|
|
16
|
-
ExperienceLevel["JUNIOR"] = "JUNIOR";
|
|
17
|
-
ExperienceLevel["INTERMEDIATE"] = "INTERMEDIATE";
|
|
18
|
-
ExperienceLevel["SENIOR"] = "SENIOR";
|
|
19
|
-
ExperienceLevel["EXPERT"] = "EXPERT";
|
|
20
|
-
})(ExperienceLevel || (ExperienceLevel = {}));
|
|
21
|
-
/* eslint-enable no-unused-vars */
|
|
22
|
-
let Candidate = class Candidate extends EnduranceSchema {
|
|
23
|
-
contact;
|
|
24
|
-
experienceLevel;
|
|
25
|
-
yearsOfExperience;
|
|
26
|
-
skills;
|
|
27
|
-
magicLinkToken;
|
|
28
|
-
magicLinkExpiresAt;
|
|
29
|
-
authToken;
|
|
30
|
-
authTokenExpiresAt;
|
|
31
|
-
static getModel() {
|
|
32
|
-
return CandidateModel;
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
__decorate([
|
|
36
|
-
EnduranceModelType.prop({ required: true, ref: 'Contact' }),
|
|
37
|
-
__metadata("design:type", Types.ObjectId)
|
|
38
|
-
], Candidate.prototype, "contact", void 0);
|
|
39
|
-
__decorate([
|
|
40
|
-
EnduranceModelType.prop({ required: false, enum: ExperienceLevel, default: ExperienceLevel.JUNIOR }),
|
|
41
|
-
__metadata("design:type", String)
|
|
42
|
-
], Candidate.prototype, "experienceLevel", void 0);
|
|
43
|
-
__decorate([
|
|
44
|
-
EnduranceModelType.prop({ required: false, type: Number, default: 0 }),
|
|
45
|
-
__metadata("design:type", Number)
|
|
46
|
-
], Candidate.prototype, "yearsOfExperience", void 0);
|
|
47
|
-
__decorate([
|
|
48
|
-
EnduranceModelType.prop({ type: [String], required: true }),
|
|
49
|
-
__metadata("design:type", Array)
|
|
50
|
-
], Candidate.prototype, "skills", void 0);
|
|
51
|
-
__decorate([
|
|
52
|
-
EnduranceModelType.prop({ required: false, type: String }),
|
|
53
|
-
__metadata("design:type", String)
|
|
54
|
-
], Candidate.prototype, "magicLinkToken", void 0);
|
|
55
|
-
__decorate([
|
|
56
|
-
EnduranceModelType.prop({ required: false, type: Date }),
|
|
57
|
-
__metadata("design:type", Date)
|
|
58
|
-
], Candidate.prototype, "magicLinkExpiresAt", void 0);
|
|
59
|
-
__decorate([
|
|
60
|
-
EnduranceModelType.prop({ required: false, type: String }),
|
|
61
|
-
__metadata("design:type", String)
|
|
62
|
-
], Candidate.prototype, "authToken", void 0);
|
|
63
|
-
__decorate([
|
|
64
|
-
EnduranceModelType.prop({ required: false, type: Date }),
|
|
65
|
-
__metadata("design:type", Date)
|
|
66
|
-
], Candidate.prototype, "authTokenExpiresAt", void 0);
|
|
67
|
-
Candidate = __decorate([
|
|
68
|
-
EnduranceModelType.modelOptions({
|
|
69
|
-
options: {
|
|
70
|
-
allowMixed: EnduranceModelType.Severity.ALLOW
|
|
71
|
-
}
|
|
72
|
-
})
|
|
73
|
-
], Candidate);
|
|
74
|
-
const CandidateModel = EnduranceModelType.getModelForClass(Candidate);
|
|
75
|
-
export default CandidateModel;
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { EnduranceSchema } from '@programisto/endurance-core';
|
|
2
|
-
import { Types } from 'mongoose';
|
|
3
|
-
export declare enum ExperienceLevel {
|
|
4
|
-
JUNIOR = "JUNIOR",
|
|
5
|
-
INTERMEDIATE = "INTERMEDIATE",
|
|
6
|
-
SENIOR = "SENIOR",
|
|
7
|
-
EXPERT = "EXPERT"
|
|
8
|
-
}
|
|
9
|
-
declare class Candidate extends EnduranceSchema {
|
|
10
|
-
contact: Types.ObjectId;
|
|
11
|
-
experienceLevel: string;
|
|
12
|
-
yearsOfExperience: number;
|
|
13
|
-
skills: string[];
|
|
14
|
-
magicLinkToken?: string;
|
|
15
|
-
magicLinkExpiresAt?: Date;
|
|
16
|
-
authToken?: string;
|
|
17
|
-
authTokenExpiresAt?: Date;
|
|
18
|
-
static getModel(): import("@typegoose/typegoose").ReturnModelType<typeof Candidate, import("@typegoose/typegoose/lib/types").BeAnObject>;
|
|
19
|
-
}
|
|
20
|
-
declare const CandidateModel: import("@typegoose/typegoose").ReturnModelType<typeof Candidate, import("@typegoose/typegoose/lib/types").BeAnObject>;
|
|
21
|
-
export default CandidateModel;
|