Skip to content

Enrich consensus with voted_tokens + build per-page HTML fragments #30

Description

@Arkoniak

What and why

The current consensus output (body_text) is a flat word stream — all structural information
(paragraph boundaries, heading labels, inline emphasis) is discarded after voting. To hand useful
content to Pandoc we need a richer intermediate: a per-token list with voted attributes that an
HTML assembler can turn into proper markup.

Changes to consensus.py

Token enrichment:

  • emphasis: bool → str | None — distinguish 'italic' / 'bold' / 'bold_italic' / None
    (Surya: <i>/<b> HTML; Qwen3-VL: *…* italic, **…** bold)
  • Add paragraph_start: boolTrue for the first token of each source block

Voting additions (same participation rules as existing emphasis voting):

  • paragraph_start — only block-capable models vote (Surya + Unlimited grounding); naïve majority
  • emphasis type — only emphasis-capable models vote (Surya + Qwen3-VL); naïve majority
  • Conflicts flagged in disagreements entries (same pattern as existing disagreement log)

Output: add voted_tokens to per-page JSON alongside existing body_text (backward compat):

"voted_tokens": [
  {"text": "Chapter", "label": "title",  "emphasis": null,     "paragraph_start": true},
  {"text": "One",     "label": "title",  "emphasis": null,     "paragraph_start": false},
  {"text": "He",      "label": "text",   "emphasis": null,     "paragraph_start": true},
  {"text": "said",    "label": "text",   "emphasis": null,     "paragraph_start": false},
  {"text": "nothing", "label": "text",   "emphasis": "italic", "paragraph_start": false}
]

New script: build_html.py

Reads voted_tokens from all consensus pages in order; outputs book.html (or chapters/*.html).

Label normalization (Surya/Unlimited → canonical):

Surya Unlimited Canonical
Title / SectionHeader title title / heading
Text text text
PageHeader / PageFooter header / footer (skipped)
Picture image (raster placeholder)
Caption caption

HTML mapping:

  • title<h1>, heading<h2>, text<p>
  • caption<figcaption> inside <figure>
  • picture<figure><img src="…"></figure> (path from Unlimited's extracted raster)
  • emphasis: 'italic'<em>, 'bold'<strong>, 'bold_italic'<strong><em>

Cross-page stitching (replaces the plain-text stitch.py approach from #20): detect open
paragraphs at page N/N+1 boundary using the same word-split / paragraph-continuation heuristics,
but now operating on the structured token stream — much cleaner because headers are already
gone and we know the label of the last/first block.

Naïve-first policy

For paragraph_start and label voting, ship the simplest working implementation and mark
disagreements in the output. Audit visually (same process as the content-disagreement audit).
Refine only after seeing real failure modes.

Downstream dependencies

This issue is a prerequisite for:

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions