Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
89.47% |
153 / 171 |
|
63.64% |
7 / 11 |
CRAP | |
0.00% |
0 / 1 |
ResultImporter | |
89.47% |
153 / 171 |
|
63.64% |
7 / 11 |
34.27 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
importByResultInput | |
100.00% |
26 / 26 |
|
100.00% |
1 / 1 |
2 | |||
updateTestVariables | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
2 | |||
updateItemResponseVariables | |
96.15% |
25 / 26 |
|
0.00% |
0 / 1 |
4 | |||
updateItemOutcomeVariables | |
100.00% |
29 / 29 |
|
100.00% |
1 / 1 |
3 | |||
getTestScoreVariables | |
73.08% |
19 / 26 |
|
0.00% |
0 / 1 |
7.96 | |||
getItemVariable | |
100.00% |
28 / 28 |
|
100.00% |
1 / 1 |
5 | |||
getVariable | |
66.67% |
6 / 9 |
|
0.00% |
0 / 1 |
5.93 | |||
createCallItemId | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getTestUri | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
getResultStorage | |
30.00% |
3 / 10 |
|
0.00% |
0 / 1 |
3.37 |
1 | <?php |
2 | |
3 | /** |
4 | * This program is free software; you can redistribute it and/or |
5 | * modify it under the terms of the GNU General Public License |
6 | * as published by the Free Software Foundation; under version 2 |
7 | * of the License (non-upgradable). |
8 | * |
9 | * This program is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | * GNU General Public License for more details. |
13 | * |
14 | * You should have received a copy of the GNU General Public License |
15 | * along with this program; if not, write to the Free Software |
16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
17 | * |
18 | * Copyright (c) 2023 (original work) Open Assessment Technologies SA. |
19 | */ |
20 | |
21 | declare(strict_types=1); |
22 | |
23 | namespace oat\taoResultServer\models\Import\Service; |
24 | |
25 | use common_exception_Error; |
26 | use core_kernel_persistence_Exception; |
27 | use oat\generis\model\data\Ontology; |
28 | use oat\taoDeliveryRdf\model\DeliveryAssemblyService; |
29 | use oat\taoOutcomeRds\model\AbstractRdsResultStorage; |
30 | use oat\taoResultServer\models\classes\ResultServerService; |
31 | use oat\taoResultServer\models\Import\Exception\ImportResultException; |
32 | use oat\taoResultServer\models\Import\Input\ImportResultInput; |
33 | use taoResultServer_models_classes_ResponseVariable; |
34 | use taoResultServer_models_classes_Variable; |
35 | use Throwable; |
36 | |
37 | class ResultImporter |
38 | { |
39 | private Ontology $ontology; |
40 | private ResultServerService $resultServerService; |
41 | |
42 | public function __construct(Ontology $ontology, ResultServerService $resultServerService) |
43 | { |
44 | $this->ontology = $ontology; |
45 | $this->resultServerService = $resultServerService; |
46 | } |
47 | |
48 | /** |
49 | * @param ImportResultInput $input |
50 | * @return void |
51 | * @throws core_kernel_persistence_Exception|Throwable|common_exception_Error|ImportResultException |
52 | */ |
53 | public function importByResultInput(ImportResultInput $input): void |
54 | { |
55 | $resultStorage = $this->getResultStorage(); |
56 | $deliveryExecutionUri = $input->getDeliveryExecutionId(); |
57 | $testUri = $this->getTestUri($resultStorage, $deliveryExecutionUri); |
58 | $testScoreVariables = $this->getTestScoreVariables($resultStorage, $deliveryExecutionUri); |
59 | |
60 | $resultStorage->getPersistence()->transactional( |
61 | function () use ($resultStorage, $input, $testUri, $deliveryExecutionUri, $testScoreVariables): void { |
62 | $this->updateItemResponseVariables($resultStorage, $input, $testUri); |
63 | |
64 | $totalScoreCalculatedByItemOutcomes = $this->updateItemOutcomeVariables( |
65 | $resultStorage, |
66 | $input, |
67 | $testUri, |
68 | $testScoreVariables['scoreTotal'] ?? 0 |
69 | ); |
70 | |
71 | if (null === $testScoreVariables) { |
72 | return; |
73 | } |
74 | |
75 | $this->updateTestVariables( |
76 | $resultStorage, |
77 | $testScoreVariables['scoreTotalVariable'], |
78 | $testScoreVariables['scoreTotalVariableId'], |
79 | $deliveryExecutionUri, |
80 | $testUri, |
81 | $totalScoreCalculatedByItemOutcomes, |
82 | $input->hasOutcomes() |
83 | ); |
84 | } |
85 | ); |
86 | } |
87 | |
88 | /** |
89 | * @throws ImportResultException |
90 | */ |
91 | private function updateTestVariables( |
92 | AbstractRdsResultStorage $resultStorage, |
93 | taoResultServer_models_classes_Variable $scoreTotalVariable, |
94 | int $scoreTotalVariableId, |
95 | string $deliveryExecutionUri, |
96 | string $testUri, |
97 | float $updatedScoreTotal, |
98 | bool $hasOutcomes |
99 | ): void { |
100 | if ($hasOutcomes) { |
101 | $scoreTotalVariable->setEpoch(microtime()); |
102 | } |
103 | $scoreTotalVariable->setValue($updatedScoreTotal); |
104 | |
105 | $resultStorage->replaceTestVariables( |
106 | $deliveryExecutionUri, |
107 | $testUri, |
108 | $deliveryExecutionUri, |
109 | [ |
110 | $scoreTotalVariableId => $scoreTotalVariable |
111 | ] |
112 | ); |
113 | } |
114 | |
115 | private function updateItemResponseVariables( |
116 | AbstractRdsResultStorage $resultStorage, |
117 | ImportResultInput $input, |
118 | string $testUri |
119 | ): void { |
120 | $deliveryExecutionUri = $input->getDeliveryExecutionId(); |
121 | |
122 | foreach ($input->getResponses() as $itemId => $responses) { |
123 | $callItemId = $this->createCallItemId($deliveryExecutionUri, $itemId); |
124 | $itemVariables = []; |
125 | |
126 | foreach ($responses as $responseId => $responseValue) { |
127 | if (!array_key_exists('correctResponse', $responseValue)) { |
128 | continue; |
129 | } |
130 | |
131 | $responseVariable = $this->getItemVariable( |
132 | $resultStorage, |
133 | $deliveryExecutionUri, |
134 | $itemId, |
135 | $callItemId, |
136 | $responseId |
137 | ); |
138 | |
139 | /** @var taoResultServer_models_classes_ResponseVariable $variable */ |
140 | $variable = $responseVariable['variable']; |
141 | $variableId = $responseVariable['variableId']; |
142 | $itemUri = $responseVariable['itemUri']; |
143 | |
144 | $variable->setCorrectResponse(boolval($responseValue['correctResponse'])); |
145 | |
146 | $itemVariables[$variableId] = $variable; |
147 | } |
148 | |
149 | $resultStorage->replaceItemVariables( |
150 | $deliveryExecutionUri, |
151 | $testUri, |
152 | $itemUri, |
153 | $callItemId, |
154 | $itemVariables |
155 | ); |
156 | } |
157 | } |
158 | |
159 | private function updateItemOutcomeVariables( |
160 | AbstractRdsResultStorage $resultStorage, |
161 | ImportResultInput $input, |
162 | string $testUri, |
163 | float $scoreTotal |
164 | ): float { |
165 | $deliveryExecutionUri = $input->getDeliveryExecutionId(); |
166 | |
167 | foreach ($input->getOutcomes() as $itemId => $outcomes) { |
168 | $itemUri = null; |
169 | $updateOutcomeVariables = []; |
170 | $callItemId = $this->createCallItemId($deliveryExecutionUri, $itemId); |
171 | |
172 | foreach ($outcomes as $outcomeId => $outcomeValue) { |
173 | $outcomeVariable = $this->getItemVariable( |
174 | $resultStorage, |
175 | $deliveryExecutionUri, |
176 | $itemId, |
177 | $callItemId, |
178 | $outcomeId |
179 | ); |
180 | |
181 | /** @var taoResultServer_models_classes_Variable $variable */ |
182 | $variable = $outcomeVariable['variable']; |
183 | $itemUri = $outcomeVariable['itemUri']; |
184 | $variableId = $outcomeVariable['variableId']; |
185 | |
186 | $scoreTotal -= (float)$variable->getValue(); |
187 | $scoreTotal += $outcomeValue; |
188 | |
189 | $variable->setValue($outcomeValue); |
190 | $variable->setExternallyGraded(true); |
191 | |
192 | $updateOutcomeVariables[$variableId] = $variable; |
193 | } |
194 | |
195 | $resultStorage->replaceItemVariables( |
196 | $deliveryExecutionUri, |
197 | $testUri, |
198 | $itemUri, |
199 | $callItemId, |
200 | $updateOutcomeVariables |
201 | ); |
202 | } |
203 | |
204 | return $scoreTotal; |
205 | } |
206 | |
207 | /** |
208 | * @throws ImportResultException |
209 | */ |
210 | private function getTestScoreVariables( |
211 | AbstractRdsResultStorage $resultStorage, |
212 | string $deliveryExecutionUri |
213 | ): ?array { |
214 | foreach ($resultStorage->getDeliveryVariables($deliveryExecutionUri) as $id => $outcomeVariable) { |
215 | $variable = $this->getVariable($outcomeVariable); |
216 | |
217 | if ($variable === null) { |
218 | continue; |
219 | } |
220 | |
221 | if ($variable->getIdentifier() === 'SCORE_TOTAL') { |
222 | $scoreTotalVariableId = $id; |
223 | $scoreTotalVariable = $variable; |
224 | $scoreTotal = (float)$variable->getValue(); |
225 | |
226 | continue; |
227 | } |
228 | |
229 | if ($variable->getIdentifier() === 'SCORE_TOTAL_MAX') { |
230 | $scoreTotalMax = (float)$variable->getValue(); |
231 | } |
232 | } |
233 | |
234 | if (!isset($scoreTotalVariable)) { |
235 | return null; |
236 | } |
237 | |
238 | if (!isset($scoreTotal, $scoreTotalVariableId, $scoreTotalMax)) { |
239 | throw new ImportResultException( |
240 | sprintf( |
241 | 'SCORE_TOTAL is null for delivery execution %s', |
242 | $deliveryExecutionUri |
243 | ) |
244 | ); |
245 | } |
246 | |
247 | return [ |
248 | 'scoreTotalVariableId' => $scoreTotalVariableId, |
249 | 'scoreTotalVariable' => $scoreTotalVariable, |
250 | 'scoreTotal' => $scoreTotal, |
251 | 'scoreTotalMax' => $scoreTotalMax, |
252 | ]; |
253 | } |
254 | |
255 | /** |
256 | * @throws ImportResultException |
257 | */ |
258 | private function getItemVariable( |
259 | AbstractRdsResultStorage $resultStorage, |
260 | string $deliveryExecutionUri, |
261 | string $itemId, |
262 | string $callItemId, |
263 | string $variableIdentifier |
264 | ): array { |
265 | $variableVersions = $resultStorage->getVariable($callItemId, $variableIdentifier); |
266 | $lastVariable = is_array($variableVersions) ? (array)end($variableVersions) : []; |
267 | |
268 | if (empty($variableVersions)) { |
269 | throw new ImportResultException( |
270 | sprintf( |
271 | 'Variable %s not found for item %s on delivery execution %s', |
272 | $variableIdentifier, |
273 | $itemId, |
274 | $deliveryExecutionUri |
275 | ) |
276 | ); |
277 | } |
278 | |
279 | /** @var taoResultServer_models_classes_Variable $variable */ |
280 | $variable = $lastVariable['variable'] ?? null; |
281 | |
282 | if (!$variable instanceof taoResultServer_models_classes_Variable) { |
283 | throw new ImportResultException( |
284 | sprintf( |
285 | 'Variable %s is typeof %s, expected instance of %s, for item %s and execution %s', |
286 | $variableIdentifier, |
287 | is_object($variable) ? get_class($variable) : gettype($variable), |
288 | taoResultServer_models_classes_Variable::class, |
289 | $itemId, |
290 | $deliveryExecutionUri |
291 | ) |
292 | ); |
293 | } |
294 | |
295 | return [ |
296 | 'itemUri' => $lastVariable['item'] ?? null, |
297 | 'variableId' => key($variableVersions), |
298 | 'variable' => $variable, |
299 | ]; |
300 | } |
301 | |
302 | /** |
303 | * @param array|mixed $outcomeVariable |
304 | */ |
305 | private function getVariable($outcomeVariable): ?taoResultServer_models_classes_Variable |
306 | { |
307 | if (!is_array($outcomeVariable)) { |
308 | return null; |
309 | } |
310 | |
311 | $variable = current($outcomeVariable); |
312 | |
313 | if (!is_object($variable) || !property_exists($variable, 'variable')) { |
314 | return null; |
315 | } |
316 | |
317 | /** @var taoResultServer_models_classes_Variable $variable */ |
318 | $variable = $variable->variable; |
319 | |
320 | if ($variable instanceof taoResultServer_models_classes_Variable) { |
321 | return $variable; |
322 | } |
323 | |
324 | return null; |
325 | } |
326 | |
327 | private function createCallItemId(string $deliveryExecutionUri, string $itemId): string |
328 | { |
329 | return sprintf('%s.%s.0', $deliveryExecutionUri, $itemId); |
330 | } |
331 | |
332 | /** |
333 | * @throws core_kernel_persistence_Exception |
334 | */ |
335 | private function getTestUri(AbstractRdsResultStorage $resultStorage, string $deliveryExecutionUri): string |
336 | { |
337 | $deliveryId = $resultStorage->getDelivery($deliveryExecutionUri); |
338 | |
339 | return (string)$this->ontology->getResource($deliveryId) |
340 | ->getOnePropertyValue($this->ontology->getProperty(DeliveryAssemblyService::PROPERTY_ORIGIN)); |
341 | } |
342 | |
343 | /** |
344 | * @throws ImportResultException |
345 | * @throws common_exception_Error |
346 | */ |
347 | private function getResultStorage(): AbstractRdsResultStorage |
348 | { |
349 | $resultStorage = $this->resultServerService->getResultStorage(); |
350 | |
351 | if ($resultStorage instanceof AbstractRdsResultStorage) { |
352 | return $resultStorage; |
353 | } |
354 | |
355 | throw new ImportResultException( |
356 | sprintf( |
357 | 'ResultStorage must be an instance of %s. Instance of %s provided', |
358 | AbstractRdsResultStorage::class, |
359 | get_class($resultStorage) |
360 | ) |
361 | ); |
362 | } |
363 | } |