Legacy WXR import 2026-08-19

Historical record — 2026-08-19

Preserved as observed. Current truth: Program Masterplan.

Headline, all measured on the bench

The legacy export quizwizz.WordPress.2026-08-19.xml (21,593,668 bytes, sha256 90acf85ac2389990923af739ab056ec116ffb7a4503c40e2c80a158392d1e806, 8,401 <item> nodes) was mined purely as a data source — never fed to WordPress’ WXR importer. 8,379 cleaned en+de questions survived extraction, 42 were already live by stem hash, and the remaining 8,337 imported through QuizWizz\Import\QuestionPackImporter::import_json() in 84 packs with 0 row rejections at import time. Published questions 732 → 9,069, read index 732 → 9,069 (drift 0). Probe after the run: 1..46, assertions=46 failures=0.

Source identity

PropertyValue
Filequizwizz.WordPress.2026-08-19.xml (bench .usr/, copied local, hashes identical)
Size / sha25621,593,668 bytes / 90acf85a…e806
Items8,401 — all wp:post_type=qw_question, all wp:status=publish
ProvenanceOpenTDB export (qw_source=OpenTDB on 8,392 rows), en 4,196 / de 4,196 / no lang 9

Meta-key mapping

Trivial: the legacy keys are already the plugin’s own. qw_choices 8,398 (PHP-serialized array, parsed by byte length), qw_correct_idx 8,398, qw_type 8,392 (multiple 7,192 / boolean 1,200), qw_lang 8,392, qw_opentdb_id 8,392, qw_explanation 7, stem in content:encoded, subjects as qw_subject nicenames (22 slugs, all already live terms), difficulty as qw_difficulty (mediumnormal).

Cleaning rules

  1. Single html.unescape() — the file contains 0 &amp;amp; double-encodings (measured).
  2. Strip tags (<[^>]+>) and shortcodes (\x5b[^\x5d]+\x5d), collapse whitespace.
  3. Comparison signs before digits rewritten per language (<1%less than 1% / unter 1%): WP’s QuestionValidator maps choices through wp_strip_all_tags(), and PHP strip_tags() deletes everything after an unclosed < — the untouched choice imports as empty and fails the pack. Found on exactly 2 rows (en+de of the same Timur question); &&&&&& choices survive unharmed.
  4. Choice distinctness checked NFKC + casefold + whitespace-collapse; correct_idx integer-in-range; stem ≤2000, choice ≤1000, explanation ≤4000.

Extraction census

Accepted en 4,191 + de 4,188, rejected 22; 4,191 + 4,188 + 22 = 8,401 exactly. Types multiple 7,181 / boolean 1,198; difficulty easy 2,777 / normal 3,834 / hard 1,768; explanations all empty (the 7 legacy qw_explanation values sat on rejected test artifacts or cleaned to empty).

Rejection ledger (named reasons, full detail in rejections.json)

ReasonRows
within-corpus duplicate stem (first occurrence kept)11
missing qw_lang/qw_type (test artifact)6
missing qw_choices (test artifact)3
fewer than 2 / more than 8 / empty choices2

Accepted rows per subject

SubjectRows
animals100
art98
celebrities100
entertainment-board-games100
entertainment-books200
entertainment-cartoon-animations200
entertainment-comics100
entertainment-film600
entertainment-japanese-anime-manga400
entertainment-music900
entertainment-television300
entertainment-video-games1398
general-knowledge899
geography695
history797
mythology100
politics100
science-computers298
science-mathematics96
science-nature498
sports300
vehicles100

Dedupe against live

Fresh post_content dump of the live 732 published questions; stem-hash (extract_overlap.py normalization, verbatim) dedupe dropped 42 rows (en 41 / de 1) as already live — the 40 live OpenTDB rows plus two hand-curated overlaps. 80 informational qw_opentdb_id collisions (the 40 live ids × en+de) — dedupe is by stem hash, never by opentdb id.

Import

84 packs (legacy-en-001…042, legacy-de-001…042), one language per pack, ≤100 questions, emitted with exactly the 7 allowed question keys and preflighted independently (OK 84 packs, 8337 questions, 0 violations) before any transfer. Each pack imported via wp_set_current_user(admin) + import_json(); every pack logged inserted == questions, skipped 0; Σ inserted = 8,337. Each import created one draft quiz — the 84 drafts are retained as import receipts (consistent with prior runs; a one-line sweep can delete them later if they clutter the admin).

Two run incidents, both recovered without data damage: the loop’s log file was unwritable from inside the CLI container, crashing the runner after a pack had fully imported (twice); resume detection was moved from the log file to the draft-quiz receipts in the database, and no pack was double-imported (0 duplicate Legacy OpenTDB titles measured).

Per-pack results

