# Sprint: Skill 119 — deterministic 1/2 placeholder substitution (no images)

Branch off master: `audit/skill-119-substitution`. Single commit. **No LLM. No production writes. CSV + UPDATE SQL output only.**

## Background

Skill 119 covers improper ↔ mixed fraction conversion. Questions follow a small set of templates:

- *"Express [VALUE] in its simplest form"*
- *"Convert [VALUE] to a whole or mixed number in its simplest form"*
- *"Change [VALUE] to an improper fraction in its simplest form"*
- *"Express [VALUE] as a mixed number"*
- *"Change [VALUE] into an improper fraction"*

Many rows have "1/2" / "½" / "\dfrac{1}{2}" / "\frac{1}{2}" as a placeholder where [VALUE] should be. The answer set tells us the form of the answer; from that we deterministically compute the value that should have replaced "1/2" in the question.

**Substitution rule:**

- If the marked-correct answer is a **mixed number** (e.g. 2⅓, 1⅞) → the original question asked to convert FROM improper. The placeholder should be the **improper fraction** equivalent of the marked answer.
- If the marked-correct answer is an **improper fraction** (e.g. 15/7, 9/4) → the original question asked to convert FROM mixed. The placeholder should be the **mixed number** equivalent of the marked answer.

Pure arithmetic. No LLM. Deterministic.

## Scope

This sprint handles ONLY:
- `skill_id = 119`
- `qa_status = 'needs_revision'`
- `type_id = 1` (MCQ)
- Question contains 1/2 placeholder patterns
- `question_image IS NULL OR question_image = ''` (no images)
- Question matches one of the conversion templates above (text contains "simplest form" OR "improper fraction" OR "mixed number" OR "whole or mixed")

Out of scope (next sprint):
- Skill 119 rows that are not conversion templates (e.g. "How many tenths", "What is 1/2 of N")
- Image-bearing rows
- Other skills

## Critical safety rules

1. **READ-ONLY against production.** No INSERT, UPDATE, DELETE from CC.
2. **No production writes from this sprint.** Pam runs the generated SQL by hand after review and backup.
3. **CSV + SQL outputs committed to repo for traceability.**

## Autonomy directive

Fully autonomous. No questions. Document decisions in PR description.

## Tasks

### Task 1: Confirm production connection

```sql
SELECT @@hostname AS db_host, DATABASE() AS db_name, NOW() AS query_time;
```

Verify `db_host = 'Math-2025'`. Stop if not.

### Task 2: Pull the working set

```sql
SELECT 
  id, skill_id, difficulty_id, qa_status,
  question, question_image,
  answer0, answer1, answer2, answer3, correct_answer,
  CASE correct_answer 
    WHEN 0 THEN answer0 WHEN 1 THEN answer1 
    WHEN 2 THEN answer2 WHEN 3 THEN answer3 
  END AS correct_answer_text
FROM questions
WHERE type_id = 1
  AND qa_status = 'needs_revision'
  AND skill_id = 119
  AND (question_image IS NULL OR question_image = '')
  AND (
    question LIKE '%1/2%' 
    OR question LIKE '%½%' 
    OR question LIKE '%\\\\dfrac{1}{2}%' 
    OR question LIKE '%\\\\frac{1}{2}%'
  )
  AND (
    question LIKE '%simplest form%'
    OR question LIKE '%improper fraction%'
    OR question LIKE '%mixed number%'
    OR question LIKE '%whole or mixed%'
  )
ORDER BY id;
```

Save to `storage/app/sprint-skill-119/working-set.csv`. Report count. Expected ~13–15 rows based on prior CSV inspection.

If count is below 5 or above 30, note in PR description and proceed.

### Task 3: Parse answer form per row

For each row, parse `correct_answer_text` to determine its form. Two cases.

