• #OCR
  • #yomitoku
  • #TursoDB
  • #書籍デジタル化
  • #Vue
  • #Claude Code
book-knowledge-base

/restructure-book を Turso API に書き換えて専門書2冊(752チャンク→126セクション)に再構造化を流した

book-knowledge-base リポジトリで、yomitoku が吐き出したページ単位のチャンクを 目次に沿って節レベルへ統合する スラッシュコマンド /restructure-book を、Turso API 対応に書き換えた。旧コマンドはローカル SQLite を better-sqlite3 で直接叩く前提で書かれていて、Embedded Replica + Turso クラウドに移った今のスキーマでは1行も動かなかった。書き換えた後、専門書2冊に通して 752チャンクを126セクションに圧縮(うち626チャンク削除)した。

旧コマンドが Turso では動かなかった

/restructure-book は2026年2月頃に書いたまま放置していて、当時はチャンクをローカルの data/books.db に入れていた。今は Turso のクラウドDBに移したので、旧コマンドの中身を見たら全部ローカルDB前提だった。

// 旧: better-sqlite3 で直接ファイルを開く
const db = new Database('./data/books.db')
db.prepare('SELECT * FROM book_chunks WHERE book_id = ?').all(bookId)

Embedded Replica のクライアント(@libsql/clientcreateClient + syncUrl)に置き換えると、API 形態が Database から Client に変わり、prepare().all()execute({ sql, args }) に変わる。SQL 自体は SQLite 互換なので変えなくてよかったが、呼び出し方が全部変わる

// 新: Embedded Replica クライアント
const client = createClient({ url, syncUrl, authToken })
const { rows } = await client.execute({
  sql: 'SELECT * FROM book_chunks WHERE book_id = ?',
  args: [bookId],
})

書き換え範囲は読み取り(チャンク取得)と書き込み(マージ後の挿入+元チャンク削除)の両方。トランザクション境界も client.transaction('write') に置き換えた。スラッシュコマンドの .md 内に埋め込んだ手順スクリプトを丸ごと一度ゴミ箱に入れて、Turso 用に書き直す形になった。

1冊目: 連結CFマニュアル(341 → 31セクション)

最初に流したのは「連結キャッシュ・フロー計算書の作成マニュアル」、341チャンク。yomitoku がページ単位でチャンクを切っていたので、p.1〜p.341 がそのまま341行ぶんDBに並んでいた。

Step 1〜3 で目次(p.4〜p.14)を読み取って章節構造を把握する。1-11-23-2 といった節番号が拾えたので、節をセクションの単位に決めた。完全性検証スクリプトで「全341チャンクが必ず1つの節に含まれているか」をチェックして、漏れがないことを確認してから Step 4 のマージに進んだ。

[検証OK] 341チャンク → 31セクション(漏れ0、重複0)

Step 4 のマージ実行は10秒くらいで終わり、Step 5 で FTS検索が新セクション粒度で正しく動くか を「連結キャッシュ」で全文検索して確認。検索結果が節単位で返ってきて、マニュアルの該当章にジャンプできる動線になっていた。

翌朝に著者取得リトライ(CAPTCHA で中断していた残444件)を回す予定だったので、引き継ぎプロンプトを memo/2026-04-29/progress.md に書いて1日目のセッションを閉じた。

2冊目: 設例CF Q&A(411 → 95セクション)

午後から「設例 連結キャッシュ・フロー計算書Q&A」(411チャンク)に同じコマンドを流した。こちらは Q1〜Q95 の 問答形式で、Qごとにページ範囲が変わる構造。

ここで yomitoku の OCR 結果に1つ問題が見つかった。Q1-2 の見出しを OCR が拾えていなかった。p.15 から始まるはずなのに、目次から推定したページとチャンク冒頭の見出しが噛み合わない。「Q1-2 が抜けている」と思ってチャンク本体を p.15 から目視で読んだら、Q1-2 の本文自体は普通に入っていて、ただ見出し行が画像認識から落ちていただけだった。

[OCR崩れ] Q1-2 見出し欠損 → p.15 本文を目視確認 → セクション境界を手動で指定

セクション境界を Q1-1 → p.13、Q1-2 → p.15 と手で書き戻して、マージスクリプトを流した。411チャンク → 95セクション、316チャンク削除

蔵書カラムに「整」「済」を並べて進捗を一目で読めるようにした

