# Past-paper ingestion spec (P1)

You are processing **one** Singapore past-paper `.url` shortcut into a structured
manifest + figure images. **Do NOT touch the database. Do NOT SSH. Do NOT run git.**
You only: download, read, and write local files (manifest + images).

## Pipeline

1. **Resolve the shortcut.** Read the given `.url` file (INI format). Take the
   `URL=` line → it points to an sgexam.com page.
2. **Find the PDF.** WebFetch that page and extract the Google Drive download
   link (looks like `https://drive.google.com/uc?export=download&id=<ID>`).
3. **Download the PDF:**
   `curl -sL "<drive-link>" -o "<outdir>/paper.pdf"`
   Verify it starts with `%PDF` (`head -c 5`). If you instead got an HTML page
   (Drive virus-scan interstitial), find the `confirm=<token>` and retry with
   `&confirm=<token>`. If still not a PDF, write `manifest.json` with
   `{"error":"download_failed","url":"..."}` and stop.
4. **Read the PDF** with the Read tool (it renders pages — you can see figures,
   clocks, coins, counts). Identify every question and the **answer key**
   (usually the last page(s)).
5. **Write** `<outdir>/manifest.json` (schema below). Leave `paper.pdf` in the
   folder. **Do NOT run python / crop images** — that step is blocked for you
   and is handled downstream. Instead, for each figure-bearing question record
   `image_needed`, the PDF `image_page` (1-based), and a short `image_loc`
   (e.g. "middle of page, below the question") so the figure can be cropped
   later.

## Manifest schema

```json
{
  "paper": {"school":"Catholic High","year":2022,"level":"P1","label":"CP5",
            "source_prefix":"Catholic High 2022 P1 CP5","has_answer_key":true},
  "questions": [
    {
      "n": 1,
      "type_id": 1,                       // 1 = MCQ, 2 = fill-in-blank, 0 = other/non-standard
      "question": "verbatim stem",        // see rules below
      "answer0": "...", "answer1": "...", "answer2": "...", "answer3": null,
      "correct_answer": 3,                // MCQ: 0-based index. FIB: null.
      "skill_id": 24,
      "difficulty_id": 2,                 // 1 easy / 2 medium / 3 hard
      "explanation": "short worked solution",
      "hints": ["hint 1", "hint 2"],
      "source": "Catholic High 2022 P1 CP5 Q1",
      "image_needed": true,               // does the question require a figure?
      "image_options": false,             // true if the 4 OPTIONS are pictures (not text)
      "image_file": "q1.png",             // figure filename in this folder, or null
      "notes": "needs clock image"        // ambiguities, no-key-solved, count-to-verify, etc.
    }
  ]
}
```

## Rules (non-negotiable)

- **Verbatim stem.** Copy the question exactly as printed. Do NOT add framing
  ("What is the sum of…?"). Do NOT keep directional words — strip "below",
  "above", "as shown in the picture below", "the cards below", etc. (the app
  places images anywhere).
- **Fill-in blanks:** put `[?]` at each blank, in order; `answer0..N` hold the
  blank answers; `correct_answer` = null. Multi-blank → multiple `[?]`.
- **MCQ:** `answer0..3` = the four option texts; `correct_answer` = 0-based index.
- **Multiplication facts** like "8 × 3 =" → render as `[?] × [?] = [?]` with
  answer0/1/2 = operand, operand, product (type_id 2).
- **Word answers** (e.g. "write 93 in words") → make it MCQ with 4 word options;
  text fill-ins grade by exact match and are fragile.
- **Image-option MCQs** (pick the right clock / coin-set / shape): set
  `image_options: true`, put a short text label in answer0..3 (e.g.
  "Clock A"), and crop each option image to `q<n>_opt0.png … q<n>_opt3.png`.
- **Convert what fits to FIB:** an *ordering* task ("arrange these numbers
  smallest→largest") becomes a FIB with one `[?]` per position (answer0..N in
  order); a *number-bond / write-the-equation* task becomes a FIB. Prefer FIB
  over type_id 0 whenever the answer is a definite value or sequence.
- **Genuinely interactive formats** (circle, colour, draw, cross-out,
  match-by-drawing) have no app question type — set `type_id: 0`, put the
  answer in `notes`. These are SKIPPED on insert (not served), so only use
  type_id 0 when the task truly can't be a value/sequence answer.
- **Answer key:** if the PDF has one, USE it (cross-check every answer). If it
  has none, solve carefully and add `"notes":"no answer key — solved"`.
- **Counts ARE answers.** For "how many" picture questions, state the required
  count in `notes` and make sure your crop shows the whole figure.
- **Source** = `"<School> <Year> P1 <Label> Q<n>"`. Derive school/year/label
  from the filename (e.g. "Check point 5" → "CP5", "Topical Quiz 1 and 2" →
  "Topical Quiz 1-2", "Mini Test 4" → "Mini Test 4").

## Images  (UPDATED — agents do NOT crop; they record the box)

