# Solution Authoring — Canonical Style Spec (READ BEFORE WRITING ANY SOLUTION)

The single source of truth for how AllGifted (AGS) math **solutions, hints, bar models, and
area/volume/speed diagrams** are built. Every future answer MUST be constructed the same way —
same structure, formulas, colours, fonts, and pipeline. Pairs with `bar-models.md` (bar visual
spec), `bar-model-pipeline.md` (rendering/deploy ops), and `../past year questions/_FIGURE_STYLE.md`
(palette). Audience: Singapore primary (P1–P6) elementary students — solutions must be EASY.

## 1. Method policy (what kind of solution)
- **Bar model / part-whole / number-bond reasoning is the DEFAULT** where the problem is
  part-whole, comparison, ratio, fraction, or before/after (Singapore math).
- **Standard formula** for direct computation (area, volume/capacity, speed/rate): use the formula
  method — do NOT force a bar model.
- **Direct/definition** for pure facts (e.g. square/cube roots).
- **NEVER use algebra below Primary 6.** No variables/equations under P6. P6 only: algebra allowed
  as a *secondary* "Alternative method (algebra)" AFTER the model, and only when a model is harder.
- Always **solve and verify the answer yourself**; provide the answer you believe correct. Do not
  trust the stored key. End every solution with a line: `Final Answer: <value>`.

## 2. THE CANONICAL FORMAT — AGS HTML dialect + PNG diagrams (LOCKED, Pam-approved 2026-06-11)
Supersedes the all-in-one-PNG approach. A solution body is **HTML text + embedded PNG diagrams**.
Every formula solution uses exactly this structure, in this order (approved pilot: **q6643 v5** —
copy it as the template):

```html
<div class="ags-formula-box" style="position:relative;background:#FAF5EE;border:3px solid #960000;border-radius:12px;padding:12px 16px 20px 16px;text-align:center;margin:8px 0;line-height:1.5"><b>Base Area = Length &times; Width<br>Height = \(\dfrac{\text{Volume}}{\text{Base Area}}\)</b><span style="position:absolute;bottom:3px;right:10px;font-size:10px;color:#960000;font-weight:600;line-height:1">AGS Formula Box</span></div>
<img class="ags-model" src="/media/questions/solutions/q<id>_d.png" style="max-width:420px;display:block;margin:10px auto" alt="diagram">
<table class="ags-working" style="border-collapse:collapse;line-height:1.5">
<tr><td style="padding:6px 8px">LHS</td><td style="padding:6px 8px">=</td><td style="padding:6px 8px">formula = substitution = result UNIT</td></tr>
<tr><td colspan="3" style="padding:6px 8px">connective sentence…</td></tr>
</table>
<div class="ags-answer" style="color:#960000;font-weight:800;margin-top:10px;line-height:1.5"><b>Final Answer: VALUE</b></div>
```

1. **AGS FORMULA BOX** — first, always. Cream `#FAF5EE` fill, **crimson `#960000` 3px border**,
   rounded 12px, **bold** centred text, `line-height:1.5`, plus the footer label
   **"AGS Formula Box"** (10px crimson, bottom-right). Contains the formula(s) used in THIS
   solution. Speed: box text `Speed = Distance ÷ Time`; the D-S-T triangle PNG goes in the
   diagram slot.
2. **Diagram `<img>`** — transparent PNG, Raleway labels: bar model / cuboid with fill+brackets /
   D-S-T triangle. Render DIAGRAM-ONLY PNGs named `q<id>_d.png` (generators §5/§6).
3. **Working table** — 3 columns `lhs | = | rhs` (equals align by construction). Every eq row:
   `quantity = formula = substitution = result`, UNITS on every line. Connective sentences =
   `colspan="3"` rows.
4. **Answer div** — crimson bold `Final Answer: …`.

**Math/encoding rules (each one is a fixed bug — do not regress):**
- Fractions **inline `\(...\)` ONLY**: `\(\dfrac{\text{Volume}}{\text{Base Area}}\)` for word/large
  fractions, `\(\frac{2}{5}\)` for small numeric ones. **NEVER `$$..$$` inside a working row**
  (display mode splits the line — the "= everywhere" bug).
- **Author via NOWDOC / JSON files, never double-quoted PHP strings** (`\text` became TAB+`ext{`
  on 2026-06-11; always verify stored bytes contain 0 tabs).
