# Sprint: Reconstruct overwritten MCQ question text

Branch off master: `fix/mcq-question-reconstruction`. Two phases — generation (LLM produces CSV) and application (Pam reviews, then SQL applies).

## Background

A bulk operation overwrote the `question` column of an unknown subset of MCQ rows with "1/2" (likely `\dfrac{1}{2}` or similar). The answer options (`answer0`–`answer3`) and `correct_answer` are intact. The task is to reconstruct the original `question` text from the surviving answer options plus context (skill, level, difficulty).

**No backups exist** (verified before this sprint started; if a backup turns up later, restore from it and abandon this work).

**Scope strictly limited to:**
- `qa_status = 'needs_revision'`
- `type_id = 1` (MCQ)
- `question` matches the overwrite pattern

**Untouched:** `answer0`, `answer1`, `answer2`, `answer3`, `correct_answer`, `qa_status`, `status_id`, all other columns.

## Autonomy directive

Work without asking questions on routine decisions. Document choices in the PR description. **Do not write to the database in this sprint.** Phase 2 (DB apply) is a separate, manual step Pam runs after reviewing the CSV.

---

## Phase 1 — Generate reconstruction CSV

### Task 1.1 — Identify the target rows

Run a detection query and save the IDs:

```sql
SELECT 
  id,
  question,
  answer0, answer1, answer2, answer3,
  correct_answer,
  skill_id,
  difficulty_id
FROM questions
WHERE qa_status = 'needs_revision'
  AND type_id = 1
  AND (
    question REGEXP '^[[:space:]]*(1/2|\\\\?dfrac\\{1\\}\\{2\\}|½)[[:space:]]*$'
    OR (question LIKE '%1/2%' AND CHAR_LENGTH(question) < 40)
    OR (question LIKE '%\\\\dfrac{1}{2}%' AND CHAR_LENGTH(question) < 50)
    OR CHAR_LENGTH(question) < 15
  );
```

Save the result as `storage/app/sprint-mcq-recon/targets.csv`. The first column is `id`, second is the current `question` text, rest is reconstruction input.

Count the rows. If > 250, alert Pam before proceeding — scope is wider than expected.

### Task 1.2 — Pull reconstruction context per row

For each target row, also fetch:

- `skill.skill_name` (or whatever the skill name column is)
- The highest-level track containing the skill (per the maxile spec §1 — highest level among tracks containing the skill)
- `level.start_maxile_level`, `level.age` (age tells the LLM what grade-level vocab to use)
- `difficulty.difficulty` (Bloom tier — 1/2/3)

Join via `skill_track` → `tracks` → `levels` and `difficulties`.

Save as `storage/app/sprint-mcq-recon/context.csv` keyed on question `id`.

### Task 1.3 — LLM reconstruction script

Create `app/Console/Commands/ReconstructMcqQuestions.php` (artisan command, NOT a controller — this runs once).

For each target row, call Claude API with this prompt template:

```
You are reconstructing the original text of a Primary-school math question whose 
question text was accidentally overwritten with placeholder "1/2". The answer 
options and correct answer are intact. Your job is to write the question text 
that would naturally lead to these answer options, with the marked option being 
correct.

CONTEXT:
- Skill: {skill_name}
- Grade level: Primary {grade} (age {age})
- Difficulty: tier {difficulty} of 3 (1=knowledge/comprehension, 2=application/analysis, 3=synthesis/evaluation)

ANSWER OPTIONS:
  A. {answer0}
  B. {answer1}
  C. {answer2}
  D. {answer3}

CORRECT ANSWER: {correct_letter} ({correct_value})

OUTPUT REQUIREMENTS:
- Produce a single complete question. Include any context (e.g. "A baker made 15 pies. ½ were sold.") needed to make the answer correct.
- Use LaTeX delimiters \\( ... \\) for inline math and \\[ ... \\] for display math. Do not use single $ delimiters.
- If the question would naturally include an input blank, use <input min="0" type="number" id="qN_blank1" class="lineinput" placeholder="?" />.
- Match the vocabulary and sentence complexity to Primary {grade} students.
- The question must produce the correct answer marked above WITHOUT ambiguity, and the wrong options must be plausible distractors a student might choose.

CONFIDENCE:
After producing the question, append on a new line: "Confidence: high|medium|low"
- high = answer options uniquely constrain the question
- medium = answer options narrow it but multiple original phrasings possible
- low = answer options are too generic to recover the original; this is a plausible reconstruction only

Return JSON: { "question": "...", "confidence": "high|medium|low", "reasoning": "..." }
```

Use the `anthropic` Python or PHP SDK with model `claude-haiku-4-5-20251001` for cost. Set `max_tokens: 800`, temperature `0` for determinism.

