# Overnight run — P2–P6 ingestion (started 2026-05-29, ~01:xx)

Pam's ask: "do likewise for P2 to P6 from sgexam, 2022 onwards — download, upload the
questions, make an image file for all the images. Autonomously. Check tomorrow."
Image clarification: "I'll colorize tomorrow. Put the ugly (B&W) ones in the folders."

## Honest scope finding
sgexam has FAR more than one night's worth (no pagination; single listing per level):
- **P2 ≈ 48** papers (2022–2025)  ← doing this fully tonight
- **P3 ≈ 141**
- **P4 ≈ 143**
- **P5 ≈ 157**
- **P6 ≈ 102**
- **Total ≈ 570 papers** → ~10,000 questions. P1 alone (43 papers) was a long session.
This is a multi-day job, not one night. So tonight's scope:

## Tonight's plan (scoped for quality, not a rushed mess)
1. **Seed P2** `.url` shortcuts (DONE — 48 files in `P2/`).
2. **Fully ingest P2** as the proven template: agents download+transcribe → I insert
   as **Draft** (`qa_status=ai_generated`, `status_id=4`, idempotent) → I crop **B&W**
   figures from each `paper.pdf` into the paper's folder for you to colourise.
3. **P3–P6**: left seeded/inventoried with this plan; ingest level-by-level over coming
   days (same machine). Did NOT bulk-insert ~9,000 dubious higher-level questions blind.

## Foundations built tonight (reusable for all levels)
- **P2–P6 skill reference**: `_p2_p6_skillmap.txt` (per-level track→skill_id, pulled from
  prod). Curriculum confirmed present: level_id 3=P2 … 7=P6; skills are level-agnostic,
  linked to level via `skill_track`→`tracks.level_id`.
- **Reliable prod DB-exec**: `php artisan tinker --execute="$(cat body.php)"` (tinker-via-
  stdin silently drops multi-line/brace blocks — avoid it).
- **Image approach**: B&W crops into folders (you colourise). Counting figures: the count
  IS the answer — AI sheets miscount (saw it on ACS test1 & test3), so verify/redraw.

## Status
- P2: 48 `.url` seeded. **Wave 1 = 7 agents running** (the 2022 papers: ACS BA3, Catholic
  High CP1&3, Tao Nan HA1/2/3, Nan Chiau LR, Rulang MT1). Will insert + crop as they land,
  then launch waves for the 2023/2024/2025 P2 papers.
- Inserts go to prod as Draft; I keep this file updated with per-paper results.

## P1 recap (from earlier today)
- ACS 2022 P1 Test 1: 14 Q, 7 figures live + verified. Q9 reworded, Q14 FIB→MCQ.
- ACS 2022 P1 Test 3: 19 Q, 9 figures live + verified (your corrected sheet). Open: Q12
  has no panel in the sheet; Q9 options malformed ("A: 2 fives") — both flagged for you.
- Other ~41 P1 papers: questions inserted (Draft); figures await your colour sheets.

## Caveats for your review
- P3–P6 (when done) will be rougher: harder math, some papers lack answer keys → flagged.
- Everything is Draft; nothing public. Word/letter answers forced to MCQ (FIB is numeric-only).
- Skill mapping is best-effort closest-topic; spot-check before publishing.

## Progress log (P2)
- **27 P2 papers ingested** (2022 ×7, 2023 ×20) — manifests + paper.pdf in each P2/<folder>/.
- **463 questions inserted to prod as Draft** (qa_status=ai_generated, status_id=4), idempotent (source+question). 35 interactive (type_0) skipped.
- **17 per-paper figure sheets** spliced → `P2/<folder>/_figures.png` (each stacks the figure-bearing PDF pages with a yellow "page N → Q.." label band, for you to crop+colourise). Remaining papers' sheets generated in the next batch.
- **Word-answer FIBs flagged** (need MCQ conversion before publish): ACS BA3 Q10; Tao Nan HA2 Q13/14/18/19/22; Tao Nan HA3 Q16/17/18/19/23; CHIJ T3 Q11. (Mass-ordering, days, durations, container labels.) Inserted as-is (Draft) — listed here for you to fix.
- Mechanism: ingestion runs via background agents (no pauses); only my insert/splice steps need an OS permission tap — batched to minimise interruptions.