- Symbols: `&times; &divide; &sup2; &sup3;` or literal UTF-8 `× ÷ ² ³` — both render everywhere.
- KaTeX font/size/weight consistency comes from blade CSS on `admin/qa/show.blade.php` and
  `admin/questions/show.blade.php`: `.katex { font-family/weight: inherit !important; font-size:
  1em !important }` (KaTeX default is 1.21em — that was the "math looks bigger" issue).
- **1.5 line spacing everywhere** (inline `line-height:1.5` + blade CSS).

**Renderers:** the QA review blade auto-detects the dialect (`ags-*` / `<img>`) and outputs raw;
legacy text+`[[model]]` solutions keep the escaped path. The app renders the dialect natively via
`MathTextUtils.renderSolution` (flutter_demo): formula-box widget + footer, `Image.network`,
aligned working table, entity decode, height 1.5. **Rebuild + deploy the app** after renderer edits.

Standard formulas (box content): Volume = Base Area × Height; Base Area = Length × Width;
Base Area = Volume/Height; Height = Volume/Base Area; Speed = Distance ÷ Time; Average =
Total ÷ Number; Total = Average × Number; Amount each day = Total ÷ Number of days;
Original = New ÷ (100% ± change%); Fraction = part/whole.

**Formula triangles (shared PNGs, AGS lime/steel/gold tints) — sit INSIDE the AGS Formula Box,
side-by-side NEXT TO the formula text** (the formula is derived from the triangle; it is the thing
to remember). Box inner = flex row: `<img …triangle… max-width:170px>` + `<b>formula</b>`.
Near-equilateral shape (base 320 × height 250), generous padding around the words (`tri2.php`).
- `ags_dst_triangle_2.png` — Distance over Speed × Time → ALL speed/distance/time questions.
- `ags_tan_triangle_2.png` — Total over Average × Number → ALL average questions.
(v1 unversioned names are dead — /media is immutable, bump the version suffix on any reshape.)
Changed-average problems (e.g. q6813 "two students join, average drops") additionally get a
**balance bar model** (`q<id>_d.png`): each new score drawn short of the dashed new-average line,
shortfalls as dashed derived segments; solve by surplus-covers-shortfall (NO algebra below P6).

## 3. Non-formula solutions
- Plain text body with `<br>` line breaks (the app breaks on `<br>`, NOT `\n`), inline `\(..\)`
  for any math, end with `Final Answer: <v>`. Bar-model word problems embed the bar-model PNG via
  `<img>` (new) or `[[model:...]]` (legacy — still supported).
- **Hints are ASCII-ONLY** (feedback_hints_ascii_only) — no `× ÷ − → • ✓`; use `-`, `x`, `/`, `->`.
- Units: 1 litre = 1000 cm^3 (= 0.001 m^3). Volume of cuboid = length x width x height.

## 4. Hints (separate `hints` table)
- 2–4 staged hints. **For a formula question, hint 1 STATES THE FORMULA** (ASCII, e.g.
  "Use the formula: Speed = Distance / Time." / "Volume = Base Area x Height."). For a model
  question, hint 1 is "Draw the bar model: ...". Each later hint adds ONE working step. **No hint
  before the last may reveal the final answer.**
- ASCII only. Stored in the `hints` table, UNIQUE `(question_id, hint_level)`, our rows `user_id=2`;
  **preserve human hints `user_id=41`** (delete only `user_id<>41` before reinserting ours).

## 5. Diagrams are MANDATORY for measurement Qs (feedback_solution_diagrams_labelled)
Area, volume/capacity, speed/rate solutions MUST embed a drawn figure with **every dimension
labelled in the diagram**. Embedded as a PNG via `[[model:questions/solutions/q<id>.png]]`.

The diagram is composed INTO the combined figure (§2) by `solution_fig.php`; `cuboid.php` provides
the box drawing + the transparent rasteriser.

### 5a. Cuboid / tank generator — `_work_barmodels/cuboid.php`
- Cabinet projection box. **Length and breadth (width) are labelled together on the BASE plane**
  (length on the front-bottom edge, width on the bottom-right receding edge) so the base area reads
  as one rectangle. **Height** on the front-left vertical edge.
