PageSourceSearch

https://machinelearningcertification.web.app/js/quiz-catalogue.js

js machinelearningcertification.web.app collected 2026-10-03 12:29:33 UTC 16,573 bytes, 331 lines download raw bytes

1/* Quiz catalogue and page adapter for the AI & ML Tutor app.
2 *
3 * The original quiz was driven by js/quiz.js, which held a `categories` array
4 * mapping a topic name to a /server_data/*.json file and rendered questions
5 * into a bespoke #game panel. Three things were wrong with it:
6 *
7 *   1. It only understood banks with flat A/B/C/D keys. Two files use
8 *      `Choices`/`Answers` instead, so 338 records rendered with no options.
9 *   2. Several topics pointed at files that do not exist (gcp_ml_mock_exam) or
10 *      that contain no real questions (ml_computer_vision: 59 blank records).
11 *   3. Duplicate topics pointed at the same bank under different spellings.
12 *
13 * Banks are now schema v2 in /quizzes/, and rendering is handled by
14 * quiz-engine.js -- the same engine the Djamgatech app uses. This file is the
15 * glue: it owns the topic list and starts the engine when a topic is chosen.
16 */
17(function () {
18  'use strict';
19
20  /* topic label -> schema-v2 bank id in /quizzes/.
21     Only banks that exist and contain real questions appear here.
22
23     `retired` marks a bank whose target exam no longer exists. We keep these
24     banks -- the underlying ML theory is still good study material -- but they
25     are grouped separately, labelled with the retirement date, and show a
26     migration notice pointing at the replacement bank. Silently serving them
27     as current "certification prep" would mislead someone paying for an exam.
28
29     `successor` is the bank id a retired exam's candidates should move to. */
30  var CATALOGUE = [
31    { id: 'machine_learning_basics',                label: 'Machine Learning Basics',              group: 'Foundations' },
32    { id: 'machine_learning_advanced',              label: 'Machine Learning Advanced',            group: 'Foundations' },
33    { id: 'ai_basics',                              label: 'AI Basics',                            group: 'Foundations' },
34    { id: 'ai_advanced',                            label: 'AI Advanced',                          group: 'Foundations' },
35    { id: 'deep_learning',                          label: 'Deep Learning',                        group: 'Foundations' },
36    { id: 'neural_networks',                        label: 'Neural Networks',                      group: 'Foundations' },
37    { id: 'ml_nlp',                                 label: 'Natural Language Processing',          group: 'Specialisms' },
38    { id: 'generative_ai',                          label: 'Generative AI',                        group: 'Specialisms' },
39    { id: 'explainable_ai',                         label: 'Explainable AI',                       group: 'Specialisms' },
40    { id: 'ml_ops_on_aws_azure_gcp',                label: 'MLOps on AWS, Azure and GCP',          group: 'Specialisms' },
41    { id: 'ai_ops_on_aws_azure_gcp',                label: 'AIOps on AWS, Azure and GCP',          group: 'Specialisms' },
42    { id: 'ml_implementation_and_operations',       label: 'ML Implementation and Operations',     group: 'Specialisms' },
43
44    { id: 'aws_ai_practitioner',                    label: 'AWS Certified AI Practitioner',        group: 'Certification prep — AWS' },
45    { id: 'aws_ml_engineer_associate',              label: 'AWS Certified ML Engineer – Associate (MLA-C02)', group: 'Certification prep — AWS' },
46    { id: 'aws_genai_developer_professional',       label: 'AWS Certified GenAI Developer – Professional (AIP-C01)', group: 'Certification prep — AWS' },
47    { id: 'aws_data_engineer_exam_questions',       label: 'AWS Certified Data Engineer Associate', group: 'Certification prep — AWS' },
48    { id: 'formatted_aws_data_engineer_exam',       label: 'AWS Data Engineer — Extra Set',        group: 'Certification prep — AWS' },
49    { id: 'azure_ai_apps_agents_ai103',             label: 'Azure AI Apps & Agents Developer (AI-103)', group: 'Certification prep — Microsoft' },
50
51    { id: 'azure_ai_mock_exam',                     label: 'Microsoft Azure AI Fundamentals (AI-901)', group: 'Certification prep — Microsoft' },
52
53    { id: 'gcp_professional_ml_engineer',           label: 'Google Professional ML Engineer',      group: 'Certification prep — Google Cloud' },
54    { id: 'gcp_generative_ai_leader',               label: 'Google Cloud Generative AI Leader',    group: 'Certification prep — Google Cloud' },
55    { id: 'gcp_ml_implementation_and_operations',   label: 'GCP ML: Implementation and Operations', group: 'Certification prep — Google Cloud' },
56
57    { id: 'nvidia_nca_genl',                        label: 'NVIDIA Certified Associate: Generative AI LLMs (NCA-GENL)', group: 'Certification prep — Other vendors' },
58    { id: 'databricks_genai_engineer_associate',    label: 'Databricks Certified Generative AI Engineer Associate', group: 'Certification prep — Other vendors' },
59
60    /* AWS ML Specialty (MLS-C01) retired 31 March 2026. Questions remain
61       useful as general AWS ML study material. */
62    { id: 'aws_machine_learning_mock_exam',         label: 'AWS ML Specialty (MLS-C01) — retired Mar 2026',       group: 'Legacy / retired exams', retired: 'MLS-C01 retired 31 March 2026' },
63    { id: 'aws_machine_learning_specialty_practice_exam2', label: 'AWS ML Specialty Practice Exam 2 — retired Mar 2026', group: 'Legacy / retired exams', retired: 'MLS-C01 retired 31 March 2026' },
64    { id: 'aws_ml_data_engineering',                label: 'AWS ML: Data Engineering (MLS-C01 domain)',           group: 'Legacy / retired exams', retired: 'MLS-C01 retired 31 March 2026' },
65    { id: 'aws_ml_exploratory_data_analysis',       label: 'AWS ML: Exploratory Data Analysis (MLS-C01 domain)',  group: 'Legacy / retired exams', retired: 'MLS-C01 retired 31 March 2026' },
66    { id: 'aws_ml_modeling',                        label: 'AWS ML: Modeling (MLS-C01 domain)',                   group: 'Legacy / retired exams', retired: 'MLS-C01 retired 31 March 2026' },
67    { id: 'aws_ml_implementation_and_operations',   label: 'AWS ML: Implementation & Operations (MLS-C01 domain)', group: 'Legacy / retired exams', retired: 'MLS-C01 retired 31 March 2026' }
68  ];
69
70  /* Order groups deliberately: current certification prep first, retired last. */
71  var GROUP_ORDER = [
72    'Certification prep — AWS',
73    'Certification prep — Microsoft',
74    'Certification prep — Google Cloud',
75    'Certification prep — Other vendors',
76    'Foundations',
77    'Specialisms',
78    'Legacy / retired exams'
79  ];
80
81  function entryFor(bankId) {
82    for (var i = 0; i < CATALOGUE.length; i++) {
83      if (CATALOGUE[i].id === bankId) return CATALOGUE[i];
84    }
85    return null;
86  }
87
88  /* Legacy topic labels that used to appear in the <select>, so old bookmarks
89     and any cached markup still resolve. Left-hand side is what the old page
90     used; right-hand side is the bank we now serve.
91     'GCP ML' pointed at gcp_ml_mock_exam.json, which has never existed in this
92     repository -- it is redirected to the Google ML Engineer bank. */
93  var ALIASES = {
94    'Machine Learning Basics': 'machine_learning_basics',
95    'Sciences - Machine Learning Advanced': 'machine_learning_advanced',
96    'Machine Learning Advanced': 'machine_learning_advanced',
97    'ML NLP Natural Language Processing': 'ml_nlp',
98    'AWS ML NLP': 'ml_nlp',
99    'ML OPS on AWS Azure GCP': 'ml_ops_on_aws_azure_gcp',
100    'Machine Learning OPS on AWS Azure GCP': 'ml_ops_on_aws_azure_gcp',
101    'AI OPS on AWS Azure GCP': 'ai_ops_on_aws_azure_gcp',
102    'AI Basics': 'ai_basics',
103    'AI Advanced': 'ai_advanced',
104    'AWS AI Practitioner': 'aws_ai_practitioner',
105    'AWS Data Engineer': 'aws_data_engineer_exam_questions',
106    'Deep Learning': 'deep_learning',
107    'Explainable AI': 'explainable_ai',
108    'Generative AI': 'generative_ai',
109    'Neural Networks': 'neural_networks',
110    'AWS Machine Learning exam Prep': 'aws_machine_learning_mock_exam',
111    'AWS Machine Learning Specialty': 'aws_machine_learning_mock_exam',
112    'Azure AI': 'azure_ai_mock_exam',
113    'Azure AI Fundamentals': 'azure_ai_mock_exam',
114    'Google Professional Machine Learning Engineer': 'gcp_professional_ml_engineer',
115    'GCP ML': 'gcp_professional_ml_engineer',
116    'AWS ML Exploratory Data Analysis': 'aws_ml_exploratory_data_analysis',
117    'AWS ML Data Engineering': 'aws_ml_data_engineering',
118    'AWS ML Modelling': 'aws_ml_modeling',
119    'AWS ML Modeling': 'aws_ml_modeling'
120  };
121
122  function resolve(value) {
123    if (!value) return null;
124    if (ALIASES[value]) return ALIASES[value];
125    for (var i = 0; i < CATALOGUE.length; i++) {
126      if (CATALOGUE[i].id === value || CATALOGUE[i].label === value) {
127        return CATALOGUE[i].id;
128      }
129    }
130    return null;
131  }
132
133  /* The old renderer owned these nodes. The engine draws everything inside
134     #qz-root, so leaving them visible shows a stale question and a dead timer
135     above the real quiz. */
136  var LEGACY_NODES = ['game', 'currentanswerbutton', 'currentanswer'];
137
138  /* The quiz sits inside <div id="app" class="workorder-card">, which has an
139     opaque WHITE background while the rest of the page is near-black. On a
140     phone that renders as a white slab around the quiz and reads as a broken
141     layout. Tag the ancestors while a quiz is running so the theme stylesheet
142     can make them transparent, and untag on exit so the rest of the page keeps
143     its normal card styling. */
144  function markHostChain(on) {
145    var n = document.getElementById('quiz-wrapper');
146    while (n && n !== document.body) {
147      if (on) { n.classList.add('qz-host'); } else { n.classList.remove('qz-host'); }
148      n = n.parentElement;
149    }
150  }
151
152  function hideLegacy() {
153    LEGACY_NODES.forEach(function (id) {
154      var el = document.getElementById(id);
155      if (el) el.style.display = 'none';
156    });
157  }
158
159  function showLegacy() {
160    LEGACY_NODES.forEach(function (id) {
161      var el = document.getElementById(id);
162      if (el) el.style.display = '';
163    });
164  }
165
166  function ensureWrapper() {
167    var wrap = document.getElementById('quiz-wrapper');
168    if (wrap) return wrap;
169    wrap = document.createElement('section');
170    wrap.id = 'quiz-wrapper';
171    var anchor = document.getElementById('game');
172    if (anchor && anchor.parentNode) {
173      anchor.parentNode.insertBefore(wrap, anchor.nextSibling);
174    } else {
175      (document.getElementById('app') || document.body).appendChild(wrap);
176    }
177    return wrap;
178  }
179
180  /* Show an honest, dismissible notice above a retired exam's quiz so nobody
181     studies for an exam they can no longer sit without knowing it. */
182  function renderRetirementNotice(bankId) {
183    var existing = document.getElementById('qz-retired-notice');
184    if (existing) existing.remove();
185
186    var entry = entryFor(bankId);
187    if (!entry || !entry.retired) return;
188
189    var box = document.createElement('div');
190    box.id = 'qz-retired-notice';
191    box.style.cssText = 'background:rgba(249,115,22,0.10);border:1px solid rgba(249,115,22,0.45);' +
192      'border-left:4px solid #F97316;border-radius:10px;padding:1rem 1.25rem;margin:0 0 1rem;';
193    box.innerHTML =
194      '<p style="margin:0 0 .4rem;font-weight:700;color:#F97316;font-size:.95rem;">' +
195        '&#9888;&#65039; This exam has retired' +
196      '</p>' +
197      '<p style="margin:0;color:rgba(255,255,255,0.85);font-size:.9rem;line-height:1.5;">' +
198        entry.retired + '. These questions are still solid AWS machine-learning study ' +
199        'material, but they no longer map to a bookable exam. If you are certifying now, ' +
200        'AWS points candidates to <a href="/quiz/aws_ml_engineer_associate.html" ' +
201        'style="color:#F97316;font-weight:600;">Certified Machine Learning Engineer &ndash; ' +
202        'Associate (MLA-C02)</a> instead.' +
203      '</p>';
204
205    var wrap = ensureWrapper();
206    wrap.insertBefore(box, wrap.firstChild);
207  }
208
209  function start(bankId, opts) {
210    if (!bankId) return;
211    var scroll = !opts || opts.scroll !== false;
212    ensureWrapper();
213    hideLegacy();
214    markHostChain(true);
215    renderRetirementNotice(bankId);
216    if (!window.loadQuiz || !window.startQuiz) {
217      console.error('[quiz-catalogue] quiz-engine.js has not loaded');
218      return;
219    }
220    window.loadQuiz(bankId).then(function () {
221      window.startQuiz();
222      if (window.__qzBackBtn) window.__qzBackBtn.style.display = '';
223      var wrap = document.getElementById('quiz-wrapper');
224      if (wrap && scroll) wrap.scrollIntoView({ behavior: 'smooth', block: 'start' });
225    }).catch(function (err) {
226      // A newer selection already superseded this one -- that request's own
227      // .then() (or a still-pending one) owns the screen now, so leave it
228      // alone rather than stomping it with an error card.
229      if (err && err.superseded) return;
230      console.error('[quiz-catalogue] could not load bank', bankId, err);
231      var wrap = ensureWrapper();
232      wrap.innerHTML = '<div class="qz-card"><p>Sorry — that quiz could not be ' +
233        'loaded. Please pick another topic.</p></div>';
234    });
235  }
236
237  /* Rebuild the topic <select> from the catalogue, so it can never again offer
238     a topic whose bank is missing or empty. */
239  function buildPicker() {
240    var sel = document.getElementById('Category') || document.getElementById('Categories');
241    if (!sel) return;
242    var groups = {};
243    CATALOGUE.forEach(function (c) {
244      (groups[c.group] = groups[c.group] || []).push(c);
245    });
246    sel.innerHTML = '<option value="">Choose a topic…</option>';
247    // Render groups in a deliberate order (current certs first, retired last)
248    // rather than whatever order the object keys happen to come back in.
249    var ordered = GROUP_ORDER.filter(function (g) { return groups[g]; });
250    Object.keys(groups).forEach(function (g) {
251      if (ordered.indexOf(g) === -1) ordered.push(g);
252    });
253    ordered.forEach(function (g) {
254      var og = document.createElement('optgroup');
255      og.label = g;
256      groups[g].forEach(function (c) {
257        var o = document.createElement('option');
258        o.value = c.id;
259        o.textContent = c.label;
260        og.appendChild(o);
261      });
262      sel.appendChild(og);
263    });
264    sel.addEventListener('change', function () { start(resolve(sel.value)); });
265  }
266
267  /* Pick a random current (non-retired) bank so a first-time visitor sees a
268     working quiz immediately instead of a bare, unlabelled dropdown -- but
269     never surprise them with a retired exam as their first impression. */
270  function randomDefault() {
271    var current = CATALOGUE.filter(function (c) { return !c.retired; });
272    if (!current.length) return null;
273    return current[Math.floor(Math.random() * current.length)].id;
274  }
275
276  function init() {
277    buildPicker();
278
279    // Deep link: /?quiz=<bank id> starts that quiz directly.
280    var params = new URLSearchParams(window.location.search);
281    var deep = resolve(params.get('quiz') || params.get('cert') || params.get('topic'));
282    if (deep) {
283      start(deep);
284    } else {
285      var pick = randomDefault();
286      if (pick) {
287        var sel = document.getElementById('Category') || document.getElementById('Categories');
288        if (sel) sel.value = pick;
289        start(pick, { scroll: false });
290      }
291    }
292
293    // Give the user a way out of the quiz. The engine has no Back control of
294    // its own -- in the Djamgatech app that button belongs to the host page --
295    // so provide one here and restore the original panel when it is used.
296    var back = document.createElement('button');
297    back.type = 'button';
298    back.id = 'qz-back';
299    back.className = 'qz-btn';
300    back.textContent = '← Choose another topic';
301    back.style.cssText = 'margin:0 0 1rem;display:none;';
302    back.addEventListener('click', function () {
303      var root = document.getElementById('qz-root');
304      if (root) root.innerHTML = '';
305      var notice = document.getElementById('qz-retired-notice');
306      if (notice) notice.remove();
307      if (window.quizTimer) clearInterval(window.quizTimer);
308      back.style.display = 'none';
309      markHostChain(false);
310      showLegacy();
311      var sel = document.getElementById('Category') || document.getElementById('Categories');
312      if (sel) { sel.value = ''; sel.scrollIntoView({ behavior: 'smooth', block: 'center' }); }
313    });
314    ensureWrapper().appendChild(back);
315    window.__qzBackBtn = back;
316  }
317
318  window.QuizCatalogue = {
319    list: CATALOGUE,
320    groupOrder: GROUP_ORDER,
321    resolve: resolve,
322    entryFor: entryFor,
323    start: start
324  };
325
326  if (document.readyState === 'loading') {
327    document.addEventListener('DOMContentLoaded', init);
328  } else {
329    init();
330  }
331})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.