Loop with simple rate limiting (1 req/sec). On error, log and continue — don't fail the whole batch. Estimated cost at 203 rows: under $0.50.

### Task 1.4 — Stage to CSV

Write output to `storage/app/sprint-mcq-recon/reconstructed.csv` with columns:

```
id, original_question, reconstructed_question, confidence, reasoning, 
skill_name, grade, difficulty, answer0, answer1, answer2, answer3, correct_answer
```

Also produce a summary `storage/app/sprint-mcq-recon/summary.md`:

- Total rows processed
- Distribution by confidence (high/medium/low counts)
- Distribution by skill (which skills have the most reconstructions)
- Rows where the API errored
- Sample of 5 high-confidence reconstructions
- Sample of 5 low-confidence reconstructions

### Task 1.5 — Sanity checks on the CSV

Run a verification pass on `reconstructed.csv` before handing to Pam:

1. **Every row has a non-empty `reconstructed_question`** — flag any blank rows
2. **No reconstructed question contains "1/2" alone** — flag if the LLM gave up and echoed the placeholder
3. **LaTeX delimiters are matched** — odd count of `\(` vs `\)` or `\[` vs `\]` → flag
4. **`reconstructed_question` length is between 15 and 500 characters** — flag outliers
5. **No reconstructed question mentions the answer letter explicitly** ("The answer is A") — flag

Flagged rows go to `storage/app/sprint-mcq-recon/flagged.csv` with the reason. These need human attention.

---

## Phase 2 — Apply (manual, separate session)

**Not run by CC. This is a Pam step.** Document the workflow in the PR description:

### Step 2.1 — Review the CSV

Pam opens `reconstructed.csv` in a spreadsheet, sorts by `confidence`, and reviews. For each row, mark a column `approved` as `1` (apply), `0` (skip — keep original placeholder), or `edit` (Pam writes a better version in `final_question` column).

Suggested review strategy:
- High-confidence: spot-check ~10%, then bulk-approve
- Medium-confidence: review all
- Low-confidence: probably skip (keep `needs_revision` with placeholder, return to manual review later)
- Flagged rows: review individually

### Step 2.2 — Generate the UPDATE SQL

CC writes a second artisan command, `ApplyReconstructedMcqQuestions.php`, that reads the reviewed CSV (only rows where `approved` is `1` or `edit`) and produces:

```sql
-- File: storage/app/sprint-mcq-recon/apply.sql
UPDATE questions SET question = '...' WHERE id = N;
UPDATE questions SET question = '...' WHERE id = M;
...
```

One statement per row. SQL-escaped properly (handle quotes, backslashes, newlines). Don't run inside Laravel — produce raw SQL Pam runs against production manually after backup.

### Step 2.3 — Backup, then apply

Pam:
1. Takes a fresh `mysqldump` of `questions` table (so we can roll back the new operation if it goes wrong)
2. Runs `apply.sql` against production
3. Verifies row counts match expectations
4. Status stays as `needs_revision` — that's intentional, these still need human approval before going to `approved`

---

## Out of scope

- DB writes from CC (Phase 2 is manual)
- Changing `qa_status` (stays `needs_revision`)
- Changing answer options or `correct_answer` (untouched)
- Other question types (Number, FIB, DAD, MS, Essay — separate sprints if needed)
- Backfilling questions where the answers themselves are garbage (different problem)
- Fixing the upstream cause of the overwrite (root-cause analysis is a separate concern; this sprint is forensic restoration)

## PR description requirements

- Branch name: `fix/mcq-question-reconstruction`
- Title: `Reconstruct overwritten MCQ question text from answer options`
- Body:
  - Target count from Task 1.1
  - Cost estimate and actual API spend
  - Confidence distribution from `summary.md`
  - Sample reconstructions (5 high, 5 medium, 5 low)
  - Count of flagged rows and reasons
  - Phase 2 workflow documented for Pam
  - Confirmation no DB writes occurred

## Files delivered

- `app/Console/Commands/ReconstructMcqQuestions.php`
- `app/Console/Commands/ApplyReconstructedMcqQuestions.php` (Phase 2 helper)
- `storage/app/sprint-mcq-recon/targets.csv`
- `storage/app/sprint-mcq-recon/context.csv`
- `storage/app/sprint-mcq-recon/reconstructed.csv`
- `storage/app/sprint-mcq-recon/summary.md`
- `storage/app/sprint-mcq-recon/flagged.csv`
- `storage/app/sprint-mcq-recon/apply.sql` (generated in Phase 2)

`storage/app/sprint-mcq-recon/` should be gitignored — these are working files, not durable artefacts.