- **Unknown dimension = `?`** — never the answer (feedback_figure_no_answer_reveal).
- Colours (AGS, §7): faces are **steel `#4A6488`** with varied `fill-opacity` for depth
  (top 0.18, front 0.34, right 0.50); **outlines + known labels = ink `#2A2A2A`**; the unknown
  `?` label is **crimson `#960000`** to flag what to find. Hidden edges dashed (`5 4`).
- Call `cuboid_svg($lenL, $widL, $hgtL)` (labels are strings, e.g. `'25 cm'` or `'?'`), then
  `bm_rasterize($svg, storage_path("app/public/questions/solutions/q<id>.png"))`.
- "Find base area" Qs: label height (known), set length & width to `?`. "Find height" Qs: label
  length & width (known), height `?`.
- **Liquid fill:** pass `$opts['fill']` (0..1) to shade the liquid light blue (`#6FB1E4`) with a
  dashed water line at the fill level. **Bracket the filled height** with `$opts['fillLabel']` (a
  `vdim` bracket on the LEFT). When a fill is shown, the full-height label moves to a bracket on the
  RIGHT. Unknown bracket value = `?` in crimson. (e.g. 6643 fill 0.4 labelled `2/5`, full height `?`.)

## 6. Bar models — `_work_barmodels/lib.php`
- Primitives: `seg()` solid quantity, `gapseg()` dashed difference/gap, `vbrace/hbrace/tbrace`
  total/difference brackets, `nameLbl()`. Build SVG → `bm_html(intro,$svg,[steps],answer,check,
  algebra?)` → `bm_insert($S)` rasterises PNG + stores body + staged hints.
- Given = plain label; **derived (worked-out) = italic + steel dotted underline** (`$derived=true`).
  Solid = quantity, dashed = gap. SVG labels use `sfrac()` (`a/b`) — SVG can't render LaTeX.

## 7. Colours — AGS palette ONLY (_FIGURE_STYLE.md)
| Name | Hex | Use |
|------|-----|-----|
| lime | `#88C808` | signature green — bars, fills, highlights |
| gold | `#BF9237` | warm accent |
| crimson | `#960000` | strong accent / the unknown "?" |
| steel | `#4A6488` | cool blue fill for solids/prisms (cuboids/tanks) |
| cream | `#FAF5EE` | soft background |
| ink | `#2A2A2A` | ALL outlines and text |
| border | `#D0ACAC` | soft table/cell borders |
Outlines/text are always **ink**. Use colour when it helps comprehension; vary it; never default to
grey by reflex. Match the scan when the source dictates a colour.

## 8. Font
**Raleway for EVERYTHING** — formula box, diagram labels, working, answer. `cuboid_rasterize`
(in `cuboid.php`, used by `solution_fig.php`) injects `_work_barmodels/fonts/Raleway.ttf` via an
`@font-face` and applies `svg text{font-family:'Raleway'}`; all `<text>` inherits it. Do NOT use
KaTeX (its math font cannot be Raleway on the app) — render formulas as part of the Raleway image.

## 9. Rendering & deploy pipeline
- PNGs rasterised by **headless Chrome** (`bm_rasterize`, Windows — no Cairo) at 2x, GD auto-trim +
  white margin. Live at `storage/app/public/questions/solutions/q<id>.png`, served `/media/...`.
- `/media` is **immutable-cached** — a same-name overwrite won't show. Admin `<img>` append
  `?t=time()`; for the app, version the filename if a diagram changes.
- Solution stored in `solutions.solution`; served only when `status_id=3` AND (`source='human'` OR
  `human_checked_at`) AND the question is public (`questions.status_id=3`).
- **Deploy = targeted scp of PNGs + direct DB writes via `php artisan tinker /tmp/<script>.php`**
  (prod tracks a feature branch behind HEAD; never `git pull` single fixes). SSH prod via IP
  `152.42.223.228` (never hostname); network is flaky — retry scp/ssh. **No concurrent SSH**
  (fail2ban). After artisan-as-root: `chown -R www-data:www-data storage bootstrap/cache`.

## 10. QA flow
Audit dimensions: question_text, answer (solve+verify), hints, solution (+ read the model PNG with
vision). Update solution+hints first, provide the right answer, THEN set `qa_status='needs_revision'`
with a `qa_notes` reason for anything a human must still decide. **Do NOT auto-flip answer keys** —
surface key changes / broken-option questions / out-of-syllabus items to Pam in a TABLE for approval.