## P2 — COMPLETE ✅
- **48/48 P2 papers ingested** (2022 ×7, 2023 ×20, 2024 ×8, 2025 ×13).
- **~945 questions inserted to prod as Draft** (qa_status=ai_generated, status_id=4). ~48 interactive (type_0) skipped. Idempotent — safe to re-run.
- **Figure sheets spliced** for every figure-bearing paper → `P2/<folder>/_figures.png` (PDF pages stacked + yellow "page N → Q.." labels). Ready for you to crop+colourise into the per-question images at `p2/images/<paper-slug>-q<n>.png` (paths already pre-wired in the DB).
- **Word-answer FIBs to convert to MCQ before publishing** (grader is exact-match; flagged, inserted as-is for your review):
  - ACS BA3 Q10(79 kg); SCGS Q4 Q5(cm); Tao Nan HA2 Q13/14/18/19/22; Tao Nan HA3 Q16/17/18/19/23; CHIJ T3 Q11(Z);
  - HP24 Q2 Q6(÷ — actually fine, symbolic); HP24 Q4-5-6 Q12(A); HP25 Q2 Q6(÷); HP25 Q4-5-6 Q12(A)/Q17(13 l)/Q18(15 l);
  - RG24 Q1&2 Q3(tank); RG24 Q1-2-3 Q73(fire engine)/Q77(Building B); RG24 Q4-5-6 Q29(pot);
  - Rosyth25 T2 Q11(2 hours); Rosyth25 T4-2 Q10(7:00 a.m.); Tao Nan25 T2 Q14(Tuna)/Q15(Highlighter).
- Several agents flagged **answer-key typos in the source PDFs** (corrected to the right value + noted in manifests): HP24 Q1 Q7, HP24 Q3 Q11, RG24 Q7-8-9 several, Rosyth25 T1 Q16, etc. Spot-check on review.

## STATUS-ID RULE (per your instruction)
"Draft" lives in the **status** field (`status_id`), not `qa_status` (which is an enum with no 'draft').
Mapping applied: **image-pending → status_id 4 (Draft); image-complete/no-image → status_id 6 ("AGS Tutor Input" = AI-generated, awaiting review)**. Both are unpublished — nothing is public. `qa_status` stays `ai_generated`. When you add a paper's images, flip its image questions from 4→6.
- Loader (`_p3_loader.php`) now sets `status_id = image_needed ? 4 : 6` automatically.
- Applied to P2+P3 so far: **564 rows = Draft(4)** (image-pending), **1126 rows = AI-generated(6)** (no image).
- NOTE: P1 rows were inserted earlier all at status_id 4 — they should get the same 4/6 split (and ACS Test1/Test3, whose images ARE uploaded, → 6). Not yet done; flag.