2冊の再構造化が終わったところで、ユーザーから「書籍ページの蔵書カラムに進捗が出てほしい」と要望が来た。/books の URL を見ると cleanup_status のタグ付き書籍が緑バッジで出ているのに、restructure_status 側が UI に出ていない。

要望を整理すると4つあった。

  • consolidated-cash-flow-manual に「済」タグを付与
  • 蔵書カラムで「整」(黄色: cleanup_status)と「済」(緑: restructure_status)を区別表示
  • 書籍ページに /shelf への動線を右寄せで追加(本棚アイコン風)
  • /restructure-book コマンドの末尾に「済」タグ自動付与処理を組み込み

cleanup_status バッジは既に実装されていて、books テーブルに DB 列があった。restructure_status は DB 列がまだなくて、/api/books 側で data/restructure-history/{bookId}.md の存在チェックで動的に判定する形にした。これで履歴ファイルが生成された瞬間に蔵書一覧へバッジが反映される。

<!-- 蔵書カラム: 整(黄)と 済(緑)を縦に並べる -->
<div class="status-badges">
  <span v-if="book.cleanup_status === 'done'" class="badge yellow">整</span>
  <span v-if="book.restructure_status === 'done'" class="badge green">済</span>
</div>

書籍ページ右上には本棚アイコン付きで /shelf リンクを置いた。蔵書一覧へすぐ戻れる動線が消えていたので、左側の蔵書カウンタと対称になる位置に右寄せした。

/shelf 画面のカバー画像にも「済」バッジを左上に乗せた

/shelf を再読み込みしたら、連結CFマニュアルのカードに「済」バッジが出ていなかった。実装を見たら 著者がNULL の本ではバッジ表示自体が消える ロジックになっていた。著者バッジと「済」バッジを同じ条件分岐に入れていたのが原因。

カバー画像の左上に白文字 + 緑背景でバッジを乗せる形に書き直して、著者の有無に依存しないよう分岐を分けた。これで著者未取得の本でも進捗だけは読める。

3冊目は OCR マージ失敗で保留

ユーザーから「『この取引でB/S・P/L はどう動く? 財務数値への影響がわかるケース100』も処理してほしい」と追加依頼が来たが、bs-pl-impact-100-cases(180チャンク)に /restructure-book を流したところで止まった。

タイトルは「ケース100」なのにケース41あたりから始まっていて、ケース1〜40がDBに入っていない。yomitoku の OCR を上下分割で回した時のマージ失敗が原因だった。下半分のチャンクだけが残って、上半分が捨てられていた。

ユーザーが「分割マージを失敗したやつだから、とりあえずいいです」と判断したので、この本は 再OCR待ちで再構造化を保留した。/restructure-book は完全なチャンクが揃っている本でしか動かない。

学び

  • スラッシュコマンドはDB API変更に弱い: ローカルDB前提のコードを .md に埋め込んでいると、DB引っ越しで全文書き直しになる。Turso クライアントの呼び出し API 差分は prepare/allexecute({sql, args}) 1パターンだったので、半日で書き換えが終わった
  • OCR の見出し欠損はチャンク本体を目視で読むまで気付けない: Q1-2 の見出しが抜けていた件は、目次の推定ページと本文冒頭が噛み合わないことから露見した。セクション統合スクリプトの完全性検証で「未割り当てチャンクが0」を確認するチェックが効いた
  • 進捗バッジは履歴ファイルの存在で動的判定にする: DB列を増やすと migrations が要るが、data/restructure-history/{bookId}.md の存在チェックなら API 側だけで完結する。バッジが付くタイミング = 履歴ファイル生成時、で揃う
  • 著者NULL でも進捗バッジは出す: 蔵書カバーのバッジ表示条件に著者の有無を混ぜると、未取得の本がUI上で消える。状態バッジと著者バッジは別レイヤーに分ける
  • OCR マージ失敗は再構造化の前段で気付ける: チャンク数が想定の半分以下、開始ページがおかしい、という兆候で再OCR を判断できる。/restructure-book を入口の検証ステップとしても使える

次にやりたいこと

  • 著者取得リトライの残444件を CAPTCHA クールダウン明けに再実行する
  • bs-pl-impact-100-cases を yomitoku の上下分割マージから流し直して再OCR、その後 /restructure-book を再実行する
  • 再構造化済み2冊の節セクションに対して、書籍横断のFTS検索(章タイトル + 節本文)を /shelf 検索ボックスから引けるようにする
  • 残りの蔵書(税効果会計の教科書、連結精算表の入門書など)を順次 /restructure-book に流して「済」バッジを揃える