For EVERY figure-bearing question (image_needed=true, type_id != 0) you MUST set:
- `image_page` : 1-based integer page the figure is on.
- `image_bbox` : `[x0,y0,x1,y1]` as FRACTIONS 0..1 of that page, drawn TIGHTLY
  around the figure/diagram ONLY — exclude the question text, headers, and the
  `www.sgexam.com` watermark. (The downstream cropper renders the page and clips
  to this box, so accuracy here = a clean figure-only image.)
- `image_loc` : short human description (fallback).
Do NOT run python or crop image files yourself.

## Math formatting (for Flutter front-end)

- **FIB answer blanks:** write `[?]` per REAL answer blank (a spot the student
  types into). Do NOT hand-write `<input>` tags — just use `[?]`. For a
  display-only blank that is NOT an answer-entry point (e.g. a shown fill line),
  use literal underscores `____________`, NOT `[?]`.
- **EVERY FIB (type_id 2) question MUST contain a `[?]` for EACH answer**
  (answer0..N): blank-count MUST equal answer-count, matched in order. NEVER emit
  a FIB question with no `[?]` (a question with an answer but no blank renders with
  no input box — the student cannot answer). For multi-part (a)/(b), put a `[?]`
  after each part. MCQ (type_id 1) stems must contain NO `[?]`.
- **Math delimiters (per the app's Math Input Guide):**
  - **Inline** (math sitting INSIDE a sentence — the common case): use `$...$`
    or `\(...\)`. Inline fractions: `\(\dfrac{1}{n}\)`; square root:
    `\(\sqrt{a^2+b^2}\)`; power: `$x^2$`. Inline keeps them small/in-line.
  - **Display** (a standalone formula on its own line — use sparingly): `$$...$$`,
    e.g. `$$\frac{a}{b}$$`.
  - DEFAULT fractions/roots/powers inside question or option text to INLINE
    (`\(\dfrac{a}{b}\)`) so they don't render as oversized blocks. Reserve `$$...$$`
    for a formula deliberately shown on its own line.
  - Exponents may also use a unicode `²`/`³` (renders fine as plain text).
- **FIB answers = INTEGERS or DECIMALS ONLY.** If the answer is a fraction
  (`a/b`) or a mixed number (`6 3/7`), the question MUST be an **MCQ** (type_id 1)
  with 4 options — NEVER FIB. Reason: the FIB input is numeric exact-match;
  fraction/mixed formats are ambiguous to type and grade. Whole numbers, decimals,
  money, counts, times → FIB is fine. For such MCQs, render the fraction options
  inline as `\(\dfrac{a}{b}\)` (mixed `\(6\dfrac{3}{7}\)`); `correct_answer` = 0-based index.
- Decimals/money plain (`0.302`, `$5.20`). Use `<br>` for hard line breaks.

## Solutions

Write a clear worked `explanation` for EVERY question (this becomes the linked
solution row). The paper's printed answer key / worked solutions ARE the source
of truth — they are almost always in the question sheet (usually the last
page(s)). Transcribe the paper's own working and final answer; do NOT invent or
guess. Cross-check that EVERY `correct_answer` / FIB answer matches the printed
key exactly; if the printed key has an obvious typo, use the mathematically
correct value and flag it in `notes`. Format: method → steps → final answer.

## Level / skill mapping (P2–P6)

Use `_p2_p6_skillmap.txt`, ONLY the section for this paper's level
(P2=level_id 3 … P6=level_id 7). Pick the closest skill_id by topic.

## P1 skill_id reference (pick the closest by topic)

Numbers to 100: 1 Counting · 2 Number notation · 3 Reading/writing numbers ·
4 Comparing · 5 Ordering · 6 Patterns · 7 Ordinal numbers · 8 Number bonds
Addition/Subtraction to 100: 9 Concepts · 11 Addition & subtraction ·
13 Add/sub within 100 (default for computation) · 14 Algorithms ·
15 1-step word problems (add/sub within 100)
Multiplication/Division: 16 Concepts · 18 Multiplying within 40 ·
19 Dividing within 20 · 20 1-step word problems (mult/div)
Money Year 1: 21 Counting amount of money · 22 Money 1-step word problems
Length Year 1: 23 measuring/comparing length
Time Year 1: 24 telling time to hour/half hour
2D Shapes: 26 patterns with 2D shapes
Picture Graphs: 1 Counting (reuse)

Mapping: bare computation → 13; add/sub word problems → 15; number sense
(place value / "x more/less" / tens-ones) → 2 or 13; ordering → 5; patterns →
6; counting objects → 1; money count → 21; money word problem → 22; time/clock
→ 24; shapes → 26; mult facts → 18 (or 16 for concept). When unsure, pick the
nearest and note it.

## qa_status / status

You do NOT set these — the inserter applies `qa_status='ai_generated'`,
`status_id=4` (Draft) to every question. Just produce an accurate manifest.
