From c504ace1671c26eb53fc77c43e612f5808693b39 Mon Sep 17 00:00:00 2001 From: william Date: Sun, 6 Sep 2026 00:17:51 +0200 Subject: [PATCH] =?UTF-8?q?Enrichit=20le=20reporting=20SCORM/xAPI=20et=20m?= =?UTF-8?q?et=20en=20conformit=C3=A9=20RGAA=20le=20player?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SCORM/xAPI : - Ajoute l'export SCORM 2004 (3rd/4th edition) au choix, en plus du 1.2 par défaut : sépare completion_status/success_status (un échec reste "completed" au lieu de retomber à tort en "incomplete" comme force la 1.2), remonte aussi cmi.interactions.n.* par question répondue. - Fournit cmi.core.score.min/max (1.2 et 2004), calculé depuis les récompenses de quiz du jeu, pour que le LMS affiche un vrai pourcentage au lieu du score brut à tort étiqueté "%". - Libellés de verbes xAPI en français en plus de l'anglais. Accessibilité (RGAA/WCAG 2.1 AA) sur le player : - Navigation clavier des objets de scène "au clic"/"au survol" (tabindex, role, Entrée/Espace, focus/blur). - alt sur les images (nom auteur ou décoratif), aria-hidden sur les icônes seules, role="dialog"/aria-live sur les boîtes de dialogue/quiz. - Landmark
+ titre de page, respect de prefers-reduced-motion. - Le quiz n'avance plus automatiquement après un délai fixe : bouton "Continuer →" explicite (RGAA 2.2.1). - Avertissement de contraste dans l'éditeur de style de dialogue. - Déclaration d'accessibilité téléchargeable depuis la modale d'export. --- db/__init__.py | 4 + db/games/get_scorm_version.py | 19 +++ db/games/get_success_threshold.py | 19 +++ db/games/set_scorm_version.py | 15 ++ db/games/set_success_threshold.py | 14 ++ publish/accessibility_declaration.py | 67 ++++++++ publish/build_scorm_package.py | 37 +++- publish/scorm_manifest.py | 58 ++++++- routes/publish/__init__.py | 3 + routes/publish/accessibility_declaration.py | 20 +++ routes/publish/scoring_settings.py | 26 +++ routes/publish/scorm_version_settings.py | 23 +++ screens/scenes/render_scene_object.py | 23 ++- .../__tests__/dialogue-box-controller.test.js | 158 ++++++++++++++++-- static/js/play/dialogue-box-controller.js | 90 +++++++--- .../offline/__tests__/render-element.test.js | 12 ++ .../__tests__/render-special-widgets.test.js | 7 + .../play/offline/__tests__/scorm-api.test.js | 76 +++++++++ .../offline/__tests__/scorm2004-api.test.js | 151 +++++++++++++++++ .../offline/__tests__/xapi-client.test.js | 93 +++++++++++ static/js/play/offline/render-element.js | 16 +- .../js/play/offline/render-special-widgets.js | 8 + static/js/play/offline/scorm-api.js | 21 ++- static/js/play/offline/scorm2004-api.js | 136 +++++++++++++++ static/js/play/offline/xapi-client.js | 117 +++++++++++-- static/js/play/triggers.js | 39 ++++- static/js/scenes/scene-editor.js | 46 +++++ static/style.css | 8 + templates/base.html | 95 ++++++++++- templates/play.html | 27 ++- templates/scene_edit.html | 6 + tests/test_accessibility_declaration.py | 35 ++++ tests/test_export_scorm.py | 64 +++++++ tests/test_scene_objects.py | 32 ++++ tests/test_scoring_settings.py | 51 ++++++ tests/test_scorm_version_settings.py | 44 +++++ 36 files changed, 1581 insertions(+), 79 deletions(-) create mode 100644 db/games/get_scorm_version.py create mode 100644 db/games/get_success_threshold.py create mode 100644 db/games/set_scorm_version.py create mode 100644 db/games/set_success_threshold.py create mode 100644 publish/accessibility_declaration.py create mode 100644 routes/publish/accessibility_declaration.py create mode 100644 routes/publish/scoring_settings.py create mode 100644 routes/publish/scorm_version_settings.py create mode 100644 static/js/play/offline/__tests__/scorm-api.test.js create mode 100644 static/js/play/offline/__tests__/scorm2004-api.test.js create mode 100644 static/js/play/offline/__tests__/xapi-client.test.js create mode 100644 static/js/play/offline/scorm2004-api.js create mode 100644 tests/test_accessibility_declaration.py create mode 100644 tests/test_scoring_settings.py create mode 100644 tests/test_scorm_version_settings.py diff --git a/db/__init__.py b/db/__init__.py index 83579465..884dd607 100644 --- a/db/__init__.py +++ b/db/__init__.py @@ -36,6 +36,10 @@ from .games.delete_game import delete_game from .games.move_game import move_game from .games.get_xapi_settings import get_xapi_settings from .games.set_xapi_settings import set_xapi_settings +from .games.get_success_threshold import get_success_threshold +from .games.set_success_threshold import set_success_threshold +from .games.get_scorm_version import get_scorm_version +from .games.set_scorm_version import set_scorm_version from .games.game_type_catalog import ( ONBOARDING_TYPES, DEFAULT_ONBOARDING_TYPE, get_onboarding_type, get_onboarding_type_raw, set_onboarding_type, diff --git a/db/games/get_scorm_version.py b/db/games/get_scorm_version.py new file mode 100644 index 00000000..fbe0b086 --- /dev/null +++ b/db/games/get_scorm_version.py @@ -0,0 +1,19 @@ +from ..connection import connect + +DEFAULT_SCORM_VERSION = "1.2" +VALID_SCORM_VERSIONS = ("1.2", "2004") + + +def get_scorm_version(slug): + """Version SCORM exportée pour ce jeu (voir publish/build_scorm_package.py, + publish/scorm_manifest.py) — '1.2' par défaut (compatibilité la plus + large) ou '2004' (3rd/4th edition, sépare completion_status/ + success_status : voir static/js/play/offline/scorm2004-api.js, corrige + la limite SCORM 1.2 où un statut unique doit coder à la fois + complétion ET réussite).""" + conn = connect(slug) + row = conn.execute("SELECT value FROM _meta WHERE key = 'scorm_version'").fetchone() + conn.close() + if row is None or row["value"] not in VALID_SCORM_VERSIONS: + return DEFAULT_SCORM_VERSION + return row["value"] diff --git a/db/games/get_success_threshold.py b/db/games/get_success_threshold.py new file mode 100644 index 00000000..8f5b5cbb --- /dev/null +++ b/db/games/get_success_threshold.py @@ -0,0 +1,19 @@ +from ..connection import connect + +DEFAULT_SUCCESS_THRESHOLD_PERCENT = 70 + + +def get_success_threshold(slug): + """Seuil de réussite (% de bonnes réponses au quiz) de CE jeu — stocké + dans _meta (clé 'success_threshold_percent'), même convention que + get_xapi_settings.py. Détermine le statut SCORM/xAPI de fin de partie + (reussi/echoue, voir static/js/play/dialogue-box-controller.js:: + forgeSyncAllQuestsCompletionToScorm) : 70% par défaut si jamais réglé.""" + conn = connect(slug) + row = conn.execute( + "SELECT value FROM _meta WHERE key = 'success_threshold_percent'" + ).fetchone() + conn.close() + if row is None or row["value"] in (None, ""): + return DEFAULT_SUCCESS_THRESHOLD_PERCENT + return int(row["value"]) diff --git a/db/games/set_scorm_version.py b/db/games/set_scorm_version.py new file mode 100644 index 00000000..21279a6d --- /dev/null +++ b/db/games/set_scorm_version.py @@ -0,0 +1,15 @@ +from ..connection import connect +from .get_scorm_version import VALID_SCORM_VERSIONS + + +def set_scorm_version(slug, version): + """Enregistre la version SCORM exportée — voir get_scorm_version.py.""" + if version not in VALID_SCORM_VERSIONS: + raise ValueError("version SCORM invalide : {!r}".format(version)) + conn = connect(slug) + conn.execute( + "INSERT OR REPLACE INTO _meta (key, value) VALUES ('scorm_version', ?)", + (version,), + ) + conn.commit() + conn.close() diff --git a/db/games/set_success_threshold.py b/db/games/set_success_threshold.py new file mode 100644 index 00000000..cb327628 --- /dev/null +++ b/db/games/set_success_threshold.py @@ -0,0 +1,14 @@ +from ..connection import connect + + +def set_success_threshold(slug, percent): + """Enregistre le seuil de réussite (0-100) — voir get_success_threshold.py.""" + percent = max(0, min(100, int(percent))) + conn = connect(slug) + conn.execute( + "INSERT OR REPLACE INTO _meta (key, value) VALUES ('success_threshold_percent', ?)", + (str(percent),), + ) + conn.commit() + conn.close() + return percent diff --git a/publish/accessibility_declaration.py b/publish/accessibility_declaration.py new file mode 100644 index 00000000..7ddd975e --- /dev/null +++ b/publish/accessibility_declaration.py @@ -0,0 +1,67 @@ +"""Déclaration d'accessibilité exportable (obligation légale RGAA pour +tout service numérique concerné, voir roadmap phase 1.2) — PAS un audit +certifié : un résumé honnête, généré à partir de ce que le moteur de jeu +implémente RÉELLEMENT aujourd'hui (voir static/js/play/triggers.js, +dialogue-box-controller.js, screens/scenes/render_scene_object.py), à +compléter/faire valider par le créateur du jeu avant publication.""" +from datetime import date + +_DECLARATION_TEMPLATE = """Déclaration d'accessibilité — {game_name} +Générée le {today} par Forge Engine + +Cette déclaration résume l'état d'accessibilité du jeu de formation +"{game_name}" au regard du RGAA (Référentiel Général d'Amélioration de +l'Accessibilité, version française de WCAG 2.1 niveau AA). Elle décrit ce +que le moteur Forge Engine implémente pour TOUT jeu exporté — elle ne +remplace pas un audit RGAA complet réalisé par un expert accessibilité, et +ne couvre pas le contenu propre à ce jeu (texte, images, choix de +couleurs) qui reste sous la responsabilité de son créateur. + +CRITÈRES PRIS EN CHARGE PAR LE MOTEUR +-------------------------------------- +- Navigation clavier : tout objet de scène réagissant "Au clic" ou "Au + survol" est focusable (Tab) et activable au clavier (Entrée/Espace), + en plus de la souris. Les boutons de dialogue/quiz sont de vrais +