{
  "contract_version": "gab_170_contract_v1",
  "gab_id": "GAB-170",
  "canonical_name": "AudioDictationPrompt",
  "module_owner": "EdTechAudioLearning",
  "renderer_key": "media_viewer",
  "required_fields": [
    "gab_id",
    "dictation_id",
    "audio",
    "instruction",
    "answer"
  ],
  "optional_fields": [
    "title",
    "tolerance",
    "feedback",
    "controls",
    "accessibility"
  ],
  "field_types": {
    "audio": "object{text:string, lang:string, rate_normal:number(0..2), rate_slow:number(0..2)}",
    "instruction": "string — consigne affichée au-dessus des contrôles",
    "answer": "string — phrase exacte de référence pour la correction",
    "tolerance": "number(≥0) — distance maximale tolérée pour 'bien' (défaut 3)",
    "feedback": "object{perfect, close, wrong, empty, reveal_label} — libellés de retour",
    "controls": "object{play_label, slow_label, repeat_label, pause_label, check_label, reveal_label}",
    "accessibility": "object{textarea_placeholder:string, transcript_always_available:boolean}"
  },
  "constraints": [
    "audio.text est la phrase lue via Web Speech API (TTS navigateur).",
    "answer est la phrase de référence pour la correction — DOIT correspondre à audio.text dans une instance réelle.",
    "tolerance est un entier ≥ 0 ; si absent, le moteur utilise 3 par défaut.",
    "Tous les libellés (instruction, boutons, feedbacks) viennent du JSON — aucun libellé en dur dans le HTML.",
    "Doctrine module EdTechAudioLearning : transcript TOUJOURS disponible (RGPD mineurs + accessibilité)."
  ],
  "blocked_conditions": [
    "audio ou audio.text absent",
    "answer absent",
    "instruction absent"
  ],
  "accessibility": [
    "keyboard_navigable",
    "focus_visible",
    "prefers_reduced_motion",
    "textarea_resizable",
    "transcript_always_available (doctrine module)"
  ],
  "qa_cases": [
    { "case": "instance conforme", "expected": "rendu complet, 0 erreur" },
    { "case": "audio.text absent", "expected": "BLOCKED listant le champ manquant" },
    { "case": "answer absent", "expected": "BLOCKED listant le champ manquant" },
    { "case": "réponse parfaite", "expected": "feedback 'perfect' affiché (mint)" },
    { "case": "réponse proche (diff <= tolerance)", "expected": "feedback 'close' + correction affichée (mint)" },
    { "case": "réponse éloignée (diff > tolerance)", "expected": "feedback 'wrong' + phrase exacte (coral)" },
    { "case": "zone de texte vide + clic vérifier", "expected": "feedback 'empty' (coral)" },
    { "case": "clic révéler", "expected": "answer affiché avec reveal_label" },
    { "case": "instance externe injectée", "expected": "rendu change sans modifier le HTML" },
    { "case": "responsive 375/768/1024", "expected": "aucun débordement horizontal" }
  ],
  "traceability": {
    "derived_from_core_gab": "GAB-170",
    "note": "Ce schema VALIDE l'instance. Le contrat pédagogique complet (input_contract/validation_logic/feedback_scoring_logic) vit dans le CORE-GAB officiel, pas ici (évite la duplication)."
  }
}
