/**
* This file is part of Sesatheque.
* Copyright 2014-2015, Association Sésamath
*
* Sesatheque is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License version 3
* as published by the Free Software Foundation.
*
* Sesatheque is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Sesatheque (LICENCE.txt).
* @see http://www.gnu.org/licenses/agpl.txt
*
*
* Ce fichier fait partie de lapplication Sésathèque, créée par lassociation Sésamath.
*
* Sésathèque est un logiciel libre ; vous pouvez le redistribuer ou le modifier suivant
* les termes de la GNU Affero General Public License version 3 telle que publiée par la
* Free Software Foundation.
* Sésathèque est distribué dans l'espoir qu'il sera utile, mais SANS AUCUNE GARANTIE
* sans même la garantie tacite de QUALITÉ MARCHANDE ou d'ADÉQUATION à UN BUT PARTICULIER.
* Consultez la GNU Affero General Public License pour plus de détails.
* Vous devez avoir reçu une copie de la GNU General Public License en même temps que Sésathèque
* (cf LICENCE.txt et http://vvlibri.org/fr/Analyse/gnu-affero-general-public-license-v3-analyse
* pour une explication en français)
*/
import { getBaseUrlFromResultat } from './helper'
let i = 0
function getScoresByNode (contenu) {
// Pour le score global, on veut un seul score par nœud (le meilleur si y'en a plusieurs)
const scoresByNode = {}
contenu.scores.forEach((score, index) => {
const node = contenu.noeuds[index]
if (!node) return console.warn('résultat incohérent avec un score sans nœud correspondant', contenu)
if (typeof score !== 'number') return
if (typeof scoresByNode[node] !== 'number' || scoresByNode[node] < score) scoresByNode[node] = score
})
return scoresByNode
}
/**
* Retourne le score d'un résultat (ce code devrait être dans j3p, mis là en attendant que les pbs de scores soient réglés dans j3p pour recalcul à chaque affichage)
* @param resultat
* @return {number|undefined}
*/
function getRealScore (resultat) {
// tant que c'est pas fini c'est undefined
if (!resultat.fin) return undefined
if (resultat.contenu && resultat.contenu.scores && resultat.contenu.scores.length) {
let total = 0
let nbNodes = 0
if (resultat.nextId) {
// on moyenne sur tous les nœuds avec scores
resultat.contenu.scores.forEach(score => {
if (typeof score === 'number' && score >= 0 && score <= 1) {
total += score
nbNodes++
}
})
} else {
// avant l'ajout de nextId dans le résultat, on avait tous les scores concaténés dans le même résultat, faut prendre le meilleur par node
const scoresByNode = getScoresByNode(resultat.contenu)
Object.entries(scoresByNode).forEach(([node, score]) => {
if (typeof score === 'number' && score >= 0 && score <= 1) {
nbNodes++
total += score
} else {
console.warn(`score ${score} du nœud ${node} incohérent dans ce résultat`, resultat)
}
})
}
if (nbNodes) return total / nbNodes
// sinon ça reste undefined
}
}
function getReponse (resultat, isTxt) {
const separator = isTxt ? '\n' : '<br />'
if (resultat && resultat.contenu && resultat.contenu.scores && resultat.contenu.scores.length) {
const nbScores = resultat.contenu.scores.length
// à une époque noeuds pouvait avoir un élément de plus
if (resultat.contenu.noeuds.length >= nbScores) {
const results = []
resultat.contenu.scores.forEach((score, index) => {
const noeud = resultat.contenu.noeuds[index]
if (noeud) {
if (typeof score === 'number' && score >= 0 && score <= 1) results.push(`Nœud ${noeud} : ${Math.round(100 * score)}%`)
} else {
console.error(`score d’index ${index} sans nœud correspondant dans ce résultat`, resultat)
}
})
if (results.length) return results.join(separator)
} else {
// bizarre on prend le meilleur score par nœud
console.warn('resultat bizarre, avec noeuds', resultat.contenu.noeuds, 'et scores', resultat.contenu.scores)
const scoresByNode = getScoresByNode(resultat.contenu)
return Object.entries(scoresByNode)
.map(([node, score]) => `Nœud ${node} : ${Math.round(100 * score)}%`)
.join(separator)
}
}
return 'Pas d’information enregistrée'
}
/**
* Exo j3p (graphe d'exercices en js), avec getHtmlFullReponse dispo
* @type TypeFormatters
* @memberOf resultatFormatters
*/
export const j3p = {
getScore: (resultat) => {
const score = getRealScore(resultat)
if (resultat.fin && typeof score !== 'number') {
console.error(`Pas de score sur un parcours terminé (1 imposé pour permettre de passer à la suite en cas de condition de réussite), résultat ${resultat.oid}`)
return 1
}
return score
},
/**
* Retourne le score à afficher sur la page html des bilans (pas forcément un nombre)
* @param {Resultat} resultat
* @return {string}
*/
getHtmlScore: (resultat) => {
// pour j3p on s'attend à avoir
if (!resultat.fin) return 'Parcours non terminé'
if (resultat.score === undefined) console.error('parcours terminé sans score', resultat)
// y'a eu un gros bug dans j3p, un résultat enregistré plusieurs fois avec le même oid, avec un tableau scores qui augmente mais un score du resultat jamais mis à jour
// ex {"noeuds":["1","1","1","1","1","1","1","1"],"pe":[0.75,1,1,1],"scores":[0.75,1,1,1],"ns":["fin","fin","fin","fin"],"boucle":[1,1,1,1],"graphe":[[],["1","squelettemtg32_Calc_Ecriture_Scientifique",[{"pe":">=0","nn":"fin","conclusion":"fin"},{"ex":"Lycee_Fraction_Reduction_1","limite":"","nbEssais":7,"nbchances":2,"nbrepetitions":4,"boitedialogue":"oui","indicationfaute":false,"simplifier":true,"a":"random","b":"random","c":"random","d":"random","e":"random","f":"random","g":"random","h":"random","j":"random","k":"random","l":"random","m":"random","n":"random","r":"random","p":"random","q":"random"}]]]}
// on recalcule score à chaque affichage
const oldScore = resultat.score
let score = getRealScore(resultat)
if (typeof score === 'number' && typeof oldScore === 'number' && Math.abs(oldScore - score) > 0.005) {
console.warn(`le score enregistré ${oldScore} ne correspond pas à celui calculé ${score}`, resultat)
if (score < oldScore && oldScore <= 1) score = oldScore // on baisse pas un score déjà attribué, même s'il est foireux…
}
if (typeof score === 'number') return Math.round(resultat.score * 100) + '%'
return 'pas de score pour ce parcours (mis à 100% pour permettre le passage au suivant en cas de minimum de réussite exigé)'
},
/**
* Retourne la réponse à insérer sur la page html des bilans
* @param {Resultat} resultat
* @param {FormatterOptions} [options] isTxt géré
* @return {string}
*/
getHtmlReponse: (resultat) => getReponse(resultat, false),
/**
* Retourne la réponse à insérer dans un csv
* @param {Resultat} resultat
* @return {string}
*/
getTxtReponse: (resultat) => getReponse(resultat, true),
/**
* Réponse en pleine page
* @param {Resultat} resultat
* @return {string}
*/
getHtmlFullReponse: function getHtmlFullReponse (resultat) {
if (resultat && resultat.contenu && resultat.contenu.noeuds && resultat.contenu.noeuds.length > 1) {
// on a un bug qq part qui donnait par ex du
// noeuds: ['1', '1'], 'pe': [1]
if (resultat.contenu.noeuds.every(node => node === '1')) {
console.warn('graphe à un seul nœud bizarrement multiple', JSON.stringify(resultat.contenu))
return ''
}
// faut virer les nœuds fin éventuels (ils ne sont plus dans les résultats récents mais toujours dans les anciens)
const noeudsActifs = resultat.contenu.graphe.filter(n => n && typeof n[1] === 'string' && n[1].toLowerCase() !== 'fin')
if (noeudsActifs.length < 2) return ''
// on gère l'unicité des ids, même si à priori on est tout seul dans une iframe
const myId = 'j3pParcours' + (i++)
const baseUrl = getBaseUrlFromResultat(resultat)
const { hostname } = new URL(baseUrl)
const isDev = /(sesamath\.dev|localhost|\.local)$/.test(hostname)
// plus besoin, webpack inclue ça dans le js
// const cssUrl = baseUrl + 'showParcours.css'
const jsUrl = `https://j3p.sesamath.${isDev ? 'dev' : 'net'}/build/loader.js`
const contenuResultat = JSON.stringify(resultat.contenu)
// dans ce qui suit, on peut mettre de l'es6 dans les ${} mais pas en dehors
// normalement le js expose en global une fct stshowParcours, mais avec un pb webpack on peut se retrouver
// avec le module complet et pas son export par défaut, et vu que c'est arrivé et resté comme ça pendant des mois…
// …on traite les deux cas
return `
<div id="${myId}"></div>
<script type="application/javascript">
(function () {
'use strict'
var conteneur = document.getElementById("${myId}")
if (typeof showParcours === 'function') return showParcours(conteneur, ${contenuResultat})
// sinon c'est le premier appel, faut le charger
var head = window.document.getElementsByTagName('head')[0]
var body = window.document.getElementsByTagName('body')[0]
if (!head || !body) throw Error('Page html malformée (head ou body manquant)')
// ajout du js
var scriptTag = document.createElement('script')
scriptTag.type = 'application/javascript'
scriptTag.src = '${jsUrl}'
scriptTag.addEventListener('load', function () {
if (typeof showParcours === 'function') return showParcours(conteneur, ${contenuResultat})
console.error('Problème de chargement de showParcours')
conteneur.innerHTML = '<p style="color:"#d33">Impossible d’afficher le parcours réalisé (problème de chargement de l’afficheur)</p>'
})
body.appendChild(scriptTag)
})()
</script>`
} else {
return ''
}
}
}