**Case A: Mixed number.** Patterns to detect (in order):
- `N\frac{a}{b}` or `N\dfrac{a}{b}` inside `$...$` or `$$...$$` (e.g. `2$\frac{1}{3}$`, `1$$\dfrac{7}{8}$$`)
- Unicode form `2½`, `1⅞` (the answer text may also use unicode)
- Plain text `2 1/3` (rare but possible)
- An integer alone (e.g. `3`, `5`) — this is also a "mixed number" answer where the fractional part is 0; treat as such

Extract `whole` (integer part) and `numerator`, `denominator` (fractional part). If integer-only, `numerator=0, denominator=1`.

**Case B: Improper fraction.** Patterns to detect:
- `\frac{N}{M}` or `\dfrac{N}{M}` inside `$...$` or `$$...$$` where N > M (proper would be N < M, but for the "simplest form" template, proper fractions like 1/2 wouldn't appear as an answer here)
- Plain text `N/M`

Extract `numerator`, `denominator`.

**If neither case matches** (e.g. answer is "All of the above", "None of the above"): mark the row as **unparseable**, leave the substitution column blank, and continue. These rows will need human review.

### Task 4: Compute substitution

For each parseable row:

**If marked answer is Mixed (whole + a/b):**
- The original question wanted to convert FROM improper TO mixed.
- The placeholder should be the improper fraction equivalent: `numerator = whole × denominator + a`, denominator unchanged.
- LaTeX form: `$$\dfrac{<numerator>}{<denominator>}$$`
- Example: marked = 2⅓ → substitution = `$$\dfrac{7}{3}$$`

**If marked answer is Improper (N/M, N > M):**
- The original question wanted to convert FROM mixed TO improper.
- The placeholder should be the mixed-number equivalent: `whole = N ÷ M (integer division)`, `remainder = N mod M`.
- LaTeX form: `<whole>$$\dfrac{<remainder>}{<M>}$$` (e.g. `2$$\dfrac{1}{7}$$` for 15/7).
- If the marked answer is itself in simplified form (e.g. 9/4 → 2¼ = 2$$\dfrac{1}{4}$$): use the simplest form. The script should reduce remainder/M by GCD if applicable.
- Example: marked = 15/7 → substitution = `2$$\dfrac{1}{7}$$`

**Format detail:** Match the LaTeX style of the original placeholder. If the original used `$$\dfrac{1}{2}$$`, use `$$\dfrac{...}{...}$$` in the substitution. If the original used plain `1/2`, use `<numerator>/<denominator>` plain text. Preserve consistency.

### Task 5: Build proposed_question

For each parseable row, replace the FIRST occurrence of the 1/2 placeholder in the question text with the computed substitution. Most skill 119 rows have only one placeholder per question; if multiple "1/2" patterns appear, replace only the first (the others may be legitimate "1/2" content like "to make 1/2 of" elsewhere — out of scope for this sprint).

**Replacement targets** (try in this order, first match wins):
1. `$$\dfrac{1}{2}$$`
2. `$$\frac{1}{2}$$`
3. `$\dfrac{1}{2}$`
4. `$\frac{1}{2}$`
5. `\dfrac{1}{2}`
6. `\frac{1}{2}`
7. `½`
8. `1/2`

If none match, log the row as **no-placeholder-found** and continue.

### Task 6: Output proposals CSV

Write `storage/app/sprint-skill-119/proposals.csv` with columns:

```
id, skill_id, difficulty_id,
old_question, marked_answer_text, marked_answer_form, 
substitution_value, proposed_question, 
parsed_ok, placeholder_found, notes
```

Where:
- `marked_answer_form` is `"mixed"` or `"improper"` or `"unparseable"`
- `substitution_value` is the computed replacement (or blank if unparseable)
- `proposed_question` is the new question text (or blank if unparseable/no-placeholder)
- `parsed_ok` is `true`/`false`
- `placeholder_found` is `true`/`false`
- `notes` is any per-row commentary (e.g. "answer was 'All of the above', skipping")

Also write `storage/app/sprint-skill-119/flagged.csv` for rows where `parsed_ok = false` OR `placeholder_found = false`. These need human review.

### Task 7: Generate UPDATE SQL (review only, not executed)

Write `storage/app/sprint-skill-119/proposed-updates.sql`:

```sql
-- Generated: 2026-05-13
-- Sprint: audit/skill-119-substitution
-- Scope: skill_id=119, qa_status=needs_revision, no images, conversion templates only
-- DO NOT RUN WITHOUT BACKUP. Status stays as needs_revision after these updates.

-- BACKUP FIRST:
-- mysqldump --no-create-info --where="id IN (<id list>)" api questions > ~/db-backups/2026-05-13-skill-119-backup.sql

START TRANSACTION;

UPDATE questions SET question = '<proposed_question>', updated_at = NOW() WHERE id = <id>;
UPDATE questions SET question = '<proposed_question>', updated_at = NOW() WHERE id = <id>;
...

-- Verify before commit:
SELECT id, question FROM questions WHERE id IN (<id list>);

-- If verified, COMMIT. If not, ROLLBACK.
COMMIT;
```

**Properly SQL-escape `proposed_question`.** This is critical — the questions contain `\dfrac`, `$$`, backslashes, special characters. Use Laravel's PDO-style escaping or `mysql_real_escape_string`-equivalent. Do NOT manually concatenate strings.

Recommend including each UPDATE with a comment showing the old → new transformation for human review:

```sql
-- Row 3358: "Express \dfrac{1}{2} in its simplest form" 
--       →   "Express \dfrac{7}{3} in its simplest form"
-- (correct_answer = 2⅓ → improper form 7/3)
UPDATE questions SET question = '...' WHERE id = 3358;
```

### Task 8: Write the report

`storage/app/sprint-skill-119/report.md`:

```markdown
# Skill 119 Substitution Proposals — 13 May 2026

## Working set
- Total skill_119 needs_revision rows matching scope: N
- Excluded due to image: N (deferred)
- Excluded as non-conversion template: N (deferred)

## Parse results
- Marked answer was mixed number: N
- Marked answer was improper fraction: N
- Marked answer was integer (whole number): N
- Unparseable answers (All of the above, None of the above): N

## Placeholder match
- Placeholder found and replaced: N
- No placeholder found in question: N

## Files generated
- proposals.csv (N rows): full proposal set with old/new question
- flagged.csv (N rows): rows that need manual review
- proposed-updates.sql: ready-to-run after backup

## Sample transformations (5 high-confidence rows)
| id | marked answer | substitution | new question |
|---|---|---|---|
| ... |

## Next steps
1. Pam reviews proposals.csv
2. Pam reviews proposed-updates.sql
3. Pam takes backup: mysqldump --where="id IN (<ids>)" api questions > backup.sql
4. Pam runs the SQL
5. Status stays as needs_revision for manual verification
```

## Out of scope

- Rows where answer is "All of the above" or "None of the above" (flagged, not proposed)
- Rows with images
- Rows that don't match conversion templates
- Direct DB writes from CC
- LLM calls
- Status changes
- Other skills

## Deliverables

- `storage/app/sprint-skill-119/working-set.csv`
- `storage/app/sprint-skill-119/proposals.csv`
- `storage/app/sprint-skill-119/flagged.csv`
- `storage/app/sprint-skill-119/proposed-updates.sql`
- `storage/app/sprint-skill-119/report.md`
- `app/Console/Commands/ProposeSkill119Substitutions.php` (the artisan command)

All committed for traceability. No production writes occurred during this sprint.

## PR description requirements

- Branch: `audit/skill-119-substitution`
- Title: `Skill 119 — propose 1/2 placeholder substitutions (deterministic)`
- Body:
  - Working set count
  - Parse / placeholder match counts
  - Sample of 5 transformations
  - Sample of any flagged rows
  - Confirmation: no DB writes, no LLM calls, no API spend
