feat: opt-in stat conversion wizard (backend + frontend) (#145) #211

Merged
claude-bot merged 2 commits from feat/145-conversion-wizard into feat/v3.9-game-aware-systems 2026-07-18 18:50:50 +00:00
Contributor

The final issue of the v3.9.0 Game-Aware Systems pillar. Targets the integration branch. Builds on #139 + #140 (+ #135, #142). Backend + frontend in two commits.

Closes #145.

What

A GM-driven, fully reversible wizard that backfills structured stats onto existing NPC/creature entries by LLM-mapping their existing free-form data — append-only, reviewed through the existing draft rail.

Backend

  • New convert_stats draft mode (migration e9f0a1b2c3d4: enum value via Alembic autocommit_block + lore_entry_drafts.current_stats_notes). audio_service.convert_lore_entry_stats maps body + loose sidebar_fields → schema-validated values (map, not invent; per-field confidence/source), validated via #139, retry-once then fail (no free-form fallback). Reuses #142's current_stats staging + approval path — approval writes only entry.stats; the original prose and sidebar rows are never rewritten (verified append-only).
  • GET .../wiki/convert-stats/queue (GM-only) lists eligible entries (linked schema-bearing system, covers type, no stats yet).
  • Latent #139 fix: LoreEntry/LoreEntryVersion.stats now use none_as_null=True so Python None → SQL NULL (not JSON 'null'), which the queue's stats IS NULL filter exposed; also fixes #140's "delete stat block" round-trip.

Frontend

  • CampaignDetail: a GM-only "Stat conversion" wizard card (shown when a schema-bearing system is linked) — fetches the queue, starts a convert_stats draft per entry, navigates to review.
  • WikiDraftReview: a convert_stats branch showing the prose read-only (unchanged) + the proposed mapped stat block with schema labels and per-field confidence · source; approve writes stats append-only. Other draft modes untouched.

Verification (Docker)

  • Backend: 654 tests pass (py3.12; +new test_convert_stats.py) — no regressions. Migration e9f0a1b2c3d4 up/down/up clean (enum value + notes column present).
  • Frontend: 338 tests pass (32 files; new wizard + convert-review suites) + production build succeeds (node:20-alpine).

Notes / small follow-ups

  • Step 1 (linking a system) is #135's selector; unlink/dormant-stats rollback is handled by #133/#140. patch_draft doesn't yet accept inline current_stats/value edits during review (same as #142) — a candidate follow-up. No bot API change; BOT_CONTRACT_VERSION stays 1.

🤖 Generated with Claude Code

The final issue of the **v3.9.0 Game-Aware Systems** pillar. Targets the integration branch. Builds on #139 + #140 (+ #135, #142). Backend + frontend in two commits. Closes #145. ## What A GM-driven, fully reversible wizard that backfills structured `stats` onto existing NPC/creature entries by **LLM-mapping their existing free-form data** — append-only, reviewed through the existing draft rail. **Backend** - New `convert_stats` draft mode (migration `e9f0a1b2c3d4`: enum value via Alembic `autocommit_block` + `lore_entry_drafts.current_stats_notes`). `audio_service.convert_lore_entry_stats` maps `body` + loose `sidebar_fields` → schema-validated `values` (**map, not invent**; per-field confidence/source), validated via #139, retry-once then fail (no free-form fallback). Reuses #142's `current_stats` staging + approval path — approval writes **only** `entry.stats`; the original prose and sidebar rows are never rewritten (verified append-only). - `GET .../wiki/convert-stats/queue` (GM-only) lists eligible entries (linked schema-bearing system, covers type, no stats yet). - **Latent #139 fix**: `LoreEntry`/`LoreEntryVersion.stats` now use `none_as_null=True` so Python `None` → SQL `NULL` (not JSON `'null'`), which the queue's `stats IS NULL` filter exposed; also fixes #140's "delete stat block" round-trip. **Frontend** - CampaignDetail: a GM-only "Stat conversion" wizard card (shown when a schema-bearing system is linked) — fetches the queue, starts a `convert_stats` draft per entry, navigates to review. - WikiDraftReview: a `convert_stats` branch showing the prose read-only (unchanged) + the proposed mapped stat block with schema labels and per-field `confidence · source`; approve writes stats append-only. Other draft modes untouched. ## Verification (Docker) - **Backend: 654 tests pass** (py3.12; +new `test_convert_stats.py`) — no regressions. Migration `e9f0a1b2c3d4` up/down/up clean (enum value + notes column present). - **Frontend: 338 tests pass** (32 files; new wizard + convert-review suites) + production build succeeds (`node:20-alpine`). ## Notes / small follow-ups - Step 1 (linking a system) is #135's selector; unlink/dormant-stats rollback is handled by #133/#140. `patch_draft` doesn't yet accept inline `current_stats`/value edits during review (same as #142) — a candidate follow-up. No bot API change; `BOT_CONTRACT_VERSION` stays 1. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Add a convert_stats draft mode that LLM-maps an entry's existing free-form data
(body + loose sidebar_fields) into schema-validated structured stats for GM
review, entering the existing draft rail. Approval writes only entry.stats
(append-only — original prose and sidebar rows untouched), reusing #142's
current_stats staging + approval path.

- New LoreDraftMode.convert_stats + lore_entry_drafts.current_stats_notes
  (review-only per-field confidence/source). Migration e9f0a1b2c3d4 adds the
  enum value (via Alembic autocommit_block) + the notes column.
- audio_service.convert_lore_entry_stats: map-not-invent prompt (emit values
  only for stats present in the source), per-field confidence/source, validate
  via #139, retry once, else fail (no free-form fallback — structured mapping
  is the point).
- draft_service.start_convert_stats_draft (title/body/sidebar unchanged); worker
  branch requires a covering schema; approval reuses #142's stats write.
- Router: convert_stats gate (npc/creature + schema coverage) + a GM-only
  "review all" queue endpoint (GET .../wiki/convert-stats/queue).
- Fix a latent #139 storage bug the queue's `stats IS NULL` filter exposed:
  LoreEntry/LoreEntryVersion.stats now use none_as_null=True so a Python None is
  stored as SQL NULL rather than the JSON scalar 'null' (also fixes #140's
  "delete stat block" round-trip).

Verified in Docker (py3.12): 654 tests pass; migration up/down/up clean (enum
value + notes column). No bot API change; BOT_CONTRACT_VERSION stays 1.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- CampaignDetail: a GM-only "Stat conversion" wizard card (shown when the
  campaign links a schema-bearing system) that fetches the eligible-entry queue
  and starts a convert_stats draft per entry, navigating to draft review.
- WikiDraftReview: a convert_stats branch that shows the article prose read-only
  (unchanged) and the proposed mapped stat block from current_stats.values with
  per-field confidence/source (from current_stats_notes), schema-labelled;
  approve writes stats append-only. Other draft modes untouched.
- api: fetchConvertStatsQueue; startLoreDraft forwards mode "convert_stats".
- Scope the #135 Linked-badge test to the unique "Linked" badge (the wizard now
  also renders the system name on screen).

Verified in Docker (node:20-alpine): 338 frontend tests pass; production build
succeeds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
claude-bot merged commit 6dcd704fc4 into feat/v3.9-game-aware-systems 2026-07-18 18:50:50 +00:00
Sign in to join this conversation.
No description provided.