PackLangQuestionsSubject rowsDraft quizInsertedSkippedsha256 (12)
legacy-en-001.jsonen1001668810007fc06065d4a0
legacy-en-002.jsonen100167891000ea7675728cdf
legacy-en-003.jsonen1001689010003f5660bdd8ca
legacy-en-004.jsonen100169911000d355e6ac8ae3
legacy-en-005.jsonen1003709210004371ea715105
legacy-en-006.jsonen1003719310001e00bfcd5c80
legacy-en-007.jsonen10027294100091c04414a548
legacy-en-008.jsonen10027395100090aeaf078dbb
legacy-en-009.jsonen1003749610003d33733476ae
legacy-en-010.jsonen1002759710009842dfb3035d
legacy-en-011.jsonen1002769810002062d5413b1f
legacy-en-012.jsonen10027799100021222172fdd2
legacy-en-013.jsonen10037900100026cb37f3b6bd
legacy-en-014.jsonen100280011000f94fa7a1002d
legacy-en-015.jsonen100381021000c7e85fe943de
legacy-en-016.jsonen100282031000799d88d5d1d1
legacy-en-017.jsonen1002830410009eb43496a010
legacy-en-018.jsonen100284051000143f88c46b2b
legacy-en-019.jsonen1002850610001904a1f63ed3
legacy-en-020.jsonen100286071000c1eb19a193c5
legacy-en-021.jsonen10028708100021c897a529cc
legacy-en-022.jsonen1005880910006687dc6147d1
legacy-en-023.jsonen100289101000daf4381a40b3
legacy-en-024.jsonen100290111000fa85d57c782c
legacy-en-025.jsonen10039112100012593b866278
legacy-en-026.jsonen1003921310003beac7d5b068
legacy-en-027.jsonen10049314100030cf82bbf442
legacy-en-028.jsonen1001941510008d7c53daf736
legacy-en-029.jsonen10029516100094b9724ae702
legacy-en-030.jsonen10019617100065bbb1ef4298
legacy-en-031.jsonen1001971810006cdaac322754
legacy-en-032.jsonen1002981910001cb7d06b94f8
legacy-en-033.jsonen100199201000aea50ff58206
legacy-en-034.jsonen1001100211000f2e27c4f41ad
legacy-en-035.jsonen1001101221000d36a46a55ac5
legacy-en-036.jsonen1002102231000136a6e16fa96
legacy-en-037.jsonen1003103241000227d5fc362cd
legacy-en-038.jsonen1002104251000a7d70a558aaa
legacy-en-039.jsonen1003105261000d70c73e268e8
legacy-en-040.jsonen1002106271000e2ab9c3f8ad3
legacy-en-041.jsonen100310728100019266c2aae60
legacy-en-042.jsonen502107795007b23d971a8c6
legacy-de-001.jsonde100124591000af80ead4955b
legacy-de-002.jsonde100125601000a6a966378c46
legacy-de-003.jsonde100126611000212848b17576
legacy-de-004.jsonde10012762100008ed0e57dc79
legacy-de-005.jsonde10032863100056a728a9aa7c
legacy-de-006.jsonde100329641000b715acca0e8f
legacy-de-007.jsonde100230651000dd0f699da95b
legacy-de-008.jsonde100231661000eb3cd1aba8c5
legacy-de-009.jsonde1003326710004e516268422f
legacy-de-010.jsonde1002336810004ed61ac4efb4
legacy-de-011.jsonde1002346910008b8f7e11b53e
legacy-de-012.jsonde100235701000adfb9398b023
legacy-de-013.jsonde1002367110000ccc86232496
legacy-de-014.jsonde100237721000f1fb46eb5d6f
legacy-de-015.jsonde1003387310007bb28bdfa118
legacy-de-016.jsonde100239741000f4200094acaa
legacy-de-017.jsonde1002407510005b1fe9828835
legacy-de-018.jsonde100241761000117f63e8249e
legacy-de-019.jsonde1002427710003601db14f6fc
legacy-de-020.jsonde100243781000fd475153d5d4
legacy-de-021.jsonde100244791000f34192f5a161
legacy-de-022.jsonde1005458010002506b4199a02
legacy-de-023.jsonde1002468110003e3982b6e7c7
legacy-de-024.jsonde10024782100052fa5b1d0925
legacy-de-025.jsonde1003488310003ac9f9c9eb07
legacy-de-026.jsonde1003498410003093b9e8b712
legacy-de-027.jsonde100450851000f8778ed6dbfd
legacy-de-028.jsonde100151861000c461e8121501
legacy-de-029.jsonde100252871000cd26fd6f7b1c
legacy-de-030.jsonde100153881000cfd78d25b19a
legacy-de-031.jsonde100154891000b88c94ecd325
legacy-de-032.jsonde100255901000111beb423fad
legacy-de-033.jsonde1001569110007900637f9553
legacy-de-034.jsonde100157921000acc17a82f41f
legacy-de-035.jsonde100158931000bacdb1aebc2b
legacy-de-036.jsonde100259941000e7a5e6630015
legacy-de-037.jsonde100360951000b4081932c37a
legacy-de-038.jsonde100361961000461a793ec179
legacy-de-039.jsonde10046297100085e768f43dac
legacy-de-040.jsonde10026398100025322067f9dc
legacy-de-041.jsonde100364991000eb5aa5aaa18e
legacy-de-042.jsonde8726587870c443cec7f961

Before / after

MeasureBeforeAfterDelta
Published qw_question7329,069+8,337
wp_qwizz_question_index rows7329,069+8,337 (drift 0)
Index lang=en5614,711+4,150
Index lang=de574,244+4,187
Index lang=es / fr57 / 5757 / 570
Question-subject rows9789,315+8,337
qw_subject terms4834830 (no new terms)
Draft quizzes1599+84 receipts

Spot check end-to-end: DE stem Weißt du, welche dieser indischen Sprachen… survived cleaning intact into legacy-de-001.json, imported as post 2359, post_content matches, qw_correct_idx=3 (Tamil), index row lang='de'.

Probe

./scripts/quizwizz-probe.sh --wave all1..46, assertions=46 failures=0. Two Wave 6 fixtures had assumed pre-import content and were modernised to self-provision: the sufficiency assertion now raises its own count ceiling to smallest-pool+1 instead of hoping a ≤19-question pool exists, and the zero-pool assertion (which hardcoded science-computers, now 298 questions) became “every canonical subject remains visible in admin tree regardless of pool”. The plugin itself was not changed in this run.