## P3 — 2022 COMPLETE, rest in progress
- **38 P3 papers ingested = ALL of P3 2022**, **~997 questions inserted** (status_id 4/6 rule applied), figure sheets spliced for every figure-bearing paper (full-page fallback where agents didn't record image_page).
- Remaining: P3 2023 (~26), 2024 (~36), 2025 (~36) — links not yet seeded; fetch + ingest next.
- A few partial papers flagged (e.g. ACS WA2 scan missing pp.5-6 → Q8-11 stems absent). Fraction answers like "[4/10]" tripped the word-FIB flag but are valid FIB (false positives).

## GRAND TOTAL so far tonight: ~4,205 Draft questions across 184 papers — **P2 ×48 COMPLETE (~945)** + **P3 ×136 COMPLETE (~3,260; ALL 2022+2023+2024+2025)**. + ~4,400 linked solutions (source='ai', auto-created at insert). + P1 figure sheets. ~180 figure sheets in Old Images (47 P3 papers tight-cropped via bbox; rest full-page pending box pass). Remaining: **P4 (~143), P5 (~157), P6 (~102)** — multi-night, same pipeline (seed → bbox agents → loader+solutions → _cropfig).

## FIGURES — SOLVED for new papers ✅
- From P3-2024-WA onward, agents record `image_bbox` per figure; `_cropfig.py <LEVEL>` crops **tight figures-only** and writes a labelled per-paper sheet to `Old Images/<LEVEL>.<paper>.png` + per-figure `fig-q<n>.png` in each paper folder. Verified clean (e.g. P3.mb-2024-wa1 = money figure only).
- Spec (`_INGESTION_SPEC.md`) updated so all P4-P6 agents do this automatically.
- BACKFILL NEEDED: P1, P2, and P3 2022/2023/2024-EOY+term papers were ingested before bbox — they still have full-page sheets. A one-off "bbox vision pass" (agent reads PDF, emits image_bbox per figure) + re-run `_cropfig.py` will give them tight crops too.

## SOLUTIONS (per your instruction)
- There IS a linked **`solutions`** table (question_id, solution, source enum('human','ai'), status_id, …).
- **Backfilled 3,924 `solutions` rows** (source='ai', status_id 4 Draft) from each question's worked explanation — every ingested P1/P2/P3 question now has a linked solution. Loader now creates a solution row per new question automatically.
- These come from the agents solving + the papers' answer keys ("most solutions are in the paper"). Free-text worked solutions; review/standardise format as you like.

## FIGURES — important reality check (per your "figures only, not whole pages")
- The sgexam PDFs are **scans: each page = ONE flat raster image**, 0 vector objects, no per-figure coordinates. So there is NO deterministic way to auto-crop *just* the figure (separating figure from question text on a scan needs visual judgment).
- My earlier `_figures.png` sheets were **full PDF pages** — not what you want. **Stopped that.**
- New plan: ingest agents now record each figure's **bounding box** (normalised page fractions) so I crop tight automatically going forward; already-ingested papers need a one-off vision pass to get boxes. The 142 full-page sheets in Old Images are placeholders to be replaced by tight crops.

## MATH FORMAT (per your "use the right format for Flutter")
- FIB blanks: `[?]` is the intended intermediate → ran the sanctioned **`php artisan questions:convert-blanks`** which converted **2,474** of them to front-end `<input class="fill-blank-input" placeholder="?" />` (7,327 already-correct skipped, 0 errors). Now folded into EVERY insert step going forward.
- Fractions: spec updated → write `\(\frac{a}{b}\)` in question/solution TEXT (renders stacked); keep FIB fraction ANSWERS plain `a/b` (so exact-match grading works). Decimals/money plain. Already-ingested plain fractions are functional (gradeable) — a cosmetic LaTeX pass can prettify later.

## IMAGE HANDOFF CONVENTION (per your instruction)
- **`Old Images/`** = my B&W "ugly" spliced sheets, one per paper, named **`<LEVEL>.<paper>.png`** (e.g. `P2.acs-2022-ba3.png`). `_splice.py` writes here automatically now. 63 sheets there so far.
- **`New Images/`** = where YOU drop the colourised versions using the **same filename** — I'll match them back to the paper and upload to `pN/images/<paper-slug>-q<n>.png`, then flip those questions status_id 4→6.
- The same sheet also stays in each paper folder as `_figures.png` (keeps re-splice idempotent).

## 2026-05-29 (late) — P4 DONE; P5/P6 in progress; major corpus fixes
- **P4 COMPLETE** (2022–2025, ~144 paper-folders, 129 figure sheets cropped).
- **P5 in progress**: 152 links seeded; ingested 2022 SA2 ×8 + 2022 WA1 ×8 + 2022 WA2 ×8 (in flight). Loader `_p5_loader.php`, validator `_p5_validate.php`.
- **P6 in progress**: 104 links seeded; 2022 prelims ×8 ingested + cropped. Loader `_p6_loader.php`, validator `_p6_validate.php`. P6 = level_id 7.
- **MATH FORMAT (final, per Pam's Math Input Guide):** inline `\(\dfrac{a}{b}\)` / `\(\sqrt{}\)` / `$x^2$`; `$$...$$` ONLY standalone display. Flutter `QuestionWithInlineAnswers` patched to route FIB-stem text through `renderMathText` (renders inline math in `<input>` FIB stems) — NEEDS APP REDEPLOY. `_frac_inline.php` converts stored `$$frac$$`→inline `\(\dfrac\)` (run after every insert). All `\(`→`$$` over-conversion reverted.
- **DEDUP**: loaders dedup on `source` ALONE (convert-blanks mutates question text, so source+question key was re-inserting). Cleaned 4,393 dup rows earlier.
- **FIB = integers/decimals ONLY**: fraction/mixed/word/ratio/direction/letter/algebra answers → MCQ. Converted 345 fraction-answer FIB→MCQ (`_fib2mcq_*`); ~11 non-numeric FIB still flagged for MCQ (P5: 23758,24134,24208; P6: 24233,24235,24246,24313,24329,24358,24459,24461) + 225 ordering/multi-blank skipped (`_fib2mcq_skipped.json`).
- **FIB MISSING INPUT FIX**: `_fix_fib_noinput.php` — appends `[?]` per answer to FIB with no blank, strips stray `[?]` from MCQ. Fixed 1,551 FIB (corpus-wide) + 467 MCQ. Run after every insert. Spec hardened: every FIB MUST have `[?]` per answer.
- **NEEDS_REVISION "1/2" RECOVERY**: 65 needs_revision had fraction overwritten to `\dfrac{1}{2}` by an old global replace. Dev DB (local) had originals. Restored 48 question texts from dev; 45 advanced needs_revision→`unreviewed` (answer matched dev); 3 held (answer mismatch: 5230,5238,5525). 12 not on dev (AI-generated-from-N), 4 diverged, 1 genuine. Backups on prod `storage/app/recover_backup_*`.
- **PIPELINE per wave**: tar manifests → loader → `_fix_fib_noinput` → `questions:convert-blanks` → `_frac_inline` → chown → validator → `_cropfig.py <LEVEL>`.

## 2026-05-29 — PROGRESS + PAUSED on account session limit
- **P5: 32/152 papers ingested** (2022 SA2+WA1+WA2+WA3 + 3×2023 EOY) = 829 ai_generated Q, render-validated clean.
- **P6: 24/104 papers ingested** (2022 prelims ×8 + 2023 prelims ×16) = 1083 ai_generated Q, render-validated clean.
- **PAUSED**: hit account session/usage limit (resets ~8:30pm Australia/Sydney). Sub-agents rejected; main-thread DB/crop still works. RESUME ingestion agents after reset.
- **Non-numeric FIB to convert→MCQ** (flagged, not yet done — needs an agent distractor pass): P5 ids 23758,24134,24208,25144,25150,25157; P6 ids 24233,24235,24246,24313,24329,24358,24459,24461,24932,24990,25011,25028 (algebra/direction/letter/ratio/time/day answers). Plus 225 ordering/multi-blank in `_fib2mcq_skipped.json`.
- **REMAINING to ingest**: P5 ~120 (2023 rest, 2024 ×39, 2025 ×38); P6 ~80 (2023 WA ×13, 2024 ×28, 2025 ×34). Pipeline per wave unchanged (seed already done: 152 P5 + 104 P6 .url files exist).
- **Next P5 wave**: 2023 EOY remaining (maha-bodhi, maris-stella, nan-hua, nanyang, raffles, red-swastika, rosyth, st-nicholas, tao-nan) then 2023 WA1/WA2/WA3, 2024, 2025.
- **Next P6 wave**: 2023 WA (nan-hua/nanyang/raffles/rosyth/scgs/st-nicholas WA1; acsj/nan-hua/nanyang/raffles/rosyth/st-nicholas/tao-nan WA2) then 2024, 2025.

## 2026-05-29 — IMAGE-OPTION MCQs need answer images (per Pam)
- Image-option MCQs (options are pictures, e.g. #26175 "which shape is 52nd") need `answer0_image..answer3_image` PATHS set (and `question_image` for the stem figure). The loaders did NOT set these.
- `_scan_imgopts.py` finds all `image_options:true` questions in manifests → `_imgopt_questions.json` (177 total: P1 16, P2 17, P3 34, P4 70, P5 12, P6 28).
- `_populate_imgopts.php` (reads that JSON, matches by source) sets `answerK_image = p<L>/images/<slug>-q<n>-opt<k>.png` for each non-null option + `question_image` if image_needed. Run 173/177 so far (4 await their paper's insert). RE-RUN after each new wave so new image-option Qs get wired. Drawings dropped in later (same as question_image).
- These option images + stem figures are part of the figure-crop / New Images colourise workflow (the actual PNGs don't exist yet; paths are wired).

## Next: P4–P6 — multi-night
Proven pipeline now reusable: seed `.url` → agent ingest → idempotent loader (`_p2_loader.php`, just point at P3 + use P3 skill section) → `_splice.py P3`. Starting P3 seeding/ingestion next; it will not finish in one night.

## 2026-05-29 — DEDUP BUG found + fixed (important)
- **Bug:** loaders deduped on `source`+`question`. But `php artisan questions:convert-blanks` rewrites the question (`[?]`→`<input>`) AFTER insert, so on the next loader run the manifest's `[?]` text no longer matched the DB's `<input>` text → every blank-bearing FIB got RE-INSERTED. P4 (re-run ~8× across waves) bore the brunt.
- **Scope:** 4,393 duplicate rows across past papers (1,028 source groups), almost all P4.
- **Fix 1 (code):** `_p4/_p5/_p6_loader.php` now dedup on **`source` alone** (source = "<School> <Year> P<L> <Label> Q<n>" is the unique key). Future re-runs are safe & idempotent regardless of convert-blanks.
- **Fix 2 (data):** scoped cleanup on prod — kept lowest id per source, deleted 4,393 dup questions + 8,807 orphaned hints + 4,393 solutions. Backup of deleted rows: `storage/app/dedupe_backup_20260529_070050.json`. Verified 0 remaining dups (7,257 rows = 7,257 distinct sources). Scope guard: only `user_id=2` + `qa_status=ai_generated` + source matching `P[1-6].* Q[0-9]+$` — legacy generic-source rows (auto:sentinel, diagnostic, AI by skill…) untouched.

## 2026-05-29 — P4 progress
- **P4 2022 ✓ + P4 2023 ✓ (all WA/EOY) + P4 2024 in progress** (16 EOY + 8 WA done = 24 of 46 2024 papers). P4 2025 (35 papers) seeded, not yet ingested.
- 154 P4 `.url` seeded (2022/2023 + 2024/2025). 81 P4 papers tight-cropped into Old Images.
- P5 (`_p5_loader.php`) and P6 (`_p6_loader.php`) loaders written, ready.

## 2026-05-29 — SVG-from-CDN-assets task DEFERRED  → SUPERSEDED 2026-05-30 (see below)
- Pam's SVG task targets `https://math.allgifted.com/chars|objects/...png`, but that host is the **"All Gifted Dashboard" SPA** (Apache) — its client-router returns the same 1236-byte index shell for EVERY path (verified incl. a fake asset name). No PNG asset library exists there yet. Per Pam: assets not built yet → task parked until the CDN library is deployed. Resume ingestion meanwhile.
- **RESOLVED 2026-05-30:** the asset library was built this session (see below). Assets live in-repo at `public/assets/math/{chars,objects}/` (webp + manifests); the SVGs reference `https://math.allgifted.com/{chars,objects}/<id>.webp`, so they render once `public/assets/math` is deployed/served at that host.

## 2026-05-30 — ILLUSTRATION / SVG PIPELINE (this session)
Goal: reproduce past-year question illustrations in AGS house style. All generated via **Higgsfield** (`nano_banana_pro`, `bg-1` style ref) → rembg transparent → WebP <120KB. Pushed to `feat/cascade-stripe-filament-2026-05-23` (7 commits, `e6de81e`..`5ba7351`).

**Asset library** — `public/assets/math/`:
- **chars (90):** cyber-boy mascot "ags math boy" (identity anchor `public/images/houses/P3.png`) at 6 grade levels `boy-p1..p6` (P1 youngest→P6 oldest = age) + 5 poses; rocket-girl = `child-girl` + 5 poses; 8 diverse Singapore kids (Chinese/Malay/Indian/Eurasian × girl/boy) each + 5 poses (wave/point/thumbsup/sit/jump). Girls lime top+navy skirt; boys steel-blue top+ink trousers.
- **objects (68):** animals + everyday/classroom items + 20 generated this session (incl. watermelon, giraffe-toy, water-bottle, juice-can, drink-cup, milk-carton, jam-jar, egg, crayon-box, burger, ice-cream-cone, donut, pail-bucket, glass-bottle, plate-with-cookies, number-train, traffic-cone, street-lamp, classroom-chair, building-house). Manifests: `ags_math_{characters,objects}_manifest.json`.

**New Images SVGs (279)** — `docs/past year questions/New Images/*.svg`: brand-styled (`<style>svg{--crimson…--border:#D0ACAC}</style>` + rounded border), either tiled `<image href=CDN webp>` for objects/people or inline primitives for geometry (faithful shape + recolour). Built from the 470-"convertible" subset of the original 2,778 crops.

**Parametric instrument generators (7)** — `tools/svg_generators/*.py` (pure stdlib, CSS-vars, brand palette) + 26 samples in `tools/samples/`:
- `clock`(hour,minute) · `balance_scale`(left,right) · `platform_scale`(reading,unit,full_scale,major_step,dial="fan"|"circle") · `number_line`(start,end,interval,marked_points) · `beaker`(capacity,reading,unit) · `beakers`(beakers=[…] multi) · `protractor`(angle) + `protractor.generate_rays(rays=[…])` · `ruler`(length_cm,marked_cm).
- Applied to ~50 instrument crops by agents (read value off crop → call generator).

**Conversion workflow (Pam's loop — memory: `feedback_newimages_conversion_workflow`):** parallel general-purpose agents convert a shard of crops; **new object/animal → skip, generate via Higgsfield → objects/, return**; **new instrument → skip, build a generator → tools/svg_generators/, return**; charts/tables/money/text/composite-priced-scenes stay **deferred-by-nature**. Commit per grade.

**QA:** `docs/past year questions/New Images/_gallery.html` — single-page browser gallery of all 279 SVGs + 26 samples (no Cairo on Windows, so SVGs aren't rasterised; open in a browser).

**PAUSED — new-crop run:** Only Images grew 2,778 → **4,632** (Pam bulk-ingesting). Isolated **1,854 genuinely-new crops** (from new papers, via the 307-paper `_extraction_log.txt` baseline; excludes `P1.acs-2022-test1.done` + `P1.acs-2022-test3` per Pam) → pre-sharded in `New Images/_new_shards/` (24 shards). **Waiting for ingestion to settle** before launching the multi-wave conversion; on resume, re-run the isolation step first if more papers arrived, then convert (commit per grade, eager-generate new objects/instruments).

## 2026-05-30 — P5/P6 INGEST COMPLETE ✅ + SVG solution generators
- **All 256 P5/P6 papers ingested:** P5 152/152 + P6 104/104. ~3,041 new questions inserted across three load passes, all as Draft (`qa_status=ai_generated`, `status_id` 4=image-pending or 6=AGS Tutor Input). Loader dedup on `source` alone (idempotent re-runs).
- **Sentence-per-line baked into prod data:** 11,810 questions + 9,797 solutions now have `<br>` after each sentence-ending period (math/decimal/money/abbreviation-safe). Idempotent — once `.<br>` is there, no trailing space, so re-runs don't double-break. Pipeline command stays `_sentence_break.php` as the last step.
- **Flutter sentence-break + bar-model marker** patches written (`lib/utils/math_text_utils.dart` new `renderSolution()` + `breakSentences` param wired into 5 call sites). Needs app redeploy.
- **Raw figure crops live on prod:** 1,837 (P5 824, P6 1,013) uploaded via `_stagecrops.py` → tar → `storage/app/public/p{5,6}/images/`. 1,018 figure questions flipped `ai_generated → unreviewed`.
- **4 corrupted PDFs flagged** for manual answer-check (`qa_status='flagged'`, 130 questions): ACS(P)'24, ACSJ'23, ACSJ'24, CH'24 — all are scrambled concatenations containing "Nan Hua 2022" pages. The crop agents now auto-halt + report on scrambled PDFs (instruction added mid-session). The prod questions+answers were transcribed from the *correct* originals at ingestion (the `fig-q*.png` in those folders are correct ACS figures, proving the right source existed at ingest); only re-cropping + re-verification against the *current* PDF is blocked.
- **Validator residuals (small, deferred):** ~28-50 bare-LaTeX (e.g. `\frac` outside `\(...\)`), ~30-50 non-numeric FIB ("Pistachio", "P, R, Q", "5 200 060" etc.) needing MCQ conversion.

### SVG solution generators (built this session — for Pam's "create Singapore-style solutions" follow-up)
Three new generators at `tools/svg_generators/`:
- **`bar_model.py`** — 4 types: `part_whole`, `comparison`, `multiplication`, `unitary`. Brand CSS-vars throughout. Alternating per-entity colour (crimson + cream / gold + ink), dusty-rose unknown blocks, cream extra blocks. MIN_SEG=40, BH=40, GAP=16.
- **`equation_line.py`** — single equation line, `emphasize=True` for crimson.
- **`solution_block.py`** — instruction → setup bar model → solved bar model → working steps → "Answer: …" composition with brand border + dividers. **Algebra schema embedded in params for the P5–P6 algebra-toggle sprint** (no retrofit): `algebra={"variables":…, "entity_expressions":…, "equations":…}` JSON-serialised onto root `<svg data-algebra=…>`; each step's optional `{"text":…, "algebra":…}` round-trips to `<text data-algebra=…>`; bar_model entity dicts accept an `expression` field (e.g. "6u", "5u + 7").

Samples at `tools/samples/{bar_model_part_whole, bar_model_multiplication, bar_model_comparison, bar_model_unitary_setup, bar_model_unitary_solved, equation_line, solution_block_unitary}.svg`. All render correctly via headless Chrome (the SVGs have no CDN deps).

### Important reality-check on math.allgifted.com
The CDN host returns the **1236-byte SPA index shell for every path** — confirmed by rasterising a CDN-referencing New Images SVG and seeing broken-image placeholders. Earlier "CDN live" claim based on a 200 OK was wrong; assets are in-repo at `public/assets/math/{chars,objects}/` but **not actually served** at that vhost yet. Any New-Images-→-PNG rasterisation pipeline is blocked until that's deployed.

### Pick up next session (Pam's two named tasks + smaller residuals)
1. **Singapore-Math style solutions for P3–P6** — per-paper agents read manifest + worked explanation, emit `solution_block.generate(...)` params + algebra metadata; main thread renders SVG → headless Chrome PNG → `storage/app/public/p{n}/solutions/<slug>-q<n>.png` → attach to `solutions.solution` via existing `[[model:…]]` marker. Carry algebra dict into the stored solution so the future toggle has no retrofit.
2. **Image upload as New Images become available** — other agent (Higgsfield) produces colourised SVGs from the Only Images crops. Watch `New Images/` for new arrivals, rasterise via headless Chrome (only after the chars/objects webp library is genuinely live at math.allgifted.com), upload as `question_image`, flip `status_id` 4→6. Re-runnable sweep.
3. Residual cleanup: bare-LaTeX wrap, non-numeric FIB→MCQ.
4. Resume Workstream B (bar-model PNG) for the remaining ~37 P6 papers + roll down to P5/P4/P3.
5. Drop correct PDFs into the 4 corrupted-paper folders for clean re-cropping + answer re-verification.

