# S01: Synthesis Prompt v5 — Nested Sections + Citations

**Goal:** Stage 5 synthesis prompt produces body_sections as a list of BodySection objects with H2/H3 nesting and inline [N] citation markers, validated by schema and test harness.
**Demo:** After this: Run test harness with new prompt → output has list-of-objects body_sections with H2/H3 nesting, citation markers on key claims, broader page scope.

## Tasks
- [x] **T01: Added BodySection/BodySubSection schema models, changed SynthesizedPage.body_sections to list[BodySection] with v2 format tag, and created citation_utils with 15 passing tests** — Add BodySubSection and BodySection Pydantic models to schemas.py. Change SynthesizedPage.body_sections from dict to list[BodySection]. Add body_sections_format field. Create a small citation validation utility (validate_citations) that checks [N] markers in section content against a moment count. This utility is reused by the test harness in T03.

## Steps

1. Read `backend/pipeline/schemas.py` fully.
2. Add `BodySubSection` model with `heading: str` and `content: str` fields.
3. Add `BodySection` model with `heading: str`, `content: str`, and `subsections: list[BodySubSection]` (default_factory=list).
4. Change `SynthesizedPage.body_sections` type from `dict` to `list[BodySection]` with updated description.
5. Add `body_sections_format: str = Field(default='v2')` to `SynthesizedPage`.
6. Create `backend/pipeline/citation_utils.py` with:
   - `extract_citations(text: str) -> list[int]` — regex extracts all `[N]` and `[N,M]` markers, returns sorted unique indices.
   - `validate_citations(sections: list[BodySection], moment_count: int) -> dict` — returns `{valid: bool, total_citations: int, invalid_indices: list[int], uncited_moments: list[int], coverage_pct: float}`.
7. Add unit tests in `backend/pipeline/test_citation_utils.py` covering: valid citations, out-of-range indices, multi-citation markers `[0,2]`, no citations, empty sections.

## Must-Haves

- [ ] BodySubSection and BodySection models exist with correct field types
- [ ] SynthesizedPage.body_sections is list[BodySection]
- [ ] body_sections_format field defaults to 'v2'
- [ ] extract_citations parses [N] and [N,M] markers correctly
- [ ] validate_citations returns coverage stats and flags invalid indices
- [ ] Unit tests pass
  - Estimate: 30m
  - Files: backend/pipeline/schemas.py, backend/pipeline/citation_utils.py, backend/pipeline/test_citation_utils.py
  - Verify: cd /home/aux/projects/content-to-kb-automator && python -m pytest backend/pipeline/test_citation_utils.py -v
- [x] **T02: Rewrote stage5_synthesis.txt with v2 body_sections (list-of-objects with heading/content/subsections), citation rules ([N] and [N,M] format), body_sections_format tag, and H3 subsection guidance** — Rewrite the output format section of `prompts/stage5_synthesis.txt` to produce the new list-of-objects body_sections structure with inline [N] citation markers. Preserve all voice, tone, synthesis philosophy, and body section naming guidance. Back up the current prompt before editing.

## Steps

1. Back up: `cp prompts/stage5_synthesis.txt prompts/stage5_synthesis.$(date +%Y%m%d_%H%M%S).bak`
2. Read the full prompt file.
3. Update the `## Body sections structure` section (line ~46): add a paragraph about H3 subsections. Sections can contain 2-3 subsections when a topic has distinct sub-aspects. Subsections are optional — flat sections (no subsections) are fine when the topic doesn't warrant splitting.
4. Add a new `## Citation rules` section after `## Body sections structure`:
   - `[N]` after specific factual claims (settings, values, frequencies, routing decisions) where N is the zero-indexed moment from the input.
   - `[N,M]` when a claim draws from multiple moments.
   - Place citation at end of sentence before period: "Set the LFO to 3/16th timing [2]."
   - Every section must have at least one citation.
   - Citations only on technical/factual claims, not general observations.
   - A moment can be cited multiple times.
5. Rewrite `## Output format` section (line ~163): replace the JSON example with new structure using `body_sections` as a list of objects with heading/content/subsections. Include citation markers in the example prose. Add `body_sections_format: "v2"` field.
6. Update `## Field rules` section (line ~199): change body_sections description from "Dict of section_name → prose" to "List of section objects with heading, content, and optional subsections. Each section's content contains inline [N] citation markers."
7. Verify: the prompt file is valid text, contains the new JSON example structure, has citation rules, and all other sections (voice, tone, plugin rules, etc.) are unchanged.

## Must-Haves

- [ ] Current prompt backed up before changes
- [ ] body_sections in JSON example is a list of objects with heading/content/subsections
- [ ] Citation rules section exists with [N] and [N,M] format
- [ ] body_sections_format: "v2" in JSON example
- [ ] All non-output-format sections preserved unchanged
- [ ] Subsections shown as optional (empty array or omitted in some example sections)
  - Estimate: 45m
  - Files: prompts/stage5_synthesis.txt
  - Verify: cd /home/aux/projects/content-to-kb-automator && python -c "import json; t=open('prompts/stage5_synthesis.txt').read(); assert 'body_sections_format' in t; assert 'Citation rules' in t or 'citation rules' in t.lower(); assert '\"subsections\"' in t; print('Prompt v5 structure OK')"
- [x] **T03: Updated test_harness.py word-count/section-count logic for list[BodySection] v2 format and added per-page citation coverage reporting with 13 new tests passing** — Update the test harness word-count and section-count logic to walk the new list-of-objects body_sections structure instead of dict.values(). Add citation coverage reporting using the citation_utils from T01. This closes the slice — the harness can parse and report on v2 synthesis output.

## Steps

1. Read `backend/pipeline/test_harness.py` fully.
2. Read `backend/pipeline/schemas.py` (updated in T01) and `backend/pipeline/citation_utils.py` (created in T01).
3. Update line ~283-289 (per-page summary in `run_synthesis`): replace `sections.values()` iteration with walking `BodySection` objects. Word count = sum of `section.content` words + sum of all `subsection.content` words. Section count = len(body_sections). Report subsection count too.
4. Update line ~457 (metadata total_words): same dict→list-of-objects change for the output metadata calculation.
5. Add citation reporting after per-page summary: import `validate_citations` from citation_utils, call it with `page.body_sections` and `len(page.moment_indices)`, log citation coverage % and any invalid indices.
6. Create a small test script `backend/pipeline/test_harness_v2_format.py` that:
   - Constructs a mock SynthesisResult with the new body_sections format (list of BodySection objects)
   - Verifies word counting works correctly (known content → expected count)
   - Verifies citation validation integrates correctly
   - Does NOT require an LLM call

## Must-Haves

- [ ] Word count logic handles list[BodySection] including subsection content
- [ ] Section count reports H2 count and subsection count
- [ ] Citation coverage logged per-page
- [ ] Metadata total_words uses new structure
- [ ] Test script passes with mock v2 data
  - Estimate: 30m
  - Files: backend/pipeline/test_harness.py, backend/pipeline/test_harness_v2_format.py
  - Verify: cd /home/aux/projects/content-to-kb-automator && python -m pytest backend/pipeline/test_harness_v2_format.py -v && python -m pytest backend/pipeline/test_citation_utils.py -v
